From 08c3204ab4d6905b0810b20830a2337106ae08ad Mon Sep 17 00:00:00 2001 From: Shreyas Devalapurkar Date: Tue, 17 Aug 2021 10:42:54 -0700 Subject: [PATCH] BHBC 1251 -> Further enhancement and correction (#458) Fixing up map layers inferring functionality due to weirdness with layers --- app/src/components/map/MapContainer.tsx | 123 ++++++-------- app/src/utils/Utils.ts | 12 ++ app/src/utils/mapLayersHelpers.ts | 207 ++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 70 deletions(-) create mode 100644 app/src/utils/mapLayersHelpers.ts diff --git a/app/src/components/map/MapContainer.tsx b/app/src/components/map/MapContainer.tsx index b6bd219f64..55b65a9623 100644 --- a/app/src/components/map/MapContainer.tsx +++ b/app/src/components/map/MapContainer.tsx @@ -24,6 +24,12 @@ import 'leaflet-fullscreen/dist/leaflet.fullscreen.css'; import { throttle } from 'lodash-es'; import { ReProjector } from 'reproj-helper'; import { useBiohubApi } from 'hooks/useBioHubApi'; +import { + determineMapGeometries, + getInferredLayersInfoByProjectedGeometry, + getInferredLayersInfoByWFSFeature, + getLayerTypesToSkipByProjectedGeometry +} from 'utils/mapLayersHelpers'; /* Get leaflet icons working @@ -69,6 +75,25 @@ const layerGeoFilterTypeMappings = { 'pub:WHSE_WILDLIFE_MANAGEMENT.WAA_WILDLIFE_MGMT_UNITS_SVW': 'GEOMETRY' }; +const layersToInfer = [ + 'pub:WHSE_TANTALIS.TA_PARK_ECORES_PA_SVW', + 'pub:WHSE_ADMIN_BOUNDARIES.ADM_NR_REGIONS_SPG', + 'pub:WHSE_ADMIN_BOUNDARIES.EADM_WLAP_REGION_BND_AREA_SVW', + 'pub:WHSE_WILDLIFE_MANAGEMENT.WAA_WILDLIFE_MGMT_UNITS_SVW' +]; + +export const envToNrmRegionsMapping = { + '1- Vancouver Island': 'West Coast Natural Resource Region', + '2- Lower Mainland': 'South Coast Natural Resource Region', + '3- Thompson': 'Thompson-Okanagan Natural Resource Region', + '8- Okanagan': 'Thompson-Okanagan Natural Resource Region', + '4- Kootenay': 'Kootenay-Boundary Natural Resource Region', + '5- Cariboo': 'Cariboo Natural Resource Region', + '6- Skeena': 'Skeena Natural Resource Region', + '7- Omineca': 'Omineca Natural Resource Region', + '9- Peace': 'Northeast Natural Resource Region' +}; + const layerContentHandlers = { 'pub:WHSE_WILDLIFE_MANAGEMENT.WAA_WILDLIFE_MGMT_UNITS_SVW': { featureKeyHandler: (feature: Feature) => feature?.properties?.OBJECTID, @@ -189,13 +214,6 @@ const MapContainer: React.FC = (props) => { const [preDefinedGeometry, setPreDefinedGeometry] = useState(); - const layersToInfer = [ - 'pub:WHSE_TANTALIS.TA_PARK_ECORES_PA_SVW', - 'pub:WHSE_ADMIN_BOUNDARIES.ADM_NR_REGIONS_SPG', - 'pub:WHSE_ADMIN_BOUNDARIES.EADM_WLAP_REGION_BND_AREA_SVW', - 'pub:WHSE_WILDLIFE_MANAGEMENT.WAA_WILDLIFE_MGMT_UNITS_SVW' - ]; - // Add a geometry defined from an existing overlay feature (via its popup) useEffect(() => { if (!preDefinedGeometry) { @@ -300,61 +318,41 @@ const MapContainer: React.FC = (props) => { return coordinatesString; }; - // TODO break this into smaller functions + /* + Function to get WFS feature details based on the existing map geometries + and layer types/filter criteria + */ const throttledGetFeatureDetails = useCallback( throttle(async (typeNames: string[], wfsParams?: IWFSParams) => { - let inferredLayersInfo; const parksInfo: Set = new Set(); // Parks and Eco-Reserves const nrmInfo: Set = new Set(); // NRM Regions const envInfo: Set = new Set(); // ENV Regions const wmuInfo: Set = new Set(); // Wildlife Management Units + let inferredLayersInfo = { + parksInfo, + nrmInfo, + envInfo, + wmuInfo + }; - let drawnGeometries: any[] = []; - - if (geometryState?.geometry.length) { - drawnGeometries = geometryState?.geometry; - } else if (nonEditableGeometries) { - drawnGeometries = nonEditableGeometries.map((geo: INonEditableGeometries) => geo.feature); - } + // Get map geometries based on whether boundary is non editable or drawn/uploaded + const mapGeometries: Feature[] = determineMapGeometries(geometryState?.geometry, nonEditableGeometries); // Convert all geometries to BC Albers projection - const reprojectedGeometries = await Promise.all(changeProjections(drawnGeometries)); + const reprojectedGeometries = await Promise.all(changeProjections(mapGeometries)); const wfsPromises: Promise[] = []; - reprojectedGeometries.forEach((projectedGeo) => { let filterCriteria = ''; const coordinatesString = generateCoordinatesString(projectedGeo.geometry); filterCriteria = `${projectedGeo.geometry.type}${coordinatesString}`; + inferredLayersInfo = getInferredLayersInfoByProjectedGeometry(projectedGeo, inferredLayersInfo); + const layerTypesToSkip = getLayerTypesToSkipByProjectedGeometry(projectedGeo); - let equivalentType = ''; - const geoId = projectedGeo.id as string; - - if (geoId && geoId.includes('TA_PARK_ECORES_PA_SVW')) { - equivalentType = 'TA_PARK_ECORES_PA_SVW'; - parksInfo.add(projectedGeo.properties?.PROTECTED_LANDS_NAME); - } - - if (geoId && geoId.includes('ADM_NR_REGIONS_SPG')) { - equivalentType = 'ADM_NR_REGIONS_SPG'; - nrmInfo.add(projectedGeo.properties?.REGION_NAME); - } - - if (geoId && geoId.includes('EADM_WLAP_REGION_BND_AREA_SVW')) { - equivalentType = 'EADM_WLAP_REGION_BND_AREA_SVW'; - envInfo.add(projectedGeo.properties?.REGION_NUMBER_NAME); - } - - if (geoId && geoId.includes('WAA_WILDLIFE_MGMT_UNITS_SVW')) { - equivalentType = 'WAA_WILDLIFE_MGMT_UNITS_SVW'; - wmuInfo.add( - `${projectedGeo.properties?.WILDLIFE_MGMT_UNIT_ID}, ${projectedGeo.properties?.GAME_MANAGEMENT_ZONE_ID}, ${projectedGeo.properties?.GAME_MANAGEMENT_ZONE_NAME}` - ); - } - + // Make Open Maps API call to retrieve intersecting features based on geometry and filter criteria typeNames.forEach((typeName: string) => { - if (!equivalentType || !typeName.includes(equivalentType)) { + if (!layerTypesToSkip.includes(typeName)) { const url = buildWFSURL(typeName, wfsParams); const geoFilterType = layerGeoFilterTypeMappings[typeName]; const filterData = `INTERSECTS(${geoFilterType}, ${filterCriteria})`; @@ -370,41 +368,26 @@ const MapContainer: React.FC = (props) => { } }); }); - const wfsResult = await Promise.all(wfsPromises); wfsResult.forEach((item: any) => { item?.features?.forEach((feature: Feature) => { - const featureId = feature.id as string; - - if (featureId.includes('TA_PARK_ECORES_PA_SVW')) { - parksInfo.add(feature.properties?.PROTECTED_LANDS_NAME); - } - - if (featureId.includes('ADM_NR_REGIONS_SPG')) { - nrmInfo.add(feature.properties?.REGION_NAME); - } - - if (featureId.includes('EADM_WLAP_REGION_BND_AREA_SVW')) { - envInfo.add(feature.properties?.REGION_NUMBER_NAME); - } - - if (featureId.includes('WAA_WILDLIFE_MGMT_UNITS_SVW')) { - wmuInfo.add( - `${feature.properties?.WILDLIFE_MGMT_UNIT_ID}, ${feature.properties?.GAME_MANAGEMENT_ZONE_ID}, ${feature.properties?.GAME_MANAGEMENT_ZONE_NAME}` - ); - } + inferredLayersInfo = getInferredLayersInfoByWFSFeature(feature, inferredLayersInfo); }); }); - inferredLayersInfo = { - parks: Array.from(parksInfo), - nrm: Array.from(nrmInfo), - env: Array.from(envInfo), - wmu: Array.from(wmuInfo) + if (!inferredLayersInfo) { + return; + } + + const inferredLayers = { + parks: Array.from(inferredLayersInfo.parksInfo), + nrm: Array.from(inferredLayersInfo.nrmInfo), + env: Array.from(inferredLayersInfo.envInfo), + wmu: Array.from(inferredLayersInfo.wmuInfo) }; - setInferredLayersInfo && setInferredLayersInfo(inferredLayersInfo); + setInferredLayersInfo && setInferredLayersInfo(inferredLayers); }, 300), [geometryState?.geometry, nonEditableGeometries] ); diff --git a/app/src/utils/Utils.ts b/app/src/utils/Utils.ts index 53f980e7da..6607812762 100644 --- a/app/src/utils/Utils.ts +++ b/app/src/utils/Utils.ts @@ -135,3 +135,15 @@ export const getFormattedFileSize = (fileSize: number) => { // gigabyte size return `${(fileSize / 1000000000).toFixed(1)} GB`; }; + +/** + * Function to get an object key by the value + * Ex: let obj = { 'role': 'admin' } -> getKeyByValue(obj, 'admin') will return 'role' + * + * @param {object} object + * @param {any} value + * @returns {any} key for the corresponding value + */ +export function getKeyByValue(object: any, value: any) { + return Object.keys(object).find((key) => object[key] === value); +} diff --git a/app/src/utils/mapLayersHelpers.ts b/app/src/utils/mapLayersHelpers.ts new file mode 100644 index 0000000000..a995ae5226 --- /dev/null +++ b/app/src/utils/mapLayersHelpers.ts @@ -0,0 +1,207 @@ +import { envToNrmRegionsMapping, INonEditableGeometries } from 'components/map/MapContainer'; +import { Feature } from 'geojson'; +import { getKeyByValue } from './Utils'; + +/** + * Function to returns an array of `Features`. + + * Only one of `geometry` and `nonEditableGeometries` will be defined at any given time. + * - If the map loads as view-only, then nonEditableGeometries may be defined. + * - If the map loads as editable, then `geometry` may be defined. + * - If neither are defined, returns an empty array. + * + * Note: when there are existing nonEditableGeometries, and the map is edited, + * the geometry array will contain all of the previous nonEditableGeometries + * as well as any newly added/drawn geometries. + * + * @param {Feature[] | undefined} geometry + * @param {INonEditableGeometries[] | undefined} nonEditableGeometries + * @returns Feature[] + */ +export function determineMapGeometries( + geometry: Feature[] | undefined, + nonEditableGeometries: INonEditableGeometries[] | undefined +): Feature[] { + if (geometry && geometry.length) { + return geometry; + } + + if (nonEditableGeometries) { + return nonEditableGeometries.map((geo: INonEditableGeometries) => geo.feature); + } + + return []; +} + +/** + * Gets the ENV region name based on the WMU GMZ ID + * + * @param {string} gmzId + * @returns {string} env region name + */ +const getENVRegionByGMZ = (gmzId: string): string => { + let env: string = ''; + + if (gmzId.includes('1')) { + env = '1- Vancouver Island'; + } else if (gmzId.includes('2')) { + env = '2- Lower Mainland'; + } else if (gmzId.includes('3')) { + env = '3- Thompson'; + } else if (gmzId.includes('4')) { + env = '4- Kootenay'; + } else if (gmzId.includes('5')) { + env = '5- Cariboo'; + } else if (gmzId.includes('6')) { + env = '6- Skeena'; + } else if (gmzId.includes('7P')) { + env = '9- Peace'; + } else if (gmzId.includes('7O')) { + env = '7- Omineca'; + } else if (gmzId.includes('8')) { + env = '8- Okanagan'; + } + + return env; +}; + +/** + * Looks at a given projected geometry and creates a list of layer types to + * skip searching for based on when the layer info can be determined directly based on the geometry itself + * (Open Maps API call is not required in this case) + * + * @param {any} projectedGeo + * @returns {string[]} layerTypesToSkip + */ +export function getLayerTypesToSkipByProjectedGeometry(projectedGeo: any) { + const geoId = projectedGeo.id as string; + let layerTypesToSkip: string[] = []; + + if (geoId && geoId.includes('TA_PARK_ECORES_PA_SVW')) { + layerTypesToSkip.push('pub:WHSE_TANTALIS.TA_PARK_ECORES_PA_SVW'); + } + + if (geoId && geoId.includes('ADM_NR_REGIONS_SPG')) { + layerTypesToSkip.push('pub:WHSE_ADMIN_BOUNDARIES.ADM_NR_REGIONS_SPG'); + layerTypesToSkip.push('pub:WHSE_ADMIN_BOUNDARIES.EADM_WLAP_REGION_BND_AREA_SVW'); + } + + if (geoId && geoId.includes('EADM_WLAP_REGION_BND_AREA_SVW')) { + layerTypesToSkip.push('pub:WHSE_ADMIN_BOUNDARIES.ADM_NR_REGIONS_SPG'); + layerTypesToSkip.push('pub:WHSE_ADMIN_BOUNDARIES.EADM_WLAP_REGION_BND_AREA_SVW'); + } + + if (geoId && geoId.includes('WAA_WILDLIFE_MGMT_UNITS_SVW')) { + layerTypesToSkip.push('pub:WHSE_WILDLIFE_MANAGEMENT.WAA_WILDLIFE_MGMT_UNITS_SVW'); + layerTypesToSkip.push('pub:WHSE_ADMIN_BOUNDARIES.ADM_NR_REGIONS_SPG'); + layerTypesToSkip.push('pub:WHSE_ADMIN_BOUNDARIES.EADM_WLAP_REGION_BND_AREA_SVW'); + } + + return layerTypesToSkip; +} + +/** + * Looks at a given projected geometry and determines if it is a known park/nrm/env/wmu layer and if so + * adds it to the list of inferred layers to display to the user + * + * @param {any} projectedGeo + * @param {any} currentLayersInfo + * @returns {any} updated currentLayersInfo + */ +export function getInferredLayersInfoByProjectedGeometry(projectedGeo: any, currentLayersInfo: any) { + const geoId = projectedGeo.id as string; + + if (geoId && geoId.includes('TA_PARK_ECORES_PA_SVW')) { + currentLayersInfo.parksInfo.add(projectedGeo.properties?.PROTECTED_LANDS_NAME); + } + + if (geoId && geoId.includes('ADM_NR_REGIONS_SPG')) { + const nrm = projectedGeo.properties?.REGION_NAME; + const env = + nrm !== 'Thompson-Okanagan Natural Resource Region' + ? [getKeyByValue(envToNrmRegionsMapping, nrm)] + : ['3- Thompson', '8- Okanagan']; + + env.forEach((envRegion) => { + if (envRegion) { + currentLayersInfo.envInfo.add(envRegion); + } + }); + currentLayersInfo.nrmInfo.add(nrm); + } + + if (geoId && geoId.includes('EADM_WLAP_REGION_BND_AREA_SVW')) { + currentLayersInfo.envInfo.add(projectedGeo.properties?.REGION_NUMBER_NAME); + currentLayersInfo.nrmInfo.add(envToNrmRegionsMapping[projectedGeo.properties?.REGION_NUMBER_NAME]); + } + + if (geoId && geoId.includes('WAA_WILDLIFE_MGMT_UNITS_SVW')) { + const gmzId = projectedGeo.properties?.GAME_MANAGEMENT_ZONE_ID; + const env: string = getENVRegionByGMZ(gmzId); + const nrm = envToNrmRegionsMapping[env]; + + currentLayersInfo.nrmInfo.add(nrm); + currentLayersInfo.envInfo.add(env); + currentLayersInfo.wmuInfo.add( + `${projectedGeo.properties?.WILDLIFE_MGMT_UNIT_ID}, ${gmzId}, ${projectedGeo.properties?.GAME_MANAGEMENT_ZONE_NAME}` + ); + } + + return currentLayersInfo; +} + +/** + * Looks at a given feature returned from the WFS service API call and determines if it is a known park/nrm/env/wmu layer and if so + * adds it to the list of inferred layers to display to the user. + * + * Performs some business logic to filter the env region based on the nrm region (1:1 mapping). + * + * Also filters the wmu layers based on the env region. This is because the mapping of env to wmu layers is not direct 1:1 + * ie; Omineca is env region 7 but Peace was split out to be env region 9, although when it comes to wmu layers - they are both GMZ ID 7 + * so Omineca is 7O and Peace is 7P (for wmu layer) + * + * @param {Feature} feature + * @param {any} currentLayersInfo + * @returns {any} updated currentLayersInfo + */ +export function getInferredLayersInfoByWFSFeature(feature: Feature, currentLayersInfo: any) { + const featureId = feature.id as string; + + if (featureId.includes('TA_PARK_ECORES_PA_SVW')) { + currentLayersInfo.parksInfo.add(feature.properties?.PROTECTED_LANDS_NAME); + } + + if (featureId.includes('ADM_NR_REGIONS_SPG')) { + currentLayersInfo.nrmInfo.add(feature.properties?.REGION_NAME); + } + + if (featureId.includes('EADM_WLAP_REGION_BND_AREA_SVW')) { + const nrmRegions = currentLayersInfo.nrmInfo; + for (let nrm of nrmRegions) { + const env = getKeyByValue(envToNrmRegionsMapping, nrm); + + if (env) { + currentLayersInfo.envInfo.add(env); + } + } + } + + if (featureId.includes('WAA_WILDLIFE_MGMT_UNITS_SVW')) { + const gmzId = feature.properties?.GAME_MANAGEMENT_ZONE_ID; + const envRegions = currentLayersInfo.envInfo; + + for (let env of envRegions) { + if ( + (env[0] === '7' && gmzId.includes('7O')) || + (env[0] === '9' && gmzId.includes('7P')) || + (env[0] !== '7' && env[0] !== '9' && gmzId.includes(env[0])) + ) { + currentLayersInfo.wmuInfo.add( + `${feature.properties?.WILDLIFE_MGMT_UNIT_ID}, ${gmzId}, ${feature.properties?.GAME_MANAGEMENT_ZONE_NAME}` + ); + } + } + } + + return currentLayersInfo; +}