라이브 체류중 누적 배지 표시
This commit is contained in:
@@ -72,7 +72,7 @@ Naver Map에는 단일 Polyline 중심의 부드러운 경로선을 먼저 그
|
|||||||
|
|
||||||
집 판정은 설정된 서울 집과 목포 집 좌표를 중심점으로 한 150m 반경을 기준으로 합니다. GPS 튐 보정이 켜져 있으면 두 집 주변 1.2km 안의 지점은 가까운 집 위치로 흡수하고, 외출은 연속된 외부 지점이 5분 이상 지속될 때만 세션으로 확정합니다. 외부 지점 사이의 공백이 2시간을 넘으면 해당 구간은 알 수 없는 구간으로 보고 외출 지속시간에 합산하지 않습니다. 여러 번 나갔다 돌아온 경우 집 체류 시간은 합산하고, 외출은 출발/복귀 단위의 세션으로 따로 보여줍니다.
|
집 판정은 설정된 서울 집과 목포 집 좌표를 중심점으로 한 150m 반경을 기준으로 합니다. GPS 튐 보정이 켜져 있으면 두 집 주변 1.2km 안의 지점은 가까운 집 위치로 흡수하고, 외출은 연속된 외부 지점이 5분 이상 지속될 때만 세션으로 확정합니다. 외부 지점 사이의 공백이 2시간을 넘으면 해당 구간은 알 수 없는 구간으로 보고 외출 지속시간에 합산하지 않습니다. 여러 번 나갔다 돌아온 경우 집 체류 시간은 합산하고, 외출은 출발/복귀 단위의 세션으로 따로 보여줍니다.
|
||||||
|
|
||||||
기록 시간 슬라이더를 움직이면 지도 아래에는 `월. 일. 시간` 형식의 선택 시각만 간결하게 표시합니다. 경로 재생은 얼굴 아이콘 마커만 사용하며, 재생 중에는 현재 위치 얼굴과 정확도 원을 숨깁니다. 재생 버튼은 지점 수에 따라 의미 있는 배속만 남겨 `2x`, `5x`, `10x`, `20x`, `50x`, `100x`, `500x` 순서로 돌고, 각 배속에서 최소 80단계 이상 보여줄 수 없으면 해당 배속은 건너뜁니다. 100m 반경 안에서 10분 이상 머문 구간은 체류 지점 마커로 묶어 표시하되, 서울 집과 목포 집 안의 체류는 계산하거나 표시하지 않습니다. 같은 위치의 체류는 하나의 마커로 합산하고, 클릭 시 총 체류 시간과 방문별 시작/종료/체류 시간을 확인할 수 있습니다.
|
기록 시간 슬라이더를 움직이면 지도 아래에는 `월. 일. 시간` 형식의 선택 시각만 간결하게 표시합니다. 경로 재생은 얼굴 아이콘 마커만 사용하며, 재생 중에는 현재 위치 얼굴과 정확도 원을 숨깁니다. 재생 버튼은 지점 수에 따라 의미 있는 배속만 남겨 `2x`, `5x`, `10x`, `20x`, `50x`, `100x`, `500x` 순서로 돌고, 각 배속에서 최소 80단계 이상 보여줄 수 없으면 해당 배속은 건너뜁니다. 100m 반경 안에서 10분 이상 머문 구간은 체류 지점 마커로 묶어 표시하되, 서울 집과 목포 집 안의 체류는 계산하거나 표시하지 않습니다. 같은 위치의 체류는 하나의 마커로 합산하고, 클릭 시 총 체류 시간과 방문별 시작/종료/체류 시간을 확인할 수 있습니다. 라이브 위치가 기존 체류 지점 100m 안에 다시 들어오면 초상화 위를 가리지 않는 커스텀 배지로 누적 체류중 시간을 표시합니다.
|
||||||
|
|
||||||
재생 중 주기적 위치 갱신으로 히스토리 경로가 다시 그려져도 현재 재생 위치를 유지합니다. 재생 타이머는 시작 당시 지점 목록에 고정하지 않고 최신 히스토리 지점 목록을 기준으로 계속 진행합니다.
|
재생 중 주기적 위치 갱신으로 히스토리 경로가 다시 그려져도 현재 재생 위치를 유지합니다. 재생 타이머는 시작 당시 지점 목록에 고정하지 않고 최신 히스토리 지점 목록을 기준으로 계속 진행합니다.
|
||||||
|
|
||||||
|
|||||||
+109
@@ -433,6 +433,9 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
|
|||||||
trackLines: [],
|
trackLines: [],
|
||||||
smallMarkers: [],
|
smallMarkers: [],
|
||||||
stayMarkers: [],
|
stayMarkers: [],
|
||||||
|
currentStayMarker: null,
|
||||||
|
currentStayTimer: null,
|
||||||
|
currentStayState: null,
|
||||||
historyStartMarker: null,
|
historyStartMarker: null,
|
||||||
historyEndMarker: null,
|
historyEndMarker: null,
|
||||||
playbackMarker: null,
|
playbackMarker: null,
|
||||||
@@ -2233,6 +2236,103 @@ function createFaceMarkerIcon(size = 60) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createCurrentStayIcon(text) {
|
||||||
|
return {
|
||||||
|
content: `<div class="current-stay-badge-wrap"><div class="current-stay-badge">${text}</div></div>`,
|
||||||
|
anchor: new naver.maps.Point(0, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentStayText(seconds) {
|
||||||
|
const duration = formatDuration(Math.round(seconds));
|
||||||
|
return langCode === 'ko' ? `누적 ${duration} 체류중` : `${duration} staying`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopCurrentStayBadge() {
|
||||||
|
if (mapState.currentStayTimer) {
|
||||||
|
clearInterval(mapState.currentStayTimer);
|
||||||
|
mapState.currentStayTimer = null;
|
||||||
|
}
|
||||||
|
if (mapState.currentStayMarker) {
|
||||||
|
mapState.currentStayMarker.setMap(null);
|
||||||
|
}
|
||||||
|
mapState.currentStayState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentStaySegmentStart(stay, currentPoint) {
|
||||||
|
const points = mapState.historyPoints || [];
|
||||||
|
let startTime = currentPoint.time || new Date().toISOString();
|
||||||
|
for (let i = points.length - 1; i >= 0; i--) {
|
||||||
|
const point = points[i];
|
||||||
|
if (distanceMeters(stay, point) > STAY_GROUP_RADIUS_M) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startTime = point.time || startTime;
|
||||||
|
}
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentStayStateFor(point) {
|
||||||
|
if (!mapState.map || !hasValidCoords(point.lat, point.lng) || !mapState.stayPoints?.length) return null;
|
||||||
|
const stay = mapState.stayPoints.find(item => distanceMeters(item, point) <= STAY_GROUP_RADIUS_M);
|
||||||
|
if (!stay) return null;
|
||||||
|
|
||||||
|
const visits = Array.isArray(stay.visits) ? stay.visits : [];
|
||||||
|
const segmentStart = currentStaySegmentStart(stay, point);
|
||||||
|
const segmentStartTs = toTimestamp(segmentStart) || toTimestamp(point.time) || Date.now();
|
||||||
|
const pointTs = toTimestamp(point.time) || Date.now();
|
||||||
|
const lastVisit = visits.at(-1);
|
||||||
|
const latestVisitIsCurrent = lastVisit
|
||||||
|
&& Math.abs((toTimestamp(lastVisit.endTime) || 0) - pointTs) <= 60000
|
||||||
|
&& distanceMeters(stay, point) <= STAY_GROUP_RADIUS_M;
|
||||||
|
const baseDuration = Math.max(0, Number(stay.duration) || 0) - (latestVisitIsCurrent ? Math.max(0, Number(lastVisit.duration) || 0) : 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
latlng: point.latlng || new naver.maps.LatLng(point.lat, point.lng),
|
||||||
|
baseDuration,
|
||||||
|
startedAt: segmentStartTs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCurrentStayBadge() {
|
||||||
|
const state = mapState.currentStayState;
|
||||||
|
if (!state || !mapState.currentStayMarker) return;
|
||||||
|
const elapsed = Math.max(0, (Date.now() - state.startedAt) / 1000);
|
||||||
|
const text = currentStayText(state.baseDuration + elapsed);
|
||||||
|
mapState.currentStayMarker.setIcon(createCurrentStayIcon(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentStayBadge(point) {
|
||||||
|
if (mapState.playbackTimer || _overrideRange) {
|
||||||
|
stopCurrentStayBadge();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = currentStayStateFor(point);
|
||||||
|
if (!state) {
|
||||||
|
stopCurrentStayBadge();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapState.currentStayState = state;
|
||||||
|
if (!mapState.currentStayMarker) {
|
||||||
|
mapState.currentStayMarker = new naver.maps.Marker({
|
||||||
|
position: state.latlng,
|
||||||
|
map: mapState.map,
|
||||||
|
zIndex: 130,
|
||||||
|
icon: createCurrentStayIcon(currentStayText(state.baseDuration))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mapState.currentStayMarker.setMap(mapState.map);
|
||||||
|
mapState.currentStayMarker.setPosition(state.latlng);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCurrentStayBadge();
|
||||||
|
if (!mapState.currentStayTimer) {
|
||||||
|
mapState.currentStayTimer = setInterval(renderCurrentStayBadge, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createMainMarker(latlng) {
|
function createMainMarker(latlng) {
|
||||||
mapState.marker = new naver.maps.Marker({
|
mapState.marker = new naver.maps.Marker({
|
||||||
position: latlng,
|
position: latlng,
|
||||||
@@ -2266,6 +2366,7 @@ function setCurrentLocationVisible(visible) {
|
|||||||
const map = visible ? mapState.map : null;
|
const map = visible ? mapState.map : null;
|
||||||
mapState.marker?.setMap?.(map);
|
mapState.marker?.setMap?.(map);
|
||||||
mapState.circle?.setMap?.(map);
|
mapState.circle?.setMap?.(map);
|
||||||
|
if (!visible) stopCurrentStayBadge();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyMarkerEffects(marker) {
|
function applyMarkerEffects(marker) {
|
||||||
@@ -2321,6 +2422,7 @@ function bounceMarker(marker) {
|
|||||||
|
|
||||||
function resetHistoryLayer() {
|
function resetHistoryLayer() {
|
||||||
stopHistoryPlayback();
|
stopHistoryPlayback();
|
||||||
|
stopCurrentStayBadge();
|
||||||
clearTrackLines();
|
clearTrackLines();
|
||||||
if (mapState.smallMarkers?.length) {
|
if (mapState.smallMarkers?.length) {
|
||||||
mapState.smallMarkers.forEach(m => m.setMap?.(null));
|
mapState.smallMarkers.forEach(m => m.setMap?.(null));
|
||||||
@@ -2479,6 +2581,7 @@ function updateDOMWithData(data) {
|
|||||||
}
|
}
|
||||||
updateConfiguredHome(data) || updateHomeCenter(data.latitude, data.longitude);
|
updateConfiguredHome(data) || updateHomeCenter(data.latitude, data.longitude);
|
||||||
setCurrentLocationVisible(false);
|
setCurrentLocationVisible(false);
|
||||||
|
stopCurrentStayBadge();
|
||||||
clearCurrentRangeCircles();
|
clearCurrentRangeCircles();
|
||||||
|
|
||||||
// 히스토리 궤적/포인트 업데이트
|
// 히스토리 궤적/포인트 업데이트
|
||||||
@@ -2530,6 +2633,12 @@ function updateDOMWithData(data) {
|
|||||||
lastLat = data.latitude;
|
lastLat = data.latitude;
|
||||||
lastLng = data.longitude;
|
lastLng = data.longitude;
|
||||||
lastUpdateISOTime = data.updated;
|
lastUpdateISOTime = data.updated;
|
||||||
|
updateCurrentStayBadge({
|
||||||
|
lat: data.latitude,
|
||||||
|
lng: data.longitude,
|
||||||
|
latlng,
|
||||||
|
time: data.updated || new Date().toISOString(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function animateCircleRadius(targetRadius) {
|
function animateCircleRadius(targetRadius) {
|
||||||
|
|||||||
Reference in New Issue
Block a user