Skip to content

Commit

Permalink
refactor(layers) - Fifth spring-board PR focusing on the new layers, …
Browse files Browse the repository at this point in the history
…bounds calculation, layerStatus propagation and more (#2245)

* Uncouple layer config from geoview-renderer
Enhanced events(!)
Added GVWFS Layer
Progress OLSource vs OLLayer for the new layers classes

* Added GVCSV
More getGeoviewLayerHybrid()
Attributions
Started calculate bounds work
getBounds for Vectors and XYZ
get bounds for WMS
getBounds, big progress

* calculate bounds refactored too

* Bounds fix, LayerStatus on groups fixed, some other small fixes

* Finalizing

* Comments & Fixes
  • Loading branch information
Alex-NRCan authored Jun 14, 2024
1 parent ef9fa5d commit 1cb89d6
Show file tree
Hide file tree
Showing 80 changed files with 3,512 additions and 2,644 deletions.
2,510 changes: 1,288 additions & 1,222 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/geoview-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"linkifyjs": "^4.1.0",
"lodash": "^4.17.21",
"material-react-table": "^2.13.0",
"ol": "^9.0.0",
"ol": "9.1.0",
"ol-mapbox-style": "^12.2.1",
"proj4": "^2.7.5",
"prop-types": "^15.8.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ <h3>Events that will generate notifications:</h3>
});

// listen to layer filter applied event
cgpv.api.maps.Map1.layer.getGeoviewLayer('uniqueValueId/1').onLayerFilterApplied((sender, payload) => {
cgpv.api.maps.Map1.layer.getGeoviewLayerHybrid('uniqueValueId/1').onLayerFilterApplied((sender, payload) => {
cgpv.api.maps.Map1.notifications.addNotificationSuccess(`Filter ${payload.filter} applied to ${payload.layerPath}`);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/geoview-core/public/templates/layers/layerlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ const createTableOfFilter = (mapId) => {
cgpv.api.maps[mapId].layer.getLayerEntryConfigIds().forEach((layerPath) => {
if (layerPath.startsWith(geoviewLayer.getGeoviewLayerId())) {
const layerConfig = cgpv.api.maps[mapId].layer.getLayerEntryConfig(layerPath);
cgpv.api.utilities.geo.getLegendStylesFromConfig(layerConfig).then((legendStyle) => {
cgpv.api.utilities.geo.getLegendStylesFromConfig(layerConfig.style).then((legendStyle) => {
mapButtonsDiv = document.createElement('td');
// mapButtonsDiv.style.width = '16.66%';
mapButtonsDiv.border = '1px solid black';
Expand Down
2 changes: 1 addition & 1 deletion packages/geoview-core/public/templates/layers/wms.html
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ <h4 id="HLYR4">4. Automatic group creation from metadata information (NOT WORKIN
// TO.DOCONT: Johann: This was to select a style for a WMS on the fly.... I think it is not use anymore. But I think it may be good to have this
// TO.DOCONT: and this mean we need the config section who extract and get the style and layers who apply the style form config or from function call.
cgpv.api.maps.LYR3.layer
.getGeoviewLayer('wmsLYR3-Root')
.getGeoviewLayerHybrid('wmsLYR3-Root')
.setStyle(dropDownContent.value, 'wmsLYR3-Root/landcover_2015_19classes');
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,25 +195,41 @@ export class LegendEventProcessor extends AbstractEventProcessor {

const suffix = layerPathNodes.slice(0, currentLevel);
const entryLayerPath = suffix.join('/');

// Get the layer config
const layerConfig = MapEventProcessor.getMapViewerLayerAPI(mapId).getLayerEntryConfig(entryLayerPath);

// If not found, skip
if (!layerConfig) return;

// Get the layer
const layer = MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayerHybrid(entryLayerPath);

// Interpret the layer name the best we can
const layerName =
getLocalizedValue(layer?.getLayerName(entryLayerPath), AppEventProcessor.getDisplayLanguage(mapId)) ||
getLocalizedValue(layerConfig.layerName, AppEventProcessor.getDisplayLanguage(mapId)) ||
getLocalizedValue(layerConfig.geoviewLayerConfig.geoviewLayerName, AppEventProcessor.getDisplayLanguage(mapId)) ||
layerConfig.layerPath;

let entryIndex = existingEntries.findIndex((entry) => entry.layerPath === entryLayerPath);
if (layerEntryIsGroupLayer(layerConfig)) {
// If all loaded
let bounds;
if (ConfigBaseClass.allLayerStatusAreGreaterThanOrEqualTo('loaded', layerConfig.listOfLayerEntryConfig)) {
// Calculate the bounds
bounds = MapEventProcessor.getMapViewerLayerAPI(mapId).calculateBounds(layerConfig.layerPath);
}

const controls: TypeLayerControls = setLayerControls(layerConfig);
if (entryIndex === -1) {
const legendLayerEntry: TypeLegendLayer = {
bounds: undefined,
bounds,
controls,
layerId: layerConfig.layerId,
layerPath: entryLayerPath,
layerName,
layerStatus: legendResultSetEntry.layerStatus,
layerName:
getLocalizedValue(layerConfig.layerName, AppEventProcessor.getDisplayLanguage(mapId)) ||
getLocalizedValue(layerConfig.geoviewLayerConfig.geoviewLayerName, AppEventProcessor.getDisplayLanguage(mapId)) ||
layerConfig.layerPath,
legendQueryStatus: legendResultSetEntry.legendQueryStatus,
type: layerConfig.entryType as TypeGeoviewLayerType,
canToggle: legendResultSetEntry.data?.type !== CONST_LAYER_TYPES.ESRI_IMAGE,
Expand All @@ -224,47 +240,65 @@ export class LegendEventProcessor extends AbstractEventProcessor {
};
existingEntries.push(legendLayerEntry);
entryIndex = existingEntries.length - 1;
} else {
// TODO: Check - Is it missing group layer entry config properties in the store?
// TO.DOCONT: At the time of writing this, it was just updating the layerStatus on the group layer entry.
// TO.DOCONT: It seemed to me it should also at least update the name and the bounds (the bounds are tricky, as they get generated only when the children are loaded)
// TO.DOCONT: Is there any other group layer entry attributes we would like to propagate in the legends store? I'd think so?
// eslint-disable-next-line no-param-reassign
existingEntries[entryIndex].layerStatus = layerConfig.layerStatus;
// eslint-disable-next-line no-param-reassign
existingEntries[entryIndex].layerName = layerName;
// eslint-disable-next-line no-param-reassign
existingEntries[entryIndex].bounds = bounds;
}
// eslint-disable-next-line no-param-reassign
else existingEntries[entryIndex].layerStatus = layerConfig.layerStatus;

// Continue recursively
createNewLegendEntries(currentLevel + 1, existingEntries[entryIndex].children);
} else {
// If loaded
let bounds;
if (layerConfig.layerStatus === 'loaded') {
// Calculate the bounds
bounds = MapEventProcessor.getMapViewerLayerAPI(mapId).calculateBounds(layerConfig.layerPath);
}

const controls: TypeLayerControls = setLayerControls(layerConfig);
const newLegendLayer: TypeLegendLayer = {
bounds: undefined,
const legendLayerEntry: TypeLegendLayer = {
bounds,
controls,
layerId: layerPathNodes[currentLevel],
layerPath: entryLayerPath,
layerAttribution: MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayer(layerPathNodes[0])!.attributions,
layerName:
legendResultSetEntry.layerName ||
getLocalizedValue(layerConfig.layerName, AppEventProcessor.getDisplayLanguage(mapId)) ||
getLocalizedValue(layerConfig.geoviewLayerConfig.geoviewLayerName, AppEventProcessor.getDisplayLanguage(mapId)) ||
layerConfig.layerPath,
layerAttribution: layer?.getAttributions(),
layerName,
layerStatus: legendResultSetEntry.layerStatus,
legendQueryStatus: legendResultSetEntry.legendQueryStatus,
styleConfig: legendResultSetEntry.data?.styleConfig,
type: legendResultSetEntry.data?.type,
type: legendResultSetEntry.data?.type || (layerConfig.entryType as TypeGeoviewLayerType),
canToggle: legendResultSetEntry.data?.type !== CONST_LAYER_TYPES.ESRI_IMAGE,
opacity: layerConfig.initialSettings?.states?.opacity || 1,
items: [] as TypeLegendItem[],
children: [] as TypeLegendLayer[],
icons: LegendEventProcessor.getLayerIconImage(legendResultSetEntry.data!) || [],
};

newLegendLayer.icons.forEach((legendLayerItem) => {
// Add the icons as items on the layer entry
legendLayerEntry.icons.forEach((legendLayerItem) => {
if (legendLayerItem.iconList)
legendLayerItem.iconList.forEach((legendLayerListItem) => {
newLegendLayer.items.push(legendLayerListItem);
legendLayerEntry.items.push(legendLayerListItem);
});
});
if (entryIndex === -1) existingEntries.push(newLegendLayer);
// eslint-disable-next-line no-param-reassign
else existingEntries[entryIndex] = newLegendLayer;

const myLayer = MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayer(layerPathNodes[0])!;
// TODO: calculateBounds issue will be tackle ASAP in a next PR
newLegendLayer.bounds = myLayer.allLayerStatusAreGreaterThanOrEqualTo('loaded') ? myLayer.calculateBounds(layerPath) : undefined;
// If non existing in the store yet
if (entryIndex === -1) {
// Add it
existingEntries.push(legendLayerEntry);
} else {
// Replace it
// eslint-disable-next-line no-param-reassign
existingEntries[entryIndex] = legendLayerEntry;
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,9 @@ export class MapEventProcessor extends AbstractEventProcessor {

// Redirect to layer to highlight
MapEventProcessor.getMapViewerLayerAPI(mapId).highlightLayer(layerPath);

// Get bounds and highlight a bounding box for the layer
const bounds = MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayer(layerPath)?.calculateBounds(layerPath);
const bounds = MapEventProcessor.getMapViewerLayerAPI(mapId).calculateBounds(layerPath);
if (bounds && bounds[0] !== Infinity) this.getMapStateProtected(mapId).actions.highlightBBox(bounds, true);

return layerPath;
Expand Down Expand Up @@ -852,12 +853,8 @@ export class MapEventProcessor extends AbstractEventProcessor {
* @return {Extent | undefined}
*/
static getLayerBounds(mapId: string, layerPath: string): Extent | undefined {
const layer = MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayer(layerPath);
if (layer) {
const bounds = layer.calculateBounds(layerPath);
if (bounds) return bounds;
}
return undefined;
// Redirect to layer api calculate bounds
return MapEventProcessor.getMapViewerLayerAPI(mapId).calculateBounds(layerPath);
}

// #endregion
Expand Down
20 changes: 10 additions & 10 deletions packages/geoview-core/src/api/events/event-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ export default class EventHelper {
/**
* Emits an event to all handlers.
* @param {T} sender - The object emitting the event
* @param {EventDelegateBase<T, U>[]} handlersList - The list of handlers to be called with the event
* @param {EventDelegateBase<T, U, Z>[]} handlersList - The list of handlers to be called with the event
* @param {U} event - The event to emit
*/
public static emitEvent<T, U>(sender: T, handlersList: EventDelegateBase<T, U>[], event: U): void {
public static emitEvent<T, U, Z>(sender: T, handlersList: EventDelegateBase<T, U, Z>[], event: U): Z[] {
// Trigger all the handlers in the array
handlersList.forEach((handler) => handler(sender, event));
return handlersList.map((handler) => handler(sender, event));
}

/**
* Adds an event handler callback in the provided handlersList.
* @param {EventDelegateBase<T, U>[]} handlersList - The list of handlers to be called with the event
* @param {EventDelegateBase<T, U>} callback - The callback to be executed whenever the event is raised
* @param {EventDelegateBase<T, U, Z>[]} handlersList - The list of handlers to be called with the event
* @param {EventDelegateBase<T, U, Z>} callback - The callback to be executed whenever the event is raised
*/
public static onEvent<T, U>(handlersList: EventDelegateBase<T, U>[], callback: EventDelegateBase<T, U>): void {
public static onEvent<T, U, Z>(handlersList: EventDelegateBase<T, U, Z>[], callback: EventDelegateBase<T, U, Z>): void {
// Push a new callback handler to the list of handlers
handlersList.push(callback);
}

/**
* Removes an event handler callback from the provided handlersList.
* @param {EventDelegateBase<T, U>[]} handlersList - The list of handlers on which to check to remove the handler
* @param {EventDelegateBase<T, U>} callback - The callback to stop being called whenever the event is emitted
* @param {EventDelegateBase<T, U, Z>[]} handlersList - The list of handlers on which to check to remove the handler
* @param {EventDelegateBase<T, U, Z>} callback - The callback to stop being called whenever the event is emitted
*/
public static offEvent<T, U>(handlersList: EventDelegateBase<T, U>[], callback: EventDelegateBase<T, U>): void {
public static offEvent<T, U, Z>(handlersList: EventDelegateBase<T, U, Z>[], callback: EventDelegateBase<T, U, Z>): void {
// Find the callback and remove it
const index = handlersList.indexOf(callback);
if (index !== -1) {
Expand All @@ -39,4 +39,4 @@ export default class EventHelper {
}
}

export type EventDelegateBase<T, U> = (sender: T, event: U) => void;
export type EventDelegateBase<T, U, Z> = (sender: T, event: U) => Z;
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export type AppBarCreatedEvent = {
/**
* Define a delegate for the event handler function signature
*/
type AppBarCreatedDelegate = EventDelegateBase<AppBarApi, AppBarCreatedEvent>;
type AppBarCreatedDelegate = EventDelegateBase<AppBarApi, AppBarCreatedEvent, void>;

/**
* Define an event for the delegate
Expand All @@ -251,4 +251,4 @@ export type AppBarRemovedEvent = {
/**
* Define a delegate for the event handler function signature
*/
type AppBarRemovedDelegate = EventDelegateBase<AppBarApi, AppBarRemovedEvent>;
type AppBarRemovedDelegate = EventDelegateBase<AppBarApi, AppBarRemovedEvent, void>;
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export type FooterTabCreatedEvent = {
/**
* Define a delegate for the event handler function signature
*/
type FooterTabCreatedDelegate = EventDelegateBase<FooterBarApi, FooterTabCreatedEvent>;
type FooterTabCreatedDelegate = EventDelegateBase<FooterBarApi, FooterTabCreatedEvent, void>;

/**
* Define an event for the delegate
Expand All @@ -163,4 +163,4 @@ export type FooterTabRemovedEvent = {
/**
* Define a delegate for the event handler function signature
*/
type FooterTabRemovedDelegate = EventDelegateBase<FooterBarApi, FooterTabRemovedEvent>;
type FooterTabRemovedDelegate = EventDelegateBase<FooterBarApi, FooterTabRemovedEvent, void>;
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export function useLegendHelpers(): unknown {
bounds: undefined,
layerId: `layer${i}`,
layerPath: `test_${generateId()}`,
layerName: `TEST---${setData.data?.layerName?.en ?? 'Unknown Layer name'}`,
layerName: `TEST---Unknown Layer name`,
type: setData.data?.type ?? CONST_LAYER_TYPES.IMAGE_STATIC,
layerStatus: setData.layerStatus,
legendQueryStatus: 'queried',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ export function SingleLayer({
};

const handleLayerClick = (): void => {
// TODO: backend set layerStatus of parent groups to 'loaded' when all children are loaded. Then we will remove 'newInstance' from the condition.
if (!['processed', 'loaded', 'newInstance'].includes(layer.layerStatus!)) {
// Only clickable if the layer status is processed or loaded
if (!['processed', 'loaded'].includes(layer.layerStatus!)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export type NavBarCreatedEvent = {
/**
* Define a delegate for the event handler function signature
*/
type NavBarCreatedDelegate = EventDelegateBase<NavBarApi, NavBarCreatedEvent>;
type NavBarCreatedDelegate = EventDelegateBase<NavBarApi, NavBarCreatedEvent, void>;

/**
* Define an event for the delegate
Expand All @@ -235,4 +235,4 @@ export type NavBarRemovedEvent = {
/**
* Define a delegate for the event handler function signature
*/
type NavBarRemovedDelegate = EventDelegateBase<NavBarApi, NavBarRemovedEvent>;
type NavBarRemovedDelegate = EventDelegateBase<NavBarApi, NavBarRemovedEvent, void>;
2 changes: 1 addition & 1 deletion packages/geoview-core/src/core/stores/state-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export class StateApi {
/**
* Define a delegate for the event handler function signature
*/
type LayersReorderedDelegate = EventDelegateBase<StateApi, LayersReorderedEvent>;
type LayersReorderedDelegate = EventDelegateBase<StateApi, LayersReorderedEvent, void>;

/**
* Define an event for the delegate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import { useStore } from 'zustand';

import { FitOptions } from 'ol/View';
import { Extent } from 'ol/extent';

import { useGeoViewStore } from '@/core/stores/stores-managers';
import { TypeLayersViewDisplayState, TypeLegendItem, TypeLegendLayer } from '@/core/components/layers/types';
import { TypeGetStore, TypeSetStore } from '@/core/stores/geoview-store';
import { TypeResultSet, TypeResultSetEntry, TypeStyleConfig } from '@/geo/map/map-schema-types';
import { OL_ZOOM_DURATION, OL_ZOOM_PADDING } from '@/core/utils/constant';
import { MapEventProcessor } from '@/api/event-processors/event-processor-children/map-event-processor';
import { TypeLocalizedString } from '@/api/config/types/map-schema-types';
import { TypeGeoviewLayerType, TypeVectorLayerStyles } from '@/geo/layer/geoview-layers/abstract-geoview-layers';
import { LegendEventProcessor } from '@/api/event-processors/event-processor-children/legend-event-processor';

Expand Down Expand Up @@ -84,14 +84,12 @@ export function initializeLayerState(set: TypeSetStore, get: TypeGetStore): ILay
const curLayers = get().layerState.legendLayers;
return LegendEventProcessor.findLayerByPath(curLayers, layerPath);
},
getLayerBounds: (layerPath: string) => {
const layer = MapEventProcessor.getMapViewerLayerAPI(get().mapId).getGeoviewLayer(layerPath);
if (layer) {
// TODO: Refactor - Layers refactoring. There needs to be a calculateBounds somewhere (new layers, new config?) to complete the full layers migration.
const bounds = layer.calculateBounds(layerPath);
if (bounds) return bounds;
}
return undefined;

// TODO: Refactor - This 'get' shouldn't be an 'action'. This function should be removed and a state getter be created to access the bounds state from the store directly (for the UI to use)
getLayerBounds: (layerPath: string): Extent | undefined => {
// TODO: Check - There is a calculateBounds() call here in a state action which should probably just get the layer bounds from the store/state? not recalculate again?
// Redirect to processor.
return MapEventProcessor.getMapViewerLayerAPI(get().mapId).calculateBounds(layerPath);
},

/**
Expand Down Expand Up @@ -172,9 +170,8 @@ export function initializeLayerState(set: TypeSetStore, get: TypeGetStore): ILay
zoomToLayerExtent: (layerPath: string): Promise<void> => {
const options: FitOptions = { padding: OL_ZOOM_PADDING, duration: OL_ZOOM_DURATION };

// Get the layer and always calculate the bounds. This will prevent bounds undefined error
const myLayer = MapEventProcessor.getMapViewerLayerAPI(get().mapId).getGeoviewLayer(layerPath.split('/')[0])!;
const bounds = myLayer.calculateBounds(layerPath);
// Calculate the bounds on the layer path.
const bounds = MapEventProcessor.getMapViewerLayerAPI(get().mapId).calculateBounds(layerPath);
if (bounds) return MapEventProcessor.zoomToExtent(get().mapId, bounds, options);
return Promise.resolve();
},
Expand Down Expand Up @@ -262,7 +259,6 @@ export type TypeLegendResultInfo = {
export type LegendQueryStatus = 'init' | 'querying' | 'queried';

export type TypeLegend = {
layerName?: TypeLocalizedString;
type: TypeGeoviewLayerType;
styleConfig?: TypeStyleConfig | null;
// Layers other than vector layers use the HTMLCanvasElement type for their legend.
Expand Down
Loading

0 comments on commit 1cb89d6

Please sign in to comment.