Enhance find my device map controls
This commit is contained in:
+368
-43
@@ -181,6 +181,17 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
|
||||
historyStart: "시작",
|
||||
historyEnd: "종료",
|
||||
historyPoint: "기록 지점",
|
||||
historyStay: "체류",
|
||||
historyStops: "체류 지점",
|
||||
historyFiltered: "GPS 튐 보정",
|
||||
historyTimeline: "시간 위치",
|
||||
speedLegend: "속도",
|
||||
speedStill: "정지",
|
||||
speedWalk: "도보",
|
||||
speedDrive: "차량",
|
||||
currentRadius: "현재 반경",
|
||||
openNaverMap: "Naver 지도",
|
||||
selectedPoint: "선택 지점",
|
||||
},
|
||||
en: {
|
||||
blog: "Blog",
|
||||
@@ -363,6 +374,17 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
|
||||
historyStart: "Start",
|
||||
historyEnd: "End",
|
||||
historyPoint: "History Point",
|
||||
historyStay: "Stay",
|
||||
historyStops: "Stops",
|
||||
historyFiltered: "GPS Filter",
|
||||
historyTimeline: "Timeline",
|
||||
speedLegend: "Speed",
|
||||
speedStill: "Still",
|
||||
speedWalk: "Walk",
|
||||
speedDrive: "Drive",
|
||||
currentRadius: "Current Radius",
|
||||
openNaverMap: "Naver Map",
|
||||
selectedPoint: "Selected Point",
|
||||
},
|
||||
},
|
||||
mapState = {
|
||||
@@ -370,13 +392,21 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
|
||||
marker: null,
|
||||
circle: null,
|
||||
trackLine: null,
|
||||
trackLines: [],
|
||||
smallMarkers: [],
|
||||
stayMarkers: [],
|
||||
historyStartMarker: null,
|
||||
historyEndMarker: null,
|
||||
playbackMarker: null,
|
||||
selectedMarker: null,
|
||||
playbackTimer: null,
|
||||
playbackIndex: 0,
|
||||
rangeCircles: [],
|
||||
rawHistoryPoints: [],
|
||||
historyPoints: [],
|
||||
historyStats: null,
|
||||
stayPoints: [],
|
||||
filterOutliers: true,
|
||||
layers: {},
|
||||
layerActive: {},
|
||||
activeMapType: 'normal',
|
||||
@@ -1079,6 +1109,11 @@ function ensureMapTools() {
|
||||
if (!container || document.getElementById('mapTools')) return;
|
||||
container.classList.add('has-map-tools');
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'mapControlPanel';
|
||||
panel.className = 'map-control-panel settingSection';
|
||||
container.insertAdjacentElement('afterend', panel);
|
||||
|
||||
const tools = document.createElement('div');
|
||||
tools.id = 'mapTools';
|
||||
tools.className = 'map-tools';
|
||||
@@ -1089,8 +1124,10 @@ function ensureMapTools() {
|
||||
<button type="button" class="map-tool" data-map-action="street"></button>
|
||||
<button type="button" class="map-tool" data-map-action="fit"></button>
|
||||
<button type="button" class="map-tool" data-map-action="play"></button>
|
||||
<button type="button" class="map-tool active" data-map-action="filter"></button>
|
||||
<button type="button" class="map-tool" data-map-action="naver"></button>
|
||||
`;
|
||||
container.appendChild(tools);
|
||||
panel.appendChild(tools);
|
||||
|
||||
tools.addEventListener('click', event => {
|
||||
const button = event.target.closest('[data-map-action]');
|
||||
@@ -1102,7 +1139,24 @@ function ensureMapTools() {
|
||||
summary.id = 'historySummaryPanel';
|
||||
summary.className = 'history-summary-panel';
|
||||
summary.hidden = true;
|
||||
container.appendChild(summary);
|
||||
panel.appendChild(summary);
|
||||
|
||||
const timeline = document.createElement('div');
|
||||
timeline.id = 'historyTimelinePanel';
|
||||
timeline.className = 'history-timeline-panel';
|
||||
timeline.hidden = true;
|
||||
timeline.innerHTML = `
|
||||
<div class="history-timeline-head">
|
||||
<strong id="historyTimelineTitle"></strong>
|
||||
<span id="historyTimelineValue">-</span>
|
||||
</div>
|
||||
<input id="historyTimelineSlider" type="range" min="0" max="0" value="0" step="1">
|
||||
`;
|
||||
panel.appendChild(timeline);
|
||||
|
||||
timeline.querySelector('#historyTimelineSlider')?.addEventListener('input', event => {
|
||||
moveSelectedHistoryPoint(Number(event.target.value), true);
|
||||
});
|
||||
updateMapToolLabels();
|
||||
}
|
||||
|
||||
@@ -1115,12 +1169,17 @@ function updateMapToolLabels() {
|
||||
street: t.mapStreet,
|
||||
fit: t.mapFit,
|
||||
play: mapState.playbackTimer ? t.mapPause : t.mapPlay,
|
||||
filter: t.historyFiltered,
|
||||
naver: t.openNaverMap,
|
||||
};
|
||||
document.querySelectorAll('[data-map-action]').forEach(button => {
|
||||
const action = button.dataset.mapAction;
|
||||
button.textContent = labelMap[action] || action;
|
||||
button.title = labelMap[action] || action;
|
||||
});
|
||||
document.querySelector('[data-map-action="filter"]')?.classList.toggle('active', mapState.filterOutliers);
|
||||
const timelineTitle = document.getElementById('historyTimelineTitle');
|
||||
if (timelineTitle) timelineTitle.textContent = t.historyTimeline;
|
||||
}
|
||||
|
||||
function handleMapTool(action) {
|
||||
@@ -1152,6 +1211,18 @@ function handleMapTool(action) {
|
||||
}
|
||||
if (action === 'play') {
|
||||
toggleHistoryPlayback();
|
||||
return;
|
||||
}
|
||||
if (action === 'filter') {
|
||||
mapState.filterOutliers = !mapState.filterOutliers;
|
||||
if (mapState.rawHistoryPoints?.length) {
|
||||
renderHistoryPoints(mapState.rawHistoryPoints);
|
||||
}
|
||||
updateMapToolLabels();
|
||||
return;
|
||||
}
|
||||
if (action === 'naver') {
|
||||
openSelectedPointInNaverMap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1208,6 +1279,93 @@ function formatSpeed(kmh) {
|
||||
return `${kmh.toFixed(kmh >= 10 ? 1 : 2)}km/h`;
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
const ts = toTimestamp(value);
|
||||
if (!ts) return '-';
|
||||
return new Date(ts).toLocaleString(langCode === 'ko' ? 'ko-KR' : 'en-US', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
function speedColor(kmh) {
|
||||
if (!Number.isFinite(kmh) || kmh < 2) return '#8e8e93';
|
||||
if (kmh < 8) return '#34c759';
|
||||
if (kmh < 35) return '#ff9500';
|
||||
return '#ff3b30';
|
||||
}
|
||||
|
||||
function speedLabel(kmh) {
|
||||
const t = translations[langCode] || translations.ko;
|
||||
if (!Number.isFinite(kmh) || kmh < 2) return t.speedStill;
|
||||
if (kmh < 8) return t.speedWalk;
|
||||
return t.speedDrive;
|
||||
}
|
||||
|
||||
function filterHistoryOutliers(points) {
|
||||
if (!mapState.filterOutliers || points.length < 3) return points;
|
||||
const filtered = [points[0]];
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const prev = filtered[filtered.length - 1];
|
||||
const cur = points[i];
|
||||
const seconds = Math.max(1, (toTimestamp(cur.time) - toTimestamp(prev.time)) / 1000);
|
||||
const distance = distanceMeters(prev, cur);
|
||||
const kmh = distance / seconds * 3.6;
|
||||
if (distance > 2000 && kmh > 180) continue;
|
||||
filtered.push(cur);
|
||||
}
|
||||
return filtered.length >= 2 ? filtered : points;
|
||||
}
|
||||
|
||||
function buildStayPoints(points) {
|
||||
const stays = [];
|
||||
let start = 0;
|
||||
const radius = 80;
|
||||
const minSeconds = 10 * 60;
|
||||
|
||||
for (let i = 1; i <= points.length; i++) {
|
||||
const base = points[start];
|
||||
const current = points[i];
|
||||
const outside = !current || distanceMeters(base, current) > radius;
|
||||
if (!outside) continue;
|
||||
|
||||
const end = points[Math.max(start, i - 1)];
|
||||
const duration = (toTimestamp(end.time) - toTimestamp(base.time)) / 1000;
|
||||
if (duration >= minSeconds) {
|
||||
const slice = points.slice(start, i);
|
||||
const lat = slice.reduce((sum, p) => sum + Number(p.lat), 0) / slice.length;
|
||||
const lng = slice.reduce((sum, p) => sum + Number(p.lng), 0) / slice.length;
|
||||
stays.push({
|
||||
lat,
|
||||
lng,
|
||||
latlng: new naver.maps.LatLng(lat, lng),
|
||||
startTime: base.time,
|
||||
endTime: end.time,
|
||||
duration,
|
||||
points: slice.length,
|
||||
});
|
||||
}
|
||||
start = Math.max(0, i - 1);
|
||||
}
|
||||
|
||||
return stays;
|
||||
}
|
||||
|
||||
function openPointInNaverMap(point) {
|
||||
if (!point || !hasValidCoords(point.lat, point.lng)) return;
|
||||
const lat = Number(point.lat);
|
||||
const lng = Number(point.lng);
|
||||
window.open(`https://map.naver.com/v5/search/${lat},${lng}`, '_blank', 'noopener');
|
||||
}
|
||||
|
||||
function openSelectedPointInNaverMap() {
|
||||
const point = mapState.historyPoints?.[mapState.playbackIndex] ||
|
||||
(hasValidCoords(lastLat, lastLng) ? { lat: lastLat, lng: lastLng } : null);
|
||||
openPointInNaverMap(point);
|
||||
}
|
||||
|
||||
function buildHistoryStats(points) {
|
||||
if (!Array.isArray(points) || points.length < 2) return null;
|
||||
let distance = 0;
|
||||
@@ -1226,6 +1384,7 @@ function buildHistoryStats(points) {
|
||||
maxGap,
|
||||
points: points.length,
|
||||
averageSpeed: duration > 0 ? distance / duration * 3.6 : 0,
|
||||
stops: mapState.stayPoints?.length || 0,
|
||||
startedAt: points[0].time,
|
||||
endedAt: points.at(-1).time,
|
||||
};
|
||||
@@ -1250,6 +1409,14 @@ function renderHistorySummary() {
|
||||
<span>${t.historyAverageSpeed}</span><strong>${formatSpeed(stats.averageSpeed)}</strong>
|
||||
<span>${t.historyMaxGap}</span><strong>${formatDuration(Math.round(stats.maxGap))}</strong>
|
||||
<span>${t.historyPoints}</span><strong>${formatNumber(stats.points)}</strong>
|
||||
<span>${t.historyStops}</span><strong>${formatNumber(stats.stops || 0)}</strong>
|
||||
<span>${t.currentRadius}</span><strong>500m / 1km</strong>
|
||||
</div>
|
||||
<div class="speed-legend">
|
||||
<span><i style="--legend-color:#8e8e93"></i>${t.speedStill}</span>
|
||||
<span><i style="--legend-color:#34c759"></i>${t.speedWalk}</span>
|
||||
<span><i style="--legend-color:#ff9500"></i>${t.speedDrive}</span>
|
||||
<span><i style="--legend-color:#ff3b30"></i>35km/h+</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -1266,8 +1433,21 @@ function buildHistoryInfoContent(point, type, label) {
|
||||
return `
|
||||
<div class="history-info-window history-info-${typeClass}">
|
||||
<strong>${title}</strong>
|
||||
<span>${getRelativeTime(point.time)}</span>
|
||||
<span>${formatDateTime(point.time)} · ${getRelativeTime(point.time)}</span>
|
||||
<span>${t.historyDistance}: ${index > 0 ? formatDistance(fromStart) : '-'}</span>
|
||||
<span>${Number(point.lat).toFixed(6)}, ${Number(point.lng).toFixed(6)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function buildStayInfoContent(stay) {
|
||||
const t = translations[langCode] || translations.ko;
|
||||
return `
|
||||
<div class="history-info-window history-info-stay">
|
||||
<strong>${t.historyStay}</strong>
|
||||
<span>${formatDuration(Math.round(stay.duration))}</span>
|
||||
<span>${formatDateTime(stay.startTime)} - ${formatDateTime(stay.endTime)}</span>
|
||||
<span>${Number(stay.lat).toFixed(6)}, ${Number(stay.lng).toFixed(6)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -1322,13 +1502,109 @@ function toggleHistoryPlayback() {
|
||||
|
||||
mapState.playbackTimer = setInterval(() => {
|
||||
const point = points[index];
|
||||
mapState.playbackMarker.setPosition(point.latlng);
|
||||
moveSelectedHistoryPoint(index, false);
|
||||
if (index % 8 === 0) mapState.map.panTo(point.latlng);
|
||||
index += 1;
|
||||
if (index >= points.length) stopHistoryPlayback();
|
||||
}, Math.max(120, Math.min(450, 10000 / points.length)));
|
||||
updateMapToolLabels();
|
||||
}
|
||||
|
||||
function clearTrackLines() {
|
||||
if (mapState.trackLine) {
|
||||
mapState.trackLine.setMap(null);
|
||||
mapState.trackLine = null;
|
||||
}
|
||||
if (mapState.trackLines?.length) {
|
||||
mapState.trackLines.forEach(line => line.setMap?.(null));
|
||||
mapState.trackLines = [];
|
||||
}
|
||||
}
|
||||
|
||||
function drawSpeedTrack(points) {
|
||||
clearTrackLines();
|
||||
if (!points?.length || points.length < 2) return;
|
||||
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const prev = points[i - 1];
|
||||
const cur = points[i];
|
||||
const seconds = Math.max(1, (toTimestamp(cur.time) - toTimestamp(prev.time)) / 1000);
|
||||
const kmh = distanceMeters(prev, cur) / seconds * 3.6;
|
||||
const line = new naver.maps.Polyline({
|
||||
map: mapState.map,
|
||||
path: [prev.latlng, cur.latlng],
|
||||
strokeColor: speedColor(kmh),
|
||||
strokeOpacity: 0.88,
|
||||
strokeWeight: 4,
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
endIcon: i === points.length - 1 ? naver.maps.PointingIcon?.BLOCK_ARROW : undefined,
|
||||
endIconSize: 16,
|
||||
clickable: true
|
||||
});
|
||||
line.segmentSpeed = kmh;
|
||||
naver.maps.Event.addListener(line, 'click', () => {
|
||||
const panel = document.getElementById('historySummaryPanel');
|
||||
if (panel) panel.hidden = !panel.hidden;
|
||||
});
|
||||
mapState.trackLines.push(line);
|
||||
}
|
||||
mapState.trackLine = mapState.trackLines.at(-1) || null;
|
||||
}
|
||||
|
||||
function updateTimelinePanel() {
|
||||
const panel = document.getElementById('historyTimelinePanel');
|
||||
const slider = document.getElementById('historyTimelineSlider');
|
||||
if (!panel || !slider) return;
|
||||
const points = mapState.historyPoints || [];
|
||||
if (!points.length) {
|
||||
panel.hidden = true;
|
||||
return;
|
||||
}
|
||||
panel.hidden = false;
|
||||
slider.max = String(points.length - 1);
|
||||
slider.value = String(Math.min(mapState.playbackIndex || 0, points.length - 1));
|
||||
moveSelectedHistoryPoint(Number(slider.value), false, false);
|
||||
}
|
||||
|
||||
function moveSelectedHistoryPoint(index, pan = false, syncSlider = true) {
|
||||
const points = mapState.historyPoints || [];
|
||||
if (!points.length) return;
|
||||
const safeIndex = Math.max(0, Math.min(points.length - 1, Number.isFinite(index) ? index : 0));
|
||||
const point = points[safeIndex];
|
||||
mapState.playbackIndex = safeIndex;
|
||||
|
||||
if (!mapState.selectedMarker) {
|
||||
mapState.selectedMarker = new naver.maps.Marker({
|
||||
position: point.latlng,
|
||||
map: mapState.map,
|
||||
zIndex: 115,
|
||||
icon: {
|
||||
content: '<div class="history-selected-marker"></div>',
|
||||
anchor: new naver.maps.Point(10, 10)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mapState.selectedMarker.setMap(mapState.map);
|
||||
mapState.selectedMarker.setPosition(point.latlng);
|
||||
}
|
||||
if (mapState.playbackMarker) {
|
||||
mapState.playbackMarker.setPosition(point.latlng);
|
||||
}
|
||||
if (pan) mapState.map.panTo(point.latlng);
|
||||
|
||||
const value = document.getElementById('historyTimelineValue');
|
||||
if (value) {
|
||||
const prev = points[Math.max(0, safeIndex - 1)];
|
||||
const seconds = safeIndex > 0 ? Math.max(1, (toTimestamp(point.time) - toTimestamp(prev.time)) / 1000) : 0;
|
||||
const kmh = safeIndex > 0 ? distanceMeters(prev, point) / seconds * 3.6 : 0;
|
||||
value.textContent = `${formatDateTime(point.time)} · ${speedLabel(kmh)} ${formatSpeed(kmh)}`;
|
||||
}
|
||||
if (syncSlider) {
|
||||
const slider = document.getElementById('historyTimelineSlider');
|
||||
if (slider) slider.value = String(safeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function createMainMarker(latlng) {
|
||||
mapState.marker = new naver.maps.Marker({
|
||||
@@ -1343,18 +1619,44 @@ function createMainMarker(latlng) {
|
||||
applyMarkerEffects(mapState.marker);
|
||||
}
|
||||
|
||||
function createAccuracyCircle(latlng, accuracy = 50) {
|
||||
mapState.circle = new naver.maps.Circle({
|
||||
map: mapState.map,
|
||||
center: latlng,
|
||||
function createAccuracyCircle(latlng, accuracy = 50) {
|
||||
mapState.circle = new naver.maps.Circle({
|
||||
map: mapState.map,
|
||||
center: latlng,
|
||||
radius: Math.max(accuracy, 10),
|
||||
strokeColor: '#007AFF',
|
||||
strokeOpacity: 0.8,
|
||||
strokeWeight: 2,
|
||||
fillColor: '#007AFF',
|
||||
fillOpacity: 0.2
|
||||
});
|
||||
}
|
||||
fillOpacity: 0.2
|
||||
});
|
||||
updateCurrentRangeCircles(latlng);
|
||||
}
|
||||
|
||||
function updateCurrentRangeCircles(latlng) {
|
||||
if (!mapState.map || !latlng) return;
|
||||
const ranges = [
|
||||
{ radius: 500, strokeColor: '#34c759', fillColor: '#34c759' },
|
||||
{ radius: 1000, strokeColor: '#ff9500', fillColor: '#ff9500' },
|
||||
];
|
||||
|
||||
ranges.forEach((range, index) => {
|
||||
if (!mapState.rangeCircles[index]) {
|
||||
mapState.rangeCircles[index] = new naver.maps.Circle({
|
||||
map: mapState.map,
|
||||
center: latlng,
|
||||
radius: range.radius,
|
||||
strokeColor: range.strokeColor,
|
||||
strokeOpacity: 0.45,
|
||||
strokeWeight: 1,
|
||||
fillColor: range.fillColor,
|
||||
fillOpacity: 0.035
|
||||
});
|
||||
} else {
|
||||
mapState.rangeCircles[index].setCenter(latlng);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyMarkerEffects(marker) {
|
||||
naver.maps.Event.addListener(marker, 'mouseover', () => {
|
||||
@@ -1409,14 +1711,15 @@ function bounceMarker(marker) {
|
||||
|
||||
function resetHistoryLayer() {
|
||||
stopHistoryPlayback();
|
||||
if (mapState.trackLine) {
|
||||
mapState.trackLine.setMap(null);
|
||||
mapState.trackLine = null;
|
||||
}
|
||||
clearTrackLines();
|
||||
if (mapState.smallMarkers?.length) {
|
||||
mapState.smallMarkers.forEach(m => m.setMap?.(null));
|
||||
mapState.smallMarkers = [];
|
||||
}
|
||||
if (mapState.stayMarkers?.length) {
|
||||
mapState.stayMarkers.forEach(m => m.setMap?.(null));
|
||||
mapState.stayMarkers = [];
|
||||
}
|
||||
if (mapState.historyStartMarker) {
|
||||
mapState.historyStartMarker.setMap(null);
|
||||
mapState.historyStartMarker = null;
|
||||
@@ -1432,9 +1735,15 @@ function resetHistoryLayer() {
|
||||
if (mapState.playbackMarker) {
|
||||
mapState.playbackMarker.setMap(null);
|
||||
}
|
||||
if (mapState.selectedMarker) {
|
||||
mapState.selectedMarker.setMap(null);
|
||||
}
|
||||
mapState.rawHistoryPoints = [];
|
||||
mapState.historyPoints = [];
|
||||
mapState.historyStats = null;
|
||||
mapState.stayPoints = [];
|
||||
renderHistorySummary();
|
||||
updateTimelinePanel();
|
||||
mapState.initialInfoOpened = false;
|
||||
mapState.historyBoundsFitted = false;
|
||||
}
|
||||
@@ -1458,39 +1767,29 @@ function updateHistoryMarkers(history) {
|
||||
.map(pos => ({ ...pos, latlng: new naver.maps.LatLng(pos.lat, pos.lng) }));
|
||||
|
||||
if (!points.length) return;
|
||||
mapState.historyPoints = points;
|
||||
mapState.rawHistoryPoints = points;
|
||||
renderHistoryPoints(points);
|
||||
}
|
||||
|
||||
if (mapState.trackLine) mapState.trackLine.setMap(null);
|
||||
mapState.trackLine = new naver.maps.Polyline({
|
||||
map: mapState.map,
|
||||
path: points.map(pos => pos.latlng),
|
||||
strokeColor: '#007AFF',
|
||||
strokeOpacity: 0.85,
|
||||
strokeWeight: 4,
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
endIcon: naver.maps.PointingIcon?.BLOCK_ARROW,
|
||||
endIconSize: 16,
|
||||
clickable: true
|
||||
});
|
||||
function renderHistoryPoints(rawPoints) {
|
||||
if (!mapState.map || !rawPoints?.length) return;
|
||||
const points = filterHistoryOutliers(rawPoints);
|
||||
mapState.historyPoints = points;
|
||||
mapState.playbackIndex = 0;
|
||||
drawSpeedTrack(points);
|
||||
mapState.stayPoints = buildStayPoints(points);
|
||||
mapState.historyStats = buildHistoryStats(points);
|
||||
if (mapState.trackLine.getDistance && mapState.historyStats) {
|
||||
const lineDistance = mapState.trackLine.getDistance();
|
||||
if (Number.isFinite(lineDistance) && lineDistance > 0) {
|
||||
mapState.historyStats.distance = lineDistance;
|
||||
mapState.historyStats.averageSpeed = mapState.historyStats.duration > 0 ? lineDistance / mapState.historyStats.duration * 3.6 : 0;
|
||||
}
|
||||
}
|
||||
renderHistorySummary();
|
||||
naver.maps.Event.addListener(mapState.trackLine, 'click', () => {
|
||||
const panel = document.getElementById('historySummaryPanel');
|
||||
if (panel) panel.hidden = !panel.hidden;
|
||||
});
|
||||
updateTimelinePanel();
|
||||
|
||||
if (mapState.smallMarkers?.length) {
|
||||
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);
|
||||
if (mapState.historyEndMarker) mapState.historyEndMarker.setMap(null);
|
||||
|
||||
@@ -1521,6 +1820,8 @@ function updateHistoryMarkers(history) {
|
||||
} else {
|
||||
marker.infoWindow.open(mapState.map, marker);
|
||||
mapState.openInfoWindow = marker.infoWindow;
|
||||
const idx = mapState.historyPoints.findIndex(item => item.time === pos.time && item.lat === pos.lat && item.lng === pos.lng);
|
||||
if (idx >= 0) moveSelectedHistoryPoint(idx, false);
|
||||
}
|
||||
});
|
||||
return marker;
|
||||
@@ -1538,6 +1839,29 @@ function updateHistoryMarkers(history) {
|
||||
mapState.smallMarkers.push(makeMarker(pos));
|
||||
});
|
||||
|
||||
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)
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
||||
if (!mapState.historyBoundsFitted) {
|
||||
fitHistoryOrCurrentBounds();
|
||||
mapState.historyBoundsFitted = true;
|
||||
@@ -1601,9 +1925,10 @@ function updateDOMWithData(data) {
|
||||
}
|
||||
|
||||
// 메인 마커 / 정확도 원 업데이트
|
||||
mapState.marker.setPosition(latlng);
|
||||
mapState.circle.setCenter(latlng);
|
||||
animateCircleRadius(Math.max(data.accuracy || 50, 10));
|
||||
mapState.marker.setPosition(latlng);
|
||||
mapState.circle.setCenter(latlng);
|
||||
animateCircleRadius(Math.max(data.accuracy || 50, 10));
|
||||
updateCurrentRangeCircles(latlng);
|
||||
|
||||
// autoPosition
|
||||
const isChecked = document.getElementById('autoPositionSwitch').checked;
|
||||
|
||||
Reference in New Issue
Block a user