From 97f0cd8d5b9714d4b67ab8437977230a3156d48b Mon Sep 17 00:00:00 2001 From: Anthony Castronova Date: Wed, 15 Apr 2026 14:56:03 -0400 Subject: [PATCH 1/7] moved perceptual models logic from theleafmap component into the mapview view --- frontend/src/components/TheLeafletMap.vue | 206 +--------------------- frontend/src/views/MapView.vue | 204 ++++++++++++++++++++- 2 files changed, 203 insertions(+), 207 deletions(-) diff --git a/frontend/src/components/TheLeafletMap.vue b/frontend/src/components/TheLeafletMap.vue index 1a94353..0c17027 100644 --- a/frontend/src/components/TheLeafletMap.vue +++ b/frontend/src/components/TheLeafletMap.vue @@ -11,7 +11,6 @@ import L from 'leaflet' import 'leaflet-draw' import 'leaflet.markercluster' -import { storeToRefs } from 'pinia' import * as esriLeaflet from 'esri-leaflet' import { onMounted, onUpdated } from 'vue' import { useMapStore } from '@/stores/map' @@ -21,24 +20,13 @@ import 'leaflet-draw/dist/leaflet.draw.css' import 'leaflet.markercluster/dist/MarkerCluster.css' import 'leaflet.markercluster/dist/MarkerCluster.Default.css' -const emit = defineEmits(['onFilter']) - const mapStore = useMapStore() -const { - mapLoaded, - userTouchedFilter, - currentFilteredData, - selectedSpatialZones, - selectedTemporalZones, - selectedProcesses, - searchTerm -} = storeToRefs(mapStore) onUpdated(() => { mapStore.leaflet.invalidateSize() }) -onMounted(async () => { +onMounted(() => { mapStore.leaflet = L.map('mapContainer', { minZoom: 2 }).setView([0, 11], 2) mapStore.layerGroup = new L.LayerGroup() mapStore.layerGroup.addTo(mapStore.leaflet) @@ -77,198 +65,8 @@ onMounted(async () => { Esri_WorldImagery.addTo(mapStore.leaflet) Esri_Hydro_Reference_Overlay.addTo(mapStore.leaflet) - await mapStore.fetchPerceptualModelsGeojson() - const bounds = L.latLngBounds(mapStore.allAvailableCoordinates) - mapStore.leaflet.setMaxBounds(bounds) - - const mixed = { - 'Perceptual Models': mapStore.layerGroup, - 'Esri Hydro Reference Overlay': Esri_Hydro_Reference_Overlay - } - L.control.layers(baselayers, mixed).addTo(mapStore.leaflet) - - const drawnItems = new L.FeatureGroup() - mapStore.drawnItems = drawnItems - mapStore.leaflet.addLayer(drawnItems) - drawnItems.setZIndex(1000) - - let currentRectangle = null - - L.Control.RectangleToggle = L.Control.extend({ - options: { position: 'topleft' }, - onAdd: function () { - const container = L.DomUtil.create( - 'div', - 'leaflet-bar leaflet-control leaflet-control-custom draw-toggle-btn' - ) - updateDrawButton(container) - let drawer = null - L.DomEvent.on(container, 'click', () => { - if (currentRectangle || drawer) { - drawnItems.clearLayers() - currentRectangle = null - mapStore.filterFeatures( - (feature) => { - if (feature.geometry.type === 'Point') { - const [lng, lat] = feature.geometry.coordinates - return currentRectangle.getBounds().contains([lat, lng]) - } - return false - }, - 'remove', - 'rectangle' - ) - userTouchedFilter.value = false - emit('onFilter', { - selectedSpatialZones, - selectedTemporalZones, - selectedProcesses, - searchTerm, - filteredFeatures: currentFilteredData.value - }) - updateDrawButton(container) - } else { - const drawer = new L.Draw.Rectangle(mapStore.leaflet, { - shapeOptions: { - color: '#3388ff', - weight: 2, - opacity: 0.8, - fillOpacity: 0.3 - }, - showArea: false - }) - drawer.enable() - currentRectangle = {} - updateDrawButton(container) - - const drawHandler = (e) => { - mapStore.leaflet.off(L.Draw.Event.CREATED, drawHandler) - drawnItems.clearLayers() - currentRectangle = e.layer - currentRectangle.feature = { - type: 'Feature', - geometry: { - type: 'Polygon', - coordinates: [ - currentRectangle.getLatLngs()[0].map((latLng) => [latLng.lng, latLng.lat]) - ] - }, - properties: {} - } - drawnItems.addLayer(currentRectangle) - mapStore.leaflet.fitBounds(currentRectangle.getBounds()) - - mapStore.filterFeatures( - (feature) => { - if (feature.geometry.type === 'Point') { - const [lng, lat] = feature.geometry.coordinates - return currentRectangle.getBounds().contains([lat, lng]) - } - return false - }, - 'add', - 'rectangle' - ) - - userTouchedFilter.value = true - emit('onFilter', { - selectedSpatialZones, - selectedTemporalZones, - selectedProcesses, - searchTerm, - filteredFeatures: currentFilteredData.value - }) - - updateDrawButton(container) - } - - mapStore.leaflet.on(L.Draw.Event.CREATED, drawHandler) - mapStore.leaflet.once('draw:drawstop', () => { - // If no rectangle was drawn, reset the toggle state - if (!drawnItems.getLayers().length) { - currentRectangle = null - updateDrawButton(container) - mapStore.leaflet.off(L.Draw.Event.CREATED, drawHandler) - } - }) - } - }) - - return container - } - }) - mapStore.leaflet.addControl(new L.Control.RectangleToggle()) - - function updateDrawButton(container) { - if (currentRectangle) { - container.innerHTML = 'close' - container.style.background = 'white' - container.title = 'Clear box' - } else { - container.style.backgroundImage = "url('/DrawIcon.ico')" - container.innerHTML = '' - container.title = 'Draw a box' - } - container.style.backgroundRepeat = 'no-repeat' - container.style.backgroundSize = '60% 60%' - container.style.backgroundPosition = 'center' - container.style.borderRadius = '4px' - container.style.width = '34px' - container.style.height = '34px' - container.style.cursor = 'pointer' - container.style.display = 'flex' - container.style.alignItems = 'center' - container.style.justifyContent = 'center' - } - L.drawLocal.draw.handlers.rectangle.tooltip.start = 'Click and drag to draw a box' - L.Control.ClearFilters = L.Control.extend({ - options: { position: 'topleft' }, - onAdd: function () { - const container = L.DomUtil.create( - 'div', - 'leaflet-bar leaflet-control leaflet-control-custom' - ) - container.title = 'Reset Filters' - - container.style.backgroundImage = "url('/ClearFilter.ico')" - container.style.backgroundRepeat = 'no-repeat' - container.style.backgroundSize = '60% 60%' - container.style.backgroundColor = 'white' - container.style.backgroundPosition = 'center' - container.style.borderRadius = '4px' - container.style.width = '34px' - container.style.height = '34px' - container.style.cursor = 'pointer' - container.style.display = 'flex' - container.style.alignItems = 'center' - container.style.justifyContent = 'center' - - L.DomEvent.on(container, 'click', () => { - mapStore.clearAllFilters() - emit('onFilter', { - selectedSpatialZones, - selectedTemporalZones, - selectedProcesses, - searchTerm, - filteredFeatures: currentFilteredData.value - }) - }) - - return container - } - }) - mapStore.leaflet.addControl(new L.Control.ClearFilters()) - - mapStore.leaflet.on('click', function (e) { - mapClick(e) - }) - - mapLoaded.value = true + L.control.layers(baselayers).addTo(mapStore.leaflet) }) - -async function mapClick() { - return -} From 550e430bc774fc9deda802b3900ce35bbc3eb3b5 Mon Sep 17 00:00:00 2001 From: Anthony Castronova Date: Wed, 15 Apr 2026 19:17:40 -0400 Subject: [PATCH 3/7] re-added styling to the legend --- frontend/src/views/PerceptualView.vue | 74 +++++++++++++++++++-------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/frontend/src/views/PerceptualView.vue b/frontend/src/views/PerceptualView.vue index 04cd3ef..2b1f757 100644 --- a/frontend/src/views/PerceptualView.vue +++ b/frontend/src/views/PerceptualView.vue @@ -8,20 +8,7 @@ -
+
@@ -98,13 +85,6 @@ onMounted(async () => { }) mapStore.leaflet.addControl(layerControl) - // Move zoom control to the right - mapStore.leaflet.removeControl(mapStore.leaflet.zoomControl) - mapStore.leaflet.zoomControl = L.control.zoom({ position: 'topright' }).addTo(mapStore.leaflet) - - mapStore.leaflet.on('overlayadd', updateLegend) - mapStore.leaflet.on('overlayremove', updateLegend) - mapStore.leaflet.on('click', function (e) { if (!activeWmsLayer) return const url = getFeatureInfoUrl(mapStore.leaflet, activeWmsLayer, e.latlng) @@ -115,6 +95,18 @@ onMounted(async () => { }) }) + // Move zoom control to the right + mapStore.leaflet.removeControl(mapStore.leaflet.zoomControl) + mapStore.leaflet.zoomControl = L.control.zoom({ position: 'topright' }).addTo(mapStore.leaflet) + + // add hooks to update the legend when layers are added or removed. + // Then call the updateLegend function manually. This is necessary + // because the overlayadd isn't called when initially adding the + // layers to the map. + mapStore.leaflet.on('overlayadd', updateLegend) + mapStore.leaflet.on('overlayremove', updateLegend) + updateLegend() + // set the mapLoaded flag to true after the map and layers have been initialized // this will turn off the loading overlay and allow the map to be displayed mapLoaded.value = true @@ -161,4 +153,42 @@ function getFeatureInfoUrl(map, layer, latlng) { } - + From accd7fe4d638ab83af2de17510d24cfe2c387170 Mon Sep 17 00:00:00 2001 From: Anthony Castronova Date: Thu, 16 Apr 2026 12:32:40 -0400 Subject: [PATCH 4/7] centered zoom to north america and set max zoom and bounds so the entire earth is not visible --- frontend/src/views/PerceptualView.vue | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/views/PerceptualView.vue b/frontend/src/views/PerceptualView.vue index 2b1f757..fb5ad9c 100644 --- a/frontend/src/views/PerceptualView.vue +++ b/frontend/src/views/PerceptualView.vue @@ -62,6 +62,17 @@ const legendUrls = { } onMounted(async () => { + // set the map bounds so that only North America is visible + // also set the minimum zoom level to prevent users from zooming + // out too far and seeing the entire world. + mapStore.leaflet.setMaxBounds( + L.latLngBounds([ + [5, -170], // SW coordinate of bbox + [75, -50] // NE coordinate of bbox + ]) + ) + mapStore.leaflet.setMinZoom(3) + // Add the perceptual models "Domain" layer to the map // Only adding the "Domain" layer by default to // control which layer is initially active. From afc0384e36ac11c0597e2b74f2f3eb3e5232e742 Mon Sep 17 00:00:00 2001 From: Anthony Castronova Date: Thu, 16 Apr 2026 12:59:01 -0400 Subject: [PATCH 5/7] cleaned legend styling so it is consistent with the layers toggle component --- frontend/src/views/PerceptualView.vue | 43 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/frontend/src/views/PerceptualView.vue b/frontend/src/views/PerceptualView.vue index fb5ad9c..2e424ae 100644 --- a/frontend/src/views/PerceptualView.vue +++ b/frontend/src/views/PerceptualView.vue @@ -131,7 +131,7 @@ function updateLegend() { const provinceOn = mapStore.leaflet.hasLayer(wmsLayerProvince) let html = '' if (domainOn) { - html += `Domain Legend` + html += `Domain Legend` } if (provinceOn) { html += `Province Legend` @@ -165,20 +165,28 @@ function getFeatureInfoUrl(map, layer, latlng) {