From caedfcf0de865e029fceef739c2db1d832a002e6 Mon Sep 17 00:00:00 2001 From: Sanjay Babu Date: Wed, 27 Nov 2024 14:15:12 -0800 Subject: [PATCH] Advanced Map - added pin or draw, migration added for adding geoJSON, model & types updated, externalAPIService updated --- .github/environments/values.dev.yaml | 1 + .github/environments/values.prod.yaml | 1 + .github/environments/values.test.yaml | 1 + app/config/custom-environment-variables.json | 3 + app/src/controllers/submission.ts | 1 + .../20241127000000_016-advanced-map.ts | 19 +++ app/src/db/models/submission.ts | 3 + app/src/db/prisma/schema.prisma | 1 + app/src/services/submission.ts | 6 +- app/src/types/Submission.ts | 2 + app/src/types/SubmissionIntake.ts | 1 + charts/pcns/values.yaml | 1 + frontend/src/components/housing/maps/Map.vue | 108 +++++++++++++++++- .../submission/SubmissionIntakeForm.vue | 90 +++++++++++++++ .../submission/SubmissionIntakeSchema.ts | 3 +- frontend/src/services/externalApiService.ts | 55 +++++++++ frontend/src/types/Submission.ts | 5 + frontend/src/utils/constants/housing.ts | 6 +- frontend/src/utils/enums/housing.ts | 3 +- 19 files changed, 302 insertions(+), 8 deletions(-) create mode 100644 app/src/db/migrations/20241127000000_016-advanced-map.ts diff --git a/.github/environments/values.dev.yaml b/.github/environments/values.dev.yaml index 4c513e90..0e588772 100644 --- a/.github/environments/values.dev.yaml +++ b/.github/environments/values.dev.yaml @@ -10,6 +10,7 @@ config: FRONTEND_GEOCODER_APIPATH: https://geocoder.api.gov.bc.ca FRONTEND_OIDC_AUTHORITY: https://dev.loginproxy.gov.bc.ca/auth/realms/standard FRONTEND_OIDC_CLIENTID: nr-permit-connect-navigator-service-5188 + FRONTEND_OPENMAPS_APIPATH: https://openmaps.gov.bc.ca/geo/pub/wfs?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&outputFormat=json&typeName=WHSE_CADASTRE.PMBC_PARCEL_FABRIC_POLY_SVW&CQL_FILTER=INTERSECTS(SHAPE, POLYGON ((query))) FRONTEND_OPENSTREETMAP_APIPATH: https://tile.openstreetmap.org FRONTEND_ORGBOOK_APIPATH: https://orgbook.gov.bc.ca/api/v4 SERVER_APIPATH: /api/v1 diff --git a/.github/environments/values.prod.yaml b/.github/environments/values.prod.yaml index 5378a424..52bb2afd 100644 --- a/.github/environments/values.prod.yaml +++ b/.github/environments/values.prod.yaml @@ -10,6 +10,7 @@ config: FRONTEND_GEOCODER_APIPATH: https://geocoder.api.gov.bc.ca FRONTEND_OIDC_AUTHORITY: https://loginproxy.gov.bc.ca/auth/realms/standard FRONTEND_OIDC_CLIENTID: nr-permit-connect-navigator-service-5188 + FRONTEND_OPENMAPS_APIPATH: https://openmaps.gov.bc.ca/geo/pub/wfs?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&outputFormat=json&typeName=WHSE_CADASTRE.PMBC_PARCEL_FABRIC_POLY_SVW&CQL_FILTER=INTERSECTS(SHAPE, POLYGON ((query))) FRONTEND_OPENSTREETMAP_APIPATH: https://tile.openstreetmap.org FRONTEND_ORGBOOK_APIPATH: https://orgbook.gov.bc.ca/api/v4 SERVER_APIPATH: /api/v1 diff --git a/.github/environments/values.test.yaml b/.github/environments/values.test.yaml index 229a040f..adbc19d5 100644 --- a/.github/environments/values.test.yaml +++ b/.github/environments/values.test.yaml @@ -10,6 +10,7 @@ config: FRONTEND_GEOCODER_APIPATH: https://geocoder.api.gov.bc.ca FRONTEND_OIDC_AUTHORITY: https://test.loginproxy.gov.bc.ca/auth/realms/standard FRONTEND_OIDC_CLIENTID: nr-permit-connect-navigator-service-5188 + FRONTEND_OPENMAPS_APIPATH: https://openmaps.gov.bc.ca/geo/pub/wfs?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&outputFormat=json&typeName=WHSE_CADASTRE.PMBC_PARCEL_FABRIC_POLY_SVW&CQL_FILTER=INTERSECTS(SHAPE, POLYGON ((query))) FRONTEND_OPENSTREETMAP_APIPATH: https://tile.openstreetmap.org FRONTEND_ORGBOOK_APIPATH: https://orgbook.gov.bc.ca/api/v4 SERVER_APIPATH: /api/v1 diff --git a/app/config/custom-environment-variables.json b/app/config/custom-environment-variables.json index 17d964ea..fa5d3032 100644 --- a/app/config/custom-environment-variables.json +++ b/app/config/custom-environment-variables.json @@ -21,6 +21,9 @@ "authority": "FRONTEND_OIDC_AUTHORITY", "clientId": "FRONTEND_OIDC_CLIENTID" }, + "openMaps": { + "apiPath": "FRONTEND_OPENMAPS_APIPATH" + }, "openStreetMap": { "apiPath": "FRONTEND_OPENSTREETMAP_APIPATH" }, diff --git a/app/src/controllers/submission.ts b/app/src/controllers/submission.ts index f9eb5812..d98ccce7 100644 --- a/app/src/controllers/submission.ts +++ b/app/src/controllers/submission.ts @@ -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, diff --git a/app/src/db/migrations/20241127000000_016-advanced-map.ts b/app/src/db/migrations/20241127000000_016-advanced-map.ts new file mode 100644 index 00000000..9ece70d9 --- /dev/null +++ b/app/src/db/migrations/20241127000000_016-advanced-map.ts @@ -0,0 +1,19 @@ +/* eslint-disable max-len */ +import type { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + return Promise.resolve().then(() => + knex.schema.alterTable('submission', function (table) { + table.json('geo_json'); + }) + ); +} + +export async function down(knex: Knex): Promise { + return Promise.resolve() // Drop public schema tables + .then(() => + knex.schema.alterTable('submission', function (table) { + table.dropColumn('geo_json'); + }) + ); +} diff --git a/app/src/db/models/submission.ts b/app/src/db/models/submission.ts index ce6564e6..3b48d0a4 100644 --- a/app/src/db/models/submission.ts +++ b/app/src/db/models/submission.ts @@ -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, @@ -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, diff --git a/app/src/db/prisma/schema.prisma b/app/src/db/prisma/schema.prisma index 3f7a8d88..1df9b7e1 100644 --- a/app/src/db/prisma/schema.prisma +++ b/app/src/db/prisma/schema.prisma @@ -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") diff --git a/app/src/services/submission.ts b/app/src/services/submission.ts index 34fbe3f5..c690d797 100644 --- a/app/src/services/submission.ts +++ b/app/src/services/submission.ts @@ -35,6 +35,7 @@ const service = { */ createSubmission: async (data: Partial) => { 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: { @@ -48,7 +49,7 @@ const service = { } } }); - + //@ts-expect-error please help return submission.fromPrismaModelWithContact(response); }, @@ -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 @@ -368,7 +370,7 @@ const service = { } } }); - + //@ts-expect-error please help return submission.fromPrismaModelWithContact(result); } catch (e: unknown) { throw e; diff --git a/app/src/types/Submission.ts b/app/src/types/Submission.ts index 49725c25..f40b23bb 100644 --- a/app/src/types/Submission.ts +++ b/app/src/types/Submission.ts @@ -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; diff --git a/app/src/types/SubmissionIntake.ts b/app/src/types/SubmissionIntake.ts index 75d3d6c6..4b2cffd2 100644 --- a/app/src/types/SubmissionIntake.ts +++ b/app/src/types/SubmissionIntake.ts @@ -38,6 +38,7 @@ export type SubmissionIntake = { naturalDisaster?: string; projectLocation?: string; projectLocationDescription?: string; + geoJSON?: JSON; ltsaPIDLookup?: string; latitude?: number | null; longitude?: number | null; diff --git a/charts/pcns/values.yaml b/charts/pcns/values.yaml index 054dd129..133106be 100644 --- a/charts/pcns/values.yaml +++ b/charts/pcns/values.yaml @@ -139,6 +139,7 @@ config: FRONTEND_GEOCODER_APIPATH: ~ FRONTEND_OIDC_AUTHORITY: ~ FRONTEND_OIDC_CLIENTID: ~ + FRONTEND_OPENMAPS_APIPATH: ~ FRONTEND_OPENSTREETMAP_APIPATH: ~ FRONTEND_ORGBOOK_APIPATH: ~ diff --git a/frontend/src/components/housing/maps/Map.vue b/frontend/src/components/housing/maps/Map.vue index 44422d6b..b154f549 100644 --- a/frontend/src/components/housing/maps/Map.vue +++ b/frontend/src/components/housing/maps/Map.vue @@ -1,10 +1,18 @@ diff --git a/frontend/src/components/housing/submission/SubmissionIntakeSchema.ts b/frontend/src/components/housing/submission/SubmissionIntakeSchema.ts index 27248e5c..4620fd0d 100644 --- a/frontend/src/components/housing/submission/SubmissionIntakeSchema.ts +++ b/frontend/src/components/housing/submission/SubmissionIntakeSchema.ts @@ -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') diff --git a/frontend/src/services/externalApiService.ts b/frontend/src/services/externalApiService.ts index 534bf011..bdfd3825 100644 --- a/frontend/src/services/externalApiService.ts +++ b/frontend/src/services/externalApiService.ts @@ -1,5 +1,8 @@ +import proj4 from 'proj4'; +import { storeToRefs } from 'pinia'; import { geocoderAxios, orgBookAxios } from './interceptors'; import { ADDRESS_CODER_QUERY_PARAMS, ORG_BOOK_QUERY_PARAMS } from '@/utils/constants/housing'; +import { useConfigStore } from '@/store'; import type { AxiosResponse } from 'axios'; @@ -21,5 +24,57 @@ export default { ...ADDRESS_CODER_QUERY_PARAMS } }); + }, + + /** + * @function getParcelDataFromPMBC + * DataBC’s Open Web Services + * Accessing geographic data via WMS/WFS + * Services Provided by OCIO - Digital Platforms & Data - Data Systems & Services + * ref: https://docs.geoserver.org/main/en/user/services/wfs/reference.html#getfeature + * ref: https://catalogue.data.gov.bc.ca/dataset/parcelmap-bc-parcel-fabric + * @returns parcel data in JSON + */ + async getParcelDataFromPMBC(polygon: Array) { + const { getConfig } = storeToRefs(useConfigStore()); + // close polygon by re-adding first point to end of array + const points = polygon.concat(polygon[0]); + + // define the source and destination layer types + // leaflet map layer + const source = proj4.Proj('EPSG:4326'); // gps format of leaflet map + // projection (BC Parcel data layer) + proj4.defs( + 'EPSG:3005', + 'PROJCS["NAD83 / BC Albers", GEOGCS["NAD83", DATUM["North_American_Datum_1983", SPHEROID["GRS 1980",6378137,298.257222101, AUTHORITY["EPSG","7019"]], TOWGS84[0,0,0,0,0,0,0], AUTHORITY["EPSG","6269"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.0174532925199433, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4269"]], PROJECTION["Albers_Conic_Equal_Area"], PARAMETER["standard_parallel_1",50], PARAMETER["standard_parallel_2",58.5], PARAMETER["latitude_of_center",45], PARAMETER["longitude_of_center",-126], PARAMETER["false_easting",1000000], PARAMETER["false_northing",0], UNIT["metre",1, AUTHORITY["EPSG","9001"]], AXIS["Easting",EAST], AXIS["Northing",NORTH], AUTHORITY["EPSG","3005"]]' + ); + const dest = proj4.Proj('EPSG:3005'); + + // convert lat/long for WFS query + const result = points.map((point) => { + //@ts-ignore + return proj4(source, dest, { x: point.lng, y: point.lat }); + }); + + // built query string for WFS request + let query = ''; + result.forEach((point, index, array) => { + query = query.concat(point.x, ' ', point.y); + if (index < array.length - 1) query = query.concat(', '); + }); + + const url = getConfig.value.openMaps.apiPath.replace('query', query); + + const response = await fetch(url); + const data = await response.json(); + return data; + }, + + async getNearestOccupant(longitude: string, lattitude: string) { + return geocoderAxios().get('/occupants/nearest.json', { + params: { + point: `${longitude},${lattitude}` + } + }); } }; diff --git a/frontend/src/types/Submission.ts b/frontend/src/types/Submission.ts index e034a8fc..d015a9c1 100644 --- a/frontend/src/types/Submission.ts +++ b/frontend/src/types/Submission.ts @@ -15,6 +15,11 @@ export type Submission = { companyNameRegistered: string; consentToFeedback?: boolean; isDevelopedInBC: string; + contactApplicantRelationship: string; + contactPreference: string; + contactPhoneNumber: string; + contactEmail: string; + geoJSON: JSON; projectName: string; projectDescription: string; projectLocationDescription: string; diff --git a/frontend/src/utils/constants/housing.ts b/frontend/src/utils/constants/housing.ts index 3f90d233..04f9e4f5 100644 --- a/frontend/src/utils/constants/housing.ts +++ b/frontend/src/utils/constants/housing.ts @@ -105,7 +105,11 @@ export const PERMIT_STATUS_LIST = [ PermitStatus.PENDING ]; -export const PROJECT_LOCATION_LIST = [ProjectLocation.STREET_ADDRESS, ProjectLocation.LOCATION_COORDINATES]; +export const PROJECT_LOCATION_LIST = [ + ProjectLocation.LOCATION_COORDINATES, + ProjectLocation.STREET_ADDRESS, + ProjectLocation.PIN_OR_DRAW +]; export const QUEUE_PRIORITY = [1, 2, 3]; diff --git a/frontend/src/utils/enums/housing.ts b/frontend/src/utils/enums/housing.ts index 0aee7cc5..fbcbd01d 100644 --- a/frontend/src/utils/enums/housing.ts +++ b/frontend/src/utils/enums/housing.ts @@ -108,7 +108,8 @@ export enum ProjectRelationship { export enum ProjectLocation { STREET_ADDRESS = 'Street address', - LOCATION_COORDINATES = 'Location coordinates' + LOCATION_COORDINATES = 'Location coordinates', + PIN_OR_DRAW = 'Pin or draw your location' } export enum SubmissionType {