diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts index db0aea7329a..2bce99e9fd1 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts @@ -123,10 +123,18 @@ export class LegendEventProcessor extends AbstractEventProcessor { * @param {string} mapId - The map identifier * @param {string} layerPath - The layer path * @param {string[]} objectIds - The IDs of features to get extents from. + * @param {string} outfield - ID field to return for services that require a value in outfields. * @returns {Promise} The extent of the feature, if available */ - static getExtentFromFeatures(mapId: string, layerPath: string, objectIds: string[]): Promise | undefined { - return MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayerHybrid(layerPath)?.getExtentFromFeatures(layerPath, objectIds); + static getExtentFromFeatures( + mapId: string, + layerPath: string, + objectIds: string[], + outfield?: string + ): Promise | undefined { + return MapEventProcessor.getMapViewerLayerAPI(mapId) + .getGeoviewLayerHybrid(layerPath) + ?.getExtentFromFeatures(layerPath, objectIds, outfield); } static getLayerIconImage(layerLegend: TypeLegend | null): TypeLegendLayerItem[] | undefined { diff --git a/packages/geoview-core/src/core/components/data-table/data-table.tsx b/packages/geoview-core/src/core/components/data-table/data-table.tsx index cda5b847cd4..50041520ad9 100644 --- a/packages/geoview-core/src/core/components/data-table/data-table.tsx +++ b/packages/geoview-core/src/core/components/data-table/data-table.tsx @@ -300,7 +300,12 @@ function DataTable({ data, layerPath, tableHeight = '500px' }: DataTableProps): let { extent } = feature; // If there is no extent, the layer is ESRI Dynamic, get the feature extent using its OBJECTID - if (!extent) extent = await getExtentFromFeatures(layerPath, [feature.fieldInfo.OBJECTID!.value as string]); + // GV: Some layers do not use OBJECTID, these are the other values seen so far. + // TODO: Update field info types to include esriFieldTypeOID to identify the ID field. + const idFields = ['OBJECTID', 'OBJECTID_1', 'FID', 'STATION_NUMBER']; + const idField = idFields.find((fieldName) => feature.fieldInfo[fieldName]?.value !== undefined); + if (!extent && idField !== undefined) + extent = await getExtentFromFeatures(layerPath, [feature.fieldInfo[idField]!.value as string], idField); if (extent) { // Project diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts index 15be2199c97..d6447c9571d 100644 --- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts +++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts @@ -37,7 +37,7 @@ export interface ILayerState { actions: { deleteLayer: (layerPath: string) => void; - getExtentFromFeatures: (layerPath: string, featureIds: string[]) => Promise; + getExtentFromFeatures: (layerPath: string, featureIds: string[], outfield?: string) => Promise; queryLayerEsriDynamic: (layerPath: string, objectIDs: number[]) => Promise; getLayer: (layerPath: string) => TypeLegendLayer | undefined; getLayerBounds: (layerPath: string) => number[] | undefined; @@ -89,9 +89,9 @@ export function initializeLayerState(set: TypeSetStore, get: TypeGetStore): ILay get().layerState.setterActions.setLayerDeleteInProgress(false); }, - getExtentFromFeatures: (layerPath: string, featureIds: string[]) => { + getExtentFromFeatures: (layerPath: string, featureIds: string[], outfield?: string) => { // Redirect to event processor - return LegendEventProcessor.getExtentFromFeatures(get().mapId, layerPath, featureIds); + return LegendEventProcessor.getExtentFromFeatures(get().mapId, layerPath, featureIds, outfield); }, /** diff --git a/packages/geoview-core/src/core/utils/constant.ts b/packages/geoview-core/src/core/utils/constant.ts index 75bd8828aa7..fb4e5bf693e 100644 --- a/packages/geoview-core/src/core/utils/constant.ts +++ b/packages/geoview-core/src/core/utils/constant.ts @@ -73,9 +73,9 @@ export const DATE_FILTER: Record = { }; export const STRING_FILTER: Record = { - contains: `(filterId) like ('%value%')`, - startsWith: `(filterId) like ('value%')`, - endsWith: `(filterId) like ('%value')`, + contains: `lower(filterId) like lower('%value%')`, + startsWith: `lower(filterId) like lower('value%')`, + endsWith: `lower(filterId) like lower('%value')`, empty: '(filterId) is null', notEmpty: '(filterId) is not null', equals: `filterId = 'value'`, diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts b/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts index 096a4aa2914..f3f20735d1f 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts @@ -1395,11 +1395,12 @@ export abstract class AbstractGeoViewLayer { * Overridable function that gets the extent of an array of features. * @param {string} layerPath - The layer path * @param {string[]} objectIds - The IDs of features to get extents from. + * @param {string} outfield - ID field to return for services that require a value in outfields. * @returns {Promise} The extent of the features, if available */ // Added eslint-disable here, because we do want to override this method in children and keep 'this'. - // eslint-disable-next-line @typescript-eslint/class-methods-use-this - getExtentFromFeatures(layerPath: string, objectIds: string[]): Promise { + // eslint-disable-next-line @typescript-eslint/class-methods-use-this, @typescript-eslint/no-unused-vars + getExtentFromFeatures(layerPath: string, objectIds: string[], outfield?: string): Promise { logger.logError(`Feature geometry for ${objectIds} is unavailable from ${layerPath}`); return Promise.resolve(undefined); } diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts index 1165e65cf9c..e5de8496b10 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts @@ -969,9 +969,10 @@ export class EsriDynamic extends AbstractGeoViewRaster { * Sends a query to get ESRI Dynamic feature geometries and calculates an extent from them. * @param {string} layerPath - The layer path. * @param {string[]} objectIds - The IDs of the features to calculate the extent from. + * @param {string} outfield - ID field to return for services that require a value in outfields. * @returns {Promise} The extent of the features, if available. */ - override async getExtentFromFeatures(layerPath: string, objectIds: string[]): Promise { + override async getExtentFromFeatures(layerPath: string, objectIds: string[], outfield?: string): Promise { // Get url for service from layer entry config const layerEntryConfig = this.getLayerConfig(layerPath)! as EsriDynamicLayerEntryConfig; let baseUrl = layerEntryConfig.source.dataAccessPath; @@ -980,7 +981,10 @@ export class EsriDynamic extends AbstractGeoViewRaster { if (baseUrl) { // Construct query if (!baseUrl.endsWith('/')) baseUrl += '/'; - const queryUrl = `${baseUrl}${layerEntryConfig.layerId}/query?&f=json&where=&objectIds=${idString}&&geometryPrecision=1&returnGeometry=true`; + // GV: Outfields here is not wanted, it is included because some sevices require it in the query. It would be possible to use + // GV cont: objectid, but it is not universal through the services, so we pass a value through. + const outfieldQuery = outfield ? `&outFields=${outfield}` : ''; + const queryUrl = `${baseUrl}${layerEntryConfig.layerId}/query?&f=json&where=&objectIds=${idString}${outfieldQuery}&returnGeometry=true`; try { const response = await fetch(queryUrl); diff --git a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts index b1188aba624..3f16cdf169e 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts @@ -23,6 +23,7 @@ import { import { esriGetFieldType, esriGetFieldDomain } from '../utils'; import { AbstractGVRaster } from './abstract-gv-raster'; import { TypeOutfieldsType } from '@/api/config/types/map-schema-types'; +import { TypeJsonObject } from '@/api/config/types/config-types'; type TypeFieldOfTheSameValue = { value: string | number | Date; nbOccurence: number }; type TypeQueryTree = { fieldValue: string | number | Date; nextField: TypeQueryTree }[]; @@ -724,16 +725,24 @@ export class GVEsriDynamic extends AbstractGVRaster { * @param {string[]} objectIds - The IDs of the features to calculate the extent from. * @returns {Promise} The extent of the features, if available. */ - override async getExtentFromFeatures(layerPath: string, objectIds: string[]): Promise { + override async getExtentFromFeatures(layerPath: string, objectIds: string[], outfield?: string): Promise { // Get url for service from layer entry config const layerEntryConfig = this.getLayerConfig(); + const serviceMetaData = layerEntryConfig.getServiceMetadata() as TypeJsonObject; + const wkid = serviceMetaData?.spatialReference.wkid ? serviceMetaData.spatialReference.wkid : undefined; let baseUrl = layerEntryConfig.source.dataAccessPath; const idString = objectIds.join('%2C'); if (baseUrl) { // Construct query if (!baseUrl.endsWith('/')) baseUrl += '/'; - const queryUrl = `${baseUrl}${layerEntryConfig.layerId}/query?&f=json&where=&objectIds=${idString}&&geometryPrecision=1&returnGeometry=true`; + // GV: outFields here is not wanted, it is included because some sevices require it in the query. It would be possible to use + // GV cont: OBJECTID, but it is not universal through the services, so we pass a value through. + const outfieldQuery = outfield ? `&outFields=${outfield}` : ''; + const precision = (serviceMetaData?.layers as Array).every((layer) => layer.geometryType !== 'esriGeometryPoint') + ? '&geometryPrecision=1' + : ''; + const queryUrl = `${baseUrl}${layerEntryConfig.layerId}/query?&f=json&where=&objectIds=${idString}${outfieldQuery}${precision}&returnGeometry=true`; try { const response = await fetch(queryUrl); @@ -743,7 +752,7 @@ export class GVEsriDynamic extends AbstractGVRaster { const responseFeatures = new EsriJSON().readFeatures( { features: responseJson.features }, { - dataProjection: `EPSG:${responseJson.spatialReference.wkid}`, + dataProjection: wkid ? `EPSG:${wkid}` : `EPSG:${responseJson.spatialReference.wkid}`, featureProjection: this.getMapViewer().getProjection().getCode(), } );