add spider cluster
This commit is contained in:
parent
952cd40efe
commit
1b428cbf78
3 changed files with 109 additions and 6 deletions
|
|
@ -20,8 +20,7 @@ import "phoenix_html";
|
||||||
// Establish Phoenix Socket and LiveView configuration.
|
// Establish Phoenix Socket and LiveView configuration.
|
||||||
import { Socket } from "phoenix";
|
import { Socket } from "phoenix";
|
||||||
import { LiveSocket } from "phoenix_live_view";
|
import { LiveSocket } from "phoenix_live_view";
|
||||||
import topbar from "../vendor/topbar"
|
import topbar from "../vendor/topbar";
|
||||||
import "../vendor/oms.min.js";
|
|
||||||
|
|
||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
|
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
|
||||||
|
|
||||||
|
|
|
||||||
108
assets/js/map.ts
108
assets/js/map.ts
|
|
@ -12,6 +12,7 @@ import type { Map as LeafletMap, Marker, TileLayer, LayerGroup, DivIcon, LatLngB
|
||||||
// Declare Leaflet as a global variable with proper typing
|
// Declare Leaflet as a global variable with proper typing
|
||||||
declare const L: typeof import('leaflet') & {
|
declare const L: typeof import('leaflet') & {
|
||||||
heatLayer?: (latlngs: Array<[number, number, number?]>, options?: any) => any;
|
heatLayer?: (latlngs: Array<[number, number, number?]>, options?: any) => any;
|
||||||
|
markerClusterGroup?: (options?: any) => any;
|
||||||
};
|
};
|
||||||
declare const OverlappingMarkerSpiderfier: any;
|
declare const OverlappingMarkerSpiderfier: any;
|
||||||
|
|
||||||
|
|
@ -211,7 +212,43 @@ let MapAPRSMap = {
|
||||||
|
|
||||||
// Store markers for management
|
// Store markers for management
|
||||||
self.markers = new Map<string, any>();
|
self.markers = new Map<string, any>();
|
||||||
self.markerLayer = L.layerGroup().addTo(self.map);
|
|
||||||
|
// Create marker cluster group for medium zoom levels (9-14)
|
||||||
|
if (L.markerClusterGroup) {
|
||||||
|
self.markerClusterGroup = L.markerClusterGroup({
|
||||||
|
showCoverageOnHover: false,
|
||||||
|
zoomToBoundsOnClick: true,
|
||||||
|
spiderfyOnMaxZoom: true,
|
||||||
|
removeOutsideVisibleBounds: true,
|
||||||
|
disableClusteringAtZoom: 11, // Show individual markers at zoom 11+
|
||||||
|
maxClusterRadius: 80,
|
||||||
|
iconCreateFunction: function(cluster: any) {
|
||||||
|
const count = cluster.getChildCount();
|
||||||
|
let size = 'small';
|
||||||
|
let className = 'marker-cluster-small';
|
||||||
|
|
||||||
|
if (count > 10) {
|
||||||
|
size = 'medium';
|
||||||
|
className = 'marker-cluster-medium';
|
||||||
|
}
|
||||||
|
if (count > 50) {
|
||||||
|
size = 'large';
|
||||||
|
className = 'marker-cluster-large';
|
||||||
|
}
|
||||||
|
|
||||||
|
return L.divIcon({
|
||||||
|
html: '<div><span>' + count + '</span></div>',
|
||||||
|
className: 'marker-cluster ' + className,
|
||||||
|
iconSize: [40, 40]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.markerClusterGroup.addTo(self.map);
|
||||||
|
self.markerLayer = self.markerClusterGroup;
|
||||||
|
} else {
|
||||||
|
// Fallback to regular layer group if clustering not available
|
||||||
|
self.markerLayer = L.layerGroup().addTo(self.map);
|
||||||
|
}
|
||||||
|
|
||||||
// Create heat layer (hidden by default)
|
// Create heat layer (hidden by default)
|
||||||
try {
|
try {
|
||||||
|
|
@ -332,6 +369,21 @@ let MapAPRSMap = {
|
||||||
const currentZoom = self.map!.getZoom();
|
const currentZoom = self.map!.getZoom();
|
||||||
const zoomDifference = self.lastZoom ? Math.abs(currentZoom - self.lastZoom) : 0;
|
const zoomDifference = self.lastZoom ? Math.abs(currentZoom - self.lastZoom) : 0;
|
||||||
|
|
||||||
|
// Handle OMS markers when crossing zoom threshold
|
||||||
|
if (self.oms) {
|
||||||
|
if (currentZoom >= 11 && (!self.lastZoom || self.lastZoom < 11)) {
|
||||||
|
// We just zoomed in past clustering threshold, add all markers to OMS
|
||||||
|
self.markers.forEach((marker) => {
|
||||||
|
if (marker && !marker._isHistorical) {
|
||||||
|
self.oms.addMarker(marker);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (currentZoom < 11 && self.lastZoom && self.lastZoom >= 11) {
|
||||||
|
// We just zoomed out below clustering threshold, remove all markers from OMS
|
||||||
|
self.oms.clearMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If zoom changed significantly (more than 2 levels), request reload of normal markers
|
// If zoom changed significantly (more than 2 levels), request reload of normal markers
|
||||||
// but preserve historical ones and current position
|
// but preserve historical ones and current position
|
||||||
if (zoomDifference > 2) {
|
if (zoomDifference > 2) {
|
||||||
|
|
@ -380,8 +432,28 @@ let MapAPRSMap = {
|
||||||
// Set up event delegation for popup navigation links
|
// Set up event delegation for popup navigation links
|
||||||
self.setupPopupNavigation();
|
self.setupPopupNavigation();
|
||||||
|
|
||||||
|
// Only initialize OMS if clustering is not available or at high zoom levels
|
||||||
if (typeof OverlappingMarkerSpiderfier !== "undefined") {
|
if (typeof OverlappingMarkerSpiderfier !== "undefined") {
|
||||||
self.oms = new OverlappingMarkerSpiderfier(self.map);
|
self.oms = new OverlappingMarkerSpiderfier(self.map, {
|
||||||
|
keepSpiderfied: true,
|
||||||
|
nearbyDistance: 40,
|
||||||
|
circleSpiralSwitchover: 9,
|
||||||
|
legWeight: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add click handler for spiderfied markers
|
||||||
|
self.oms.addListener('click', function(marker: any) {
|
||||||
|
if (marker.openPopup) marker.openPopup();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Style the spider legs
|
||||||
|
self.oms.addListener('spiderfy', function(markers: any) {
|
||||||
|
self.map.closePopup();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.oms.addListener('unspiderfy', function(markers: any) {
|
||||||
|
// Markers return to normal positions
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1071,6 +1143,7 @@ let MapAPRSMap = {
|
||||||
lng: lng,
|
lng: lng,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Mark historical markers for identification
|
// Mark historical markers for identification
|
||||||
if (data.historical) {
|
if (data.historical) {
|
||||||
|
|
@ -1116,7 +1189,8 @@ let MapAPRSMap = {
|
||||||
marker.openPopup();
|
marker.openPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.oms && marker) {
|
// Only add to OMS if we're at zoom level 11+ (when clustering is disabled)
|
||||||
|
if (self.oms && marker && self.map && self.map.getZoom() >= 11) {
|
||||||
self.oms.addMarker(marker);
|
self.oms.addMarker(marker);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1142,7 +1216,11 @@ let MapAPRSMap = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.oms && marker) {
|
if (self.oms && marker) {
|
||||||
self.oms.removeMarker(marker);
|
try {
|
||||||
|
self.oms.removeMarker(marker);
|
||||||
|
} catch (e) {
|
||||||
|
console.debug("Error removing marker from OMS:", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1476,6 +1554,19 @@ let MapAPRSMap = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up OMS (OverlappingMarkerSpiderfier)
|
||||||
|
if (self.oms !== undefined) {
|
||||||
|
try {
|
||||||
|
self.oms.clearMarkers();
|
||||||
|
self.oms.clearListeners('click');
|
||||||
|
self.oms.clearListeners('spiderfy');
|
||||||
|
self.oms.clearListeners('unspiderfy');
|
||||||
|
self.oms = undefined;
|
||||||
|
} catch (e) {
|
||||||
|
console.debug("Error cleaning up OMS:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up heat layer
|
// Clean up heat layer
|
||||||
if (self.heatLayer !== undefined && self.map !== undefined) {
|
if (self.heatLayer !== undefined && self.map !== undefined) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1490,6 +1581,15 @@ let MapAPRSMap = {
|
||||||
|
|
||||||
if (self.markerLayer !== undefined) {
|
if (self.markerLayer !== undefined) {
|
||||||
self.markerLayer!.clearLayers();
|
self.markerLayer!.clearLayers();
|
||||||
|
// If it's a cluster group, remove it from the map
|
||||||
|
if (self.markerClusterGroup !== undefined && self.map !== undefined) {
|
||||||
|
try {
|
||||||
|
self.map.removeLayer(self.markerClusterGroup);
|
||||||
|
self.markerClusterGroup = undefined;
|
||||||
|
} catch (e) {
|
||||||
|
console.debug("Error removing cluster group:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (self.markers !== undefined) {
|
if (self.markers !== undefined) {
|
||||||
self.markers!.clear();
|
self.markers!.clear();
|
||||||
|
|
|
||||||
|
|
@ -704,6 +704,10 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
</script>
|
</script>
|
||||||
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js">
|
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js">
|
||||||
</script>
|
</script>
|
||||||
|
<script
|
||||||
|
src="https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier-Leaflet/0.2.6/oms.min.js"
|
||||||
|
>
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#aprs-map {
|
#aprs-map {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue