import { safePromiseAll } from './fetch.mjs'; import { loadData } from './data-loader.mjs'; import { getSmallIconFromWmoCode } from '../icons.mjs'; import { getOpenMeteoObservationSnapshot } from './weather.mjs'; const BASE_MAP_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}'; const BOUNDARY_MAP_URL = 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}'; const DEFAULT_MAX_NEARBY_MARKERS = 7; const MIN_CITY_DISTANCE_METERS = 25000; const MIN_MARKER_PIXEL_DISTANCE = 85; let radarCitiesCache = null; const createMap = (mapElement) => window.L.map(mapElement, { zoomControl: false, dragging: false, touchZoom: false, scrollWheelZoom: false, doubleClickZoom: false, boxZoom: false, keyboard: false, tap: false, attributionControl: false, preferCanvas: true, }); const addBaseLayers = (map) => { const baseLayer = window.L.tileLayer(BASE_MAP_URL, { maxZoom: 10, minZoom: 1, crossOrigin: true, className: 'radar-base-layer', }).addTo(map); const boundaryLayer = window.L.tileLayer(BOUNDARY_MAP_URL, { maxZoom: 10, minZoom: 1, opacity: 0.6, crossOrigin: true, className: 'radar-boundary-layer', }).addTo(map); return { baseLayer, boundaryLayer }; }; const setPrimaryLocationMarker = (map, existingMarker, latitude, longitude) => { if (existingMarker && map.hasLayer(existingMarker)) { map.removeLayer(existingMarker); } return window.L.circleMarker([latitude, longitude], { radius: 5, color: '#000', weight: 2, fillColor: '#ff0', fillOpacity: 1, interactive: false, className: 'location-marker', }).addTo(map); }; const loadRadarCities = async () => { if (!radarCitiesCache) { radarCitiesCache = await loadData('radarcities'); } return radarCitiesCache ?? []; }; const selectNearbyCities = (map, sourceLocation, cities, options = {}) => { const { maxMarkers = DEFAULT_MAX_NEARBY_MARKERS, minCityDistanceMeters = MIN_CITY_DISTANCE_METERS, minMarkerPixelDistance = MIN_MARKER_PIXEL_DISTANCE, } = options; const bounds = map.getBounds(); const currentLatLng = window.L.latLng(sourceLocation.latitude, sourceLocation.longitude); const visibleCities = cities .filter((city) => bounds.contains([city.lat, city.lon])) .filter((city) => currentLatLng.distanceTo([city.lat, city.lon]) > minCityDistanceMeters) .map((city) => ({ ...city, distance: currentLatLng.distanceTo([city.lat, city.lon]), point: map.latLngToContainerPoint([city.lat, city.lon]), })) .sort((a, b) => a.distance - b.distance); const selected = []; visibleCities.forEach((city) => { if (selected.length >= maxMarkers) return; const overlaps = selected.some((existingCity) => existingCity.point.distanceTo(city.point) < minMarkerPixelDistance); if (!overlaps) selected.push(city); }); if (selected.length === 0 && visibleCities.length > 0) { selected.push(visibleCities[0]); } return selected; }; const buildNearbyWeatherMarker = (city, observation) => { const icon = getSmallIconFromWmoCode(observation.weatherCode, observation.isDay); const markerHtml = `