diff --git a/assets/js/app.js b/assets/js/app.js index 3948c69..4ed6428 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -25,7 +25,7 @@ import topbar from "../vendor/topbar"; let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); // Import minimal APRS map hook -import MinimalAPRSMap from "./minimal_map.js"; +import MinimalAPRSMap from "./minimal_map"; // APRS Map Hook let Hooks = {}; diff --git a/assets/js/minimal_map.js b/assets/js/minimal_map.ts similarity index 57% rename from assets/js/minimal_map.js rename to assets/js/minimal_map.ts index d3ae4c7..161aa55 100644 --- a/assets/js/minimal_map.js +++ b/assets/js/minimal_map.ts @@ -1,38 +1,106 @@ +// Declare Leaflet as a global variable +declare const L: any; + // Minimal APRS Map Hook - handles only basic map interaction // All data logic handled by LiveView +type LiveViewHookContext = { + el: HTMLElement & { _leaflet_id?: any }; + pushEvent: (event: string, payload: any) => void; + handleEvent: (event: string, callback: Function) => void; + map?: any; + markers?: Map; + markerStates?: Map; + markerLayer?: any; + boundsTimer?: ReturnType; + resizeHandler?: () => void; + errors?: string[]; + initializationAttempts?: number; + maxInitializationAttempts?: number; + lastZoom?: number; + [key: string]: any; +}; + +interface MarkerData { + id: string; + lat: number; + lng: number; + callsign?: string; + comment?: string; + symbol_table_id?: string; + symbol_code?: string; + symbol_description?: string; + popup?: string; + historical?: boolean; + color?: string; +} + +interface BoundsData { + north: number; + south: number; + east: number; + west: number; +} + +interface CenterData { + lat: number; + lng: number; +} + +interface MarkerState { + lat: number; + lng: number; + symbol_table: string; + symbol_code: string; + popup?: string; + historical?: boolean; +} + +interface MapEventData { + bounds?: BoundsData; + center?: CenterData; + zoom?: number; + id?: string; + callsign?: string; + lat?: number; + lng?: number; + markers?: MarkerData[]; +} + let MinimalAPRSMap = { mounted() { + const self = this as unknown as LiveViewHookContext; // Initialize error tracking - this.errors = []; - this.initializationAttempts = 0; - this.maxInitializationAttempts = 3; + self.errors = []; + self.initializationAttempts = 0; + self.maxInitializationAttempts = 3; - this.attemptInitialization(); + self.attemptInitialization(); }, attemptInitialization() { - this.initializationAttempts++; + const self = this as unknown as LiveViewHookContext; + self.initializationAttempts!++; // Check if Leaflet is available if (typeof L === "undefined") { console.error("Leaflet library not loaded!"); - this.errors.push("Leaflet library not available"); + self.errors!.push("Leaflet library not available"); - if (this.initializationAttempts < this.maxInitializationAttempts) { - setTimeout(() => this.attemptInitialization(), 1000); + if (self.initializationAttempts! < self.maxInitializationAttempts!) { + setTimeout(() => self.attemptInitialization(), 1000); return; } else { - this.handleFatalError("Leaflet library failed to load after multiple attempts"); + self.handleFatalError("Leaflet library failed to load after multiple attempts"); return; } } // Get initial center and zoom from server-provided data attributes - let initialCenter, initialZoom; + let initialCenter: CenterData, initialZoom: number; try { - const centerData = this.el.dataset.center; - const zoomData = this.el.dataset.zoom; + const centerData = self.el.dataset.center; + const zoomData = self.el.dataset.zoom; if (!centerData || !zoomData) { throw new Error("Missing map data attributes"); @@ -61,39 +129,40 @@ let MinimalAPRSMap = { initialZoom = 5; } - this.initializeMap(initialCenter, initialZoom); + self.initializeMap(initialCenter, initialZoom); }, - initializeMap(initialCenter, initialZoom) { + initializeMap(initialCenter: CenterData, initialZoom: number) { + const self = this as unknown as LiveViewHookContext; // Ensure the element and its parent exist - if (!this.el || !this.el.parentNode) { + if (!self.el || !self.el.parentNode) { console.warn("Map element or parent not found, retrying..."); - if (this.initializationAttempts < this.maxInitializationAttempts) { - setTimeout(() => this.attemptInitialization(), 500); + if (self.initializationAttempts! < self.maxInitializationAttempts!) { + setTimeout(() => self.attemptInitialization(), 500); return; } else { - this.handleFatalError("Map element structure invalid"); + self.handleFatalError("Map element structure invalid"); return; } } // Check element dimensions - const rect = this.el.getBoundingClientRect(); + const rect = self.el.getBoundingClientRect(); // Validate element has dimensions if (rect.width === 0 || rect.height === 0) { console.warn("Map element has no dimensions, retrying..."); - this.errors.push("Map element has zero dimensions"); + self.errors!.push("Map element has zero dimensions"); // Try to force dimensions - this.el.style.width = "100%"; - this.el.style.height = "100vh"; + self.el.style.width = "100%"; + self.el.style.height = "100vh"; - if (this.initializationAttempts < this.maxInitializationAttempts) { - setTimeout(() => this.attemptInitialization(), 500); + if (self.initializationAttempts! < self.maxInitializationAttempts!) { + setTimeout(() => self.attemptInitialization(), 500); return; } else { - this.handleFatalError("Map element never gained proper dimensions"); + self.handleFatalError("Map element never gained proper dimensions"); return; } } @@ -101,36 +170,36 @@ let MinimalAPRSMap = { // Initialize basic map try { // Check if map is already initialized - if (this.map) { + if (self.map) { console.warn("Map already exists, reinitializing..."); - this.map.remove(); - this.map = null; + self.map.remove(); + self.map = null; } // Ensure element ID is unique and not already initialized - if (this.el._leaflet_id) { + if (self.el._leaflet_id) { console.warn("Map element already has Leaflet ID, cleaning up..."); - // Remove any existing Leaflet instance - if (L.DomUtil.get(this.el.id)) { - L.DomUtil.remove(L.DomUtil.get(this.el.id)); + const el = L.DomUtil.get(self.el.id) as HTMLElement | null; + if (el !== null) { + L.DomUtil.remove(el); } - delete this.el._leaflet_id; + delete self.el._leaflet_id; } - this.map = L.map(this.el, { + self.map = L.map(self.el, { zoomControl: true, attributionControl: true, closePopupOnClick: true, }).setView([initialCenter.lat, initialCenter.lng], initialZoom); } catch (error) { console.error("Error initializing map:", error); - this.errors.push("Map initialization failed: " + error.message); + self.errors!.push("Map initialization failed: " + (error instanceof Error ? error.message : error)); - if (this.initializationAttempts < this.maxInitializationAttempts) { - setTimeout(() => this.attemptInitialization(), 1000); + if (self.initializationAttempts! < self.maxInitializationAttempts!) { + setTimeout(() => self.attemptInitialization(), 1000); return; } else { - this.handleFatalError("Map initialization failed after multiple attempts"); + self.handleFatalError("Map initialization failed after multiple attempts"); return; } } @@ -142,80 +211,80 @@ let MinimalAPRSMap = { '© OpenStreetMap contributors | APRS.me', maxZoom: 19, }); - tileLayer.addTo(this.map); + tileLayer.addTo(self.map); } catch (error) { - this.errors.push("Tile layer failed: " + error.message); + self.errors!.push("Tile layer failed: " + (error instanceof Error ? error.message : error)); } // Store markers for management - this.markers = new Map(); - this.markerLayer = L.layerGroup().addTo(this.map); + self.markers = new Map(); + self.markerLayer = L.layerGroup().addTo(self.map); // Track marker states to prevent unnecessary operations - this.markerStates = new Map(); + self.markerStates = new Map(); // Force initial size calculation try { - this.map.invalidateSize(); + self.map.invalidateSize(); } catch (error) { console.error("Error invalidating map size:", error); } // Track when map is ready - this.map.whenReady(() => { + self.map!.whenReady(() => { try { - this.lastZoom = this.map.getZoom(); - this.pushEvent("map_ready", {}); - this.sendBoundsToServer(); + self.lastZoom = self.map!.getZoom(); + self.pushEvent("map_ready", {}); + self.sendBoundsToServer(); } catch (error) { console.error("Error in map ready callback:", error); } }); // Send bounds to LiveView when map moves - this.map.on("moveend", () => { - if (this.boundsTimer) clearTimeout(this.boundsTimer); - this.boundsTimer = setTimeout(() => { - this.sendBoundsToServer(); + self.map!.on("moveend", () => { + if (self.boundsTimer) clearTimeout(self.boundsTimer); + self.boundsTimer = setTimeout(() => { + self.sendBoundsToServer(); }, 300); }); // Handle zoom changes with optimization for large zoom differences - this.map.on("zoomend", () => { - if (this.boundsTimer) clearTimeout(this.boundsTimer); - this.boundsTimer = setTimeout(() => { - const currentZoom = this.map.getZoom(); - const zoomDifference = this.lastZoom ? Math.abs(currentZoom - this.lastZoom) : 0; + self.map!.on("zoomend", () => { + if (self.boundsTimer) clearTimeout(self.boundsTimer); + self.boundsTimer = setTimeout(() => { + const currentZoom = self.map!.getZoom(); + const zoomDifference = self.lastZoom ? Math.abs(currentZoom - self.lastZoom) : 0; // If zoom changed significantly (more than 2 levels), clear and reload if (zoomDifference > 2) { - this.pushEvent("clear_and_reload_markers", {}); + self.pushEvent("clear_and_reload_markers", {}); } - this.sendBoundsToServer(); - this.lastZoom = currentZoom; + self.sendBoundsToServer(); + self.lastZoom = currentZoom; }, 300); }); // Handle resize - this.resizeHandler = () => { + self.resizeHandler = () => { try { - if (this.map) { - this.map.invalidateSize(); + if (self.map) { + self.map.invalidateSize(); } } catch (error) { console.error("Error invalidating map size on resize:", error); } }; - window.addEventListener("resize", this.resizeHandler); + window.addEventListener("resize", self.resizeHandler); // Add a delayed size check setTimeout(() => { - if (this.el && this.el.parentNode) { - const rect = this.el.getBoundingClientRect(); - if (this.map && rect.width > 0 && rect.height > 0) { + if (self.el && self.el.parentNode) { + const rect = self.el.getBoundingClientRect(); + if (self.map && rect.width > 0 && rect.height > 0) { try { - this.map.invalidateSize(); + self.map.invalidateSize(); } catch (error) { console.error("Error re-invalidating map size:", error); } @@ -224,16 +293,17 @@ let MinimalAPRSMap = { }, 1000); // LiveView event handlers - this.setupLiveViewHandlers(); + self.setupLiveViewHandlers(); }, - handleFatalError(message) { + handleFatalError(message: string) { + const self = this as unknown as LiveViewHookContext; console.error("Fatal map error:", message); - console.error("All errors:", this.errors); + console.error("All errors:", self.errors); // Display error message to user - if (this.el) { - this.el.innerHTML = ` + if (self.el) { + self.el.innerHTML = `
{ - this.addMarker(data); + self.handleEvent("add_marker", (data: MarkerData) => { + self.addMarker(data); }); // Add multiple markers at once - this.handleEvent("add_markers", (data) => { + self.handleEvent("add_markers", (data: { markers: MarkerData[] }) => { if (data.markers && Array.isArray(data.markers)) { - data.markers.forEach((marker) => this.addMarker(marker)); + data.markers.forEach((marker) => self.addMarker(marker)); } }); // Remove marker - this.handleEvent("remove_marker", (data) => { - this.removeMarker(data.id); + self.handleEvent("remove_marker", (data: { id: string }) => { + self.removeMarker(data.id); }); // Clear all markers - this.handleEvent("clear_markers", () => { - this.clearAllMarkers(); + self.handleEvent("clear_markers", () => { + self.clearAllMarkers(); }); // Update marker - this.handleEvent("update_marker", (data) => { - this.updateMarker(data); + self.handleEvent("update_marker", (data: MarkerData) => { + self.updateMarker(data); }); // Zoom to location - this.handleEvent("zoom_to_location", (data) => { - if (!this.map) { + self.handleEvent("zoom_to_location", (data: { lat: number; lng: number; zoom?: number }) => { + if (!self.map) { console.error("Map not initialized, cannot zoom"); return; } if (data.lat && data.lng) { - const lat = parseFloat(data.lat); - const lng = parseFloat(data.lng); - const zoom = parseInt(data.zoom || 12); + const lat = parseFloat(data.lat.toString()); + const lng = parseFloat(data.lng.toString()); + const zoom = parseInt(data.zoom?.toString() || "12"); // Validate coordinates if (isNaN(lat) || isNaN(lng) || lat < -90 || lat > 90 || lng < -180 || lng > 180) { @@ -310,29 +381,29 @@ let MinimalAPRSMap = { try { // Check element dimensions before zoom - const beforeRect = this.el.getBoundingClientRect(); + const beforeRect = self.el.getBoundingClientRect(); // Force map size recalculation before zoom - this.map.invalidateSize(); + self.map.invalidateSize(); // Use a slight delay to ensure map is ready setTimeout(() => { - if (this.map) { - this.map.setView([lat, lng], zoom, { + if (self.map) { + self.map.setView([lat, lng], zoom, { animate: true, duration: 1, }); // Check element dimensions after zoom setTimeout(() => { - const afterRect = this.el.getBoundingClientRect(); + const afterRect = self.el.getBoundingClientRect(); if (afterRect.width === 0 || afterRect.height === 0) { console.error("Map element lost dimensions after zoom!"); // Try to restore dimensions - this.el.style.width = "100vw"; - this.el.style.height = "100vh"; - this.map.invalidateSize(); + self.el.style.width = "100vw"; + self.el.style.height = "100vh"; + self.map.invalidateSize(); } }, 1000); } @@ -346,92 +417,93 @@ let MinimalAPRSMap = { }); // Handle geolocation requests - this.handleEvent("request_geolocation", () => { + self.handleEvent("request_geolocation", () => { if ("geolocation" in navigator) { navigator.geolocation.getCurrentPosition( (position) => { const { latitude, longitude } = position.coords; - this.pushEvent("set_location", { lat: latitude, lng: longitude }); + self.pushEvent("set_location", { lat: latitude, lng: longitude }); }, (error) => { console.warn("Geolocation error:", error.message); - this.pushEvent("geolocation_error", { error: error.message }); + self.pushEvent("geolocation_error", { error: error.message }); }, ); } else { console.warn("Geolocation not available"); - this.pushEvent("geolocation_error", { error: "Geolocation not supported" }); + self.pushEvent("geolocation_error", { error: "Geolocation not supported" }); } }); // Handle new packets from LiveView - this.handleEvent("new_packet", (data) => { - this.addMarker({ + self.handleEvent("new_packet", (data: MarkerData) => { + self.addMarker({ ...data, historical: false, - popup: this.buildPopupContent(data), + popup: self.buildPopupContent(data), }); }); // Handle historical packets during replay - this.handleEvent("historical_packet", (data) => { - this.addMarker({ + self.handleEvent("historical_packet", (data: MarkerData) => { + self.addMarker({ ...data, historical: true, - popup: this.buildPopupContent(data), + popup: self.buildPopupContent(data), }); }); // Handle refresh markers event - this.handleEvent("refresh_markers", () => { + self.handleEvent("refresh_markers", () => { // Remove markers that are outside current bounds - if (this.map) { - const bounds = this.map.getBounds(); - this.removeMarkersOutsideBounds(bounds); + if (self.map) { + const bounds = self.map.getBounds(); + self.removeMarkersOutsideBounds(bounds); } }); // Handle clearing historical packets - this.handleEvent("clear_historical_packets", () => { + self.handleEvent("clear_historical_packets", () => { // Remove all historical markers - const markersToRemove = []; - this.markers.forEach((marker, id) => { - if (marker._isHistorical) { + const markersToRemove: string[] = []; + self.markers!.forEach((marker: any, id: any) => { + if ((marker as any)._isHistorical) { markersToRemove.push(id); } }); - markersToRemove.forEach((id) => this.removeMarker(id)); + markersToRemove.forEach((id) => self.removeMarker(id)); }); // Handle bounds-based marker filtering - this.handleEvent("filter_markers_by_bounds", (data) => { + self.handleEvent("filter_markers_by_bounds", (data: { bounds: BoundsData }) => { if (data.bounds) { // Create Leaflet bounds object from server data const bounds = L.latLngBounds( [data.bounds.south, data.bounds.west], [data.bounds.north, data.bounds.east], ); - this.removeMarkersOutsideBounds(bounds); + self.removeMarkersOutsideBounds(bounds); } }); // Handle clearing all markers and reloading visible ones - this.handleEvent("clear_and_reload_markers", () => { + self.handleEvent("clear_and_reload_markers", () => { // This event is just a trigger - the server will handle clearing and adding markers }); }, sendBoundsToServer() { - if (!this.map) return; + const self = this as unknown as LiveViewHookContext; + if (!self.map) return; - const bounds = this.map.getBounds(); - const center = this.map.getCenter(); - const zoom = this.map.getZoom(); + const bounds = self.map!.getBounds(); + const center = self.map!.getCenter(); + const zoom = self.map!.getZoom(); // Remove markers that are now outside the visible bounds - this.removeMarkersOutsideBounds(bounds); + self.removeMarkersOutsideBounds(bounds); - this.pushEvent("bounds_changed", { + self.pushEvent("bounds_changed", { bounds: { north: bounds.getNorth(), south: bounds.getSouth(), @@ -446,14 +518,15 @@ let MinimalAPRSMap = { }); }, - addMarker(data) { + addMarker(data: MarkerData) { + const self = this as unknown as LiveViewHookContext; if (!data.id || !data.lat || !data.lng) { console.warn("Invalid marker data:", data); return; } - const lat = parseFloat(data.lat); - const lng = parseFloat(data.lng); + const lat = parseFloat(data.lat.toString()); + const lng = parseFloat(data.lng.toString()); // Validate coordinates if (isNaN(lat) || isNaN(lng) || lat < -90 || lat > 90 || lng < -180 || lng > 180) { @@ -462,8 +535,8 @@ let MinimalAPRSMap = { } // Check if marker already exists with same position and data - const existingMarker = this.markers.get(data.id); - const existingState = this.markerStates.get(data.id); + const existingMarker = self.markers!.get(data.id); + const existingState = self.markerStates!.get(data.id); if (existingMarker && existingState) { // Check if marker needs updating @@ -471,7 +544,7 @@ let MinimalAPRSMap = { const positionChanged = Math.abs(currentPos.lat - lat) > 0.0001 || Math.abs(currentPos.lng - lng) > 0.0001; const dataChanged = - existingState.symbol_table !== data.symbol_table || + existingState.symbol_table !== data.symbol_table_id || existingState.symbol_code !== data.symbol_code || existingState.popup !== data.popup; @@ -482,10 +555,10 @@ let MinimalAPRSMap = { } // Remove existing marker if it exists - this.removeMarker(data.id); + self.removeMarker(data.id); // Create marker icon - const icon = this.createMarkerIcon(data); + const icon = self.createMarkerIcon(data); // Create marker const marker = L.marker([lat, lng], { icon: icon }); @@ -497,7 +570,7 @@ let MinimalAPRSMap = { // Handle marker click marker.on("click", () => { - this.pushEvent("marker_clicked", { + self.pushEvent("marker_clicked", { id: data.id, callsign: data.callsign, lat: lat, @@ -507,42 +580,44 @@ let MinimalAPRSMap = { // Mark historical markers for identification if (data.historical) { - marker._isHistorical = true; + (marker as any)._isHistorical = true; } // Add to map and store reference - marker.addTo(this.markerLayer); - this.markers.set(data.id, marker); + marker.addTo(self.markerLayer!); + self.markers!.set(data.id, marker); // Store marker state for optimization - this.markerStates.set(data.id, { + self.markerStates!.set(data.id, { lat: lat, lng: lng, - symbol_table: data.symbol_table, - symbol_code: data.symbol_code, + symbol_table: data.symbol_table_id || "/", + symbol_code: data.symbol_code || ">", popup: data.popup, historical: data.historical, }); }, - removeMarker(id) { - const marker = this.markers.get(id); + removeMarker(id: string) { + const self = this as unknown as LiveViewHookContext; + const marker = self.markers!.get(id); if (marker) { - this.markerLayer.removeLayer(marker); - this.markers.delete(id); - this.markerStates.delete(id); + self.markerLayer!.removeLayer(marker); + self.markers!.delete(id); + self.markerStates!.delete(id); } }, - updateMarker(data) { + updateMarker(data: MarkerData) { + const self = this as unknown as LiveViewHookContext; if (!data.id) return; - const existingMarker = this.markers.get(data.id); + const existingMarker = self.markers!.get(data.id); if (existingMarker) { // Update position if provided if (data.lat && data.lng) { - const lat = parseFloat(data.lat); - const lng = parseFloat(data.lng); + const lat = parseFloat(data.lat.toString()); + const lng = parseFloat(data.lng.toString()); if (!isNaN(lat) && !isNaN(lng)) { existingMarker.setLatLng([lat, lng]); } @@ -554,28 +629,30 @@ let MinimalAPRSMap = { } // Update icon if data changed - if (data.symbol_table || data.symbol_code || data.color) { - const newIcon = this.createMarkerIcon(data); + if (data.symbol_table_id || data.symbol_code || data.color) { + const newIcon = self.createMarkerIcon(data); existingMarker.setIcon(newIcon); } } else { // Marker doesn't exist, create it - this.addMarker(data); + self.addMarker(data); } }, clearAllMarkers() { - this.markerLayer.clearLayers(); - this.markers.clear(); - this.markerStates.clear(); + const self = this as unknown as LiveViewHookContext; + self.markerLayer!.clearLayers(); + self.markers!.clear(); + self.markerStates!.clear(); }, - removeMarkersOutsideBounds(bounds) { - if (!bounds || !this.markers) return; + removeMarkersOutsideBounds(bounds: L.LatLngBounds) { + const self = this as unknown as LiveViewHookContext; + if (!bounds || !self.markers) return; - const markersToRemove = []; + const markersToRemove: string[] = []; - this.markers.forEach((marker, id) => { + self.markers!.forEach((marker: L.Marker, id: string) => { const position = marker.getLatLng(); const lat = position.lat; const lng = position.lng; @@ -584,7 +661,7 @@ let MinimalAPRSMap = { const latOutOfBounds = lat < bounds.getSouth() || lat > bounds.getNorth(); // Check longitude bounds (handle potential wrapping) - let lngOutOfBounds; + let lngOutOfBounds: boolean; if (bounds.getWest() <= bounds.getEast()) { // Normal case: bounds don't cross antimeridian lngOutOfBounds = lng < bounds.getWest() || lng > bounds.getEast(); @@ -600,16 +677,16 @@ let MinimalAPRSMap = { }); // Remove out-of-bounds markers - markersToRemove.forEach((id) => this.removeMarker(id)); + markersToRemove.forEach((id) => self.removeMarker(id)); }, - createMarkerIcon(data) { + createMarkerIcon(data: MarkerData): L.DivIcon { const symbolTableId = data.symbol_table_id || "/"; const symbolCode = data.symbol_code || ">"; - + // Get the correct sprite sheet based on symbol table // Use high-DPI versions (@2x) for better quality - const spriteFile = symbolTableId === "/" + const spriteFile = symbolTableId === "/" ? "/aprs-symbols/aprs-symbols-24-0@2x.png" : "/aprs-symbols/aprs-symbols-24-1@2x.png"; @@ -617,7 +694,7 @@ let MinimalAPRSMap = { // The sprite sheet is organized as a 16x8 grid (128 symbols total) // ASCII codes 32-127 map to positions 0-95 const charCode = symbolCode.charCodeAt(0); - + // Convert ASCII to sprite sheet position // The sprite sheet is organized in a specific way: // - First row (0): ASCII 32-47 @@ -630,7 +707,7 @@ let MinimalAPRSMap = { const position = charCode - 32; const row = Math.floor(position / 16); const column = position % 16; - + // Each symbol is 48x48 pixels in @2x version (24x24 * 2) const x = -column * 48; const y = -row * 48; @@ -667,7 +744,7 @@ let MinimalAPRSMap = { }); }, - buildPopupContent(data) { + buildPopupContent(data: MarkerData): string { const callsign = data.callsign || data.id || "Unknown"; const comment = data.comment || ""; const symbolTableId = data.symbol_table_id || "/"; @@ -693,33 +770,25 @@ let MinimalAPRSMap = { }, destroyed() { - // Clean up timers - if (this.boundsTimer) { - clearTimeout(this.boundsTimer); + const self = this as unknown as LiveViewHookContext; + if (self.boundsTimer !== undefined) { + clearTimeout(self.boundsTimer); } - - // Clean up event listeners - if (this.resizeHandler) { - window.removeEventListener("resize", this.resizeHandler); + if (self.resizeHandler !== undefined) { + window.removeEventListener("resize", self.resizeHandler); } - - // Clean up markers - if (this.markerLayer) { - this.markerLayer.clearLayers(); + if (self.markerLayer !== undefined) { + self.markerLayer!.clearLayers(); } - - if (this.markers) { - this.markers.clear(); + if (self.markers !== undefined) { + self.markers!.clear(); } - - if (this.markerStates) { - this.markerStates.clear(); + if (self.markerStates !== undefined) { + self.markerStates!.clear(); } - - // Clean up map - if (this.map) { - this.map.remove(); - this.map = null; + if (self.map !== undefined) { + self.map!.remove(); + self.map = undefined; } }, }; diff --git a/assets/js/types/leaflet.d.ts b/assets/js/types/leaflet.d.ts new file mode 100644 index 0000000..0cdbb5b --- /dev/null +++ b/assets/js/types/leaflet.d.ts @@ -0,0 +1,96 @@ +declare namespace L { + class Map { + constructor(element: string | HTMLElement, options?: MapOptions); + setView(center: LatLngExpression, zoom: number, options?: ZoomOptions): this; + getCenter(): LatLng; + getZoom(): number; + getBounds(): LatLngBounds; + remove(): void; + invalidateSize(options?: { animate?: boolean; pan?: boolean }): this; + whenReady(callback: () => void): this; + on(event: string, handler: (e: any) => void): this; + } + + interface MapOptions { + zoomControl?: boolean; + attributionControl?: boolean; + closePopupOnClick?: boolean; + } + + interface ZoomOptions { + animate?: boolean; + duration?: number; + } + + class LatLng { + lat: number; + lng: number; + constructor(lat: number, lng: number); + } + + type LatLngExpression = LatLng | [number, number]; + + class LatLngBounds { + constructor(southWest: LatLngExpression, northEast: LatLngExpression); + getNorth(): number; + getSouth(): number; + getEast(): number; + getWest(): number; + } + + class Marker { + constructor(latLng: LatLngExpression, options?: MarkerOptions); + setLatLng(latLng: LatLngExpression): this; + bindPopup(content: string | HTMLElement): this; + setPopupContent(content: string | HTMLElement): this; + setIcon(icon: DivIcon): this; + getLatLng(): LatLng; + } + + interface MarkerOptions { + icon?: DivIcon; + } + + class DivIcon { + constructor(options: DivIconOptions); + } + + interface DivIconOptions { + html?: string | HTMLElement; + className?: string; + iconSize?: [number, number]; + iconAnchor?: [number, number]; + } + + class LayerGroup { + addTo(map: Map): this; + clearLayers(): this; + removeLayer(layer: Layer): this; + } + + interface Layer { + _leaflet_id?: number; + } + + class TileLayer { + constructor(urlTemplate: string, options?: TileLayerOptions); + addTo(map: Map): this; + } + + interface TileLayerOptions { + attribution?: string; + maxZoom?: number; + } + + function map(element: string | HTMLElement, options?: MapOptions): Map; + function marker(latLng: LatLngExpression, options?: MarkerOptions): Marker; + function tileLayer(urlTemplate: string, options?: TileLayerOptions): TileLayer; + function latLngBounds(southWest: LatLngExpression, northEast: LatLngExpression): LatLngBounds; + function divIcon(options: DivIconOptions): DivIcon; + function layerGroup(): LayerGroup; + function latLng(lat: number, lng: number): LatLng; + function DomUtil(): { + get(id: string): HTMLElement | null; + remove(el: HTMLElement): void; + }; +} \ No newline at end of file diff --git a/assets/vendor/leaflet/leaflet.css b/assets/vendor/leaflet/leaflet.css new file mode 100644 index 0000000..d9ee57d --- /dev/null +++ b/assets/vendor/leaflet/leaflet.css @@ -0,0 +1 @@ +.leaflet-image-layer,.leaflet-layer,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-pane,.leaflet-pane>canvas,.leaflet-pane>svg,.leaflet-tile,.leaflet-tile-container,.leaflet-zoom-box{position:absolute;left:0;top:0}.leaflet-container{overflow:hidden}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile{-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::selection{background:0 0}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{width:1600px;height:1600px;-webkit-transform-origin:0 0}.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-overlay-pane svg{max-width:none!important;max-height:none!important}.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer{max-width:none!important;max-height:none!important;width:auto;padding:0}.leaflet-container img.leaflet-tile{mix-blend-mode:plus-lighter}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:rgba(51,181,229,.4)}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{width:0;height:0;-moz-box-sizing:border-box;box-sizing:border-box;z-index:800}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{position:relative;z-index:800;pointer-events:visiblePainted;pointer-events:auto}.leaflet-bottom,.leaflet-top{position:absolute;z-index:1000;pointer-events:none}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-popup{opacity:0;-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}svg.leaflet-zoom-animated{will-change:transform}.leaflet-zoom-anim .leaflet-zoom-animated{-webkit-transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);-moz-transition:-moz-transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1)}.leaflet-pan-anim .leaflet-tile,.leaflet-zoom-anim .leaflet-tile{-webkit-transition:none;-moz-transition:none;transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-control,.leaflet-popup-pane{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive,.leaflet-dragging .leaflet-marker-draggable{cursor:move;cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.leaflet-image-layer,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-image-layer.leaflet-interactive,.leaflet-marker-icon.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{background:#ddd;outline-offset:1px}.leaflet-container a{color:#0078a8}.leaflet-zoom-box{border:2px dotted #38f;background:rgba(255,255,255,.5)}.leaflet-container{font-family:"Helvetica Neue",Arial,Helvetica,sans-serif;font-size:12px;font-size:.75rem;line-height:1.5}.leaflet-bar{box-shadow:0 1px 5px rgba(0,0,0,.65);border-radius:4px}.leaflet-bar a{background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;display:block;text-align:center;text-decoration:none;color:#000}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50% 50%;background-repeat:no-repeat;display:block}.leaflet-bar a:focus,.leaflet-bar a:hover{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-bottom:none}.leaflet-bar a.leaflet-disabled{cursor:default;background-color:#f4f4f4;color:#bbb}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-left-radius:2px;border-bottom-right-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{font:bold 18px 'Lucida Console',Monaco,monospace;text-indent:1px}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-control-layers{box-shadow:0 1px 5px rgba(0,0,0,.4);background:#fff;border-radius:5px}.leaflet-control-layers-toggle{background-image:url(images/layers.png);width:36px;height:36px}.leaflet-retina .leaflet-control-layers-toggle{background-image:url(images/layers-2x.png);background-size:26px 26px}.leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px}.leaflet-control-layers .leaflet-control-layers-list,.leaflet-control-layers-expanded .leaflet-control-layers-toggle{display:none}.leaflet-control-layers-expanded .leaflet-control-layers-list{display:block;position:relative}.leaflet-control-layers-expanded{padding:6px 10px 6px 6px;color:#333;background:#fff}.leaflet-control-layers-scrollbar{overflow-y:scroll;overflow-x:hidden;padding-right:5px}.leaflet-control-layers-selector{margin-top:2px;position:relative;top:1px}.leaflet-control-layers label{display:block;font-size:13px;font-size:1.08333em}.leaflet-control-layers-separator{height:0;border-top:1px solid #ddd;margin:5px -10px 5px -6px}.leaflet-default-icon-path{background-image:url(images/marker-icon.png)}.leaflet-container .leaflet-control-attribution{background:#fff;background:rgba(255,255,255,.8);margin:0}.leaflet-control-attribution,.leaflet-control-scale-line{padding:0 5px;color:#333;line-height:1.4}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:focus,.leaflet-control-attribution a:hover{text-decoration:underline}.leaflet-attribution-flag{display:inline!important;vertical-align:baseline!important;width:1em;height:.6669em}.leaflet-left .leaflet-control-scale{margin-left:5px}.leaflet-bottom .leaflet-control-scale{margin-bottom:5px}.leaflet-control-scale-line{border:2px solid #777;border-top:none;line-height:1.1;padding:2px 5px 1px;white-space:nowrap;-moz-box-sizing:border-box;box-sizing:border-box;background:rgba(255,255,255,.8);text-shadow:1px 1px #fff}.leaflet-control-scale-line:not(:first-child){border-top:2px solid #777;border-bottom:none;margin-top:-2px}.leaflet-control-scale-line:not(:first-child):not(:last-child){border-bottom:2px solid #777}.leaflet-touch .leaflet-bar,.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers{box-shadow:none}.leaflet-touch .leaflet-bar,.leaflet-touch .leaflet-control-layers{border:2px solid rgba(0,0,0,.2);background-clip:padding-box}.leaflet-popup{position:absolute;text-align:center;margin-bottom:20px}.leaflet-popup-content-wrapper{padding:1px;text-align:left;border-radius:12px}.leaflet-popup-content{margin:13px 24px 13px 20px;line-height:1.3;font-size:13px;font-size:1.08333em;min-height:1px}.leaflet-popup-content p{margin:17px 0;margin:1.3em 0}.leaflet-popup-tip-container{width:40px;height:20px;position:absolute;left:50%;margin-top:-1px;margin-left:-20px;overflow:hidden;pointer-events:none}.leaflet-popup-tip{width:17px;height:17px;padding:1px;margin:-10px auto 0;pointer-events:auto;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{background:#fff;color:#333;box-shadow:0 3px 14px rgba(0,0,0,.4)}.leaflet-container a.leaflet-popup-close-button{position:absolute;top:0;right:0;border:none;text-align:center;width:24px;height:24px;font:16px/24px Tahoma,Verdana,sans-serif;color:#757575;text-decoration:none;background:0 0}.leaflet-container a.leaflet-popup-close-button:focus,.leaflet-container a.leaflet-popup-close-button:hover{color:#585858}.leaflet-popup-scrolled{overflow:auto}.leaflet-oldie .leaflet-popup-content-wrapper{-ms-zoom:1}.leaflet-oldie .leaflet-popup-tip{width:24px;margin:0 auto}.leaflet-oldie .leaflet-control-layers,.leaflet-oldie .leaflet-control-zoom,.leaflet-oldie .leaflet-popup-content-wrapper,.leaflet-oldie .leaflet-popup-tip{border:1px solid #999}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{position:absolute;padding:6px;background-color:#fff;border:1px solid #fff;border-radius:3px;color:#222;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;box-shadow:0 1px 3px rgba(0,0,0,.4)}.leaflet-tooltip.leaflet-interactive{cursor:pointer;pointer-events:auto}.leaflet-tooltip-bottom:before,.leaflet-tooltip-left:before,.leaflet-tooltip-right:before,.leaflet-tooltip-top:before{position:absolute;pointer-events:none;border:6px solid transparent;background:0 0;content:""}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-bottom:before,.leaflet-tooltip-top:before{left:50%;margin-left:-6px}.leaflet-tooltip-top:before{bottom:0;margin-bottom:-12px;border-top-color:#fff}.leaflet-tooltip-bottom:before{top:0;margin-top:-12px;margin-left:-6px;border-bottom-color:#fff}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{top:50%;margin-top:-6px}.leaflet-tooltip-left:before{right:0;margin-right:-12px;border-left-color:#fff}.leaflet-tooltip-right:before{left:0;margin-left:-12px;border-right-color:#fff}@media print{.leaflet-control{-webkit-print-color-adjust:exact;print-color-adjust:exact}} \ No newline at end of file diff --git a/assets/vendor/leaflet/leaflet.js b/assets/vendor/leaflet/leaflet.js new file mode 100644 index 0000000..af7e878 --- /dev/null +++ b/assets/vendor/leaflet/leaflet.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var e,i,n=1,o=arguments.length;n=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=c(t);var e=this.min,i=this.max,n=t.min,o=(t=t.max).x>=e.x&&n.x<=i.x,t=t.y>=e.y&&n.y<=i.y;return o&&t},overlaps:function(t){t=c(t);var e=this.min,i=this.max,n=t.min,o=(t=t.max).x>e.x&&n.xe.y&&n.y=n.lat&&i.lat<=o.lat&&e.lng>=n.lng&&i.lng<=o.lng},intersects:function(t){t=g(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),o=(t=t.getNorthEast()).lat>=e.lat&&n.lat<=i.lat,t=t.lng>=e.lng&&n.lng<=i.lng;return o&&t},overlaps:function(t){t=g(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),o=(t=t.getNorthEast()).lat>e.lat&&n.late.lng&&n.lng","http://www.w3.org/2000/svg"===(jt.firstChild&&jt.firstChild.namespaceURI));function w(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var b={ie:Me,ielt9:pt,edge:n,webkit:mt,android:ft,android23:gt,androidStock:vt,opera:yt,chrome:xt,gecko:wt,safari:bt,phantom:Pt,opera12:o,win:Lt,ie3d:Tt,webkit3d:Mt,gecko3d:_t,any3d:zt,mobile:Gi,mobileWebkit:Ct,mobileWebkit3d:Zt,msPointer:St,pointer:Et,touch:Ot,touchNative:kt,mobileOpera:At,mobileGecko:Bt,retina:It,passiveEvents:Rt,canvas:Nt,svg:Dt,vml:!Dt&&function(){try{var t=document.createElement("div"),e=(t.innerHTML='',t.firstChild);return e.style.behavior="url(#default#VML)",e&&"object"==typeof e.adj}catch(t){return!1}}(),inlineSvg:jt,mac:0===navigator.platform.indexOf("Mac"),linux:0===navigator.platform.indexOf("Linux")},Ht=b.msPointer?"MSPointerDown":"pointerdown",Wt=b.msPointer?"MSPointerMove":"pointermove",Ft=b.msPointer?"MSPointerUp":"pointerup",Ut=b.msPointer?"MSPointerCancel":"pointercancel",Vt={touchstart:Ht,touchmove:Wt,touchend:Ft,touchcancel:Ut},qt={touchstart:function(t,e){e.MSPOINTER_TYPE_TOUCH&&e.pointerType===e.MSPOINTER_TYPE_TOUCH&&O(e),$t(t,e)},touchmove:$t,touchend:$t,touchcancel:$t},Gt={},Kt=!1;function Yt(t){Gt[t.pointerId]=t}function Xt(t){Gt[t.pointerId]&&(Gt[t.pointerId]=t)}function Jt(t){delete Gt[t.pointerId]}function $t(t,e){if(e.pointerType!==(e.MSPOINTER_TYPE_MOUSE||"mouse")){for(var i in e.touches=[],Gt)e.touches.push(Gt[i]);e.changedTouches=[e],t(e)}}var Qt=200;var te,ee,ie,ne,oe,se=fe(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),re=fe(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),ae="webkitTransition"===re||"OTransition"===re?re+"End":"transitionend";function he(t){return"string"==typeof t?document.getElementById(t):t}function le(t,e){var i=t.style[e]||t.currentStyle&&t.currentStyle[e];return"auto"===(i=i&&"auto"!==i||!document.defaultView?i:(t=document.defaultView.getComputedStyle(t,null))?t[e]:null)?null:i}function P(t,e,i){return(t=document.createElement(t)).className=e||"",i&&i.appendChild(t),t}function T(t){var e=t.parentNode;e&&e.removeChild(t)}function ue(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function ce(t){var e=t.parentNode;e&&e.lastChild!==t&&e.appendChild(t)}function de(t){var e=t.parentNode;e&&e.firstChild!==t&&e.insertBefore(t,e.firstChild)}function _e(t,e){return void 0!==t.classList?t.classList.contains(e):0<(t=me(t)).length&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(t)}function M(t,e){var i;if(void 0!==t.classList)for(var n=F(e),o=0,s=n.length;othis.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,e){this._enforcingBounds=!0;var i=this.getCenter(),t=this._limitCenter(i,this._zoom,g(t));return i.equals(t)||this.panTo(t,e),this._enforcingBounds=!1,this},panInside:function(t,e){var i=m((e=e||{}).paddingTopLeft||e.padding||[0,0]),n=m(e.paddingBottomRight||e.padding||[0,0]),o=this.project(this.getCenter()),t=this.project(t),s=(i=c([(s=this.getPixelBounds()).min.add(i),s.max.subtract(n)])).getSize();return i.contains(t)||(this._enforcingBounds=!0,n=t.subtract(i.getCenter()),i=i.extend(t).getSize().subtract(s),o.x+=n.x<0?-i.x:i.x,o.y+=n.y<0?-i.y:i.y,this.panTo(this.unproject(o),e),this._enforcingBounds=!1),this},invalidateSize:function(t){if(!this._loaded)return this;t=l({animate:!1,pan:!0},!0===t?{animate:!0}:t);var e=this.getSize(),i=(this._sizeChanged=!0,this._lastCenter=null,this.getSize()),n=e.divideBy(2).round(),o=i.divideBy(2).round();return(n=n.subtract(o)).x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:i})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){var e,i;return t=this._locateOptions=l({timeout:1e4,watch:!1},t),"geolocation"in navigator?(e=a(this._handleGeolocationResponse,this),i=a(this._handleGeolocationError,this),t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t)):this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e;this._container._leaflet_id&&(e=t.code,t=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout"),this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+t+"."}))},_handleGeolocationResponse:function(t){if(this._container._leaflet_id){var e,i,n=new v(t.coords.latitude,t.coords.longitude),o=n.toBounds(2*t.coords.accuracy),s=this._locateOptions,r=(s.setView&&(e=this.getBoundsZoom(o),this.setView(n,s.maxZoom?Math.min(e,s.maxZoom):e)),{latlng:n,bounds:o,timestamp:t.timestamp});for(i in t.coords)"number"==typeof t.coords[i]&&(r[i]=t.coords[i]);this.fire("locationfound",r)}},addHandler:function(t,e){return e&&(e=this[t]=new e(this),this._handlers.push(e),this.options[t]&&e.enable()),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}for(var t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),T(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(r(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)T(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,e){return e=P("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),e||this._mapPane),t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter.clone():this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new s(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=g(t),i=m(i||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),t=t.getSouthEast(),i=this.getSize().subtract(i),t=c(this.project(t,n),this.project(r,n)).getSize(),r=b.any3d?this.options.zoomSnap:1,a=i.x/t.x,i=i.y/t.y,t=e?Math.max(a,i):Math.min(a,i),n=this.getScaleZoom(t,n);return r&&(n=Math.round(n/(r/100))*(r/100),n=e?Math.ceil(n/r)*r:Math.floor(n/r)*r),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new p(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,e){return new f(t=this._getTopLeftPoint(t,e),t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,e){var i=this.options.crs;return e=void 0===e?this._zoom:e,i.scale(t)/i.scale(e)},getScaleZoom:function(t,e){var i=this.options.crs,t=(e=void 0===e?this._zoom:e,i.zoom(t*i.scale(e)));return isNaN(t)?1/0:t},project:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.latLngToPoint(x(t),e)},unproject:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.pointToLatLng(m(t),e)},layerPointToLatLng:function(t){return t=m(t).add(this.getPixelOrigin()),this.unproject(t)},latLngToLayerPoint:function(t){return this.project(x(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(x(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(g(t))},distance:function(t,e){return this.options.crs.distance(x(t),x(e))},containerPointToLayerPoint:function(t){return m(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return m(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){return t=this.containerPointToLayerPoint(m(t)),this.layerPointToLatLng(t)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(x(t)))},mouseEventToContainerPoint:function(t){return Ie(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){if(!(t=this._container=he(t)))throw new Error("Map container not found.");if(t._leaflet_id)throw new Error("Map container is already initialized.");S(t,"scroll",this._onScroll,this),this._containerId=d(t)},_initLayout:function(){var t=this._container,e=(this._fadeAnimated=this.options.fadeAnimation&&b.any3d,M(t,"leaflet-container"+(b.touch?" leaflet-touch":"")+(b.retina?" leaflet-retina":"")+(b.ielt9?" leaflet-oldie":"")+(b.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":"")),le(t,"position"));"absolute"!==e&&"relative"!==e&&"fixed"!==e&&"sticky"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Z(this._mapPane,new p(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(M(t.markerPane,"leaflet-zoom-hide"),M(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,e,i){Z(this._mapPane,new p(0,0));var n=!this._loaded,o=(this._loaded=!0,e=this._limitZoom(e),this.fire("viewprereset"),this._zoom!==e);this._moveStart(o,i)._move(t,e)._moveEnd(o),this.fire("viewreset"),n&&this.fire("load")},_moveStart:function(t,e){return t&&this.fire("zoomstart"),e||this.fire("movestart"),this},_move:function(t,e,i,n){void 0===e&&(e=this._zoom);var o=this._zoom!==e;return this._zoom=e,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),n?i&&i.pinch&&this.fire("zoom",i):((o||i&&i.pinch)&&this.fire("zoom",i),this.fire("move",i)),this},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return r(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Z(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var e=t?k:S;e((this._targets[d(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&e(window,"resize",this._onResize,this),b.any3d&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){r(this._resizeRequest),this._resizeRequest=y(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,e){for(var i,n=[],o="mouseout"===e||"mouseover"===e,s=t.target||t.srcElement,r=!1;s;){if((i=this._targets[d(s)])&&("click"===e||"preclick"===e)&&this._draggableMoved(i)){r=!0;break}if(i&&i.listens(e,!0)){if(o&&!De(s,t))break;if(n.push(i),o)break}if(s===this._container)break;s=s.parentNode}return n.length||r||o||!this.listens(e,!0)?n:[this]},_isClickDisabled:function(t){for(;t&&t!==this._container;){if(t._leaflet_disable_click)return!0;t=t.parentNode}},_handleDOMEvent:function(t){var e,i=t.target||t.srcElement;!this._loaded||i._leaflet_disable_events||"click"===t.type&&this._isClickDisabled(i)||("mousedown"===(e=t.type)&&we(i),this._fireDOMEvent(t,e))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,e,i){"click"===t.type&&((a=l({},t)).type="preclick",this._fireDOMEvent(a,a.type,i));var n=this._findEventTargets(t,e);if(i){for(var o=[],s=0;sthis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),n=this._getCenterOffset(t)._divideBy(1-1/n);if(!0!==i.animate&&!this.getSize().contains(n))return!1;y(function(){this._moveStart(!0,i.noMoveStart||!1)._animateZoom(t,e,!0)},this)}return!0},_animateZoom:function(t,e,i,n){this._mapPane&&(i&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=e,M(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:e,noUpdate:n}),this._tempFireZoomEvent||(this._tempFireZoomEvent=this._zoom!==this._animateToZoom),this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&z(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire("zoom"),delete this._tempFireZoomEvent,this.fire("move"),this._moveEnd(!0))}});function He(t){return new B(t)}var B=et.extend({options:{position:"topright"},initialize:function(t){h(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),t=t._controlCorners[i];return M(e,"leaflet-control"),-1!==i.indexOf("bottom")?t.insertBefore(e,t.firstChild):t.appendChild(e),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(T(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",(e=document.createElement("div")).innerHTML=t,e.firstChild},_addItem:function(t){var e,i=document.createElement("label"),n=this._map.hasLayer(t.layer);t.overlay?((e=document.createElement("input")).type="checkbox",e.className="leaflet-control-layers-selector",e.defaultChecked=n):e=this._createRadioElement("leaflet-base-layers_"+d(this),n),this._layerControlInputs.push(e),e.layerId=d(t.layer),S(e,"click",this._onInputClick,this);(n=document.createElement("span")).innerHTML=" "+t.name;var o=document.createElement("span");return i.appendChild(o),o.appendChild(e),o.appendChild(n),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(i),this._checkDisabledLayers(),i},_onInputClick:function(){if(!this._preventClick){var t,e,i=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=i.length-1;0<=s;s--)t=i[s],e=this._getLayer(t.layerId).layer,t.checked?n.push(e):t.checked||o.push(e);for(s=0;se.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expandSafely:function(){var t=this._section,e=(this._preventClick=!0,S(t,"click",O),this.expand(),this);setTimeout(function(){k(t,"click",O),e._preventClick=!1})}})),Fe=B.extend({options:{position:"topleft",zoomInText:'',zoomInTitle:"Zoom in",zoomOutText:'',zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=P("div",e+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,e+"-in",i,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,e+"-out",i,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,e,i,n,o){return(i=P("a",i,n)).innerHTML=t,i.href="#",i.title=e,i.setAttribute("role","button"),i.setAttribute("aria-label",e),Oe(i),S(i,"click",Ae),S(i,"click",o,this),S(i,"click",this._refocusOnMap,this),i},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";z(this._zoomInButton,e),z(this._zoomOutButton,e),this._zoomInButton.setAttribute("aria-disabled","false"),this._zoomOutButton.setAttribute("aria-disabled","false"),!this._disabled&&t._zoom!==t.getMinZoom()||(M(this._zoomOutButton,e),this._zoomOutButton.setAttribute("aria-disabled","true")),!this._disabled&&t._zoom!==t.getMaxZoom()||(M(this._zoomInButton,e),this._zoomInButton.setAttribute("aria-disabled","true"))}}),Ue=(A.mergeOptions({zoomControl:!0}),A.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new Fe,this.addControl(this.zoomControl))}),B.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var e="leaflet-control-scale",i=P("div",e),n=this.options;return this._addScales(n,e+"-line",i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=P("div",e,i)),t.imperial&&(this._iScale=P("div",e,i))},_update:function(){var t=(e=this._map).getSize().y/2,e=e.distance(e.containerPointToLatLng([0,t]),e.containerPointToLatLng([this.options.maxWidth,t]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var e=this._getRoundNum(t);this._updateScale(this._mScale,e<1e3?e+" m":e/1e3+" km",e/t)},_updateImperial:function(t){var e,i;5280<(t=3.2808399*t)?(i=this._getRoundNum(e=t/5280),this._updateScale(this._iScale,i+" mi",i/e)):(i=this._getRoundNum(t),this._updateScale(this._iScale,i+" ft",i/t))},_updateScale:function(t,e,i){t.style.width=Math.round(this.options.maxWidth*i)+"px",t.innerHTML=e},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1);return e*(10<=(t=t/e)?10:5<=t?5:3<=t?3:2<=t?2:1)}})),Ve=B.extend({options:{position:"bottomright",prefix:''+(b.inlineSvg?' ':"")+"Leaflet"},initialize:function(t){h(this,t),this._attributions={}},onAdd:function(t){for(var e in(t.attributionControl=this)._container=P("div","leaflet-control-attribution"),Oe(this._container),t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return this._update(),t.on("layeradd",this._addAttribution,this),this._container},onRemove:function(t){t.off("layeradd",this._addAttribution,this)},_addAttribution:function(t){t.layer.getAttribution&&(this.addAttribution(t.layer.getAttribution()),t.layer.once("remove",function(){this.removeAttribution(t.layer.getAttribution())},this))},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t,e=[];for(t in this._attributions)this._attributions[t]&&e.push(t);var i=[];this.options.prefix&&i.push(this.options.prefix),e.length&&i.push(e.join(", ")),this._container.innerHTML=i.join(' ')}}});A.mergeOptions({attributionControl:!0}),A.addInitHook(function(){this.options.attributionControl&&(new Ve).addTo(this)}),B.Layers=We,B.Zoom=Fe,B.Scale=Ue,B.Attribution=Ve,He.layers=function(t,e,i){return new We(t,e,i)},He.zoom=function(t){return new Fe(t)},He.scale=function(t){return new Ue(t)},He.attribution=function(t){return new Ve(t)};(n=et.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}})).addTo=function(t,e){return t.addHandler(e,this),this};var mt={Events:e},qe=b.touch?"touchstart mousedown":"mousedown",Ge=it.extend({options:{clickTolerance:3},initialize:function(t,e,i,n){h(this,n),this._element=t,this._dragStartTarget=e||t,this._preventOutline=i},enable:function(){this._enabled||(S(this._dragStartTarget,qe,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Ge._dragging===this&&this.finishDrag(!0),k(this._dragStartTarget,qe,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var e,i;this._enabled&&(this._moved=!1,_e(this._element,"leaflet-zoom-anim")||(t.touches&&1!==t.touches.length?Ge._dragging===this&&this.finishDrag():Ge._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((Ge._dragging=this)._preventOutline&&we(this._element),ye(),ie(),this._moving||(this.fire("down"),i=t.touches?t.touches[0]:t,e=Pe(this._element),this._startPoint=new p(i.clientX,i.clientY),this._startPos=ve(this._element),this._parentScale=Le(e),i="mousedown"===t.type,S(document,i?"mousemove":"touchmove",this._onMove,this),S(document,i?"mouseup":"touchend touchcancel",this._onUp,this)))))},_onMove:function(t){var e;this._enabled&&(t.touches&&1e&&(s.push(t[r]),a=r);return ae.max.x&&(i|=2),t.ye.max.y&&(i|=8),i}function ni(t,e,i,n){var o=e.x,e=e.y,s=i.x-o,r=i.y-e,a=s*s+r*r;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(l=!l);return l||fi.prototype._containsPoint.call(this,t,!0)}})),vi=hi.extend({initialize:function(t,e){h(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,o=u(t)?t:t.features;if(o){for(e=0,i=o.length;es.x&&(r=i.x+a-s.x+o.x),i.x-r-n.x<(a=0)&&(r=i.x-n.x),i.y+e+o.y>s.y&&(a=i.y+e-s.y+o.y),i.y-a-n.y<0&&(a=i.y-n.y),(r||a)&&(this.options.keepInView&&(this._autopanning=!0),t.fire("autopanstart").panBy([r,a]))))},_getAnchor:function(){return m(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}})),Oi=(A.mergeOptions({closePopupOnClick:!0}),A.include({openPopup:function(t,e,i){return this._initOverlay(ki,t,e,i).openOn(this),this},closePopup:function(t){return(t=arguments.length?t:this._popup)&&t.close(),this}}),o.include({bindPopup:function(t,e){return this._popup=this._initOverlay(ki,this._popup,t,e),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t){return this._popup&&(this instanceof hi||(this._popup._source=this),this._popup._prepareOpen(t||this._latlng)&&this._popup.openOn(this._map)),this},closePopup:function(){return this._popup&&this._popup.close(),this},togglePopup:function(){return this._popup&&this._popup.toggle(this),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var e;this._popup&&this._map&&(Ae(t),e=t.layer||t.target,this._popup._source!==e||e instanceof _i?(this._popup._source=e,this.openPopup(t.latlng)):this._map.hasLayer(this._popup)?this.closePopup():this.openPopup(t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}}),Ei.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,opacity:.9},onAdd:function(t){Ei.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&(this.addEventParent(this._source),this._source.fire("tooltipopen",{tooltip:this},!0))},onRemove:function(t){Ei.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&(this.removeEventParent(this._source),this._source.fire("tooltipclose",{tooltip:this},!0))},getEvents:function(){var t=Ei.prototype.getEvents.call(this);return this.options.permanent||(t.preclick=this.close),t},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=P("div",t),this._container.setAttribute("role","tooltip"),this._container.setAttribute("id","leaflet-tooltip-"+d(this))},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var e,i=this._map,n=this._container,o=i.latLngToContainerPoint(i.getCenter()),i=i.layerPointToContainerPoint(t),s=this.options.direction,r=n.offsetWidth,a=n.offsetHeight,h=m(this.options.offset),l=this._getAnchor(),i="top"===s?(e=r/2,a):"bottom"===s?(e=r/2,0):(e="center"===s?r/2:"right"===s?0:"left"===s?r:i.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oi.max.x)||!e.wrapLat&&(t.yi.max.y))return!1}return!this.options.bounds||(e=this._tileCoordsToBounds(t),g(this.options.bounds).overlaps(e))},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var e=this._map,i=this.getTileSize(),n=t.scaleBy(i),i=n.add(i);return[e.unproject(n,t.z),e.unproject(i,t.z)]},_tileCoordsToBounds:function(t){return t=new s((t=this._tileCoordsToNwSe(t))[0],t[1]),this.options.noWrap?t:this._map.wrapLatLngBounds(t)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var e=new p(+(t=t.split(":"))[0],+t[1]);return e.z=+t[2],e},_removeTile:function(t){var e=this._tiles[t];e&&(T(e.el),delete this._tiles[t],this.fire("tileunload",{tile:e.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){M(t,"leaflet-tile");var e=this.getTileSize();t.style.width=e.x+"px",t.style.height=e.y+"px",t.onselectstart=_,t.onmousemove=_,b.ielt9&&this.options.opacity<1&&C(t,this.options.opacity)},_addTile:function(t,e){var i=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&y(a(this._tileReady,this,t,null,o)),Z(o,i),this._tiles[n]={el:o,coords:t,current:!0},e.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,e,i){e&&this.fire("tileerror",{error:e,tile:i,coords:t});var n=this._tileCoordsToKey(t);(i=this._tiles[n])&&(i.loaded=+new Date,this._map._fadeAnimated?(C(i.el,0),r(this._fadeFrame),this._fadeFrame=y(this._updateOpacity,this)):(i.active=!0,this._pruneTiles()),e||(M(i.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:i.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),b.ielt9||!this._map._fadeAnimated?y(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var e=new p(this._wrapX?H(t.x,this._wrapX):t.x,this._wrapY?H(t.y,this._wrapY):t.y);return e.z=t.z,e},_pxBoundsToTileRange:function(t){var e=this.getTileSize();return new f(t.min.unscaleBy(e).floor(),t.max.unscaleBy(e).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}})),Ii=Bi.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1,referrerPolicy:!1},initialize:function(t,e){this._url=t,(e=h(this,e)).detectRetina&&b.retina&&0')}}catch(t){}return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}(),Mt={_initContainer:function(){this._container=P("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Di.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var e=t._container=Wi("shape");M(e,"leaflet-vml-shape "+(this.options.className||"")),e.coordsize="1 1",t._path=Wi("path"),e.appendChild(t._path),this._updateStyle(t),this._layers[d(t)]=t},_addPath:function(t){var e=t._container;this._container.appendChild(e),t.options.interactive&&t.addInteractiveTarget(e)},_removePath:function(t){var e=t._container;T(e),t.removeInteractiveTarget(e),delete this._layers[d(t)]},_updateStyle:function(t){var e=t._stroke,i=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(e=e||(t._stroke=Wi("stroke")),o.appendChild(e),e.weight=n.weight+"px",e.color=n.color,e.opacity=n.opacity,n.dashArray?e.dashStyle=u(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):e.dashStyle="",e.endcap=n.lineCap.replace("butt","flat"),e.joinstyle=n.lineJoin):e&&(o.removeChild(e),t._stroke=null),n.fill?(i=i||(t._fill=Wi("fill")),o.appendChild(i),i.color=n.fillColor||n.color,i.opacity=n.fillOpacity):i&&(o.removeChild(i),t._fill=null)},_updateCircle:function(t){var e=t._point.round(),i=Math.round(t._radius),n=Math.round(t._radiusY||i);this._setPath(t,t._empty()?"M0 0":"AL "+e.x+","+e.y+" "+i+","+n+" 0,23592600")},_setPath:function(t,e){t._path.v=e},_bringToFront:function(t){ce(t._container)},_bringToBack:function(t){de(t._container)}},Fi=b.vml?Wi:ct,Ui=Di.extend({_initContainer:function(){this._container=Fi("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=Fi("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){T(this._container),k(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_update:function(){var t,e,i;this._map._animatingZoom&&this._bounds||(Di.prototype._update.call(this),e=(t=this._bounds).getSize(),i=this._container,this._svgSize&&this._svgSize.equals(e)||(this._svgSize=e,i.setAttribute("width",e.x),i.setAttribute("height",e.y)),Z(i,t.min),i.setAttribute("viewBox",[t.min.x,t.min.y,e.x,e.y].join(" ")),this.fire("update"))},_initPath:function(t){var e=t._path=Fi("path");t.options.className&&M(e,t.options.className),t.options.interactive&&M(e,"leaflet-interactive"),this._updateStyle(t),this._layers[d(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){T(t._path),t.removeInteractiveTarget(t._path),delete this._layers[d(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var e=t._path,t=t.options;e&&(t.stroke?(e.setAttribute("stroke",t.color),e.setAttribute("stroke-opacity",t.opacity),e.setAttribute("stroke-width",t.weight),e.setAttribute("stroke-linecap",t.lineCap),e.setAttribute("stroke-linejoin",t.lineJoin),t.dashArray?e.setAttribute("stroke-dasharray",t.dashArray):e.removeAttribute("stroke-dasharray"),t.dashOffset?e.setAttribute("stroke-dashoffset",t.dashOffset):e.removeAttribute("stroke-dashoffset")):e.setAttribute("stroke","none"),t.fill?(e.setAttribute("fill",t.fillColor||t.color),e.setAttribute("fill-opacity",t.fillOpacity),e.setAttribute("fill-rule",t.fillRule||"evenodd")):e.setAttribute("fill","none"))},_updatePoly:function(t,e){this._setPath(t,dt(t._parts,e))},_updateCircle:function(t){var e=t._point,i=Math.max(Math.round(t._radius),1),n="a"+i+","+(Math.max(Math.round(t._radiusY),1)||i)+" 0 1,0 ",e=t._empty()?"M0 0":"M"+(e.x-i)+","+e.y+n+2*i+",0 "+n+2*-i+",0 ";this._setPath(t,e)},_setPath:function(t,e){t._path.setAttribute("d",e)},_bringToFront:function(t){ce(t._path)},_bringToBack:function(t){de(t._path)}});function Vi(t){return b.svg||b.vml?new Ui(t):null}b.vml&&Ui.include(Mt),A.include({getRenderer:function(t){return t=(t=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer()),this.hasLayer(t)||this.addLayer(t),t},_getPaneRenderer:function(t){var e;return"overlayPane"!==t&&void 0!==t&&(void 0===(e=this._paneRenderers[t])&&(e=this._createRenderer({pane:t}),this._paneRenderers[t]=e),e)},_createRenderer:function(t){return this.options.preferCanvas&&Hi(t)||Vi(t)}});var qi=gi.extend({initialize:function(t,e){gi.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=g(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}}),_t=(Ui.create=Fi,Ui.pointsToPath=dt,vi.geometryToLayer=yi,vi.coordsToLatLng=wi,vi.coordsToLatLngs=bi,vi.latLngToCoords=Pi,vi.latLngsToCoords=Li,vi.getFeature=Ti,vi.asFeature=Mi,A.mergeOptions({boxZoom:!0}),n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){S(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){k(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){T(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),ie(),ye(),this._startPoint=this._map.mouseEventToContainerPoint(t),S(document,{contextmenu:Ae,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=P("div","leaflet-zoom-box",this._container),M(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var e=(t=new f(this._point,this._startPoint)).getSize();Z(this._box,t.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(T(this._box),z(this._container,"leaflet-crosshair")),Te(),xe(),k(document,{contextmenu:Ae,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0),t=new s(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(t).fire("boxzoomend",{boxZoomBounds:t})))},_onKeyDown:function(t){27===t.keyCode&&(this._finish(),this._clearDeferredResetState(),this._resetState())}})),zt=(A.addInitHook("addHandler","boxZoom",_t),A.mergeOptions({doubleClickZoom:!0}),n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,i=e.getZoom(),n=e.options.zoomDelta,i=t.originalEvent.shiftKey?i-n:i+n;"center"===e.options.doubleClickZoom?e.setZoom(i):e.setZoomAround(t.containerPoint,i)}})),Gi=(A.addInitHook("addHandler","doubleClickZoom",zt),A.mergeOptions({dragging:!0,inertia:!0,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0}),n.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new Ge(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),M(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){z(this._map._container,"leaflet-grab"),z(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,e=this._map;e._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=g(this._map.options.maxBounds),this._offsetLimit=c(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,e.fire("movestart").fire("dragstart"),e.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var e,i;this._map.options.inertia&&(e=this._lastTime=+new Date,i=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(i),this._times.push(e),this._prunePositions(e)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1e.max.x&&(t.x=this._viscousLimit(t.x,e.max.x)),t.y>e.max.y&&(t.y=this._viscousLimit(t.y,e.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=((o=this._draggable._newPos.x)-e+i)%t+e-i,o=(o+e+i)%t-e-i,t=Math.abs(n+i)e.getMaxZoom()&&1 Path.expand("../deps", __DIR__)} ] diff --git a/config/dev.exs b/config/dev.exs index 33f5cb1..366cabb 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -26,7 +26,12 @@ config :aprs, AprsWeb.Endpoint, debug_errors: true, secret_key_base: "Vv8Uh9w7tSoac1wBo7A8MyTSE9J+RzNeAzPTGcsW2InUBHpPBgt1dACJrIZorfdH", watchers: [ - esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, + esbuild: + {Esbuild, :install_and_run, + [ + :default, + ~w(--sourcemap=inline --watch --loader:.ts=ts) + ]}, tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} ] @@ -37,12 +42,8 @@ config :aprs, AprsWeb.Endpoint, ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", ~r"priv/gettext/.*(po)$", ~r"lib/aprs_web/(live|views)/.*(ex)$", - # ## SSL Support - # - # In order to use HTTPS in development, a self-signed - # certificate can be generated by running the following - # Mix task: - ~r"lib/aprs_web/templates/.*(eex)$" + ~r"lib/aprs_web/templates/.*(eex)$", + ~r"assets/vendor/.*(js|css)$" ] ] diff --git a/config/prod.exs b/config/prod.exs index e05f601..2542265 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -19,3 +19,11 @@ config :logger, level: :info # Configures Swoosh API Client config :swoosh, :api_client, Aprs.Finch + +config :esbuild, + version: "0.17.11", + default: [ + args: ~w(js/app.js --bundle --target=es2020 --outdir=../priv/static/assets --loader:.ts=ts), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] diff --git a/lib/aprs_web/components/layouts/root.html.heex b/lib/aprs_web/components/layouts/root.html.heex index 9a69a2a..08e8352 100644 --- a/lib/aprs_web/components/layouts/root.html.heex +++ b/lib/aprs_web/components/layouts/root.html.heex @@ -8,8 +8,9 @@ {assigns[:page_title] || "Aprs"} - + + + {@inner_content} diff --git a/mix.exs b/mix.exs index 18bb236..55eb85c 100644 --- a/mix.exs +++ b/mix.exs @@ -64,7 +64,7 @@ defmodule Aprs.MixProject do {:swoosh, "~> 1.3"}, {:telemetry_metrics, "~> 1.0"}, {:telemetry_poller, "~> 1.0"}, - {:esbuild, "~> 0.5", runtime: Mix.env() == :dev}, + {:esbuild, "~> 0.5", runtime: Mix.env() == :dev, config: "assets/esbuild.config.js"}, {:tailwind, "~> 0.3.1", runtime: Mix.env() == :dev}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.0", only: :dev, runtime: false}, diff --git a/priv/static/aprs-symbols/aprs-symbols-24-0-54b527f606747432bc5e148cef29fbab.png b/priv/static/aprs-symbols/aprs-symbols-24-0-54b527f606747432bc5e148cef29fbab.png new file mode 100644 index 0000000..8a2713f Binary files /dev/null and b/priv/static/aprs-symbols/aprs-symbols-24-0-54b527f606747432bc5e148cef29fbab.png differ diff --git a/priv/static/aprs-symbols/aprs-symbols-24-0@2x-4ca288d1e7aec0fa88d38a40cad714ee.png b/priv/static/aprs-symbols/aprs-symbols-24-0@2x-4ca288d1e7aec0fa88d38a40cad714ee.png new file mode 100644 index 0000000..d6c15bb Binary files /dev/null and b/priv/static/aprs-symbols/aprs-symbols-24-0@2x-4ca288d1e7aec0fa88d38a40cad714ee.png differ diff --git a/priv/static/aprs-symbols/aprs-symbols-24-1-0789af3239048c860ed389fa82a99eb3.png b/priv/static/aprs-symbols/aprs-symbols-24-1-0789af3239048c860ed389fa82a99eb3.png new file mode 100644 index 0000000..10126ef Binary files /dev/null and b/priv/static/aprs-symbols/aprs-symbols-24-1-0789af3239048c860ed389fa82a99eb3.png differ diff --git a/priv/static/aprs-symbols/aprs-symbols-24-1@2x-cf4e77e249deb181aacbc9a56fd71b48.png b/priv/static/aprs-symbols/aprs-symbols-24-1@2x-cf4e77e249deb181aacbc9a56fd71b48.png new file mode 100644 index 0000000..f7e1a78 Binary files /dev/null and b/priv/static/aprs-symbols/aprs-symbols-24-1@2x-cf4e77e249deb181aacbc9a56fd71b48.png differ diff --git a/priv/static/aprs-symbols/aprs-symbols-24-2-729bb6d944f4251560da2d180d7d4a0b.png b/priv/static/aprs-symbols/aprs-symbols-24-2-729bb6d944f4251560da2d180d7d4a0b.png new file mode 100644 index 0000000..c1dfa98 Binary files /dev/null and b/priv/static/aprs-symbols/aprs-symbols-24-2-729bb6d944f4251560da2d180d7d4a0b.png differ diff --git a/priv/static/aprs-symbols/aprs-symbols-24-2@2x-4fca30cf66ffe92e8d140dc2cd0dcb78.png b/priv/static/aprs-symbols/aprs-symbols-24-2@2x-4fca30cf66ffe92e8d140dc2cd0dcb78.png new file mode 100644 index 0000000..ac8918a Binary files /dev/null and b/priv/static/aprs-symbols/aprs-symbols-24-2@2x-4fca30cf66ffe92e8d140dc2cd0dcb78.png differ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1467170 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "allowJs": true, + "checkJs": false, + "noEmit": true, + "isolatedModules": true, + "resolveJsonModule": true, + "baseUrl": ".", + "paths": { + "*": [ + "assets/*" + ] + }, + "typeRoots": [ + "./assets/js/types" + ] + }, + "include": [ + "assets/js/**/*.ts", + "assets/js/**/*.js", + "assets/js/types/**/*.d.ts" + ], + "exclude": [ + "node_modules", + "deps", + "_build", + "assets/node_modules" + ] +} \ No newline at end of file