Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2691 Replace Raster Basemap with Vector Tile Basemap #2698

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@

import { ThemeProvider } from '@mui/material/styles';

import BaseLayer from 'ol/layer/Base';
import TileLayer from 'ol/layer/Tile';
import { OverviewMap as OLOverviewMap } from 'ol/control';
import VectorTileLayer from 'ol/layer/VectorTile';
import { VectorTile } from 'ol/source';
import OLMap from 'ol/Map';
import { OverviewMap as OLOverviewMap } from 'ol/control';
import { applyStyle } from 'ol-mapbox-style';

import { cgpvTheme } from '@/ui/style/theme';
import { OverviewMapToggle } from './overview-map-toggle';
Expand All @@ -24,6 +28,46 @@
olMap: OLMap;
};

function useCreateOverviewMapLayers(mapId: string): () => BaseLayer[] {
const createLayers = (): BaseLayer[] => {
const defaultBasemap = MapEventProcessor.createOverviewMapBasemap(mapId);

const newLayers: BaseLayer[] = [];
defaultBasemap?.layers.forEach((layer) => {
// create a tile layer for this basemap layer
let tileLayer;
if (layer.source instanceof VectorTile) {
tileLayer = new VectorTileLayer({
opacity: layer.opacity,
source: layer.source,
declutter: true,
});

const tileGrid = layer.source.getTileGrid();
if (tileGrid) {
applyStyle(tileLayer, layer.styleUrl, {
resolutions: tileGrid.getResolutions(),
}).catch((err) => logger.logError(err));
}
} else {
tileLayer = new TileLayer({
opacity: layer.opacity,
source: layer.source,
});
}

if (tileLayer) {
// add this layer to the basemap group
tileLayer.set(mapId, 'basemap');
newLayers.push(tileLayer as BaseLayer);
}
});
return newLayers;
};

return createLayers;
}

