체류 마커 줌별 묶음과 위치 갱신 순서 정리

This commit is contained in:
seo
2026-07-12 16:27:09 +09:00
parent 534bc379b5
commit fa8159db11
2 changed files with 88 additions and 40 deletions
+87 -39
View File
@@ -488,6 +488,7 @@ const CURRENT_STAY_BADGE_WIDTH = 140;
const CURRENT_STAY_BADGE_HEIGHT = 28;
const CURRENT_STAY_BADGE_GAP = 8;
const CURRENT_STAY_MIN_ZOOM = 13;
const STAY_CLUSTER_MIN_ZOOM = 13;
const FALLBACK_BACK_TARGET = 'https://chaegeon.com/';
const BACK_HISTORY_KEY = 'chaegeon.backHistory.v1';
@@ -1426,6 +1427,7 @@ function initMap(latlng, accuracy = 50) {
createAccuracyCircle(latlng, accuracy);
updateHomeCircles();
naver.maps.Event.addListener(mapState.map, 'zoom_changed', () => {
renderStayMarkers();
if (mapState.map.getZoom() < CURRENT_STAY_MIN_ZOOM) {
stopCurrentStayBadge();
setStayMarkersVisibleForCurrentPoint(
@@ -2285,8 +2287,8 @@ function setStayMarkersVisibleForCurrentPoint(point) {
if (!mapState.stayMarkers?.length) return;
const shouldHideCurrent = point && !_overrideRange && !mapState.playbackTimer && hasValidCoords(point.lat, point.lng);
mapState.stayMarkers.forEach(marker => {
const stay = marker.stayPoint;
const hide = shouldHideCurrent && stay && distanceMeters(stay, point) <= STAY_GROUP_RADIUS_M;
const stays = marker.stayPoints || (marker.stayPoint ? [marker.stayPoint] : []);
const hide = shouldHideCurrent && stays.some(stay => distanceMeters(stay, point) <= STAY_GROUP_RADIUS_M);
marker.setMap(hide ? null : mapState.map);
});
}
@@ -2527,6 +2529,82 @@ function applyHistoryCounts(data) {
mapState.historyRenderCount = Number.isFinite(Number(data?.historyRenderCount)) ? Number(data.historyRenderCount) : historyLength;
}
function stayClusterRadiusForZoom(zoom) {
if (zoom >= STAY_CLUSTER_MIN_ZOOM) return 0;
if (zoom <= 8) return 70000;
if (zoom <= 10) return 25000;
return 6000;
}
function buildStayMarkerGroups(stays) {
const zoom = mapState.map?.getZoom?.() || 12;
const radius = stayClusterRadiusForZoom(zoom);
if (!radius) return stays.map(stay => ({ lat: stay.lat, lng: stay.lng, stays: [stay] }));
const groups = [];
stays.forEach(stay => {
const group = groups.find(item => distanceMeters(item, stay) <= radius);
if (group) {
const oldCount = group.stays.length;
const nextCount = oldCount + 1;
group.lat = ((group.lat * oldCount) + Number(stay.lat)) / nextCount;
group.lng = ((group.lng * oldCount) + Number(stay.lng)) / nextCount;
group.stays.push(stay);
} else {
groups.push({ lat: Number(stay.lat), lng: Number(stay.lng), stays: [stay] });
}
});
return groups;
}
function renderStayMarkers() {
if (!mapState.map) return;
if (mapState.stayMarkers?.length) {
mapState.stayMarkers.forEach(marker => marker.setMap(null));
mapState.stayMarkers = [];
}
if (!mapState.stayPoints?.length) return;
buildStayMarkerGroups(mapState.stayPoints).forEach(group => {
const isCluster = group.stays.length > 1;
const duration = group.stays.reduce((sum, stay) => sum + (Number(stay.duration) || 0), 0);
const label = isCluster ? `${formatNumber(group.stays.length)}` : formatDuration(Math.round(duration));
const marker = new naver.maps.Marker({
position: new naver.maps.LatLng(group.lat, group.lng),
map: mapState.map,
zIndex: isCluster ? 75 : 80,
icon: {
content: `<div class="history-marker history-marker-stay">${label}</div>`,
anchor: new naver.maps.Point(26, 14)
}
});
marker.stayPoints = group.stays;
if (isCluster) {
naver.maps.Event.addListener(marker, "click", () => {
mapState.map.panTo(marker.getPosition());
mapState.map.setZoom(Math.max(mapState.map.getZoom() + 2, STAY_CLUSTER_MIN_ZOOM));
});
} else {
const stay = group.stays[0];
marker.infoWindow = new naver.maps.InfoWindow({
content: buildStayInfoContent(stay)
});
marker.stayPoint = stay;
naver.maps.Event.addListener(marker, "click", () => {
if (mapState.openInfoWindow && mapState.openInfoWindow !== marker.infoWindow) {
mapState.openInfoWindow.close();
}
marker.infoWindow.open(mapState.map, marker);
mapState.openInfoWindow = marker.infoWindow;
});
}
mapState.stayMarkers.push(marker);
});
setStayMarkersVisibleForCurrentPoint(
hasValidCoords(lastLat, lastLng) ? { lat: lastLat, lng: lastLng } : null
);
}
function renderHistoryPoints(rawPoints) {
if (!mapState.map || !rawPoints?.length) return;
const keepPlaybackIndex = !!mapState.playbackTimer;
@@ -2547,10 +2625,6 @@ function renderHistoryPoints(rawPoints) {
mapState.smallMarkers.forEach(marker => marker.setMap(null));
mapState.smallMarkers = [];
}
if (mapState.stayMarkers?.length) {
mapState.stayMarkers.forEach(marker => marker.setMap(null));
mapState.stayMarkers = [];
}
if (mapState.historyStartMarker) {
mapState.historyStartMarker.setMap(null);
mapState.historyStartMarker = null;
@@ -2560,32 +2634,7 @@ function renderHistoryPoints(rawPoints) {
mapState.historyEndMarker = null;
}
mapState.stayPoints.forEach(stay => {
const marker = new naver.maps.Marker({
position: stay.latlng,
map: mapState.map,
zIndex: 80,
icon: {
content: `<div class="history-marker history-marker-stay">${formatDuration(Math.round(stay.duration))}</div>`,
anchor: new naver.maps.Point(26, 14)
}
});
marker.infoWindow = new naver.maps.InfoWindow({
content: buildStayInfoContent(stay)
});
marker.stayPoint = stay;
naver.maps.Event.addListener(marker, "click", () => {
if (mapState.openInfoWindow && mapState.openInfoWindow !== marker.infoWindow) {
mapState.openInfoWindow.close();
}
marker.infoWindow.open(mapState.map, marker);
mapState.openInfoWindow = marker.infoWindow;
});
mapState.stayMarkers.push(marker);
});
setStayMarkersVisibleForCurrentPoint(
hasValidCoords(lastLat, lastLng) ? { lat: lastLat, lng: lastLng } : null
);
renderStayMarkers();
if (!mapState.historyBoundsFitted) {
fitHistoryOrCurrentBounds();
@@ -2650,13 +2699,6 @@ function updateDOMWithData(data) {
initMap(latlng, data.accuracy);
}
// 라이브 모드에서도 최신 24시간 기록을 계속 반영한다.
// 현재 위치 자동 이동은 지도 중심만 바꾸므로, 경로와 종료 마커는 여기서 별도로 갱신한다.
if (data.history?.length > 0) {
applyHistoryCounts(data);
updateHistoryMarkers(data.history);
}
// 메인 마커 / 정확도 원 업데이트
const isPlaybackActive = !!mapState.playbackTimer;
setCurrentLocationVisible(!isPlaybackActive);
@@ -2679,6 +2721,12 @@ function updateDOMWithData(data) {
latlng,
time: data.updated || new Date().toISOString(),
};
// 라이브 위치 마커를 먼저 최신화한 뒤 같은 기준으로 경로와 체류 마커를 다시 그린다.
if (data.history?.length > 0) {
applyHistoryCounts(data);
updateHistoryMarkers(data.history);
}
setStayMarkersVisibleForCurrentPoint(currentPoint);
updateCurrentStayBadge(currentPoint);
}