diff --git a/angular.json b/angular.json index ff910dfe6..ec822b8b1 100644 --- a/angular.json +++ b/angular.json @@ -61,13 +61,18 @@ "glob": "**/*", "input": "projects/admin-core/assets/icons", "output": "icons" + }, + { + "glob": "**/*", + "input": "./node_modules/cesium/Build/Cesium", + "output": "cesium" } ], "styles": [ "projects/core/assets/custom-theme.scss", - "projects/core/assets/tailormap-styles.css" + "projects/core/assets/tailormap-styles.css", + "./node_modules/cesium/Build/Cesium/Widgets/widgets.css" ], - "scripts": [], "allowedCommonJsDependencies": [ "xml-utils", "pbf", diff --git a/package-lock.json b/package-lock.json index 52d38f327..8064cbbec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@sentry/browser": "^8.33.1", "@stardazed/zlib": "^1.0.1", "@tinyhttp/content-disposition": "^2.2.1", + "cesium": "^1.116.0", "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "jsts": "^2.11.3", @@ -5165,18 +5166,6 @@ "node": ">=14" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -6188,6 +6177,39 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -6457,9 +6479,9 @@ } }, "node_modules/@types/node": { - "version": "20.16.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.9.tgz", - "integrity": "sha512-rkvIVJxsOfBejxK7I0FO5sa2WxFmJCzoDwcd88+fq/CUfynNywTo/1/T6hyFz22CyztsnLS9nVlHOnTI36RH5w==", + "version": "20.14.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", + "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", "dev": true, "dependencies": { "undici-types": "~6.19.2" @@ -6499,9 +6521,10 @@ "dev": true }, "node_modules/@types/rbush": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.3.tgz", - "integrity": "sha512-lX55lR0iYCgapxD3IrgujpQA1zDxwZI5qMRelKvmKAsSMplFVr7wmMpG7/6+Op2tjrgEex8o3vjg8CRDrRNYxg==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.4.tgz", + "integrity": "sha512-knSt9cCW8jj1ZSFcFeBZaX++OucmfPxxHiRwTahZfJlnQsek7O0bazTJHWD2RVj9LEoejUYF2de3/stf+QXcXw==", + "license": "MIT" }, "node_modules/@types/resolve": { "version": "1.20.2", @@ -7172,6 +7195,37 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.0-rc.46", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", + "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", + "dev": true, + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@zkochan/js-yaml": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", + "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@zkochan/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -10036,6 +10090,39 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "dev": true, + "dependencies": { + "dotenv": "^16.4.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "node_modules/earcut": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", @@ -15389,76 +15476,6 @@ "node": ">=12.0.0" } }, - "node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -15646,15 +15663,6 @@ "source-map-support": "^0.5.5" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -17503,11 +17511,12 @@ "dev": true }, "node_modules/ol": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/ol/-/ol-10.2.1.tgz", - "integrity": "sha512-2bB/y2vEnmzjqynP0NA7Cp8k86No3Psn63Dueicep3E3i09axWRVIG5IS/bylEAGfWQx0QXD/uljkyFoY60Wig==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ol/-/ol-10.1.0.tgz", + "integrity": "sha512-/efepydpzhFoeczA9KAN5t7G0WpFhP46ZXEfSl6JbZ7ipQZ2axpkYB2qt0qcOUlPFYMt7/XQFApH652KB08tTg==", + "license": "BSD-2-Clause", "dependencies": { - "@types/rbush": "3.0.3", + "@types/rbush": "^3.0.3", "color-rgba": "^3.0.0", "color-space": "^2.0.1", "earcut": "^3.0.0", @@ -21027,9 +21036,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -26053,12 +26062,6 @@ "dev": true, "optional": true }, - "@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true - }, "@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -26741,6 +26744,35 @@ "requires": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.4" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "requires": { + "tslib": "^2.4.0" } }, "@types/aria-query": { @@ -27007,9 +27039,9 @@ } }, "@types/node": { - "version": "20.16.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.9.tgz", - "integrity": "sha512-rkvIVJxsOfBejxK7I0FO5sa2WxFmJCzoDwcd88+fq/CUfynNywTo/1/T6hyFz22CyztsnLS9nVlHOnTI36RH5w==", + "version": "20.14.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", + "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", "dev": true, "requires": { "undici-types": "~6.19.2" @@ -27049,9 +27081,9 @@ "dev": true }, "@types/rbush": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.3.tgz", - "integrity": "sha512-lX55lR0iYCgapxD3IrgujpQA1zDxwZI5qMRelKvmKAsSMplFVr7wmMpG7/6+Op2tjrgEex8o3vjg8CRDrRNYxg==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.4.tgz", + "integrity": "sha512-knSt9cCW8jj1ZSFcFeBZaX++OucmfPxxHiRwTahZfJlnQsek7O0bazTJHWD2RVj9LEoejUYF2de3/stf+QXcXw==" }, "@types/resolve": { "version": "1.20.2", @@ -27552,6 +27584,33 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "@yarnpkg/parsers": { + "version": "3.0.0-rc.46", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", + "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", + "dev": true, + "requires": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + } + }, + "@zkochan/js-yaml": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", + "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } + } + }, "abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -29633,6 +29692,27 @@ "domhandler": "^5.0.3" } }, + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true + }, + "dotenv-expand": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "dev": true, + "requires": { + "dotenv": "^16.4.4" + } + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "earcut": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", @@ -33615,61 +33695,6 @@ "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true }, - "jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - } - } - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -33812,15 +33837,6 @@ "source-map-support": "^0.5.5" } }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -35147,11 +35163,11 @@ "dev": true }, "ol": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/ol/-/ol-10.2.1.tgz", - "integrity": "sha512-2bB/y2vEnmzjqynP0NA7Cp8k86No3Psn63Dueicep3E3i09axWRVIG5IS/bylEAGfWQx0QXD/uljkyFoY60Wig==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ol/-/ol-10.1.0.tgz", + "integrity": "sha512-/efepydpzhFoeczA9KAN5t7G0WpFhP46ZXEfSl6JbZ7ipQZ2axpkYB2qt0qcOUlPFYMt7/XQFApH652KB08tTg==", "requires": { - "@types/rbush": "3.0.3", + "@types/rbush": "^3.0.3", "color-rgba": "^3.0.0", "color-space": "^2.0.1", "earcut": "^3.0.0", @@ -37693,9 +37709,9 @@ } }, "undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, "unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index 8ef858db2..e2cb79089 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@sentry/browser": "^8.33.1", "@stardazed/zlib": "^1.0.1", "@tinyhttp/content-disposition": "^2.2.1", + "cesium": "^1.116.0", "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "jsts": "^2.11.3", @@ -57,6 +58,7 @@ "nanoid": "^5.0.7", "ol": "^10.2.1", "proj4": "^2.12.1", + "olcs": "^2.22.0", "rxjs": "^7.8.1", "svg2pdf.js": "^2.2.4", "tslib": "^2.6.2", diff --git a/projects/admin-api/src/lib/models/geo-service-protocol.enum.ts b/projects/admin-api/src/lib/models/geo-service-protocol.enum.ts index c64fcf0c3..9df648382 100644 --- a/projects/admin-api/src/lib/models/geo-service-protocol.enum.ts +++ b/projects/admin-api/src/lib/models/geo-service-protocol.enum.ts @@ -2,4 +2,5 @@ export enum GeoServiceProtocolEnum { WMS = 'wms', WMTS = 'wmts', XYZ = 'xyz', + TILESET3D = 'Tileset3D' } diff --git a/projects/admin-core/assets/locale/messages.admin-core.de.xlf b/projects/admin-core/assets/locale/messages.admin-core.de.xlf index e4a901feb..140194a18 100644 --- a/projects/admin-core/assets/locale/messages.admin-core.de.xlf +++ b/projects/admin-core/assets/locale/messages.admin-core.de.xlf @@ -158,6 +158,10 @@ Editable Bearbeitbar + + Enable 3D viewing + Enable 3D viewing + Error while creating application. Fehler beim Erstellen der Anwendung. @@ -1691,4 +1695,4 @@ - + \ No newline at end of file diff --git a/projects/admin-core/assets/locale/messages.admin-core.en.xlf b/projects/admin-core/assets/locale/messages.admin-core.en.xlf index 0256b59fb..b9bcdf3f9 100644 --- a/projects/admin-core/assets/locale/messages.admin-core.en.xlf +++ b/projects/admin-core/assets/locale/messages.admin-core.en.xlf @@ -119,6 +119,9 @@ Editable + + Enable 3D viewing + Error while creating application. diff --git a/projects/admin-core/assets/locale/messages.admin-core.nl.xlf b/projects/admin-core/assets/locale/messages.admin-core.nl.xlf index 75ad16ce5..1eb251e1a 100644 --- a/projects/admin-core/assets/locale/messages.admin-core.nl.xlf +++ b/projects/admin-core/assets/locale/messages.admin-core.nl.xlf @@ -157,6 +157,10 @@ Editable Bewerkbaar + + Enable 3D viewing + 3D-weergave inschakelen + Error while creating application. Fout bij het maken van de applicatie. @@ -1675,4 +1679,4 @@ - + \ No newline at end of file diff --git a/projects/admin-core/src/lib/application/application-form/application-form.component.html b/projects/admin-core/src/lib/application/application-form/application-form.component.html index 9e3214ae0..04cbb822c 100644 --- a/projects/admin-core/src/lib/application/application-form/application-form.component.html +++ b/projects/admin-core/src/lib/application/application-form/application-form.component.html @@ -53,6 +53,9 @@ Hide language switcher in viewer + Enable 3D viewing + } diff --git a/projects/admin-core/src/lib/application/application-form/application-form.component.spec.ts b/projects/admin-core/src/lib/application/application-form/application-form.component.spec.ts index 1686b380e..a7e28bd30 100644 --- a/projects/admin-core/src/lib/application/application-form/application-form.component.spec.ts +++ b/projects/admin-core/src/lib/application/application-form/application-form.component.spec.ts @@ -68,6 +68,7 @@ describe('ApplicationFormComponent', () => { hideLanguageSwitcher: false, }, uiSettings: { + enable3D: false, hideLoginButton: false, }, }); @@ -96,6 +97,7 @@ describe('ApplicationFormComponent', () => { hideLanguageSwitcher: false, }, uiSettings: { + enable3D: false, hideLoginButton: false, }, }); diff --git a/projects/admin-core/src/lib/application/application-form/application-form.component.ts b/projects/admin-core/src/lib/application/application-form/application-form.component.ts index a4bc24ff9..fed3d5684 100644 --- a/projects/admin-core/src/lib/application/application-form/application-form.component.ts +++ b/projects/admin-core/src/lib/application/application-form/application-form.component.ts @@ -64,6 +64,7 @@ export class ApplicationFormComponent implements OnInit, OnDestroy { }), defaultLanguage: new FormControl(null), hideLoginButton: new FormControl(null), + enable3D: new FormControl(null), hideLanguageSwitcher: new FormControl(null), initialExtent: new FormControl(null), maxExtent: new FormControl(null), @@ -122,6 +123,7 @@ export class ApplicationFormComponent implements OnInit, OnDestroy { }; const uiSettings: UiSettingsModel = { hideLoginButton: typeof value.hideLoginButton === 'boolean' ? value.hideLoginButton : false, + enable3D: typeof value.enable3D === 'boolean' ? value.enable3D : false, }; this.updateApplication.emit({ application, i18nSettings, uiSettings }); }); @@ -160,6 +162,9 @@ export class ApplicationFormComponent implements OnInit, OnDestroy { hideLoginButton: typeof application?.settings?.uiSettings?.hideLoginButton === "boolean" ? application.settings.uiSettings.hideLoginButton : null, + enable3D: typeof application?.settings?.uiSettings?.enable3D === "boolean" + ? application.settings.uiSettings.enable3D + : null, authorizationRules: application ? application.authorizationRules : [AUTHORIZATION_RULE_ANONYMOUS], }, { emitEvent: false }); } diff --git a/projects/admin-core/src/lib/catalog/geo-service-form/geo-service-form.component.ts b/projects/admin-core/src/lib/catalog/geo-service-form/geo-service-form.component.ts index 90acf4241..0f1a1cb8e 100644 --- a/projects/admin-core/src/lib/catalog/geo-service-form/geo-service-form.component.ts +++ b/projects/admin-core/src/lib/catalog/geo-service-form/geo-service-form.component.ts @@ -20,7 +20,9 @@ export class GeoServiceFormComponent implements OnInit { private destroyed = new Subject(); private _geoService: GeoServiceModel | null = null; - public protocols: GeoServiceProtocolEnum[] = [ GeoServiceProtocolEnum.WMS, GeoServiceProtocolEnum.WMTS, GeoServiceProtocolEnum.XYZ ]; + public protocols: GeoServiceProtocolEnum[] = [ + GeoServiceProtocolEnum.WMS, GeoServiceProtocolEnum.WMTS, GeoServiceProtocolEnum.XYZ, GeoServiceProtocolEnum.TILESET3D, + ]; private readonly XYZ_CRS_DEFAULT = 'EPSG:3857'; @Input() diff --git a/projects/admin-core/src/lib/catalog/helpers/catalog-filter.helper.ts b/projects/admin-core/src/lib/catalog/helpers/catalog-filter.helper.ts index 0989dccf3..5efee7487 100644 --- a/projects/admin-core/src/lib/catalog/helpers/catalog-filter.helper.ts +++ b/projects/admin-core/src/lib/catalog/helpers/catalog-filter.helper.ts @@ -4,7 +4,7 @@ import { ExtendedGeoServiceLayerModel } from '../models/extended-geo-service-lay import { ExtendedFeatureSourceModel } from '../models/extended-feature-source.model'; import { ExtendedFeatureTypeModel } from '../models/extended-feature-type.model'; import { CatalogTreeHelper } from './catalog-tree.helper'; -import { CatalogItemKindEnum } from '@tailormap-admin/admin-api'; +import { CatalogItemKindEnum, GeoServiceProtocolEnum } from '@tailormap-admin/admin-api'; import { CatalogExtendedModel } from '../models/catalog-extended.model'; import { ExtendedCatalogModelHelper } from './extended-catalog-model.helper'; @@ -58,8 +58,14 @@ export class CatalogFilterHelper { return CatalogTreeHelper.catalogToTree(catalogNodes, services, serviceLayers, [], [], featureTypes); } const allLayersMap = new Map(serviceLayers.map(l => [ l.id, l ])); + const allServicesMap = new Map(services.map(s => [ s.id, s ])); const filteredItems = CatalogFilterHelper.getFilteredItems(catalogNodes, services, serviceLayers, [], featureTypes, item => { if (ExtendedCatalogModelHelper.isGeoServiceLayerModel(item)) { + const service = allServicesMap.get(item.serviceId); + if (service && service.protocol === GeoServiceProtocolEnum.TILESET3D) { + return true; + } + // if (item.crs?.includes(crs)) { return true; } diff --git a/projects/api/src/lib/models/app-layer.model.ts b/projects/api/src/lib/models/app-layer.model.ts index 83d440c92..478c92c07 100644 --- a/projects/api/src/lib/models/app-layer.model.ts +++ b/projects/api/src/lib/models/app-layer.model.ts @@ -27,5 +27,6 @@ export interface AppLayerModel { attribution?: string; description?: string; autoRefreshInSeconds?: number | null; + enableCollision?: boolean; searchIndex: LayerSearchIndexModel | null; } diff --git a/projects/api/src/lib/models/service-protocol.enum.ts b/projects/api/src/lib/models/service-protocol.enum.ts index c46e1f907..b12571c39 100644 --- a/projects/api/src/lib/models/service-protocol.enum.ts +++ b/projects/api/src/lib/models/service-protocol.enum.ts @@ -2,4 +2,5 @@ export enum ServiceProtocol { WMS = 'wms', WMTS = 'wmts', XYZ = 'xyz', + TILESET3D = 'Tileset3D', } diff --git a/projects/api/src/lib/models/ui-settings.model.ts b/projects/api/src/lib/models/ui-settings.model.ts index bb10a9ce7..78bb889d5 100644 --- a/projects/api/src/lib/models/ui-settings.model.ts +++ b/projects/api/src/lib/models/ui-settings.model.ts @@ -1,3 +1,4 @@ export interface UiSettingsModel { hideLoginButton?: boolean; + enable3D?: boolean; } diff --git a/projects/core/assets/icons/tools/3d.svg b/projects/core/assets/icons/tools/3d.svg new file mode 100644 index 000000000..ad8fb9580 --- /dev/null +++ b/projects/core/assets/icons/tools/3d.svg @@ -0,0 +1 @@ + diff --git a/projects/core/assets/locale/messages.core.de.xlf b/projects/core/assets/locale/messages.core.de.xlf index ff5e15929..eb74b45ef 100644 --- a/projects/core/assets/locale/messages.core.de.xlf +++ b/projects/core/assets/locale/messages.core.de.xlf @@ -529,6 +529,10 @@ Draw filter geometry Filtergeometrie zeichnen + + Switch to 2D to draw + Switch to 2D to draw + ends with endet mit @@ -1001,6 +1005,10 @@ Embed URL URL einbetten + + Switch to 3D + Switch to 3D + Type at least characters to start searching Geben Sie mindestens Zeichen ein, um mit der Suche zu beginnen @@ -1035,4 +1043,4 @@ - + \ No newline at end of file diff --git a/projects/core/assets/locale/messages.core.en.xlf b/projects/core/assets/locale/messages.core.en.xlf index 4b01c07d4..8abcf845a 100644 --- a/projects/core/assets/locale/messages.core.en.xlf +++ b/projects/core/assets/locale/messages.core.en.xlf @@ -398,6 +398,9 @@ Draw filter geometry + + Switch to 2D to draw + ends with @@ -752,6 +755,9 @@ Embed URL + + Switch to 3D + Type at least characters to start searching diff --git a/projects/core/assets/locale/messages.core.nl.xlf b/projects/core/assets/locale/messages.core.nl.xlf index 138d1421f..d9060d6a8 100644 --- a/projects/core/assets/locale/messages.core.nl.xlf +++ b/projects/core/assets/locale/messages.core.nl.xlf @@ -529,6 +529,10 @@ Draw filter geometry Filtergeometrie tekenen + + Switch to 2D to draw + Switch naar 2D om te tekenen + ends with eindigt met @@ -1001,6 +1005,10 @@ Embed URL URL insluiten + + Switch to 3D + Activeer 3D + Type at least characters to start searching Typ ten minste tekens om te beginnen met zoeken diff --git a/projects/core/src/lib/components/drawing/drawing-menu-button/drawing-menu-button.component.spec.ts b/projects/core/src/lib/components/drawing/drawing-menu-button/drawing-menu-button.component.spec.ts index 68221c4c3..42275bbf7 100644 --- a/projects/core/src/lib/components/drawing/drawing-menu-button/drawing-menu-button.component.spec.ts +++ b/projects/core/src/lib/components/drawing/drawing-menu-button/drawing-menu-button.component.spec.ts @@ -7,6 +7,7 @@ import userEvent from '@testing-library/user-event'; import { SharedModule } from '@tailormap-viewer/shared'; import { provideMockStore } from '@ngrx/store/testing'; import { coreStateKey, initialCoreState } from '../../../state/core.state'; +import { selectIn3DView } from '../../../map/state/map.selectors'; describe('DrawingMenuButtonComponent', () => { @@ -20,7 +21,10 @@ describe('DrawingMenuButtonComponent', () => { declarations: [MenubarButtonComponent], imports: [ SharedModule, MatIconTestingModule ], providers: [ - provideMockStore({ initialState: { [coreStateKey]: initialCoreState } }), + provideMockStore({ + initialState: { [coreStateKey]: initialCoreState }, + selectors: [{ selector: selectIn3DView, value: false }], + }), { provide: MenubarService, useValue: menubarService }, ], }); diff --git a/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.html b/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.html index 9fbc2627e..b8a890ff9 100644 --- a/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.html +++ b/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.html @@ -22,8 +22,12 @@

Add spatial filter

- - + @if (( in3DView$ | async ) === false) { + + + } @else { +
Switch to 2D to draw
+ }
diff --git a/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.spec.ts b/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.spec.ts index e14929c50..08e3db17a 100644 --- a/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.spec.ts +++ b/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.spec.ts @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/angular'; import { SpatialFilterFormComponent } from './spatial-filter-form.component'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; -import { selectFilterableLayers } from '../../../map/state/map.selectors'; +import { selectFilterableLayers, selectIn3DView } from '../../../map/state/map.selectors'; import { hasSelectedLayersAndGeometry, selectSelectedFilterGroupError, selectSelectedFilterGroupId, selectSelectedLayersCount, @@ -34,6 +34,7 @@ const setup = async (conf: { { selector: hasSelectedLayersAndGeometry, value: conf.selectedLayersAndGeometry || false }, { selector: selectSelectedFilterGroupId, value: conf.selectedFilterGroup?.id || null }, { selector: selectSelectedFilterGroupError, value: conf.selectedFilterGroup?.error || undefined }, + { selector: selectIn3DView, value: false }, ], }); const mapServiceMock = createMapServiceMock(); diff --git a/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.ts b/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.ts index f3f6d638b..e9d6eea20 100644 --- a/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.ts +++ b/projects/core/src/lib/components/filter/spatial-filter-form/spatial-filter-form.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; -import { selectFilterableLayers } from '../../../map/state/map.selectors'; +import { selectFilterableLayers, selectIn3DView } from '../../../map/state/map.selectors'; import { ExtendedAppLayerModel } from '../../../map/models'; import { Observable, of, Subject, switchMap, takeUntil } from 'rxjs'; import { MapService } from '@tailormap-viewer/map'; @@ -43,6 +43,7 @@ export class SpatialFilterFormComponent implements OnInit, OnDestroy { public hasSelectedLayersAndGeometry$: Observable = of(false); public isLoadingReferenceGeometry$: Observable = of(false); public currentGroupError$: Observable = of(undefined); + public in3DView$: Observable = of(false); constructor( private store$: Store, @@ -59,6 +60,7 @@ export class SpatialFilterFormComponent implements OnInit, OnDestroy { this.hasSelectedLayersAndGeometry$ = this.store$.select(hasSelectedLayersAndGeometry); this.currentGroup$ = this.store$.select(selectSelectedFilterGroupId); this.currentGroupError$ = this.store$.select(selectSelectedFilterGroupError); + this.in3DView$ = this.store$.select(selectIn3DView); this.isLoadingReferenceGeometry$ = this.currentGroup$ .pipe( filter(TypesHelper.isDefined), diff --git a/projects/core/src/lib/components/menubar/menubar.component.spec.ts b/projects/core/src/lib/components/menubar/menubar.component.spec.ts index b639197e6..9edb3e0d4 100644 --- a/projects/core/src/lib/components/menubar/menubar.component.spec.ts +++ b/projects/core/src/lib/components/menubar/menubar.component.spec.ts @@ -8,6 +8,8 @@ import { SharedModule } from '@tailormap-viewer/shared'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RegisteredComponentsRendererComponent } from '../registered-components-renderer/registered-components-renderer.component'; import { ComponentRegistrationService } from '../../services/component-registration.service'; +import { provideMockStore } from '@ngrx/store/testing'; +import { selectIn3DView } from '../../map/state/map.selectors'; @Component({ selector: 'tm-menu-button-test', @@ -40,6 +42,7 @@ describe('MenubarComponent', () => { ], providers: [ { provide: ComponentRegistrationService, useValue: mockedControlsService }, + provideMockStore({ selectors: [{ selector: selectIn3DView, value: false }] }), ], }); expect(await screen.findByText(/Click me/)).toBeInTheDocument(); diff --git a/projects/core/src/lib/components/print/print-menu-button/print-menu-button.component.spec.ts b/projects/core/src/lib/components/print/print-menu-button/print-menu-button.component.spec.ts index d7f458da7..b5c6c4084 100644 --- a/projects/core/src/lib/components/print/print-menu-button/print-menu-button.component.spec.ts +++ b/projects/core/src/lib/components/print/print-menu-button/print-menu-button.component.spec.ts @@ -7,6 +7,7 @@ import { of } from 'rxjs'; import userEvent from '@testing-library/user-event'; import { provideMockStore } from '@ngrx/store/testing'; import { coreStateKey, initialCoreState } from '../../../state/core.state'; +import { selectIn3DView } from '../../../map/state/map.selectors'; describe('PrintMenuButtonComponent', () => { @@ -20,7 +21,10 @@ describe('PrintMenuButtonComponent', () => { declarations: [MenubarButtonComponent], imports: [ SharedModule, MatIconTestingModule ], providers: [ - provideMockStore({ initialState: { [coreStateKey]: initialCoreState } }), + provideMockStore({ + initialState: { [coreStateKey]: initialCoreState }, + selectors: [{ selector: selectIn3DView, value: false }], + }), { provide: MenubarService, useValue: menubarService }, ], }); diff --git a/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.spec.ts b/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.spec.ts index 32dfea5da..0c8b87e97 100644 --- a/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.spec.ts +++ b/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.spec.ts @@ -3,6 +3,8 @@ import { RegisteredComponentsRendererComponent } from './registered-components-r import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { of } from 'rxjs'; import { ComponentRegistrationService } from '../../services/component-registration.service'; +import { provideMockStore } from '@ngrx/store/testing'; +import { selectIn3DView } from '../../map/state/map.selectors'; @Component({ selector: 'tm-testing', @@ -23,6 +25,7 @@ describe('RegisteredComponentsRendererComponent', () => { schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ { provide: ComponentRegistrationService, useValue: mockedControlsService }, + provideMockStore({ selectors: [{ selector: selectIn3DView, value: false }] }), ], }); expect(await screen.findByText(/TESTING CONTROLS/)).toBeInTheDocument(); diff --git a/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.ts b/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.ts index 1bdf958da..4e63f0d81 100644 --- a/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.ts +++ b/projects/core/src/lib/components/registered-components-renderer/registered-components-renderer.component.ts @@ -1,12 +1,15 @@ import { Component, OnInit, ChangeDetectionStrategy, Input, ViewChild, ViewContainerRef, ChangeDetectorRef, DestroyRef, } from '@angular/core'; -import { ComponentModel } from '@tailormap-viewer/api'; +import { BaseComponentTypeEnum, ComponentModel } from '@tailormap-viewer/api'; import { AreaType, ComponentRegistrationService } from '../../services/component-registration.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { debounceTime } from 'rxjs'; import { DynamicComponentsHelper } from '@tailormap-viewer/shared'; import { ComponentConfigHelper } from '../../shared/helpers/component-config.helper'; +import { map, combineLatest } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { selectIn3DView } from '../../map/state/map.selectors'; @Component({ selector: 'tm-registered-components-renderer', @@ -24,14 +27,36 @@ export class RegisteredComponentsRendererComponent implements OnInit { @ViewChild('componentsContainer', { read: ViewContainerRef, static: true }) private componentsContainer: ViewContainerRef | null = null; + private disallowingComponents = [ + BaseComponentTypeEnum.PRINT, + BaseComponentTypeEnum.DRAWING, + BaseComponentTypeEnum.COORDINATE_LINK_WINDOW, + BaseComponentTypeEnum.MEASURE, + BaseComponentTypeEnum.COORDINATE_PICKER, + BaseComponentTypeEnum.STREETVIEW, + ]; + constructor( private componentRegistrationService: ComponentRegistrationService, private cdr: ChangeDetectorRef, private destroyRef: DestroyRef, + private store$: Store, ) { } public ngOnInit(): void { - this.componentRegistrationService.getRegisteredComponents$(this.area) + combineLatest([ + this.componentRegistrationService.getRegisteredComponents$(this.area), + this.store$.select(selectIn3DView), + ]).pipe( + map(([ components, in3D ]) => { + if (in3D) { + return components.filter( + component => !this.disallowingComponents.some(disallowingComponent => disallowingComponent === component.type), + ); + } + return components; + }), + ) .pipe( takeUntilDestroyed(this.destroyRef), debounceTime(10), diff --git a/projects/core/src/lib/components/toolbar/clicked-coordinates/clicked-coordinates.component.spec.ts b/projects/core/src/lib/components/toolbar/clicked-coordinates/clicked-coordinates.component.spec.ts index a437537c2..06e85a13e 100644 --- a/projects/core/src/lib/components/toolbar/clicked-coordinates/clicked-coordinates.component.spec.ts +++ b/projects/core/src/lib/components/toolbar/clicked-coordinates/clicked-coordinates.component.spec.ts @@ -8,6 +8,7 @@ import { ToolbarComponentEnum } from '../models/toolbar-component.enum'; import { SharedModule } from '@tailormap-viewer/shared'; import { MatIconTestingModule } from '@angular/material/icon/testing'; import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; +import { selectIn3DView } from '../../../map/state/map.selectors'; describe('ClickedCoordinatesComponent', () => { @@ -22,6 +23,7 @@ describe('ClickedCoordinatesComponent', () => { provideMockStore({ selectors: [ { selector: isActiveToolbarTool(ToolbarComponentEnum.SELECT_COORDINATES), value: true }, + { selector: selectIn3DView, value: false }, ], }), ], diff --git a/projects/core/src/lib/components/toolbar/coordinate-link-window/coordinate-link-window.component.spec.ts b/projects/core/src/lib/components/toolbar/coordinate-link-window/coordinate-link-window.component.spec.ts index dbb227d89..d6081a81f 100644 --- a/projects/core/src/lib/components/toolbar/coordinate-link-window/coordinate-link-window.component.spec.ts +++ b/projects/core/src/lib/components/toolbar/coordinate-link-window/coordinate-link-window.component.spec.ts @@ -10,6 +10,7 @@ import { registerTool } from '../state/toolbar.actions'; import { ToolbarComponentEnum } from '../models/toolbar-component.enum'; import userEvent from '@testing-library/user-event'; import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; +import { selectIn3DView } from '../../../map/state/map.selectors'; const setup = async (withConfig?: boolean) => { const config: CoordinateLinkWindowConfigModel | undefined = withConfig ? { @@ -23,6 +24,7 @@ const setup = async (withConfig?: boolean) => { const storeMock = { select: jest.fn(() => of(({ config }))), dispatch: jest.fn(), + selectors: [{ selector: selectIn3DView, value: false }], }; const mapClickSubject = new Subject(); const mapServiceMock = getMapServiceMock(tool => ({ diff --git a/projects/core/src/lib/components/toolbar/measure/measure.component.spec.ts b/projects/core/src/lib/components/toolbar/measure/measure.component.spec.ts index 2211bb18b..340862496 100644 --- a/projects/core/src/lib/components/toolbar/measure/measure.component.spec.ts +++ b/projects/core/src/lib/components/toolbar/measure/measure.component.spec.ts @@ -12,6 +12,7 @@ import { Store } from '@ngrx/store'; import { activateTool, deactivateTool, registerTool } from '../state/toolbar.actions'; import { selectComponentsConfig } from '../../../state/core.selectors'; import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; +import { selectIn3DView } from '../../../map/state/map.selectors'; const setup = async () => { const tooltipMock: any = { @@ -33,6 +34,7 @@ const setup = async () => { selectors: [ { selector: selectActiveTool, value: ToolbarComponentEnum.MEASURE }, { selector: selectComponentsConfig, value: [] }, + { selector: selectIn3DView, value: false }, ], }); const mockDispatch = jest.fn(); diff --git a/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.spec.ts b/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.spec.ts index 42c78fd38..e777141cb 100644 --- a/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.spec.ts +++ b/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.spec.ts @@ -2,6 +2,8 @@ import { render, screen } from '@testing-library/angular'; import { MouseCoordinatesComponent } from './mouse-coordinates.component'; import { of } from 'rxjs'; import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; +import { provideMockStore } from '@ngrx/store/testing'; +import { selectIn3DView } from '../../../map/state/map.selectors'; describe('MouseCoordinatesComponent', () => { @@ -10,7 +12,12 @@ describe('MouseCoordinatesComponent', () => { () => ({ mouseMove$: of({ type: 'move', mapCoordinates: [ 50, 60 ] }) }), ); await render(MouseCoordinatesComponent, { - providers: [mapServiceMock.provider], + providers: [ + mapServiceMock.provider, + provideMockStore({ + selectors: [{ selector: selectIn3DView, value: false }], + }), + ], }); expect(await screen.getByText('50')); expect(await screen.getByText('|')); diff --git a/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.ts b/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.ts index e61fd6679..e4fab9376 100644 --- a/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.ts +++ b/projects/core/src/lib/components/toolbar/mouse-coordinates/mouse-coordinates.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { MapService, MousePositionToolConfigModel, MousePositionToolModel, ToolTypeEnum } from '@tailormap-viewer/map'; import { concatMap, Observable, of, Subject, switchMap, takeUntil } from 'rxjs'; +import { Store } from '@ngrx/store'; @Component({ selector: 'tm-mouse-coordinates', @@ -16,7 +17,8 @@ export class MouseCoordinatesComponent implements OnInit, OnDestroy { constructor( private mapService: MapService, - ) { } + private store$: Store, + ) {} public ngOnInit(): void { this.coordinates$ = this.mapService.createTool$({ diff --git a/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.spec.ts b/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.spec.ts index e47079de2..ef23a4f9b 100644 --- a/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.spec.ts +++ b/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.spec.ts @@ -1,6 +1,8 @@ import { render, screen } from '@testing-library/angular'; import { ScaleBarComponent } from './scale-bar.component'; import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; +import { provideMockStore } from '@ngrx/store/testing'; +import { selectIn3DView } from '../../../map/state/map.selectors'; describe('ScaleBarComponent', () => { @@ -12,7 +14,12 @@ describe('ScaleBarComponent', () => { }; const mapServiceMock = getMapServiceMock(() => mockTool); await render(ScaleBarComponent, { - providers: [mapServiceMock.provider], + providers: [ + mapServiceMock.provider, + provideMockStore({ + selectors: [{ selector: selectIn3DView, value: false }], + }), + ], }); expect(mapServiceMock.createTool$).toHaveBeenCalled(); expect(mockTool.setTarget).toHaveBeenCalled(); diff --git a/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.ts b/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.ts index 9ebfe476a..be5ae6444 100644 --- a/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.ts +++ b/projects/core/src/lib/components/toolbar/scale-bar/scale-bar.component.ts @@ -19,7 +19,7 @@ export class ScaleBarComponent implements OnInit, OnDestroy { constructor( private mapService: MapService, - ) { } + ) {} public ngOnInit(): void { this.mapService.createTool$({ diff --git a/projects/core/src/lib/components/toolbar/streetview/streetview.component.spec.ts b/projects/core/src/lib/components/toolbar/streetview/streetview.component.spec.ts index 3c327c2d0..90655eb5f 100644 --- a/projects/core/src/lib/components/toolbar/streetview/streetview.component.spec.ts +++ b/projects/core/src/lib/components/toolbar/streetview/streetview.component.spec.ts @@ -8,6 +8,7 @@ import { SharedModule } from '@tailormap-viewer/shared'; import { MatIconTestingModule } from '@angular/material/icon/testing'; import { StreetviewComponent } from './streetview.component'; import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; +import { selectIn3DView } from '../../../map/state/map.selectors'; describe('StreetviewComponent', () => { @@ -21,6 +22,7 @@ describe('StreetviewComponent', () => { provideMockStore({ selectors: [ { selector: isActiveToolbarTool(ToolbarComponentEnum.STREETVIEW), value: true }, + { selector: selectIn3DView, value: false }, ], }), ], diff --git a/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.css b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.css new file mode 100644 index 000000000..d3b6bf805 --- /dev/null +++ b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.css @@ -0,0 +1,4 @@ +.clicked-coordinates button { + border-radius: 3px; +} + diff --git a/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.html b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.html new file mode 100644 index 000000000..9d82e6c75 --- /dev/null +++ b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.html @@ -0,0 +1,11 @@ +@if ((enable$|async) && (allowSwitch$|async)){ +
+ +
+} diff --git a/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.spec.ts b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.spec.ts new file mode 100644 index 000000000..16a9a50ff --- /dev/null +++ b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.spec.ts @@ -0,0 +1,52 @@ +import { render, screen } from '@testing-library/angular'; +import { Switch3DComponent } from './switch3-d.component'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { provideMockStore } from '@ngrx/store/testing'; +import { SharedModule } from '@tailormap-viewer/shared'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { selectEnable3D } from '../../../state/core.selectors'; +import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; +import { selectActiveTool } from '../state/toolbar.selectors'; + +describe('Switch3DComponent', () => { + + test('should render', async () => { + const mapServiceMock = getMapServiceMock(); + await render(Switch3DComponent, { + schemas: [CUSTOM_ELEMENTS_SCHEMA], + imports: [ SharedModule, MatIconTestingModule ], + providers: [ + { provide: MatSnackBar, useValue: { dismiss: jest.fn() } }, + mapServiceMock.provider, + provideMockStore({ + selectors: [ + { selector: selectEnable3D, value: true }, + { selector: selectActiveTool, value: null }, + ], + }), + ], + }); + expect(screen.getByLabelText('Switch to 3D')).toBeInTheDocument(); + }); + + test('should not render', async () => { + const mapServiceMock = getMapServiceMock(); + await render(Switch3DComponent, { + schemas: [CUSTOM_ELEMENTS_SCHEMA], + imports: [ SharedModule, MatIconTestingModule ], + providers: [ + { provide: MatSnackBar, useValue: { dismiss: jest.fn() } }, + mapServiceMock.provider, + provideMockStore({ + selectors: [ + { selector: selectEnable3D, value: false }, + { selector: selectActiveTool, value: null }, + ], + }), + ], + }); + expect(screen.queryByLabelText('Switch to 3D')).not.toBeInTheDocument(); + }); + +}); diff --git a/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.ts b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.ts new file mode 100644 index 000000000..8bc4fb319 --- /dev/null +++ b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.component.ts @@ -0,0 +1,67 @@ +import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core'; +import { map, Observable, Subject, combineLatest } from 'rxjs'; +import { MapService } from '@tailormap-viewer/map'; +import { Store } from '@ngrx/store'; +import { selectEnable3D } from '../../../state/core.selectors'; +import { toggleIn3DView } from '../../../map/state/map.actions'; +import { MenubarService } from '../../menubar'; +import { BaseComponentTypeEnum } from '@tailormap-viewer/api'; +import { selectActiveTool } from '../state/toolbar.selectors'; +import { ToolbarComponentEnum } from '../models/toolbar-component.enum'; + + +@Component({ + selector: 'tm-switch3-d', + templateUrl: './switch3-d.component.html', + styleUrls: ['./switch3-d.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Switch3DComponent implements OnDestroy { + + private destroyed = new Subject(); + public enable$: Observable; + public allowSwitch$: Observable; + private disallowingComponents = [ + BaseComponentTypeEnum.PRINT, + BaseComponentTypeEnum.DRAWING, + ]; + private disAllowingTools = [ + ToolbarComponentEnum.SELECT_COORDINATES, + ToolbarComponentEnum.COORDINATE_LINK_WINDOW, + ToolbarComponentEnum.STREETVIEW, + ToolbarComponentEnum.MEASURE, + ]; + + constructor( + private store$: Store, + private mapService: MapService, + private menubarService: MenubarService, + ) { + this.enable$ = this.store$.select(selectEnable3D); + this.allowSwitch$ = combineLatest([ + this.menubarService.getActiveComponent$().pipe( + map( + component => !this.disallowingComponents.some(disallowingComponent => disallowingComponent === component?.componentId), + ), + ), + this.store$.select(selectActiveTool).pipe( + map( + tool => !this.disAllowingTools.some(disallowingTool => disallowingTool === tool), + ), + ), + ]).pipe( + map(([ componentBoolean, toolBoolean ]) => componentBoolean && toolBoolean), + ); + } + + public ngOnDestroy() { + this.destroyed.next(null); + this.destroyed.complete(); + } + + public toggle() { + this.mapService.switch3D$(); + this.store$.dispatch(toggleIn3DView()); + } + +} diff --git a/projects/core/src/lib/components/toolbar/switch3D/switch3-d.module.ts b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.module.ts new file mode 100644 index 000000000..e7f3ae045 --- /dev/null +++ b/projects/core/src/lib/components/toolbar/switch3D/switch3-d.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@tailormap-viewer/shared'; +import { ClipboardModule } from '@angular/cdk/clipboard'; +import { Switch3DComponent } from './switch3-d.component'; + +@NgModule({ + declarations: [ + Switch3DComponent, + ], + imports: [ + CommonModule, + ClipboardModule, + SharedModule, + ], + exports: [ + Switch3DComponent, + ], +}) +export class Switch3DModule { +} diff --git a/projects/core/src/lib/core.module.ts b/projects/core/src/lib/core.module.ts index aa63923a3..e39996bae 100644 --- a/projects/core/src/lib/core.module.ts +++ b/projects/core/src/lib/core.module.ts @@ -1,4 +1,4 @@ -import { APP_INITIALIZER, InjectionToken, ModuleWithProviders, NgModule } from '@angular/core'; +import { APP_INITIALIZER, Inject, InjectionToken, ModuleWithProviders, NgModule } from '@angular/core'; import { LoginComponent, ViewerAppComponent } from './pages'; import { MapModule } from '@tailormap-viewer/map'; import { StoreModule } from '@ngrx/store'; @@ -11,7 +11,9 @@ import { EnvironmentConfigModel, TAILORMAP_API_V1_SERVICE, TAILORMAP_SECURITY_API_V1_SERVICE, TailormapApiV1Service, TailormapSecurityApiV1Service, } from '@tailormap-viewer/api'; -import { ICON_SERVICE_ICON_LOCATION, IconService, RouterHistoryService, SharedModule } from '@tailormap-viewer/shared'; +import { + ExternalLibsLoaderHelper, ICON_SERVICE_ICON_LOCATION, IconService, RouterHistoryService, SharedModule, +} from '@tailormap-viewer/shared'; import { ComponentsModule } from './components/components.module'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; @@ -98,11 +100,13 @@ export class CoreModule { domSanitizer: DomSanitizer, iconService: IconService, authenticatedUserService: AuthenticatedUserService, + @Inject(APP_BASE_HREF) baseHref: string, _appStyleService: ApplicationStyleService, _routerHistoryService: RouterHistoryService, ) { iconService.loadIconsToIconRegistry(matIconRegistry, domSanitizer); authenticatedUserService.fetchUserDetails(); + ExternalLibsLoaderHelper.setBaseHref(baseHref); } public static forRoot(config: EnvironmentConfigModel): ModuleWithProviders { diff --git a/projects/core/src/lib/layout/base-layout/base-layout.component.html b/projects/core/src/lib/layout/base-layout/base-layout.component.html index 2e569d506..817f215a5 100644 --- a/projects/core/src/lib/layout/base-layout/base-layout.component.html +++ b/projects/core/src/lib/layout/base-layout/base-layout.component.html @@ -1,50 +1,51 @@ - +
- - - - - - + + + + + +
- - + + - - + +
- - - - - - - - + + + + + + + + +
- +
- - - + + +
- - + +
- - + +
diff --git a/projects/core/src/lib/layout/base-layout/base-layout.component.spec.ts b/projects/core/src/lib/layout/base-layout/base-layout.component.spec.ts index 8a7737a2a..bd31359dc 100644 --- a/projects/core/src/lib/layout/base-layout/base-layout.component.spec.ts +++ b/projects/core/src/lib/layout/base-layout/base-layout.component.spec.ts @@ -4,6 +4,7 @@ import { provideMockStore } from '@ngrx/store/testing'; import { selectComponentsConfig } from '../../state/core.selectors'; import { BaseComponentTypeEnum } from '@tailormap-viewer/api'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { selectIn3DView } from '../../map/state/map.selectors'; describe('BaseLayoutComponent', () => { @@ -15,6 +16,7 @@ describe('BaseLayoutComponent', () => { selector: selectComponentsConfig, value: (disabledComponents || []).map(type => ({ type, config: { enabled: false } } )), }, + { selector: selectIn3DView, value: false }, ], }); const { container } = await render(BaseLayoutComponent, { diff --git a/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.html b/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.html index 422b2d489..bb033b457 100644 --- a/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.html +++ b/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.html @@ -1,27 +1,27 @@ - +
- +
- - - - + + + +
- +
- - + +
- +
- +
diff --git a/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.spec.ts b/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.spec.ts index 0883fc0fc..c5b7fb5fa 100644 --- a/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.spec.ts +++ b/projects/core/src/lib/layout/embedded-layout/embedded-layout.component.spec.ts @@ -4,6 +4,7 @@ import { provideMockStore } from '@ngrx/store/testing'; import { selectComponentsConfig } from '../../state/core.selectors'; import { BaseComponentTypeEnum } from '@tailormap-viewer/api'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { selectIn3DView } from '../../map/state/map.selectors'; describe('EmbeddedLayoutComponent', () => { @@ -15,6 +16,7 @@ describe('EmbeddedLayoutComponent', () => { selector: selectComponentsConfig, value: (disabledComponents || []).map(type => ({ type, config: { enabled: false } } )), }, + { selector: selectIn3DView, value: false }, ], }); const { container } = await render(EmbeddedLayoutComponent, { diff --git a/projects/core/src/lib/layout/layout.module.ts b/projects/core/src/lib/layout/layout.module.ts index 142178a0b..d4b8569b9 100644 --- a/projects/core/src/lib/layout/layout.module.ts +++ b/projects/core/src/lib/layout/layout.module.ts @@ -5,6 +5,7 @@ import { ComponentsModule } from '../components/components.module'; import { MapModule } from '@tailormap-viewer/map'; import { EmbeddedLayoutComponent } from './embedded-layout/embedded-layout.component'; import { ShareViewerModule } from '../components/toolbar/share-viewer/share-viewer.module'; +import { Switch3DModule } from "../components/toolbar/switch3D/switch3-d.module"; @@ -18,6 +19,7 @@ import { ShareViewerModule } from '../components/toolbar/share-viewer/share-view ComponentsModule, MapModule, ShareViewerModule, + Switch3DModule, ], exports: [ BaseLayoutComponent, diff --git a/projects/core/src/lib/layout/layout.service.ts b/projects/core/src/lib/layout/layout.service.ts index d870f428e..779c4056e 100644 --- a/projects/core/src/lib/layout/layout.service.ts +++ b/projects/core/src/lib/layout/layout.service.ts @@ -1,28 +1,49 @@ -import { map, Observable } from 'rxjs'; -import { BaseComponentConfigHelper, ComponentModel } from '@tailormap-viewer/api'; +import { map, Observable, combineLatest } from 'rxjs'; +import { BaseComponentConfigHelper, BaseComponentTypeEnum, ComponentModel } from '@tailormap-viewer/api'; import { selectComponentsConfig } from '../state/core.selectors'; import { Store } from '@ngrx/store'; import { Injectable } from '@angular/core'; +import { selectIn3DView } from '../map/state/map.selectors'; + +export interface LayoutConfig { + config: ComponentModel[]; + in3D: boolean; +} @Injectable({ providedIn: 'root', }) export class LayoutService { - public componentsConfig$: Observable; + public componentsConfig$: Observable; - constructor(private store$: Store) { - this.componentsConfig$ = this.store$.select(selectComponentsConfig); - } + private disallowingComponents = [ + BaseComponentTypeEnum.PRINT, + BaseComponentTypeEnum.DRAWING, + BaseComponentTypeEnum.COORDINATE_LINK_WINDOW, + BaseComponentTypeEnum.MEASURE, + BaseComponentTypeEnum.COORDINATE_PICKER, + BaseComponentTypeEnum.STREETVIEW, + ]; - public isComponentEnabled(config: ComponentModel[], componentType: string) { - return BaseComponentConfigHelper.isComponentEnabled(config, componentType); + constructor(private store$: Store) { + this.componentsConfig$ = combineLatest([ + store$.select(selectComponentsConfig), + store$.select(selectIn3DView), + ]).pipe( + map(([ components, in3DView ]) => { + return { config: components, in3D: in3DView }; + }), + ); } - public isComponentEnabled$(componentType: string) { - return this.componentsConfig$.pipe( - map(config => BaseComponentConfigHelper.isComponentEnabled(config, componentType)), - ); + public isComponentEnabled(layoutConfig: LayoutConfig, componentType: string) { + if (layoutConfig.in3D) { + if (this.disallowingComponents.some(disallowingComponent => disallowingComponent === componentType)) { + return false; + } + } + return BaseComponentConfigHelper.isComponentEnabled(layoutConfig.config, componentType); } } diff --git a/projects/core/src/lib/map/services/application-map.service.ts b/projects/core/src/lib/map/services/application-map.service.ts index dc819aa36..a494f1e2f 100644 --- a/projects/core/src/lib/map/services/application-map.service.ts +++ b/projects/core/src/lib/map/services/application-map.service.ts @@ -1,19 +1,20 @@ import { Injectable, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { - LayerModel, LayerTypesEnum, MapService, OgcHelper, ServiceLayerModel, WMSLayerModel, WMTSLayerModel, XyzLayerModel, + LayerModel, LayerTypesEnum, MapService, OgcHelper, ServiceLayerModel, WMSLayerModel, WMTSLayerModel, XyzLayerModel, Tileset3DLayerModel, } from '@tailormap-viewer/map'; import { combineLatest, concatMap, distinctUntilChanged, filter, forkJoin, map, Observable, of, Subject, take, takeUntil, tap } from 'rxjs'; import { ServiceModel, ServiceProtocol } from '@tailormap-viewer/api'; import { HttpClient, HttpParams } from '@angular/common/http'; import { ArrayHelper } from '@tailormap-viewer/shared'; -import { selectMapOptions, selectOrderedVisibleBackgroundLayers, selectOrderedVisibleLayersWithServices } from '../state/map.selectors'; +import { selectMapOptions, selectOrderedVisibleBackgroundLayers, selectOrderedVisibleLayersWithServices, select3Dlayers } from '../state/map.selectors'; import { ExtendedAppLayerModel } from '../models'; import { selectCQLFilters } from '../../filter/state/filter.selectors'; import { withLatestFrom } from 'rxjs/operators'; import { BookmarkService } from '../../services/bookmark/bookmark.service'; import { MapBookmarkHelper } from '../../services/application-bookmark/bookmark.helper'; import { ApplicationBookmarkFragments } from '../../services/application-bookmark/application-bookmark-fragments'; +import { selectEnable3D } from '../../state/core.selectors'; import { ApplicationLayerRefreshService } from './application-layer-refresh.service'; @Injectable({ @@ -71,6 +72,17 @@ export class ApplicationMapService implements OnDestroy { .subscribe(([ layers, layerManager ]) => { layerManager.setLayers(layers.filter(isValidLayer)); }); + + if (this.store$.select(selectEnable3D)) { + this.store$.select(select3Dlayers) + .pipe( + takeUntil(this.destroyed), + concatMap(layers => this.get3DLayersAndLayerManager$(layers)), + ) + .subscribe(([ layers, layerManager ]) => { + layerManager.addLayers(layers.filter(isValidLayer)); + }); + } } public selectOrderedVisibleLayersWithFilters$() { @@ -93,6 +105,15 @@ export class ApplicationMapService implements OnDestroy { ]); } + private get3DLayersAndLayerManager$(serviceLayers: ExtendedAppLayerModel[]) { + const layers$ = serviceLayers + .map(layer => this.convertAppLayerToMapLayer$(layer)); + return forkJoin([ + layers$.length > 0 ? forkJoin(layers$) : of([]), + this.mapService.getCesiumLayerManager$().pipe(take(1)), + ]); + } + public ngOnDestroy() { this.destroyed.next(null); this.destroyed.complete(); @@ -154,6 +175,13 @@ export class ApplicationMapService implements OnDestroy { }; return of(layer); } + if (service.protocol === ServiceProtocol.TILESET3D) { + const layer: Tileset3DLayerModel = { + ...defaultLayerProps, + layerType: LayerTypesEnum.TILESET3D, + }; + return of(layer); + } return of(null); } diff --git a/projects/core/src/lib/map/state/map.actions.ts b/projects/core/src/lib/map/state/map.actions.ts index 41dc6606e..0e4ebd8d4 100644 --- a/projects/core/src/lib/map/state/map.actions.ts +++ b/projects/core/src/lib/map/state/map.actions.ts @@ -68,3 +68,6 @@ export const updateLayerTreeNodes = createAction( `${mapActionsPrefix} Update Layer Tree`, props<{ layerTreeNodes: ExtendedLayerTreeNodeModel[] }>(), ); +export const toggleIn3DView = createAction( + `${mapActionsPrefix} Toggle In3DView`, +); diff --git a/projects/core/src/lib/map/state/map.reducer.ts b/projects/core/src/lib/map/state/map.reducer.ts index 8285cd8e1..39c149620 100644 --- a/projects/core/src/lib/map/state/map.reducer.ts +++ b/projects/core/src/lib/map/state/map.reducer.ts @@ -208,6 +208,11 @@ const onUpdateLayerTreeNodes = ( layerTreeNodes: payload.layerTreeNodes, }); +const onToggleIn3DView = (state: MapState): MapState => ({ + ...state, + in3DView: !state.in3DView, +}); + const mapReducerImpl = createReducer( initialMapState, on(MapActions.loadMap, onLoadMap), @@ -226,5 +231,6 @@ const mapReducerImpl = createReducer( on(MapActions.setLayerOpacity, onSetLayerOpacity), on(MapActions.addLayerDetails, onAddLayerDetails), on(MapActions.updateLayerTreeNodes, onUpdateLayerTreeNodes), + on(MapActions.toggleIn3DView, onToggleIn3DView), ); export const mapReducer = (state: MapState | undefined, action: Action) => mapReducerImpl(state, action); diff --git a/projects/core/src/lib/map/state/map.selectors.ts b/projects/core/src/lib/map/state/map.selectors.ts index 3da1ae981..687c07e89 100644 --- a/projects/core/src/lib/map/state/map.selectors.ts +++ b/projects/core/src/lib/map/state/map.selectors.ts @@ -17,6 +17,7 @@ export const selectBackgroundLayerTreeNodes = createSelector(selectMapState, sta export const selectSelectedBackgroundNodeId = createSelector(selectMapState, state => state.selectedBackgroundNode); export const selectLoadStatus = createSelector(selectMapState, state => state.loadStatus); export const selectLayerDetailsAll = createSelector(selectMapState, state => state.layerDetails); +export const selectIn3DView = createSelector(selectMapState, state => state.in3DView); export const selectMapOptions = createSelector( selectMapSettings, @@ -264,3 +265,8 @@ export const selectSearchableLayers = createSelector( selectOrderedVisibleLayersWithServices, layers => layers.filter(l => l.searchIndex !== null), ); + +export const select3Dlayers = createSelector( + selectLayersWithServices, + layers => layers.filter(l => l.service?.protocol === ServiceProtocol.TILESET3D), +); diff --git a/projects/core/src/lib/map/state/map.state.ts b/projects/core/src/lib/map/state/map.state.ts index 770aff683..7f7893dc2 100644 --- a/projects/core/src/lib/map/state/map.state.ts +++ b/projects/core/src/lib/map/state/map.state.ts @@ -21,6 +21,7 @@ export interface MapState { layerDetails: LayerDetailsModel[]; selectedLayer?: string; selectedBackgroundNode?: string; + in3DView: boolean; } export const initialMapState: MapState = { @@ -30,4 +31,5 @@ export const initialMapState: MapState = { layerDetails: [], baseLayerTreeNodes: [], layerTreeNodes: [], + in3DView: false, }; diff --git a/projects/core/src/lib/state/core.selectors.ts b/projects/core/src/lib/state/core.selectors.ts index 9fbf30a11..1978f2c79 100644 --- a/projects/core/src/lib/state/core.selectors.ts +++ b/projects/core/src/lib/state/core.selectors.ts @@ -20,6 +20,11 @@ export const selectShowLoginButton = createSelector( viewerState => viewerState?.uiSettings?.hideLoginButton !== true, ); +export const selectEnable3D = createSelector( + selectViewerState, + viewerState => viewerState?.uiSettings?.enable3D === true, +); + export const selectComponentsConfig = createSelector( selectViewerState, state => { diff --git a/projects/map/src/lib/cesium-map/cesium-layer-manager.ts b/projects/map/src/lib/cesium-map/cesium-layer-manager.ts new file mode 100644 index 000000000..280cb1034 --- /dev/null +++ b/projects/map/src/lib/cesium-map/cesium-layer-manager.ts @@ -0,0 +1,128 @@ +import { Map as OlMap } from 'ol'; +import { LayerModel } from '../models/layer.model'; +import { LayerTypesHelper } from '../helpers/layer-types.helper'; +import { NgZone } from '@angular/core'; +import type OLCesium from 'olcs'; +import { BehaviorSubject, filter, from, map, Observable, take } from 'rxjs'; +import type { Cesium3DTileset, Scene } from 'cesium'; +import { ExternalLibsLoaderHelper } from '@tailormap-viewer/shared'; + +export class CesiumLayerManager { + + private map3D: BehaviorSubject = new BehaviorSubject(null); + private layers3D: Map = new Map(); + + constructor( + private olMap: OlMap, + private ngZone: NgZone, + ) { + } + + public init() { + ExternalLibsLoaderHelper.loadScript$('cesium/Cesium.js') + .pipe(filter(loaded => loaded), take(1)) + .subscribe(() => { + this.setupOlCesium(); + }); + } + + private setupOlCesium() { + this.ngZone.runOutsideAngular(() => { + from(from(import('olcs'))) + .pipe(take(1)) + .subscribe(olCsModule => { + const ol3d = new olCsModule.default({ + map: this.olMap, + }); + this.map3D.next(ol3d); + }); + + // this.executeScene3DAction(scene3D => { + // const OLCS_ION_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2YjQ4MDkzYi02ZjJjLTQ5YTgtY' + + // 'jAyZC1lN2IxZGZlMDFlMDkiLCJpZCI6MTk3Mzk5LCJpYXQiOjE3MDg2Nzg4ODh9.DQT_DNkF7XS8vtMtIde2oeZsJoQTqm4K3qFahQ1-tR8'; + // Cesium.Ion.defaultAccessToken = OLCS_ION_TOKEN; + // scene3D.setTerrain( + // new Cesium.Terrain( + // Cesium.CesiumTerrainProvider.fromIonAssetId(1), + // ), + // ); + // }); + }); + } + + public getMap3D$(): Observable { + const isNotNullMap3D = (item: OLCesium | null): item is OLCesium => item !== null; + return this.map3D.asObservable().pipe(filter(isNotNullMap3D)); + } + + public executeMap3DAction(fn: (olMap3D: OLCesium) => void) { + this.getMap3D$() + .pipe(take(1)) + .subscribe(olMap3D => this.ngZone.runOutsideAngular(() => fn(olMap3D))); + } + + public executeScene3DAction(fn: (scene3D: Scene) => void) { + this.getMap3D$() + .pipe(take(1), map(map3d => map3d.getCesiumScene())) + .subscribe(scene3D => { + this.ngZone.runOutsideAngular(() => fn(scene3D)); + }); + } + + public switch3D$(){ + this.executeMap3DAction(olMap3D => { + olMap3D.setEnabled(!olMap3D.getEnabled()); + if (!olMap3D.getEnabled()){ + this.olMap.getView().setRotation(0); + } + }); + } + + public addLayers(layers: LayerModel[]){ + this.ngZone.runOutsideAngular(() => { + layers.forEach(layer => { + if (layer.visible) { + this.addLayer(layer); + } else { + this.removeLayer(layer); + } + }); + }); + } + + private addLayer(layer: LayerModel) { + this.executeScene3DAction(async scene3D => { + if (this.layers3D.has(layer.id)) { + const primitive = scene3D.primitives.get(this.layers3D.get(layer.id) ?? 0); + primitive.show = true; + } else { + this.create3DLayer(layer)?.then(cesiumLayer => { + if (cesiumLayer) { + scene3D.primitives.add(cesiumLayer, this.layers3D.size); + this.layers3D.set(layer.id, this.layers3D.size); + } + }); + } + }); + } + + private removeLayer(layer: LayerModel) { + this.executeScene3DAction(async scene3D => { + if (this.layers3D.has(layer.id)) { + const primitive = scene3D.primitives.get(this.layers3D.get(layer.id) ?? 0); + primitive.show = false; + } + }); + } + + private create3DLayer( + layer: LayerModel, + ): Promise | null { + if (LayerTypesHelper.isTileset3DLayer(layer)) { + const url = layer.url; + return Cesium.Cesium3DTileset.fromUrl(url); + } + return null; + } + +} diff --git a/projects/map/src/lib/helpers/layer-types.helper.ts b/projects/map/src/lib/helpers/layer-types.helper.ts index 74bc62d5f..6d4e90a00 100644 --- a/projects/map/src/lib/helpers/layer-types.helper.ts +++ b/projects/map/src/lib/helpers/layer-types.helper.ts @@ -4,6 +4,7 @@ import { LayerTypesEnum } from '../models/layer-types.enum'; import { VectorLayerModel } from '../models/vector-layer.model'; import { XyzLayerModel } from '../models/xyz-layer.model'; import { WMTSLayerModel } from '../models/wmts-layer.model'; +import { Tileset3DLayerModel } from '../models/tileset3D-layer.model'; import { ServiceLayerModel } from '../models/service-layer.model'; export class LayerTypesHelper { @@ -28,4 +29,8 @@ export class LayerTypesHelper { return layer.layerType === LayerTypesEnum.WMTS; } + public static isTileset3DLayer(layer: LayerModel): layer is Tileset3DLayerModel { + return layer.layerType === LayerTypesEnum.TILESET3D; + } + } diff --git a/projects/map/src/lib/map-service/map.service.ts b/projects/map/src/lib/map-service/map.service.ts index 43ed4d0d0..1f0f0d40a 100644 --- a/projects/map/src/lib/map-service/map.service.ts +++ b/projects/map/src/lib/map-service/map.service.ts @@ -1,6 +1,7 @@ import { Injectable, NgZone } from '@angular/core'; import { OpenLayersMap } from '../openlayers-map/openlayers-map'; -import { combineLatest, finalize, map, Observable, take, tap } from 'rxjs'; +import { CesiumLayerManager } from '../cesium-map/cesium-layer-manager'; +import { BehaviorSubject, combineLatest, filter, finalize, map, Observable, take, tap } from 'rxjs'; import { LayerManagerModel, LayerModel, LayerTypesEnum, MapStyleModel, MapViewDetailsModel, MapViewerOptionsModel, OpenlayersExtent, ToolConfigModel, ToolModel, @@ -41,12 +42,15 @@ export interface MapExportOptions { export class MapService { private readonly map: OpenLayersMap; + private map3D: BehaviorSubject = new BehaviorSubject(null); + private made3D: boolean; constructor( private ngZone: NgZone, private httpXsrfTokenExtractor: HttpXsrfTokenExtractor, ) { this.map = new OpenLayersMap(this.ngZone, this.httpXsrfTokenExtractor); + this.made3D = false; } public initMap(options: MapViewerOptionsModel, initialOptions?: { initialCenter?: [number, number]; initialZoom?: number }) { @@ -255,4 +259,34 @@ export class MapService { this.map.setPadding(padding); } + public getCesiumLayerManager$(): Observable { + const isLayerManager = (item: CesiumLayerManager | null): item is CesiumLayerManager => item !== null; + return this.map3D.asObservable().pipe(filter(isLayerManager)); + } + + public executeCLMAction(fn: (cesiumLayerManager: CesiumLayerManager) => void) { + this.getCesiumLayerManager$() + .pipe(take(1)) + .subscribe(cesiumLayerManager => fn(cesiumLayerManager)); + } + + public make3D$(){ + if (!this.made3D) { + this.map.executeMapAction(olMap => { + this.map3D.next(new CesiumLayerManager(olMap, this.ngZone)); + }); + this.executeCLMAction(cesiumLayerManager => { + cesiumLayerManager.init(); + }); + this.made3D = true; + } + } + + public switch3D$(){ + this.make3D$(); + this.executeCLMAction(cesiumLayerManager => { + cesiumLayerManager.switch3D$(); + }); + } + } diff --git a/projects/map/src/lib/models/index.ts b/projects/map/src/lib/models/index.ts index a38473250..133fa656d 100644 --- a/projects/map/src/lib/models/index.ts +++ b/projects/map/src/lib/models/index.ts @@ -10,6 +10,7 @@ export * from './service-layer.model'; export * from './tool-manager.model'; export * from './xyz-layer.model'; export * from './wms-layer.model'; +export * from './tileset3D-layer.model'; export * from './tools'; export * from './map-view-details.model'; export * from './map-style.model'; diff --git a/projects/map/src/lib/models/layer-manager.model.ts b/projects/map/src/lib/models/layer-manager.model.ts index d1a7213d4..6649a645c 100644 --- a/projects/map/src/lib/models/layer-manager.model.ts +++ b/projects/map/src/lib/models/layer-manager.model.ts @@ -1,8 +1,11 @@ import { LayerModel } from './layer.model'; import { Vector as VectorLayer, Image as ImageLayer, Tile as TileLayer } from 'ol/layer'; import { ImageWMS, WMTS, XYZ, TileWMS } from 'ol/source'; +import { Cesium3DTileset } from 'cesium'; -export type LayerTypes = VectorLayer | TileLayer | ImageLayer | TileLayer | TileLayer | null; +export type LayerTypes = VectorLayer | TileLayer | + ImageLayer | TileLayer | TileLayer | null; +export type LayerTypes3D = Promise | null; export interface LayerManagerModel { setBackgroundLayers(layers: LayerModel[]): void; diff --git a/projects/map/src/lib/models/layer-types.enum.ts b/projects/map/src/lib/models/layer-types.enum.ts index 3512aaae8..295bfe091 100644 --- a/projects/map/src/lib/models/layer-types.enum.ts +++ b/projects/map/src/lib/models/layer-types.enum.ts @@ -3,4 +3,5 @@ export enum LayerTypesEnum { XYZ, WMS, WMTS, + TILESET3D, } diff --git a/projects/map/src/lib/models/tileset3D-layer.model.ts b/projects/map/src/lib/models/tileset3D-layer.model.ts new file mode 100644 index 000000000..3e35b5391 --- /dev/null +++ b/projects/map/src/lib/models/tileset3D-layer.model.ts @@ -0,0 +1,5 @@ +import { ServiceLayerModel } from './service-layer.model'; + +export interface Tileset3DLayerModel extends ServiceLayerModel { + enableCollision?: boolean; +} diff --git a/projects/map/src/typings.d.ts b/projects/map/src/typings.d.ts index 35949c1df..273a7f499 100644 --- a/projects/map/src/typings.d.ts +++ b/projects/map/src/typings.d.ts @@ -21,3 +21,7 @@ declare module 'jsts/org/locationtech/jts/geom' { public difference(geom: any): any; } } + +// eslint-disable-next-line @typescript-eslint/naming-convention +declare const Cesium; +declare const CESIUM_BASE_URL; diff --git a/projects/shared/assets/locale/messages.shared.de.xlf b/projects/shared/assets/locale/messages.shared.de.xlf index 2719fd2bf..be41c3cc8 100644 --- a/projects/shared/assets/locale/messages.shared.de.xlf +++ b/projects/shared/assets/locale/messages.shared.de.xlf @@ -75,4 +75,4 @@ - + \ No newline at end of file diff --git a/projects/shared/assets/locale/messages.shared.nl.xlf b/projects/shared/assets/locale/messages.shared.nl.xlf index 1b5dae53c..11c95504d 100644 --- a/projects/shared/assets/locale/messages.shared.nl.xlf +++ b/projects/shared/assets/locale/messages.shared.nl.xlf @@ -75,4 +75,4 @@ - + \ No newline at end of file diff --git a/projects/shared/src/lib/helpers/external-libs-loader.helper.ts b/projects/shared/src/lib/helpers/external-libs-loader.helper.ts new file mode 100644 index 000000000..8fb34a5ef --- /dev/null +++ b/projects/shared/src/lib/helpers/external-libs-loader.helper.ts @@ -0,0 +1,48 @@ +import { BehaviorSubject, Observable, of } from 'rxjs'; + +export class ExternalLibsLoaderHelper { + + private static baseHref: string = ''; + private static loadedScripts = new Map>(); + + public static setBaseHref(baseHref: string) { + ExternalLibsLoaderHelper.baseHref = baseHref; + } + + public static loadScript$(url: string): Observable { + const scriptUrl = ExternalLibsLoaderHelper.baseHref + url; + const current = ExternalLibsLoaderHelper.loadedScripts.get(url); + if (current) { + return current.asObservable(); + } + const ext: string = url.split('.').reverse().shift() || ''; + if (ext === '') { + return of(false); + } + const loaderSubject = new BehaviorSubject(false); + ExternalLibsLoaderHelper.loadedScripts.set(scriptUrl, ExternalLibsLoaderHelper.addTag(ext, scriptUrl, loaderSubject)); + return loaderSubject.asObservable(); + } + + private static addTag(ext: string, url: string, loaderSubject: BehaviorSubject) { + if (ext === 'js') { + const script: HTMLScriptElement = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + script.async = true; + script.onload = () => loaderSubject.next(true); + document.body.appendChild(script); + } + if (ext === 'css') { + const link: HTMLLinkElement = document.createElement('link'); + link.href = url; + link.type = 'text/css'; + link.rel = 'stylesheet'; + link.onload = () => loaderSubject.next(true); + const head: HTMLHeadElement = document.getElementsByTagName('head')[0]; + head.appendChild(link); + } + return loaderSubject; + } + +} diff --git a/projects/shared/src/lib/helpers/index.ts b/projects/shared/src/lib/helpers/index.ts index 21900bbf6..4ac8d4b4d 100644 --- a/projects/shared/src/lib/helpers/index.ts +++ b/projects/shared/src/lib/helpers/index.ts @@ -18,3 +18,4 @@ export * from './debounce.helper'; export * from './clipboard.helper'; export * from './filter.helper'; export * from './markdown.helper'; +export * from './external-libs-loader.helper'; diff --git a/projects/shared/src/lib/services/icons/icon.service.ts b/projects/shared/src/lib/services/icons/icon.service.ts index aa8271e03..ec5a1795e 100644 --- a/projects/shared/src/lib/services/icons/icon.service.ts +++ b/projects/shared/src/lib/services/icons/icon.service.ts @@ -27,7 +27,7 @@ export class IconService { icons: [ 'cursor', 'measure_area', 'measure_length', 'position', 'push_pin', 'measure_length_outline', 'measure_area_outline', 'draw_point', 'draw_line', 'draw_polygon', 'draw_circle', 'draw_label', 'draw_ellipse', 'draw_rectangle', 'draw_square', 'draw_star', - 'streetview', 'share', + 'streetview', 'share', '3d', ], }, { folder: 'style', icons: [ 'bold', 'italic' ] },