From ac199ba802cb005ab6d44566d7268989c6cb505e Mon Sep 17 00:00:00 2001 From: GrahamS-Quartech <112989452+GrahamS-Quartech@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:11:34 -0700 Subject: [PATCH] SIMSBIOHUB 347/341 - Names from shapefiles (#1146) * Added some additional logic to the API to extract a name and description from the provided shape file when creating sampling sites. * It will search the keys of the geometry's properties for "name" or "label" and "des" or "desc" or "descr" (exact match), then use the value for the sampling site name and description respectively. * Added a character limit to the sampling site name field in the edit form. --------- Co-authored-by: JeremyQuartech --- .../sample-location-repository.ts | 17 +++++++---- api/src/services/sample-location-service.ts | 28 +++++++++++++++---- .../edit/components/SampleSiteEditForm.tsx | 2 +- .../SampleSiteGeneralInformationForm.tsx | 4 +-- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/api/src/repositories/sample-location-repository.ts b/api/src/repositories/sample-location-repository.ts index 162691b4f9..e9667ad722 100644 --- a/api/src/repositories/sample-location-repository.ts +++ b/api/src/repositories/sample-location-repository.ts @@ -25,7 +25,9 @@ export const SampleLocationRecord = z.object({ export type SampleLocationRecord = z.infer; // Insert Object for Sample Locations -export type InsertSampleLocationRecord = Pick; +export type InsertSampleLocationRecord = Pick & { + name: string | undefined; +}; // Update Object for Sample Locations export type UpdateSampleLocationRecord = Pick< @@ -152,6 +154,10 @@ export class SampleLocationRepository extends BaseRepository { * @memberof SampleLocationRepository */ async insertSampleLocation(sample: InsertSampleLocationRecord): Promise { + const shapeNameOrQuery = sample.name + ? SQL`${sample.name}` + : SQL`(SELECT concat('Sample Site ', (SELECT count(survey_sample_site_id) + 1 FROM survey_sample_site sss WHERE survey_id = ${sample.survey_id})))`; + const sqlStatement = SQL` INSERT INTO survey_sample_site ( survey_id, @@ -160,11 +166,10 @@ export class SampleLocationRepository extends BaseRepository { geojson, geography ) VALUES ( - ${sample.survey_id}, - (SELECT concat('Sample Site ', (SELECT count(survey_sample_site_id) + 1 FROM survey_sample_site sss WHERE survey_id = ${sample.survey_id}))), - ${sample.description}, - ${sample.geojson}, - `; + ${sample.survey_id},`.append(shapeNameOrQuery).append(SQL`, + ${sample.description}, + ${sample.geojson}, + `); const geometryCollectionSQL = generateGeometryCollectionSQL(sample.geojson); sqlStatement.append(SQL` diff --git a/api/src/services/sample-location-service.ts b/api/src/services/sample-location-service.ts index e254f708b2..9eddcbffe1 100644 --- a/api/src/services/sample-location-service.ts +++ b/api/src/services/sample-location-service.ts @@ -1,4 +1,4 @@ -import { Feature } from '@turf/helpers'; +import { Feature, Geometry, GeometryCollection, Properties } from '@turf/helpers'; import { IDBConnection } from '../database/db'; import { SampleLocationRecord, @@ -58,19 +58,37 @@ export class SampleLocationService extends DBService { /** * Inserts survey Sample Locations. * + * It is a business requirement to use strings from the properties field of provided geometry + * to determine the name and description of sampling locations when possible. + * + * If there is no string contained in the fields 'name', 'label' to be used in our db, + * the system will auto-generate a name of 'Sampling Site #x', where x is taken from the greatest value + * integer id + 1 in the db. + * * @param {PostSampleLocations} sampleLocations * @return {*} {Promise} * @memberof SampleLocationService */ async insertSampleLocations(sampleLocations: PostSampleLocations): Promise { const methodService = new SampleMethodService(this.connection); - + const shapeFileFeatureName = (geometry: Feature): string | undefined => { + const nameKey = Object.keys(geometry.properties ?? {}).find( + (key) => key.toLowerCase() === 'name' || key.toLowerCase() === 'label' + ); + return nameKey && geometry.properties ? geometry.properties[nameKey].substring(0, 50) : undefined; + }; + const shapeFileFeatureDesc = (geometry: Feature): string | undefined => { + const descKey = Object.keys(geometry.properties ?? {}).find( + (key) => key.toLowerCase() === 'desc' || key.toLowerCase() === 'descr' || key.toLowerCase() === 'des' + ); + return descKey && geometry.properties ? geometry.properties[descKey].substring(0, 250) : undefined; + }; // Create a sample location for each feature found - const promises = sampleLocations.survey_sample_sites.map((item, index) => { + const promises = sampleLocations.survey_sample_sites.map((item) => { const sampleLocation = { survey_id: sampleLocations.survey_id, - name: `Sample Site ${index + 1}`, // Business requirement to default the names to Sample Site # on creation - description: sampleLocations.description, + name: shapeFileFeatureName(item), // If this function returns undefined, insertSampleLocation will auto-generate a name instead. + description: shapeFileFeatureDesc(item) || sampleLocations.description, geojson: item }; diff --git a/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteEditForm.tsx b/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteEditForm.tsx index 2cc7cebe87..f181db2af5 100644 --- a/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteEditForm.tsx +++ b/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteEditForm.tsx @@ -33,7 +33,7 @@ export interface ISampleSiteEditFormProps { export const samplingSiteYupSchema = yup.object({ sampleSite: yup.object({ - name: yup.string().default(''), + name: yup.string().default('').max(50, 'Maximum 50 characters.'), description: yup.string().default('').nullable(), survey_sample_sites: yup .array(yup.object()) diff --git a/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteGeneralInformationForm.tsx b/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteGeneralInformationForm.tsx index c700fbe603..91db456d78 100644 --- a/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteGeneralInformationForm.tsx +++ b/app/src/features/surveys/observations/sampling-sites/edit/components/SampleSiteGeneralInformationForm.tsx @@ -15,9 +15,7 @@ const SampleSiteGeneralInformationForm: React.FC = (props) => {