/**
* Creates an overview map control and adds it to the map
* @param {OverwiewMapProps} props - Overview map props containing the viewer
Expand All @@ -36,6 +80,7 @@

const { olMap } = props;
const mapId = useGeoViewMapId();
const createLayers = useCreateOverviewMapLayers(mapId);

// get the values from store
const hideOnZoom = useMapOverviewMapHideZoom();
Expand Down Expand Up @@ -76,34 +121,26 @@
// TODO: use async - look for better options then Timeout
setTimeout(() => {
overviewMapCtrl.setMap(olMap!);
setTimeout(() => overviewMapCtrl.setCollapsed(false), 500);
setTimeout(() => {
overviewMapCtrl.setCollapsed(false);
overviewMapCtrl.getOverviewMap().setLayers(createLayers());
}, 500);
}, 2000);
}
}, [projection, olMap]);

Check warning on line 130 in packages/geoview-core/src/core/components/overview-map/overview-map.tsx

View workflow job for this annotation

GitHub Actions / Build demo files / build-geoview

React Hook useEffect has a missing dependency: 'createLayers'. Either include it or remove the dependency array

useEffect(() => {
// Log
logger.logTraceUseEffect('OVERVIEW-MAP - displayLanguage', displayLanguage, displayTheme);

// get default overview map
const defaultBasemap = MapEventProcessor.createOverviewMapBasemap(mapId);

const toggleButton = document.createElement('div');

// If moving the overview map creation / updating somewhere else, maybe can do the below instead
// overviewMapControl.element = toggleButton;

const overviewMapControl = new OLOverviewMap({
className: `ol-overviewmap ol-custom-overviewmap`,
layers: defaultBasemap?.layers.map((layer) => {
// create a tile layer for this basemap layer
const tileLayer = new TileLayer({
opacity: layer.opacity,
source: layer.source,
});

// add this layer to the basemap group
tileLayer.set(mapId, 'basemap');

return tileLayer;
}),
layers: createLayers(),
collapseLabel: toggleButton,
label: toggleButton,
collapsed: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,14 @@ export class UUIDmapConfigReader {
// Log
logger.logWarning(`Layer type ${layerType} not supported`);
}

// If there's only the one layer, replace the layer name with the name from GeoCore
if (
listOfGeoviewLayerConfig[i].listOfLayerEntryConfig.length === 1 &&
!listOfGeoviewLayerConfig[i].listOfLayerEntryConfig[0].listOfLayerEntryConfig
) {
listOfGeoviewLayerConfig[i].listOfLayerEntryConfig[0].layerName = name as string;
}
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions packages/geoview-core/src/geo/layer/basemap/basemap-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OSM, XYZ } from 'ol/source';
import { OSM, VectorTile, XYZ } from 'ol/source';
import { Extent } from 'ol/extent';

/** ******************************************************************************************************************************
Expand Down Expand Up @@ -27,7 +27,8 @@ export type TypeBasemapLayer = {
basemapId: string;
url?: string;
jsonUrl?: string;
source: OSM | XYZ;
styleUrl?: string;
source: OSM | XYZ | VectorTile;
type: string;
opacity: number;
resolutions: number[];
Expand Down
143 changes: 89 additions & 54 deletions packages/geoview-core/src/geo/layer/basemap/basemap.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import axios, { AxiosResponse } from 'axios';

import { Extent } from 'ol/extent';
import { XYZ, OSM } from 'ol/source';
import { XYZ, OSM, VectorTile } from 'ol/source';
import TileGrid from 'ol/tilegrid/TileGrid';
import TileLayer from 'ol/layer/Tile';
import VectorTileLayer from 'ol/layer/VectorTile';
import MVT from 'ol/format/MVT';

import { applyStyle } from 'ol-mapbox-style';

import { TypeBasemapOptions, TypeValidMapProjectionCodes, TypeDisplayLanguage } from '@config/types/map-schema-types';
import { EventDelegateBase, api } from '@/app';
Expand Down Expand Up @@ -72,20 +76,28 @@ export class Basemap {
basemapsList: TypeJsonObject = toJsonObject({
3978: {
transport: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBMT_CBCT_GEOM_3978/MapServer/WMTS/tile/1.0.0/CBMT_CBCT_GEOM_3978/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBMT_CBCT_GEOM_3978/MapServer?f=json',
url: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3978_V_OSM/VectorTileServer',
jsonUrl: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3978_V_OSM/VectorTileServer?f=json',
styleUrl:
'https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/b2bba07183154bc7a0b0073be80474f7/resources/styles/root.json',
},
simple: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/Simple/MapServer/WMTS/tile/1.0.0/Simple/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/Simple/MapServer?f=json',
url: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/Simple_V/VectorTileServer',
jsonUrl: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/Simple_V/VectorTileServer?f=json',
styleUrl:
'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/Simple_V/VectorTileServer/resources/styles/root.json',
},
shaded: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBME_CBCE_HS_RO_3978/MapServer/WMTS/tile/1.0.0/CBMT_CBCT_GEOM_3978/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBME_CBCE_HS_RO_3978/MapServer?f=json',
},
label: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/xxxx_TXT_3978/MapServer/WMTS/tile/1.0.0/xxxx_TXT_3978/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/xxxx_TXT_3978/MapServer?f=json',
url: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3978_V_OSM/VectorTileServer',
jsonUrl: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3978_V_OSM/VectorTileServer?f=json',
styleUrl: {
en: 'https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/5e29be10a78547a3a6a219ccf7bd42d0/resources/styles/root.json',
fr: 'https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/667be32b4489469cbccc551144d457be/resources/styles/root.json',
},
},
imagery: {
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
Expand All @@ -94,20 +106,28 @@ export class Basemap {
},
3857: {
transport: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBMT_CBCT_GEOM_3857/MapServer/WMTS/tile/1.0.0/BaseMaps_CBMT_CBCT_GEOM_3857/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBMT_CBCT_GEOM_3857/MapServer?f=json',
url: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3857_V_OSM/VectorTileServer',
jsonUrl: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3857_V_OSM/VectorTileServer?f=json',
styleUrl:
'https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/516e82031eee4e97a71652ebb0e94879/resources/styles/root.json',
},
simple: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/Simple/MapServer/WMTS/tile/1.0.0/Simple/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/Simple/MapServer?f=json',
url: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/Simple_3857/VectorTileServer',
jsonUrl: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/Simple_3857/VectorTileServer?f=json',
styleUrl:
'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/Simple_3857/VectorTileServer/resources/styles/root.json',
},
shaded: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBME_CBCE_HS_RO_3978/MapServer/WMTS/tile/1.0.0/CBMT_CBCT_GEOM_3978/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/CBME_CBCE_HS_RO_3978/MapServer?f=json',
},
label: {
url: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/xxxx_TXT_3857/MapServer/WMTS/tile/1.0.0/xxxx_TXT_3857/default/default028mm/{z}/{y}/{x}.jpg',
jsonUrl: 'https://maps-cartes.services.geo.ca/server2_serveur2/rest/services/BaseMaps/xxxx_TXT_3857/MapServer?f=json',
url: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3857_V_OSM/VectorTileServer',
jsonUrl: 'https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3857_V_OSM/VectorTileServer?f=json',
styleUrl: {
en: 'https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/50cb5f2dfaaf4ccb88f37be2d8e5661e/resources/styles/root.json',
fr: 'https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/0d77943ff03e43fc933b411f80c9b835/resources/styles/root.json',
},
},
imagery: {
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
Expand All @@ -119,27 +139,6 @@ export class Basemap {
// Keep all callback delegates references
#onBasemapChangedHandlers: BasemapChangedDelegate[] = [];

// #region PRIVATE UTILITY FUNCTIONS

/**
* Get projection from basemap url
* Because OpenLayers can reproject on the fly raster, some like Shaded and Simple even if only available in 3978
* can be use in 3857. For this we need to make a difference between map projection and url use for the basemap
* @param {string} url - Basemap url
* @returns {number} Projection code
* @private
*/
static #getProjectionFromUrl(url: string): number {
let code = 0;
const index = url.indexOf('/MapServer');

