Skip to content

Commit

Permalink
Fixing GeoJSON details/datatable (#2315)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex-NRCan authored Jun 26, 2024
1 parent 2b421fb commit 41ed66c
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export type TypeVectorLayerGroup = LayerGroup;
export type TypeVectorLayer = VectorSource<Feature>;
export type TypeBaseVectorLayer = BaseLayer | TypeVectorLayerGroup | TypeVectorLayer;

const EXCLUDED_HEADERS_LAT = ['latitude', 'lat', 'y', 'ycoord', 'latitude/latitude', 'latitude / latitude'];
const EXCLUDED_HEADERS_LNG = ['longitude', 'lon', 'x', 'xcoord', 'longitude/longitude', 'longitude / longitude'];
const EXCLUDED_HEADERS_GEN = ['geometry', 'geom'];
const EXCLUDED_HEADERS = EXCLUDED_HEADERS_LAT.concat(EXCLUDED_HEADERS_LNG).concat(EXCLUDED_HEADERS_GEN);

/**
* Determine if layer instance is a vector layer
*
Expand Down Expand Up @@ -166,7 +171,7 @@ export abstract class AbstractGeoViewVector extends AbstractGeoViewLayer {
xhr.onerror = onError;
xhr.onload = async () => {
if (xhr.status === 200) {
let features: Feature[] | null;
let features: Feature[] | undefined;
if (layerConfig.schemaTag === CONST_LAYER_TYPES.CSV) {
// Convert the CSV to features
features = AbstractGeoViewVector.convertCsv(this.mapId, xhr.responseText, layerConfig as VectorLayerEntryConfig);
Expand All @@ -176,6 +181,7 @@ export abstract class AbstractGeoViewVector extends AbstractGeoViewLayer {
featureProjection: projection,
extent,
}) as Feature[];

// ESRI Feature layer response will have exceededTransferLimit property set to true if there are more features
// GV Some layers will return XML, skip
if (xhr.responseText.search('<?xml ') === -1 && JSON.parse(xhr.responseText).exceededTransferLimit) {
Expand All @@ -197,38 +203,51 @@ export abstract class AbstractGeoViewVector extends AbstractGeoViewLayer {
which is defined as the midnight at the beginning of January 1, 1970, UTC (equivalent to the UNIX epoch). If the date type
is not a number, we assume it is provided as an ISO UTC string. If not, the result is unpredictable.
*/
if (layerConfig.source?.featureInfo?.queryable && features) {
const featureInfo = (layerConfig.source as TypeBaseSourceVectorInitialConfig).featureInfo!;
const fieldTypes = featureInfo.fieldTypes?.split(',');
const fieldNames = getLocalizedValue(featureInfo.outfields, AppEventProcessor.getDisplayLanguage(this.mapId))!.split(',');
const dateFields = fieldTypes?.reduce<string[]>((accumulator, entryFieldType, i) => {
if (entryFieldType === 'date') accumulator.push(fieldNames![i]);
return accumulator;
}, []);
if (dateFields?.length) {
features.forEach((feature) => {
dateFields.forEach((fieldName) => {
let fieldValue = feature.get(fieldName);
if (typeof fieldValue === 'number') {
let dateString = DateMgt.convertMilisecondsToDate(fieldValue);
dateString = DateMgt.applyInputDateFormat(dateString, this.serverDateFragmentsOrder);
(feature as Feature).set(fieldName, DateMgt.convertToMilliseconds(dateString), true);
} else {
if (!this.serverDateFragmentsOrder)
this.serverDateFragmentsOrder = DateMgt.getDateFragmentsOrder(DateMgt.deduceDateFormat(fieldValue));
fieldValue = DateMgt.applyInputDateFormat(fieldValue, this.serverDateFragmentsOrder);
(feature as Feature).set(fieldName, DateMgt.convertToMilliseconds(fieldValue), true);
}
if (features) {
// If there's no feature info, build it from features
if (!layerConfig.source?.featureInfo && features.length > 0) {
// Grab first feature as example
const feature = features[0];
const headers = Object.keys(feature.getProperties());
const values = Object.values(feature.getProperties());
AbstractGeoViewVector.#processFeatureInfoConfig(headers, values, EXCLUDED_HEADERS, layerConfig as VectorLayerEntryConfig);
}

// If feature info is queryable
if (layerConfig.source?.featureInfo?.queryable) {
const featureInfo = (layerConfig.source as TypeBaseSourceVectorInitialConfig).featureInfo!;
const fieldTypes = featureInfo.fieldTypes?.split(',');
const fieldNames = getLocalizedValue(featureInfo.outfields, AppEventProcessor.getDisplayLanguage(this.mapId))!.split(',');
const dateFields = fieldTypes?.reduce<string[]>((accumulator, entryFieldType, i) => {
if (entryFieldType === 'date') accumulator.push(fieldNames[i]);
return accumulator;
}, []);
if (dateFields?.length) {
features.forEach((feature) => {
dateFields.forEach((fieldName) => {
let fieldValue = feature.get(fieldName);
if (typeof fieldValue === 'number') {
let dateString = DateMgt.convertMilisecondsToDate(fieldValue);
dateString = DateMgt.applyInputDateFormat(dateString, this.serverDateFragmentsOrder);
(feature as Feature).set(fieldName, DateMgt.convertToMilliseconds(dateString), true);
} else {
if (!this.serverDateFragmentsOrder)
this.serverDateFragmentsOrder = DateMgt.getDateFragmentsOrder(DateMgt.deduceDateFormat(fieldValue));
fieldValue = DateMgt.applyInputDateFormat(fieldValue, this.serverDateFragmentsOrder);
(feature as Feature).set(fieldName, DateMgt.convertToMilliseconds(fieldValue), true);
}
});
});
});
}
}
}
if (features) {

// Add the features to the source
vectorSource.addFeatures(features);
if (success) success(features as Feature[]);
const layer = this.getOLLayer(layerConfig.layerPath);
layer?.changed();
}

if (success) success(features as Feature[]);
const layer = this.getOLLayer(layerConfig.layerPath);
layer?.changed();
} else {
onError();
}
Expand Down Expand Up @@ -533,23 +552,21 @@ export abstract class AbstractGeoViewVector extends AbstractGeoViewLayer {
*
* @returns {Feature[]} The array of features.
*/
static convertCsv(mapId: string, csvData: string, layerConfig: VectorLayerEntryConfig): Feature[] | null {
static convertCsv(mapId: string, csvData: string, layerConfig: VectorLayerEntryConfig): Feature[] | undefined {
// GV: This function and the below private static ones used to be in the CSV class directly, but something wasn't working with a 'Private element not accessible' error.
// GV: After moving the code to the mother class, it worked. It'll remain here for now until the config refactoring can take care of it in its re-writing

const inProjection: ProjectionLike = layerConfig.source!.dataProjection || Projection.PROJECTION_NAMES.LNGLAT;
const outProjection: ProjectionLike = MapEventProcessor.getMapViewer(mapId).getProjection().getCode();
const latList = ['latitude', 'lat', 'y', 'ycoord', 'latitude/latitude', 'latitude / latitude'];
const lonList = ['longitude', 'lon', 'x', 'xcoord', 'longitude/longitude', 'longitude / longitude'];

const features: Feature[] = [];
let latIndex: number | undefined;
let lonIndex: number | undefined;
const csvRows = AbstractGeoViewVector.#csvStringToArray(csvData, layerConfig.source!.separator || ',');
const headers: string[] = csvRows[0];
for (let i = 0; i < headers.length; i++) {
if (latList.includes(headers[i].toLowerCase())) latIndex = i;
if (lonList.includes(headers[i].toLowerCase())) lonIndex = i;
if (EXCLUDED_HEADERS_LAT.includes(headers[i].toLowerCase())) latIndex = i;
if (EXCLUDED_HEADERS_LNG.includes(headers[i].toLowerCase())) lonIndex = i;
}

if (latIndex === undefined || lonIndex === undefined) {
Expand All @@ -558,10 +575,10 @@ export abstract class AbstractGeoViewVector extends AbstractGeoViewLayer {
// TODO: find a more centralized way to trap error and display message
api.maps[mapId].notifications.showError(errorMsg);
layerConfig.layerStatus = 'error';
return null;
return undefined;
}

AbstractGeoViewVector.#processFeatureInfoConfig(headers, csvRows[1], [latIndex, lonIndex], layerConfig);
AbstractGeoViewVector.#processFeatureInfoConfig(headers, csvRows[1], EXCLUDED_HEADERS, layerConfig);

for (let i = 1; i < csvRows.length; i++) {
const currentRow = csvRows[i];
Expand Down Expand Up @@ -620,7 +637,7 @@ export abstract class AbstractGeoViewVector extends AbstractGeoViewLayer {
static #processFeatureInfoConfig(
headers: string[],
firstRow: string[],
lonLatIndices: number[],
excludedHeaders: string[],
layerConfig: VectorLayerEntryConfig
): void {
if (!layerConfig.source) layerConfig.source = {};
Expand All @@ -635,9 +652,9 @@ export abstract class AbstractGeoViewVector extends AbstractGeoViewLayer {
layerConfig.source.featureInfo.fieldTypes = '';
}
if (processAliasFields) layerConfig.source.featureInfo.aliasFields = { en: '' };
headers.forEach((header) => {
const index = headers.indexOf(header);
if (index !== lonLatIndices[0] && index !== lonLatIndices[1]) {
headers.forEach((header, index) => {
// If not excluded
if (!excludedHeaders.includes(header)) {
let type = 'string';
if (firstRow[index] && firstRow[index] !== '' && Number(firstRow[index])) type = 'number';
if (processOutField) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
layerEntryIsGroupLayer,
TypeBaseSourceVectorInitialConfig,
} from '@/geo/map/map-schema-types';
import { Cast, toJsonObject } from '@/core/types/global-types';
import { Cast, TypeJsonObject } from '@/core/types/global-types';
import { getLocalizedValue } from '@/core/utils/utilities';
import { GeoJSONLayerEntryConfig } from '@/core/utils/config/validation-classes/vector-validation-classes/geojson-layer-entry-config';
import { VectorLayerEntryConfig } from '@/core/utils/config/validation-classes/vector-layer-entry-config';
Expand Down Expand Up @@ -167,7 +167,6 @@ export class GeoJSON extends AbstractGeoViewVector {
(layerMetadata) => layerMetadata.layerId === layerConfig.layerId && layerMetadata.layerIdExtension === layerConfig.layerIdExtension
);
if (layerMetadataFound) {
this.setLayerMetadata(layerConfig.layerPath, toJsonObject(layerMetadataFound));
layerConfig.layerName = layerConfig.layerName || layerMetadataFound.layerName;
layerConfig.source = defaultsDeep(layerConfig.source, layerMetadataFound.source);
layerConfig.initialSettings = defaultsDeep(layerConfig.initialSettings, layerMetadataFound.initialSettings);
Expand Down Expand Up @@ -204,6 +203,10 @@ export class GeoJSON extends AbstractGeoViewVector {
// TODO: Check - Why are we converting to the map projection in the pre-processing? It'd be better to standardize to 4326 here (or leave untouched), as it's part of the initial configuration and handle it later?
layerConfig.initialSettings.extent = this.getMapViewer().convertExtentLngLatToMapProj(layerConfig.initialSettings.extent);
}

// Setting the layer metadata now with the updated config values. Setting the layer metadata with the config, directly, like it's done in CSV
this.setLayerMetadata(layerConfig.layerPath, Cast<TypeJsonObject>(layerConfig));

return Promise.resolve(layerConfig);
}

Expand Down

0 comments on commit 41ed66c

Please sign in to comment.