From 51200d3ae0ed174fe9f1b1325515b7e868325e25 Mon Sep 17 00:00:00 2001 From: Sanjay Babu Date: Thu, 23 Jan 2025 11:32:50 -0800 Subject: [PATCH 01/10] Frontend - SubmissionIntakeForm, Map updated with pin or draw, externalAPIService updated, types, enums updated --- frontend/src/components/housing/maps/Map.vue | 100 +++++++++++++++++- .../housing/submission/SubmissionForm.vue | 9 +- .../submission/SubmissionIntakeForm.vue | 86 +++++++++++++++ .../submission/SubmissionIntakeSchema.ts | 3 +- frontend/src/services/externalApiService.ts | 8 ++ frontend/src/types/Submission.ts | 2 + frontend/src/utils/constants/housing.ts | 6 +- frontend/src/utils/enums/housing.ts | 3 +- .../housing/projects/ProjectsList.spec.ts | 1 + 9 files changed, 212 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/housing/maps/Map.vue b/frontend/src/components/housing/maps/Map.vue index 758512ed..0d4b9bdd 100644 --- a/frontend/src/components/housing/maps/Map.vue +++ b/frontend/src/components/housing/maps/Map.vue @@ -3,8 +3,10 @@ import * as L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import '@geoman-io/leaflet-geoman-free'; import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css'; -import { onMounted, onUpdated, watch } from 'vue'; +import { onMounted, onUpdated, ref, watch } from 'vue'; +import { useToast } from '@/lib/primevue'; +import { externalApiService } from '@/services'; import { BC_BOUNDARIES_LOWER, BC_BOUNDARIES_UPPER, @@ -14,22 +16,37 @@ import { OSM_URL_TEMPLATE } from '@/utils/constants/mapping'; +import type { GeoJSON } from 'geojson'; +import type { Ref } from 'vue'; + // Props const { disabled = false, latitude = undefined, - longitude = undefined + longitude = undefined, + pinOrDraw = false } = defineProps<{ disabled?: boolean; latitude?: number; longitude?: number; + pinOrDraw?: boolean; }>(); +// Constants +const POINT = 'Point'; + // Actions let marker: L.Marker; let map: L.Map; +const geoJSON: Ref = ref(undefined); +const toast = useToast(); +const oldLayer = ref(undefined); + +// Emits +const emit = defineEmits(['map:polygonUpdated', 'map:pinUpdated']); async function initMap() { + L.Icon.Default.prototype = new L.Icon(MAP_ICON_OPTIONS_RED); const osm = L.tileLayer(OSM_URL_TEMPLATE, OSM_TILE_LAYER_OPTIONS); map = L.map('map', { @@ -43,6 +60,83 @@ async function initMap() { map.on('drag', function () { map.panInsideBounds(bcBounds, { animate: false }); }); + + if (pinOrDraw) { + map.on('pm:create', async (e) => { + try { + const geo = e.layer as L.GeoJSON; + + if (oldLayer.value) { + //@ts-ignore - insufficient type definitions + map.removeLayer(oldLayer.value); + oldLayer.value = undefined; + } + oldLayer.value = e.layer; + + //@ts-ignore - insufficient type definitions + if (geo.toGeoJSON().geometry.type === POINT) { + //@ts-ignore - insufficient type definitions + let latitude = geo.toGeoJSON().geometry.coordinates[1]; + //@ts-ignore - insufficient type definitions + let longitude = geo.toGeoJSON().geometry.coordinates[0]; + + getNearestOccupant(longitude, latitude); + } else { + geoJSON.value = geo.toGeoJSON(); + + emit('map:polygonUpdated', { geoJSON: geoJSON.value }); + } + // Zoom in + if (geo.getBounds) map.fitBounds(geo.getBounds()); + //@ts-ignore - insufficient type definitions + else map.flyTo(geo.getLatLng(), 17); + } catch (e: any) { + toast.error('Error', e.message); + } + }); + initControls(); + } +} + +// Function to remove all markers from the map +function removeAllMarkers() { + map.eachLayer((layer) => { + if (layer instanceof L.Marker) { + map.removeLayer(layer); + } + }); +} + +// show parcel data from Geocoder +async function getNearestOccupant(longitude: string, latitude: string) { + const result = await externalApiService.getNearestOccupant(longitude, latitude); + const address = result.data.properties.occupantAliasAddress; + if (!address || address.length == 0) { + toast.warn('No address found'); + } + emit('map:pinUpdated', { + longitude: longitude, + latitude: latitude, + address: address + }); +} + +function initControls() { + map.pm.addControls({ + position: 'topleft', + + // Create + drawCircleMarker: false, + drawCircle: false, + drawText: false, + drawPolyline: false, + + // Edit + cutPolygon: false, + dragMode: false, + editMode: false, + rotateMode: false + }); } function disableInteraction() { @@ -83,6 +177,8 @@ defineExpose({ resizeMap }); onMounted(async () => { await initMap(); + // Clear all markers on mount + removeAllMarkers(); }); onUpdated(async () => { diff --git a/frontend/src/components/housing/submission/SubmissionForm.vue b/frontend/src/components/housing/submission/SubmissionForm.vue index 4a90e5a0..3ed5fd95 100644 --- a/frontend/src/components/housing/submission/SubmissionForm.vue +++ b/frontend/src/components/housing/submission/SubmissionForm.vue @@ -324,6 +324,7 @@ onMounted(async () => { locality: submission.locality, province: submission.province, locationPIDs: submission.locationPIDs, + locationPIDsAuto: submission.locationPIDsAuto, latitude: submission.latitude, longitude: submission.longitude, geomarkUrl: submission.geomarkUrl, @@ -669,7 +670,13 @@ onMounted(async () => { :options="YES_NO_LIST" />