if (url.substring(index - 6, index) === 'Simple') code = 3978;
else code = Number(url.substring(index - 4, index));

return code;
}
// #endregion

// #region OVERVIEW MAP
async setOverviewMap(): Promise<void> {
const overviewMap = await this.createCoreBasemap({ basemapId: 'transport', shaded: false, labeled: false });
Expand Down Expand Up @@ -235,17 +234,26 @@ export class Basemap {
// Set extent for this layer
extent = [fullExtent.xmin as number, fullExtent.ymin as number, fullExtent.xmax as number, fullExtent.ymax as number];

// Because OpenLayers can reproject on the fly raster, some like Shaded and Simple even if only available in 3978
// can be use in 3857. For this we need to make a difference between map projection and url use for the basemap
urlProj = Basemap.#getProjectionFromUrl(basemapLayer.url as string);
// Set the spatial Reference for this layer
urlProj = tileInfo.spatialReference.latestWkid as number;

// Return a basemap layer
return {
basemapId,
type: basemapId,
url: basemapLayer.url as string,
jsonUrl: basemapLayer.jsonUrl as string,
source: new XYZ({
let source;
if (basemapLayer.styleUrl) {
const tileSize = [tileInfo.rows as number, tileInfo.cols as number];
source = new VectorTile({
attributions: getLocalizedMessage('mapctrl.attribution.defaultnrcan', AppEventProcessor.getDisplayLanguage(this.mapId)),
projection: Projection.PROJECTIONS[urlProj],
url: basemapLayer.url as string,
format: new MVT(),
tileGrid: new TileGrid({
tileSize,
extent,
origin,
resolutions,
}),
});
} else {
source = new XYZ({
attributions: getLocalizedMessage('mapctrl.attribution.defaultnrcan', AppEventProcessor.getDisplayLanguage(this.mapId)),
projection: Projection.PROJECTIONS[urlProj],
url: basemapLayer.url as string,
Expand All @@ -255,7 +263,17 @@ export class Basemap {
origin,
resolutions,
}),
}),
});
}

// Return a basemap layer
return {
basemapId,
type: basemapId,
url: basemapLayer.url as string,
jsonUrl: basemapLayer.jsonUrl as string,
styleUrl: basemapLayer.styleUrl as string,
source,
opacity,
origin,
extent,
Expand Down Expand Up @@ -403,11 +421,9 @@ export class Basemap {
const labelLayer = await this.#createBasemapLayer(
'label',
toJsonObject({
url: (this.basemapsList[projectionCode].label.url as string)?.replaceAll('xxxx', languageCode === 'en' ? 'CBMT' : 'CBCT'),
jsonUrl: (this.basemapsList[projectionCode].label.jsonUrl as string)?.replaceAll(
'xxxx',
languageCode === 'en' ? 'CBMT' : 'CBCT'
),
url: this.basemapsList[projectionCode].label.url as string,
jsonUrl: this.basemapsList[projectionCode].label.jsonUrl as string,
styleUrl: this.basemapsList[projectionCode].label.styleUrl[languageCode] as string,
}),
0.8,
true
Expand Down Expand Up @@ -487,6 +503,7 @@ export class Basemap {
return {
...layer,
url: languageCode === 'en' ? (layer.url as unknown as bilingual).en : (layer.url as unknown as bilingual).fr,
// TODO: Handle custom vector tile basemaps too
source: new XYZ({
attributions: attribution[languageCode],
projection: Projection.PROJECTIONS[projection],
Expand Down Expand Up @@ -525,6 +542,7 @@ export class Basemap {
this.defaultExtent = basemap?.defaultExtent;

this.setBasemap(basemap);
await this.setOverviewMap();
}
}

Expand Down Expand Up @@ -560,10 +578,27 @@ export class Basemap {

// Add basemap layers
basemap.layers.forEach((layer, index) => {
const basemapLayer = new TileLayer({
opacity: layer.opacity,
source: layer.source,
});
let basemapLayer;
if (layer.source instanceof VectorTile) {
basemapLayer = new VectorTileLayer({
opacity: layer.opacity,
source: layer.source,
declutter: true,
});

// Apply Style to Vector Tile Basemap
const tileGrid = layer.source.getTileGrid();
if (tileGrid) {
applyStyle(basemapLayer, layer.styleUrl, {
resolutions: tileGrid.getResolutions(),
}).catch((err) => logger.logError(err));
}
} else {
basemapLayer = new TileLayer({
opacity: layer.opacity,
source: layer.source,
});
}

// Set this basemap's group id to basemap
basemapLayer.set('mapId', 'basemap');
Expand Down
Loading
Loading