diff --git a/jest.config.js b/jest.config.js index e5f63aae3..81ef72041 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,7 +11,7 @@ module.exports = { }, transformIgnorePatterns: [ '/node_modules/(?!(ol|@babel|jest-runtime|@terrestris|color-*|query-string|' + - 'decode-uri-component|split-on-first|filter-obj|geostyler-openlayers-parser|geostyler-style|keycloak-js))' + 'decode-uri-component|split-on-first|filter-obj|geostyler-openlayers-parser|geostyler-style|keycloak-js|pbf))' ], testRegex: '/src/.*\\.spec.(ts|js)$', collectCoverageFrom: [ diff --git a/package-lock.json b/package-lock.json index bd71143ee..359545c2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "jest-cli": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "ol": "^10.3.1", + "ol-mapbox-style": "^12.4.0", "openapi-types": "^12.1.3", "rimraf": "^6.0.0", "semantic-release": "^24.2.1", @@ -56,7 +57,8 @@ }, "peerDependencies": { "@terrestris/ol-util": ">=20", - "ol": ">=10" + "ol": ">=10.3.1", + "ol-mapbox-style": ">=12.4.0" } }, "node_modules/@ampproject/remapping": { @@ -3331,6 +3333,38 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-style-spec": { + "version": "13.28.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz", + "integrity": "sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/unitbezier": "^0.0.0", + "csscolorparser": "~1.0.2", + "json-stringify-pretty-compact": "^2.0.0", + "minimist": "^1.2.6", + "rw": "^1.3.3", + "sort-object": "^0.3.2" + }, + "bin": { + "gl-style-composite": "bin/gl-style-composite.js", + "gl-style-format": "bin/gl-style-format.js", + "gl-style-migrate": "bin/gl-style-migrate.js", + "gl-style-validate": "bin/gl-style-validate.js" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -3409,6 +3443,20 @@ "node": ">=10" } }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", + "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8460,6 +8508,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "dev": true, + "license": "MIT" + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -13447,6 +13502,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz", + "integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -13798,6 +13860,13 @@ "tmpl": "1.0.5" } }, + "node_modules/mapbox-to-css-font": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.5.tgz", + "integrity": "sha512-VJ6nB8emkO9VODI0Fk+TQ/0zKBTqmf/Pkt8Xv0kHstoc0iXRajA00DAid4Kc3K5xeFIOoiZrVxijEzj0GLVO2w==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/marchingsquares": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/marchingsquares/-/marchingsquares-1.3.3.tgz", @@ -17562,6 +17631,20 @@ "url": "https://opencollective.com/openlayers" } }, + "node_modules/ol-mapbox-style": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-12.4.0.tgz", + "integrity": "sha512-P8Jg9AXSG6FpUNrADejpwMG0HbmHTZOJQQocACzaDL0QrU4kzmCvj06xUIKhTxT5mtC413pCVAbyXJ4mx0XFnQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@mapbox/mapbox-gl-style-spec": "^13.23.1", + "mapbox-to-css-font": "^2.4.1" + }, + "peerDependencies": { + "ol": "*" + } + }, "node_modules/ol/node_modules/earcut": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", @@ -19038,6 +19121,13 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -19651,6 +19741,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sort-asc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz", + "integrity": "sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.1.1.tgz", + "integrity": "sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-0.3.2.tgz", + "integrity": "sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==", + "dev": true, + "dependencies": { + "sort-asc": "^0.1.0", + "sort-desc": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 384da6710..f2a39cfa9 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "jest-cli": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "ol": "^10.3.1", + "ol-mapbox-style": "^12.4.0", "openapi-types": "^12.1.3", "rimraf": "^6.0.0", "semantic-release": "^24.2.1", @@ -78,7 +79,8 @@ }, "peerDependencies": { "@terrestris/ol-util": ">=20", - "ol": ">=10" + "ol": ">=10.3.1", + "ol-mapbox-style": ">=12.4.0" }, "engines": { "node": ">=20", diff --git a/src/model/enum/LayerType.ts b/src/model/enum/LayerType.ts index cd15c7ed6..274ef5d9d 100644 --- a/src/model/enum/LayerType.ts +++ b/src/model/enum/LayerType.ts @@ -1,3 +1,3 @@ -export type LayerType = 'TILEWMS' | 'VECTORTILE' | 'WFS' | 'WMS' | 'WMTS' | 'XYZ' | 'WMSTIME'; +export type LayerType = 'TILEWMS' | 'VECTORTILE' | 'WFS' | 'WMS' | 'WMTS' | 'XYZ' | 'WMSTIME' | 'MAPBOXSTYLE'; export default LayerType; diff --git a/src/parser/SHOGunApplicationUtil.ts b/src/parser/SHOGunApplicationUtil.ts index 7ffa37a0a..21f9b4d50 100644 --- a/src/parser/SHOGunApplicationUtil.ts +++ b/src/parser/SHOGunApplicationUtil.ts @@ -24,38 +24,33 @@ import OlPinchZoom from 'ol/interaction/PinchZoom'; import OlLayerBase from 'ol/layer/Base'; import OlLayerGroup from 'ol/layer/Group'; import OlImageLayer from 'ol/layer/Image'; +import OlLayer from 'ol/layer/Layer'; import OlTileLayer from 'ol/layer/Tile'; import OlLayerVector from 'ol/layer/Vector'; import { bbox as olStrategyBbox } from 'ol/loadingstrategy'; -import { fromLonLat, ProjectionLike as OlProjectionLike } from 'ol/proj'; +import { fromLonLat, Projection as OlProjection, ProjectionLike as OlProjectionLike } from 'ol/proj'; import { Units } from 'ol/proj/Units'; -import OlImageWMS, { - Options as OlImageWMSOptions -} from 'ol/source/ImageWMS'; -import OlTileWMS, { - Options as OlTileWMSOptions -} from 'ol/source/TileWMS'; +import OlImageWMS, { Options as OlImageWMSOptions } from 'ol/source/ImageWMS'; +import OlTileWMS, { Options as OlTileWMSOptions } from 'ol/source/TileWMS'; import OlSourceVector from 'ol/source/Vector'; import OlSourceWMTS, { optionsFromCapabilities } from 'ol/source/WMTS'; -import OlSourceXYZ, { - Options as OlSourceXYZOptions -} from 'ol/source/XYZ'; +import OlSourceXYZ, { Options as OlSourceXYZOptions } from 'ol/source/XYZ'; import OlTile from 'ol/Tile'; import OlTileGrid from 'ol/tilegrid/TileGrid'; import OlTileGridWMTS from 'ol/tilegrid/WMTS'; import OlTileState from 'ol/TileState'; import OlView, { ViewOptions as OlViewOptions } from 'ol/View'; +import { apply as applyMapboxStyle } from 'ol-mapbox-style'; + import Logger from '@terrestris/base-util/dist/Logger'; import { UrlUtil } from '@terrestris/base-util/dist/UrlUtil/UrlUtil'; import CapabilitiesUtil from '@terrestris/ol-util/dist/CapabilitiesUtil/CapabilitiesUtil'; import { MapUtil } from '@terrestris/ol-util/dist/MapUtil/MapUtil'; -import { ProjectionUtil, defaultProj4CrsDefinitions } from '@terrestris/ol-util/dist/ProjectionUtil/ProjectionUtil'; +import { defaultProj4CrsDefinitions, ProjectionUtil } from '@terrestris/ol-util/dist/ProjectionUtil/ProjectionUtil'; -import { - allLayersByIds -} from '../graphqlqueries/Layers'; +import { allLayersByIds } from '../graphqlqueries/Layers'; import Application, { DefaultLayerTree, MapInteraction } from '../model/Application'; import Layer from '../model/Layer'; import { getBearerTokenHeader } from '../security/getBearerTokenHeader'; @@ -143,7 +138,7 @@ class SHOGunApplicationUtil< }); } } catch (e) { - Logger.warn('Could not parse the layer tree: ' + e); + Logger.warn('Could not parse the layer tree: ', e); return new OlLayerGroup(); } } @@ -171,15 +166,13 @@ class SHOGunApplicationUtil< DragZoom: OlDragZoom }; - const olInteractions: OlInteraction[] = interactions.map((interaction: keyof typeof classMap) => { + return interactions.map((interaction: keyof typeof classMap) => { const InteractionClass = classMap[interaction] as typeof OlInteraction; if (InteractionClass) { return new InteractionClass(); } Logger.warn(`Interaction '${interaction}' not supported.`); }).filter((interaction: OlInteraction | undefined) => interaction !== undefined) as OlInteraction[]; - - return olInteractions; } private async fetchLayers(application: T): Promise { @@ -213,7 +206,7 @@ class SHOGunApplicationUtil< return layers; } catch (e) { - Logger.warn('Could not parse the layer tree: ' + e); + Logger.warn('Could not parse the layer tree: ', e); return []; } } @@ -301,8 +294,13 @@ class SHOGunApplicationUtil< return this.parseXYZLayer(layer); } - // TODO Add support for VECTORTILE - throw new Error('Currently only WMTS, WMS, TILEWMS, WFS, WMSTIME and XYZ layers are supported.'); + if (layer.type === 'MAPBOXSTYLE') { + return await this.parseMapboxStyleLayer(layer, projection); + } + + Logger.error(`Unsupported layer type ${layer.type} given. Currently only WMS, TILEWMS, WMTS, ` + + 'WFS, WMSTIME, XYZ and MAPBOXSTYLE are supported.'); + } parseImageLayer(layer: S) { @@ -696,6 +694,50 @@ class SHOGunApplicationUtil< return xyzLayer; } + async parseMapboxStyleLayer(layer: S, projection?: OlProjectionLike) { + const { + url, + useBearerToken, + resolutions + } = layer.sourceConfig || {}; + + const { + minResolution, + maxResolution, + opacity, + } = layer.clientConfig || {}; + + const mapBoxLayerGroup = new OlLayerGroup({ + minResolution, + maxResolution, + opacity + }); + + try { + await applyMapboxStyle( + mapBoxLayerGroup, + url, + { + projection: (projection instanceof OlProjection) ? projection.getCode() : projection, + resolutions: resolutions, + transformRequest: (resourceUrl) => { + return new Request(resourceUrl, { + headers: useBearerToken ? { + ...getBearerTokenHeader(this.client?.getKeycloak()) + } : {} + }); + } + } + ); + } catch (e) { + Logger.error(`Could apply mapbox style to OlLayerGroup: ${e}`); + } + + this.setLayerProperties(mapBoxLayerGroup, layer); + + return mapBoxLayerGroup; + } + getMapScales(resolutions: number[], projUnit: Units = 'm'): number[] { return resolutions .map((res: number) => diff --git a/tsconfig.json b/tsconfig.json index 3e029f930..0389eff56 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "lib": ["es5", "es6", "dom"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */