diff --git a/packages/geoview-core/public/configs/navigator/06-basic-footer.json b/packages/geoview-core/public/configs/navigator/06-basic-footer.json
index a8661a225e7..d2901384c24 100644
--- a/packages/geoview-core/public/configs/navigator/06-basic-footer.json
+++ b/packages/geoview-core/public/configs/navigator/06-basic-footer.json
@@ -9,6 +9,28 @@
"shaded": true,
"labeled": true
},
+ "overlayObjects": {
+ "pointMarkers": {
+ "group1": [
+ {
+ "id": "1",
+ "coordinate": [-100, 60],
+ "color": "blue",
+ "opacity": 0.5
+ },
+ {
+ "id": "2",
+ "coordinate": [-80, 65],
+ "color": "rgb(0, 226, 0)"
+ },
+ {
+ "id": "3",
+ "coordinate": [-115, 66],
+ "color": "#C52022"
+ }
+ ]
+ }
+ },
"listOfGeoviewLayerConfig": [
{
"geoviewLayerId": "airborne_radioactivity",
diff --git a/packages/geoview-core/public/img/marker-icon36.png b/packages/geoview-core/public/img/marker-icon36.png
new file mode 100644
index 00000000000..028b60c0326
Binary files /dev/null and b/packages/geoview-core/public/img/marker-icon36.png differ
diff --git a/packages/geoview-core/public/templates/demos/demo-function-event.html b/packages/geoview-core/public/templates/demos/demo-function-event.html
index 1ade15b3aee..85d22d6f45b 100644
--- a/packages/geoview-core/public/templates/demos/demo-function-event.html
+++ b/packages/geoview-core/public/templates/demos/demo-function-event.html
@@ -123,11 +123,11 @@
API Functions:
cgpv.api.maps.Map1.appBarApi.selectAppBarTab('AppbarPanelButtonGeolocator', 'geolocator')
- cgpv.api.maps.Map1.layer.hoverFeatureInfoLayerSet.disableHoverListener('esriFeatureLYR5/0')
+ cgpv.api.maps.Map1.layer.hoverFeatureInfoLayerSet.disableHoverListener('esriFeatureLYR5/0')
cgpv.api.maps.Map1.layer.hoverFeatureInfoLayerSet.enableHoverListener('esriFeatureLYR5/0')
- cgpv.api.maps.Map1.layer.featureInfoLayerSet.disableClickListener('esriFeatureLYR5/0')
+ cgpv.api.maps.Map1.layer.featureInfoLayerSet.disableClickListener('esriFeatureLYR5/0')
cgpv.api.maps.Map1.layer.featureInfoLayerSet.enableClickListener('esriFeatureLYR5/0')
@@ -140,17 +140,18 @@ API Functions:
cgpv.api.maps.Map1.getMapLayerOrderInfo()
- cgpv.api.maps.Map1.getMapLayerOrderInfo()
+ cgpv.api.maps.Map1.setLanguage('en')
+ cgpv.api.maps.Map1.setLanguage('fr')
- const basemap = await cgpv.api.maps.Map1.basemap.createCoreBasemap(basemapOptions = {basemapId: 'simple'})
+ const basemap = await cgpv.api.maps.Map1.basemap.createCoreBasemap(basemapOptions = {basemapId: 'simple'})
cgpv.api.maps.Map1.basemap.setBasemap(basemap)
cgpv.api.maps.Map1.stateApi.setSelectedLayersTabLayer('nonmetalmines/5')
- cgpv.api.maps.Map1.plugins['swiper'].activateForLayer('nonmetalmines/5')
+ cgpv.api.maps.Map1.plugins['swiper'].activateForLayer('nonmetalmines/5')
cgpv.api.maps.Map1.plugins['swiper'].deActivateForLayer('nonmetalmines/5')
@@ -167,6 +168,16 @@ API Functions:
cgpv.api.maps.Map1.reloadWithCurrentState()
+
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.addPointMarkers('group1', markers)
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.removePointMarkersOrGroup('group1');
+
+
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.zoomToPointMarkers('group1', ['1', '3']);
+
+
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.zoomToPointMarkerGroup('group1');
+
Events that will generate notifications:
@@ -184,6 +195,7 @@ Events that will generate notifications:
- onBasemapChanged
- onLayersReordered
- onLayerOpacityChanged for Water Quantity
+ - onMapAddedToDiv
@@ -283,8 +295,11 @@ Events that will generate notifications:
cgpv.api.maps.Map1.notifications.addNotificationSuccess(`${payload.layerPath} opacity changed to ${payload.opacity}`);
});
- });
-
+ // listen to map added to div event
+ cgpv.api.onMapAddedToDiv((sender, payload) => {
+ cgpv.api.maps[payload.mapId].notifications.addNotificationSuccess(`Map ${payload.mapId} added`);
+ });
+ })
// Add WMS Button======================================================================================================
// find the button element by ID
var addLayerButton = document.getElementById('Add-layer');
@@ -518,7 +533,7 @@ Events that will generate notifications:
// add an event listener when a button is clicked
changeLanguageEnglishButton.addEventListener('click', async () => {
- console.log(cgpv.api.maps.Map1.setLanguage('en'));
+ cgpv.api.maps.Map1.setLanguage('en');
});
// Enable language fr Button================================================================================================
@@ -527,7 +542,7 @@ Events that will generate notifications:
// add an event listener when a button is clicked
changeLanguageFrenchButton.addEventListener('click', async () => {
- console.log(cgpv.api.maps.Map1.setLanguage('fr'));
+ cgpv.api.maps.Map1.setLanguage('fr');
});
// Change basemap Button================================================================================================
@@ -607,6 +622,61 @@ Events that will generate notifications:
reloadMapButton.addEventListener('click', () => {
cgpv.api.maps.Map1.reloadWithCurrentState();
});
+
+ // Add-marker Button================================================================================================
+ // find the button element by ID
+ var addMarkerButton = document.getElementById('Add-marker');
+
+ const markers = [
+ {
+ "id": "1",
+ "coordinate": [-100, 60],
+ "color": "blue",
+ "opacity": 0.5
+ },
+ {
+ "id": "2",
+ "coordinate": [-80, 65],
+ "color": "rgb(0, 226, 0)"
+ },
+ {
+ "id": "3",
+ "coordinate": [-115, 66],
+ "color": "#C52022"
+ }
+ ]
+
+ // add an event listener when a button is clicked
+ addMarkerButton.addEventListener('click', () => {
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.addPointMarkers('group1', markers);
+ });
+
+ // Remove-marker Button================================================================================================
+ // find the button element by ID
+ var removeMarkerButton = document.getElementById('Remove-marker');
+
+ // add an event listener when a button is clicked
+ removeMarkerButton.addEventListener('click', () => {
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.removePointMarkersOrGroup('group1');
+ });
+
+ // Zoom-to-markers Button================================================================================================
+ // find the button element by ID
+ var zoomToMarkersButton = document.getElementById('Zoom-to-markers');
+
+ // add an event listener when a button is clicked
+ zoomToMarkersButton.addEventListener('click', () => {
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.zoomToPointMarkers('group1', ['1', '3']);
+ });
+
+ // Zoom-to-marker-group Button================================================================================================
+ // find the button element by ID
+ var zoomToMarkerGroupButton = document.getElementById('Zoom-to-marker-group');
+
+ // add an event listener when a button is clicked
+ zoomToMarkerGroupButton.addEventListener('click', () => {
+ cgpv.api.maps.Map1.layer.featureHighlight.pointMarkers.zoomToPointMarkerGroup('group1');
+ });
// create snippets
window.addEventListener('load', () => {
diff --git a/packages/geoview-core/schema.json b/packages/geoview-core/schema.json
index ff23c65fc4b..d30c03bb863 100644
--- a/packages/geoview-core/schema.json
+++ b/packages/geoview-core/schema.json
@@ -565,7 +565,7 @@
},
"dataProjection": {
"type": "string",
- "description": "The projection code of the source. Used only for GeoJSON format. Default value is EPSG:4326. "
+ "description": "The projection code of the source. Used only for GeoJSON format. Default value is EPSG:4326."
},
"featureInfo": {
"$ref": "#/definitions/TypeFeatureInfoLayerConfig"
@@ -1346,6 +1346,9 @@
"highlightColor": {
"$ref": "#/definitions/TypeHighlightColors"
},
+ "overlayObjects": {
+ "$ref": "#/definitions/TypeOverlayObjects"
+ },
"extraOptions": {
"type": "object",
"description": "Additional options used for OpenLayers map options"
@@ -1388,6 +1391,60 @@
"default": "black",
"description": "Color to use for feature highlights."
},
+ "TypeOverlayObjects": {
+ "type": "object",
+ "properties": {
+ "pointMarkers": {
+ "$ref": "#/definitions/TypePointMarkers"
+ }
+ }
+ },
+ "TypePointMarkers": {
+ "type": "object",
+ "patternProperties": {
+ "[^]*": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/TypePointMarker"
+ }
+ }
+ }
+ },
+ "TypePointMarker": {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "ID for point marker. Must be unique in group."
+ },
+ "coordinate": {
+ "type": "array",
+ "minItems": 2,
+ "maxItems": 2,
+ "items": {
+ "type": "number"
+ },
+ "description": "The coordinates of the marker."
+ },
+ "color": {
+ "type": "string",
+ "default": "green",
+ "description": "Marker color."
+ },
+ "opacity": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1,
+ "default": 1
+ },
+ "projection": {
+ "type": "number",
+ "description": "The projection code of the coordinates. Default value is 4326."
+ }
+ },
+ "required": ["id", "coordinate"]
+ },
"TypeListOfGeoviewLayerConfig": {
"description": "List of GeoView Layers in the order which they should be added to the map.",
"type": "array",
diff --git a/packages/geoview-core/src/api/api.ts b/packages/geoview-core/src/api/api.ts
index b7e850272da..304a65cab38 100644
--- a/packages/geoview-core/src/api/api.ts
+++ b/packages/geoview-core/src/api/api.ts
@@ -12,6 +12,7 @@ import { MapViewer } from '@/geo/map/map-viewer';
import * as GeoUtilities from '@/geo/utils/utilities';
import { initMapDivFromFunctionCall } from '@/app';
+import EventHelper, { EventDelegateBase } from './events/event-helper';
/**
* Class used to handle api calls (events, functions etc...)
@@ -32,6 +33,9 @@ export class API {
// utilities object
utilities;
+ // Keep all callback delegates references
+ #onMapAddedToDivHandlers: MapAddedToDivDelegate[] = [];
+
/**
* Initiate the event and projection objects
*/
@@ -110,9 +114,51 @@ export class API {
if (mapDiv) {
// Init by function call
await initMapDivFromFunctionCall(mapDiv, mapConfig);
+ this.#emitMapAddedToDiv({ mapId: divId });
return Promise.resolve();
}
return Promise.reject(new Error(`Div with id ${divId} does not exist`));
}
+
+ /**
+ * Emits an event to all handlers.
+ * @param {MapAddedToDivEvent} event - The event to emit
+ * @private
+ */
+ #emitMapAddedToDiv(event: MapAddedToDivEvent): void {
+ // Emit the event for all handlers
+ EventHelper.emitEvent(this, this.#onMapAddedToDivHandlers, event);
+ }
+
+ /**
+ * Registers a map added to div event handler.
+ * @param {MapAddedToDivDelegate} callback - The callback to be executed whenever the event is emitted
+ */
+ onMapAddedToDiv(callback: MapAddedToDivDelegate): void {
+ // Register the event handler
+ EventHelper.onEvent(this.#onMapAddedToDivHandlers, callback);
+ }
+
+ /**
+ * Unregisters a map added to div event handler.
+ * @param {MapAddedToDivdDelegate} callback - The callback to stop being called whenever the event is emitted
+ */
+ offMapAddedToDiv(callback: MapAddedToDivDelegate): void {
+ // Unregister the event handler
+ EventHelper.offEvent(this.#onMapAddedToDivHandlers, callback);
+ }
}
+
+/**
+ * Define a delegate for the event handler function signature
+ */
+type MapAddedToDivDelegate = EventDelegateBase;
+
+/**
+ * Define an event for the delegate
+ */
+export type MapAddedToDivEvent = {
+ // The added layer
+ mapId: string;
+};
diff --git a/packages/geoview-core/src/api/config/types/config-constants.ts b/packages/geoview-core/src/api/config/types/config-constants.ts
index b95c3943c9b..923f23cc158 100644
--- a/packages/geoview-core/src/api/config/types/config-constants.ts
+++ b/packages/geoview-core/src/api/config/types/config-constants.ts
@@ -159,6 +159,9 @@ export const CV_DEFAULT_MAP_FEATURE_CONFIG = Cast({
interaction: 'dynamic',
listOfGeoviewLayerConfig: [],
highlightColor: 'black',
+ overlayObjects: {
+ pointMarkers: {},
+ },
viewSettings: {
initialView: {
zoomAndCenter: [3.5, CV_MAP_CENTER[3978]],
diff --git a/packages/geoview-core/src/api/config/types/config-validation-schema.json b/packages/geoview-core/src/api/config/types/config-validation-schema.json
index 8ed9e999e14..727358c76cc 100644
--- a/packages/geoview-core/src/api/config/types/config-validation-schema.json
+++ b/packages/geoview-core/src/api/config/types/config-validation-schema.json
@@ -73,6 +73,9 @@
"highlightColor": {
"$ref": "#/definitions/TypeHighlightColors"
},
+ "overlayObjects": {
+ "$ref": "#/definitions/TypeOverlayObjects"
+ },
"extraOptions": {
"description": "Additional options used for OpenLayers map options",
"type": "object"
@@ -506,6 +509,60 @@
"enum": ["black", "white", "red", "green"],
"default": "black"
},
+ "TypeOverlayObjects": {
+ "type": "object",
+ "properties": {
+ "pointMarkers": {
+ "$ref": "#/definitions/TypePointMarkers"
+ }
+ }
+ },
+ "TypePointMarkers": {
+ "type": "object",
+ "patternProperties": {
+ "[^]*": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/TypePointMarker"
+ }
+ }
+ }
+ },
+ "TypePointMarker": {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "ID for point marker. Must be unique in group."
+ },
+ "coordinate": {
+ "type": "array",
+ "minItems": 2,
+ "maxItems": 2,
+ "items": {
+ "type": "number"
+ },
+ "description": "The coordinates of the marker."
+ },
+ "color": {
+ "type": "string",
+ "default": "green",
+ "description": "Marker color."
+ },
+ "opacity": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1,
+ "default": 1
+ },
+ "projection": {
+ "type": "number",
+ "description": "The projection code of the coordinates. Default value is 4326."
+ }
+ },
+ "required": ["id", "coordinate"]
+ },
"TypeDisplayLanguage": {
"description": "Display languages supported.",
"enum": ["en", "fr"]
diff --git a/packages/geoview-core/src/api/config/types/map-schema-types.ts b/packages/geoview-core/src/api/config/types/map-schema-types.ts
index 2b9f42fdaff..667b1e27b91 100644
--- a/packages/geoview-core/src/api/config/types/map-schema-types.ts
+++ b/packages/geoview-core/src/api/config/types/map-schema-types.ts
@@ -1,4 +1,5 @@
import { AbstractGeoviewLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-layer-config';
+import { Coordinate } from 'ol/coordinate';
import { TimeDimension } from '@/core/utils/date-mgt';
@@ -154,6 +155,8 @@ export type TypeMapConfig = {
viewSettings: TypeViewSettings;
/** Highlight color. */
highlightColor?: TypeHighlightColors;
+ /** Point markers to add to map. */
+ overlayObjects?: TypeOverlayObjects;
/** Additional options used for OpenLayers map options. */
extraOptions?: Record;
};
@@ -230,6 +233,30 @@ export type TypeValidMapProjectionCodes = 3978 | 3857;
/** Type used to define valid highlight colors. */
export type TypeHighlightColors = 'black' | 'white' | 'red' | 'green';
+/** Type used to define overlay objects. */
+// TODO: Add more overlay objects - polygons, bounding box?
+export type TypeOverlayObjects = {
+ /** Non interactive markers */
+ pointMarkers?: TypePointMarkers;
+};
+
+/** Type used to define point markers object. */
+type TypePointMarkers = Record;
+
+/** Type used to define point marker. */
+export type TypePointMarker = {
+ /** ID for marker, must be unique within group */
+ id: string;
+ /** Marker coordinates, unique in group, projection code must be added if not in lon/lat */
+ coordinate: Coordinate;
+ /** Marker color */
+ color?: string;
+ /** Marker opacity */
+ opacity?: number;
+ /** Projection code if coordinates are not in lon/lat */
+ projectionCode?: number;
+};
+
// #region GEOVIEW LAYERS
/** Parent class of the GeoView layers. */
diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/data-table-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/data-table-event-processor.ts
index 5ab3272707a..67731d347cd 100644
--- a/packages/geoview-core/src/api/event-processors/event-processor-children/data-table-event-processor.ts
+++ b/packages/geoview-core/src/api/event-processors/event-processor-children/data-table-event-processor.ts
@@ -70,8 +70,8 @@ export class DataTableEventProcessor extends AbstractEventProcessor {
* @param {string} filter - The filter
*/
static addOrUpdateTableFilter(mapId: string, layerPath: string, filter: string): void {
- const curSliderFilters = this.getDataTableState(mapId)?.tableFilters;
- this.getDataTableState(mapId)?.setterActions.setTableFilters({ ...curSliderFilters, [layerPath]: filter });
+ const curTableFilters = this.getDataTableState(mapId)?.tableFilters;
+ this.getDataTableState(mapId)?.setterActions.setTableFilters({ ...curTableFilters, [layerPath]: filter });
}
/**
@@ -88,8 +88,7 @@ export class DataTableEventProcessor extends AbstractEventProcessor {
* Propagates feature info layer sets to the store.
* The propagation actually happens only if it wasn't already there. Otherwise, no update is propagated.
* @param {string} mapId - The map identifier of the modified result set.
- * @param {string} layerPath - The layer path that has changed.
- * @param {TypeFeatureInfoResultSet} resultSet - The result set associated to the map.
+ * @param {TypeAllFeatureInfoResultSetEntry} resultSetEntry - The result set associated to the map.
*/
static propagateFeatureInfoToStore(mapId: string, resultSetEntry: TypeAllFeatureInfoResultSetEntry): void {
/**
diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts
index 6dd6a824e69..afc0a000498 100644
--- a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts
+++ b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts
@@ -15,6 +15,7 @@ import {
TypeValidFooterBarTabsCoreProps,
TypeValidMapProjectionCodes,
TypeViewSettings,
+ TypePointMarker,
} from '@config/types/map-schema-types';
import { api } from '@/app';
import { LayerApi } from '@/geo/layer/layer';
@@ -312,6 +313,10 @@ export class MapEventProcessor extends AbstractEventProcessor {
return this.getMapStateProtected(mapId).initialFilters[layerPath];
}
+ static getPointMarkers(mapId: string): Record {
+ return this.getMapStateProtected(mapId).pointMarkers;
+ }
+
static clickMarkerIconShow(mapId: string, marker: TypeClickMarker): void {
// Project coords
const projectedCoords = Projection.transformPoints(
@@ -560,6 +565,64 @@ export class MapEventProcessor extends AbstractEventProcessor {
}
}
+ /**
+ * Add a point marker
+ * @param {string} mapId - The ID of the map.
+ * @param {string} group - The group to add the markers to.
+ * @param {TypePointMarker} pointMarkers - The point markers to add.
+ */
+ static addPointMarkers(mapId: string, group: string, pointMarkers: TypePointMarker[]): void {
+ const curMarkers = this.getMapStateProtected(mapId).pointMarkers;
+
+ // Check for existing group, and existing markers that match input IDs or coordinates
+ let groupMarkers = curMarkers[group];
+ if (groupMarkers) {
+ pointMarkers.forEach((pointMarker) => {
+ // Replace any existing ids or markers at the same coordinates with new marker
+ groupMarkers = groupMarkers.filter((marker) => marker.coordinate.join() !== pointMarker.coordinate.join());
+ groupMarkers = groupMarkers.filter((marker) => marker.id !== pointMarker.id);
+ groupMarkers.push(pointMarker);
+ });
+ } else {
+ groupMarkers = pointMarkers;
+ }
+
+ // Set the group markers, and update on the map
+ curMarkers[group] = groupMarkers;
+ this.getMapStateProtected(mapId).setterActions.setPointMarkers(curMarkers);
+ MapEventProcessor.getMapViewerLayerAPI(mapId).featureHighlight.pointMarkers.updatePointMarkers(curMarkers);
+ }
+
+ /**
+ * Remove a point marker
+ * @param {string} mapId - The ID of the map.
+ * @param {string} group - The group to remove the markers from.
+ * @param {string | Coordinate} idsOrCoordinates - The IDs or coordinates of the markers to remove.
+ */
+ static removePointMarkersOrGroup(mapId: string, group: string, idsOrCoordinates?: string[] | Coordinate[]): void {
+ const curMarkers = this.getMapStateProtected(mapId).pointMarkers;
+
+ // If no IDs or coordinates are provided, remove group
+ if (!idsOrCoordinates) {
+ delete curMarkers[group];
+ } else {
+ // Set property to check
+ const property = typeof idsOrCoordinates[0] === 'string' ? 'id' : 'coordinate';
+
+ // Filter out markers that match given ones
+ let groupMarkers = curMarkers[group];
+ idsOrCoordinates.forEach((idOrCoordinate) => {
+ groupMarkers = groupMarkers.filter((marker) => marker[property] !== idOrCoordinate);
+ });
+
+ curMarkers[group] = groupMarkers;
+ }
+
+ // Set the pointMarkers and update on map
+ this.getMapStateProtected(mapId).setterActions.setPointMarkers(curMarkers);
+ MapEventProcessor.getMapViewerLayerAPI(mapId).featureHighlight.pointMarkers.updatePointMarkers(curMarkers);
+ }
+
/**
* Update or remove the layer highlight.
* @param {string} mapId - The ID of the map.
@@ -1084,6 +1147,7 @@ export class MapEventProcessor extends AbstractEventProcessor {
interaction: this.getMapInteraction(mapId),
listOfGeoviewLayerConfig,
highlightColor: config.map.highlightColor,
+ overlayObjects: { pointMarkers: this.getPointMarkers(mapId) },
viewSettings,
};
diff --git a/packages/geoview-core/src/api/plugin/footer-plugin.ts b/packages/geoview-core/src/api/plugin/footer-plugin.ts
index 9ccc7f5db90..982270919a3 100644
--- a/packages/geoview-core/src/api/plugin/footer-plugin.ts
+++ b/packages/geoview-core/src/api/plugin/footer-plugin.ts
@@ -53,7 +53,7 @@ export abstract class FooterPlugin extends AbstractPlugin {
// No need to log, parent class does it well already via removed() function.
// Remove the footer tab
- if (this.value) this.mapViewer().footerBarApi.removeTab(this.footerProps!.id);
+ if (this.value && this.mapViewer()?.footerBarApi) this.mapViewer().footerBarApi.removeTab(this.footerProps!.id);
}
/**
diff --git a/packages/geoview-core/src/core/components/point-markers/point-markers.ts b/packages/geoview-core/src/core/components/point-markers/point-markers.ts
new file mode 100644
index 00000000000..6e4120b662a
--- /dev/null
+++ b/packages/geoview-core/src/core/components/point-markers/point-markers.ts
@@ -0,0 +1,188 @@
+import { Coordinate } from 'ol/coordinate';
+import Feature from 'ol/Feature';
+import Point from 'ol/geom/Point';
+import { Icon, Style } from 'ol/style';
+import { Extent } from 'ol/extent';
+import { Projection } from '@/geo/utils/projection';
+import { getExtentUnion } from '@/geo/utils/utilities';
+import { MapEventProcessor } from '@/api/event-processors/event-processor-children/map-event-processor';
+import { FeatureHighlight, getScriptAndAssetURL, MapViewer } from '@/app';
+import { logger } from '@/core/utils/logger';
+import { TypePointMarker } from '@/api/config/types/map-schema-types';
+
+/**
+ * A class to handle point markers
+ *
+ * @exports
+ * @class PointMarkers
+ */
+export class PointMarkers {
+ /** The feature highlight class, used to access overlay layer source */
+ #featureHighlight: FeatureHighlight;
+
+ /** The map projection */
+ mapProjection: string;
+
+ /** The map ID */
+ mapId: string;
+
+ /** Array to track marker feature IDs */
+ #featureIds: string[] = [];
+
+ /**
+ * Initializes point marker classes
+ * @param {MapViewer} mapViewer - The map viewer
+ * @param {FeatureHighlight} featureHighlight - The feature highlight class
+ */
+ constructor(mapViewer: MapViewer, featureHighlight: FeatureHighlight) {
+ this.mapProjection = mapViewer.map.getView().getProjection().getCode();
+ this.mapId = mapViewer.mapId;
+ this.#featureHighlight = featureHighlight;
+ if (Object.keys(MapEventProcessor.getPointMarkers(this.mapId)).length)
+ this.updatePointMarkers(MapEventProcessor.getPointMarkers(this.mapId));
+ }
+
+ /**
+ * Update the point markers on the map.
+ * @param {Record} mapPointMarkers - The markers
+ */
+ updatePointMarkers(mapPointMarkers: Record): void {
+ // Remove existing markers
+ this.#removePointMarkersFromMap();
+
+ // Add point markers to map
+ Object.keys(mapPointMarkers).forEach((markerGroup) => {
+ mapPointMarkers[markerGroup].forEach((point) => {
+ const pointStyle = new Style({
+ image: new Icon({
+ anchor: [0.5, 1],
+ src: `${getScriptAndAssetURL()}/img/marker-icon36.png`,
+ color: point.color || 'green',
+ opacity: point.opacity || 1,
+ scale: 0.25,
+ }),
+ });
+
+ const pointFeature = new Feature({
+ geometry: new Point(
+ Projection.transformPoints([point.coordinate], `EPSG:${point.projectionCode || 4326}`, this.mapProjection)[0]
+ ),
+ });
+
+ // Set ID and style for feature
+ const featureId = `${markerGroup}-${point.id}`;
+ pointFeature.setId(featureId);
+ pointFeature.setStyle(pointStyle);
+
+ // Add feature to source
+ this.#featureHighlight.highlighSource.addFeature(pointFeature);
+ // Add ID to array
+ this.#featureIds.push(featureId);
+ });
+ });
+ }
+
+ /**
+ * Remove the point markers from the map.
+ * @private
+ */
+ #removePointMarkersFromMap(): void {
+ this.#featureIds.forEach((id) => {
+ const feature = this.#featureHighlight.highlighSource.getFeatureById(id);
+ if (feature) this.#featureHighlight.highlighSource.removeFeature(feature);
+ });
+ this.#featureIds = [];
+ }
+
+ /**
+ * Add point markers.
+ * @param {string} group - The group to add the markers to.
+ * @param {Record[]} pointMarkers - The masrker to add.
+ */
+ addPointMarkers(group: string, pointMarkers: TypePointMarker[]): void {
+ // Redirect to event processor
+ MapEventProcessor.addPointMarkers(this.mapId, group, pointMarkers);
+ }
+
+ /**
+ * Remove an array of point markers or a point marker group.
+ * @param {string} group - The group to remove the markers from.
+ * @param {string[] | Coordinate[]} idsOrCoordinates - The id or coordinate of the marker to remove.
+ */
+ removePointMarkersOrGroup(group: string, idsOrCoordinates?: string[] | Coordinate[]): void {
+ // Redirect to event processor
+ MapEventProcessor.removePointMarkersOrGroup(this.mapId, group, idsOrCoordinates);
+ }
+
+ /**
+ * Zoom to point marker group.
+ * @param {string} group - The group to zoom to.
+ */
+ zoomToPointMarkerGroup(group: string): void {
+ const groupMarkers = MapEventProcessor.getPointMarkers(this.mapId)[group];
+
+ if (groupMarkers) {
+ // Create list of feature IDs
+ const idList: string[] = groupMarkers.map((marker) => marker.id);
+
+ // If there are IDs, zoom to them
+ if (idList.length) this.zoomToPointMarkers(group, idList);
+ else logger.logError(`Point marker group ${group} has no markers.`);
+ } else logger.logError(`Point marker group ${group} does not exist.`);
+ }
+
+ /**
+ * Zoom to point markers.
+ * @param {string} group - The group containing the markers to zoom to.
+ * @param {string | Coordinate} ids - The ids of the markers to zoom to.
+ */
+ zoomToPointMarkers(group: string, ids: string[]): void {
+ // Create list of feature IDs
+ const idList = ids.map((id) => `${group}-${id}`);
+
+ // Get extent of point markers and zoom to it
+ const extent = this.getExtentFromMarkerIds(idList);
+ if (extent)
+ MapEventProcessor.zoomToExtent(this.mapId, extent).catch((error: unknown) => {
+ // Log
+ logger.logPromiseFailed('zoomToExtent in zoomToPointMarkersOrGroup in MapEventProcessor', error);
+ });
+ else logger.logError(`Point marker group ${group} has no markers or does not exist, or point marker ids ${ids} are not correct.`);
+ }
+
+ /**
+ * Get the extent of point markers.
+ * @param {string[]} ids - The ids of markers to get the extents of.
+ * @returns {Extent | undefined} The calculated extent or undefined.
+ */
+ getExtentFromMarkerIds(ids: string[]): Extent | undefined {
+ if (ids.length) {
+ // Get the point coordinates and extrapolate to extent
+ const coordinates = ids
+ .map((id) => {
+ const feature = this.#featureHighlight.highlighSource.getFeatureById(id);
+ if (feature) {
+ const pointCoordinates = (feature?.getGeometry() as Point).getCoordinates();
+ return [pointCoordinates[0], pointCoordinates[1], pointCoordinates[0], pointCoordinates[1]] as Extent;
+ }
+ return undefined;
+ })
+ .filter((extents) => extents);
+
+ // If only one extent, return
+ if (coordinates.length === 1) return coordinates[0];
+
+ // Find max extent of points
+ if (coordinates.length) {
+ let extent = coordinates[0] as number[];
+ for (let i = 1; i < coordinates.length; i++) {
+ extent = getExtentUnion(extent, coordinates[i]);
+ }
+
+ return extent;
+ }
+ }
+
+ return undefined;
+ }
+}
diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
index ccbf6e9cd48..f5a3c8e847e 100644
--- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
+++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
@@ -14,6 +14,7 @@ import { TypeMapMouseInfo } from '@/geo/map/map-viewer';
import { MapEventProcessor } from '@/api/event-processors/event-processor-children/map-event-processor';
import { TypeClickMarker } from '@/core/components/click-marker/click-marker';
import { TypeFeatureInfoEntry } from '@/geo/map/map-schema-types';
+import { TypePointMarker } from '@/api/config/types/map-schema-types';
import { TypeFeatureInfoResultSet, TypeHoverFeatureInfo } from './feature-info-state';
import { CV_MAP_CENTER } from '@/api/config/types/config-constants';
@@ -46,6 +47,7 @@ export interface IMapState {
overviewMap: boolean;
overviewMapHideZoom: number;
pointerPosition?: TypeMapMouseInfo;
+ pointMarkers: Record;
rotation: number;
scale: TypeScaleInfo;
size: [number, number];
@@ -65,6 +67,8 @@ export interface IMapState {
highlightBBox: (extent: Extent, isLayerHighlight?: boolean) => void;
addHighlightedFeature: (feature: TypeFeatureInfoEntry) => void;
removeHighlightedFeature: (feature: TypeFeatureInfoEntry | 'all') => void;
+ addPointMarkers: (group: string, pointMarkers: TypePointMarker[]) => void;
+ removePointMarkersOrGroup: (group: string, idsOrCoordinates?: string[] | Coordinate[]) => void;
reorderLayer: (layerPath: string, move: number) => void;
resetBasemap: () => Promise;
setLegendCollapsed: (layerPath: string, newValue?: boolean) => void;
@@ -104,6 +108,7 @@ export interface IMapState {
scale: TypeScaleInfo
) => void;
setPointerPosition: (pointerPosition: TypeMapMouseInfo) => void;
+ setPointMarkers: (pointMarkers: Record) => void;
setClickCoordinates: (clickCoordinates: TypeMapMouseInfo) => void;
setCurrentBasemapOptions: (basemapOptions: TypeBasemapOptions) => void;
setFixNorth: (ifFix: boolean) => void;
@@ -147,6 +152,7 @@ export function initializeMapState(set: TypeSetStore, get: TypeGetStore): IMapSt
overviewMap: false,
overviewMapHideZoom: 0,
pointerPosition: undefined,
+ pointMarkers: {},
rotation: 0,
scale: { lineWidth: '', labelGraphic: '', labelNumeric: '' } as TypeScaleInfo,
size: [0, 0] as [number, number],
@@ -171,6 +177,7 @@ export function initializeMapState(set: TypeSetStore, get: TypeGetStore): IMapSt
northArrow: geoviewConfig.components!.indexOf('north-arrow') > -1 || false,
overviewMap: geoviewConfig.components!.indexOf('overview-map') > -1 || false,
overviewMapHideZoom: geoviewConfig.overviewMap !== undefined ? geoviewConfig.overviewMap.hideOnZoom : 0,
+ pointMarkers: geoviewConfig.map.overlayObjects?.pointMarkers || {},
rotation: geoviewConfig.map.viewSettings.rotation || 0,
zoom: geoviewConfig.map.viewSettings.initialView?.zoomAndCenter
? geoviewConfig.map.viewSettings.initialView.zoomAndCenter[0]
@@ -277,6 +284,26 @@ export function initializeMapState(set: TypeSetStore, get: TypeGetStore): IMapSt
MapEventProcessor.removeHighlightedFeature(get().mapId, feature);
},
+ /**
+ * Add point markers.
+ * @param {string} group - The group to add the point to
+ * @param {TypePointMarker[]} pointMarkers - The points to add
+ */
+ addPointMarkers: (group: string, pointMarkers: TypePointMarker[]): void => {
+ // Redirect to processor
+ return MapEventProcessor.addPointMarkers(get().mapId, group, pointMarkers);
+ },
+
+ /**
+ * Remove a point marker.
+ * @param {string} group - The group to remove the point from
+ * @param {string[] | Coordinate[]} idsOrCoordinates - The point to remove
+ */
+ removePointMarkersOrGroup: (group: string, idsOrCoordinates?: string[] | Coordinate[]): void => {
+ // Redirect to processor
+ return MapEventProcessor.removePointMarkersOrGroup(get().mapId, group, idsOrCoordinates);
+ },
+
/**
* Reorders the layer.
* @param {string} layerPath - The path of the layer.
@@ -603,6 +630,19 @@ export function initializeMapState(set: TypeSetStore, get: TypeGetStore): IMapSt
});
},
+ /**
+ * Sets the point markers.
+ * @param {Record} pointMarkers - The new point markers.
+ */
+ setPointMarkers: (pointMarkers: Record): void => {
+ set({
+ mapState: {
+ ...get().mapState,
+ pointMarkers,
+ },
+ });
+ },
+
/**
* Sets map move end properties.
* @param {Coordinate} centerCoordinates - The center coordinates of the map.
@@ -827,6 +867,7 @@ export const useMapClickCoordinates = (): TypeMapMouseInfo | undefined =>
useStore(useGeoViewStore(), (state) => state.mapState.clickCoordinates);
export const useMapExtent = (): Extent | undefined => useStore(useGeoViewStore(), (state) => state.mapState.mapExtent);
export const useMapFixNorth = (): boolean => useStore(useGeoViewStore(), (state) => state.mapState.fixNorth);
+export const useMapInitialFilters = (): Record => useStore(useGeoViewStore(), (state) => state.mapState.initialFilters);
export const useMapInteraction = (): TypeInteraction => useStore(useGeoViewStore(), (state) => state.mapState.interaction);
export const useMapHoverFeatureInfo = (): TypeHoverFeatureInfo => useStore(useGeoViewStore(), (state) => state.mapState.hoverFeatureInfo);
export const useMapLoaded = (): boolean => useStore(useGeoViewStore(), (state) => state.mapState.mapLoaded);
@@ -837,6 +878,8 @@ export const useMapOverviewMap = (): boolean => useStore(useGeoViewStore(), (sta
export const useMapOverviewMapHideZoom = (): number => useStore(useGeoViewStore(), (state) => state.mapState.overviewMapHideZoom);
export const useMapPointerPosition = (): TypeMapMouseInfo | undefined =>
useStore(useGeoViewStore(), (state) => state.mapState.pointerPosition);
+export const useMapPointMarkers = (): Record =>
+ useStore(useGeoViewStore(), (state) => state.mapState.pointMarkers);
export const useMapProjection = (): TypeValidMapProjectionCodes => useStore(useGeoViewStore(), (state) => state.mapState.currentProjection);
export const useMapRotation = (): number => useStore(useGeoViewStore(), (state) => state.mapState.rotation);
export const useMapScale = (): TypeScaleInfo => useStore(useGeoViewStore(), (state) => state.mapState.scale);
diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts
index a8f2ab1d278..37fdfa227a9 100644
--- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts
+++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts
@@ -254,6 +254,6 @@ export interface TypeTimeSliderValues {
export const useTimeSliderLayers = (): TimeSliderLayerSet => useStore(useGeoViewStore(), (state) => state.timeSliderState.timeSliderLayers);
export const useTimeSliderSelectedLayerPath = (): string => useStore(useGeoViewStore(), (state) => state.timeSliderState.selectedLayerPath);
export const useTimeSliderFilters = (): Record =>
- useStore(useGeoViewStore(), (state) => state.timeSliderState.sliderFilters);
+ useStore(useGeoViewStore(), (state) => state.timeSliderState?.sliderFilters);
export const useTimeSliderStoreActions = (): TimeSliderActions => useStore(useGeoViewStore(), (state) => state.timeSliderState.actions);
diff --git a/packages/geoview-core/src/geo/map/feature-highlight.ts b/packages/geoview-core/src/geo/map/feature-highlight.ts
index a848a26e503..9a581753dbf 100644
--- a/packages/geoview-core/src/geo/map/feature-highlight.ts
+++ b/packages/geoview-core/src/geo/map/feature-highlight.ts
@@ -13,6 +13,7 @@ import { TypeHighlightColors } from '@config/types/map-schema-types';
import { logger } from '@/core/utils/logger';
import { MapViewer } from '@/geo/map/map-viewer';
import { TypeFeatureInfoEntry } from './map-schema-types';
+import { PointMarkers } from '@/core/components/point-markers/point-markers';
/** *****************************************************************************************************************************
* A class to handle highlighting of features
@@ -22,12 +23,15 @@ import { TypeFeatureInfoEntry } from './map-schema-types';
*/
export class FeatureHighlight {
/** The vector source to use for the animation features */
- #highlighSource: VectorSource = new VectorSource();
+ highlighSource: VectorSource = new VectorSource();
/** The hidden layer to display animations. */
// GV It's public, to save an eslint warning, because even if it's not read in this class, it's actually important to instanciate per OpenLayer design.
overlayLayer: VectorLayer;
+ // Used to access point markers
+ pointMarkers: PointMarkers;
+
/** The fill for the highlight */
#highlightColor = 'black';
@@ -51,7 +55,8 @@ export class FeatureHighlight {
* @param {MapViewer} mapViewer a reference to the map viewer
*/
constructor(mapViewer: MapViewer) {
- this.overlayLayer = new VectorLayer({ source: this.#highlighSource, map: mapViewer.map });
+ this.overlayLayer = new VectorLayer({ source: this.highlighSource, map: mapViewer.map });
+ this.pointMarkers = new PointMarkers(mapViewer, this);
// if (this.#highlightColor !== undefined)
// this.changeHighlightColor(MapEventProcessor.getMapHighlightColor(this.#mapId) as TypeHighlightColors);
}
@@ -107,7 +112,7 @@ export class FeatureHighlight {
feature.setStyle(this.#highlightStyle);
feature.setId(id);
this.#highlightedFeatureIds.push(id);
- this.#highlighSource.addFeature(feature);
+ this.highlighSource.addFeature(feature);
}
/**
@@ -117,14 +122,14 @@ export class FeatureHighlight {
removeHighlight(id: string): void {
if (id === 'all' && this.#highlightedFeatureIds.length) {
for (let i = 0; i < this.#highlightedFeatureIds.length; i++) {
- this.#highlighSource.removeFeature(this.#highlighSource.getFeatureById(this.#highlightedFeatureIds[i]) as Feature);
+ this.highlighSource.removeFeature(this.highlighSource.getFeatureById(this.#highlightedFeatureIds[i]) as Feature);
}
this.#highlightedFeatureIds = [];
} else if (this.#highlightedFeatureIds.length) {
for (let i = this.#highlightedFeatureIds.length - 1; i >= 0; i--) {
if (this.#highlightedFeatureIds[i] === id || this.#highlightedFeatureIds[i].startsWith(`${id}-`)) {
- if (this.#highlighSource.getFeatureById(this.#highlightedFeatureIds[i]))
- this.#highlighSource.removeFeature(this.#highlighSource.getFeatureById(this.#highlightedFeatureIds[i]) as Feature);
+ if (this.highlighSource.getFeatureById(this.#highlightedFeatureIds[i]))
+ this.highlighSource.removeFeature(this.highlighSource.getFeatureById(this.#highlightedFeatureIds[i]) as Feature);
this.#highlightedFeatureIds.splice(i, 1);
}
}
@@ -202,8 +207,8 @@ export class FeatureHighlight {
* @param {boolean} isLayerHighlight - Optional if it is a layer highlight
*/
highlightGeolocatorBBox(extent: Extent, isLayerHighlight = false): void {
- if (this.#highlighSource.getFeatureById('geoLocatorFeature')) {
- this.#highlighSource.removeFeature(this.#highlighSource.getFeatureById('geoLocatorFeature') as Feature);
+ if (this.highlighSource.getFeatureById('geoLocatorFeature')) {
+ this.highlighSource.removeFeature(this.highlighSource.getFeatureById('geoLocatorFeature') as Feature);
clearTimeout(this.#bboxTimeout as NodeJS.Timeout);
}
const bboxPoly = fromExtent(extent);
@@ -211,10 +216,10 @@ export class FeatureHighlight {
const style = this.#darkOutlineStyle;
bboxFeature.setStyle(style);
bboxFeature.setId('geoLocatorFeature');
- this.#highlighSource.addFeature(bboxFeature);
+ this.highlighSource.addFeature(bboxFeature);
if (!isLayerHighlight)
this.#bboxTimeout = setTimeout(
- () => this.#highlighSource.removeFeature(this.#highlighSource.getFeatureById('geoLocatorFeature') as Feature),
+ () => this.highlighSource.removeFeature(this.highlighSource.getFeatureById('geoLocatorFeature') as Feature),
5000
);
}
@@ -223,6 +228,6 @@ export class FeatureHighlight {
* Removes bounding box highlight
*/
removeBBoxHighlight(): void {
- this.#highlighSource.removeFeature(this.#highlighSource.getFeatureById('geoLocatorFeature') as Feature);
+ this.highlighSource.removeFeature(this.highlighSource.getFeatureById('geoLocatorFeature') as Feature);
}
}
diff --git a/packages/geoview-core/src/geo/map/map-schema-types.ts b/packages/geoview-core/src/geo/map/map-schema-types.ts
index 5cdc8a691e9..952abd663dd 100644
--- a/packages/geoview-core/src/geo/map/map-schema-types.ts
+++ b/packages/geoview-core/src/geo/map/map-schema-types.ts
@@ -10,6 +10,7 @@ import {
TypeViewSettings,
TypeInteraction,
TypeHighlightColors,
+ TypeOverlayObjects,
TypeValidMapProjectionCodes,
TypeDisplayTheme,
TypeLocalizedString,
@@ -539,6 +540,8 @@ export type TypeMapConfig = {
viewSettings: TypeViewSettings; //! config
/** Highlight color. */
highlightColor?: TypeHighlightColors; //! config
+ /** Highlight color. */
+ overlayObjects?: TypeOverlayObjects; //! config
/** Additional options used for OpenLayers map options. */
extraOptions?: Record;
};
diff --git a/packages/geoview-core/src/geo/map/map-viewer.ts b/packages/geoview-core/src/geo/map/map-viewer.ts
index a3b1862cdd5..eb4cb0ae9a4 100644
--- a/packages/geoview-core/src/geo/map/map-viewer.ts
+++ b/packages/geoview-core/src/geo/map/map-viewer.ts
@@ -605,12 +605,18 @@ export class MapViewer {
// Zoom to extent provided in config, it present
if (this.mapFeaturesConfig.map.viewSettings.initialView?.extent)
- await this.zoomToExtent(
- Projection.transformExtent(
- this.mapFeaturesConfig.map.viewSettings.initialView?.extent,
- Projection.PROJECTION_NAMES.LNGLAT,
- `EPSG:${this.mapFeaturesConfig.map.viewSettings.projection}`
- )
+ setTimeout(
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
+ () =>
+ this.zoomToExtent(
+ Projection.transformExtent(
+ this.mapFeaturesConfig.map.viewSettings.initialView?.extent as Extent,
+ Projection.PROJECTION_NAMES.LNGLAT,
+ `EPSG:${this.mapFeaturesConfig.map.viewSettings.projection}`
+ ),
+ { padding: [0, 0, 0, 0] }
+ ).catch((error) => logger.logPromiseFailed('promiseMapLayers in #checkMapLayersProcessed in map-viewer', error)),
+ 200
);
// Zoom to extents of layers selected in config, if provided.