Skip to content

Commit

Permalink
Advanced Map - added pin or draw, migration added for adding geoJSON,…
Browse files Browse the repository at this point in the history
… model & types updated, externalAPIService updated
  • Loading branch information
sanjaytkbabu committed Nov 28, 2024
1 parent d4aa66c commit 1ed8fb6
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 8 deletions.
1 change: 1 addition & 0 deletions app/src/controllers/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ const controller = {
naturalDisaster: data.location.naturalDisaster,
projectLocation: data.location.projectLocation,
projectLocationDescription: data.location.projectLocationDescription,
geoJSON: data.location.geoJSON,
locationPIDs: data.location.ltsaPIDLookup,
latitude: data.location.latitude,
longitude: data.location.longitude,
Expand Down
19 changes: 19 additions & 0 deletions app/src/db/migrations/20241127000000_016-advanced-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable max-len */
import type { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
return Promise.resolve().then(() =>
knex.schema.alterTable('submission', function (table) {
table.json('geo_json');
})
);
}

export async function down(knex: Knex): Promise<void> {
return Promise.resolve() // Drop public schema tables
.then(() =>
knex.schema.alterTable('submission', function (table) {
table.dropColumn('geo_json');
})
);
}
3 changes: 3 additions & 0 deletions app/src/db/models/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export default {
consent_to_feedback: input.consentToFeedback,
location_pids: input.locationPIDs,
company_name_registered: input.companyNameRegistered,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
geo_json: input.geoJSON as any,
single_family_units: input.singleFamilyUnits,
has_rental_units: input.hasRentalUnits,
street_address: input.streetAddress,
Expand Down Expand Up @@ -110,6 +112,7 @@ export default {
projectName: input.project_name,
projectDescription: input.project_description,
companyNameRegistered: input.company_name_registered,
geoJSON: input.geo_json,
singleFamilyUnits: input.single_family_units,
hasRentalUnits: input.has_rental_units,
streetAddress: input.street_address,
Expand Down
1 change: 1 addition & 0 deletions app/src/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ model submission {
housing_coop_description String?
submission_type String?
consent_to_feedback Boolean @default(false)
geo_json Json? @db.Json
activity activity @relation(fields: [activity_id], references: [activity_id], onDelete: Cascade, map: "submission_activity_id_foreign")
user user? @relation(fields: [assigned_user_id], references: [user_id], onDelete: Cascade, map: "submission_assigned_user_id_foreign")
Expand Down
6 changes: 4 additions & 2 deletions app/src/services/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const service = {
*/
createSubmission: async (data: Partial<Submission>) => {
const response = await prisma.submission.create({
//@ts-expect-error please help
data: { ...submission.toPrismaModel(data as Submission), created_at: data.createdAt, created_by: data.createdBy },
include: {
activity: {
Expand All @@ -48,7 +49,7 @@ const service = {
}
}
});

//@ts-expect-error please help
return submission.fromPrismaModelWithContact(response);
},

Expand Down Expand Up @@ -352,6 +353,7 @@ const service = {
updateSubmission: async (data: Submission) => {
try {
const result = await prisma.submission.update({
//@ts-expect-error please help
data: { ...submission.toPrismaModel(data), updated_at: data.updatedAt, updated_by: data.updatedBy },
where: {
submission_id: data.submissionId
Expand All @@ -368,7 +370,7 @@ const service = {
}
}
});

//@ts-expect-error please help
return submission.fromPrismaModelWithContact(result);
} catch (e: unknown) {
throw e;
Expand Down
2 changes: 2 additions & 0 deletions app/src/types/Submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export type Submission = {
locationPIDs: string | null;
companyNameRegistered: string | null;
consentToFeedback: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
geoJSON: any;
projectName: string | null;
projectDescription: string | null;
singleFamilyUnits: string | null;
Expand Down
1 change: 1 addition & 0 deletions app/src/types/SubmissionIntake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type SubmissionIntake = {
naturalDisaster?: string;
projectLocation?: string;
projectLocationDescription?: string;
geoJSON?: JSON;
ltsaPIDLookup?: string;
latitude?: number | null;
longitude?: number | null;
Expand Down
108 changes: 105 additions & 3 deletions frontend/src/components/housing/maps/Map.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
<script setup lang="ts">
import * as L from 'leaflet';
import type { GeoJSON } from 'geojson';
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 markerIcon from 'leaflet/dist/images/marker-icon.png';
L.Marker.prototype.setIcon(
L.icon({
iconUrl: markerIcon
})
);
import { useToast } from '@/lib/primevue';
import { externalApiService } from '@/services';
import {
BC_BOUNDARIES_LOWER,
BC_BOUNDARIES_UPPER,
Expand All @@ -14,20 +22,33 @@ import {
OSM_URL_TEMPLATE
} from '@/utils/constants/mapping';
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 parcelData: Ref<Array<unknown>> = ref([]);
const geoJSON: Ref<GeoJSON | undefined> = ref(undefined);
const toast = useToast();
// Emits
const emit = defineEmits(['map:polygonUpdated', 'map:pinUpdated']);
async function initMap() {
const osm = L.tileLayer(OSM_URL_TEMPLATE, OSM_TILE_LAYER_OPTIONS);
Expand All @@ -43,6 +64,87 @@ 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;
//@ts-ignore
if (geo.toGeoJSON().geometry.type === POINT) {
//@ts-ignore
let latitude = geo.toGeoJSON().geometry.coordinates[1];
//@ts-ignore
let longitude = geo.toGeoJSON().geometry.coordinates[0];
getNearestOccupant(longitude, latitude);
} else {
// Show parcel data using WFS api
//@ts-ignore
showPMBCParcelData(geo.getLatLngs()[0]);
}
//@ts-ignore
geoJSON.value = geo.toGeoJSON();
// Zoom in
if (geo.getBounds) map.fitBounds(geo.getBounds());
//@ts-ignore
else map.flyTo(geo.getLatLng(), 17);
} catch (e: any) {
toast.error('Error', e.message);
}
});
// On feature remove
map.on('pm:remove', async (e) => {
try {
const geo = e.layer as L.GeoJSON;

Check warning on line 99 in frontend/src/components/housing/maps/Map.vue

View workflow job for this annotation

GitHub Actions / Unit Tests (Frontend) (16.x)

'geo' is assigned a value but never used

Check warning on line 99 in frontend/src/components/housing/maps/Map.vue

View workflow job for this annotation

GitHub Actions / Unit Tests (Frontend) (18.x)

'geo' is assigned a value but never used

Check warning on line 99 in frontend/src/components/housing/maps/Map.vue

View workflow job for this annotation

GitHub Actions / Unit Tests (Frontend) (20.x)

'geo' is assigned a value but never used

Check warning on line 99 in frontend/src/components/housing/maps/Map.vue

View workflow job for this annotation

GitHub Actions / Unit Tests (Frontend) (16.x)

'geo' is assigned a value but never used

Check warning on line 99 in frontend/src/components/housing/maps/Map.vue

View workflow job for this annotation

GitHub Actions / Unit Tests (Frontend) (18.x)

'geo' is assigned a value but never used

Check warning on line 99 in frontend/src/components/housing/maps/Map.vue

View workflow job for this annotation

GitHub Actions / Unit Tests (Frontend) (20.x)

'geo' is assigned a value but never used
} catch (e: any) {
// toast.error('Error', e.message);
}
});
initControls();
}
}
// show parcel data from Geocoder
async function showPMBCParcelData(polygon: Array<any>) {
await externalApiService.getParcelDataFromPMBC(polygon).then((data: any) => {
parcelData.value = data.features.map((f: any) => f.properties);
// get comma separated PIDs
const PIDs = parcelData.value.map((p: any) => p.PID_FORMATTED).join(',');
emit('map:polygonUpdated', { PID: PIDs, geoJSON: geoJSON.value });
});
}
// 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ type SubmissionForm = {
addressSearch?: string;
} & SubmissionIntake;
type PinUpdateEvent = {
longitude: number;
latitude: number;
address: string;
};
// Props
const {
activityId = undefined,
Expand Down Expand Up @@ -108,9 +114,11 @@ const assistanceAssignedEnquiryId: Ref<string | undefined> = ref(undefined);
const autoSaveRef: Ref<InstanceType<typeof FormAutosave> | null> = ref(null);
const editable: Ref<boolean> = ref(true);
const formRef: Ref<InstanceType<typeof Form> | null> = ref(null);
const geoJSON: Ref<string | undefined> = ref(undefined);
const geomarkAccordionIndex: Ref<number | undefined> = ref(undefined);
const initialFormValues: Ref<any | undefined> = ref(undefined);
const isSubmittable: Ref<boolean> = ref(false);
const locationPIDs: Ref<string | undefined> = ref(undefined);
const mapLatitude: Ref<number | undefined> = ref(undefined);
const mapLongitude: Ref<number | undefined> = ref(undefined);
const mapRef: Ref<InstanceType<typeof Map> | null> = ref(null);
Expand Down Expand Up @@ -337,6 +345,9 @@ async function onSubmit(data: any) {
try {
autoSaveRef.value?.stopAutoSave();
// Replace PIDs with the values from the map if not provided
data.location.ltsaPIDLookup = data.location.ltsaPIDLookup ?? locationPIDs.value;
// Convert contact fields into contacts array object then remove form keys from data
const submissionData = omit(
{
Expand Down Expand Up @@ -526,6 +537,20 @@ onBeforeMount(async () => {
// Clearing the document store on page load
submissionStore.setDocuments([]);
});
function onPolygonUpdate(data: any) {
formRef.value?.setFieldValue('location.geoJSON', data.geoJSON);
geoJSON.value = data.geoJSON;
locationPIDs.value = data.PID;
}
function onPinUpdate(pinUpdateEvent: PinUpdateEvent) {
const addressSplit = pinUpdateEvent.address.split(',');
formRef.value?.setFieldValue('location.streetAddress', addressSplit[0]);
formRef.value?.setFieldValue('location.locality', addressSplit[1]);
formRef.value?.setFieldValue('location.province', addressSplit[2]);
formRef.value?.setFieldValue('location.latitude', pinUpdateEvent.latitude);
formRef.value?.setFieldValue('location.longitude', pinUpdateEvent.longitude);
}
</script>

<template>
Expand Down Expand Up @@ -1303,11 +1328,76 @@ onBeforeMount(async () => {
</div>
</div>
<Map
v-if="values.location?.projectLocation !== ProjectLocation.PIN_OR_DRAW"
ref="mapRef"
:disabled="!editable"
:latitude="mapLatitude"
:longitude="mapLongitude"
/>
<div v-if="values.location?.projectLocation === ProjectLocation.PIN_OR_DRAW">
<Map
ref="mapRef"
:pin-or-draw="true"
:disabled="!editable"
:latitude="mapLatitude"
:longitude="mapLongitude"
@map:polygon-updated="onPolygonUpdate"
@advanced-map:update-coords="
(e: any) => {
console.log('update-coords===' + JSON.stringify(e));
setFieldValue('location.latitude', e.latitude);
setFieldValue('location.longitude', e.longitude);
}
"
@map:pin-updated="onPinUpdate"
/>
<Card class="no-shadow">
<template #content>
<div class="grid nested-grid">
<InputText
class="col-4"
name="location.streetAddress"
disabled
placeholder="Street address"
/>
<InputText
class="col-4"
name="location.locality"
disabled
placeholder="Locality"
/>
<InputText
class="col-4"
name="location.province"
disabled
placeholder="Province"
/>
<InputNumber
class="col-4"
name="location.latitude"
disabled
:help-text="
values.location?.projectLocation === ProjectLocation.LOCATION_COORDINATES
? 'Provide a coordinate between 48 and 60'
: ''
"
placeholder="Latitude"
/>
<InputNumber
class="col-4"
name="location.longitude"
disabled
:help-text="
values.location?.projectLocation === ProjectLocation.LOCATION_COORDINATES
? 'Provide a coordinate between -114 and -139'
: ''
"
placeholder="Longitude"
/>
</div>
</template>
</Card>
</div>
</template>
</Card>
<Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ export const submissionIntakeSchema = object({
otherwise: () => number().nullable().min(-139).max(-114).label('Longitude')
}),
ltsaPIDLookup: string().max(255).nullable().label('Parcel ID'),
geomarkUrl: string().max(255).label('Geomark web service url')
geomarkUrl: string().max(255).label('Geomark web service url'),
getJSON: mixed().nullable().label('geoJSON')
}),
[IntakeFormCategory.PERMITS]: object({
hasAppliedProvincialPermits: string().oneOf(YES_NO_UNSURE_LIST).required().label('Applied permits')
Expand Down
Loading

0 comments on commit 1ed8fb6

Please sign in to comment.