├── .yarnrc.yml ├── .prettierignore ├── .prettierrc ├── viewer └── build │ ├── _redirects │ ├── _headers │ └── index.html ├── libraries ├── timeslider │ ├── src │ │ ├── locale │ │ │ └── inv.json │ │ ├── components │ │ │ └── TimeSlider │ │ │ │ ├── index.ts │ │ │ │ ├── TimeSlider.css │ │ │ │ ├── designer.ts │ │ │ │ ├── TimeSlider.tsx │ │ │ │ └── TimeSliderModel.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── app │ │ ├── layout.xml │ │ └── app.json │ ├── package.json │ └── README.md ├── mapillary │ ├── src │ │ ├── components │ │ │ └── Mapillary │ │ │ │ ├── index.ts │ │ │ │ ├── Mapillary.css │ │ │ │ ├── designer.ts │ │ │ │ ├── Mapillary.tsx │ │ │ │ └── MapillaryModel.ts │ │ ├── locale │ │ │ └── inv.json │ │ └── index.ts │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── app │ │ ├── layout.xml │ │ └── app.json │ ├── package.json │ └── README.md ├── 3d-tools │ ├── src │ │ ├── components │ │ │ ├── LineOfSight │ │ │ │ ├── index.ts │ │ │ │ ├── LineOfSightModel.ts │ │ │ │ └── LineOfSight.tsx │ │ │ ├── Slice │ │ │ │ ├── index.ts │ │ │ │ ├── SliceModel.ts │ │ │ │ ├── designer.ts │ │ │ │ └── Slice.tsx │ │ │ ├── AreaMeasurement │ │ │ │ ├── index.ts │ │ │ │ ├── AreaMeasurementModel.ts │ │ │ │ └── AreaMeasurement.tsx │ │ │ ├── LineMeasurement │ │ │ │ ├── index.ts │ │ │ │ ├── LineMeasurementModel.ts │ │ │ │ └── LineMeasurement.tsx │ │ │ ├── Daylight │ │ │ │ ├── index.ts │ │ │ │ ├── DaylightModel.ts │ │ │ │ ├── Daylight.tsx │ │ │ │ └── designer.ts │ │ │ ├── ShadowCast │ │ │ │ ├── index.ts │ │ │ │ ├── ShadowCastModel.ts │ │ │ │ ├── ShadowCast.tsx │ │ │ │ └── designer.ts │ │ │ └── ElevationProfile │ │ │ │ ├── index.ts │ │ │ │ ├── ElevationProfileModel.ts │ │ │ │ ├── ElevationProfile.tsx │ │ │ │ └── designer.ts │ │ ├── index.ts │ │ └── locale │ │ │ └── inv.json │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── package.json │ ├── app │ │ ├── app.json │ │ └── layout.xml │ └── README.md ├── library-viewer │ ├── src │ │ ├── components │ │ │ ├── ReadMe │ │ │ │ ├── index.ts │ │ │ │ ├── ReadMeModel.ts │ │ │ │ └── ReadMe.tsx │ │ │ ├── PickList │ │ │ │ ├── index.ts │ │ │ │ ├── PickListModel.ts │ │ │ │ └── PickList.tsx │ │ │ └── LibraryViewer │ │ │ │ ├── index.ts │ │ │ │ ├── LibraryViewer.css │ │ │ │ ├── LibraryViewer.tsx │ │ │ │ └── LibraryViewerModel.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── app │ │ ├── layout.xml │ │ └── app.json │ ├── package.json │ └── README.md └── eagle-view-viewer │ ├── src │ ├── components │ │ └── EmbeddedExplorer │ │ │ ├── index.ts │ │ │ ├── designer.ts │ │ │ ├── embedded-explorer.ts │ │ │ ├── EagleView.tsx │ │ │ ├── ScaleLevels.ts │ │ │ └── EagleViewModel.ts │ ├── locale │ │ └── inv.json │ └── index.ts │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── package.json │ ├── app │ ├── layout.xml │ └── app.json │ └── README.md ├── cypress ├── fixtures │ └── example.json ├── mapUtils.ts ├── tsconfig.json ├── support │ ├── e2e.js │ ├── index.d.ts │ └── commands.js ├── plugins │ └── index.js └── e2e │ ├── sample-viewer.cy.ts │ └── mapillary.cy.ts ├── .gitignore ├── scripts ├── copy-static-files.js └── serve-library-viewer.js ├── .vscode └── settings.json ├── cypress.config.ts ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── package.json ├── CODE_OF_CONDUCT.md └── README.md /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | libraries/*/build 2 | viewer/build -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /viewer/build/_redirects: -------------------------------------------------------------------------------- 1 | /viewer/* https://apps.vertigisstudio.com/web/:splat 200 2 | /* /index.html 200 3 | -------------------------------------------------------------------------------- /libraries/timeslider/src/locale/inv.json: -------------------------------------------------------------------------------- 1 | { 2 | "language-web-incubator-time-slider-title": "Time Slider" 3 | } 4 | -------------------------------------------------------------------------------- /viewer/build/_headers: -------------------------------------------------------------------------------- 1 | /static/* 2 | Cache-Control: public 3 | Cache-Control: max-age=365000000 4 | Cache-Control: immutable 5 | -------------------------------------------------------------------------------- /libraries/mapillary/src/components/Mapillary/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Mapillary"; 2 | export { default as MapillaryModel } from "./MapillaryModel"; 3 | -------------------------------------------------------------------------------- /libraries/timeslider/src/components/TimeSlider/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./TimeSlider"; 2 | export { default as TimeSliderModel } from "./TimeSliderModel"; 3 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/LineOfSight/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./LineOfSight"; 2 | export { default as LineOfSightModel } from "./LineOfSightModel"; 3 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/ReadMe/index.ts: -------------------------------------------------------------------------------- 1 | import ReadMeModel from "./ReadMeModel"; 2 | 3 | export { default } from "./ReadMe"; 4 | export { ReadMeModel }; 5 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Slice/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Slice"; 2 | export { default as SliceModel } from "./SliceModel"; 3 | export * from "./designer"; 4 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/components/EmbeddedExplorer/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./EagleView"; 2 | export { default as EagleViewModel } from "./EagleViewModel"; 3 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/PickList/index.ts: -------------------------------------------------------------------------------- 1 | import PickListModel from "./PickListModel"; 2 | 3 | export { default } from "./PickList"; 4 | export { PickListModel }; 5 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/AreaMeasurement/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./AreaMeasurement"; 2 | export { default as AreaMeasurementModel } from "./AreaMeasurementModel"; 3 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/LineMeasurement/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./LineMeasurement"; 2 | export { default as LineMeasurementModel } from "./LineMeasurementModel"; 3 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Daylight/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Daylight"; 2 | export { default as DaylightModel } from "./DaylightModel"; 3 | export * from "./designer"; 4 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ShadowCast/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ShadowCast"; 2 | export { default as ShadowCastModel } from "./ShadowCastModel"; 3 | export * from "./designer"; 4 | -------------------------------------------------------------------------------- /libraries/timeslider/src/components/TimeSlider/TimeSlider.css: -------------------------------------------------------------------------------- 1 | /* Adjust Esri styling for the Time Slider component wrapper */ 2 | .esri-time-slider__layout--wide { 3 | min-width: 858px; 4 | } 5 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/LibraryViewer/index.ts: -------------------------------------------------------------------------------- 1 | import LibraryViewerModel from "./LibraryViewerModel"; 2 | 3 | export { default } from "./LibraryViewer"; 4 | export { LibraryViewerModel }; 5 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /libraries/3d-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vertigis/web-sdk/config/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | }, 6 | "include": [".eslintrc.js", "src"] 7 | } 8 | -------------------------------------------------------------------------------- /libraries/mapillary/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vertigis/web-sdk/config/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | }, 6 | "include": [".eslintrc.js", "src"] 7 | } 8 | -------------------------------------------------------------------------------- /libraries/timeslider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vertigis/web-sdk/config/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | }, 6 | "include": [".eslintrc.js", "src"] 7 | } 8 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ElevationProfile/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ElevationProfile"; 2 | export { default as ElevationProfileModel } from "./ElevationProfileModel"; 3 | export * from "./designer"; 4 | -------------------------------------------------------------------------------- /libraries/library-viewer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vertigis/web-sdk/config/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | }, 6 | "include": [".eslintrc.js", "src"] 7 | } 8 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vertigis/web-sdk/config/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | }, 6 | "include": [".eslintrc.js", "src"] 7 | } 8 | -------------------------------------------------------------------------------- /libraries/3d-tools/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve("@vertigis/web-sdk/config/.eslintrc")], 3 | parserOptions: { 4 | tsConfigRootDir: __dirname, 5 | }, 6 | rules: {}, 7 | }; 8 | -------------------------------------------------------------------------------- /libraries/mapillary/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve("@vertigis/web-sdk/config/.eslintrc")], 3 | parserOptions: { 4 | tsConfigRootDir: __dirname, 5 | }, 6 | rules: {}, 7 | }; 8 | -------------------------------------------------------------------------------- /libraries/timeslider/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve("@vertigis/web-sdk/config/.eslintrc")], 3 | parserOptions: { 4 | tsConfigRootDir: __dirname, 5 | }, 6 | rules: {}, 7 | }; 8 | -------------------------------------------------------------------------------- /libraries/library-viewer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve("@vertigis/web-sdk/config/.eslintrc")], 3 | parserOptions: { 4 | tsConfigRootDir: __dirname, 5 | }, 6 | rules: {}, 7 | }; 8 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve("@vertigis/web-sdk/config/.eslintrc")], 3 | parserOptions: { 4 | tsConfigRootDir: __dirname, 5 | }, 6 | rules: {}, 7 | }; 8 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/LibraryViewer/LibraryViewer.css: -------------------------------------------------------------------------------- 1 | /* Fixing a very odd bug created in the mapillary sample by a "clever" MUI 2 | style. */ 3 | .library-viewer > :not(style) ~ :not(style) { 4 | margin: 0; 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | package-lock.json 5 | **/build/* 6 | !viewer/build/index.html 7 | !viewer/build/_* 8 | .yarn 9 | libraries/eagle-view-viewer/localhost+2-key.pem 10 | libraries/eagle-view-viewer/localhost+2.pem 11 | libraries/eagle-view-viewer/app/WebMapUrls.txt 12 | -------------------------------------------------------------------------------- /libraries/mapillary/app/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /libraries/timeslider/app/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /libraries/3d-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library-3d-tools", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "vertigis-web-sdk build", 7 | "start": "vertigis-web-sdk start" 8 | }, 9 | "devDependencies": { 10 | "@vertigis/web": "^5.37.0", 11 | "@vertigis/web-sdk": "^1.11.1", 12 | "typescript": "~5.3.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libraries/library-viewer/app/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /libraries/timeslider/app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "items": [ 4 | { 5 | "$type": "map-extension", 6 | "id": "map-config-1", 7 | "webMap": "https://latitudegeo.maps.arcgis.com/home/item.html?id=3033d37311d147768ec787d875382965" 8 | }, 9 | { 10 | "$type": "time-slider", 11 | "id": "time-slider-config-1" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /scripts/copy-static-files.js: -------------------------------------------------------------------------------- 1 | const { exec } = require("child_process"); 2 | 3 | const libraries = ["mapillary", "timeslider", "3d-tools"]; 4 | 5 | exec("shx cp -r ./libraries/library-viewer/{app,build}/* ./viewer/build"); 6 | 7 | libraries.forEach((library) => { 8 | exec(`shx mkdir -p ./viewer/build/${library}`); 9 | exec( 10 | `shx cp -r ./libraries/${library}/{app,build,README.md} ./viewer/build/${library}` 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eagle-view-viewer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "vertigis-web-sdk build", 7 | "start": "vertigis-web-sdk start" 8 | }, 9 | "dependencies": { 10 | "@vertigis/web": "^5.37.0" 11 | }, 12 | "devDependencies": { 13 | "@vertigis/web-sdk": "^1.10.0", 14 | "typescript": "~5.3.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "arcgis", 4 | "closeto", 5 | "clsx", 6 | "Geocortex", 7 | "locationmarker", 8 | "mapillary", 9 | "nodechanged", 10 | "povchanged", 11 | "Rerender", 12 | "serializable", 13 | "unsync", 14 | "unsynced", 15 | "vertigis" 16 | ], 17 | "eslint.workingDirectories": [{ "pattern": "./libraries/*/" }] 18 | } 19 | -------------------------------------------------------------------------------- /libraries/mapillary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library-mapillary", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "vertigis-web-sdk build", 7 | "start": "vertigis-web-sdk start" 8 | }, 9 | "dependencies": { 10 | "@vertigis/web": "^5.37.0", 11 | "mapillary-js": "^4.1.2" 12 | }, 13 | "devDependencies": { 14 | "@vertigis/web-sdk": "^1.10.0", 15 | "typescript": "~5.3.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libraries/3d-tools/app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "items": [ 4 | { 5 | "$type": "map-extension", 6 | "id": "map-config-1", 7 | "initialViewMode": "scene", 8 | "webScene": "https://www.arcgis.com/home/item.html?id=94e00add11334767afb0abdce49c9a43" 9 | }, 10 | { 11 | "$type": "branding", 12 | "id": "branding", 13 | "activeTheme": "dark" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/app/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /libraries/mapillary/app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "items": [ 4 | { 5 | "$type": "map-extension", 6 | "id": "map-config-1", 7 | "webScene": "https://www.arcgis.com/home/item.html?id=94e00add11334767afb0abdce49c9a43" 8 | }, 9 | { 10 | "$type": "mapillary", 11 | "id": "mapillary-config-1", 12 | "mapillaryKey": "MLY|4371038552987952|3051b8de90b9a19c55ba2e5edd9376db" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /libraries/timeslider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library-time-slider", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "vertigis-web-sdk build", 7 | "start": "vertigis-web-sdk start" 8 | }, 9 | "dependencies": { 10 | "@vertigis/web": "^5.37.0", 11 | "tslib": "^2.6.2" 12 | }, 13 | "devDependencies": { 14 | "@vertigis/web-sdk": "^1.11.1", 15 | "typescript": "~5.3.3" 16 | }, 17 | "peerDependencies": { 18 | "tslib": "^2.6.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "items": [ 4 | { 5 | "$type": "map-extension", 6 | "id": "map-config-1", 7 | "webMap": "https://latitudegeo.maps.arcgis.com/home/item.html?id=ce389e2afb7a48e1aa3db85e57949f92" 8 | }, 9 | { 10 | "$type": "eagleview", 11 | "id": "eagleview-config-1", 12 | "apiKey": "e8ac86a1-1b08-40cb-ae64-92d12dfce9a5" 13 | }, 14 | { 15 | "id": "scale-input-config-1", 16 | "$type": "scale-input" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | chromeWebSecurity: false, 5 | defaultCommandTimeout: 30000, 6 | modifyObstructiveCode: false, 7 | numTestsKeptInMemory: 0, 8 | video: false, 9 | viewportWidth: 1280, 10 | viewportHeight: 720, 11 | e2e: { 12 | // We've imported your old cypress plugins here. 13 | // You may want to clean this up later by importing these. 14 | setupNodeEvents(on, config) { 15 | return require("./cypress/plugins/index.js")(on, config); 16 | }, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /cypress/mapUtils.ts: -------------------------------------------------------------------------------- 1 | export function getMapOrSceneView(mapEl: JQuery) { 2 | const mapId = mapEl[0].getAttribute("data-layout-id")!; 3 | const win = mapEl[0].ownerDocument?.defaultView! as any; 4 | 5 | return win.__maps[mapId] || win.__scenes[mapId]; 6 | } 7 | 8 | export function expectMapToBeStationary(mapEl: JQuery) { 9 | const map = getMapOrSceneView(mapEl); 10 | expect(map.stationary).to.be.true; 11 | } 12 | 13 | export function getCurrentViewpoint(mapEl: JQuery) { 14 | const map = getMapOrSceneView(mapEl); 15 | return map.viewpoint.toJSON(); 16 | } 17 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | "types": ["cypress"] 18 | }, 19 | "include": ["**/*"] 20 | } 21 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/locale/inv.json: -------------------------------------------------------------------------------- 1 | { 2 | "language-designer-eagleview-title": "EagleView Embedded Explorer", 3 | "language-designer-eagleview-api-key-description": "Add your EagleView API Key here to enable this component.", 4 | "language-designer-eagleview-api-key-title": "API Key", 5 | "language-designer-eagleview-default-scale-description": "Scale to zoom to after adding the location marker to the map component. Enter 0 to remain at the current scale.", 6 | "language-designer-eagleview-default-scale-title": "Default Scale", 7 | "language-eagleview-recenter-title": "Recenter the map", 8 | "language-eagleview-close-title": "Close Eagleview" 9 | } 10 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | import "./commands"; 17 | 18 | Cypress.Screenshot.defaults({ screenshotOnRunFailure: false }); 19 | -------------------------------------------------------------------------------- /libraries/timeslider/README.md: -------------------------------------------------------------------------------- 1 | # Time Slider Component 2 | 3 | The `Time Slider` component in this library renders an [Esri Time Slider](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-TimeSlider.html) widget which enables playback of time-aware layers in a VertiGIS Studio Web application. 4 | 5 | The [component](src/components/TimeSlider/TimeSlider.tsx) initializes Esri's [Time Slider JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-TimeSlider.html) and acts as a wrapper so it can be placed anywhere in a VertiGIS Studio Web application. 6 | 7 | The VertiGIS Studio Map component in the sample instantiates a sample VertiGIS Studio Web application with a Time Slider component pre-configured. 8 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/ReadMe/ReadMeModel.ts: -------------------------------------------------------------------------------- 1 | import { command } from "@vertigis/web/messaging"; 2 | import type { ComponentModelProperties } from "@vertigis/web/models"; 3 | import { ComponentModelBase, serializable } from "@vertigis/web/models"; 4 | 5 | @serializable 6 | export default class ReadMeModel extends ComponentModelBase { 7 | readme: string; 8 | 9 | @command("library-viewer.display-readme") 10 | protected async _executeDisplayReadMe(libraryId: string): Promise { 11 | this.readme = ( 12 | await import( 13 | /* webpackExclude: /node_modules/ */ `../../../../../libraries/${libraryId}/README.md` 14 | ) 15 | )?.default; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | # Fixes https://github.com/expo/expo-github-action/issues/20 16 | - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p 17 | 18 | - name: Use Node.js 20 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 20.x 22 | 23 | - run: corepack enable 24 | 25 | - run: yarn 26 | 27 | - run: yarn build 28 | 29 | - run: yarn test 30 | -------------------------------------------------------------------------------- /libraries/library-viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library-viewer", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "license": "UNLICENSED", 7 | "author": "", 8 | "type": "commonjs", 9 | "scripts": { 10 | "build": "vertigis-web-sdk build", 11 | "start": "vertigis-web-sdk start", 12 | "upgrade": "vertigis-web-sdk upgrade" 13 | }, 14 | "dependencies": { 15 | "xml2js": "^0.6.2" 16 | }, 17 | "devDependencies": { 18 | "@types/xml2js": "^0.4.14", 19 | "@vertigis/web": "^5.37.0", 20 | "@vertigis/web-sdk": "^1.10.0", 21 | "typescript": "~5.3.3" 22 | }, 23 | "overrides": { 24 | "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libraries/mapillary/src/components/Mapillary/Mapillary.css: -------------------------------------------------------------------------------- 1 | .third-party-map-controls { 2 | position: absolute; 3 | top: 1rem; 4 | right: 1rem; 5 | background-color: var(--buttonBackground); 6 | opacity: 0.8; 7 | } 8 | 9 | .third-party-map-controls:hover { 10 | opacity: 1; 11 | } 12 | 13 | .third-party-map-controls button { 14 | border-radius: inherit; 15 | } 16 | 17 | .third-party-map-controls svg { 18 | fill: var(--buttonIcon); 19 | } 20 | 21 | .third-party-map-controls button.selected { 22 | background-color: var(--emphasizedButtonBackground); 23 | } 24 | 25 | .third-party-map-controls button.selected svg { 26 | fill: var(--emphasizedButtonIcon); 27 | } 28 | 29 | .mapillary-map-container { 30 | height: 100%; 31 | } 32 | 33 | .mapillary-js { 34 | height: 100%; 35 | } 36 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/PickList/PickListModel.ts: -------------------------------------------------------------------------------- 1 | import { command } from "@vertigis/web/messaging"; 2 | import { 3 | ComponentModelBase, 4 | serializable, 5 | type ComponentModelProperties, 6 | } from "@vertigis/web/models"; 7 | 8 | import type { Library } from "../LibraryViewer/LibraryViewerModel"; 9 | 10 | export interface SetLibraryArgs { 11 | libraries: Library[]; 12 | selectedLibrary: string; 13 | } 14 | 15 | @serializable 16 | export default class PickListModel extends ComponentModelBase { 17 | libraries: Library[]; 18 | selectedLibrary: string; 19 | 20 | @command("library-viewer.set-libraries") 21 | protected _executeSetLibraries({ libraries, selectedLibrary }): void { 22 | this.libraries = libraries; 23 | this.selectedLibrary = selectedLibrary; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cypress/support/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Cypress { 2 | interface Chainable { 3 | /** 4 | * Get the root element of the map component. 5 | * If no `id` is specified, it will return the first map. 6 | */ 7 | getMap(id?: string): Chainable>; 8 | /** 9 | * Get the `body` element from the viewer `iframe`. 10 | */ 11 | getViewer(): Chainable>; 12 | /** 13 | * Get the `body` element from the parent `iframe` of the viewer. 14 | */ 15 | getViewerParent(): Chainable>; 16 | /** 17 | * Get the `body` element from the viewer `iframe` when nested within a 18 | * parent iframe. 19 | */ 20 | getNestedViewer(): Chainable>; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/README.md: -------------------------------------------------------------------------------- 1 | # EagleView Embedded Explorer 2 | 3 | The `EagleView` component in this library renders an [Embedded Explorer](https://embedded-explorer.eagleview.com/static/docs/) control. 4 | 5 | The [component](src/components/EmbeddedExplorer/EagleView.tsx) initializes the embedded explorer and acts as a wrapper so it can be placed anywhere in a VertiGIS Studio Web application. It will sync the current position of the Web applications map with the location of the embedded explorer so that changes in either map will be reflected int the other view. 6 | 7 | The VertiGIS Studio Map component in the sample instantiates a sample VertiGIS Studio Web application with an EagleView component pre-configured. 8 | 9 | Please [see this article](https://support.vertigis.com/hc/en-us/articles/28311610134418-Add-Eagleview-Explorer-to-VertiGIS-Studio-Web) for instructions on deploying the EagleView Embedded Explorer component for VertiGIS Studio Web. 10 | -------------------------------------------------------------------------------- /libraries/library-viewer/app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "items": [ 4 | { 5 | "$type": "library-viewer-model", 6 | "id": "library-viewer-1", 7 | "libraries": [ 8 | { 9 | "id": "mapillary", 10 | "title": "Mapillary" 11 | }, 12 | { 13 | "id": "timeslider", 14 | "title": "ESRI Timeslider" 15 | }, 16 | { 17 | "id": "3d-tools", 18 | "title": "3D Tools" 19 | }, 20 | { 21 | "id": "eagle-view-viewer", 22 | "title": "Eagle View Embedded Explorer" 23 | } 24 | ] 25 | }, 26 | { 27 | "$type": "layout", 28 | "id": "default", 29 | "title": "Default", 30 | "url": "./layout.xml" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /libraries/mapillary/README.md: -------------------------------------------------------------------------------- 1 | # Mapillary Component 2 | 3 | The `Mapillary` component in this library renders a [Mapillary](https://www.mapillary.com/) viewer and facilitates bi-directional communication with the rest of the VertiGIS Studio Web application. In this sample we've used Mapillary to present street-level imagery alongside the VertiGIS Studio Map component. 4 | 5 | The [component model](src/components/Mapillary/MapillaryModel.ts) initializes the Mapillary viewer by referencing their [mapillary-js JavaScript library](https://github.com/mapillary/mapillary-js) that was installed as a [project dependency](package.json) of this sample. 6 | 7 | The VertiGIS Studio Map component in the sample is controlled using the [`map.zoom-to-viewpoint`](https://developers.vertigisstudio.com/docs/web/api-commands-operations-events#command-map.zoom-to-viewpoint) and [`location-marker.*`](https://developers.vertigisstudio.com/docs/web/api-commands-operations-events#command-location-marker.create) commands in response to events from the Mapillary viewer. 8 | -------------------------------------------------------------------------------- /scripts/serve-library-viewer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Serves the library viewer from a static folder. This ends up being fairly close 3 | * to how the viewer is run by Netlify. 4 | */ 5 | const express = require("express"); 6 | const path = require("path"); 7 | const http = require("http"); 8 | const https = require("https"); 9 | const { createProxyMiddleware } = require("http-proxy-middleware"); 10 | 11 | const app = express(); 12 | const viewerUrl = "https://apps.vertigisstudio.com/web"; 13 | 14 | app.use("/", express.static(path.join(__dirname, "../viewer/build"))); 15 | app.use( 16 | "/viewer", 17 | createProxyMiddleware({ 18 | target: viewerUrl, 19 | agent: viewerUrl.startsWith("https") 20 | ? new https.Agent({ keepAlive: true }) 21 | : new http.Agent({ keepAlive: true }), 22 | changeOrigin: true, 23 | pathRewrite: { 24 | "^/viewer": "/", 25 | }, 26 | }) 27 | ); 28 | 29 | app.listen(3008); 30 | console.log("Library Viewer is being served at http://localhost:3008"); 31 | -------------------------------------------------------------------------------- /libraries/library-viewer/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with the [VertiGIS Studio Web SDK](https://github.com/vertigis/vertigis-web-sdk). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the project in development mode. Open [http://localhost:3001](http://localhost:3001) to view it in the browser. 10 | 11 | The page will automatically reload if you make changes to the code. You will see build errors and warnings in the console. 12 | 13 | ### `npm run build` 14 | 15 | Builds the library for production to the `build` folder. It optimizes the build for the best performance. 16 | 17 | Your custom library is now ready to be deployed! 18 | 19 | See the [section about deployment](https://developers.vertigis.com/docs/web/sdk-deployment/) in the [Developer Center](https://developers.vertigis.com/docs/web/overview/) for more information. 20 | 21 | ## Learn More 22 | 23 | Find [further documentation on the SDK](https://developers.vertigis.com/docs/web/sdk-overview/) on the [VertiGIS Studio Developer Center](https://developers.vertigis.com/docs/web/overview/) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 VertiGIS North America Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/LineOfSight/LineOfSightModel.ts: -------------------------------------------------------------------------------- 1 | import type { MapModel } from "@vertigis/web/mapping"; 2 | import type { 3 | PropertyDefs, 4 | ComponentModelProperties, 5 | } from "@vertigis/web/models"; 6 | import { 7 | ComponentModelBase, 8 | serializable, 9 | importModel, 10 | } from "@vertigis/web/models"; 11 | 12 | type LineOfSightModelProperties = ComponentModelProperties; 13 | 14 | @serializable 15 | export default class LineOfSightModel extends ComponentModelBase { 16 | @importModel("map-extension") 17 | map: MapModel | undefined; 18 | 19 | protected override _getSerializableProperties(): PropertyDefs { 20 | const props = super._getSerializableProperties(); 21 | return { 22 | ...props, 23 | title: { 24 | ...this._toPropertyDef(props.title), 25 | default: "language-web-incubator-line-of-sight-3d-title", 26 | }, 27 | icon: { 28 | ...this._toPropertyDef(props.icon), 29 | default: "map-3rd-party", 30 | }, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/AreaMeasurement/AreaMeasurementModel.ts: -------------------------------------------------------------------------------- 1 | import type { MapModel } from "@vertigis/web/mapping"; 2 | import type { 3 | PropertyDefs, 4 | ComponentModelProperties, 5 | } from "@vertigis/web/models"; 6 | import { 7 | ComponentModelBase, 8 | serializable, 9 | importModel, 10 | } from "@vertigis/web/models"; 11 | 12 | type AreaMeasurementModelProperties = ComponentModelProperties; 13 | 14 | @serializable 15 | export default class AreaMeasurementModel extends ComponentModelBase { 16 | @importModel("map-extension") 17 | map: MapModel | undefined; 18 | 19 | protected override _getSerializableProperties(): PropertyDefs { 20 | const props = super._getSerializableProperties(); 21 | return { 22 | ...props, 23 | title: { 24 | ...this._toPropertyDef(props.title), 25 | default: "language-web-incubator-area-measurement-3d-title", 26 | }, 27 | icon: { 28 | ...this._toPropertyDef(props.icon), 29 | default: "map-3rd-party", 30 | }, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/LineMeasurement/LineMeasurementModel.ts: -------------------------------------------------------------------------------- 1 | import type { MapModel } from "@vertigis/web/mapping"; 2 | import type { 3 | PropertyDefs, 4 | ComponentModelProperties, 5 | } from "@vertigis/web/models"; 6 | import { 7 | ComponentModelBase, 8 | serializable, 9 | importModel, 10 | } from "@vertigis/web/models"; 11 | 12 | type LineMeasurementModelProperties = ComponentModelProperties; 13 | 14 | @serializable 15 | export default class LineMeasurementModel extends ComponentModelBase { 16 | @importModel("map-extension") 17 | map: MapModel | undefined; 18 | 19 | protected override _getSerializableProperties(): PropertyDefs { 20 | const props = super._getSerializableProperties(); 21 | return { 22 | ...props, 23 | title: { 24 | ...this._toPropertyDef(props.title), 25 | default: "language-web-incubator-line-measurement-3d-title", 26 | }, 27 | icon: { 28 | ...this._toPropertyDef(props.icon), 29 | default: "map-3rd-party", 30 | }, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/ReadMe/ReadMe.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | import type { LayoutElementProperties } from "@vertigis/web/components"; 3 | import { LayoutElement } from "@vertigis/web/components"; 4 | import { useWatchAndRerender } from "@vertigis/web/ui"; 5 | import Markdown from "@vertigis/web/ui/Markdown"; 6 | import type { FC } from "react"; 7 | import { useEffect, useState } from "react"; 8 | 9 | import type ReadMeModel from "./ReadMeModel"; 10 | 11 | export interface ReadMeProps extends LayoutElementProperties {} 12 | 13 | const ReadMe: FC = ({ model, ...layoutProps }) => { 14 | const [readmeText, setReadmeText] = useState(); 15 | useWatchAndRerender(model, "readme"); 16 | 17 | useEffect(() => { 18 | setReadmeText(model.readme); 19 | }, [model.readme]); 20 | 21 | return ( 22 | 23 | {readmeText && ( 24 | 28 | )} 29 | 30 | ); 31 | }; 32 | 33 | export default ReadMe; 34 | -------------------------------------------------------------------------------- /libraries/3d-tools/app/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /libraries/mapillary/src/locale/inv.json: -------------------------------------------------------------------------------- 1 | { 2 | "language-designer-mapillary-key-description": "Add your API Key for Mapillary here to enable this component.", 3 | "language-designer-mapillary-key-title": "Mapillary Key", 4 | "language-designer-mapillary-search-radius-description": "Distance in meters to search from the location marker for a valid Mapillary image node.", 5 | "language-designer-mapillary-search-radius-title": "Search Radius", 6 | "language-designer-mapillary-default-scale-description": "Scale to zoom to after adding the location marker to the map component. Enter 0 to remain at the current scale.", 7 | "language-designer-mapillary-default-scale-title": "Default Scale", 8 | "language-designer-mapillary-start-synced-description": "Enable this to automatically link the position and viewpoint of Mapillary with the web component.", 9 | "language-designer-mapillary-start-synced-title": "Start Synced", 10 | "language-web-incubator-mapillary-title": "Mapillary", 11 | "language-web-incubator-mapillary-enable-sync-title": "Enable positional sync", 12 | "language-web-incubator-mapillary-disable-sync-title": "Disable positional sync", 13 | "language-web-incubator-mapillary-recenter-title": "Recenter the map" 14 | } 15 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Slice/SliceModel.ts: -------------------------------------------------------------------------------- 1 | import type { MapModel } from "@vertigis/web/mapping"; 2 | import type { 3 | PropertyDefs, 4 | ComponentModelProperties, 5 | } from "@vertigis/web/models"; 6 | import { 7 | ComponentModelBase, 8 | serializable, 9 | importModel, 10 | } from "@vertigis/web/models"; 11 | 12 | interface SliceModelProperties extends ComponentModelProperties { 13 | tiltEnabled?: boolean; 14 | } 15 | 16 | @serializable 17 | export default class SliceModel extends ComponentModelBase { 18 | @importModel("map-extension") 19 | map: MapModel | undefined; 20 | 21 | tiltEnabled?: boolean; 22 | 23 | protected override _getSerializableProperties(): PropertyDefs { 24 | const props = super._getSerializableProperties(); 25 | return { 26 | ...props, 27 | title: { 28 | ...this._toPropertyDef(props.title), 29 | default: "language-web-incubator-slice-3d-title", 30 | }, 31 | icon: { 32 | ...this._toPropertyDef(props.icon), 33 | default: "map-3rd-party", 34 | }, 35 | tiltEnabled: { 36 | ...this._toPropertyDef(props.tiltEnabled), 37 | default: true, 38 | }, 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | on("before:browser:launch", (browser = {}, launchOptions) => { 22 | if (browser.family === "chromium" && browser.name !== "electron") { 23 | // Remove default `disable-gpu` flag. 24 | launchOptions.args = launchOptions.args.filter( 25 | (arg) => arg !== "--disable-gpu" 26 | ); 27 | // Coerce Chrome into enabling GPU accel as it often will default to 28 | // software only on Linux systems. 29 | launchOptions.args.push("--ignore-gpu-blacklist"); 30 | 31 | return launchOptions; 32 | } 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { LibraryRegistry } from "@vertigis/web/config"; 2 | 3 | import EagleView, { EagleViewModel } from "./components/EmbeddedExplorer"; 4 | import { 5 | applyEVSettings, 6 | getEVSettings, 7 | getPictSettingsSchema, 8 | } from "./components/EmbeddedExplorer/designer"; 9 | import invLanguage from "./locale/inv.json"; 10 | 11 | const LAYOUT_NAMESPACE = "vertigis.web.incubator.eagleview"; 12 | 13 | export default function (registry: LibraryRegistry): void { 14 | registry.registerComponent({ 15 | category: "map", 16 | name: "eagleview", 17 | namespace: LAYOUT_NAMESPACE, 18 | getComponentType: () => EagleView, 19 | getDesignerSettings: getEVSettings, 20 | applyDesignerSettings: applyEVSettings, 21 | getDesignerSettingsSchema: getPictSettingsSchema, 22 | itemType: "eagleview", 23 | title: "language-designer-eagleview-title", 24 | iconId: "map-3rd-party", 25 | }); 26 | registry.registerModel({ 27 | getModel: (config) => new EagleViewModel(config), 28 | itemType: "eagleview", 29 | }); 30 | registry.registerLanguageResources({ 31 | locale: "inv", 32 | values: invLanguage as { [key: string]: string }, 33 | }); 34 | registry.registerLanguageResources({ 35 | locale: "en", 36 | values: invLanguage as { [key: string]: string }, 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /libraries/mapillary/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { LibraryRegistry } from "@vertigis/web/config"; 2 | import type { GetDesignerSettingsSchemaArgs } from "@vertigis/web/designer"; 3 | 4 | import Mapillary, { MapillaryModel } from "./components/Mapillary"; 5 | import { 6 | applySettings, 7 | getSettings, 8 | getSettingsSchema, 9 | } from "./components/Mapillary/designer"; 10 | import invLanguage from "./locale/inv.json"; 11 | 12 | export default function (registry: LibraryRegistry): void { 13 | registry.registerComponent({ 14 | name: "mapillary", 15 | namespace: "vertigis.web.incubator", 16 | getComponentType: () => Mapillary, 17 | getDesignerSettings: ( 18 | args: GetDesignerSettingsSchemaArgs 19 | ) => getSettings(args), 20 | applyDesignerSettings: (args) => applySettings(args), 21 | getDesignerSettingsSchema: ( 22 | args: GetDesignerSettingsSchemaArgs 23 | ) => getSettingsSchema(args), 24 | itemType: "mapillary", 25 | title: "language-web-incubator-mapillary-title", 26 | iconId: "map-3rd-party", 27 | }); 28 | registry.registerModel({ 29 | getModel: (config) => new MapillaryModel(config), 30 | itemType: "mapillary", 31 | }); 32 | registry.registerLanguageResources({ 33 | locale: "inv", 34 | values: invLanguage as { [key: string]: string }, 35 | }); 36 | registry.registerLanguageResources({ 37 | locale: "en", 38 | values: invLanguage as { [key: string]: string }, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /libraries/timeslider/src/components/TimeSlider/designer.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ApplyDesignerSettingsCallback, 3 | ComponentModelDesignerSettings, 4 | DesignerSettings, 5 | GetDesignerSettingsCallback, 6 | GetDesignerSettingsSchemaCallback, 7 | SettingsSchema, 8 | } from "@vertigis/web/designer"; 9 | import { 10 | applyComponentModelDesignerSettings, 11 | getComponentModelDesignerSettings, 12 | getComponentModelDesignerSettingsSchema, 13 | } from "@vertigis/web/designer"; 14 | 15 | import type TimeSliderModel from "./TimeSliderModel"; 16 | 17 | export type TimeSliderSettings = ComponentModelDesignerSettings; 18 | 19 | export type SettingsMap = DesignerSettings; 20 | 21 | export const applySettings: ApplyDesignerSettingsCallback< 22 | TimeSliderModel, 23 | SettingsMap 24 | > = async (args) => { 25 | const { model, settings } = args; 26 | await applyComponentModelDesignerSettings(args); 27 | model.assignProperties(settings); 28 | }; 29 | 30 | export const getSettings: GetDesignerSettingsCallback< 31 | TimeSliderModel, 32 | SettingsMap 33 | > = async (args) => ({ 34 | ...(await getComponentModelDesignerSettings(args)), 35 | }); 36 | 37 | export const getSettingsSchema: GetDesignerSettingsSchemaCallback< 38 | TimeSliderModel, 39 | SettingsMap 40 | > = async (args) => { 41 | const baseSchema = await getComponentModelDesignerSettingsSchema(args); 42 | const schema: SettingsSchema = { 43 | ...baseSchema, 44 | settings: [...baseSchema.settings], 45 | }; 46 | return schema; 47 | }; 48 | -------------------------------------------------------------------------------- /libraries/timeslider/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { LibraryRegistry } from "@vertigis/web/config"; 2 | import type { GetDesignerSettingsSchemaArgs } from "@vertigis/web/designer"; 3 | 4 | import TimeSlider, { TimeSliderModel } from "./components/TimeSlider"; 5 | import { 6 | applySettings, 7 | getSettings, 8 | getSettingsSchema, 9 | } from "./components/TimeSlider/designer"; 10 | import invLanguage from "./locale/inv.json"; 11 | 12 | export default function (registry: LibraryRegistry): void { 13 | registry.registerComponent({ 14 | name: "time-slider", 15 | namespace: "vertigis.web.incubator.time-slider", 16 | getComponentType: () => TimeSlider, 17 | getDesignerSettings: ( 18 | args: GetDesignerSettingsSchemaArgs 19 | ) => getSettings(args), 20 | applyDesignerSettings: (args) => applySettings(args), 21 | getDesignerSettingsSchema: ( 22 | args: GetDesignerSettingsSchemaArgs 23 | ) => getSettingsSchema(args), 24 | itemType: "time-slider", 25 | title: "language-web-incubator-time-slider-title", 26 | iconId: "range-start", 27 | }); 28 | registry.registerModel({ 29 | getModel: (config) => new TimeSliderModel(config), 30 | itemType: "time-slider", 31 | }); 32 | registry.registerLanguageResources({ 33 | locale: "inv", 34 | values: invLanguage as { [key: string]: string }, 35 | }); 36 | registry.registerLanguageResources({ 37 | locale: "en", 38 | values: invLanguage as { [key: string]: string }, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/LibraryViewer/LibraryViewer.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | LayoutElement, 3 | type LayoutElementProperties, 4 | } from "@vertigis/web/components"; 5 | import LayoutElementContainer from "@vertigis/web/components/LayoutElementContainer"; 6 | import { useWatchAndRerender } from "@vertigis/web/ui"; 7 | import Link from "@vertigis/web/ui/Link"; 8 | import Stack from "@vertigis/web/ui/Stack"; 9 | import type { FC } from "react"; 10 | 11 | import type LibraryViewerModel from "./LibraryViewerModel"; 12 | import "./LibraryViewer.css"; 13 | 14 | export interface LibraryViewerProps 15 | extends LayoutElementProperties {} 16 | 17 | const LibraryViewer: FC = ({ 18 | model, 19 | children, 20 | ...layoutProps 21 | }) => { 22 | const { selectedLibrary, libraryUrl } = model; 23 | useWatchAndRerender(model, "libraryUrl"); 24 | 25 | return ( 26 | 27 | 28 | 29 | {children} 30 | 31 | 36 | View the source code on GitHub 37 | 38 | 39 | Download this library 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default LibraryViewer; 47 | -------------------------------------------------------------------------------- /cypress/e2e/sample-viewer.cy.ts: -------------------------------------------------------------------------------- 1 | const getIframeDocument = () => { 2 | return cy 3 | .get(`iframe[name="viewer"]`) 4 | .its("0.contentDocument") 5 | .should("exist"); 6 | }; 7 | 8 | const getIframeBody = () => { 9 | return getIframeDocument() 10 | .its("body") 11 | .should("not.be.undefined") 12 | .then(cy.wrap); 13 | }; 14 | 15 | const getApplication = () => { 16 | return getIframeBody() 17 | .find("#gcx-app") 18 | .should("have.attr", "library-loaded") 19 | .then(cy.wrap); 20 | }; 21 | 22 | const getMap = (id?: string) => { 23 | const selector = id ? `[gcx-id="${id}"]` : ".gcx-map"; 24 | getApplication(); 25 | return getIframeBody() 26 | .find(selector) 27 | .and((el) => { 28 | const mapId = el[0].getAttribute("data-layout-id"); 29 | const win = el[0].ownerDocument?.defaultView as Window & any; 30 | const map = win.__maps?.[mapId!] || win.__scenes?.[mapId!]; 31 | 32 | // Wait for global map data to be available once initialized 33 | expect(!!map, "expect map to be created").to.be.true; 34 | expect(map.ready, "expect map to be ready").to.be.true; 35 | }) 36 | .then(cy.wrap); 37 | }; 38 | 39 | describe("sample-viewer", () => { 40 | it("loads the viewer frame", () => { 41 | cy.visit(`http://localhost:3001`); 42 | getIframeBody(); 43 | }); 44 | 45 | it("loads the viewer application", () => { 46 | cy.visit(`http://localhost:3001`); 47 | getApplication(); 48 | }); 49 | 50 | it("loads the correct sample", () => { 51 | cy.visit(`http://localhost:3001/#timeslider`); 52 | getIframeBody() 53 | .find(".gcx-readme") 54 | .should("contain.text", "Time Slider"); 55 | }); 56 | 57 | it("loads a map", () => { 58 | cy.visit(`http://localhost:3001/#timeslider`); 59 | getMap(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /libraries/3d-tools/README.md: -------------------------------------------------------------------------------- 1 | # Esri 3D Widget Components 2 | 3 | Esri's JavaScript 4.x API contains a series of ready-to-use UI widgets. Included in this library are some of Esri's 3D widgets wrapped in a VertiGIS Studio Web component so that they can be used in a VertiGIS Studio Web application. 4 | 5 | The [AreaMeasurement component](src\components\AreaMeasurement\AreaMeasurement.tsx) wraps Esri's [AreaMeasurement3D JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-AreaMeasurement3D.html). 6 | 7 | The [LineMeasurement component](src\components\LineMeasurement\LineMeasurement.tsx) wraps Esri's [DirectLineMeasurement3D JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-DirectLineMeasurement3D.html). 8 | 9 | The [Daylight component](src\components\Daylight\Daylight.tsx) wraps Esri's [Daylight JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Daylight.html). 10 | 11 | The [ElevationProfile component](src\components\ElevationProfile\ElevationProfile.tsx) wraps Esri's [ElevationProfile JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-ElevationProfile.html). 12 | 13 | The [LineOfSight component](src\components\LineOfSight\LineOfSight.tsx) wraps Esri's [LineOfSight JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-LineOfSight.html). 14 | 15 | The [ShadowCast component](src\components\ShadowCast\ShadowCast.tsx) wraps Esri's [ShadowCast JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-ShadowCast.html). 16 | 17 | The [Slice component](src\components\Slice\Slice.tsx) wraps Esri's [Slice JavaScript widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Slice.html). 18 | 19 | The sample VertiGIS Studio Web application instantiates the included 3D components in a Tab to allow you to view and interact with each widget separately. 20 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { LibraryRegistry } from "@vertigis/web/config"; 2 | 3 | import LibraryViewer, { LibraryViewerModel } from "./components/LibraryViewer"; 4 | import PickList, { PickListModel } from "./components/PickList"; 5 | import ReadMe, { ReadMeModel } from "./components/ReadMe"; 6 | 7 | const LAYOUT_NAMESPACE = "libraryViewer"; 8 | 9 | export default function (registry: LibraryRegistry): void { 10 | registry.registerComponent({ 11 | name: "pick-list", 12 | namespace: LAYOUT_NAMESPACE, 13 | getComponentType: () => PickList, 14 | itemType: "pick-list-model", 15 | title: "PickList", 16 | }); 17 | registry.registerComponent({ 18 | name: "readme", 19 | namespace: LAYOUT_NAMESPACE, 20 | getComponentType: () => ReadMe, 21 | itemType: "readme-model", 22 | title: "ReadMe", 23 | }); 24 | registry.registerComponent({ 25 | name: "library-viewer", 26 | namespace: LAYOUT_NAMESPACE, 27 | getComponentType: () => LibraryViewer, 28 | itemType: "library-viewer-model", 29 | title: "LibraryViewer", 30 | }); 31 | registry.registerModel({ 32 | getModel: (config) => new PickListModel(config), 33 | itemType: "pick-list-model", 34 | }); 35 | registry.registerModel({ 36 | getModel: (config) => new ReadMeModel(config), 37 | itemType: "readme-model", 38 | }); 39 | registry.registerModel({ 40 | getModel: (config) => new LibraryViewerModel(config), 41 | itemType: "library-viewer-model", 42 | }); 43 | registry.registerCommand({ 44 | name: "library-viewer.load-library", 45 | itemType: "library-viewer-model", 46 | }); 47 | registry.registerCommand({ 48 | name: "library-viewer.display-readme", 49 | itemType: "readme-model", 50 | }); 51 | registry.registerCommand({ 52 | name: "library-viewer.set-libraries", 53 | itemType: "pick-list-model", 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/components/EmbeddedExplorer/designer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyComponentModelDesignerSettings, 3 | type ApplyDesignerSettingsCallback, 4 | type ComponentModelDesignerSettings, 5 | type DesignerSettings, 6 | getComponentModelDesignerSettings, 7 | getComponentModelDesignerSettingsSchema, 8 | type GetDesignerSettingsCallback, 9 | type GetDesignerSettingsSchemaCallback, 10 | type SettingsSchema, 11 | } from "@vertigis/web/designer"; 12 | 13 | import type EagleViewModel from "./EagleViewModel"; 14 | 15 | export interface EagleViewSettings extends ComponentModelDesignerSettings { 16 | apiKey: string; 17 | } 18 | 19 | export type SettingsMap = DesignerSettings; 20 | 21 | export const applyEVSettings: ApplyDesignerSettingsCallback< 22 | EagleViewModel, 23 | SettingsMap 24 | > = async (args) => { 25 | const { model, settings } = args; 26 | 27 | await applyComponentModelDesignerSettings(args); 28 | 29 | model.assignProperties(settings); 30 | }; 31 | 32 | export const getEVSettings: GetDesignerSettingsCallback< 33 | EagleViewModel, 34 | SettingsMap 35 | > = async (args) => { 36 | const { model } = args; 37 | const { apiKey } = model; 38 | return { 39 | ...(await getComponentModelDesignerSettings(args)), 40 | apiKey, 41 | }; 42 | }; 43 | 44 | export const getPictSettingsSchema: GetDesignerSettingsSchemaCallback< 45 | EagleViewModel, 46 | SettingsMap 47 | > = async (args) => { 48 | const baseSchema = await getComponentModelDesignerSettingsSchema(args); 49 | const schema: SettingsSchema = { 50 | ...baseSchema, 51 | settings: [ 52 | ...baseSchema.settings, 53 | { 54 | id: "apiKey", 55 | type: "text", 56 | description: "language-designer-eagleview-api-key-description", 57 | displayName: "language-designer-eagleview-api-key-title", 58 | }, 59 | ], 60 | }; 61 | return schema; 62 | }; 63 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/AreaMeasurement/AreaMeasurement.tsx: -------------------------------------------------------------------------------- 1 | import type Accessor from "@arcgis/core/core/Accessor"; 2 | import AreaMeasurement3DWidget from "@arcgis/core/widgets/AreaMeasurement3D"; 3 | import { useWatchAndRerender } from "@vertigis/web/ui"; 4 | import Link from "@vertigis/web/ui/Link"; 5 | import type { 6 | MapWidgetConstructor, 7 | MapWidgetProps, 8 | } from "@vertigis/web/ui/esriUtils"; 9 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 10 | import type { ReactElement } from "react"; 11 | import { useState, useEffect } from "react"; 12 | 13 | import type AreaMeasurementModel from "./AreaMeasurementModel"; 14 | 15 | export type AreaMeasurementProps = MapWidgetProps< 16 | AreaMeasurementModel & Accessor 17 | >; 18 | 19 | const AreaMeasurement3DWrapper = createEsriMapWidget( 20 | AreaMeasurement3DWidget as MapWidgetConstructor, 21 | true, 22 | true 23 | ); 24 | 25 | export default function AreaMeasurement3D( 26 | props: AreaMeasurementProps 27 | ): ReactElement { 28 | const { model } = props; 29 | const { map } = model; 30 | const [widget, setWidget] = useState(); 31 | 32 | useWatchAndRerender(map, ["map", "viewMode"]); 33 | useWatchAndRerender(widget?.viewModel, "state"); 34 | useEffect(() => { 35 | if (!widget) { 36 | return; 37 | } 38 | widget.label = model.title; 39 | }, [widget, model.title]); 40 | 41 | if (map.viewMode === "map") { 42 | return null; 43 | } 44 | 45 | return ( 46 | 51 | {widget?.viewModel?.state === "measured" && ( 52 | widget.viewModel.clear()} 55 | > 56 | language-web-incubator-common-clear 57 | 58 | )} 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/PickList/PickList.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | LayoutElement, 3 | type LayoutElementProperties, 4 | } from "@vertigis/web/components"; 5 | import { useWatchAndRerender } from "@vertigis/web/ui"; 6 | import ListItemButton from "@vertigis/web/ui/ListItemButton"; 7 | import MenuList from "@vertigis/web/ui/MenuList"; 8 | import Paper from "@vertigis/web/ui/Paper"; 9 | import Typography from "@vertigis/web/ui/Typography"; 10 | import type { FC } from "react"; 11 | import { useCallback } from "react"; 12 | 13 | import type PickListModel from "./PickListModel"; 14 | import type { Library } from "../LibraryViewer/LibraryViewerModel"; 15 | 16 | export interface PickListProps extends LayoutElementProperties {} 17 | 18 | const PickList: FC = ({ model, title, ...layoutProps }) => { 19 | const { libraries, selectedLibrary } = model; 20 | useWatchAndRerender(model, "libraries"); 21 | 22 | const onClick = useCallback( 23 | (library: Library) => { 24 | if (selectedLibrary !== library.id) { 25 | model.selectedLibrary = library.id; 26 | parent.location.hash = library.id; 27 | } 28 | }, 29 | [model, selectedLibrary] 30 | ); 31 | 32 | return ( 33 | 34 | 35 | {title} 36 | 37 | {libraries?.map((library) => ( 38 | onClick(library)} 42 | > 43 | 44 | {library.title} 45 | 46 | 47 | ))} 48 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default PickList; 55 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/LineOfSight/LineOfSight.tsx: -------------------------------------------------------------------------------- 1 | import type Accessor from "@arcgis/core/core/Accessor"; 2 | import LineOfSightWidget from "@arcgis/core/widgets/LineOfSight"; 3 | import { useWatchAndRerender } from "@vertigis/web/ui"; 4 | import Link from "@vertigis/web/ui/Link"; 5 | import type { 6 | MapWidgetConstructor, 7 | MapWidgetProps, 8 | } from "@vertigis/web/ui/esriUtils"; 9 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 10 | import { useEffect, useState } from "react"; 11 | import type { ReactElement } from "react"; 12 | 13 | import type LineOfSightModel from "./LineOfSightModel"; 14 | 15 | export type LineOfSightWidgetProps = MapWidgetProps< 16 | LineOfSightModel & Accessor 17 | >; 18 | 19 | const LineOfSightWrapper = createEsriMapWidget( 20 | LineOfSightWidget as MapWidgetConstructor, 21 | true, 22 | true 23 | ); 24 | 25 | export default function LineOfSight( 26 | props: LineOfSightWidgetProps 27 | ): ReactElement { 28 | const { model } = props; 29 | const { map } = model; 30 | const [widget, setWidget] = useState(); 31 | 32 | useWatchAndRerender(map, ["map", "viewMode"]); 33 | useWatchAndRerender(widget?.viewModel, "state"); 34 | useEffect(() => { 35 | if (!widget) { 36 | return; 37 | } 38 | widget.label = model.title; 39 | }, [widget, model.title]); 40 | 41 | if (map.viewMode === "map") { 42 | return null; 43 | } 44 | 45 | const widgetIsActive = 46 | widget?.viewModel?.state === "creating" || 47 | widget?.viewModel?.state === "created"; 48 | 49 | return ( 50 | 55 | {widgetIsActive && ( 56 | widget.viewModel.clear()} 59 | > 60 | language-web-incubator-common-clear 61 | 62 | )} 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/LineMeasurement/LineMeasurement.tsx: -------------------------------------------------------------------------------- 1 | import type Accessor from "@arcgis/core/core/Accessor"; 2 | import DirectLineMeasurement3DWidget from "@arcgis/core/widgets/DirectLineMeasurement3D"; 3 | import { useWatchAndRerender } from "@vertigis/web/ui"; 4 | import Link from "@vertigis/web/ui/Link"; 5 | import type { 6 | MapWidgetConstructor, 7 | MapWidgetProps, 8 | } from "@vertigis/web/ui/esriUtils"; 9 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 10 | import type { ReactElement } from "react"; 11 | import { useState, useEffect } from "react"; 12 | 13 | import type LineMeasurementModel from "./LineMeasurementModel"; 14 | 15 | export type AreaMeasurementProps = MapWidgetProps< 16 | LineMeasurementModel & Accessor 17 | >; 18 | 19 | const DirectLineMeasurement3DWidgetWrapper = createEsriMapWidget( 20 | DirectLineMeasurement3DWidget as MapWidgetConstructor, 21 | true, 22 | true 23 | ); 24 | 25 | export default function LineMeasurement( 26 | props: AreaMeasurementProps 27 | ): ReactElement { 28 | const { model } = props; 29 | const { map } = model; 30 | const [widget, setWidget] = useState(); 31 | 32 | useWatchAndRerender(map, ["map", "viewMode"]); 33 | useWatchAndRerender(widget?.viewModel, "state"); 34 | useEffect(() => { 35 | if (!widget) { 36 | return; 37 | } 38 | widget.label = model.title; 39 | }, [widget, model.title]); 40 | 41 | if (map.viewMode === "map") { 42 | return null; 43 | } 44 | 45 | return ( 46 | 51 | {widget?.viewModel?.state === "measured" && ( 52 | widget.viewModel.clear()} 55 | > 56 | language-web-incubator-common-clear 57 | 58 | )} 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Slice/designer.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ApplyDesignerSettingsCallback, 3 | ComponentModelDesignerSettings, 4 | DesignerSettings, 5 | GetDesignerSettingsCallback, 6 | GetDesignerSettingsSchemaCallback, 7 | Setting, 8 | SettingsSchema, 9 | } from "@vertigis/web/designer"; 10 | import { 11 | applyComponentModelDesignerSettings, 12 | getComponentModelDesignerSettings, 13 | getComponentModelDesignerSettingsSchema, 14 | } from "@vertigis/web/designer"; 15 | 16 | import type SliceModel from "./SliceModel"; 17 | 18 | export interface SliceSettings extends ComponentModelDesignerSettings { 19 | tiltEnabled?: boolean; 20 | } 21 | 22 | export type SettingsMap = DesignerSettings; 23 | 24 | export const applySettings: ApplyDesignerSettingsCallback< 25 | SliceModel, 26 | SettingsMap 27 | > = async (args) => { 28 | const { model, settings } = args; 29 | await applyComponentModelDesignerSettings(args); 30 | model.assignProperties(settings); 31 | }; 32 | 33 | export const getSettings: GetDesignerSettingsCallback< 34 | SliceModel, 35 | SettingsMap 36 | > = async (args) => { 37 | const { model } = args; 38 | const { tiltEnabled } = model; 39 | return { 40 | ...(await getComponentModelDesignerSettings(args)), 41 | tiltEnabled, 42 | }; 43 | }; 44 | 45 | export const getSettingsSchema: GetDesignerSettingsSchemaCallback< 46 | SliceModel, 47 | SettingsMap 48 | > = async (args) => { 49 | const baseSchema = await getComponentModelDesignerSettingsSchema(args); 50 | (baseSchema.settings[0].settings as Setting[]) = ( 51 | baseSchema.settings[0].settings as Setting[] 52 | ).concat([ 53 | { 54 | id: "tiltEnabled", 55 | type: "checkbox", 56 | description: 57 | "language-designer-3d-tools-slice-tilt-enabled-description", 58 | displayName: "language-designer-3d-tools-slice-tilt-enabled-title", 59 | }, 60 | ]); 61 | const schema: SettingsSchema = { 62 | ...baseSchema, 63 | settings: [...baseSchema.settings], 64 | }; 65 | return schema; 66 | }; 67 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Daylight/DaylightModel.ts: -------------------------------------------------------------------------------- 1 | import type { MapModel } from "@vertigis/web/mapping"; 2 | import type { 3 | PropertyDefs, 4 | ComponentModelProperties, 5 | } from "@vertigis/web/models"; 6 | import { 7 | ComponentModelBase, 8 | serializable, 9 | importModel, 10 | } from "@vertigis/web/models"; 11 | 12 | type DateOrSeason = "date" | "season"; 13 | 14 | interface DaylightModelProperties extends ComponentModelProperties { 15 | playButtons?: boolean; 16 | shadowsToggle?: boolean; 17 | datePicker?: boolean; 18 | timezone?: boolean; 19 | dateOrSeason?: DateOrSeason; 20 | playSpeedMultiplier?: number; 21 | } 22 | 23 | @serializable 24 | export default class DaylightModel extends ComponentModelBase { 25 | @importModel("map-extension") 26 | map: MapModel | undefined; 27 | 28 | playButtons: boolean; 29 | shadowsToggle: boolean; 30 | datePicker: boolean; 31 | timezone: boolean; 32 | dateOrSeason: DateOrSeason; 33 | playSpeedMultiplier: number; 34 | 35 | protected override _getSerializableProperties(): PropertyDefs { 36 | const props = super._getSerializableProperties(); 37 | return { 38 | ...props, 39 | playButtons: { 40 | serializeModes: ["initial"], 41 | default: true, 42 | }, 43 | shadowsToggle: { 44 | serializeModes: ["initial"], 45 | default: true, 46 | }, 47 | datePicker: { 48 | serializeModes: ["initial"], 49 | default: true, 50 | }, 51 | timezone: { 52 | serializeModes: ["initial"], 53 | default: true, 54 | }, 55 | dateOrSeason: { 56 | serializeModes: ["initial"], 57 | default: "date", 58 | }, 59 | playSpeedMultiplier: { 60 | serializeModes: ["initial"], 61 | default: 1, 62 | }, 63 | title: { 64 | ...this._toPropertyDef(props.title), 65 | default: "language-web-incubator-daylight-widget-3d-title", 66 | }, 67 | icon: { 68 | ...this._toPropertyDef(props.icon), 69 | default: "map-3rd-party", 70 | }, 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cypress/e2e/mapillary.cy.ts: -------------------------------------------------------------------------------- 1 | import { getMapOrSceneView } from "../mapUtils"; 2 | 3 | const sampleName = "mapillary"; 4 | 5 | // Although we provide the exact number from the street view position when 6 | // setting the map center position, the reported map center will not be exactly 7 | // the same. 8 | const mapCenterPrecision = 1e-3; 9 | const markerCenterPrecision = 1e-3; 10 | 11 | const expectMapAndMarkerCenter = (lat: number, lon: number) => 12 | cy 13 | .getViewer() 14 | .getMap() 15 | .should((mapEl) => { 16 | const mapView = getMapOrSceneView(mapEl); 17 | 18 | // Check map center 19 | expect(mapView.center.latitude).to.be.closeTo( 20 | lat, 21 | mapCenterPrecision 22 | ); 23 | expect(mapView.center.longitude).to.be.closeTo( 24 | lon, 25 | mapCenterPrecision 26 | ); 27 | 28 | // Check location marker center 29 | const locationMarker = mapView.map.allLayers 30 | .find((layer: any) => layer.id === "__GCX_MARKUP_1") 31 | .graphics.getItemAt(0); 32 | expect(locationMarker.geometry.latitude).to.be.closeTo( 33 | lat, 34 | markerCenterPrecision 35 | ); 36 | expect(locationMarker.geometry.longitude).to.be.closeTo( 37 | lon, 38 | markerCenterPrecision 39 | ); 40 | }); 41 | 42 | // This sample currently does not run on GitHub due to needing to load a 3d 43 | // scene, but may be enabled on your workstation. 44 | xdescribe(sampleName, () => { 45 | it("synchronizes marker position with street view position", () => { 46 | cy.visit(`http://localhost:3001/#${sampleName}`); 47 | 48 | // The following test depends on the web scene being used and the current 49 | // state of the mapillary database. 50 | 51 | // Marker is set initially to match street view position. 52 | expectMapAndMarkerCenter(51.90797166666704, 4.489869999999996); 53 | 54 | // Find the forward arrow by querying for the mapillary image id that 55 | // represents the next image in the forward direction. 56 | cy.getViewer().find('[data-id="1876951449133876"]').click(); 57 | 58 | // Marker is updated to match new street view position. 59 | expectMapAndMarkerCenter(51.908061666667, 4.4896200000001); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/components/EmbeddedExplorer/embedded-explorer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Partial interface for the EagleView Embedded Explorer widget. 3 | */ 4 | 5 | export interface EagleViewView { 6 | lonLat: { lat: number; lon: number }; 7 | rotation: number; 8 | zoom: number; 9 | } 10 | 11 | interface EmbeddedExplorerOptions { 12 | containerId?: string; 13 | apiKey?: string; 14 | theme?: "light" | "dark"; 15 | view?: EagleViewView; 16 | } 17 | 18 | export interface EmbeddedExplorerInstance { 19 | initialize(options: EmbeddedExplorerOptions): void; 20 | loadContent(contentId: string): Promise; 21 | setTheme(theme: "light" | "dark"): void; 22 | on( 23 | event: 24 | | "onViewUpdate" 25 | | "featureClick" 26 | | "layerSearch" 27 | | "Errors" 28 | | "onMapReady" 29 | | "onLayersDataLoad", 30 | callback: (...args: any[]) => void 31 | ): void; 32 | off( 33 | event: 34 | | "onViewUpdate" 35 | | "featureClick" 36 | | "layerSearch" 37 | | "Errors" 38 | | "onMapReady" 39 | | "onLayersDataLoad" 40 | ): void; 41 | destroy(containerId: string): void; 42 | mount( 43 | containerId: string, 44 | options?: EmbeddedExplorerOptions 45 | ): EmbeddedExplorerInstance; 46 | setView(view: EagleViewView, done?: () => void): void; 47 | } 48 | 49 | export interface WindowWithEv extends Window { 50 | ev?: { 51 | EmbeddedExplorer: { 52 | // eslint-disable-next-line @typescript-eslint/prefer-function-type 53 | new (): EmbeddedExplorerInstance; 54 | }; 55 | }; 56 | 57 | define?: any; 58 | require?: any; 59 | } 60 | 61 | /** 62 | * Creates a script element for the EagleView Embedded Explorer widget. 63 | * This script is loaded from the EagleView CDN. 64 | * 65 | * @returns {HTMLScriptElement} The script element to be appended to the document. 66 | */ 67 | export const createEagleViewScript = (): HTMLScriptElement => { 68 | const script = document.createElement("script"); 69 | script.id = "embedded-explorer-widget"; 70 | script.src = 71 | "https://embedded-explorer.eagleview.com/static/embedded-explorer-widget.js"; 72 | script.type = "application/javascript"; 73 | script.crossOrigin = "anonymous|use-credentials"; 74 | script.async = false; 75 | return script; 76 | }; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": { 4 | "packages": [ 5 | "libraries/*", 6 | "viewer" 7 | ] 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/vertigis/vertigis-web-incubator.git" 12 | }, 13 | "author": "Eric Parsons ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/vertigis/vertigis-web-incubator/issues" 17 | }, 18 | "homepage": "https://github.com/vertigis/vertigis-web-incubator#readme", 19 | "scripts": { 20 | "build": "yarn build:libraries && yarn build:viewer && yarn copy-static-files", 21 | "build:libraries": "yarn workspaces foreach -Rpt --from \"libraries/!(library-viewer)\" run build", 22 | "build:viewer": "yarn workspace library-viewer run build", 23 | "prettier": "prettier --write \"**/*.ts\" \"**/*.json\" \"**/*.tsx\" \"**/*.js\"", 24 | "test": "concurrently -k -s first -n webpack,cypress \"yarn start\" \"cypress run --browser chrome\"", 25 | "test:watch": "concurrently -k -s first -n webpack,cypress \"yarn start\" \"cypress open\"", 26 | "start": "yarn workspace library-viewer start", 27 | "copy-static-files": "node scripts/copy-static-files.js", 28 | "serve-library-viewer": "node scripts/serve-library-viewer.js" 29 | }, 30 | "devDependencies": { 31 | "concurrently": "^7.6.0", 32 | "cross-env": "^7.0.3", 33 | "cypress": "^12.3.0", 34 | "eslint": "~8.56.0", 35 | "eslint-config-prettier": "^9.1.0", 36 | "eslint-plugin-import": "~2.29.1", 37 | "eslint-plugin-only-warn": "^1.1.0", 38 | "eslint-plugin-react": "^7.34.3", 39 | "eslint-plugin-react-hooks": "~4.6.2", 40 | "express": "^4.19.2", 41 | "file-loader": "^6.2.0", 42 | "http-proxy-middleware": "^3.0.0", 43 | "lint-staged": "^13.1.0", 44 | "prettier": "^2.8.3", 45 | "shx": "^0.3.4", 46 | "simple-git-hooks": "^2.8.1", 47 | "ts-loader": "^9.5.1", 48 | "typescript": "~5.3.3", 49 | "webpack": "~5.90.2" 50 | }, 51 | "resolutions": { 52 | "@types/react": "^18.3.2" 53 | }, 54 | "simple-git-hooks": { 55 | "pre-commit": "npx lint-staged" 56 | }, 57 | "lint-staged": { 58 | "*.{js,json,ts,tsx,md,css,scss}": "prettier --write" 59 | }, 60 | "packageManager": "yarn@4.2.2" 61 | } 62 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Daylight/Daylight.tsx: -------------------------------------------------------------------------------- 1 | import type Accessor from "@arcgis/core/core/Accessor"; 2 | import DaylightWidget from "@arcgis/core/widgets/Daylight"; 3 | import { useWatchAndRerender, styled } from "@vertigis/web/ui"; 4 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 5 | import type { 6 | MapWidgetConstructor, 7 | MapWidgetProps, 8 | } from "@vertigis/web/ui/esriUtils"; 9 | import { useEffect, useState } from "react"; 10 | import type { ReactElement } from "react"; 11 | 12 | import type DaylightModel from "./DaylightModel"; 13 | 14 | export type DaylightWidgetProps = MapWidgetProps; 15 | 16 | const DaylightWidgetWrapper = createEsriMapWidget( 17 | DaylightWidget as MapWidgetConstructor, 18 | true, 19 | true 20 | ); 21 | 22 | const StyledDaylightWrapper = styled(DaylightWidgetWrapper)({ 23 | "--calcite-ui-text-1": "var(--primaryForeground)", 24 | "& .esri-widget": { 25 | width: "100%", 26 | }, 27 | }); 28 | 29 | export default function Daylight(props: DaylightWidgetProps): ReactElement { 30 | const { model } = props; 31 | const { map } = model; 32 | const [widget, setWidget] = useState(); 33 | 34 | useWatchAndRerender(map, ["map", "viewMode"]); 35 | useWatchAndRerender(model, [ 36 | "title", 37 | "datePicker", 38 | "playButtons", 39 | "shadowsToggle", 40 | "timezone", 41 | "dateOrSeason", 42 | "playSpeedMultiplier", 43 | ]); 44 | useEffect(() => { 45 | if (!widget) { 46 | return; 47 | } 48 | widget.visibleElements = { 49 | datePicker: model.datePicker, 50 | playButtons: model.playButtons, 51 | shadowsToggle: model.shadowsToggle, 52 | timezone: model.timezone, 53 | }; 54 | widget.label = model.title; 55 | widget.dateOrSeason = model.dateOrSeason; 56 | widget.playSpeedMultiplier = model.playSpeedMultiplier; 57 | }, [ 58 | widget, 59 | model, 60 | model.title, 61 | model.datePicker, 62 | model.playButtons, 63 | model.shadowsToggle, 64 | model.timezone, 65 | model.dateOrSeason, 66 | model.playSpeedMultiplier, 67 | ]); 68 | 69 | if (map.viewMode === "map") { 70 | return null; 71 | } 72 | 73 | return ( 74 | 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | 27 | Cypress.Commands.add("getViewer", { prevSubject: "optional" }, (subject) => 28 | ( 29 | (subject && 30 | cy 31 | .wrap(subject, { log: false }) 32 | .find('iframe[name="viewer"]', { log: false })) || 33 | cy.get('iframe[name="viewer"]', { log: false }) 34 | ) 35 | .its("0.contentDocument.body", { log: false }) 36 | .should("not.be.empty") 37 | .then((result) => cy.wrap(result, { log: false })) 38 | ); 39 | 40 | Cypress.Commands.add("getViewerParent", () => 41 | cy 42 | .get('iframe[data-cy="viewer-outer-frame"]', { log: false }) 43 | .its("0.contentDocument.body", { log: false }) 44 | .should("not.be.empty") 45 | .then((subject) => cy.wrap(subject, { log: false })) 46 | ); 47 | 48 | Cypress.Commands.add("getNestedViewer", () => cy.getViewerParent().getViewer()); 49 | 50 | Cypress.Commands.add("getMap", { prevSubject: "element" }, (subject, id) => { 51 | const selector = id ? `[gcx-id="${id}"]` : ".gcx-map"; 52 | 53 | return cy 54 | .wrap(subject, { log: false }) 55 | .find(selector, { log: false, timeout: 30000 }) 56 | .and((el) => { 57 | const mapId = el[0].getAttribute("data-layout-id"); 58 | const win = el[0].ownerDocument?.defaultView; 59 | const map = win.__maps?.[mapId] || win.__scenes?.[mapId]; 60 | 61 | // Wait for global map data to be available once initialized 62 | expect(!!map, "expect map to be created").to.be.true; 63 | expect(map.ready, "expect map to be ready").to.be.true; 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ShadowCast/ShadowCastModel.ts: -------------------------------------------------------------------------------- 1 | import type { MapModel } from "@vertigis/web/mapping"; 2 | import type { 3 | PropertyDefs, 4 | ComponentModelProperties, 5 | } from "@vertigis/web/models"; 6 | import { 7 | ComponentModelBase, 8 | serializable, 9 | importModel, 10 | } from "@vertigis/web/models"; 11 | 12 | type VisualizationType = "threshold" | "duration" | "discrete"; 13 | 14 | interface ShadowCastModelProperties extends ComponentModelProperties { 15 | timeRangeSlider?: boolean; 16 | timezone?: boolean; 17 | datePicker?: boolean; 18 | visualizationOptions?: boolean; 19 | colorPicker?: boolean; 20 | tooltip?: boolean; 21 | visualizationType?: VisualizationType; 22 | } 23 | 24 | @serializable 25 | export default class ShadowCastModel extends ComponentModelBase { 26 | @importModel("map-extension") 27 | map: MapModel | undefined; 28 | 29 | timeRangeSlider?: boolean; 30 | timezone?: boolean; 31 | datePicker?: boolean; 32 | visualizationOptions?: boolean; 33 | colorPicker?: boolean; 34 | tooltip?: boolean; 35 | visualizationType?: VisualizationType; 36 | 37 | protected override _getSerializableProperties(): PropertyDefs { 38 | const props = super._getSerializableProperties(); 39 | return { 40 | ...props, 41 | timeRangeSlider: { 42 | serializeModes: ["initial"], 43 | default: true, 44 | }, 45 | timezone: { 46 | serializeModes: ["initial"], 47 | default: true, 48 | }, 49 | datePicker: { 50 | serializeModes: ["initial"], 51 | default: true, 52 | }, 53 | visualizationOptions: { 54 | serializeModes: ["initial"], 55 | default: true, 56 | }, 57 | colorPicker: { 58 | serializeModes: ["initial"], 59 | default: true, 60 | }, 61 | visualizationType: { 62 | serializeModes: ["initial"], 63 | default: "threshold", 64 | }, 65 | tooltip: { 66 | serializeModes: ["initial"], 67 | default: true, 68 | }, 69 | title: { 70 | ...this._toPropertyDef(props.title), 71 | default: "language-web-incubator-shadow-cast-3d-title", 72 | }, 73 | icon: { 74 | ...this._toPropertyDef(props.icon), 75 | default: "map-3rd-party", 76 | }, 77 | }; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /libraries/timeslider/src/components/TimeSlider/TimeSlider.tsx: -------------------------------------------------------------------------------- 1 | import type WebMap from "@arcgis/core/WebMap"; 2 | import EsriTimeSlider from "@arcgis/core/widgets/TimeSlider"; 3 | import { useWatch, useWatchAndRerender } from "@vertigis/web/ui"; 4 | import type { MapWidgetProps } from "@vertigis/web/ui/esriUtils"; 5 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 6 | import type { ComponentType, FC } from "react"; 7 | import React, { useCallback } from "react"; 8 | 9 | import type TimeSliderModel from "./TimeSliderModel"; 10 | 11 | import "./TimeSlider.css"; 12 | 13 | export type TimeSliderProps = MapWidgetProps; 14 | const TimeSliderWrapper: ComponentType = createEsriMapWidget< 15 | TimeSliderModel, 16 | EsriTimeSlider 17 | >(EsriTimeSlider, undefined, true); 18 | 19 | const TimeSlider: FC = (props) => { 20 | const { model, ...other } = props; 21 | 22 | // Any time the Time Slider model properties change, we need to re-render 23 | // the component so our UI is consistent, as well as set the Esri widget 24 | // configuration. 25 | useWatchAndRerender(model, ["map.map"]); 26 | useWatch( 27 | model, 28 | "map.map", 29 | (newMap: WebMap) => { 30 | // Only update the model if the web map has changed. 31 | if (model.widget) { 32 | model.widget.stop(); 33 | // Check the web map for existing time slider config 34 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 35 | model.updateTimeSliderWidget(model.widget, newMap); 36 | } 37 | }, 38 | [model.map.view] 39 | ); 40 | 41 | const onWidgetCreated = useCallback( 42 | (widget: EsriTimeSlider) => { 43 | const map = model.map; 44 | 45 | // Synchronize values from model. 46 | if (!map?.map) { 47 | return; 48 | } 49 | 50 | const webMap = map.map as WebMap; 51 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 52 | model.updateTimeSliderWidget(widget, webMap); 53 | }, 54 | [model] 55 | ); 56 | const onWidgetDestroyed = useCallback(() => { 57 | // Remove the time extent filter on the map view. 58 | model.map.view.timeExtent = null; 59 | model.widget = null; 60 | }, [model]); 61 | 62 | return ( 63 | 70 | ); 71 | }; 72 | 73 | export default TimeSlider; 74 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ShadowCast/ShadowCast.tsx: -------------------------------------------------------------------------------- 1 | import type Accessor from "@arcgis/core/core/Accessor"; 2 | import ShadowCastWidget from "@arcgis/core/widgets/ShadowCast"; 3 | import { useWatchAndRerender, styled } from "@vertigis/web/ui"; 4 | import type { 5 | MapWidgetConstructor, 6 | MapWidgetProps, 7 | } from "@vertigis/web/ui/esriUtils"; 8 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 9 | import { useEffect, useState } from "react"; 10 | import type { ReactElement } from "react"; 11 | 12 | import type ShadowCastModel from "./ShadowCastModel"; 13 | 14 | export type ShadowCastModelWidgetProps = MapWidgetProps< 15 | ShadowCastModel & Accessor 16 | >; 17 | 18 | const ShadowCastWrapper = createEsriMapWidget( 19 | ShadowCastWidget as MapWidgetConstructor, 20 | true, 21 | true 22 | ); 23 | 24 | const StyledShadowCastWrapper = styled(ShadowCastWrapper)( 25 | ({ theme: { typography } }) => ({ 26 | "--calcite-ui-text-1": "var(--primaryForeground)", 27 | "--calcite-font-size--2": typography.fontSize, 28 | "& .esri-widget": { 29 | width: "100%", 30 | }, 31 | "& .calcite-select": { 32 | "--calcite-select-font-size": typography.fontSize, 33 | }, 34 | }) 35 | ); 36 | 37 | export default function ShadowCast( 38 | props: ShadowCastModelWidgetProps 39 | ): ReactElement { 40 | const { model } = props; 41 | const { map } = model; 42 | const [widget, setWidget] = useState(); 43 | 44 | useWatchAndRerender(map, ["map", "viewMode"]); 45 | useWatchAndRerender(model, [ 46 | "title", 47 | "timeRangeSlider", 48 | "timezone", 49 | "datePicker", 50 | "visualizationOptions", 51 | "colorPicker", 52 | "tooltip", 53 | "visualizationType", 54 | ]); 55 | useEffect(() => { 56 | if (!widget) { 57 | return; 58 | } 59 | widget.visibleElements = { 60 | timeRangeSlider: model.timeRangeSlider, 61 | timezone: model.timezone, 62 | datePicker: model.datePicker, 63 | visualizationOptions: model.visualizationOptions, 64 | colorPicker: model.colorPicker, 65 | tooltip: model.tooltip, 66 | }; 67 | widget.label = model.title; 68 | if (widget.viewModel) { 69 | widget.viewModel.visualizationType = model.visualizationType; 70 | } 71 | }, [ 72 | widget, 73 | widget?.viewModel, 74 | model, 75 | model.title, 76 | model.timeRangeSlider, 77 | model.timezone, 78 | model.datePicker, 79 | model.visualizationOptions, 80 | model.colorPicker, 81 | model.tooltip, 82 | model.visualizationType, 83 | ]); 84 | 85 | if (map.viewMode === "map") { 86 | return null; 87 | } 88 | 89 | return ( 90 | 94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /libraries/mapillary/src/components/Mapillary/designer.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ApplyDesignerSettingsCallback, 3 | ComponentModelDesignerSettings, 4 | DesignerSettings, 5 | GetDesignerSettingsCallback, 6 | GetDesignerSettingsSchemaCallback, 7 | Setting, 8 | SettingsSchema, 9 | } from "@vertigis/web/designer"; 10 | import { 11 | applyComponentModelDesignerSettings, 12 | getComponentModelDesignerSettings, 13 | getComponentModelDesignerSettingsSchema, 14 | } from "@vertigis/web/designer"; 15 | 16 | import type MapillaryModel from "./MapillaryModel"; 17 | 18 | export interface MapillarySettings extends ComponentModelDesignerSettings { 19 | mapillaryKey: string; 20 | searchRadius: number; 21 | defaultScale: number; 22 | startSynced: boolean; 23 | } 24 | 25 | export type SettingsMap = DesignerSettings; 26 | 27 | export const applySettings: ApplyDesignerSettingsCallback< 28 | MapillaryModel, 29 | SettingsMap 30 | > = async (args) => { 31 | const { model, settings } = args; 32 | await applyComponentModelDesignerSettings(args); 33 | model.assignProperties(settings); 34 | }; 35 | 36 | export const getSettings: GetDesignerSettingsCallback< 37 | MapillaryModel, 38 | SettingsMap 39 | > = async (args) => { 40 | const { model } = args; 41 | const { mapillaryKey, searchRadius, defaultScale, startSynced } = model; 42 | return { 43 | ...(await getComponentModelDesignerSettings(args)), 44 | mapillaryKey, 45 | searchRadius, 46 | defaultScale, 47 | startSynced, 48 | }; 49 | }; 50 | 51 | export const getSettingsSchema: GetDesignerSettingsSchemaCallback< 52 | MapillaryModel, 53 | SettingsMap 54 | > = async (args) => { 55 | const baseSchema = await getComponentModelDesignerSettingsSchema(args); 56 | (baseSchema.settings[0].settings as Setting[]) = ( 57 | baseSchema.settings[0].settings as Setting[] 58 | ).concat([ 59 | { 60 | id: "mapillaryKey", 61 | type: "text", 62 | description: "language-designer-mapillary-key-description", 63 | displayName: "language-designer-mapillary-key-title", 64 | }, 65 | { 66 | id: "searchRadius", 67 | type: "number", 68 | description: 69 | "language-designer-mapillary-search-radius-description", 70 | displayName: "language-designer-mapillary-search-radius-title", 71 | }, 72 | { 73 | id: "defaultScale", 74 | type: "number", 75 | description: 76 | "language-designer-mapillary-default-scale-description", 77 | displayName: "language-designer-mapillary-default-scale-title", 78 | }, 79 | { 80 | id: "startSynced", 81 | type: "checkbox", 82 | description: "language-designer-mapillary-start-synced-description", 83 | displayName: "language-designer-mapillary-start-synced-title", 84 | }, 85 | ]); 86 | const schema: SettingsSchema = { 87 | ...baseSchema, 88 | settings: [...baseSchema.settings], 89 | }; 90 | return schema; 91 | }; 92 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/components/EmbeddedExplorer/EagleView.tsx: -------------------------------------------------------------------------------- 1 | import type { LayoutElementProperties } from "@vertigis/web/components"; 2 | import { LayoutElement } from "@vertigis/web/components"; 3 | import React, { useEffect } from "react"; 4 | import ReactDOM from "react-dom"; 5 | 6 | import type { EagleViewModel } from "."; 7 | import { createEagleViewScript, type WindowWithEv } from "./embedded-explorer"; 8 | 9 | const selectorsToTrim = ["h1", "h2", "h3", "h4", "h5", "h6", "body", "label"]; 10 | 11 | export default function EagleView( 12 | props: LayoutElementProperties 13 | ): React.ReactElement { 14 | const { model } = props; 15 | 16 | useEffect(() => { 17 | const window = document.defaultView as WindowWithEv; 18 | const mountEmbeddedExplorer = () => { 19 | const explorer = new window.ev.EmbeddedExplorer(); 20 | 21 | model.e3 = explorer.mount("eagle-view-map", { 22 | apiKey: model.apiKey, 23 | view: model.getPointForEagleView(true), 24 | }); 25 | 26 | /** 27 | * EagleView pollutes the page with inline styles that override 28 | * global styles. This is a workaround to remove those styles. 29 | */ 30 | const newStyles = [...document.styleSheets].filter( 31 | (style) => !previousStyle.includes(style) 32 | ); 33 | newStyles.forEach((style) => { 34 | const rules = [...style.cssRules]; 35 | for (let index = rules.length - 1; index >= 0; index--) { 36 | const rule = rules[index]; 37 | if ( 38 | rule instanceof CSSStyleRule && 39 | selectorsToTrim.some( 40 | (selector) => rule.selectorText === selector 41 | ) 42 | ) { 43 | style.deleteRule(index); 44 | } 45 | } 46 | }); 47 | }; 48 | 49 | const evScriptElt = document.getElementById("embedded-explorer-widget"); 50 | const previousStyle = [...document.styleSheets]; 51 | if (evScriptElt == null) { 52 | // This is required to get the EagleView script running. 53 | window.define("React", React); 54 | window.define("ReactDOM", ReactDOM); 55 | window.define("react-virtualized", undefined); 56 | window.define("recharts", undefined); 57 | const embedded_explorer_script = createEagleViewScript(); 58 | 59 | // We need to force the necessary modules to load. 60 | embedded_explorer_script.addEventListener("load", async () => { 61 | window.require(["lib"], mountEmbeddedExplorer); 62 | }); 63 | 64 | document.body.appendChild(embedded_explorer_script); 65 | } else { 66 | requestAnimationFrame(() => mountEmbeddedExplorer()); 67 | } 68 | 69 | return () => { 70 | model.e3 = undefined; 71 | }; 72 | }, [model]); 73 | 74 | return ( 75 | 76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at eric.parsons@vertigis.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Slice/Slice.tsx: -------------------------------------------------------------------------------- 1 | import type Accessor from "@arcgis/core/core/Accessor"; 2 | import SliceWidget from "@arcgis/core/widgets/Slice"; 3 | import { useWatchAndRerender } from "@vertigis/web/ui"; 4 | import Link from "@vertigis/web/ui/Link"; 5 | import type { 6 | MapWidgetConstructor, 7 | MapWidgetProps, 8 | } from "@vertigis/web/ui/esriUtils"; 9 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 10 | import { useEffect, useState } from "react"; 11 | import type { ReactElement } from "react"; 12 | 13 | import type SliceModel from "./SliceModel"; 14 | 15 | export type SliceWidgetProps = MapWidgetProps; 16 | 17 | const SliceWidgetWrapper = createEsriMapWidget( 18 | SliceWidget as MapWidgetConstructor, 19 | true, 20 | true 21 | ); 22 | 23 | export default function Slice(props: SliceWidgetProps): ReactElement { 24 | const { model } = props; 25 | const { map } = model; 26 | const [widget, setWidget] = useState(); 27 | 28 | useWatchAndRerender(map, ["map", "viewMode"]); 29 | useWatchAndRerender(model, ["title", "tiltEnabled"]); 30 | useWatchAndRerender(widget?.viewModel, "state"); 31 | 32 | useEffect(() => { 33 | if (!widget) { 34 | return; 35 | } 36 | 37 | widget.label = model.title; 38 | if (widget.viewModel) { 39 | widget.viewModel.tiltEnabled = model.tiltEnabled; 40 | } 41 | }, [map, model.tiltEnabled, model.title, widget, widget?.viewModel]); 42 | 43 | useEffect(() => { 44 | if (!widget?.container) { 45 | return undefined; 46 | } 47 | 48 | const observer = new MutationObserver((results) => { 49 | results.forEach((mutation) => { 50 | const buttonAdded = !![...mutation.addedNodes.values()].find( 51 | (node) => { 52 | const className: string = node["className"] ?? ""; 53 | return className.includes("esri-slice__cancel-button"); 54 | } 55 | ); 56 | if (buttonAdded) { 57 | map["_suppressMapClick"] = true; 58 | } 59 | 60 | const buttonRemoved = !![ 61 | ...mutation.removedNodes.values(), 62 | ].find((node) => { 63 | const className: string = node["className"] ?? ""; 64 | return className.includes("esri-slice__cancel-button"); 65 | }); 66 | if (buttonRemoved) { 67 | map["_suppressMapClick"] = false; 68 | } 69 | }); 70 | }); 71 | observer.observe(widget.container, { 72 | subtree: true, 73 | childList: true, 74 | }); 75 | 76 | return () => { 77 | observer.disconnect(); 78 | map["_suppressMapClick"] = false; 79 | }; 80 | }, [widget, map]); 81 | 82 | if (map.viewMode === "map") { 83 | return null; 84 | } 85 | 86 | const widgetIsSlicing = 87 | widget?.viewModel?.state === "sliced" || 88 | widget?.viewModel?.state === "slicing"; 89 | 90 | return ( 91 | 96 | {widgetIsSlicing && ( 97 | widget.viewModel.clear()} 100 | > 101 | language-web-incubator-common-clear 102 | 103 | )} 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ElevationProfile/ElevationProfileModel.ts: -------------------------------------------------------------------------------- 1 | import type { MapModel } from "@vertigis/web/mapping"; 2 | import type { 3 | PropertyDefs, 4 | ComponentModelProperties, 5 | } from "@vertigis/web/models"; 6 | import { 7 | ComponentModelBase, 8 | serializable, 9 | importModel, 10 | } from "@vertigis/web/models"; 11 | export interface ElevationProfileModelProperties 12 | extends ComponentModelProperties { 13 | legend?: boolean; 14 | chart?: boolean; 15 | clearButton?: boolean; 16 | settingsButton?: boolean; 17 | sketchButton?: boolean; 18 | selectButton?: boolean; 19 | uniformChartScalingToggle?: boolean; 20 | profileLineGround?: boolean; 21 | profileLineInput?: boolean; 22 | profileLineView?: boolean; 23 | profileLineGroundColor?: string; 24 | profileLineInputColor?: string; 25 | profileLineViewColor?: string; 26 | } 27 | 28 | @serializable 29 | export default class ElevationProfileModel extends ComponentModelBase { 30 | @importModel("map-extension") 31 | map: MapModel | undefined; 32 | 33 | legend: boolean; 34 | chart: boolean; 35 | clearButton: boolean; 36 | settingsButton: boolean; 37 | sketchButton: boolean; 38 | selectButton: boolean; 39 | uniformChartScalingToggle: boolean; 40 | profileLineGround: boolean; 41 | profileLineInput: boolean; 42 | profileLineView: boolean; 43 | profileLineGroundColor?: string; 44 | profileLineInputColor?: string; 45 | profileLineViewColor?: string; 46 | 47 | protected override _getSerializableProperties(): PropertyDefs { 48 | const props = super._getSerializableProperties(); 49 | return { 50 | ...props, 51 | title: { 52 | ...this._toPropertyDef(props.title), 53 | default: "language-web-incubator-elevation-profile-3d-title", 54 | }, 55 | icon: { 56 | ...this._toPropertyDef(props.icon), 57 | default: "map-3rd-party", 58 | }, 59 | legend: { 60 | serializeModes: ["initial"], 61 | default: true, 62 | }, 63 | chart: { 64 | serializeModes: ["initial"], 65 | default: true, 66 | }, 67 | clearButton: { 68 | serializeModes: ["initial"], 69 | default: true, 70 | }, 71 | settingsButton: { 72 | serializeModes: ["initial"], 73 | default: true, 74 | }, 75 | sketchButton: { 76 | serializeModes: ["initial"], 77 | default: true, 78 | }, 79 | selectButton: { 80 | serializeModes: ["initial"], 81 | default: true, 82 | }, 83 | uniformChartScalingToggle: { 84 | serializeModes: ["initial"], 85 | default: true, 86 | }, 87 | profileLineGround: { 88 | serializeModes: ["initial"], 89 | default: true, 90 | }, 91 | profileLineInput: { 92 | serializeModes: ["initial"], 93 | default: true, 94 | }, 95 | profileLineView: { 96 | serializeModes: ["initial"], 97 | default: true, 98 | }, 99 | profileLineGroundColor: { 100 | serializeModes: ["initial"], 101 | default: "#ff7f00", 102 | }, 103 | profileLineInputColor: { 104 | serializeModes: ["initial"], 105 | default: "#00c8c8", 106 | }, 107 | profileLineViewColor: { 108 | serializeModes: ["initial"], 109 | default: "#cf4ccf", 110 | }, 111 | }; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ElevationProfile/ElevationProfile.tsx: -------------------------------------------------------------------------------- 1 | import type Accessor from "@arcgis/core/core/Accessor"; 2 | import ElevationProfileWidget from "@arcgis/core/widgets/ElevationProfile"; 3 | import ElevationProfileLineGround from "@arcgis/core/widgets/ElevationProfile/ElevationProfileLineGround"; 4 | import ElevationProfileLineInput from "@arcgis/core/widgets/ElevationProfile/ElevationProfileLineInput"; 5 | import ElevationProfileLineView from "@arcgis/core/widgets/ElevationProfile/ElevationProfileLineView"; 6 | import { useWatchAndRerender } from "@vertigis/web/ui"; 7 | import type { MapWidgetProps } from "@vertigis/web/ui/esriUtils"; 8 | import { createEsriMapWidget } from "@vertigis/web/ui/esriUtils"; 9 | import { useEffect, useState } from "react"; 10 | import type { ReactElement } from "react"; 11 | 12 | import type ElevationProfileModel from "./ElevationProfileModel"; 13 | 14 | export type ElevationProfileWidgetProps = MapWidgetProps< 15 | ElevationProfileModel & Accessor 16 | >; 17 | 18 | const ElevationProfileWidgetWrapper = createEsriMapWidget< 19 | ElevationProfileModel & Accessor, 20 | ElevationProfileWidget 21 | >(ElevationProfileWidget, true, true); 22 | 23 | export default function ElevationProfile( 24 | props: ElevationProfileWidgetProps 25 | ): ReactElement { 26 | const { model } = props; 27 | const { map } = model; 28 | const [widget, setWidget] = useState(); 29 | 30 | useWatchAndRerender(map, ["map", "viewMode"]); 31 | useWatchAndRerender(model, [ 32 | "title", 33 | "legend", 34 | "chart", 35 | "clearButton", 36 | "settingsButton", 37 | "sketchButton", 38 | "selectButton", 39 | "uniformChartScalingToggle", 40 | "profileLineGround", 41 | "profileLineInput", 42 | "profileLineView", 43 | "profileLineGroundColor", 44 | "profileLineInputColor", 45 | "profileLineViewColor", 46 | ]); 47 | useEffect(() => { 48 | if (!widget) { 49 | return; 50 | } 51 | widget.label = model.title; 52 | widget.visibleElements = { 53 | legend: model.legend, 54 | chart: model.chart, 55 | clearButton: model.clearButton, 56 | settingsButton: model.settingsButton, 57 | sketchButton: model.sketchButton, 58 | selectButton: model.selectButton, 59 | uniformChartScalingToggle: model.uniformChartScalingToggle, 60 | }; 61 | widget.profiles.removeAll(); 62 | if (model.profileLineGround) { 63 | widget.profiles.add( 64 | new ElevationProfileLineGround({ 65 | color: model.profileLineGroundColor, 66 | }) 67 | ); 68 | } 69 | if (model.profileLineInput) { 70 | widget.profiles.add( 71 | new ElevationProfileLineInput({ 72 | color: model.profileLineInputColor, 73 | }) 74 | ); 75 | } 76 | if (model.profileLineView) { 77 | widget.profiles.add( 78 | new ElevationProfileLineView({ 79 | color: model.profileLineViewColor, 80 | }) 81 | ); 82 | } 83 | }, [ 84 | widget, 85 | model.title, 86 | model.profileLineGround, 87 | model.profileLineInput, 88 | model.profileLineView, 89 | model.legend, 90 | model.chart, 91 | model.clearButton, 92 | model.settingsButton, 93 | model.sketchButton, 94 | model.selectButton, 95 | model.uniformChartScalingToggle, 96 | model.profileLineGroundColor, 97 | model.profileLineInputColor, 98 | model.profileLineViewColor, 99 | ]); 100 | 101 | if (map.viewMode === "map") { 102 | return null; 103 | } 104 | 105 | return ( 106 | 110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VertiGIS Studio Web Component Incubator 2 | 3 | ![CI](https://github.com/vertigis/vertigis-web-incubator/workflows/CI/badge.svg) 4 | 5 | View the component libraries live samples at [https://vertigis-web-incubator.netlify.app](https://vertigis-web-incubator.netlify.app). 6 | 7 | This project includes a collection of [VertiGIS Studio Web](https://vertigisstudio.com/products/vertigis-studio-web/) component libraries built using the [VertiGIS Studio Web SDK](https://developers.vertigisstudio.com/docs/web/sdk-overview/). Check out the [live samples](https://vertigis-web-incubator.netlify.app/) for an easy way to preview and download the component libraries in your browser without needing to run the project locally. The source for each library is located within the [libraries](libraries) directory of this project. 8 | 9 | **These component libraries are experimental and provided free of charge without warranty under the [MIT license](LICENSE).** We will do our best to keep them functional and up-to-date. If you need assistance to modify or improve these libraries, feel free to reach out to our [professional services department](https://vertigisstudio.com/support-services/professional-services/). 10 | 11 | ## Using the component libraries in your app 12 | 13 | The easiest way to use these component libraries in your VertiGIS Studio Web apps, is by using the _Download this library_ link in the [live samples viewer](https://vertigis-web-incubator.netlify.app/). Once the component library you'd like to use has been downloaded, you can [follow the steps](https://developers.vertigisstudio.com/docs/web/sdk-deployment#uploading-custom-code-to-an-app) on the VertiGIS Studio Developer Center to use the _Upload Library_ feature in the VertiGIS Studio Web Designer. 14 | 15 | ## Running the libraries locally 16 | 17 | You will need to install the latest LTS version of [Node.js](https://nodejs.org/). 18 | 19 | ### Installing dependencies 20 | 21 | You can install the dependencies for all sample projects by running [`yarn`](https://yarnpkg.com/) at the root of this repository. The easiest way to install and update yarn is to run `npm install -g yarn`. 22 | 23 | Alternatively you may install the dependencies for a single sample project by running `yarn` or `npm install` in the root of a sample directory. 24 | 25 | ### Running a library 26 | 27 | Run `yarn start` or `npm start` within the root of a sample directory. For example you can run the `mapillary` sample by running `yarn start` within the [libraries/mapillary](libraries/mapillary) directory. This will launch the VertiGIS Studio Web SDK development server. 28 | 29 | ### Creating a new library 30 | 31 | Each sample follows the same pattern as the VertiGIS Studio Web SDK. The easiest way to create a new sample is to copy an existing sample directory, and rename the `name` property in the `package.json` of your sample to suit. Once created you will need to add your sample to the samples viewer `libraries` array in [the viewer source](viewer/src/App.tsx) to have it show up in the list of libraries. 32 | 33 | ### Testing 34 | 35 | The tests for each sample are located in the [cypress/integration](cypress/integration) directory. 36 | 37 | The libraries will need to be built prior to running the test using `yarn build:libraries`. You can run the tests in interactive watch mode using `yarn test:watch`, or run all of the tests using `yarn test` from the root of this project. 38 | 39 | ### Running the Samples Viewer Locally 40 | 41 | To run the samples viewer, first build all of the sample projects by running `yarn build:libraries` in the root of this project, followed by running `yarn start` to start the viewer. 42 | 43 | ## Documentation 44 | 45 | Before diving into the VertiGIS Studio Web SDK, be sure to check out our [Developer Center](https://developers.vertigisstudio.com/docs/web/overview/) to learn the various concepts of building and configuring VertiGIS Studio Web applications. There is a surprising amount that can be accomplished through [layout](https://developers.vertigisstudio.com/docs/web/configuration-layout-getting-started/), [app config](https://developers.vertigisstudio.com/docs/web/configuration-app-config-getting-started/), and [VertiGIS Studio Workflow](https://vertigisstudio.com/products/vertigis-studio-workflow/) which can be [configured in the app config](https://developers.vertigisstudio.com/docs/web/tutorial-run-workflow-app-config/) without even needing to use the SDK! 46 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/Daylight/designer.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ApplyDesignerSettingsCallback, 3 | ComponentModelDesignerSettings, 4 | DesignerSettings, 5 | GetDesignerSettingsCallback, 6 | GetDesignerSettingsSchemaCallback, 7 | Setting, 8 | SettingsSchema, 9 | } from "@vertigis/web/designer"; 10 | import { 11 | applyComponentModelDesignerSettings, 12 | getComponentModelDesignerSettings, 13 | getComponentModelDesignerSettingsSchema, 14 | } from "@vertigis/web/designer"; 15 | 16 | import type DaylightModel from "./DaylightModel"; 17 | 18 | type DateOrSeason = "date" | "season"; 19 | 20 | export interface DaylightSettings extends ComponentModelDesignerSettings { 21 | playButtons: boolean; 22 | shadowsToggle: boolean; 23 | datePicker: boolean; 24 | timezone: boolean; 25 | dateOrSeason: DateOrSeason; 26 | playSpeedMultiplier: number; 27 | } 28 | 29 | export type SettingsMap = DesignerSettings; 30 | 31 | export const applySettings: ApplyDesignerSettingsCallback< 32 | DaylightModel, 33 | SettingsMap 34 | > = async (args) => { 35 | const { model, settings } = args; 36 | await applyComponentModelDesignerSettings(args); 37 | model.assignProperties(settings); 38 | }; 39 | 40 | export const getSettings: GetDesignerSettingsCallback< 41 | DaylightModel, 42 | SettingsMap 43 | > = async (args) => { 44 | const { model } = args; 45 | const { 46 | playButtons, 47 | shadowsToggle, 48 | datePicker, 49 | timezone, 50 | dateOrSeason, 51 | playSpeedMultiplier, 52 | } = model; 53 | return { 54 | ...(await getComponentModelDesignerSettings(args)), 55 | playButtons, 56 | shadowsToggle, 57 | datePicker, 58 | timezone, 59 | dateOrSeason, 60 | playSpeedMultiplier, 61 | }; 62 | }; 63 | 64 | export const getSettingsSchema: GetDesignerSettingsSchemaCallback< 65 | DaylightModel, 66 | SettingsMap 67 | > = async (args) => { 68 | const baseSchema = await getComponentModelDesignerSettingsSchema(args); 69 | (baseSchema.settings[0].settings as Setting[]) = ( 70 | baseSchema.settings[0].settings as Setting[] 71 | ).concat([ 72 | { 73 | id: "playButtons", 74 | type: "checkbox", 75 | description: 76 | "language-designer-3d-tools-daylight-play-buttons-description", 77 | displayName: 78 | "language-designer-3d-tools-daylight-play-buttons-title", 79 | }, 80 | { 81 | id: "shadowsToggle", 82 | type: "checkbox", 83 | description: 84 | "language-designer-3d-tools-daylight-shadows-toggle-description", 85 | displayName: 86 | "language-designer-3d-tools-daylight-shadows-toggle-title", 87 | }, 88 | { 89 | id: "datePicker", 90 | type: "checkbox", 91 | description: 92 | "language-designer-3d-tools-daylight-date-picker-description", 93 | displayName: 94 | "language-designer-3d-tools-daylight-date-picker-title", 95 | }, 96 | { 97 | id: "timezone", 98 | type: "checkbox", 99 | description: 100 | "language-designer-3d-tools-daylight-timezone-description", 101 | displayName: "language-designer-3d-tools-daylight-timezone-title", 102 | }, 103 | { 104 | id: "playSpeedMultiplier", 105 | type: "number", 106 | description: 107 | "language-designer-3d-tools-daylight-play-speed-multiplier-description", 108 | displayName: 109 | "language-designer-3d-tools-daylight-play-speed-multiplier-title", 110 | isRequired: true, 111 | min: 0, 112 | }, 113 | { 114 | id: "dateOrSeason", 115 | type: "select", 116 | description: 117 | "language-designer-3d-tools-daylight-date-or-season-description", 118 | displayName: 119 | "language-designer-3d-tools-daylight-date-or-season-title", 120 | values: [ 121 | { 122 | displayName: 123 | "language-designer-3d-tools-daylight-date-or-season-date", 124 | value: "date", 125 | }, 126 | { 127 | displayName: 128 | "language-designer-3d-tools-daylight-date-or-season-season", 129 | value: "season", 130 | }, 131 | ], 132 | }, 133 | ]); 134 | const schema: SettingsSchema = { 135 | ...baseSchema, 136 | settings: [...baseSchema.settings], 137 | }; 138 | return schema; 139 | }; 140 | -------------------------------------------------------------------------------- /libraries/mapillary/src/components/Mapillary/Mapillary.tsx: -------------------------------------------------------------------------------- 1 | import type { LayoutElementProperties } from "@vertigis/web/components"; 2 | import { LayoutElement } from "@vertigis/web/components"; 3 | import ButtonGroup from "@vertigis/web/ui/ButtonGroup"; 4 | import IconButton from "@vertigis/web/ui/IconButton"; 5 | // Import the necessary CSS for the Mapillary viewer to be styled correctly. 6 | import "mapillary-js/dist/mapillary.css"; 7 | import "./Mapillary.css"; 8 | import { useWatchAndRerender } from "@vertigis/web/ui/hooks"; 9 | import CenterMap from "@vertigis/web/ui/icons/CenterMap"; 10 | import MapSyncOff from "@vertigis/web/ui/icons/MapSyncOff"; 11 | import MapSyncOn from "@vertigis/web/ui/icons/MapSyncOn"; 12 | import clsx from "clsx"; 13 | import { Viewer, TransitionMode } from "mapillary-js"; 14 | import type { ReactElement } from "react"; 15 | import { useEffect, useRef } from "react"; 16 | 17 | import type MapillaryModel from "./MapillaryModel"; 18 | 19 | export default function Mapillary( 20 | props: LayoutElementProperties 21 | ): ReactElement { 22 | const { model } = props; 23 | const mlyRootEl = useRef(); 24 | 25 | const onSyncToggle = () => 26 | (model.synchronizePosition = !model.synchronizePosition); 27 | const onRecenter = () => model.recenter(); 28 | 29 | useWatchAndRerender(model, "synchronizePosition"); 30 | 31 | useEffect(() => { 32 | const mapillary = new Viewer({ 33 | imageId: "2935399116683438", 34 | container: mlyRootEl.current, 35 | accessToken: model.mapillaryKey, 36 | component: { 37 | // Initialize the view immediately without user interaction. 38 | cover: false, 39 | }, 40 | }); 41 | mapillary.setTransitionMode(TransitionMode.Instantaneous); 42 | model.mapillary = mapillary; 43 | 44 | const handleViewportResize = () => { 45 | mapillary.resize(); 46 | }; 47 | 48 | // Viewer size is dynamic so resize should be called every time the viewport size changes. 49 | const resizeObserver = new ResizeObserver(handleViewportResize); 50 | const viewportDiv = mlyRootEl.current; 51 | resizeObserver.observe(viewportDiv); 52 | 53 | // These handlers are necessary as Mapillary cannot handle the many 54 | // update events caused by dragging the location marker. We'll only 55 | // handle the last one fired when the mouse button is released. 56 | const mouseDownHandler = (): void => 57 | (model.currentMarkerPosition = undefined); 58 | const mouseUpHandler = (): void => { 59 | if ( 60 | model.mapillary?.isNavigable && 61 | !model.updating && 62 | model.currentMarkerPosition 63 | ) { 64 | model.updating = true; 65 | 66 | // const { latitude, longitude } = model.currentMarkerPosition; 67 | model.currentMarkerPosition = undefined; 68 | 69 | // See comment in MapillaryModel.ts 70 | // void model.moveCloseToPosition(latitude, longitude); 71 | } 72 | }; 73 | document.body.addEventListener("mousedown", mouseDownHandler); 74 | document.body.addEventListener("mouseup", mouseUpHandler); 75 | 76 | // Clean up when this component is unmounted from the DOM. 77 | return () => { 78 | // Remove listeners. 79 | document.body.removeEventListener("mousedown", mouseDownHandler); 80 | document.body.removeEventListener("mouseup", mouseUpHandler); 81 | resizeObserver.unobserve(viewportDiv); 82 | 83 | // Clear out the Mapillary instance property. This will take care of 84 | // cleaning up. 85 | model.mapillary = undefined; 86 | }; 87 | }, [model, model.id, model.mapillaryKey]); 88 | 89 | return ( 90 | 91 |
92 |
93 | 94 | 105 | {model.synchronizePosition ? ( 106 | 107 | ) : ( 108 | 109 | )} 110 | 111 | 117 | 118 | 119 | 120 |
121 | 122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ShadowCast/designer.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ApplyDesignerSettingsCallback, 3 | ComponentModelDesignerSettings, 4 | DesignerSettings, 5 | GetDesignerSettingsCallback, 6 | GetDesignerSettingsSchemaCallback, 7 | Setting, 8 | SettingsSchema, 9 | } from "@vertigis/web/designer"; 10 | import { 11 | applyComponentModelDesignerSettings, 12 | getComponentModelDesignerSettings, 13 | getComponentModelDesignerSettingsSchema, 14 | } from "@vertigis/web/designer"; 15 | 16 | import type ShadowCastModel from "./ShadowCastModel"; 17 | 18 | export interface ShadowCastSettings extends ComponentModelDesignerSettings { 19 | timeRangeSlider?: boolean; 20 | timezone?: boolean; 21 | datePicker?: boolean; 22 | visualizationOptions?: boolean; 23 | colorPicker?: boolean; 24 | tooltip?: boolean; 25 | visualizationType?: "threshold" | "duration" | "discrete"; 26 | } 27 | 28 | export type SettingsMap = DesignerSettings; 29 | 30 | export const applySettings: ApplyDesignerSettingsCallback< 31 | ShadowCastModel, 32 | SettingsMap 33 | > = async (args) => { 34 | const { model, settings } = args; 35 | await applyComponentModelDesignerSettings(args); 36 | model.assignProperties(settings); 37 | }; 38 | 39 | export const getSettings: GetDesignerSettingsCallback< 40 | ShadowCastModel, 41 | SettingsMap 42 | > = async (args) => { 43 | const { model } = args; 44 | const { 45 | timeRangeSlider, 46 | timezone, 47 | datePicker, 48 | visualizationOptions, 49 | colorPicker, 50 | tooltip, 51 | visualizationType, 52 | } = model; 53 | return { 54 | ...(await getComponentModelDesignerSettings(args)), 55 | timeRangeSlider, 56 | timezone, 57 | datePicker, 58 | visualizationOptions, 59 | colorPicker, 60 | tooltip, 61 | visualizationType, 62 | }; 63 | }; 64 | 65 | export const getSettingsSchema: GetDesignerSettingsSchemaCallback< 66 | ShadowCastModel, 67 | SettingsMap 68 | > = async (args) => { 69 | const baseSchema = await getComponentModelDesignerSettingsSchema(args); 70 | (baseSchema.settings[0].settings as Setting[]) = ( 71 | baseSchema.settings[0].settings as Setting[] 72 | ).concat([ 73 | { 74 | id: "timeRangeSlider", 75 | type: "checkbox", 76 | description: 77 | "language-designer-3d-tools-shadow-cast-time-range-slider-description", 78 | displayName: 79 | "language-designer-3d-tools-shadow-cast-time-range-slider-title", 80 | }, 81 | { 82 | id: "timezone", 83 | type: "checkbox", 84 | description: 85 | "language-designer-3d-tools-shadow-cast-timezone-description", 86 | displayName: 87 | "language-designer-3d-tools-shadow-cast-timezone-title", 88 | }, 89 | { 90 | id: "datePicker", 91 | type: "checkbox", 92 | description: 93 | "language-designer-3d-tools-shadow-cast-date-picker-description", 94 | displayName: 95 | "language-designer-3d-tools-shadow-cast-date-picker-title", 96 | }, 97 | { 98 | id: "visualizationOptions", 99 | type: "checkbox", 100 | description: 101 | "language-designer-3d-tools-shadow-cast-visualization-options-description", 102 | displayName: 103 | "language-designer-3d-tools-shadow-cast-visualization-options-title", 104 | }, 105 | { 106 | id: "colorPicker", 107 | type: "checkbox", 108 | description: 109 | "language-designer-3d-tools-shadow-cast-color-picker-description", 110 | displayName: 111 | "language-designer-3d-tools-shadow-cast-color-picker-title", 112 | }, 113 | { 114 | id: "tooltip", 115 | type: "checkbox", 116 | description: 117 | "language-designer-3d-tools-shadow-cast-tooltip-description", 118 | displayName: "language-designer-3d-tools-shadow-cast-tooltip-title", 119 | }, 120 | { 121 | id: "visualizationType", 122 | type: "select", 123 | description: 124 | "language-designer-3d-tools-shadow-cast-visualization-type-description", 125 | displayName: 126 | "language-designer-3d-tools-shadow-cast-visualization-type-title", 127 | values: [ 128 | { 129 | displayName: 130 | "language-designer-3d-tools-shadow-cast-visualization-type-threshold", 131 | value: "threshold", 132 | }, 133 | { 134 | displayName: 135 | "language-designer-3d-tools-shadow-cast-visualization-type-duration", 136 | value: "duration", 137 | }, 138 | { 139 | displayName: 140 | "language-designer-3d-tools-shadow-cast-visualization-type-discrete", 141 | value: "discrete", 142 | }, 143 | ], 144 | }, 145 | ]); 146 | 147 | const schema: SettingsSchema = { 148 | ...baseSchema, 149 | settings: [...baseSchema.settings], 150 | }; 151 | return schema; 152 | }; 153 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/components/EmbeddedExplorer/ScaleLevels.ts: -------------------------------------------------------------------------------- 1 | interface LevelInfo { 2 | level: number; 3 | scale: number; 4 | } 5 | 6 | export enum TileType { 7 | Raster = "Raster", 8 | Vector = "Vector" 9 | } 10 | 11 | export default class ScaleLevels { 12 | 13 | static VectorTileLevels: LevelInfo[] = [ 14 | { "level": 0, "scale": 591657527.591555 }, 15 | { "level": 1, "scale": 295828763.795777 }, 16 | { "level": 2, "scale": 147914381.897889 }, 17 | { "level": 3, "scale": 73957190.948944 }, 18 | { "level": 4, "scale": 36978595.474472 }, 19 | { "level": 5, "scale": 18489297.737236 }, 20 | { "level": 6, "scale": 9244648.868618 }, 21 | { "level": 7, "scale": 4622324.434309 }, 22 | { "level": 8, "scale": 2311162.217155 }, 23 | { "level": 9, "scale": 1155581.108577 }, 24 | { "level": 10, "scale": 577790.554289 }, 25 | { "level": 11, "scale": 288895.277144 }, 26 | { "level": 12, "scale": 144447.638572 }, 27 | { "level": 13, "scale": 72223.819286 }, 28 | { "level": 14, "scale": 36111.909643 }, 29 | { "level": 15, "scale": 18055.954822 }, 30 | { "level": 16, "scale": 9027.977411 }, 31 | { "level": 17, "scale": 4513.988705 }, 32 | { "level": 18, "scale": 2256.994353 }, 33 | { "level": 19, "scale": 1128.497176 }, 34 | { "level": 20, "scale": 564.248588 }, 35 | { "level": 21, "scale": 282.124294 }, 36 | { "level": 22, "scale": 141.062147 } 37 | ]; 38 | 39 | static RasterTileLevels: LevelInfo[] = [ 40 | { 41 | "level": 0, 42 | "scale": 295828763.795778 43 | }, 44 | { 45 | "level": 1, 46 | "scale": 147914381.897889 47 | }, 48 | { 49 | "level": 2, 50 | "scale": 73957190.9489445 51 | }, 52 | { 53 | "level": 3, 54 | "scale": 36978595.474472 55 | }, 56 | { 57 | "level": 4, 58 | "scale": 18489297.737236 59 | }, 60 | { 61 | "level": 5, 62 | "scale": 9244648.868618 63 | }, 64 | { 65 | "level": 6, 66 | "scale": 4622324.434309 67 | }, 68 | { 69 | "level": 7, 70 | "scale": 2311162.2171545 71 | }, 72 | { 73 | "level": 8, 74 | "scale": 1155581.1085775 75 | }, 76 | { 77 | "level": 9, 78 | "scale": 577790.5542885 79 | }, 80 | { 81 | "level": 10, 82 | "scale": 288895.2771445 83 | }, 84 | { 85 | "level": 11, 86 | "scale": 144447.638572 87 | }, 88 | { 89 | "level": 12, 90 | "scale": 72223.819286 91 | }, 92 | { 93 | "level": 13, 94 | "scale": 36111.909643 95 | }, 96 | { 97 | "level": 14, 98 | "scale": 18055.9548215 99 | }, 100 | { 101 | "level": 15, 102 | "scale": 9027.977411 103 | }, 104 | { 105 | "level": 16, 106 | "scale": 4513.9887055 107 | }, 108 | { 109 | "level": 17, 110 | "scale": 2256.9943525 111 | }, 112 | { 113 | "level": 18, 114 | "scale": 1128.4971765 115 | }, 116 | { 117 | "level": 19, 118 | "scale": 564.248588 119 | }, 120 | { 121 | "level": 20, 122 | "scale": 282.124294 123 | }, 124 | { 125 | "level": 21, 126 | "scale": 141.062147 127 | }, 128 | { 129 | "level": 22, 130 | "scale": 70.5310735 131 | } 132 | ] 133 | 134 | /** 135 | * Compares two arrays of LevelInfo objects for equality. 136 | * @param scaleLevels The first array of LevelInfo objects. 137 | * @param other The second array of LevelInfo objects. Defaults to ScaleLevels.Levels. 138 | * @returns True if the arrays are equal, false otherwise. 139 | */ 140 | static equals(scaleLevels: LevelInfo[], other: LevelInfo[] = ScaleLevels.RasterTileLevels): boolean { 141 | if (scaleLevels.length !== other.length) { 142 | return false; 143 | } 144 | for (let i = 0; i < scaleLevels.length; i++) { 145 | if (scaleLevels[i].level !== other[i].level || scaleLevels[i].scale !== other[i].scale) { 146 | return false; 147 | } 148 | } 149 | return true; 150 | } 151 | 152 | /** 153 | * Returns the tiling scheme zoom level associated with the provided scale. 154 | * Note: whole number levels only. 155 | */ 156 | static GetLevelForScale(scale: number, tileType: TileType = TileType.Raster): number | undefined { 157 | 158 | const levels = tileType === TileType.Raster ? ScaleLevels.RasterTileLevels : ScaleLevels.VectorTileLevels; 159 | 160 | let closest = levels[0]; 161 | 162 | for (const level of levels) { 163 | if (Math.abs(level.scale - scale) < Math.abs(closest.scale - scale)) { 164 | closest = level; 165 | } 166 | } 167 | return closest.level; 168 | } 169 | 170 | /** 171 | * Returns the scale associated with the provided ESRI/Google web mapping tiling scheme zoom level. 172 | * Note: the level will be rounded to the nearest whole number. 173 | */ 174 | static GetScaleForLevel(level: number, tileType: TileType = TileType.Raster): number | undefined { 175 | const levelInfo = (tileType === TileType.Raster ? ScaleLevels.RasterTileLevels : ScaleLevels.VectorTileLevels).find(l => l.level === Math.round(level)); 176 | return levelInfo?.scale; 177 | } 178 | 179 | 180 | } -------------------------------------------------------------------------------- /libraries/3d-tools/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { LibraryRegistry } from "@vertigis/web/config"; 2 | import type { GetDesignerSettingsSchemaArgs } from "@vertigis/web/designer"; 3 | 4 | import type { DaylightModel } from "./components/Daylight"; 5 | import type { ElevationProfileModel } from "./components/ElevationProfile"; 6 | import type { ShadowCastModel } from "./components/ShadowCast"; 7 | import type { SliceModel } from "./components/Slice"; 8 | import invLanguage from "./locale/inv.json"; 9 | 10 | const getAreaMeasurement = () => import("./components/AreaMeasurement"); 11 | const getLineMeasurement = () => import("./components/LineMeasurement"); 12 | const getDaylight = () => import("./components/Daylight"); 13 | const getElevationProfile = () => import("./components/ElevationProfile"); 14 | const getLineOfSight = () => import("./components/LineOfSight"); 15 | const getSlice = () => import("./components/Slice"); 16 | const getShadowCast = () => import("./components/ShadowCast"); 17 | 18 | export default function (registry: LibraryRegistry): void { 19 | registry.registerModel({ 20 | getModel: async (config) => 21 | new (await getAreaMeasurement()).AreaMeasurementModel(config), 22 | itemType: "area-measurement-3d", 23 | }); 24 | registry.registerModel({ 25 | getModel: async (config) => 26 | new (await getLineMeasurement()).LineMeasurementModel(config), 27 | itemType: "line-measurement-3d", 28 | }); 29 | registry.registerModel({ 30 | getModel: async (config) => 31 | new (await getLineOfSight()).LineOfSightModel(config), 32 | itemType: "line-of-sight-3d", 33 | }); 34 | registry.registerModel({ 35 | getModel: async (config) => 36 | new (await getDaylight()).DaylightModel(config), 37 | itemType: "daylight-widget-3d", 38 | }); 39 | registry.registerModel({ 40 | getModel: async (config) => 41 | new (await getElevationProfile()).ElevationProfileModel(config), 42 | itemType: "elevation-profile-3d", 43 | }); 44 | registry.registerModel({ 45 | getModel: async (config) => 46 | new (await getShadowCast()).ShadowCastModel(config), 47 | itemType: "shadow-cast-3d", 48 | }); 49 | registry.registerModel({ 50 | getModel: async (config) => new (await getSlice()).SliceModel(config), 51 | itemType: "slice-3d", 52 | }); 53 | 54 | registry.registerComponent({ 55 | name: "area-measurement-3d", 56 | namespace: "vertigis.web.incubator", 57 | getComponentType: async () => (await getAreaMeasurement()).default, 58 | category: "map", 59 | itemType: "area-measurement-3d", 60 | title: "language-web-incubator-area-measurement-3d-title", 61 | description: "language-web-incubator-area-measurement-3d-description", 62 | iconId: "map-3rd-party", 63 | }); 64 | registry.registerComponent({ 65 | name: "line-of-sight-3d", 66 | namespace: "vertigis.web.incubator", 67 | getComponentType: async () => (await getLineOfSight()).default, 68 | category: "map", 69 | itemType: "line-of-sight-3d", 70 | title: "language-web-incubator-line-of-sight-3d-title", 71 | description: "language-web-incubator-line-of-sight-3d-description", 72 | iconId: "map-3rd-party", 73 | }); 74 | registry.registerComponent({ 75 | name: "line-measurement-3d", 76 | namespace: "vertigis.web.incubator", 77 | getComponentType: async () => (await getLineMeasurement()).default, 78 | category: "map", 79 | itemType: "line-measurement-3d", 80 | title: "language-web-incubator-line-measurement-3d-title", 81 | description: "language-web-incubator-line-measurement-3d-description", 82 | iconId: "map-3rd-party", 83 | }); 84 | registry.registerComponent({ 85 | name: "daylight-widget-3d", 86 | namespace: "vertigis.web.incubator", 87 | getComponentType: async () => (await getDaylight()).default, 88 | getDesignerSettings: async ( 89 | args: GetDesignerSettingsSchemaArgs 90 | ) => (await getDaylight()).getSettings(args), 91 | applyDesignerSettings: async (args) => 92 | (await getDaylight()).applySettings(args), 93 | getDesignerSettingsSchema: async ( 94 | args: GetDesignerSettingsSchemaArgs 95 | ) => (await getDaylight()).getSettingsSchema(args), 96 | category: "map", 97 | itemType: "daylight-widget-3d", 98 | title: "language-web-incubator-daylight-widget-3d-title", 99 | description: "language-web-incubator-daylight-widget-3d-description", 100 | iconId: "map-3rd-party", 101 | }); 102 | registry.registerComponent({ 103 | name: "elevation-profile-3d", 104 | namespace: "vertigis.web.incubator", 105 | getComponentType: async () => (await getElevationProfile()).default, 106 | getDesignerSettings: async ( 107 | args: GetDesignerSettingsSchemaArgs 108 | ) => (await getElevationProfile()).getSettings(args), 109 | applyDesignerSettings: async (args) => 110 | (await getElevationProfile()).applySettings(args), 111 | getDesignerSettingsSchema: async ( 112 | args: GetDesignerSettingsSchemaArgs 113 | ) => (await getElevationProfile()).getSettingsSchema(args), 114 | category: "map", 115 | itemType: "elevation-profile-3d", 116 | title: "language-web-incubator-elevation-profile-3d-title", 117 | description: "language-web-incubator-elevation-profile-3d-description", 118 | iconId: "map-3rd-party", 119 | }); 120 | registry.registerComponent({ 121 | name: "shadow-cast-3d", 122 | namespace: "vertigis.web.incubator", 123 | getComponentType: async () => (await getShadowCast()).default, 124 | getDesignerSettings: async ( 125 | args: GetDesignerSettingsSchemaArgs 126 | ) => (await getShadowCast()).getSettings(args), 127 | applyDesignerSettings: async (args) => 128 | (await getShadowCast()).applySettings(args), 129 | getDesignerSettingsSchema: async ( 130 | args: GetDesignerSettingsSchemaArgs 131 | ) => (await getShadowCast()).getSettingsSchema(args), 132 | category: "map", 133 | itemType: "shadow-cast-3d", 134 | title: "language-web-incubator-shadow-cast-3d-title", 135 | description: "language-web-incubator-shadow-cast-3d-description", 136 | iconId: "map-3rd-party", 137 | }); 138 | registry.registerComponent({ 139 | name: "slice-3d", 140 | namespace: "vertigis.web.incubator", 141 | getComponentType: async () => (await getSlice()).default, 142 | getDesignerSettings: async ( 143 | args: GetDesignerSettingsSchemaArgs 144 | ) => (await getSlice()).getSettings(args), 145 | applyDesignerSettings: async (args) => 146 | (await getSlice()).applySettings(args), 147 | getDesignerSettingsSchema: async ( 148 | args: GetDesignerSettingsSchemaArgs 149 | ) => (await getSlice()).getSettingsSchema(args), 150 | category: "map", 151 | itemType: "slice-3d", 152 | title: "language-web-incubator-slice-3d-title", 153 | description: "language-web-incubator-slice-3d-description", 154 | iconId: "map-3rd-party", 155 | }); 156 | 157 | registry.registerLanguageResources({ 158 | locale: "inv", 159 | values: invLanguage as { [key: string]: string }, 160 | }); 161 | registry.registerLanguageResources({ 162 | locale: "en", 163 | values: invLanguage as { [key: string]: string }, 164 | }); 165 | } 166 | -------------------------------------------------------------------------------- /viewer/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | VertiGIS Studio Web Library Viewer 8 | 68 | 69 | 70 | 175 |
176 |
177 |
178 |
179 |
180 |
181 | 182 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/locale/inv.json: -------------------------------------------------------------------------------- 1 | { 2 | "language-web-incubator-common-clear": "Clear", 3 | "language-web-incubator-area-measurement-3d-title": "Area Measurement 3D", 4 | "language-web-incubator-line-measurement-3d-title": "Direct Line Measurement 3D", 5 | "language-web-incubator-line-of-sight-3d-title": "Line of Sight", 6 | "language-web-incubator-daylight-widget-3d-title": "Daylight", 7 | "language-web-incubator-elevation-profile-3d-title": "Elevation Profile", 8 | "language-web-incubator-shadow-cast-3d-title": "Shadow Cast", 9 | "language-web-incubator-slice-3d-title": "Slice", 10 | "language-web-incubator-area-measurement-3d-description": "The AreaMeasurement3D component calculates and displays the area and perimeter of a polygon.", 11 | "language-web-incubator-line-measurement-3d-description": "The DirectLineMeasurement3D widget calculates and displays the 3D distance between two points.", 12 | "language-web-incubator-line-of-sight-3d-description": "The LineOfSight component is a 3D analysis tool that allows you to perform visibility analysis in a SceneView.", 13 | "language-web-incubator-daylight-widget-3d-description": "The daylight component can be used to manipulate the time and date and to toggle shadows in a SceneView.", 14 | "language-web-incubator-elevation-profile-3d-description": "The ElevationProfile component is used to generate and display an elevation profile from an input line graphic.", 15 | "language-web-incubator-shadow-cast-3d-description": "The ShadowCast component displays the cumulative shadows of 3D features in a SceneView.", 16 | "language-web-incubator-slice-3d-description": "The slice component is a 3D analysis tool that can be used to reveal occluded content in a SceneView.", 17 | "language-designer-3d-tools-daylight-play-buttons-description": "When disabled, neither of the play buttons are displayed.", 18 | "language-designer-3d-tools-daylight-play-buttons-title": "Show Widget Play Buttons", 19 | "language-designer-3d-tools-daylight-shadows-toggle-description": "When disabled, the shadow toggle button is not displayed.", 20 | "language-designer-3d-tools-daylight-shadows-toggle-title": "Show Widget Shadow Checkbox", 21 | "language-designer-3d-tools-daylight-date-picker-description": "When disabled, neither the date nor the season picker are displayed.", 22 | "language-designer-3d-tools-daylight-date-picker-title": "Show Widget Date Picker", 23 | "language-designer-3d-tools-daylight-timezone-description": "When disabled, the timezone selector is not displayed.", 24 | "language-designer-3d-tools-daylight-timezone-title": "Show Widget Timezone", 25 | "language-designer-3d-tools-daylight-date-or-season-description": "Controls whether the widget displays a date or a season picker.", 26 | "language-designer-3d-tools-daylight-date-or-season-title": "Date or Season", 27 | "language-designer-3d-tools-daylight-date-or-season-date": "Date", 28 | "language-designer-3d-tools-daylight-date-or-season-season": "Season", 29 | "language-designer-3d-tools-daylight-play-speed-multiplier-description": "Controls the speed of the daytime and date animation.", 30 | "language-designer-3d-tools-daylight-play-speed-multiplier-title": "Play Speed Multiplier", 31 | "language-designer-3d-tools-shadow-cast-time-range-slider-description": "When disabled, the slider used to select a time range for the analysis is not displayed.", 32 | "language-designer-3d-tools-shadow-cast-time-range-slider-title": "Show Widget Time Range Slider", 33 | "language-designer-3d-tools-shadow-cast-timezone-description": "When disabled, the dropdown used to select a timezone for the time range is not displayed.", 34 | "language-designer-3d-tools-shadow-cast-timezone-title": "Show Widget Time Range Timezone", 35 | "language-designer-3d-tools-shadow-cast-date-picker-description": "When disabled, the date picker is not displayed.", 36 | "language-designer-3d-tools-shadow-cast-date-picker-title": "Show Widget Date Picker", 37 | "language-designer-3d-tools-shadow-cast-visualization-options-description": "When disabled, the options for the various visualization modes are not displayed.", 38 | "language-designer-3d-tools-shadow-cast-visualization-options-title": "Show Widget Visualization Options", 39 | "language-designer-3d-tools-shadow-cast-color-picker-description": "When disabled, the color picker is not displayed in the options for any of the visualization modes.", 40 | "language-designer-3d-tools-shadow-cast-color-picker-title": "Show Widget Color Picker", 41 | "language-designer-3d-tools-shadow-cast-tooltip-description": "When disabled, the tooltip with the accumulated shadow time is not displayed when hovering the view.", 42 | "language-designer-3d-tools-shadow-cast-tooltip-title": "Show Tooltip in View", 43 | "language-designer-3d-tools-shadow-cast-visualization-type-description": "Type of visualization to use when showing the shadows.", 44 | "language-designer-3d-tools-shadow-cast-visualization-type-title": "Default Shadow Visualization", 45 | "language-designer-3d-tools-shadow-cast-visualization-type-threshold": "Areas Above Threshold", 46 | "language-designer-3d-tools-shadow-cast-visualization-type-duration": "Total Shadow Duration", 47 | "language-designer-3d-tools-shadow-cast-visualization-type-discrete": "Discrete Shadows", 48 | "language-designer-3d-tools-elevation-profile-legend-description": "When disabled, the legend (which includes statistics) is not displayed.", 49 | "language-designer-3d-tools-elevation-profile-legend-title": "Show Widget Legend", 50 | "language-designer-3d-tools-elevation-profile-chart-description": "When disabled, the chart is not displayed.", 51 | "language-designer-3d-tools-elevation-profile-chart-title": "Show Widget Chart", 52 | "language-designer-3d-tools-elevation-profile-clear-button-description": "When disabled the button used to clear the current elevation profile is not displayed.", 53 | "language-designer-3d-tools-elevation-profile-clear-button-title": "Show Widget Clear Button", 54 | "language-designer-3d-tools-elevation-profile-settings-button-description": "When disabled, the button used to open the settings popup is not displayed.", 55 | "language-designer-3d-tools-elevation-profile-settings-button-title": "Show Widget Settings Button", 56 | "language-designer-3d-tools-elevation-profile-sketch-button-description": "When disabled, the button used to start drawing/sketching is not displayed.", 57 | "language-designer-3d-tools-elevation-profile-sketch-button-title": "Show Widget Sketch Button", 58 | "language-designer-3d-tools-elevation-profile-select-button-description": "When disabled, the button used to select a path is not displayed.", 59 | "language-designer-3d-tools-elevation-profile-select-button-title": "Show Widget Select Button", 60 | "language-designer-3d-tools-elevation-profile-uniform-chart-scaling-toggle-description": "When disabled, the element used to toggle uniform chart scaling on or off is not displayed.", 61 | "language-designer-3d-tools-elevation-profile-uniform-chart-scaling-toggle-title": "Show Widget Uniform Chart Scaling", 62 | "language-designer-3d-tools-elevation-profile-profile-line-ground-description": "Profile line which samples elevation from the Ground of the Map currently set in the View.", 63 | "language-designer-3d-tools-elevation-profile-profile-line-ground-title": "Use Ground Line Profile ", 64 | "language-designer-3d-tools-elevation-profile-profile-line-input-description": "Profile line which samples elevation from a custom elevation source, for example by creating a new ElevationLayer, or by using an elevation layer from ground.layers.", 65 | "language-designer-3d-tools-elevation-profile-profile-line-input-title": "Use Line Input Profile", 66 | "language-designer-3d-tools-elevation-profile-profile-line-view-description": "Profile line which samples elevation directly from the SceneView.", 67 | "language-designer-3d-tools-elevation-profile-profile-line-view-title": "Use Line View Profile", 68 | "language-designer-3d-tools-elevation-profile-profile-line-ground-color-title": "Ground Line Profile Color", 69 | "language-designer-3d-tools-elevation-profile-profile-line-ground-color-description": "Color of the line on the chart and in the view.", 70 | "language-designer-3d-tools-elevation-profile-profile-line-input-color-title": "Line Input Profile Color", 71 | "language-designer-3d-tools-elevation-profile-profile-line-input-color-description": "Color of the line on the chart and in the view.", 72 | "language-designer-3d-tools-elevation-profile-profile-line-view-color-title": "Line View Profile Color", 73 | "language-designer-3d-tools-elevation-profile-profile-line-view-color-description": "Color of the line on the chart and in the view.", 74 | "language-designer-3d-tools-slice-tilt-enabled-description": "Enable tilting the slice shape.", 75 | "language-designer-3d-tools-slice-tilt-enabled-title": "Enable Widget Tilting" 76 | } 77 | -------------------------------------------------------------------------------- /libraries/3d-tools/src/components/ElevationProfile/designer.ts: -------------------------------------------------------------------------------- 1 | import type { ColorJson } from "@vertigis/arcgis-extensions/json/SymbolJson"; 2 | import { toColor } from "@vertigis/web/branding"; 3 | import type { 4 | ApplyDesignerSettingsCallback, 5 | ComponentModelDesignerSettings, 6 | DesignerSettings, 7 | GetDesignerSettingsCallback, 8 | GetDesignerSettingsSchemaCallback, 9 | Setting, 10 | SettingsSchema, 11 | } from "@vertigis/web/designer"; 12 | import { 13 | applyComponentModelDesignerSettings, 14 | getComponentModelDesignerSettings, 15 | getComponentModelDesignerSettingsSchema, 16 | } from "@vertigis/web/designer"; 17 | 18 | import type { ElevationProfileModelProperties } from "./ElevationProfileModel"; 19 | import type ElevationProfileModel from "./ElevationProfileModel"; 20 | 21 | export interface ElevationProfileSettings 22 | extends ComponentModelDesignerSettings { 23 | legend?: boolean; 24 | chart?: boolean; 25 | clearButton?: boolean; 26 | settingsButton?: boolean; 27 | sketchButton?: boolean; 28 | selectButton?: boolean; 29 | uniformChartScalingToggle?: boolean; 30 | profileLineGround?: boolean; 31 | profileLineGroundColor?: ColorJson; 32 | profileLineInput?: boolean; 33 | profileLineInputColor?: ColorJson; 34 | profileLineView?: boolean; 35 | profileLineViewColor?: ColorJson; 36 | } 37 | 38 | export type SettingsMap = DesignerSettings; 39 | 40 | export const applySettings: ApplyDesignerSettingsCallback< 41 | ElevationProfileModel, 42 | SettingsMap 43 | > = async (args) => { 44 | const { model, settings } = args; 45 | const { 46 | profileLineGroundColor, 47 | profileLineInputColor, 48 | profileLineViewColor, 49 | ...otherSettings 50 | } = settings; 51 | await applyComponentModelDesignerSettings(args); 52 | 53 | const applySettings: Partial = 54 | otherSettings; 55 | if (profileLineGroundColor) { 56 | applySettings.profileLineGroundColor = toColor( 57 | profileLineGroundColor 58 | )?.toCss(); 59 | } 60 | if (profileLineInputColor) { 61 | applySettings.profileLineInputColor = toColor( 62 | profileLineInputColor 63 | )?.toCss(); 64 | } 65 | if (profileLineViewColor) { 66 | applySettings.profileLineViewColor = 67 | toColor(profileLineViewColor)?.toCss(); 68 | } 69 | 70 | model.assignProperties(applySettings); 71 | }; 72 | 73 | export const getSettings: GetDesignerSettingsCallback< 74 | ElevationProfileModel, 75 | SettingsMap 76 | > = async (args) => { 77 | const { model } = args; 78 | const { 79 | legend, 80 | chart, 81 | clearButton, 82 | settingsButton, 83 | sketchButton, 84 | selectButton, 85 | uniformChartScalingToggle, 86 | profileLineGround, 87 | profileLineInput, 88 | profileLineView, 89 | profileLineGroundColor, 90 | profileLineInputColor, 91 | profileLineViewColor, 92 | } = model; 93 | return { 94 | ...(await getComponentModelDesignerSettings(args)), 95 | legend, 96 | chart, 97 | clearButton, 98 | settingsButton, 99 | sketchButton, 100 | selectButton, 101 | uniformChartScalingToggle, 102 | profileLineGround, 103 | profileLineInput, 104 | profileLineView, 105 | profileLineGroundColor: toColor(profileLineGroundColor).toJSON(), 106 | profileLineInputColor: toColor(profileLineInputColor).toJSON(), 107 | profileLineViewColor: toColor(profileLineViewColor).toJSON(), 108 | }; 109 | }; 110 | 111 | export const getSettingsSchema: GetDesignerSettingsSchemaCallback< 112 | ElevationProfileModel, 113 | SettingsMap 114 | > = async (args) => { 115 | const baseSchema = await getComponentModelDesignerSettingsSchema(args); 116 | (baseSchema.settings[0].settings as Setting[]) = ( 117 | baseSchema.settings[0].settings as Setting[] 118 | ).concat([ 119 | { 120 | id: "legend", 121 | type: "checkbox", 122 | description: 123 | "language-designer-3d-tools-elevation-profile-legend-description", 124 | displayName: 125 | "language-designer-3d-tools-elevation-profile-legend-title", 126 | }, 127 | { 128 | id: "chart", 129 | type: "checkbox", 130 | description: 131 | "language-designer-3d-tools-elevation-profile-chart-description", 132 | displayName: 133 | "language-designer-3d-tools-elevation-profile-chart-title", 134 | }, 135 | { 136 | id: "clearButton", 137 | type: "checkbox", 138 | description: 139 | "language-designer-3d-tools-elevation-profile-clear-button-description", 140 | displayName: 141 | "language-designer-3d-tools-elevation-profile-clear-button-title", 142 | }, 143 | { 144 | id: "settingsButton", 145 | type: "checkbox", 146 | description: 147 | "language-designer-3d-tools-elevation-profile-settings-button-description", 148 | displayName: 149 | "language-designer-3d-tools-elevation-profile-settings-button-title", 150 | }, 151 | { 152 | id: "sketchButton", 153 | type: "checkbox", 154 | description: 155 | "language-designer-3d-tools-elevation-profile-sketch-button-description", 156 | displayName: 157 | "language-designer-3d-tools-elevation-profile-sketch-button-title", 158 | }, 159 | { 160 | id: "selectButton", 161 | type: "checkbox", 162 | description: 163 | "language-designer-3d-tools-elevation-profile-select-button-description", 164 | displayName: 165 | "language-designer-3d-tools-elevation-profile-select-button-title", 166 | }, 167 | { 168 | id: "uniformChartScalingToggle", 169 | type: "checkbox", 170 | description: 171 | "language-designer-3d-tools-elevation-profile-uniform-chart-scaling-toggle-description", 172 | displayName: 173 | "language-designer-3d-tools-elevation-profile-uniform-chart-scaling-toggle-title", 174 | }, 175 | { 176 | id: "profileLineGround", 177 | type: "checkbox", 178 | description: 179 | "language-designer-3d-tools-elevation-profile-profile-line-ground-description", 180 | displayName: 181 | "language-designer-3d-tools-elevation-profile-profile-line-ground-title", 182 | }, 183 | { 184 | id: "profileLineGroundColor", 185 | type: "color", 186 | displayName: 187 | "language-designer-3d-tools-elevation-profile-profile-line-ground-color-title", 188 | description: 189 | "language-designer-3d-tools-elevation-profile-profile-line-ground-color-description", 190 | isVisible: (settings) => settings.profileLineGround, 191 | }, 192 | { 193 | id: "profileLineInput", 194 | type: "checkbox", 195 | description: 196 | "language-designer-3d-tools-elevation-profile-profile-line-input-description", 197 | displayName: 198 | "language-designer-3d-tools-elevation-profile-profile-line-input-title", 199 | }, 200 | { 201 | id: "profileLineInputColor", 202 | type: "color", 203 | displayName: 204 | "language-designer-3d-tools-elevation-profile-profile-line-input-color-title", 205 | description: 206 | "language-designer-3d-tools-elevation-profile-profile-line-input-color-description", 207 | isVisible: (settings) => settings.profileLineInput, 208 | }, 209 | { 210 | id: "profileLineView", 211 | type: "checkbox", 212 | description: 213 | "language-designer-3d-tools-elevation-profile-profile-line-view-description", 214 | displayName: 215 | "language-designer-3d-tools-elevation-profile-profile-line-view-title", 216 | }, 217 | { 218 | id: "profileLineViewColor", 219 | type: "color", 220 | displayName: 221 | "language-designer-3d-tools-elevation-profile-profile-line-view-color-title", 222 | description: 223 | "language-designer-3d-tools-elevation-profile-profile-line-view-color-description", 224 | isVisible: (settings) => settings.profileLineView, 225 | }, 226 | ]); 227 | 228 | const schema: SettingsSchema = { 229 | ...baseSchema, 230 | settings: [...baseSchema.settings], 231 | }; 232 | return schema; 233 | }; 234 | -------------------------------------------------------------------------------- /libraries/timeslider/src/components/TimeSlider/TimeSliderModel.ts: -------------------------------------------------------------------------------- 1 | import type WebMap from "@arcgis/core/WebMap"; 2 | import type FeatureLayer from "@arcgis/core/layers/FeatureLayer"; 3 | import TimeExtent from "@arcgis/core/time/TimeExtent"; 4 | import type EsriTimeSlider from "@arcgis/core/widgets/TimeSlider"; 5 | import { ItemType } from "@vertigis/arcgis-extensions/ItemType"; 6 | import type { MapModel } from "@vertigis/web/mapping/MapModel"; 7 | import { 8 | ComponentModelBase, 9 | importModel, 10 | serializable, 11 | } from "@vertigis/web/models"; 12 | import type { 13 | ComponentModelProperties, 14 | PropertyDefs, 15 | } from "@vertigis/web/models"; 16 | 17 | export type TimeSliderLayout = EsriTimeSlider["layout"]; 18 | export type TimeSliderMode = EsriTimeSlider["mode"]; 19 | 20 | interface TimeSliderModelProperties extends ComponentModelProperties { 21 | /** The layout size for this time slider widget. */ 22 | layout?: TimeSliderLayout; 23 | /** The toggle for whether the playback will automatically loop or not. */ 24 | loop?: boolean; 25 | /** The number of milliseconds to wait between each stop. */ 26 | playRate?: number; 27 | /** The various functional modes for this time slider. */ 28 | mode?: TimeSliderMode; 29 | /** The toggle for overriding the default stops in this time slider. */ 30 | /** The toggle to show time information in this widget. */ 31 | timeVisible?: boolean; 32 | } 33 | 34 | @serializable 35 | export default class TimeSliderModel extends ComponentModelBase { 36 | @importModel(ItemType.MAP_EXTENSION) 37 | map: MapModel | undefined; 38 | widget: EsriTimeSlider; 39 | _layout: TimeSliderLayout; 40 | _loop: boolean; 41 | _playRate: number; 42 | _mode: TimeSliderMode; 43 | _timeVisible: boolean; 44 | 45 | get layout(): TimeSliderLayout { 46 | return this.widget?.layout ?? this._layout; 47 | } 48 | set layout(value: TimeSliderLayout) { 49 | this._layout = value; 50 | if (this.widget) { 51 | this.widget.layout = value; 52 | } 53 | } 54 | 55 | get loop(): boolean { 56 | return this.widget?.loop ?? this._loop; 57 | } 58 | set loop(value: boolean) { 59 | this._loop = value; 60 | if (this.widget) { 61 | this.widget.loop = value; 62 | } 63 | } 64 | 65 | get playRate(): number { 66 | return this.widget?.playRate ?? this._playRate; 67 | } 68 | set playRate(value: number) { 69 | this._playRate = value; 70 | if (this.widget) { 71 | this.widget.playRate = value; 72 | } 73 | } 74 | 75 | get mode(): TimeSliderMode { 76 | return this.widget?.mode ?? this._mode; 77 | } 78 | set mode(value: TimeSliderMode) { 79 | this._mode = value; 80 | if (this.widget) { 81 | this.widget.mode = value; 82 | } 83 | } 84 | 85 | get timeVisible(): boolean { 86 | return this.widget?.timeVisible ?? this._timeVisible; 87 | } 88 | set timeVisible(value: boolean) { 89 | this._timeVisible = value; 90 | if (this.widget) { 91 | this.widget.timeVisible = value; 92 | } 93 | } 94 | 95 | public updateTimeSliderWidget = async ( 96 | widget: EsriTimeSlider, 97 | webMap: WebMap 98 | ): Promise => { 99 | // Reset model with default values. 100 | this.layout = "auto"; 101 | this.loop = true; 102 | this.playRate = 1000; 103 | this.mode = "time-window"; 104 | this.timeVisible = false; 105 | // Check the web map for existing time slider config. 106 | if (webMap?.widgets?.timeSlider) { 107 | const timeSlider = webMap.widgets.timeSlider; 108 | await this._updateWidgetFromWebMapTimeSlider( 109 | widget, 110 | timeSlider, 111 | webMap 112 | ); 113 | } else { 114 | await this._updateWidgetFromLayerTimeInfos(widget, webMap); 115 | } 116 | // Sync model properties with the time slider widget. 117 | widget.layout = this.layout; 118 | widget.loop = this.loop; 119 | widget.playRate = this.playRate; 120 | widget.mode = this.mode; 121 | widget.timeVisible = this.timeVisible; 122 | this.widget = widget; 123 | }; 124 | 125 | protected override _getSerializableProperties(): PropertyDefs { 126 | const props = super._getSerializableProperties(); 127 | return { 128 | ...props, 129 | title: { 130 | ...this._toPropertyDef(props.title), 131 | default: "language-web-incubator-time-slider-title", 132 | }, 133 | icon: { 134 | ...this._toPropertyDef(props.icon), 135 | default: "range-start", 136 | }, 137 | }; 138 | } 139 | 140 | private readonly _updateWidgetFromLayerTimeInfos = async ( 141 | widget: EsriTimeSlider, 142 | map: __esri.Map 143 | ): Promise => { 144 | let start, end: Date; 145 | let timeVisible: boolean; 146 | for (const tempLayer of map.allLayers.toArray()) { 147 | const layer = tempLayer as FeatureLayer; 148 | // eslint-disable-next-line no-await-in-loop 149 | await layer.load(); 150 | if (layer?.timeInfo?.fullTimeExtent) { 151 | if ( 152 | start === undefined || 153 | start > layer.timeInfo.fullTimeExtent.start 154 | ) { 155 | start = layer.timeInfo.fullTimeExtent.start; 156 | } 157 | if ( 158 | end === undefined || 159 | end < layer.timeInfo.fullTimeExtent.end 160 | ) { 161 | end = layer.timeInfo.fullTimeExtent.end; 162 | } 163 | if (layer.timeInfo) { 164 | if (!timeVisible) { 165 | timeVisible = layer.timeInfo["useTime"]; 166 | } 167 | } 168 | } 169 | } 170 | widget.fullTimeExtent = new TimeExtent({ start, end }); 171 | // Ensure time slider playback will end on the latest date in the full 172 | // time extent. 173 | if ( 174 | widget.fullTimeExtent && 175 | widget.effectiveStops?.[widget.effectiveStops.length - 1] < 176 | widget.fullTimeExtent.end 177 | ) { 178 | widget.effectiveStops.push(widget.fullTimeExtent.end); 179 | } 180 | this.timeVisible = timeVisible; 181 | }; 182 | 183 | private readonly _updateWidgetFromWebMapTimeSlider = async ( 184 | widget: EsriTimeSlider, 185 | timeSlider: __esri.TimeSlider, 186 | map: WebMap 187 | ): Promise => { 188 | let timeExtentOption: string; 189 | if (timeSlider.fullTimeExtent) { 190 | widget.fullTimeExtent = timeSlider.fullTimeExtent; 191 | widget.set("timeExtent", timeSlider.currentTimeExtent); 192 | } else { 193 | widget.fullTimeExtent = timeSlider.currentTimeExtent; 194 | timeExtentOption = "currentTimeExtent"; 195 | } 196 | // Override full extent from the time slider config with layer timeInfos 197 | // instead - the config isn't always accurate to the layers in the web 198 | // map/scene. 199 | await this._updateWidgetFromLayerTimeInfos(widget, map); 200 | if (timeSlider.stops) { 201 | widget.stops = { 202 | dates: timeSlider.stops, 203 | timeExtent: widget.fullTimeExtent, 204 | }; 205 | } else if (timeSlider.numStops) { 206 | widget.stops = { 207 | count: timeSlider.numStops, 208 | timeExtent: widget.fullTimeExtent, 209 | }; 210 | } else if (timeSlider.stopInterval) { 211 | widget.stops = { 212 | interval: timeSlider.stopInterval, 213 | timeExtent: widget.fullTimeExtent, 214 | }; 215 | } 216 | // Ensure time slider playback will end on the latest date in the full 217 | // time extent. 218 | if ( 219 | widget.fullTimeExtent && 220 | widget.effectiveStops?.[widget.effectiveStops.length - 1] < 221 | widget.fullTimeExtent.end 222 | ) { 223 | widget.effectiveStops.push(widget.fullTimeExtent.end); 224 | } 225 | if (timeExtentOption === "currentTimeExtent") { 226 | // Set a default timeExtent if fullTimeExtent was null. 227 | widget.set( 228 | "timeExtent", 229 | new TimeExtent({ 230 | start: widget.effectiveStops[0], 231 | end: widget.effectiveStops[1], 232 | }) 233 | ); 234 | } 235 | // Set properties to model from time slider config. 236 | if (timeSlider.loop) { 237 | this.loop = widget.loop; 238 | } 239 | if (timeSlider.stopDelay) { 240 | this.playRate = widget.playRate; 241 | } 242 | if (timeSlider.numThumbs === 1) { 243 | this.mode = "instant"; 244 | } else { 245 | this.mode = "time-window"; 246 | } 247 | }; 248 | } 249 | -------------------------------------------------------------------------------- /libraries/eagle-view-viewer/src/components/EmbeddedExplorer/EagleViewModel.ts: -------------------------------------------------------------------------------- 1 | import Viewpoint from "@arcgis/core/Viewpoint"; 2 | import Point from "@arcgis/core/geometry/Point"; 3 | import SpatialReference from "@arcgis/core/geometry/SpatialReference"; 4 | import * as projectOperator from "@arcgis/core/geometry/operators/projectOperator"; 5 | import type { MapModel } from "@vertigis/web/mapping"; 6 | import type { 7 | ComponentModelProperties, 8 | PropertyDefs, 9 | } from "@vertigis/web/models"; 10 | import { 11 | serializable, 12 | ComponentModelBase, 13 | importModel, 14 | } from "@vertigis/web/models"; 15 | import { debounce } from "@vertigis/web/ui/debounce"; 16 | 17 | import ScaleLevels,{ TileType } from "./ScaleLevels"; 18 | import type { 19 | EagleViewView, 20 | EmbeddedExplorerInstance 21 | } from "./embedded-explorer"; 22 | 23 | 24 | interface EagleViewProperties extends ComponentModelProperties { 25 | apiKey?: string; 26 | } 27 | 28 | export enum ViewUpdatedEventSource { 29 | e3, 30 | VSW, 31 | None, 32 | } 33 | 34 | @serializable 35 | export default class EagleViewModel extends ComponentModelBase { 36 | apiKey: string; 37 | mapUpdateEventSource = ViewUpdatedEventSource.None; 38 | private _viewpointChangeHandle: IHandle; 39 | private _lastWebPoint: Viewpoint | undefined; 40 | private _lastEagleViewPoint: EagleViewView | undefined; 41 | 42 | /** 43 | * Set/Get MapModel 44 | */ 45 | private _map: MapModel | undefined; 46 | 47 | get map(): MapModel | undefined { 48 | return this._map; 49 | } 50 | @importModel("map-extension") 51 | set map(map: MapModel | undefined) { 52 | console.log("EagleViewModel.setMap()"); 53 | if (map === this._map) { 54 | return; 55 | } 56 | this._viewpointChangeHandle?.remove(); 57 | this._map = map; 58 | this._viewpointChangeHandle = 59 | this.messages.events.map.viewpointChanged.subscribe( 60 | debounce(() => this._onVSWMapExtentUpdated(), 100) 61 | ); 62 | } 63 | 64 | /** 65 | * Set/Get Eagleview embedded viewer 66 | */ 67 | private _e3: EmbeddedExplorerInstance | undefined; 68 | 69 | get e3(): EmbeddedExplorerInstance | undefined { 70 | return this._e3; 71 | } 72 | 73 | set e3(instance: EmbeddedExplorerInstance | undefined) { 74 | if (instance === this._e3) { 75 | return; 76 | } 77 | 78 | // If an instance already exists, clean it up first. 79 | if (this._e3) { 80 | this._e3.off("onViewUpdate"); 81 | } 82 | 83 | this._e3 = instance; 84 | this._e3?.on("onMapReady", () => { 85 | this._e3.on("onViewUpdate", (args: EagleViewView) => 86 | this._onE3ViewUpdated(args) 87 | ); 88 | }); 89 | } 90 | 91 | /** 92 | * Re-projects if necessary, a point to Web Mercator 93 | * @param position 94 | * @returns position, in Web Mercator 95 | */ 96 | getPointForEagleView(force: boolean): EagleViewView { 97 | let position = this.map.view.center; 98 | if (!position.spatialReference.isWebMercator) { 99 | position = projectOperator.execute( 100 | position, 101 | new SpatialReference({ wkid: 3857 }) 102 | ) as Point; 103 | } 104 | 105 | // EagleView uses a different rotation direction, so we need to negate it 106 | const rotation = 360 - this.map.view.viewpoint.rotation; 107 | const zoom = ScaleLevels.GetLevelForScale(this.map.view.scale, TileType.Raster); 108 | 109 | // Attempt to avoid unnecessary updates to the EagleView view. 110 | if ( 111 | !force && 112 | this._lastEagleViewPoint?.lonLat?.lat === position.latitude && 113 | this._lastEagleViewPoint.lonLat.lon === position.longitude && 114 | isWithinTolerance(this._lastEagleViewPoint.rotation, rotation, 1) && 115 | isWithinTolerance(this._lastEagleViewPoint.zoom, zoom, 1) 116 | ) { 117 | return undefined; 118 | } 119 | 120 | return (this._lastEagleViewPoint = { 121 | lonLat: { lat: position.latitude, lon: position.longitude }, 122 | rotation, 123 | zoom, 124 | }); 125 | } 126 | 127 | protected override async _onInitialize(): Promise { 128 | await super._onInitialize(); 129 | 130 | if (!projectOperator.isLoaded()) { 131 | await projectOperator.load(); 132 | } 133 | } 134 | 135 | protected override _getSerializableProperties(): PropertyDefs { 136 | const props = super._getSerializableProperties(); 137 | return { 138 | ...props, 139 | apiKey: { 140 | serializeModes: ["initial"], 141 | default: "", 142 | }, 143 | title: { 144 | ...this._toPropertyDef(props.title), 145 | default: "language-web-incubator-view-title", 146 | }, 147 | icon: { 148 | ...this._toPropertyDef(props.icon), 149 | default: "map-3rd-party", 150 | }, 151 | }; 152 | } 153 | 154 | private _onVSWMapExtentUpdated(): void { 155 | if (this.map && this.e3) { 156 | // if e3 wasn't the ViewUpdatedEventSource, ignore this./ 157 | // this would be the case if the view updated event was triggered by 158 | // the VSW map being updated. 159 | if (this.mapUpdateEventSource !== ViewUpdatedEventSource.None) { 160 | return; 161 | } 162 | 163 | try { 164 | this.mapUpdateEventSource = ViewUpdatedEventSource.VSW; 165 | const viewpoint = this.getPointForEagleView(false); 166 | if (viewpoint) { 167 | this.e3.off("onViewUpdate"); 168 | this.e3.setView(viewpoint, () => { 169 | setTimeout(() => { 170 | this.mapUpdateEventSource = ViewUpdatedEventSource.None; 171 | this.e3.on("onViewUpdate", (args: EagleViewView) => 172 | this._onE3ViewUpdated(args) 173 | ); 174 | }, 500)}); 175 | } else { 176 | this.mapUpdateEventSource = ViewUpdatedEventSource.None; 177 | } 178 | } catch { 179 | // Do nothing. 180 | } 181 | } 182 | } 183 | 184 | /** 185 | * Update the webview to reflect the EagleView view update. This is called 186 | * when the EagleView view is updated by user interaction. 187 | */ 188 | private async _onE3ViewUpdated(updatedView: EagleViewView) { 189 | if (this.mapUpdateEventSource !== ViewUpdatedEventSource.None) { 190 | return; 191 | } 192 | 193 | try { 194 | this.mapUpdateEventSource = ViewUpdatedEventSource.e3; 195 | const point = new Point({ 196 | x: updatedView.lonLat.lon, 197 | y: updatedView.lonLat.lat, 198 | spatialReference: new SpatialReference({ wkid: 4326 }), 199 | }); 200 | 201 | // Need to convert the zoom level to map scale Eagleview appears to 202 | // use MapBox tile schema, so we need to add 1 to the zoom level 203 | // const zoom = Math.round(updatedView.zoom + 1); 204 | const vswScale = ScaleLevels.GetScaleForLevel(updatedView.zoom, TileType.Raster); // (this.map).scaleLevels.items[zoom]; 205 | 206 | const viewpoint = new Viewpoint({ 207 | targetGeometry: point, 208 | rotation: updatedView.rotation * -1, 209 | scale: vswScale, 210 | }); 211 | 212 | // Check to see if the point is in the same SR as the map. If not, 213 | // we need to re-project it 214 | if ( 215 | point.spatialReference.wkid !== this.map.spatialReference.wkid 216 | ) { 217 | viewpoint.targetGeometry = projectOperator.execute( 218 | point, 219 | this.map.spatialReference 220 | ) as Point; 221 | } 222 | 223 | // Attempt to avoid unnecessary updates to the VSW map. 224 | if ( 225 | (this._lastWebPoint?.targetGeometry as Point)?.equals(point) && 226 | isWithinTolerance( 227 | this._lastWebPoint.rotation, 228 | viewpoint.rotation, 229 | 1 230 | ) && 231 | isWithinTolerance(this._lastWebPoint.scale, viewpoint.scale, 1) 232 | ) { 233 | return; 234 | } 235 | 236 | this._lastWebPoint = viewpoint; 237 | this._viewpointChangeHandle?.remove(); 238 | await this.messages.commands.map.goToViewpoint.execute({ 239 | viewpoint, 240 | }); 241 | } finally { 242 | requestAnimationFrame(() => { 243 | this.mapUpdateEventSource = ViewUpdatedEventSource.None; 244 | this._viewpointChangeHandle = 245 | this.messages.events.map.viewpointChanged.subscribe( 246 | debounce(() => this._onVSWMapExtentUpdated(), 500) 247 | ); 248 | }); 249 | } 250 | } 251 | } 252 | 253 | const isWithinTolerance = (a: number, b: number, tolerance: number): boolean => 254 | Math.abs(a - b) <= tolerance; 255 | -------------------------------------------------------------------------------- /libraries/library-viewer/src/components/LibraryViewer/LibraryViewerModel.ts: -------------------------------------------------------------------------------- 1 | import type { AppConfig } from "@vertigis/web/AppConfig"; 2 | import type { AppContext } from "@vertigis/web/AppContext"; 3 | import { command } from "@vertigis/web/messaging"; 4 | import { 5 | type ComponentModelProperties, 6 | ComponentModelBase, 7 | serializable, 8 | type PropertyDefs, 9 | } from "@vertigis/web/models"; 10 | import { inject, FrameworkServiceType } from "@vertigis/web/services"; 11 | import { Builder, parseStringPromise as parseString } from "xml2js"; 12 | 13 | import type { SetLibraryArgs } from "../PickList/PickListModel"; 14 | 15 | export interface Library { 16 | /** The id of the library. This will be the name of the containing folder. */ 17 | id: string; 18 | /** A human-readable title for the library. */ 19 | title?: string; 20 | } 21 | 22 | export interface LibraryViewerModelProperties extends ComponentModelProperties { 23 | libraries?: Library[]; 24 | } 25 | 26 | @serializable 27 | export default class LibraryViewerModel extends ComponentModelBase { 28 | @inject(FrameworkServiceType.APP_CONTEXT) 29 | appContext: AppContext; 30 | 31 | /** 32 | * Configuration for the libraries in this collection that you'd like to be 33 | * available through this viewer. The `id` is the name of a sibling folder 34 | * with a library in it, and the `name` is whatever you'd like to show up as 35 | * the title in the picklist. 36 | */ 37 | libraries: Library[]; 38 | 39 | selectedLibrary: string; 40 | libraryUrl: string; 41 | 42 | protected override async _onInitialize(): Promise { 43 | await super._onInitialize(); 44 | 45 | this.selectedLibrary = parent.location.hash.substring(1); 46 | if (this.selectedLibrary === "") { 47 | this.selectedLibrary = this.libraries[0].id; 48 | } 49 | 50 | // The 'library-loaded' flag being set on the host element indicates that 51 | // this is the merged config and the rest of the UI should be set up 52 | // now. Otherwise we'll need to create the config and reload the app. 53 | const { hostElement } = this.appContext; 54 | if (hostElement.hasAttribute("library-loaded")) { 55 | hostElement.style.display = "block"; 56 | 57 | // When running in netlify this lets us show a loading spinner, see 58 | // `viewer/build/index.html` 59 | const iframe = parent.document.getElementById( 60 | "vgs_web_library_viewer_iframe" 61 | ); 62 | if (iframe) { 63 | iframe.style.display = "block"; 64 | } 65 | // setTimeout is necessary here in order to make sure our custom 66 | // commands have initialized. 67 | // eslint-disable-next-line @typescript-eslint/no-misused-promises 68 | setTimeout(async () => { 69 | await this._displayUI(this.selectedLibrary); 70 | if (!parent.onhashchange) { 71 | parent.onhashchange = () => this._handleHashChangeEvent(); 72 | } 73 | }, 100); 74 | } else { 75 | hostElement.style.display = "none"; 76 | parent.location.hash = this.selectedLibrary; 77 | // eslint-disable-next-line @typescript-eslint/no-misused-promises 78 | setTimeout(() => this._loadLibrary(this.selectedLibrary), 100); 79 | } 80 | } 81 | 82 | protected override async _onDestroy(): Promise { 83 | await super._onDestroy(); 84 | parent.onhashchange = undefined; 85 | } 86 | 87 | protected override _getSerializableProperties(): PropertyDefs { 88 | const props = super._getSerializableProperties(); 89 | return { 90 | ...props, 91 | libraries: { 92 | serializeModes: ["initial"], 93 | }, 94 | }; 95 | } 96 | 97 | @command("library-viewer.load-library") 98 | protected async _executeLoadLibrary(libraryConfig: Library): Promise { 99 | const { id: libraryId } = libraryConfig; 100 | const { hostElement } = this.appContext; 101 | const viewerId = "library-viewer"; 102 | 103 | // Gather the materials needed for the library to load. 104 | const [ 105 | sampleAppConfig, 106 | sampleLayout, 107 | customLibrary, 108 | viewerAppConfig, 109 | viewerLayout, 110 | ] = await Promise.all([ 111 | import( 112 | /* webpackExclude: /node_modules/ */ `../../../../../libraries/${libraryId}/app/app.json` 113 | ), 114 | import( 115 | /* webpackExclude: /node_modules/ */ `../../../../../libraries/${libraryId}/app/layout.xml` 116 | ), 117 | import( 118 | /* webpackExclude: /(node_modules|library-viewer)/ */ `../../../../../libraries/${libraryId}/build/main.js` 119 | ), 120 | import( 121 | /* webpackExclude: /node_modules/ */ `../../../../../libraries/${viewerId}/app/app.json` 122 | ), 123 | import( 124 | /* webpackExclude: /node_modules/ */ `../../../../../libraries/${viewerId}/app/layout.xml` 125 | ), 126 | ]); 127 | 128 | // Parse the currently loaded layout and the library sample layout into 129 | // JSON objects. 130 | const [libraryXml, baseXml] = await Promise.all([ 131 | parseString(sampleLayout?.default as string), 132 | parseString(viewerLayout?.default as string), 133 | ]); 134 | 135 | // Merge their top level attributes to get all the namespaces. 136 | baseXml.layout.$ = { ...baseXml.layout.$, ...libraryXml.layout.$ }; 137 | 138 | // Add the sample layout items as children of the library-viewer component. 139 | const mergeRoot = 140 | baseXml.layout.split[0].stack[0]["library:library-viewer"][0]; 141 | this._getChildElementNames( 142 | libraryXml.layout as Record 143 | ).forEach( 144 | (element) => (mergeRoot[element] = libraryXml.layout[element]) 145 | ); 146 | 147 | // Output the merged layout as a string. 148 | const xmlBuilder = new Builder(); 149 | const mergedLayout = xmlBuilder.buildObject(baseXml); 150 | 151 | // Merge the 'items' collections of the two AppConfig objects, taking 152 | // care not to modify the originals. 153 | const mergedAppConfig = { ...viewerAppConfig.default } as AppConfig; 154 | const mergedItems = [...viewerAppConfig.default.items]; 155 | mergedAppConfig.items = mergedItems; 156 | const libraryAppConfig = sampleAppConfig?.default as AppConfig; 157 | libraryAppConfig.items.forEach((item) => { 158 | if (!mergedItems.some((appItem) => appItem.id === item.id)) { 159 | mergedItems.push(item); 160 | } else { 161 | const existingItem = mergedItems.find( 162 | (appItem) => appItem.id === item.id 163 | ); 164 | const mergedItem = { ...existingItem, ...item }; 165 | mergedItems.splice( 166 | mergedItems.indexOf(existingItem), 167 | 1, 168 | mergedItem 169 | ); 170 | } 171 | }); 172 | 173 | // Cleanly shutdown the current application. 174 | await this.appContext.shutdown(); 175 | 176 | // Bootstrap a new viewer application in the current iframe with the 177 | // merged layout and config. 178 | (window.require as any)(["require", "web"], (require, webViewer) => { 179 | require([ 180 | "@vertigis/web-libraries!/common", 181 | "@vertigis/web-libraries!/web", 182 | "/main.js", 183 | ], (...libs) => { 184 | const options = { 185 | appConfig: mergedAppConfig, 186 | debugMode: true, 187 | hostElement, 188 | layout: mergedLayout, 189 | libraries: [ 190 | ...libs.map((lib) => lib.default), 191 | customLibrary.default, 192 | ], 193 | applicationParams: [ 194 | ["includeFunctionalTestHelpers", "true"], 195 | ], 196 | }; 197 | webViewer.bootstrap(options); 198 | }); 199 | }); 200 | } 201 | 202 | private async _displayUI(selectedLibrary: string): Promise { 203 | const constructedUrl = 204 | // This import makes the library download available at the constructed url. 205 | ( 206 | await import( 207 | /* webpackExclude: /(node_modules|library-viewer)/ */ 208 | `!!file-loader?{"name": "static/js/[name].[contenthash:8].[ext]"}!../../../../../libraries/${selectedLibrary}/build/main.js` 209 | ) 210 | )?.default as string; 211 | 212 | // This happens in the production build for some reason. 213 | if (constructedUrl.startsWith(".static")) { 214 | this.libraryUrl = constructedUrl.replace(".", "../"); 215 | } else { 216 | this.libraryUrl = constructedUrl; 217 | } 218 | 219 | await Promise.all([ 220 | this.messages 221 | .command("library-viewer.set-libraries") 222 | .execute({ libraries: this.libraries, selectedLibrary }), 223 | this.messages 224 | .command("library-viewer.display-readme") 225 | .execute(selectedLibrary), 226 | ]); 227 | } 228 | 229 | private async _loadLibrary(selectedLibrary: string): Promise { 230 | const { hostElement } = this.appContext; 231 | const library = this.libraries.find( 232 | (library) => library.id === selectedLibrary 233 | ); 234 | if (library) { 235 | hostElement.setAttribute("library-loaded", selectedLibrary); 236 | await this.messages 237 | .command("library-viewer.load-library") 238 | .execute(library); 239 | } 240 | } 241 | 242 | private async _handleHashChangeEvent(): Promise { 243 | await this._loadLibrary(parent.location.hash.substring(1)); 244 | } 245 | 246 | private _getChildElementNames(element: Record): string[] { 247 | return Object.keys(element).filter((key) => key !== "$"); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /libraries/mapillary/src/components/Mapillary/MapillaryModel.ts: -------------------------------------------------------------------------------- 1 | import Point from "@arcgis/core/geometry/Point"; 2 | import { watch } from "@vertigis/arcgis-extensions/support/observableUtils"; 3 | import type { MapModel } from "@vertigis/web/mapping"; 4 | import type { 5 | PropertyDefs, 6 | ComponentModelProperties, 7 | } from "@vertigis/web/models"; 8 | import { 9 | ComponentModelBase, 10 | serializable, 11 | importModel, 12 | } from "@vertigis/web/models"; 13 | import { throttle } from "@vertigis/web/ui"; 14 | import type { IViewer, ViewerImageEvent, LngLat } from "mapillary-js"; 15 | 16 | interface MapillaryModelProperties extends ComponentModelProperties { 17 | mapillaryKey?: string; 18 | searchRadius?: number; 19 | defaultScale?: number; 20 | startSynced?: boolean; 21 | } 22 | 23 | interface MapillaryCamera { 24 | latitude: number; 25 | longitude: number; 26 | heading: number; 27 | tilt: number; 28 | fov: number; 29 | } 30 | 31 | /** 32 | * Convert Mapillary bearing to a Scene's camera rotation. 33 | * @param bearing Mapillary bearing in degrees (degrees relative to due north). 34 | * @returns Scene camera rotation in degrees (degrees rotation of due north). 35 | */ 36 | function getCameraRotationFromBearing(bearing: number): number { 37 | return 360 - bearing; 38 | } 39 | 40 | @serializable 41 | export default class MapillaryModel extends ComponentModelBase { 42 | mapillaryKey: string; 43 | searchRadius: number; 44 | defaultScale: number; 45 | startSynced: boolean; 46 | synchronizePosition: boolean; 47 | 48 | readonly imageQueryUrl = "https://a.mapillary.com/v3/images"; 49 | 50 | // The latest location received from a locationmarker.update event 51 | currentMarkerPosition: { latitude: number; longitude: number }; 52 | updating = false; 53 | 54 | // The computed position of the current Mapillary image 55 | private _currentImagePosition: LngLat; 56 | 57 | private _awaitViewHandle: IHandle; 58 | private _viewerUpdateHandle: IHandle; 59 | private _handleMarkerUpdate = true; 60 | private _synced = false; 61 | 62 | /** 63 | * Handles pov changes once the image position is known. 64 | */ 65 | private readonly _onPerspectiveChange = throttle(async () => { 66 | if (!this.map || !this.mapillary || this.updating) { 67 | return; 68 | } 69 | 70 | this.updating = true; 71 | 72 | const { latitude, longitude, heading, tilt, fov } = 73 | await this._getMapillaryCamera(); 74 | 75 | const centerPoint = new Point({ 76 | latitude, 77 | longitude, 78 | }); 79 | 80 | this._handleMarkerUpdate = false; 81 | 82 | await Promise.all([ 83 | this.messages.commands.locationMarker.update.execute({ 84 | geometry: centerPoint, 85 | heading, 86 | tilt, 87 | fov, 88 | id: this.id, 89 | maps: this.map, 90 | }), 91 | this.synchronizePosition 92 | ? this.messages.commands.map.zoomToViewpoint.execute({ 93 | maps: this.map, 94 | viewpoint: { 95 | rotation: getCameraRotationFromBearing(heading), 96 | targetGeometry: centerPoint, 97 | scale: this.defaultScale, 98 | }, 99 | }) 100 | : undefined, 101 | ]).finally(() => (this.updating = false)); 102 | }, 128); 103 | 104 | private _mapillary: IViewer | undefined; 105 | get mapillary(): IViewer | undefined { 106 | return this._mapillary; 107 | } 108 | set mapillary(instance: IViewer | undefined) { 109 | if (instance === this._mapillary) { 110 | return; 111 | } 112 | 113 | this._viewerUpdateHandle?.remove(); 114 | 115 | // If an instance already exists, clean it up first. 116 | if (this._mapillary) { 117 | // Clean up event handlers. 118 | this.mapillary.off("image", this._onImageChange); 119 | this.mapillary.off("pov", this._onPerspectiveChange); 120 | 121 | // Activating the cover appears to be the best way to "clean up" Mapillary. 122 | // https://github.com/mapillary/mapillary-js/blob/8b6fc2f36e3011218954d95d601062ff6aa41ad9/src/viewer/ComponentController.ts#L184-L192 123 | this.mapillary.activateCover(); 124 | 125 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 126 | this._unsyncMaps(); 127 | } 128 | 129 | this._mapillary = instance; 130 | 131 | // A new instance is being set - add the event handlers. 132 | if (instance) { 133 | // Listen for changes to the currently displayed mapillary image 134 | this.mapillary.on("image", this._onImageChange); 135 | 136 | // Change the current mapillary image when the location marker is moved. 137 | this._viewerUpdateHandle = 138 | this.messages.events.locationMarker.updated.subscribe((event) => 139 | this._handleViewerUpdate(event) 140 | ); 141 | } 142 | 143 | // We may need to sync if the map and initialized view have arrived first. 144 | if (!this._synced && this.map.view) { 145 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 146 | this._syncMaps(); 147 | } 148 | } 149 | 150 | private _map: MapModel | undefined; 151 | get map(): MapModel | undefined { 152 | return this._map; 153 | } 154 | @importModel("map-extension") 155 | set map(instance: MapModel | undefined) { 156 | if (instance === this._map) { 157 | return; 158 | } 159 | 160 | // If an instance already exists, clean it up first. 161 | if (this._map) { 162 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 163 | this._unsyncMaps(); 164 | } 165 | this._map = instance; 166 | 167 | // We may need to wait for the view to arrive before proceeding. 168 | this._awaitViewHandle = watch(this.map, "view", (view) => { 169 | if (view) { 170 | this._awaitViewHandle.remove(); 171 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 172 | this._syncMaps(); 173 | } 174 | }); 175 | } 176 | 177 | async recenter(): Promise { 178 | const { latitude, longitude, heading } = 179 | await this._getMapillaryCamera(); 180 | 181 | const centerPoint = new Point({ 182 | latitude, 183 | longitude, 184 | }); 185 | 186 | await this.messages.commands.map.zoomToViewpoint.execute({ 187 | maps: this.map, 188 | viewpoint: { 189 | rotation: getCameraRotationFromBearing(heading), 190 | targetGeometry: centerPoint, 191 | scale: this.defaultScale, 192 | }, 193 | }); 194 | } 195 | 196 | // TODO: Bring back when CORS issue is resolved. 197 | // https://forum.mapillary.com/t/web-app-blocked-by-cors-policy-mapillary/5357 198 | // https://forum.mapillary.com/t/cors-error-when-requesting-coverage-vector-tiles/5303 199 | 200 | // async moveCloseToPosition(latitude: number, longitude: number): 201 | // Promise {try {const url = 202 | // `https://tiles.mapillary.com/maps/vtp/mly1_public/2/17/${latitude}/${longitude}?access_token=${this.mapillaryKey}`; 203 | // const response = await fetch(url); const data = await response.json(); 204 | // const imgKey = data?.features?.[0]?.properties?.key; 205 | 206 | // if (imgKey) { 207 | // await this.mapillary.moveTo(imgKey); 208 | // this.updating = false; 209 | // } else { 210 | // this.updating = false; 211 | // this._activateCover(); 212 | // } 213 | // } catch { 214 | // this.updating = false; 215 | // this._activateCover(); 216 | // } 217 | 218 | protected override async _onDestroy(): Promise { 219 | await super._onDestroy(); 220 | this._viewerUpdateHandle?.remove(); 221 | this._awaitViewHandle?.remove(); 222 | } 223 | 224 | protected override _getSerializableProperties(): PropertyDefs { 225 | const props = super._getSerializableProperties(); 226 | return { 227 | ...props, 228 | mapillaryKey: { 229 | serializeModes: ["initial"], 230 | default: "", 231 | }, 232 | searchRadius: { 233 | serializeModes: ["initial"], 234 | default: 500, 235 | }, 236 | defaultScale: { 237 | serializeModes: ["initial"], 238 | default: 3000, 239 | }, 240 | startSynced: { 241 | serializeModes: ["initial"], 242 | default: true, 243 | }, 244 | title: { 245 | ...this._toPropertyDef(props.title), 246 | default: "language-web-incubator-mapillary-title", 247 | }, 248 | icon: { 249 | ...this._toPropertyDef(props.icon), 250 | default: "map-3rd-party", 251 | }, 252 | }; 253 | } 254 | 255 | /** 256 | * Setup the initial state of the maps such as the location marker and map 257 | * position. 258 | */ 259 | private async _syncMaps(): Promise { 260 | if (!this.map || !this.mapillary || this._synced) { 261 | return; 262 | } 263 | 264 | this._synced = true; 265 | this.synchronizePosition = this.startSynced ?? true; 266 | 267 | // Set mapillary as close as possible to the center of the view 268 | // await this.moveCloseToPosition( 269 | // this.map.view.center.latitude, 270 | // this.map.view.center.longitude 271 | // ); 272 | 273 | // Create location marker based on current location from Mapillary and 274 | // pan/zoom VertiGIS Studio map to the location. 275 | const { latitude, longitude, heading, tilt, fov } = 276 | await this._getMapillaryCamera(); 277 | 278 | const centerPoint = new Point({ latitude, longitude }); 279 | await Promise.all([ 280 | this.messages.commands.locationMarker.create.execute({ 281 | fov, 282 | geometry: centerPoint, 283 | heading, 284 | tilt, 285 | id: this.id, 286 | maps: this.map, 287 | // When the CORS issue above is resolved change this to `true` 288 | userDraggable: false, 289 | }), 290 | this.synchronizePosition 291 | ? this.messages.commands.map.zoomToViewpoint.execute({ 292 | maps: this.map, 293 | viewpoint: { 294 | rotation: getCameraRotationFromBearing(heading), 295 | targetGeometry: centerPoint, 296 | scale: this.defaultScale, 297 | }, 298 | }) 299 | : undefined, 300 | ]); 301 | } 302 | 303 | private async _unsyncMaps(): Promise { 304 | this._synced = false; 305 | 306 | await this.messages.commands.locationMarker.remove.execute({ 307 | id: this.id, 308 | maps: this.map, 309 | }); 310 | } 311 | 312 | private _handleViewerUpdate(event: any): void { 313 | if (this._handleMarkerUpdate) { 314 | const updatePoint = event.geometry as Point; 315 | this.currentMarkerPosition = { 316 | latitude: updatePoint.latitude, 317 | longitude: updatePoint.longitude, 318 | }; 319 | } 320 | this._handleMarkerUpdate = true; 321 | } 322 | 323 | /** 324 | * When the 'merged' property is set on the image we know that the position 325 | * reported will be the computed location rather than a raw GPS value. We 326 | * ignore all updates sent while the computed position is unknown as the raw 327 | * GPS value can be inaccurate and will not exactly match the observed 328 | * position of the camera. See: 329 | * https://bl.ocks.org/oscarlorentzon/16946cb9eedfad2a64669cb1121e6c75 330 | */ 331 | private readonly _onImageChange = (event: ViewerImageEvent) => { 332 | const { image } = event; 333 | if (image.merged) { 334 | this._currentImagePosition = image.lngLat; 335 | 336 | // Set the initial marker position for this image. 337 | this._onPerspectiveChange(); 338 | 339 | // Handle further pov changes. 340 | this.mapillary.on("pov", this._onPerspectiveChange); 341 | } else { 342 | this._currentImagePosition = undefined; 343 | this.mapillary.off("pov", this._onPerspectiveChange); 344 | } 345 | }; 346 | 347 | /** 348 | * Gets the current POV of the mapillary camera 349 | */ 350 | private async _getMapillaryCamera(): Promise { 351 | if (!this.mapillary) { 352 | return undefined; 353 | } 354 | 355 | // Will return a raw GPS value if the image position has not yet been calculated. 356 | const [{ lat, lng }, { bearing, tilt }, fov] = await Promise.all([ 357 | this._currentImagePosition ?? this.mapillary.getPosition(), 358 | this.mapillary.getPointOfView() as Promise<{ 359 | bearing: number; 360 | tilt: number; 361 | }>, 362 | this.mapillary.getFieldOfView(), 363 | ]); 364 | 365 | return { 366 | latitude: lat, 367 | longitude: lng, 368 | heading: bearing, 369 | tilt: tilt + 90, 370 | fov, 371 | }; 372 | } 373 | 374 | private _activateCover() { 375 | this.updating = false; 376 | this.mapillary.activateCover(); 377 | } 378 | } 379 | --------------------------------------------------------------------------------