#49 feat: separacion de marcadores de provenance escalada por zoom en el mapa

This commit is contained in:
2026-08-16 04:19:17 -05:00
parent 18026617ce
commit a251ae59eb
3 changed files with 97 additions and 9 deletions

View File

@@ -67,6 +67,36 @@
return [Number(current.latitude), Number(current.longitude)]
})
const MIN_PIXEL_GAP = 40
function degreesPerPixel (zoom) {
return 360 / (256 * Math.pow(2, zoom))
}
function groupedPositionKeys () {
const counts = new Map()
for (const markerData of markers.value) {
const key = markerData.basePosition.join(',')
counts.set(key, (counts.get(key) || 0) + 1)
}
return new Set(
[...counts.entries()].filter(entry => entry[1] > 1).map(entry => entry[0])
)
}
function positionAtZoom (markerData, zoom) {
const key = markerData.basePosition.join(',')
if (!groupedPositionKeys().has(key)) return markerData.basePosition
const group = markers.value.filter(m => m.basePosition.join(',') === key)
const index = group.findIndex(m => m === markerData)
const angle = (index / group.length) * Math.PI * 2
const offset = MIN_PIXEL_GAP * degreesPerPixel(zoom)
return [
markerData.basePosition[0] + Math.cos(angle) * offset,
markerData.basePosition[1] + Math.sin(angle) * offset,
]
}
const markers = computed(() => {
const result = []
for (const entry of props.provenance || []) {
@@ -77,7 +107,7 @@
result.push({
product: entry.product,
relation: rel,
position: [Number(municipality.latitude), Number(municipality.longitude)],
basePosition: [Number(municipality.latitude), Number(municipality.longitude)],
})
}
}
@@ -126,29 +156,37 @@
storeMarker = L.marker(storePosition.value, { icon: personIcon }).addTo(map)
storeMarker.bindTooltip('Tienda')
storeMarker.on('mouseover', () => {
markers.value.forEach(markerData => showLine(markerData.position, storePosition.value))
productMarkers.forEach(item => showLine(item.marker.getLatLng(), storePosition.value))
})
storeMarker.on('mouseout', clearLines)
}
function addProductMarkers () {
const zoom = map.getZoom()
markers.value.forEach(markerData => {
const marker = L.marker(markerData.position, { icon: productIcon(markerData) }).addTo(map)
const marker = L.marker(positionAtZoom(markerData, zoom), { icon: productIcon(markerData) }).addTo(map)
marker.bindTooltip(markerData.product.name)
marker.on('click', () => openDialog(markerData))
marker.on('mouseover', () => {
if (storePosition.value) showLine(markerData.position, storePosition.value)
if (storePosition.value) showLine(marker.getLatLng(), storePosition.value)
})
marker.on('mouseout', clearLines)
productMarkers.push(marker)
productMarkers.push({ marker, data: markerData })
})
}
function onZoomEnd () {
const zoom = map.getZoom()
productMarkers.forEach(item => {
item.marker.setLatLng(positionAtZoom(item.data, zoom))
})
}
function fitBounds () {
const bounds = L.latLngBounds()
markers.value.forEach(markerData => bounds.extend(markerData.position))
markers.value.forEach(markerData => bounds.extend(markerData.basePosition))
if (storePosition.value) bounds.extend(storePosition.value)
if (bounds.isValid()) map.fitBounds(bounds, { padding: [40, 40] })
if (bounds.isValid()) map.fitBounds(bounds, { padding: [60, 60] })
}
function openDialog (markerData) {
@@ -159,7 +197,7 @@
function initMap () {
if (!mapEl.value || map) return
const center = storePosition.value || markers.value[0].position
const center = storePosition.value || markers.value[0].basePosition
map = L.map(mapEl.value, { scrollWheelZoom: false }).setView(center, 6)
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
@@ -167,6 +205,7 @@
}).addTo(map)
addStoreMarker()
addProductMarkers()
map.on('zoomend', onZoomEnd)
fitBounds()
}