Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

클러스터 알고리즘 테스트, api 경로 수정 #232

Merged
merged 8 commits into from
Dec 3, 2024
2 changes: 1 addition & 1 deletion frontend/src/api/place/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const putPlaceToCourse = async ({
}) => {
const { data } = await axiosInstance.put<CoursePlace[]>(
END_POINTS.PUT_PLACE_TO_COURSE(id),
{ places },
{ pins: places },
);
return { ...data, id };
};
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/components/ImageSkeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,18 @@ const ImageWithSkeleton = ({
height,
}: ImageWithSkeletonProps) => {
const [loaded, setLoaded] = useState(false);
const [showSkeleton, setShowSkeleton] = useState(true);
const [error, setError] = useState(false);

useEffect(() => {
const img = new Image();
img.src = src;

img.onload = () => {
setShowSkeleton(false);
setTimeout(() => {
setLoaded(true);
}, 3000);
setLoaded(true);
};

img.onerror = () => {
setError(true);
setShowSkeleton(false);
};
}, [src]);

Expand All @@ -49,9 +44,15 @@ const ImageWithSkeleton = ({

return (
<div className="h-20 w-20 flex-shrink-0">
{!loaded && <div className="h-full w-full animate-pulse bg-slate-400" />}
{!loaded && (
<div className="h-full w-full animate-pulse rounded bg-c_button_gray"></div>
)}
{loaded && (
<img className="h-full w-full object-cover" src={src} alt={alt} />
<img
className="h-full w-full rounded object-cover"
src={src}
alt={alt}
/>
)}
</div>
);
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/Place/DeletePlaceButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const DeletePlaceButton = ({ placeId, places }: DeletePlaceButtonProps) => {
const putPlaceToCourseMutation = usePutPlaceToCourseMutation();
const addToast = useStore((state) => state.addToast);

const onClickMapMode = () => {
const onClickMapMode = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
deletePlaceMutation.mutate(
{ id, placeId },
{
Expand All @@ -39,7 +40,8 @@ const DeletePlaceButton = ({ placeId, places }: DeletePlaceButtonProps) => {
[places],
);

const onClickCourseMode = () => {
const onClickCourseMode = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
putPlaceToCourseMutation.mutate(
{ id, places: newPlaces },
{
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Place/PlaceListPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ const PlaceListPanel = ({
{places.map((place, index) => (
<Draggable
isDragDisabled={!isDraggable}
key={place.google_place_id.toString()}
draggableId={place.id.toString()}
key={place.id?.toString()}
draggableId={place.id?.toString()}
index={index}
>
{(provided, snapshot) => (
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/constants/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,6 @@ export const USER_ERROR_MESSAGE = {
E303: '해당 장소가 이미 존재합니다.',
E304: '유효하지 않은 지도입니다. 다시 확인해 주세요.',
E201: '요청한 장소를 찾을 수 없습니다.',
E999: '서버에 문제가 발생했습니다. 잠시 후 다시 시도해 주세요.',
E900: '양식이 잘못 되었습니다. 다시 확인해 주세요.',
};
9 changes: 2 additions & 7 deletions frontend/src/hooks/useMarker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ export const useMarker = (props: MarkerProps) => {
newMarker.map = map;
setMarker(newMarker);
addMarker(newMarker);
console.log(newMarker, 'add new marker');

return () => {
newMarker.map = null;
setMarker(null);
google.maps.event.clearInstanceListeners(newMarker);
removeMarker(newMarker);
};
}, [map, order]);
Expand All @@ -90,14 +89,10 @@ export const useMarker = (props: MarkerProps) => {
infoWindow.open({ anchor: marker, map });
moveTo(position?.lat as number, position?.lng as number);
});

google.maps.event.addListener(map, 'click', () => {
infoWindow.close();
});

return () => {
google.maps.event.clearInstanceListeners(marker);
google.maps.event.clearInstanceListeners(map);
};
}, [marker, onClick]);
return marker;
};
Expand Down
73 changes: 69 additions & 4 deletions frontend/src/lib/CustomMarkerClusterer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ClusterStats,
Marker,
MarkerClusterer,
MarkerClustererEvents,
Expand All @@ -17,11 +18,20 @@
this.markerLatLngSet = new Set();
}

public onAdd(): void {
const map = this.getMap();
if (!map) return;
this.idleListener = map.addListener('idle', () => {
this.render();
});
this.render();
}

public addMarker(
marker: google.maps.marker.AdvancedMarkerElement,
noDraw?: boolean,
): void {
const markerLatLng = `${marker.position?.lat}${marker.position?.lng}`;
const markerLatLng = `${marker.position?.lat} ${marker.position?.lng}`;
if (this.markerLatLngSet.has(markerLatLng)) {
return;
}
Expand All @@ -34,6 +44,29 @@
}
}

public removeMarker(
marker: google.maps.marker.AdvancedMarkerElement,
noDraw?: boolean,
): boolean {
const index = this.markers.indexOf(marker);
if (index === -1) {
// Marker is not in our list of markers, so do nothing:
return false;
}

const markerLatLng = `${marker.position?.lat} ${marker.position?.lng}`;
this.markerLatLngSet.delete(markerLatLng);

MarkerUtils.setMap(marker, null);
this.markers.splice(index, 1); // Remove the marker from the list of managed markers

if (!noDraw) {
this.render();
}

return true;
}

public render(): void {
const map = this.getMap();
if (map instanceof google.maps.Map && map.getProjection()) {
Expand All @@ -47,7 +80,7 @@
map,
mapCanvasProjection: this.getProjection(),
});

console.log(changed, 'given changed');

Check warning on line 83 in frontend/src/lib/CustomMarkerClusterer.ts

View workflow job for this annotation

GitHub Actions / Lint, Build and Test (frontend)

Unexpected console statement
// Allow algorithms to return flag on whether the clusters/markers have changed.
if (changed || changed === undefined) {
// Accumulate the markers of the clusters composed of a single marker.
Expand All @@ -71,7 +104,6 @@
// The marker:
// - was previously rendered because it is from a cluster with 1 marker,
// - should no more be rendered as it is not in singleMarker.
console.log('cluster.marker removed', cluster.marker);
MarkerUtils.setMap(cluster.marker!, null);
}
} else {
Expand All @@ -83,7 +115,7 @@
this.clusters = clusters;
this.renderClusters();
// Delayed removal of the markers of the former groups.
console.log('groupMarkers', groupMarkers);

setTimeout(() => {
groupMarkers.forEach((marker) => {
MarkerUtils.setMap(marker, null);
Expand All @@ -97,6 +129,39 @@
);
}
}

protected renderClusters(): void {
// Generate stats to pass to renderers.
const stats = new ClusterStats(this.markers, this.clusters);

const map = this.getMap() as google.maps.Map;

this.clusters.forEach((cluster) => {
if (cluster.markers?.length === 1) {
cluster.marker = cluster.markers[0];
} else {
// Generate the marker to represent the group.
cluster.marker = this.renderer.render(cluster, stats, map);
// Make sure all individual markers are removed from the map.
cluster.markers?.forEach((marker) => MarkerUtils.setMap(marker, null));
if (this.onClusterClick) {
cluster.marker.addListener(
'click',
/* istanbul ignore next */
(event: google.maps.MapMouseEvent) => {
google.maps.event.trigger(
this,
MarkerClustererEvents.CLUSTER_CLICK,
cluster,
);
this.onClusterClick(event, cluster, map);
},
);
}
}
MarkerUtils.setMap(cluster.marker, map);
});
}
}

export const clustererOptions = {
Expand Down
54 changes: 54 additions & 0 deletions frontend/src/lib/CustomSuperCluseterAlgorithm.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,64 @@
import {
AlgorithmInput,
AlgorithmOutput,
getPaddedViewport,
MarkerUtils,
SuperClusterViewportAlgorithm,
SuperClusterViewportOptions,
SuperClusterViewportState,
} from '@googlemaps/markerclusterer';
import equal from 'fast-deep-equal';

export class CustomSuperClusterAlgorithm extends SuperClusterViewportAlgorithm {
constructor({ ...options }: SuperClusterViewportOptions) {
super(options);
this.clusters = [];
}

public calculate(input: AlgorithmInput): AlgorithmOutput {
const state: SuperClusterViewportState = {
zoom: Math.round(input.map.getZoom()!),
view: getPaddedViewport(
input.map.getBounds()!,
input.mapCanvasProjection,
this.viewportPadding,
),
};

// let changed = !equal(this.state, state);
let changed = false;
if (!equal(input.markers, this.markers)) {
// TODO use proxy to avoid copy?
this.markers = [...input.markers];

const points = this.markers.map((marker) => {
const position = MarkerUtils.getPosition(marker);
const coordinates = [position.lng(), position.lat()];
return {
type: 'Feature' as const,
geometry: {
type: 'Point' as const,
coordinates,
},
properties: { marker },
};
});
this.superCluster.load(points);
}

const newClusters = this.cluster(input);
console.log(newClusters.length, this.clusters.length);

Check warning on line 50 in frontend/src/lib/CustomSuperCluseterAlgorithm.ts

View workflow job for this annotation

GitHub Actions / Lint, Build and Test (frontend)

Unexpected console statement
//this.clusters.length !== newClusters.length
//!equal(this.clusters, newClusters)
if (this.clusters.length !== newClusters.length) {
this.clusters = newClusters;
changed = true;
} else {
changed = false;
}

this.state = state;

return { clusters: this.clusters, changed };
}
}
57 changes: 57 additions & 0 deletions frontend/src/lib/SuperClusterAlgorithm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
AlgorithmInput,
AlgorithmOutput,
getPaddedViewport,
MarkerUtils,
SuperClusterViewportAlgorithm,
SuperClusterViewportOptions,
SuperClusterViewportState,
} from '@googlemaps/markerclusterer';
import equal from 'fast-deep-equal';

export class SuperClusterAlgorithmTest extends SuperClusterViewportAlgorithm {
constructor({ ...options }: SuperClusterViewportOptions) {
super(options);
this.clusters = [];
}

public calculate(input: AlgorithmInput): AlgorithmOutput {
const state: SuperClusterViewportState = {
zoom: Math.round(input.map.getZoom()!),
view: getPaddedViewport(
input.map.getBounds()!,
input.mapCanvasProjection,
this.viewportPadding,
),
};

let changed = !equal(this.state, state);
console.log('Origin changed', changed);

Check warning on line 29 in frontend/src/lib/SuperClusterAlgorithm.ts

View workflow job for this annotation

GitHub Actions / Lint, Build and Test (frontend)

Unexpected console statement
if (!equal(input.markers, this.markers)) {
changed = true;
// TODO use proxy to avoid copy?
this.markers = [...input.markers];

const points = this.markers.map((marker) => {
const position = MarkerUtils.getPosition(marker);
const coordinates = [position.lng(), position.lat()];
return {
type: 'Feature' as const,
geometry: {
type: 'Point' as const,
coordinates,
},
properties: { marker },
};
});
this.superCluster.load(points);
}

if (changed) {
this.clusters = this.cluster(input);
this.state = state;
}

return { clusters: this.clusters, changed };
}
}
Loading
Loading