├── .eslintignore ├── examples ├── Cesium ├── CesiumUnminified ├── data │ ├── Box0.bin │ ├── icon.png │ ├── image-static.png │ ├── geojson │ │ ├── ground_vector_data.geojson │ │ └── vector_data.geojson │ ├── Box.gltf │ └── arrow5.dae ├── inject_ol_cesium.js ├── _proj21781.js ├── sidebyside.js ├── lazy.js ├── 3dtiles.html ├── kml.html ├── imageWMS.html ├── image-static.html ├── customProj.html ├── tracking.html ├── mvt.html ├── kml.js ├── sidebyside.html ├── selection.html ├── index.html ├── lazy.html ├── wmts.html ├── 3dtiles.js ├── imageWMS.js ├── buildings.html ├── exports.js ├── image-static.js ├── print.html ├── customProj.js ├── fillstyle.html ├── main.js ├── selection.js ├── main.html ├── groundvectors.html ├── rotate.html ├── synthvectors.html ├── icon-position.html ├── layer-group.js ├── wmts.js ├── print.js ├── tracking.js ├── exports.html ├── mvt.js ├── vectors.html ├── fillstyle.js ├── buildings.js ├── rastersync.js ├── rastersync.html ├── icon-position.js ├── synthvectors.js ├── overlay.html ├── layer-group.html ├── rotate.js ├── groundvectors.js ├── overlay.js ├── _code-sandbox.js └── ol.css ├── src ├── global.d.ts ├── olcs │ ├── olcs.css │ ├── print.ts │ ├── print │ │ ├── printUtils.ts │ │ ├── computeRectangle.ts │ │ ├── takeCesiumScreenshot.ts │ │ └── drawCesiumMask.ts │ ├── math.ts │ ├── contrib │ │ └── LazyLoader.ts │ ├── dashPatternConversion.test.ts │ ├── core │ │ └── VectorLayerCounterpart.ts │ ├── AutoRenderLoop.ts │ ├── util.ts │ ├── OverlaySynchronizer.ts │ ├── VectorSynchronizer.ts │ └── RasterSynchronizer.ts └── olcs.ts ├── css └── olcs.css ├── gh-pages-template ├── images │ ├── body-bg.jpg │ ├── header-bg.jpg │ ├── highlight-bg.jpg │ ├── sidebar-bg.jpg │ ├── apidoc-button.png │ ├── github-button.png │ ├── download-button.png │ └── examples-button.png ├── _config.yml ├── index.md └── stylesheets │ ├── pygment_trac.css │ └── print.css ├── .gitignore ├── .github ├── renovate.json └── workflows │ └── test.yml ├── tsconfig.json ├── RELEASE.md ├── LICENSE ├── .eslintrc.yaml ├── CONTRIBUTING.md ├── PROPERTIES.md ├── package.json ├── README.md └── .eslintrc-es6.yaml /.eslintignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | -------------------------------------------------------------------------------- /examples/Cesium: -------------------------------------------------------------------------------- 1 | ../../node_modules/cesium/Build/Cesium -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const Cesium: typeof import('cesium'); -------------------------------------------------------------------------------- /examples/CesiumUnminified: -------------------------------------------------------------------------------- 1 | ../../node_modules/cesium/Build/CesiumUnminified/ -------------------------------------------------------------------------------- /src/olcs/olcs.css: -------------------------------------------------------------------------------- 1 | .olcs-hideoverlay { 2 | visibility:hidden; 3 | } 4 | -------------------------------------------------------------------------------- /css/olcs.css: -------------------------------------------------------------------------------- 1 | .olcs-hideoverlay .ol-overlay-container { 2 | visibility:hidden; 3 | } 4 | -------------------------------------------------------------------------------- /examples/data/Box0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/examples/data/Box0.bin -------------------------------------------------------------------------------- /examples/data/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/examples/data/icon.png -------------------------------------------------------------------------------- /examples/data/image-static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/examples/data/image-static.png -------------------------------------------------------------------------------- /gh-pages-template/images/body-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/body-bg.jpg -------------------------------------------------------------------------------- /gh-pages-template/images/header-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/header-bg.jpg -------------------------------------------------------------------------------- /gh-pages-template/images/highlight-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/highlight-bg.jpg -------------------------------------------------------------------------------- /gh-pages-template/images/sidebar-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/sidebar-bg.jpg -------------------------------------------------------------------------------- /gh-pages-template/images/apidoc-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/apidoc-button.png -------------------------------------------------------------------------------- /gh-pages-template/images/github-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/github-button.png -------------------------------------------------------------------------------- /gh-pages-template/images/download-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/download-button.png -------------------------------------------------------------------------------- /gh-pages-template/images/examples-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openlayers/ol-cesium/HEAD/gh-pages-template/images/examples-button.png -------------------------------------------------------------------------------- /src/olcs/print.ts: -------------------------------------------------------------------------------- 1 | export * from './print/computeRectangle.js'; 2 | export * from './print/drawCesiumMask.js'; 3 | export * from './print/takeCesiumScreenshot.js'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.build/ 2 | /lib/ 3 | /apidoc/ 4 | dist/ 5 | node_modules/ 6 | /examples/example-list.js 7 | /compilation-stats.json 8 | /*.zip 9 | *.swp 10 | /*.tgz 11 | .parcel-cache/ 12 | -------------------------------------------------------------------------------- /gh-pages-template/_config.yml: -------------------------------------------------------------------------------- 1 | title: Ol-Cesium 2 | description: OpenLayers - Cesium integration library 3 | google_analytics: 4 | show_downloads: true 5 | theme: jekyll-theme-architect 6 | exclude: [] 7 | gems: 8 | - jekyll-mentions 9 | -------------------------------------------------------------------------------- /src/olcs/print/printUtils.ts: -------------------------------------------------------------------------------- 1 | export {computeRectangle} from './computeRectangle.js'; 2 | 3 | export type {RectangleOutput} from './computeRectangle.js'; 4 | 5 | export function computeDPIForPDF(pdfPixels: [number, number], imageDimensions: [number, number]): number { 6 | const widthDPI = imageDimensions[0] / (pdfPixels[0] / 72); 7 | const heightDPI = imageDimensions[1] / (pdfPixels[1] / 72); 8 | 9 | // this can be fractional 10 | return (widthDPI + heightDPI) / 2; 11 | } 12 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", "schedule:earlyMondays"], 4 | "lockFileMaintenance": { 5 | "enabled": true, 6 | "automerge": true 7 | }, 8 | "packageRules": [{ 9 | "matchPackagePrefixes": ["ol", "cesium"], 10 | "matchUpdateTypes": ["minor"], 11 | "enabled": false 12 | }, { 13 | "matchUpdateTypes": ["minor", "patch"], 14 | "groupName": "all patch and minor versions" 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /src/olcs/math.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts radians to to degrees. 3 | * 4 | * @param angleInRadians Angle in radians. 5 | * @return Angle in degrees. 6 | */ 7 | export function toDegrees(angleInRadians: number): number { 8 | return angleInRadians * 180 / Math.PI; 9 | } 10 | 11 | 12 | /** 13 | * Converts degrees to radians. 14 | * 15 | * @param angleInDegrees Angle in degrees. 16 | * @return Angle in radians. 17 | */ 18 | export function toRadians(angleInDegrees: number): number { 19 | return angleInDegrees * Math.PI / 180; 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | main: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: 20 16 | - name: CI 17 | run: npm ci 18 | - name: Lint 19 | run: npm run lint 20 | - name: Prepare 21 | run: npm run prepare 22 | - name: Test 23 | run: npm test 24 | - name: Build examples 25 | run: npm run build-examples 26 | -------------------------------------------------------------------------------- /examples/inject_ol_cesium.js: -------------------------------------------------------------------------------- 1 | // This file is ES5 on purpose. 2 | // It does not go through webpack; it is directly loaded by the html. 3 | (function() { 4 | /* eslint-disable no-var */ 5 | var mode = window.location.href.match(/mode=([a-z0-9\-]+)\&?/i); 6 | var isDev = mode && mode[1] === 'dev'; 7 | window.IS_DEV = isDev; 8 | var cs = 'node_modules/cesium/Build/' + (isDev ? 'CesiumUnminified/Cesium.js' : 'Cesium/Cesium.js'); 9 | 10 | window.CESIUM_URL = `${cs}`; 11 | if (!window.LAZY_CESIUM) { 12 | document.write(``); 13 | } 14 | 15 | /* eslint-enable no-var */ 16 | })(); 17 | -------------------------------------------------------------------------------- /examples/_proj21781.js: -------------------------------------------------------------------------------- 1 | 2 | import {register as olProj4Register} from 'ol/proj/proj4.js'; 3 | import proj4 from 'proj4'; 4 | import {get as getProjection} from 'ol/proj.js'; 5 | 6 | const epsg21781def = [ 7 | '+proj=somerc', 8 | '+lat_0=46.95240555555556', 9 | '+lon_0=7.439583333333333', 10 | '+k_0=1', 11 | '+x_0=600000', 12 | '+y_0=200000', 13 | '+ellps=bessel', 14 | '+towgs84=674.374,15.056,405.346,0,0,0,0', 15 | '+units=m', 16 | '+no_defs' 17 | ].join(' '); 18 | const epsg21781extent = [420000, 30000, 900000, 350000]; 19 | 20 | proj4.defs('EPSG:21781', epsg21781def); 21 | 22 | olProj4Register(proj4); 23 | getProjection('EPSG:21781').setExtent(epsg21781extent); 24 | 25 | -------------------------------------------------------------------------------- /gh-pages-template/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Download release](https://cdn.rawgit.com/ahocevar/a95de14fc607dcecce5a/raw/8dab32630b9c76526caa4ac152a339384ef0efd9/download-button.png)](https://github.com/openlayers/ol-cesium/releases/) [![Browse examples](https://cdn.rawgit.com/ahocevar/a95de14fc607dcecce5a/raw/95ff79a24b0ce611f99d46b113a215e6cb75b05a/examples-button.png)](http://openlayers.org/ol-cesium/examples/) [![View API docs](https://cdn.rawgit.com/ahocevar/a95de14fc607dcecce5a/raw/c027bfb4f33b163600ff55c7b7ecd4647fcbfc42/docs-button.png)](http://openlayers.org/ol-cesium/apidoc) 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/olcs/contrib/LazyLoader.ts: -------------------------------------------------------------------------------- 1 | export default class LazyLoader { 2 | private promise: Promise | undefined; 3 | private url_: string; 4 | 5 | /** 6 | * @param url 7 | * @api 8 | */ 9 | constructor(url: string) { 10 | this.url_ = url; 11 | } 12 | 13 | /** 14 | * Load Cesium by injecting a script tag. 15 | * @api 16 | */ 17 | load(): Promise { 18 | if (!this.promise) { 19 | // not yet loading 20 | this.promise = new Promise((resolve, reject) => { 21 | const script = document.createElement('script'); 22 | script.onload = () => resolve(); 23 | script.onerror = () => reject(); 24 | document.head.appendChild(script); 25 | script.src = this.url_; 26 | }); 27 | } 28 | return this.promise; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["es2023", "dom"], 5 | "allowJs": true, 6 | "baseUrl": "./", 7 | "skipLibCheck": true, 8 | "moduleResolution": "Node", 9 | "module": "ES2022", 10 | "verbatimModuleSyntax": true, 11 | "outDir": "./lib", 12 | "inlineSourceMap": true, 13 | "noImplicitAny": true, 14 | "declarationMap": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "allowSyntheticDefaultImports": true, 17 | "paths": { 18 | "ol/*": ["node_modules/ol/*"] 19 | }, 20 | "declaration": true, 21 | "declarationDir": "./lib/types" 22 | }, 23 | "include": [ 24 | "src/**/*.ts" 25 | ], 26 | "typeAcquisition": { 27 | "enable": false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Howto create a new release 2 | 3 | ## Frequency 4 | Each month, a new release is published in accordance to https://github.com/openlayers/ol-cesium/wiki/Versioning-policy. 5 | 6 | ## Steps 7 | - Update OpenLayers and Cesium dependencies to latest stable version 8 | - Check the Cesium CHANGES.md for API changes and update the Cesium externs 9 | - Update the version number in `package.json` 10 | - Compile from scratch and run all tests: 11 | - npm run lint 12 | - npm run typecheck 13 | - npm start; open http://localhost:3000/examples # test all examples (dev mode) 14 | - npm run build-examples; python3 -m http.server --directory dist 12345; open http://localhost:12345/examples # test all examples (built mode) 15 | - Publish with: 16 | - npm version minor # or patch 17 | - npm pack 18 | - npm publish # this will publish package olcs (ol-cesium package is obsolete and not updated anymore) 19 | - git push --tags 20 | -------------------------------------------------------------------------------- /examples/sidebyside.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olSourceOSM from 'ol/source/OSM.js'; 3 | import olLayerTile from 'ol/layer/Tile.js'; 4 | import olMap from 'ol/Map.js'; 5 | import {transform} from 'ol/proj.js'; 6 | import olView from 'ol/View.js'; 7 | 8 | const view = new olView({ 9 | center: transform([25, 20], 'EPSG:4326', 'EPSG:3857'), 10 | zoom: 3, 11 | rotation: Math.PI / 6 12 | }); 13 | 14 | const ol2d = new olMap({ 15 | layers: [ 16 | new olLayerTile({ 17 | source: new olSourceOSM() 18 | }) 19 | ], 20 | target: 'map2d', 21 | view 22 | }); 23 | 24 | const ol3d = new OLCesium({map: ol2d, target: 'mapCesium'}); 25 | 26 | ol3d.getCesiumScene(); 27 | ol3d.setEnabled(true); 28 | 29 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 30 | 31 | //##REMOVE## Keep this tag, split code here for code sandbox 32 | 33 | import {initCodeSandbox} from './_code-sandbox.js'; 34 | initCodeSandbox('rawjs/sidebyside.js'); 35 | -------------------------------------------------------------------------------- /examples/lazy.js: -------------------------------------------------------------------------------- 1 | import {transform} from 'ol/proj.js'; 2 | import olView from 'ol/View.js'; 3 | import {defaults as olControlDefaults} from 'ol/control.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import olMap from 'ol/Map.js'; 7 | import {ContribManager} from 'olcs'; 8 | 9 | const ol2d = new olMap({ 10 | layers: [ 11 | new olLayerTile({ 12 | source: new olSourceOSM() 13 | }) 14 | ], 15 | controls: olControlDefaults({ 16 | attributionOptions: { 17 | collapsible: false 18 | } 19 | }), 20 | target: 'mapCesium', 21 | view: new olView({ 22 | center: transform([25, 20], 'EPSG:4326', 'EPSG:3857'), 23 | zoom: 3 24 | }) 25 | }); 26 | 27 | window['manager'] = new ContribManager(window.CESIUM_URL, { 28 | map: ol2d, 29 | cameraExtentInRadians: [0.0897, 0.7923, 0.2003, 0.8417] 30 | }); 31 | 32 | //##REMOVE## Keep this tag, split code here for code sandbox 33 | 34 | import {initCodeSandbox} from './_code-sandbox.js'; 35 | initCodeSandbox('rawjs/lazy.js'); 36 | -------------------------------------------------------------------------------- /src/olcs.ts: -------------------------------------------------------------------------------- 1 | import OLCesium from './olcs/OLCesium.js'; 2 | export default OLCesium; 3 | 4 | export type {OLCesiumOptions} from './olcs/OLCesium.js'; 5 | 6 | export {default as AbstractSynchronizer} from './olcs/AbstractSynchronizer.js'; 7 | export {default as RasterSynchronizer} from './olcs/RasterSynchronizer.js'; 8 | export {default as VectorSynchronizer} from './olcs/VectorSynchronizer.js'; 9 | export {default as OverlaySynchronizer} from './olcs/OverlaySynchronizer.js'; 10 | 11 | export {default as FeatureConverter} from './olcs/FeatureConverter.js'; 12 | export {default as OLCSCamera} from './olcs/Camera.js'; 13 | 14 | // Core api functions 15 | export * from './olcs/core.js'; 16 | export {default as OLImageryProvider} from './olcs/core/OLImageryProvider.js'; 17 | export {default as VectorLayerCounterpart} from './olcs/core/VectorLayerCounterpart.js'; 18 | 19 | // Print functions 20 | export * from './olcs/print.js'; 21 | 22 | // Contrib Manager 23 | export {default as ContribManager} from './olcs/contrib/Manager.js'; 24 | export {default as ContribLazyLoader} from './olcs/contrib/LazyLoader.js'; 25 | -------------------------------------------------------------------------------- /src/olcs/print/computeRectangle.ts: -------------------------------------------------------------------------------- 1 | export interface RectangleOutput { 2 | scaling: [number, number]; 3 | offsetX: number; 4 | offsetY: number; 5 | width: number; 6 | height: number; 7 | } 8 | 9 | export function computeRectangle(canvas: HTMLCanvasElement, tw: number, th: number): RectangleOutput { 10 | 11 | const maskAspectRatio = tw / th; 12 | let maskSize; 13 | 14 | if (maskAspectRatio > 1) { 15 | // landscape 16 | maskSize = [canvas.width, canvas.width / maskAspectRatio]; 17 | if (maskSize[1] > canvas.height) { 18 | maskSize = [canvas.height * maskAspectRatio, canvas.height]; 19 | } 20 | } 21 | else { 22 | // portrait 23 | maskSize = [canvas.height * maskAspectRatio, canvas.height]; 24 | if (maskSize[0] > canvas.width) { 25 | maskSize = [canvas.width, canvas.width / maskAspectRatio]; 26 | } 27 | } 28 | 29 | return { 30 | scaling: [maskSize[0] / canvas.width, maskSize[1] / canvas.height], 31 | width: maskSize[0], 32 | height: maskSize[1], 33 | offsetX: (canvas.width - maskSize[0]) / 2, 34 | offsetY: (canvas.height - maskSize[1]) / 2, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /examples/3dtiles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | 3d tiles example 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/kml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Kml example 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/olcs/print/takeCesiumScreenshot.ts: -------------------------------------------------------------------------------- 1 | import type {Scene} from 'cesium'; 2 | 3 | interface ScreenshotOptions { 4 | offsetX: number; 5 | offsetY: number; 6 | width: number; 7 | height: number; 8 | } 9 | 10 | /** 11 | */ 12 | export function takeScreenshot(scene: Scene, options: ScreenshotOptions): Promise { 13 | return new Promise((resolve, reject) => { 14 | // preserveDrawingBuffers is false so we render on demand and immediately read the buffer 15 | const remover = scene.postRender.addEventListener(() => { 16 | remover(); 17 | try { 18 | let url; 19 | 20 | if (options) { 21 | const smallerCanvas = document.createElement('canvas'); 22 | 23 | smallerCanvas.width = options.width; 24 | smallerCanvas.height = options.height; 25 | smallerCanvas.getContext('2d').drawImage( 26 | scene.canvas, 27 | options.offsetX, options.offsetY, options.width, options.height, 28 | 0, 0, options.width, options.height); 29 | url = smallerCanvas.toDataURL(); 30 | } 31 | else { 32 | url = scene.canvas.toDataURL(); 33 | } 34 | resolve(url); 35 | } 36 | catch (e) { 37 | reject(e); 38 | } 39 | }); 40 | 41 | scene.requestRender(); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /examples/imageWMS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Image WMS example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/image-static.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Image static example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/customProj.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Custom projection example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014-present, OpenLayers Contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /examples/tracking.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Tracking example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
An OpenLayers point moves randomly. It is tracked in 3D.
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/mvt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | MapBox rasterized Imagery Provider example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - openlayers 4 | - .eslintrc-es6.yaml 5 | - plugin:import/recommended 6 | - plugin:import/typescript 7 | settings: 8 | import/resolver: 9 | # You will also need to install and configure the TypeScript resolver 10 | # See also https://github.com/import-js/eslint-import-resolver-typescript#configuration 11 | typescript: true 12 | node: true 13 | rules: 14 | no-console: 0 15 | comma-dangle: 0 16 | no-extra-boolean-cast: 0 17 | brace-style: 0 18 | prefer-template: 0 19 | no-multiple-empty-lines: 0 20 | valid-jsdoc: 0 21 | indent: [2, 2, { 22 | VariableDeclarator: 2, 23 | SwitchCase: 1, 24 | MemberExpression: 2, 25 | FunctionDeclaration: { 26 | parameters: 2, 27 | body: 1 28 | }, 29 | FunctionExpression: { 30 | parameters: 2, 31 | body: 1 32 | }, 33 | CallExpression: { 34 | arguments: 2 35 | } 36 | }] 37 | '@typescript-eslint/consistent-type-imports': error 38 | '@typescript-eslint/no-unused-vars': [error, {vars: all, args: none}] 39 | overrides: 40 | - files: [ "examples/*.js" ] 41 | rules: 42 | import/no-unresolved: 0 43 | - files: ['*.ts'] 44 | rules: 45 | import/extensions: [ 46 | "error", 47 | { 48 | "ts": "never", 49 | "js": "always" 50 | } 51 | ] 52 | globals: 53 | webpack: false 54 | Cesium: false 55 | -------------------------------------------------------------------------------- /examples/kml.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import {transform} from 'ol/proj.js'; 3 | import olView from 'ol/View.js'; 4 | import {defaults as olControlDefaults} from 'ol/control.js'; 5 | import olSourceOSM from 'ol/source/OSM.js'; 6 | import olLayerTile from 'ol/layer/Tile.js'; 7 | import olMap from 'ol/Map.js'; 8 | 9 | const Cesium = window.Cesium; 10 | const ol2d = new olMap({ 11 | layers: [ 12 | new olLayerTile({ 13 | source: new olSourceOSM() 14 | }) 15 | ], 16 | controls: olControlDefaults({ 17 | attributionOptions: { 18 | collapsible: false 19 | } 20 | }), 21 | target: 'mapCesium', 22 | view: new olView({ 23 | center: transform([25, 20], 'EPSG:4326', 'EPSG:3857'), 24 | zoom: 3 25 | }) 26 | }); 27 | 28 | const ol3d = new OLCesium({map: ol2d}); 29 | const scene = ol3d.getCesiumScene(); 30 | 31 | ol3d.getDataSources().add( 32 | Cesium.KmlDataSource.load( 33 | 'https://api3.geo.admin.ch/ogcproxy?url=' + 34 | 'https%3A%2F%2Fdav0.bgdi.admin.ch%2Fbazl_web%2FActive_Obstacles.kmz', 35 | { 36 | camera: scene.camera, 37 | canvas: scene.canvas 38 | } 39 | ) 40 | ); 41 | 42 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 43 | 44 | //##REMOVE## Keep this tag, split code here for code sandbox 45 | 46 | import {initCodeSandbox} from './_code-sandbox.js'; 47 | initCodeSandbox('rawjs/kml.js'); 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Ol-Cesium 2 | 3 | Thanks for your interest in contributing to Ol-Cesium. 4 | 5 | ## Contributor License Agreement 6 | 7 | Your contribution will be under our [license](https://raw.githubusercontent.com/openlayers/ol-cesium/master/LICENSE) 8 | as per [GitHub's terms of service](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license). 9 | 10 | ## Setting up development environment 11 | 12 | You will obviously start by 13 | [forking](https://github.com/openlayers/ol-cesium/fork) the ol-cesium repository. 14 | 15 | ```bash 16 | npm i 17 | npm start 18 | browse http://localhost:1234 19 | ``` 20 | 21 | ### OLCesium import 22 | 23 | The examples use a compiled version of OLCesium. If you want to use the source code directly you can define an alias. 24 | See https://en.parceljs.org/module_resolution.html#aliases 25 | 26 | ### The `check` target 27 | 28 | ```bash 29 | npm run check 30 | ``` 31 | 32 | Run this before every commit. It will catch many problems quickly. 33 | 34 | ### Address a single issue or add a single item of functionality 35 | 36 | Please submit separate pull requests for separate issues. This allows each to 37 | be reviewed on its own merits. 38 | 39 | ### Typescript imports 40 | 41 | Write typescript files with the ".ts" extension. 42 | But import these files with a ".js" extension. 43 | This is a useful strangeness of Typescript which, in the end, produces correct transpiled code. 44 | -------------------------------------------------------------------------------- /examples/sidebyside.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Side-by-side example 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/selection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Selection example 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
A country may be highlighted by clicking it on the OpenLayers map. 22 |
It gets automatically selected in Cesium. 23 |
If you see flickering in Cesium, please check your graphic card drivers.
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OL-Cesium examples 6 | 7 | 8 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/lazy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Lazy initialization 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
Delay downloading the Cesium script and initializing the 3D globe.
21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/wmts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ol-Cesium | WMTS example 8 | 9 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/olcs/dashPatternConversion.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'node:test'; 2 | import assert from 'node:assert'; 3 | 4 | import {dashPattern} from './FeatureConverter.js'; 5 | 6 | function olPatternAsBinary(pattern: number[]): string { 7 | const pattern16 = dashPattern(pattern); 8 | return pattern16.toString(2); 9 | } 10 | 11 | test('pattern-100-100', (t) => { 12 | // Whatever the scaling, the result should be the same. 13 | // Only propoprtion matters. 14 | assert.strictEqual(olPatternAsBinary([100, 100]), '1111111100000000'); 15 | assert.strictEqual(olPatternAsBinary([1, 1]), '1111111100000000'); 16 | assert.strictEqual(olPatternAsBinary([999999, 999999]), '1111111100000000'); 17 | }); 18 | 19 | test('pattern-100-100-100-100', (t) => { 20 | // We should see 4 dashes of equal length. 21 | assert.strictEqual(olPatternAsBinary([100, 100, 100, 100]), '1111000011110000'); 22 | }); 23 | 24 | test('pattern-too-long', (t) => { 25 | // When the pattern is too long, here we have 200 segments 26 | // we should still ensure that the pattern is well formed (I guess): 27 | // - starts with a 1: 28 | // - ends with a 0. 29 | assert.strictEqual(olPatternAsBinary(Array.from({length: 200}, _ => 1)), '1000000000000000'); 30 | }); 31 | 32 | 33 | test('pattern-uneven', (t) => { 34 | // When the pattern is uneven, it is duplicated 35 | assert.strictEqual(olPatternAsBinary([6, 1, 2, 6, 1, 2]), '1111101100000100'); 36 | assert.strictEqual(olPatternAsBinary([6, 1, 2]), '1111101100000100'); 37 | assert.strictEqual(olPatternAsBinary([12, 2, 4]), '1111101100000100'); 38 | }); 39 | -------------------------------------------------------------------------------- /examples/3dtiles.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import {transform} from 'ol/proj.js'; 3 | import olView from 'ol/View.js'; 4 | import {defaults as olControlDefaults} from 'ol/control.js'; 5 | import olSourceOSM from 'ol/source/OSM.js'; 6 | import olLayerTile from 'ol/layer/Tile.js'; 7 | import olMap from 'ol/Map.js'; 8 | 9 | const Cesium = window.Cesium; 10 | 11 | const ol2d = new olMap({ 12 | layers: [ 13 | new olLayerTile({ 14 | source: new olSourceOSM() 15 | }) 16 | ], 17 | controls: olControlDefaults({ 18 | attributionOptions: { 19 | collapsible: false 20 | } 21 | }), 22 | target: 'map', 23 | view: new olView({ 24 | center: transform([25, 20], 'EPSG:4326', 'EPSG:3857'), 25 | zoom: 3 26 | }) 27 | }); 28 | const ol3d = new OLCesium({ 29 | map: ol2d, 30 | }); 31 | const scene = ol3d.getCesiumScene(); 32 | Cesium.CesiumTerrainProvider.fromUrl('https://download.swissgeol.ch/cli_terrain/ch-2m/').then(tp => scene.terrainProvider = tp); 33 | Cesium.Cesium3DTileset.fromUrl('https://sys-3d.dev.bgdi.ch/ch.swisstopo.swisstlm3d.3d/v1/tileset.json').then(ts => scene.primitives.add(ts)); 34 | ol3d.setEnabled(true); 35 | 36 | 37 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 38 | 39 | scene.camera.flyTo({ 40 | destination: Cesium.Cartesian3.fromDegrees(6.54254, 46.50802, 1000.0) 41 | }); 42 | 43 | //##REMOVE## Keep this tag, split code here for code sandbox 44 | 45 | import {initCodeSandbox} from './_code-sandbox.js'; 46 | initCodeSandbox('rawjs/3dtiles.js', 'rawjs/_proj21781.js'); 47 | -------------------------------------------------------------------------------- /examples/imageWMS.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olView from 'ol/View.js'; 3 | import {defaults as olControlDefaults} from 'ol/control.js'; 4 | import olSourceImageWMS from 'ol/source/ImageWMS.js'; 5 | import olSourceOSM from 'ol/source/OSM.js'; 6 | import olLayerImage from 'ol/layer/Image.js'; 7 | import olLayerTile from 'ol/layer/Tile.js'; 8 | import olMap from 'ol/Map.js'; 9 | 10 | const imageWMSSource = new olSourceImageWMS({ 11 | url: 'https://ahocevar.com/geoserver/wms', 12 | params: {'LAYERS': 'topp:states'}, 13 | ratio: 1 14 | }); 15 | 16 | const Cesium = window.Cesium; 17 | const ol2d = new olMap({ 18 | layers: [ 19 | new olLayerTile({ 20 | source: new olSourceOSM() 21 | }), 22 | new olLayerImage({ 23 | extent: [-13884991, 2870341, -7455066, 6338219], 24 | source: imageWMSSource 25 | }) 26 | ], 27 | controls: olControlDefaults({ 28 | attributionOptions: { 29 | collapsible: false 30 | } 31 | }), 32 | target: 'mapCesium', 33 | view: new olView({ 34 | center: [-10967567.978507737, 4204193.972847062], 35 | zoom: 3 36 | }) 37 | }); 38 | 39 | const ol3d = new OLCesium({ 40 | map: ol2d, 41 | time() { 42 | return Cesium.JulianDate.now(); 43 | } 44 | }); 45 | ol3d.getCesiumScene(); 46 | ol3d.setEnabled(true); 47 | 48 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 49 | 50 | //##REMOVE## Keep this tag, split code here for code sandbox 51 | 52 | import {initCodeSandbox} from './_code-sandbox.js'; 53 | initCodeSandbox('rawjs/imageWMS.js'); 54 | -------------------------------------------------------------------------------- /examples/buildings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Buildings example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | A building may be highlighted by clicking it on the OpenLayers map. 24 |
It gets automatically selected in Cesium. 25 |
If you see flickering in Cesium, please check your graphic card drivers. 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/exports.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import {transform} from 'ol/proj.js'; 3 | import olView from 'ol/View.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import olMap from 'ol/Map.js'; 7 | 8 | const ol2d = new olMap({ 9 | layers: [ 10 | new olLayerTile({ 11 | source: new olSourceOSM() 12 | }) 13 | ], 14 | target: 'mapCesium', 15 | view: new olView({ 16 | center: transform([25, 20], 'EPSG:4326', 'EPSG:3857'), 17 | zoom: 3 18 | }) 19 | }); 20 | 21 | const ol3d = new OLCesium({map: ol2d}); 22 | ol3d.getCesiumScene(); 23 | 24 | ol3d.setEnabled(true); 25 | const camera = ol3d.getCamera(); 26 | 27 | const infoDiv = document.getElementById('infoDiv'); 28 | const printInfo = function() { 29 | infoDiv.innerHTML = `Center: ${camera.getCenter()}
` + 30 | `Distance: ${camera.getDistance()}
` + 31 | `Heading: ${camera.getHeading()}
` + 32 | `Tilt: ${camera.getTilt()}
` + 33 | `Position: ${camera.getPosition()}
` + 34 | `Altitude: ${camera.getAltitude()}
`; 35 | }; 36 | setInterval(printInfo, 100); 37 | 38 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 39 | window['camera'] = camera; 40 | window['olProjTransform'] = transform; 41 | 42 | //##REMOVE## Keep this tag, split code here for code sandbox 43 | 44 | import {initCodeSandbox} from './_code-sandbox.js'; 45 | initCodeSandbox('rawjs/exports.js'); 46 | -------------------------------------------------------------------------------- /examples/image-static.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import {transform} from 'ol/proj.js'; 3 | import olView from 'ol/View.js'; 4 | import {defaults as olControlDefaults} from 'ol/control.js'; 5 | import olSourceOSM from 'ol/source/OSM.js'; 6 | import olLayerTile from 'ol/layer/Tile.js'; 7 | import olMap from 'ol/Map.js'; 8 | 9 | import {Image as ImageLayer} from 'ol/layer.js'; 10 | import {getCenter} from 'ol/extent.js'; 11 | import Static from 'ol/source/ImageStatic.js'; 12 | 13 | const imageExtent = [-40, 50, -10, 65]; 14 | 15 | const ol2d = new olMap({ 16 | layers: [ 17 | new olLayerTile({ 18 | source: new olSourceOSM() 19 | }), 20 | new ImageLayer({ 21 | source: new Static({ 22 | url: 'data/image-static.png', 23 | crossOrigin: '', 24 | projection: 'EPSG:4326', 25 | imageExtent 26 | }) 27 | }) 28 | ], 29 | controls: olControlDefaults({ 30 | attributionOptions: { 31 | collapsible: false 32 | } 33 | }), 34 | target: 'mapCesium', 35 | view: new olView({ 36 | center: transform(getCenter(imageExtent), 'EPSG:4326', 'EPSG:3857'), 37 | zoom: 4, 38 | projection: 'EPSG:3857' 39 | }) 40 | }); 41 | 42 | const ol3d = new OLCesium({ 43 | map: ol2d 44 | }); 45 | ol3d.getCesiumScene(); 46 | ol3d.setEnabled(true); 47 | 48 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 49 | 50 | //##REMOVE## Keep this tag, split code here for code sandbox 51 | 52 | import {initCodeSandbox} from './_code-sandbox.js'; 53 | initCodeSandbox('rawjs/image-static.js', 'data/image-static.png'); 54 | -------------------------------------------------------------------------------- /examples/print.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Print example 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 | 21 | 22 | 26 | 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/customProj.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olView from 'ol/View.js'; 3 | import olSourceImageWMS from 'ol/source/ImageWMS.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerImage from 'ol/layer/Image.js'; 6 | import olLayerTile from 'ol/layer/Tile.js'; 7 | import olMap from 'ol/Map.js'; 8 | import {get as getProjection} from 'ol/proj.js'; 9 | import './_proj21781.js'; 10 | 11 | const customProjSource = new olSourceImageWMS({ 12 | attributions: '© National parks / geo.admin.ch', 14 | crossOrigin: 'anonymous', 15 | params: {'LAYERS': 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung'}, 16 | projection: 'EPSG:21781', 17 | url: 'https://wms.geo.admin.ch/' 18 | }); 19 | 20 | customProjSource.set('olcs_projection', getProjection('EPSG:3857')); 21 | 22 | const Cesium = window.Cesium; 23 | const ol2d = new olMap({ 24 | layers: [ 25 | new olLayerTile({ 26 | source: new olSourceOSM() 27 | }), 28 | new olLayerImage({ 29 | source: customProjSource 30 | }) 31 | ], 32 | target: 'mapCesium', 33 | view: new olView({ 34 | center: [860434.6266531206, 6029479.0044273855], 35 | zoom: 6 36 | }) 37 | }); 38 | 39 | const ol3d = new OLCesium({ 40 | map: ol2d, 41 | time() { 42 | return Cesium.JulianDate.now(); 43 | } 44 | }); 45 | ol3d.getCesiumScene(); 46 | ol3d.setEnabled(true); 47 | 48 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 49 | 50 | //##REMOVE## Keep this tag, split code here for code sandbox 51 | 52 | import {initCodeSandbox} from './_code-sandbox.js'; 53 | initCodeSandbox('rawjs/customProj.js', 'rawjs/_proj21781.js'); 54 | -------------------------------------------------------------------------------- /examples/fillstyle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Vectors example 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/olcs/core/VectorLayerCounterpart.ts: -------------------------------------------------------------------------------- 1 | import {unByKey as olObservableUnByKey} from 'ol/Observable.js'; 2 | import type Projection from 'ol/proj/Projection.js'; 3 | import type {Billboard, BillboardCollection, Primitive, PrimitiveCollection, Scene} from 'cesium'; 4 | import type {EventsKey} from 'ol/events.js'; 5 | 6 | 7 | /** 8 | * Context for feature conversion. 9 | */ 10 | export type OlFeatureToCesiumContext = { 11 | projection: Projection | string, 12 | billboards: BillboardCollection, 13 | featureToCesiumMap: Record>, 14 | primitives: PrimitiveCollection 15 | }; 16 | 17 | 18 | export default class VectorLayerCounterpart { 19 | olListenKeys: EventsKey[] = []; 20 | context: OlFeatureToCesiumContext; 21 | private rootCollection_: PrimitiveCollection; 22 | /** 23 | * Result of the conversion of an OpenLayers layer to Cesium. 24 | */ 25 | constructor(layerProjection: Projection | string, scene: Scene) { 26 | const billboards = new Cesium.BillboardCollection({scene}); 27 | const primitives = new Cesium.PrimitiveCollection(); 28 | this.rootCollection_ = new Cesium.PrimitiveCollection(); 29 | this.context = { 30 | projection: layerProjection, 31 | billboards, 32 | featureToCesiumMap: {}, 33 | primitives 34 | }; 35 | 36 | this.rootCollection_.add(billboards); 37 | this.rootCollection_.add(primitives); 38 | } 39 | 40 | /** 41 | * Unlisten. 42 | */ 43 | destroy() { 44 | this.olListenKeys.forEach(olObservableUnByKey); 45 | this.olListenKeys.length = 0; 46 | } 47 | 48 | getRootPrimitive(): PrimitiveCollection { 49 | return this.rootCollection_; 50 | } 51 | } 52 | 53 | 54 | export type PrimitiveCollectionCounterpart = PrimitiveCollection & {counterpart: VectorLayerCounterpart}; 55 | -------------------------------------------------------------------------------- /examples/main.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import {transform} from 'ol/proj.js'; 3 | import olView from 'ol/View.js'; 4 | import {defaults as olControlDefaults} from 'ol/control.js'; 5 | import olSourceOSM from 'ol/source/OSM.js'; 6 | import olLayerTile from 'ol/layer/Tile.js'; 7 | import olMap from 'ol/Map.js'; 8 | 9 | const Cesium = window.Cesium; 10 | const ol2d = new olMap({ 11 | layers: [ 12 | new olLayerTile({ 13 | source: new olSourceOSM() 14 | }) 15 | ], 16 | controls: olControlDefaults({ 17 | attributionOptions: { 18 | collapsible: false 19 | } 20 | }), 21 | target: 'mapCesium', 22 | view: new olView({ 23 | center: transform([25, 20], 'EPSG:4326', 'EPSG:3857'), 24 | zoom: 3 25 | }) 26 | }); 27 | 28 | const timeElt = document.getElementById('time'); 29 | const ol3d = new OLCesium({ 30 | map: ol2d, 31 | time() { 32 | const val = timeElt.value; 33 | if (ol3d.getCesiumScene().globe.enableLighting && val) { 34 | const d = new Date(); 35 | d.setUTCHours(val); 36 | return Cesium.JulianDate.fromDate(d); 37 | } 38 | return Cesium.JulianDate.now(); 39 | } 40 | }); 41 | const scene = ol3d.getCesiumScene(); 42 | ol3d.setEnabled(true); 43 | 44 | 45 | timeElt.style.display = 'none'; 46 | 47 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 48 | window['toggleTime'] = function() { 49 | scene.globe.enableLighting = !scene.globe.enableLighting; 50 | if (timeElt.style.display === 'none') { 51 | timeElt.style.display = 'inline-block'; 52 | } else { 53 | timeElt.style.display = 'none'; 54 | } 55 | }; 56 | 57 | //##REMOVE## Keep this tag, split code here for code sandbox 58 | 59 | import {initCodeSandbox} from './_code-sandbox.js'; 60 | initCodeSandbox('rawjs/main.js'); 61 | -------------------------------------------------------------------------------- /examples/selection.js: -------------------------------------------------------------------------------- 1 | import olStyleStroke from 'ol/style/Stroke.js'; 2 | import olStyleFill from 'ol/style/Fill.js'; 3 | import olStyleStyle from 'ol/style/Style.js'; 4 | import OLCesium from 'olcs'; 5 | import olView from 'ol/View.js'; 6 | import olFormatGeoJSON from 'ol/format/GeoJSON.js'; 7 | import olSourceVector from 'ol/source/Vector.js'; 8 | import olLayerVector from 'ol/layer/Vector.js'; 9 | import olSourceOSM from 'ol/source/OSM.js'; 10 | import olLayerTile from 'ol/layer/Tile.js'; 11 | import olMap from 'ol/Map.js'; 12 | 13 | 14 | const raster = new olLayerTile({ 15 | source: new olSourceOSM() 16 | }); 17 | 18 | const vector = new olLayerVector({ 19 | source: new olSourceVector({ 20 | format: new olFormatGeoJSON(), 21 | url: 'data/geojson/countries.geojson', 22 | crossOrigin: 'anonymous' 23 | }) 24 | }); 25 | 26 | const map = new olMap({ 27 | layers: [raster, vector], 28 | target: 'map2d', 29 | view: new olView({ 30 | center: [0, 0], 31 | zoom: 2 32 | }) 33 | }); 34 | 35 | 36 | const ol3d = new OLCesium({map, target: 'mapCesium'}); 37 | ol3d.setEnabled(true); 38 | 39 | const selectionStyle = new olStyleStyle({ 40 | fill: new olStyleFill({ 41 | color: [255, 255, 255, 0.6] 42 | }), 43 | stroke: new olStyleStroke({ 44 | color: [0, 153, 255, 1], 45 | width: 3 46 | }) 47 | }); 48 | 49 | let selectedFeature; 50 | map.on('click', (e) => { 51 | if (selectedFeature) { 52 | selectedFeature.setStyle(null); 53 | } 54 | selectedFeature = map.forEachFeatureAtPixel( 55 | e.pixel, 56 | (feature, layer) => feature); 57 | if (selectedFeature) { 58 | selectedFeature.setStyle(selectionStyle); 59 | } 60 | }); 61 | 62 | //##REMOVE## Keep this tag, split code here for code sandbox 63 | 64 | import {initCodeSandbox} from './_code-sandbox.js'; 65 | initCodeSandbox('rawjs/selection.js', 'data/geojson/countries.geojson'); 66 | 67 | -------------------------------------------------------------------------------- /examples/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 | 23 | 24 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/groundvectors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Ground vectors example 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 |
26 |
Vectors are synchronized from the OpenLayers map to the Cesium scene. 27 |
3D positioning and some styling is supported, with optional clamping to the ground.
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/rotate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Animated rotation example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Animated rotation a la Google Map.
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/synthvectors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Synthetic vector layer example using several layers 7 | 8 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 |
32 |
33 |
Click to add a new layer of 1000 randomly colored circles. 34 |
Other shapes or icons may be used in spite of the circles. 35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/icon-position.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Icon position example 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 |
26 |
28 |
Icon on the right has default placement: it is centered on the point position.
29 | The icon on the left has its anchor on the bottom center
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/layer-group.js: -------------------------------------------------------------------------------- 1 | import olMap from 'ol/Map.js'; 2 | import olView from 'ol/View.js'; 3 | import olLayerGroup from 'ol/layer/Group.js'; 4 | import olLayerTile from 'ol/layer/Tile.js'; 5 | import {fromLonLat} from 'ol/proj.js'; 6 | import olSourceOSM from 'ol/source/OSM.js'; 7 | import olSourceTileJSON from 'ol/source/TileJSON.js'; 8 | import OLCesium from 'olcs'; 9 | 10 | const layer0 = new olLayerTile({ 11 | source: new olSourceOSM() 12 | }); 13 | 14 | const key = 'pk.eyJ1IjoiZ2JvMiIsImEiOiJjazFraHV4N3gwZHliM2JucHYxdTNnNXh1In0.tzs3TxoVCaMNQf455mh-3w'; 15 | 16 | const layer10 = new olLayerTile({ 17 | source: new olSourceTileJSON({ 18 | url: 'https://api.tiles.mapbox.com/v4/mapbox.20110804-hoa-foodinsecurity-3month.json?secure&access_token=' + key, 19 | crossOrigin: 'anonymous' 20 | }) 21 | }); 22 | 23 | const layer11 = new olLayerTile({ 24 | source: new olSourceTileJSON({ 25 | url: 'https://api.tiles.mapbox.com/v4/mapbox.world-borders-light.json?secure&access_token=' + key, 26 | crossOrigin: 'anonymous' 27 | }) 28 | }); 29 | 30 | const layer1 = new olLayerGroup({ 31 | layers: [ 32 | layer10, 33 | layer11 34 | ] 35 | }); 36 | 37 | const ol2d = new olMap({ 38 | layers: [layer0, layer1], 39 | target: 'map2d', 40 | view: new olView({ 41 | center: fromLonLat([37.40570, 8.81566]), 42 | zoom: 4 43 | }) 44 | }); 45 | 46 | const ol3d = new OLCesium({map: ol2d, target: 'mapCesium'}); 47 | ol3d.setEnabled(true); 48 | 49 | function getLayer(layername) { 50 | switch (layername) { 51 | case 'layer0': 52 | return layer0; 53 | case 'layer1': 54 | return layer1; 55 | case 'layer10': 56 | return layer10; 57 | case 'layer11': 58 | return layer11; 59 | default: 60 | throw new Error('Unknown layer'); 61 | } 62 | } 63 | 64 | window['toggleLayer'] = function(element, name) { 65 | getLayer(name).setVisible(element.checked); 66 | }; 67 | window['setLayerOpacity'] = function(element, name) { 68 | getLayer(name).setOpacity(parseFloat(element.value)); 69 | }; 70 | 71 | //##REMOVE## Keep this tag, split code here for code sandbox 72 | 73 | import {initCodeSandbox} from './_code-sandbox.js'; 74 | initCodeSandbox('rawjs/layer-group.js'); 75 | -------------------------------------------------------------------------------- /examples/wmts.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | 3 | import Map from 'ol/Map.js'; 4 | import View from 'ol/View.js'; 5 | import {getWidth, getTopLeft} from 'ol/extent.js'; 6 | import TileLayer from 'ol/layer/Tile.js'; 7 | import {get as getProjection} from 'ol/proj.js'; 8 | import OSM from 'ol/source/OSM.js'; 9 | import WMTS from 'ol/source/WMTS.js'; 10 | import WMTSTileGrid from 'ol/tilegrid/WMTS.js'; 11 | 12 | const projection = getProjection('EPSG:3857'); 13 | const projectionExtent = projection.getExtent(); 14 | const size = getWidth(projectionExtent) / 256; 15 | const resolutions = new Array(14); 16 | const matrixIds = new Array(14); 17 | for (let z = 0; z < 14; ++z) { 18 | // generate resolutions and matrixIds arrays for this WMTS 19 | resolutions[z] = size / Math.pow(2, z); 20 | matrixIds[z] = z; 21 | } 22 | 23 | const ol2d = new Map({ 24 | layers: [ 25 | new TileLayer({ 26 | source: new OSM(), 27 | opacity: 0.7 28 | }), 29 | new TileLayer({ 30 | opacity: 0.7, 31 | source: new WMTS({ 32 | attributions: 'Tiles © ArcGIS', 34 | url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/' + 35 | 'services/WorldTimeZones/MapServer/WMTS/', 36 | layer: 'WorldTimeZones', 37 | matrixSet: 'GoogleMapsCompatible', 38 | format: 'image/png', 39 | projection, 40 | tileGrid: new WMTSTileGrid({ 41 | origin: getTopLeft(projectionExtent), 42 | resolutions, 43 | matrixIds 44 | }), 45 | style: 'default', 46 | wrapX: true 47 | }) 48 | }) 49 | ], 50 | target: 'mapCesium', 51 | view: new View({ 52 | center: [-11158582, 4813697], 53 | zoom: 4 54 | }) 55 | }); 56 | 57 | const ol3d = new OLCesium({ 58 | map: ol2d, 59 | }); 60 | ol3d.getCesiumScene(); 61 | ol3d.setEnabled(true); 62 | 63 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 64 | 65 | //##REMOVE## Keep this tag, split code here for code sandbox 66 | 67 | import {initCodeSandbox} from './_code-sandbox.js'; 68 | initCodeSandbox('rawjs/wmts.js'); 69 | -------------------------------------------------------------------------------- /examples/print.js: -------------------------------------------------------------------------------- 1 | import OLCesium, {takeScreenshot, autoDrawMask, computeRectangle} from 'olcs'; 2 | import {transform} from 'ol/proj.js'; 3 | import olView from 'ol/View.js'; 4 | import {defaults as olControlDefaults} from 'ol/control.js'; 5 | import olSourceOSM from 'ol/source/OSM.js'; 6 | import olLayerTile from 'ol/layer/Tile.js'; 7 | import olMap from 'ol/Map.js'; 8 | 9 | const ol2d = new olMap({ 10 | layers: [ 11 | new olLayerTile({ 12 | source: new olSourceOSM() 13 | }) 14 | ], 15 | controls: olControlDefaults({ 16 | attributionOptions: { 17 | collapsible: false 18 | } 19 | }), 20 | target: 'mapCesium', 21 | view: new olView({ 22 | center: transform([25, 20], 'EPSG:4326', 'EPSG:3857'), 23 | zoom: 3 24 | }) 25 | }); 26 | 27 | const ol3d = new OLCesium({ 28 | map: ol2d, 29 | }); 30 | const scene = ol3d.getCesiumScene(); 31 | ol3d.setEnabled(true); 32 | 33 | 34 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 35 | document.getElementById('printScale').addEventListener('change', evt => ol3d.setResolutionScale(Number.parseFloat(evt.target.value))); 36 | 37 | function scalingOptions() { 38 | const printValue = document.querySelector('#printValue').value; 39 | const canvas = scene.canvas; 40 | const v = Math.min(canvas.width, canvas.height); 41 | switch (printValue) { 42 | case 'portrait': 43 | return computeRectangle(canvas, v / 4, v / 2); 44 | default: 45 | return computeRectangle(canvas, v / 2, v / 4); 46 | } 47 | } 48 | autoDrawMask(scene, () => scalingOptions().scaling); 49 | 50 | document.querySelector('#takeScreenshot').addEventListener('click', window.takeScreenshot); 51 | 52 | window['takeScreenshot'] = async function() { 53 | const r = scalingOptions(); 54 | console.log(r); 55 | const value = await takeScreenshot(scene, r); 56 | const img = new Image(); 57 | const canvas = scene.canvas; 58 | img.src = value; 59 | img.width = r.width / (canvas.width / canvas.clientWidth); 60 | img.height = r.height / (canvas.height / canvas.clientHeight); 61 | document.querySelector('#screenshots').append(img); 62 | }; 63 | 64 | //##REMOVE## Keep this tag, split code here for code sandbox 65 | 66 | import {initCodeSandbox} from './_code-sandbox.js'; 67 | initCodeSandbox('rawjs/print.js'); 68 | -------------------------------------------------------------------------------- /examples/tracking.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olView from 'ol/View.js'; 3 | import {defaults as olControlDefaults} from 'ol/control.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import olSourceVector from 'ol/source/Vector.js'; 7 | import olLayerVector from 'ol/layer/Vector.js'; 8 | import olStyleIcon from 'ol/style/Icon.js'; 9 | import olStyleStyle from 'ol/style/Style.js'; 10 | import olFeature from 'ol/Feature.js'; 11 | import olGeomPoint from 'ol/geom/Point.js'; 12 | import olMap from 'ol/Map.js'; 13 | 14 | const point = new olGeomPoint([700000, 200000, 100000]); 15 | 16 | const iconFeature = new olFeature({ 17 | geometry: point 18 | }); 19 | 20 | const iconStyle = new olStyleStyle({ 21 | image: new olStyleIcon(/** @type {olx.style.IconOptions} */ ({ 22 | anchor: [0.5, 46], 23 | anchorXUnits: 'fraction', 24 | anchorYUnits: 'pixels', 25 | opacity: 0.75, 26 | src: 'data/icon.png', 27 | crossOrigin: 'anonymous' 28 | })) 29 | }); 30 | 31 | iconFeature.setStyle(iconStyle); 32 | 33 | const vectorSource2 = new olSourceVector({ 34 | features: [iconFeature] 35 | }); 36 | const vectorLayer2 = new olLayerVector({ 37 | renderMode: 'image', 38 | source: vectorSource2 39 | }); 40 | 41 | const map = new olMap({ 42 | layers: [ 43 | new olLayerTile({ 44 | source: new olSourceOSM() 45 | }), 46 | vectorLayer2 47 | ], 48 | target: 'mapCesium', 49 | controls: olControlDefaults({ 50 | attributionOptions: { 51 | collapsible: false 52 | } 53 | }), 54 | view: new olView({ 55 | center: [0, 0], 56 | zoom: 2 57 | }) 58 | }); 59 | 60 | const ol3d = new OLCesium({map}); 61 | ol3d.getCesiumScene(); 62 | ol3d.setEnabled(true); 63 | 64 | let tracking = false; 65 | window['toggleTracking'] = function() { 66 | tracking = !tracking; 67 | ol3d.trackedFeature = tracking ? iconFeature : undefined; 68 | }; 69 | 70 | setInterval(() => { 71 | const old = point.getCoordinates(); 72 | point.setCoordinates([ 73 | old[0] + 1000 * Math.random(), 74 | old[1] + 1000 * Math.random(), 75 | old[2] 76 | ]); 77 | iconFeature.changed(); 78 | }, 100); 79 | 80 | //##REMOVE## Keep this tag, split code here for code sandbox 81 | 82 | import {initCodeSandbox} from './_code-sandbox.js'; 83 | initCodeSandbox('rawjs/tracking.js', 'data/icon.png'); 84 | -------------------------------------------------------------------------------- /src/olcs/AutoRenderLoop.ts: -------------------------------------------------------------------------------- 1 | import type {Scene} from 'cesium'; 2 | import type OLCesium from './OLCesium.js'; 3 | 4 | /** 5 | * By default Cesium (used to?) renders as often as possible. 6 | * This is a waste of resources (CPU/GPU/battery). 7 | * An alternative mechanism in Cesium is on-demand rendering. 8 | * This class makes use of this alternative method and add some additionnal render points. 9 | */ 10 | export default class AutoRenderLoop { 11 | ol3d: OLCesium; 12 | private scene_: Scene; 13 | private canvas_: HTMLCanvasElement; 14 | private _boundNotifyRepaintRequired: typeof this.notifyRepaintRequired; 15 | private repaintEventNames_ = [ 16 | 'mousemove', 'mousedown', 'mouseup', 17 | 'touchstart', 'touchend', 'touchmove', 18 | 'pointerdown', 'pointerup', 'pointermove', 19 | 'wheel' 20 | ] as const; 21 | 22 | /** 23 | * @param ol3d 24 | */ 25 | constructor(ol3d: OLCesium) { 26 | this.ol3d = ol3d; 27 | this.scene_ = ol3d.getCesiumScene(); 28 | this.canvas_ = this.scene_.canvas; 29 | this._boundNotifyRepaintRequired = this.notifyRepaintRequired.bind(this); 30 | this.enable(); 31 | } 32 | 33 | /** 34 | * Enable. 35 | */ 36 | enable() { 37 | this.scene_.requestRenderMode = true; 38 | this.scene_.maximumRenderTimeChange = 1000; 39 | for (const repaintKey of this.repaintEventNames_) { 40 | this.canvas_.addEventListener(repaintKey, this._boundNotifyRepaintRequired, false); 41 | } 42 | 43 | window.addEventListener('resize', this._boundNotifyRepaintRequired, false); 44 | 45 | // Listen for changes on the layer group 46 | this.ol3d.getOlMap().getLayerGroup().on('change', this._boundNotifyRepaintRequired); 47 | } 48 | 49 | /** 50 | * Disable. 51 | */ 52 | disable() { 53 | for (const repaintKey of this.repaintEventNames_) { 54 | this.canvas_.removeEventListener(repaintKey, this._boundNotifyRepaintRequired, false); 55 | } 56 | 57 | window.removeEventListener('resize', this._boundNotifyRepaintRequired, false); 58 | 59 | this.ol3d.getOlMap().getLayerGroup().un('change', this._boundNotifyRepaintRequired); 60 | this.scene_.requestRenderMode = false; 61 | } 62 | 63 | /** 64 | * Restart render loop. 65 | * Force a restart of the render loop. 66 | */ 67 | restartRenderLoop() { 68 | this.notifyRepaintRequired(); 69 | } 70 | 71 | notifyRepaintRequired() { 72 | if (!this.scene_.isDestroyed()) { 73 | this.scene_.requestRender(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/exports.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Exported methods example 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/mvt.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olMap from 'ol/Map.js'; 3 | import TileLayer from 'ol/layer/Tile.js'; 4 | import {get as getProjection} from 'ol/proj.js'; 5 | import View from 'ol/View.js'; 6 | import MVT from 'ol/format/MVT.js'; 7 | import VectorTileLayer from 'ol/layer/VectorTile.js'; 8 | import VectorTileSource from 'ol/source/VectorTile.js'; 9 | import Stroke from 'ol/style/Stroke.js'; 10 | import Style from 'ol/style/Style.js'; 11 | import OSMSource from 'ol/source/OSM.js'; 12 | import './_proj21781.js'; 13 | 14 | const projection = getProjection('EPSG:3857'); 15 | console.assert(projection); 16 | 17 | let styleNumber = 0; 18 | 19 | function createMVTStyle(color = 'purple') { 20 | return [ 21 | new Style({ 22 | stroke: new Stroke({ 23 | color, 24 | width: 4 25 | }) 26 | }) 27 | ]; 28 | } 29 | 30 | const allStyles = [ 31 | createMVTStyle(), 32 | createMVTStyle('red'), 33 | ]; 34 | 35 | function createMVTLayer(url, maxZoom) { 36 | const source = new VectorTileSource({ 37 | url, 38 | attributions: 'Schweizmobil', 39 | format: new MVT(), 40 | }); 41 | const styles = allStyles[styleNumber]; 42 | source.set('olcs_skip', false); 43 | source.set('olcs_minimumLevel', 6); 44 | return new VectorTileLayer({ 45 | source, 46 | extent: [572215, 5684416, 1277662, 6145307], // swiss extent 47 | opacity: 0.6, 48 | style: styles 49 | }); 50 | } 51 | 52 | export const mvtLayer = createMVTLayer( 53 | 'https://map.schweizmobil.ch/api/4/mvt_routes/wander/3857/{z}/{x}/{y}.pbf?olcs', 54 | 20); 55 | 56 | function createOSMLayer() { 57 | const source = new OSMSource(); 58 | return new TileLayer({source}); 59 | } 60 | 61 | const ol2d = new olMap({ 62 | layers: [ 63 | createOSMLayer(), 64 | mvtLayer 65 | ], 66 | target: 'mapCesium', 67 | view: new View() 68 | }); 69 | 70 | const ol3d = new OLCesium({ 71 | map: ol2d, 72 | }); 73 | setTimeout(() => { 74 | ol3d.setEnabled(true); 75 | }); 76 | 77 | const EXTENT = [572215, 5684416, 1277662, 6145307]; 78 | const padding = -50000; 79 | ol2d.getView().fit([ 80 | EXTENT[0] - padding, EXTENT[1] - padding, 81 | EXTENT[2] + padding, EXTENT[3] + padding, 82 | ]); 83 | 84 | 85 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 86 | document.getElementById('toggle').addEventListener('click', () => { 87 | styleNumber = (styleNumber + 1) % 2; 88 | mvtLayer.setStyle(allStyles[styleNumber]); 89 | }); 90 | 91 | //##REMOVE## Keep this tag, split code here for code sandbox 92 | 93 | import {initCodeSandbox} from './_code-sandbox.js'; 94 | initCodeSandbox('rawjs/mvt.js', 'rawjs/_proj21781.js'); 95 | -------------------------------------------------------------------------------- /examples/vectors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Vectors example 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
24 |
26 |
28 |
30 |
32 |
33 | 34 | 36 |
37 |
Vectors are synchronized from the OpenLayers map to the Cesium scene. 38 |
3D positioning and some styling is supported.
The render loop is automatically stopped when idle.
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/olcs/util.ts: -------------------------------------------------------------------------------- 1 | import type {Projection} from 'ol/proj.js'; 2 | import type {Source} from 'ol/source.js'; 3 | 4 | let _imageRenderingPixelatedSupported: boolean = undefined; 5 | let _imageRenderingValue: string = undefined; 6 | 7 | 8 | /** 9 | * https://caniuse.com/mdn-css_properties_image-rendering_pixelated 10 | * @return whether the browser supports 11 | */ 12 | export function supportsImageRenderingPixelated(): boolean { 13 | if (_imageRenderingPixelatedSupported === undefined) { 14 | const canvas = document.createElement('canvas'); 15 | canvas.setAttribute('style', 'image-rendering: -moz-crisp-edges; image-rendering: crisp-edges; image-rendering: pixelated;'); 16 | // canvas.style.imageRendering will be undefined, null or an 17 | // empty string on unsupported browsers. 18 | const imageRenderingValue = canvas.style.imageRendering; 19 | _imageRenderingPixelatedSupported = !!imageRenderingValue; 20 | if (_imageRenderingPixelatedSupported) { 21 | _imageRenderingValue = imageRenderingValue; 22 | } 23 | } 24 | return _imageRenderingPixelatedSupported; 25 | } 26 | 27 | 28 | /** 29 | * The value supported by thie browser for the CSS property "image-rendering" 30 | * @return {string} 31 | */ 32 | export function imageRenderingValue() { 33 | supportsImageRenderingPixelated(); 34 | return _imageRenderingValue || ''; 35 | } 36 | 37 | /** 38 | * Return the projection of the source that Cesium should use. 39 | * 40 | * @param source Source. 41 | * @return The projection of the source. 42 | */ 43 | export function getSourceProjection(source: Source): Projection { 44 | return source.get('olcs_projection') as Projection || source.getProjection(); 45 | } 46 | 47 | 48 | /** 49 | * Counter for getUid. 50 | * @type {number} 51 | */ 52 | let uidCounter_ = 0; 53 | 54 | /** 55 | * Gets a unique ID for an object. This mutates the object so that further calls 56 | * with the same object as a parameter returns the same value. Unique IDs are generated 57 | * as a strictly increasing sequence. Adapted from goog.getUid. Similar to OL getUid. 58 | * 59 | * @param obj The object to get the unique ID for. 60 | * @return The unique ID for the object. 61 | */ 62 | export function getUid(obj: any): number { 63 | return obj.olcs_uid || (obj.olcs_uid = ++uidCounter_); 64 | } 65 | 66 | export function waitReady(object: Type): Promise { 67 | const o = object as any; 68 | const p = o.readyPromise; 69 | if (p) { 70 | return p; 71 | } 72 | if (o.ready !== undefined) { 73 | if (o.ready) { 74 | return Promise.resolve(object); 75 | } 76 | return new Promise((resolve, _) => { 77 | // FIXME: this is crazy 78 | // alternative: intercept _ready = true 79 | // altnerative: pass a timeout 80 | const stopper = setInterval(() => { 81 | if (o.ready) { 82 | clearInterval(stopper); 83 | resolve(object); 84 | } 85 | }, 20); 86 | }); 87 | } 88 | return Promise.reject('Not a readyable object'); 89 | } 90 | -------------------------------------------------------------------------------- /examples/fillstyle.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olView from 'ol/View.js'; 3 | import {defaults as olControlDefaults} from 'ol/control.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import olStyleStyle from 'ol/style/Style.js'; 7 | import olFeature from 'ol/Feature.js'; 8 | import olStyleStroke from 'ol/style/Stroke.js'; 9 | import {defaults as interactionDefaults} from 'ol/interaction.js'; 10 | import olStyleFill from 'ol/style/Fill.js'; 11 | import olMap from 'ol/Map.js'; 12 | import olSourceVector from 'ol/source/Vector.js'; 13 | import olGeomPolygon from 'ol/geom/Polygon.js'; 14 | import olLayerVector from 'ol/layer/Vector.js'; 15 | 16 | const vectorSource = new olSourceVector({ 17 | features: [] 18 | }); 19 | 20 | const vectorLayer = new olLayerVector({ 21 | source: vectorSource, 22 | altitudeMode: 'clampToGround' 23 | }); 24 | 25 | const image = new Image(); 26 | image.onload = () => { 27 | const canvas = document.createElement('canvas'); 28 | const ctx = canvas.getContext('2d'); 29 | canvas.width = 32; 30 | canvas.height = 48; 31 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height); 32 | const canvas2 = document.createElement('canvas'); 33 | const ctx2 = canvas2.getContext('2d'); 34 | 35 | const polygonFeature = new olFeature({ 36 | geometry: new olGeomPolygon([[[-3e6, 0], [-3e6, 2e6], [-1e6, 2e6], [-1e6, 0], [-3e6, 0]]]) 37 | }); 38 | polygonFeature.setStyle(new olStyleStyle({ 39 | stroke: new olStyleStroke({ 40 | color: 'green', 41 | lineDash: [4], 42 | width: 3 43 | }), 44 | fill: new olStyleFill({ 45 | color: ctx2.createPattern(canvas, 'repeat') 46 | }) 47 | })); 48 | vectorSource.addFeature(polygonFeature); 49 | }; 50 | image.src = 'data/icon.png'; 51 | 52 | const map = new olMap({ 53 | interactions: interactionDefaults(), 54 | layers: [ 55 | new olLayerTile({ 56 | source: new olSourceOSM() 57 | }), 58 | vectorLayer 59 | ], 60 | target: 'map2d', 61 | controls: olControlDefaults({ 62 | attributionOptions: { 63 | collapsible: false 64 | } 65 | }), 66 | view: new olView({ 67 | center: [-2e6, 1e6], 68 | zoom: 4 69 | }) 70 | }); 71 | 72 | const ol3d = new OLCesium({map, target: 'mapCesium'}); 73 | const scene = ol3d.getCesiumScene(); 74 | ol3d.setEnabled(true); 75 | 76 | window['ol3d'] = ol3d; 77 | window['scene'] = scene; 78 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 79 | 80 | ol3d.enableAutoRenderLoop(); 81 | 82 | window['toggleClampToGround'] = function() { 83 | let altitudeMode; 84 | if (!vectorLayer.get('altitudeMode')) { 85 | altitudeMode = 'clampToGround'; 86 | } 87 | vectorLayer.set('altitudeMode', altitudeMode); 88 | map.removeLayer(vectorLayer); 89 | map.addLayer(vectorLayer); 90 | }; 91 | 92 | //##REMOVE## Keep this tag, split code here for code sandbox 93 | 94 | import {initCodeSandbox} from './_code-sandbox.js'; 95 | initCodeSandbox('rawjs/fillstyle.js', 'data/icon.png'); 96 | -------------------------------------------------------------------------------- /examples/buildings.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olStyleStroke from 'ol/style/Stroke.js'; 3 | import olStyleFill from 'ol/style/Fill.js'; 4 | import olStyleStyle from 'ol/style/Style.js'; 5 | import olView from 'ol/View.js'; 6 | import olFormatGeoJSON from 'ol/format/GeoJSON.js'; 7 | import olSourceVector from 'ol/source/Vector.js'; 8 | import olLayerVector from 'ol/layer/Vector.js'; 9 | import olSourceOSM from 'ol/source/OSM.js'; 10 | import olLayerTile from 'ol/layer/Tile.js'; 11 | import olMap from 'ol/Map.js'; 12 | 13 | const raster = new olLayerTile({ 14 | source: new olSourceOSM(), 15 | }); 16 | 17 | const vector = new olLayerVector({ 18 | style(feature, resolution) { 19 | const fillColor = feature.get('fillColor') || 'white'; 20 | const strokeColor = feature.get('strokeColor') || 'grey'; 21 | return new olStyleStyle({ 22 | fill: new olStyleFill({ 23 | color: fillColor, 24 | }), 25 | stroke: new olStyleStroke({ 26 | color: strokeColor, 27 | width: 1, 28 | }), 29 | }); 30 | }, 31 | source: new olSourceVector({ 32 | format: new olFormatGeoJSON(), 33 | url: 'data/geojson/buildings.geojson', 34 | }), 35 | }); 36 | 37 | const map = new olMap({ 38 | layers: [raster, vector], 39 | target: 'map2d', 40 | view: new olView({ 41 | center: [0, 0], 42 | zoom: 2, 43 | }), 44 | }); 45 | // Enable the property 'olcs_shadows' for the entire set of features 46 | // Alternatively, you can enable 'olcs_shadows' for each feature individually 47 | vector.set('olcs_shadows', true); 48 | 49 | 50 | const ol3d = new OLCesium({map, target: 'mapCesium'}); 51 | ol3d.setEnabled(true); 52 | 53 | // Be aware that enabling the following properties can impact performance 54 | // Enable shadow map to allow Cesium to cast scene's shadows 55 | const scene = ol3d.getCesiumScene(); 56 | scene.shadowMap.enabled = true; 57 | // Enable lighting the globe with the sun as a light source to have dynamic lighting conditions according to the position of the sun 58 | scene.globe.enableLighting = true; 59 | 60 | 61 | const vectorSource = vector.getSource(); 62 | vectorSource.once('featuresloadend', () => { 63 | if (vectorSource.getState() === 'ready') { 64 | map.getView().fit(vector.getSource().getExtent()); 65 | } 66 | }); 67 | 68 | 69 | const selectionStyle = new olStyleStyle({ 70 | fill: new olStyleFill({ 71 | color: [0, 255, 0, 1], 72 | }), 73 | stroke: new olStyleStroke({ 74 | color: [0, 153, 255, 1], 75 | width: 3, 76 | }), 77 | }); 78 | 79 | let selectedFeature; 80 | map.on('click', (e) => { 81 | if (selectedFeature) { 82 | selectedFeature.setStyle(null); 83 | } 84 | selectedFeature = map.forEachFeatureAtPixel( 85 | e.pixel, 86 | (feature, layer) => feature 87 | ); 88 | if (selectedFeature) { 89 | selectedFeature.setStyle(selectionStyle); 90 | } 91 | }); 92 | 93 | //##REMOVE## Keep this tag, split code here for code sandbox 94 | 95 | import {initCodeSandbox} from './_code-sandbox.js'; 96 | initCodeSandbox('rawjs/buildings.js', 'data/geojson/buildings.geojson'); 97 | -------------------------------------------------------------------------------- /examples/rastersync.js: -------------------------------------------------------------------------------- 1 | import olSourceTileWMS from 'ol/source/TileWMS.js'; 2 | import StadiaMaps from 'ol/source/StadiaMaps.js'; 3 | import OLCesium from 'olcs'; 4 | import olLayerGroup from 'ol/layer/Group.js'; 5 | import olMap from 'ol/Map.js'; 6 | import olSourceTileJSON from 'ol/source/TileJSON.js'; 7 | import olSourceOSM from 'ol/source/OSM.js'; 8 | import olLayerTile from 'ol/layer/Tile.js'; 9 | import {transform} from 'ol/proj.js'; 10 | import olView from 'ol/View.js'; 11 | 12 | const view = new olView({ 13 | center: transform([-112.2, 36.06], 'EPSG:4326', 'EPSG:3857'), 14 | zoom: 11 15 | }); 16 | 17 | const layer0 = new olLayerTile({ 18 | source: new olSourceOSM() 19 | }); 20 | 21 | const key = 'pk.eyJ1IjoiZ2JvMiIsImEiOiJjazFraHV4N3gwZHliM2JucHYxdTNnNXh1In0.tzs3TxoVCaMNQf455mh-3w'; 22 | const tileJsonSource = new olSourceTileJSON({ 23 | url: 'https://api.tiles.mapbox.com/v4/mapbox.world-borders-light.json?access_token=' + key, 24 | crossOrigin: 'anonymous' 25 | }); 26 | 27 | const layer2 = new olLayerTile({ 28 | source: tileJsonSource 29 | }); 30 | const ol2d = new olMap({ 31 | layers: [layer0, new olLayerGroup({layers: [layer2]})], 32 | target: 'map2d', 33 | view, 34 | }); 35 | 36 | const ol3d = new OLCesium({map: ol2d, target: 'mapCesium'}); 37 | 38 | ol3d.getCesiumScene(); 39 | ol3d.setEnabled(true); 40 | 41 | const addStamen = function() { // eslint-disable-line no-unused-vars 42 | ol2d.addLayer(new olLayerTile({ 43 | source: new StadiaMaps({ 44 | opacity: 0.7, 45 | layer: 'stamen_watercolor' 46 | }) 47 | })); 48 | }; 49 | 50 | const tileWMSSource = new olSourceTileWMS({ 51 | url: 'https://ahocevar.com/geoserver/wms', 52 | params: {'LAYERS': 'topp:states', 'TILED': true}, 53 | serverType: 'geoserver', 54 | crossOrigin: 'anonymous' 55 | }); 56 | 57 | const addTileWMS = function() { // eslint-disable-line no-unused-vars 58 | ol2d.addLayer(new olLayerTile({ 59 | opacity: 0.5, 60 | extent: [-13884991, 2870341, -7455066, 6338219], 61 | source: tileWMSSource 62 | })); 63 | }; 64 | 65 | let changeI = 0; 66 | const changeTileWMSParams = function() { // eslint-disable-line no-unused-vars 67 | tileWMSSource.updateParams({ 68 | 'LAYERS': (changeI++) % 2 == 0 ? 'nurc:Img_Sample' : 'topp:states' 69 | }); 70 | }; 71 | 72 | const addTileJSON = function() { // eslint-disable-line no-unused-vars 73 | ol2d.addLayer(new olLayerTile({ 74 | source: tileJsonSource 75 | })); 76 | }; 77 | 78 | const removeLastLayer = function() { // eslint-disable-line no-unused-vars 79 | const length = ol2d.getLayers().getLength(); 80 | if (length > 0) { 81 | ol2d.getLayers().removeAt(length - 1); 82 | } 83 | }; 84 | 85 | window['global'] = { 86 | ol2d, 87 | removeLastLayer, 88 | addStamen, 89 | addTileWMS, 90 | addTileJSON, 91 | changeTileWMSParams, 92 | layer0, 93 | layer2 94 | }; 95 | 96 | //##REMOVE## Keep this tag, split code here for code sandbox 97 | 98 | import {initCodeSandbox} from './_code-sandbox.js'; 99 | initCodeSandbox('rawjs/rastersync.js'); 100 | -------------------------------------------------------------------------------- /PROPERTIES.md: -------------------------------------------------------------------------------- 1 | # OLCEsium Properties 2 | 3 | ## olcs_extruded_height 4 | Value: number 5 | The distance in meters between the polygon's extruded face and the ellipsoid surface. 6 | Check buildings example for usage in context. 7 | 8 | ## olcs_shadows 9 | Value: boolean 10 | Enables shadow casting in 3D. Can be either applied to the entire feature set or by feature individually. 11 | In order for it to work, [shadowMap](https://cesium.com/learn/cesiumjs/ref-doc/Scene.html?classFilter=scene#shadowMap) needs to be enabled in the Cesium scene. To use the sun as light source, enable [enableLighting](https://cesium.com/learn/cesiumjs/ref-doc/Globe.html#enableLighting) on the Globe. 12 | Check buildings example for usage in context. 13 | 14 | ## olcs_model 15 | Value: object({cesiumOptions: options}) 16 | Is used to define the 3D model representation of an OpenLayers vector feature in the Cesium scene. This property allows you to customize how the feature is visualized and positioned in the 3D world. 17 | 18 | To use the olcs_model property, set it as a property on your OpenLayers vector feature. It should be an object that specifies the properties of the 3D model. The olcs_model object can have the following key-value pairs: 19 | 20 | - url: The URL of the 3D model's data source (required). 21 | - scale: A numeric value representing the scale of the model (optional). 22 | - minimumPixelSize: The minimum pixel size at which the model will be visible (optional). 23 | - heightReference: Specifies how the model's height is interpreted in the scene (optional). 24 | 25 | Check vectors example for usage in context. 26 | 27 | ## olcs_skip 28 | Value: boolean 29 | Allows you to exclude specific layers from being rendered in the Cesium 3D view. This can be useful when you want to control which layers are displayed in the 2D map view and which are displayed in the 3D Cesium view. 30 | 31 | ## olcs_minimumLevel 32 | Value: number 33 | Allows you to set a minimum zoom level for rendering 3D tiles in the Cesium view. This property helps to control the level of detail displayed in the 3D view based on the current zoom level. 34 | Check mvt example for usage in context. 35 | 36 | ## olcs_tileLoadFunction (ImageWMS sources) 37 | Value: https://openlayers.org/en/latest/apidoc/module-ol_Tile.html#~LoadFunction 38 | Allows to use a custom function, for example when converting a WMS image source to a tiled one. 39 | 40 | ## olcs_projection 41 | Value: https://openlayers.org/en/latest/apidoc/module-ol_proj_Projection-Projection.html 42 | Allows to use an alternative projection in CesiumJS. See the customProj example. 43 | 44 | ## olcs_polygon_kind 45 | Value: "rectangle" 46 | Allows to use the Cesium Rectangle geometry instead of a polygon geometry. See the vector example. 47 | 48 | ## olcs_3d_geometry (OL vector source) 49 | Value: https://openlayers.org/en/latest/apidoc/module-ol_geom_Geometry-Geometry.html 50 | Allows to use an alternative geometry in CesiumJS. 51 | 52 | ## olcs_proxy 53 | Value: https://cesium.com/learn/cesiumjs/ref-doc/Proxy.html 54 | Allows to add authentication information to requests sent by CesiumJS or manipulate request. 55 | 56 | ## olcs_extent 57 | Value: An array of numbers representing an extent: [minx, miny, maxx, maxy] 58 | Allows to restrict a tiled imagery layer to a rectangle. This avoid sending useless requests. 59 | -------------------------------------------------------------------------------- /examples/rastersync.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Buildings example 7 | 8 | 9 | 14 | 15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 |
29 | 30 | 31 | 32 |

33 | 34 | 35 |
36 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 48 | 49 | 50 | 51 | 52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/data/geojson/ground_vector_data.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { 4 | "type": "name", 5 | "properties": { 6 | "name": "EPSG:4326" 7 | } 8 | }, 9 | "features": [ 10 | { 11 | "type": "Feature", 12 | "properties": {}, 13 | "geometry": { 14 | "type": "Point", 15 | "coordinates": [ 16 | 151.63681983947754, 17 | -30.48244572917909 18 | ] 19 | } 20 | }, 21 | { 22 | "type": "Feature", 23 | "properties": {}, 24 | "geometry": { 25 | "type": "LineString", 26 | "coordinates": [ 27 | [ 28 | 151.64051055908203, 29 | -30.48226081008541 30 | ], 31 | [ 32 | 151.64325714111328, 33 | -30.479671905887578 34 | ], 35 | [ 36 | 151.6448450088501, 37 | -30.48166906662488 38 | ], 39 | [ 40 | 151.64244174957275, 41 | -30.481817002827256 42 | ] 43 | ] 44 | } 45 | }, 46 | { 47 | "type": "Feature", 48 | "properties": {}, 49 | "geometry": { 50 | "type": "Polygon", 51 | "coordinates": [ 52 | [ 53 | [ 54 | 151.6357898712158, 55 | -30.484294900793916 56 | ], 57 | [ 58 | 151.63377285003662, 59 | -30.487475393790533 60 | ], 61 | [ 62 | 151.6374635696411, 63 | -30.488769750717125 64 | ], 65 | [ 66 | 151.64029598236084, 67 | -30.486587824805195 68 | ], 69 | [ 70 | 151.63896560668945, 71 | -30.483851102810334 72 | ], 73 | [ 74 | 151.6357898712158, 75 | -30.484294900793916 76 | ] 77 | ], 78 | [ 79 | [ 80 | 151.6367769241333, 81 | -30.485348912894754 82 | ], 83 | [ 84 | 151.63559675216675, 85 | -30.486754244606537 86 | ], 87 | [ 88 | 151.63752794265747, 89 | -30.487290484252792 90 | ], 91 | [ 92 | 151.63825750350952, 93 | -30.485959125209646 94 | ], 95 | [ 96 | 151.6367769241333, 97 | -30.485348912894754 98 | ] 99 | ] 100 | ] 101 | } 102 | }, 103 | { 104 | "type": "Feature", 105 | "properties": {}, 106 | "geometry": { 107 | "type": "Polygon", 108 | "coordinates": [ 109 | [ 110 | [ 111 | 151.64179801940918, 112 | -30.487993138627072 113 | ], 114 | [ 115 | 151.64733409881592, 116 | -30.487993138627072 117 | ], 118 | [ 119 | 151.64733409881592, 120 | -30.483851102810334 121 | ], 122 | [ 123 | 151.64179801940918, 124 | -30.483851102810334 125 | ], 126 | [ 127 | 151.64179801940918, 128 | -30.487993138627072 129 | ] 130 | ] 131 | ] 132 | } 133 | } 134 | ] 135 | } -------------------------------------------------------------------------------- /examples/data/Box.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0", 5 | "copyright": "First Source; Second Source; Third Source" 6 | }, 7 | "scene": 0, 8 | "scenes": [ 9 | { 10 | "nodes": [ 11 | 0 12 | ] 13 | } 14 | ], 15 | "nodes": [ 16 | { 17 | "children": [ 18 | 1 19 | ], 20 | "matrix": [ 21 | 1.0, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | 0.0, 27 | -1.0, 28 | 0.0, 29 | 0.0, 30 | 1.0, 31 | 0.0, 32 | 0.0, 33 | 0.0, 34 | 0.0, 35 | 0.0, 36 | 1.0 37 | ] 38 | }, 39 | { 40 | "mesh": 0 41 | } 42 | ], 43 | "meshes": [ 44 | { 45 | "primitives": [ 46 | { 47 | "attributes": { 48 | "NORMAL": 1, 49 | "POSITION": 2 50 | }, 51 | "indices": 0, 52 | "mode": 4, 53 | "material": 0 54 | } 55 | ], 56 | "name": "Mesh" 57 | } 58 | ], 59 | "accessors": [ 60 | { 61 | "bufferView": 0, 62 | "byteOffset": 0, 63 | "componentType": 5123, 64 | "count": 36, 65 | "max": [ 66 | 23 67 | ], 68 | "min": [ 69 | 0 70 | ], 71 | "type": "SCALAR" 72 | }, 73 | { 74 | "bufferView": 1, 75 | "byteOffset": 0, 76 | "componentType": 5126, 77 | "count": 24, 78 | "max": [ 79 | 1.0, 80 | 1.0, 81 | 1.0 82 | ], 83 | "min": [ 84 | -1.0, 85 | -1.0, 86 | -1.0 87 | ], 88 | "type": "VEC3" 89 | }, 90 | { 91 | "bufferView": 1, 92 | "byteOffset": 288, 93 | "componentType": 5126, 94 | "count": 24, 95 | "max": [ 96 | 0.5, 97 | 0.5, 98 | 0.5 99 | ], 100 | "min": [ 101 | -0.5, 102 | -0.5, 103 | -0.5 104 | ], 105 | "type": "VEC3" 106 | } 107 | ], 108 | "materials": [ 109 | { 110 | "pbrMetallicRoughness": { 111 | "baseColorFactor": [ 112 | 0.800000011920929, 113 | 0.0, 114 | 0.0, 115 | 1.0 116 | ], 117 | "metallicFactor": 0.0 118 | }, 119 | "name": "Red" 120 | } 121 | ], 122 | "bufferViews": [ 123 | { 124 | "buffer": 0, 125 | "byteOffset": 576, 126 | "byteLength": 72, 127 | "target": 34963 128 | }, 129 | { 130 | "buffer": 0, 131 | "byteOffset": 0, 132 | "byteLength": 576, 133 | "byteStride": 12, 134 | "target": 34962 135 | } 136 | ], 137 | "buffers": [ 138 | { 139 | "byteLength": 648, 140 | "uri": "Box0.bin" 141 | } 142 | ] 143 | } 144 | -------------------------------------------------------------------------------- /examples/icon-position.js: -------------------------------------------------------------------------------- 1 | import OLCesium, {rotateAroundAxis, pickBottomPoint} from 'olcs'; 2 | import olView from 'ol/View.js'; 3 | import {defaults as olControlDefaults} from 'ol/control.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import olStyleText from 'ol/style/Text.js'; 7 | import olStyleIcon from 'ol/style/Icon.js'; 8 | import olStyleStyle from 'ol/style/Style.js'; 9 | import olGeomPoint from 'ol/geom/Point.js'; 10 | import olFeature from 'ol/Feature.js'; 11 | import olStyleStroke from 'ol/style/Stroke.js'; 12 | import {defaults as interactionDefaults} from 'ol/interaction.js'; 13 | import olStyleFill from 'ol/style/Fill.js'; 14 | import olMap from 'ol/Map.js'; 15 | import olSourceVector from 'ol/source/Vector.js'; 16 | import olLayerVector from 'ol/layer/Vector.js'; 17 | 18 | 19 | const icon1Feature = new olFeature({ 20 | geometry: new olGeomPoint([700000, 200000]) 21 | }); 22 | icon1Feature.setStyle(new olStyleStyle({ 23 | image: new olStyleIcon(/** @type {olx.style.IconOptions} */ ({ 24 | anchor: [0.5, 1], 25 | src: 'data/icon.png', 26 | })), 27 | text: new olStyleText({ 28 | text: 'Icon with anchor on the bottom center', 29 | stroke: new olStyleStroke({ 30 | color: 'black', 31 | width: 3 32 | }), 33 | fill: new olStyleFill({ 34 | color: 'white' 35 | }) 36 | }) 37 | })); 38 | 39 | const icon2Feature = new olFeature({ 40 | geometry: new olGeomPoint([1000000, 200000]) 41 | }); 42 | icon2Feature.setStyle(new olStyleStyle({ 43 | image: new olStyleIcon(/** @type {olx.style.IconOptions} */ ({ 44 | src: 'data/image-static.png' 45 | })), 46 | text: new olStyleText({ 47 | text: 'Default positioning', 48 | stroke: new olStyleStroke({ 49 | color: 'black', 50 | width: 3 51 | }), 52 | fill: new olStyleFill({ 53 | color: 'white' 54 | }) 55 | }) 56 | })); 57 | 58 | const vectorSource = new olSourceVector({ 59 | features: [ 60 | icon1Feature, 61 | icon2Feature 62 | ] 63 | }); 64 | 65 | const vectorLayer = new olLayerVector({ 66 | source: vectorSource 67 | }); 68 | 69 | const map = new olMap({ 70 | interactions: interactionDefaults(), 71 | layers: [ 72 | new olLayerTile({ 73 | source: new olSourceOSM() 74 | }), 75 | vectorLayer 76 | ], 77 | target: 'map2d', 78 | controls: olControlDefaults({ 79 | attributionOptions: { 80 | collapsible: false 81 | } 82 | }), 83 | view: new olView({ 84 | center: [850000, 200000], 85 | zoom: 7 86 | }) 87 | }); 88 | 89 | const Cesium = window.Cesium; 90 | const ol3d = new OLCesium({map, target: 'mapCesium'}); 91 | const scene = ol3d.getCesiumScene(); 92 | ol3d.setEnabled(true); 93 | 94 | window['toggleClampToGround'] = function() { 95 | let altitudeMode; 96 | if (!vectorLayer.get('altitudeMode')) { 97 | altitudeMode = 'clampToGround'; 98 | } 99 | vectorLayer.set('altitudeMode', altitudeMode); 100 | map.removeLayer(vectorLayer); 101 | map.addLayer(vectorLayer); 102 | }; 103 | 104 | window['ol3d'] = ol3d; 105 | window['scene'] = scene; 106 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 107 | 108 | ol3d.enableAutoRenderLoop(); 109 | 110 | // Tilt camera 111 | const camera = scene.camera; 112 | const pivot = pickBottomPoint(scene); 113 | if (pivot) { 114 | const options = {}; 115 | const transform = Cesium.Matrix4.fromTranslation(pivot); 116 | const axis = camera.right; 117 | rotateAroundAxis(camera, -Math.PI / 4, axis, transform, options); 118 | } 119 | 120 | //##REMOVE## Keep this tag, split code here for code sandbox 121 | 122 | import {initCodeSandbox} from './_code-sandbox.js'; 123 | initCodeSandbox('rawjs/icon-position.js', 'data/icon.png', 'data/image-static.png'); 124 | -------------------------------------------------------------------------------- /examples/synthvectors.js: -------------------------------------------------------------------------------- 1 | import olLayerVector from 'ol/layer/Vector.js'; 2 | import olSourceVector from 'ol/source/Vector.js'; 3 | import olStyleFill from 'ol/style/Fill.js'; 4 | import olStyleCircle from 'ol/style/Circle.js'; 5 | import olStyleStyle from 'ol/style/Style.js'; 6 | import OLCesium from 'olcs'; 7 | import olView from 'ol/View.js'; 8 | import olMap from 'ol/Map.js'; 9 | import olSourceOSM from 'ol/source/OSM.js'; 10 | import olLayerTile from 'ol/layer/Tile.js'; 11 | import olFeature from 'ol/Feature.js'; 12 | import olGeomPoint from 'ol/geom/Point.js'; 13 | 14 | 15 | let total = 0; 16 | let created = 0; 17 | let added = 0; 18 | const vectorLayers = []; 19 | 20 | const tile = new olLayerTile({ 21 | source: new olSourceOSM() 22 | }); 23 | 24 | const map = new olMap({ 25 | layers: [tile], 26 | target: 'mapCesium', 27 | view: new olView({ 28 | center: [0, 0], 29 | zoom: 2 30 | }) 31 | }); 32 | 33 | 34 | const ol3d = new OLCesium({map}); 35 | const scene = ol3d.getCesiumScene(); 36 | ol3d.setEnabled(true); 37 | 38 | // Show off 3D feature picking 39 | const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); 40 | let lastPicked; 41 | handler.setInputAction((movement) => { 42 | const pickedObjects = scene.drillPick(movement.position); 43 | if (Cesium.defined(pickedObjects)) { 44 | for (let i = 0; i < pickedObjects.length; ++i) { 45 | const picked = pickedObjects[i].primitive; 46 | if (picked.olFeature == lastPicked) {continue;} 47 | const carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(picked.position); 48 | console.log('Picked feature', picked.olFeature, ' is at ', carto); 49 | lastPicked = picked.olFeature; 50 | } 51 | } else { 52 | lastPicked = undefined; 53 | } 54 | }, Cesium.ScreenSpaceEventType.LEFT_CLICK); 55 | 56 | 57 | window['clearFeatures'] = function() { 58 | vectorLayers.forEach((layer) => { 59 | map.getLayers().remove(layer); 60 | }); 61 | vectorLayers.length = 0; 62 | total = document.getElementById('total').innerHTML = 0; 63 | document.getElementById('created').innerHTML = ''; 64 | document.getElementById('added').innerHTML = ''; 65 | }; 66 | 67 | window['addFeatures'] = function() { 68 | let then = Date.now(); 69 | const count = 1000; 70 | const features = []; 71 | const e = 18000000; 72 | for (let i = 0; i < count; ++i) { 73 | const feature = new olFeature({ 74 | geometry: new olGeomPoint([ 75 | 2 * e * Math.random() - e, 76 | 2 * e * Math.random() - e, 77 | e * Math.random() 78 | ]) 79 | }); 80 | const style = [new olStyleStyle({ 81 | image: new olStyleCircle({ 82 | radius: 2, 83 | fill: new olStyleFill({color: [ 84 | Math.random() * 255, 85 | Math.random() * 255, 86 | Math.random() * 255, 87 | Math.random() 88 | ]}) 89 | }) 90 | })]; 91 | feature.setStyle(style); 92 | 93 | feature.setId(e * Math.random()); 94 | features.push(feature); 95 | } 96 | 97 | let now = Date.now(); 98 | created = now - then; 99 | then = now; 100 | 101 | const vectorSource = new olSourceVector({}); 102 | const vector = new olLayerVector({ 103 | source: vectorSource 104 | }); 105 | vectorSource.addFeatures(features); 106 | map.addLayer(vector); 107 | vectorLayers.push(vector); 108 | now = Date.now(); 109 | added = now - then; 110 | total += count; 111 | 112 | document.getElementById('total').innerHTML = total; 113 | document.getElementById('created').innerHTML = `Features created in ${created}ms.`; 114 | document.getElementById('added').innerHTML = `Features added in ${added}ms.`; 115 | }; 116 | 117 | //##REMOVE## Keep this tag, split code here for code sandbox 118 | 119 | import {initCodeSandbox} from './_code-sandbox.js'; 120 | initCodeSandbox('rawjs/synthvectors.js'); 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "olcs", 3 | "version": "2.22.3", 4 | "description": "OpenLayers Cesium integration and plugin library", 5 | "scripts": { 6 | "test": "node --enable-source-maps --import @swc-node/register/esm-register --test src/olcs/*.test.ts", 7 | "build-examples": "rm -rf dist; parcel build --dist-dir dist/examples && npm run copy-static-dist && npm run copy-rawjs-sandbox", 8 | "prepare": "tsc --pretty; npm run doc", 9 | "typecheck": "tsc --pretty --noEmit", 10 | "lint": "eslint src examples", 11 | "checks": "npm run lint && npm run typecheck", 12 | "doc": "typedoc --name ol-cesium --excludeExternals --out apidoc --entryPoints src/olcs.ts --tsconfig ./tsconfig.json", 13 | "copy-static-dist": "mkdir -p dist/examples/node_modules/ol; mkdir -p dist/examples/node_modules/cesium/Build/; cp examples/inject_ol_cesium.js dist/examples/; cp node_modules/ol/ol.css dist/examples/node_modules/ol/; cp -R node_modules/cesium/Build/CesiumUnminified node_modules/cesium/Build/Cesium dist/examples/node_modules/cesium/Build/; cp -R examples/data dist/examples/", 14 | "copy-rawjs-sandbox": "mkdir -p dist/examples/rawjs/; cp -r examples/* dist/examples/rawjs; ", 15 | "prestart": "rm -rf dist; npm run copy-static-dist", 16 | "start": "parcel serve ./examples/index.html --dist-dir dist/examples --no-autoinstall & tsc --watch", 17 | "serve-built-examples": "python3 -m http.server --directory dist 12345" 18 | }, 19 | "targets": { 20 | "examples": { 21 | "source": "./examples/index.html", 22 | "context": "browser", 23 | "publicUrl": "./", 24 | "optimize": true, 25 | "isLibrary": false, 26 | "sourceMap": true 27 | } 28 | }, 29 | "alias": { 30 | "olcs": "./lib/olcs", 31 | "buffer": false 32 | }, 33 | "source": "./src/olcs.ts", 34 | "type": "module", 35 | "main": "./lib/olcs.js", 36 | "exports": { 37 | ".": { 38 | "types": "./lib/types/olcs.d.ts", 39 | "default": "./lib/olcs.js" 40 | }, 41 | "./src/*": { 42 | "types": "./lib/types/*.d.ts", 43 | "default": "./lib/olcs/*.js" 44 | }, 45 | "./*": { 46 | "types": "./lib/types/*.d.ts", 47 | "default": "./lib/olcs/*.js" 48 | } 49 | }, 50 | "parcelIgnore": [ 51 | "dist/examples/rawjs/*" 52 | ], 53 | "types": "lib/types/olcs.d.ts", 54 | "directories": { 55 | "doc": "apidoc/" 56 | }, 57 | "homepage": "https://github.com/openlayers/ol-cesium/", 58 | "keywords": [ 59 | "Cesium", 60 | "OpenLayers", 61 | "OL", 62 | "Synchronization", 63 | "Plugins" 64 | ], 65 | "files": [ 66 | "PROPERTIES.md", 67 | "css/", 68 | "src/", 69 | "lib/", 70 | "apidoc/" 71 | ], 72 | "repository": { 73 | "type": "git", 74 | "url": "git://github.com/openlayers/ol-cesium.git" 75 | }, 76 | "license": "BSD-2-Clause", 77 | "bugs": { 78 | "url": "https://github.com/openlayers/ol-cesium/issues" 79 | }, 80 | "maintainers": [ 81 | { 82 | "name": "Guillaume Beraudo", 83 | "email": "guillaume.beraudo@camptocamp.com", 84 | "url": "https://github.com/gberaudo" 85 | } 86 | ], 87 | "peerDependencies": { 88 | "cesium": ">= 1.90.0", 89 | "ol": ">= 9.2, >= 10" 90 | }, 91 | "devDependencies": { 92 | "@babel/parser": "7.27.5", 93 | "@mapbox/geojsonhint": "3.3.0", 94 | "@swc-node/register": "1.10.10", 95 | "@typescript-eslint/eslint-plugin": "8.33.1", 96 | "@typescript-eslint/parser": "8.33.1", 97 | "cesium": "1.130.0", 98 | "eslint": "8.57.0", 99 | "eslint-config-openlayers": "12.0.0", 100 | "eslint-import-resolver-typescript": "3.6.3", 101 | "eslint-plugin-import": "2.31.0", 102 | "ol": "10.5.0", 103 | "parcel": "2.15.2", 104 | "proj4": "2.17.0", 105 | "typedoc": "0.28.5", 106 | "typescript": "5.8.3" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /examples/data/geojson/vector_data.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { 4 | "type": "name", 5 | "properties": { 6 | "name": "EPSG:3857" 7 | } 8 | }, 9 | "features": [ 10 | { 11 | "type": "Feature", 12 | "geometry": null, 13 | "properties": { 14 | "type": "without geometry as allowed in spec" 15 | } 16 | }, 17 | { 18 | "type": "Feature", 19 | "properties": {}, 20 | "geometry": { 21 | "type": "Point", 22 | "coordinates": [0, 0, 1e5] 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "properties": {}, 28 | "geometry": { 29 | "type": "LineString", 30 | "coordinates": [ 31 | [1e5, 1e5, 1], 32 | [2e5, 2e5, 100000], 33 | [3e5, 3e5, 1000], 34 | [4e5, 4e5, 100000], 35 | [5e5, 5e5, 100], 36 | [6e5, 6e5, 100000], 37 | [7e5, 7e5, 1] 38 | ] 39 | } 40 | }, 41 | { 42 | "type": "Feature", 43 | "properties": {}, 44 | "geometry": { 45 | "type": "LineString", 46 | "coordinates": [ 47 | [4e6, -2e6, 100000], 48 | [8e6, 2e6, 200000] 49 | ] 50 | } 51 | }, 52 | { 53 | "type": "Feature", 54 | "properties": {}, 55 | "geometry": { 56 | "type": "LineString", 57 | "coordinates": [[4e6, 2e6], [8e6, -2e6]] 58 | } 59 | }, 60 | { 61 | "type": "Feature", 62 | "properties": {}, 63 | "geometry": { 64 | "type": "Polygon", 65 | "coordinates": [ 66 | [[-5e6, -1e6, 1e6], [-4e6, 1e6, 1e6], [-3e6, -1e6, 1e6], [-5e6, -1e6, 1e6]] 67 | ] 68 | } 69 | }, 70 | { 71 | "type": "Feature", 72 | "properties": {}, 73 | "geometry": { 74 | "type": "MultiLineString", 75 | "coordinates": [ 76 | [[-1e6, -7.5e5], [-1e6, 7.5e5]], 77 | [[1e6, -7.5e5], [1e6, 7.5e5]], 78 | [[-7.5e5, -1e6], [7.5e5, -1e6]], 79 | [[-7.5e5, 1e6], [7.5e5, 1e6]] 80 | ] 81 | } 82 | }, 83 | { 84 | "type": "Feature", 85 | "properties": {}, 86 | "geometry": { 87 | "type": "MultiPolygon", 88 | "coordinates": [ 89 | [[ 90 | [-5e6, 6e6, 8e5], 91 | [-5e6, 8e6, 8e5], 92 | [-3e6, 8e6, 8e5], 93 | [-3e6, 6e6, 8e5], 94 | [-5e6, 6e6, 8e5] 95 | ]], 96 | [[ 97 | [-2e6, 6e6, 4e5], 98 | [-2e6, 8e6, 1e5], 99 | [0, 8e6, 0], 100 | [0, 6e6, 0], 101 | [-2e6, 6e6, 4e5]] 102 | ], 103 | [[ 104 | [1e6, 6e6, 1e5], 105 | [1e6, 8e6, 0], 106 | [3e6, 8e6, 0], 107 | [3e6, 6e6, 0], 108 | [1e6, 6e6, 1e5] 109 | ]] 110 | ] 111 | } 112 | }, 113 | { 114 | "type": "Feature", 115 | "properties": {}, 116 | "geometry": { 117 | "type": "MultiPoint", 118 | "coordinates": [ 119 | [-5e6, 6e6, 8e5], 120 | [-2e6, 6e6, 4e5], 121 | [1e6, 6e6, 1e5] 122 | ] 123 | } 124 | }, 125 | { 126 | "type": "Feature", 127 | "properties": {}, 128 | "geometry": { 129 | "type": "GeometryCollection", 130 | "geometries": [ 131 | { 132 | "type": "LineString", 133 | "coordinates": [[-5e6, -5e6], [0, -5e6]] 134 | }, 135 | { 136 | "type": "Point", 137 | "coordinates": [4e6, -5e6] 138 | }, 139 | { 140 | "type": "Polygon", 141 | "coordinates": [[ 142 | [1e6, -6e6], 143 | [2e6, -4e6], 144 | [3e6, -6e6], 145 | [1e6, -6e6] 146 | ]] 147 | } 148 | ] 149 | } 150 | } 151 | ] 152 | } 153 | -------------------------------------------------------------------------------- /src/olcs/OverlaySynchronizer.ts: -------------------------------------------------------------------------------- 1 | import type {Collection, Map as OLMap, Overlay} from 'ol'; 2 | import SynchronizedOverlay from './SynchronizedOverlay.js'; 3 | import {getUid} from './util.js'; 4 | import type {Scene} from 'cesium'; 5 | import type {CollectionEvent} from 'ol/Collection.js'; 6 | import {unByKey as olObservableUnByKey} from 'ol/Observable.js'; 7 | import type {EventsKey} from 'ol/events.js'; 8 | 9 | export default class OverlaySynchronizer { 10 | private overlayCollection_: Collection; 11 | private overlayContainerStopEvent_: HTMLDivElement; 12 | private overlayContainer_: HTMLDivElement; 13 | private overlayMap_: Map = new Map(); 14 | private overlayEvents = ['click', 'dblclick', 'mousedown', 'touchstart', 'pointerdown', 'mousewheel', 'wheel']; 15 | private listenerKeys_: EventsKey[] = []; 16 | 17 | /** 18 | * @param map 19 | * @param scene 20 | * @constructor 21 | * @api 22 | */ 23 | constructor(protected map: OLMap, protected scene: Scene) { 24 | this.map = map; 25 | this.overlayCollection_ = this.map.getOverlays(); 26 | this.scene = scene; 27 | this.overlayContainerStopEvent_ = document.createElement('div'); 28 | this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent'; 29 | this.overlayEvents.forEach((name) => { 30 | this.overlayContainerStopEvent_.addEventListener(name, evt => evt.stopPropagation()); 31 | }); 32 | this.scene.canvas.parentElement.appendChild(this.overlayContainerStopEvent_); 33 | 34 | this.overlayContainer_ = document.createElement('div'); 35 | this.overlayContainer_.className = 'ol-overlaycontainer'; 36 | this.scene.canvas.parentElement.appendChild(this.overlayContainer_); 37 | } 38 | 39 | /** 40 | * Get the element that serves as a container for overlays that don't allow 41 | * event propagation. Elements added to this container won't let mousedown and 42 | * touchstart events through to the map, so clicks and gestures on an overlay 43 | * don't trigger any {@link ol.MapBrowserEvent}. 44 | * @return The map's overlay container that stops events. 45 | */ 46 | getOverlayContainerStopEvent(): Element { 47 | return this.overlayContainerStopEvent_; 48 | } 49 | 50 | /** 51 | * Get the element that serves as a container for overlays. 52 | * @return The map's overlay container. 53 | */ 54 | getOverlayContainer(): Element { 55 | return this.overlayContainer_; 56 | } 57 | 58 | /** 59 | * Destroy all and perform complete synchronization of the overlays. 60 | * @api 61 | */ 62 | synchronize() { 63 | this.destroyAll(); 64 | this.overlayCollection_.forEach((overlay) => { this.addOverlay(overlay); }); 65 | this.listenerKeys_.push( 66 | this.overlayCollection_.on('add', (evt: CollectionEvent) => this.addOverlay(evt.element)) 67 | ); 68 | this.listenerKeys_.push( 69 | this.overlayCollection_.on('remove', (evt: CollectionEvent) => this.removeOverlay(evt.element)) 70 | ); 71 | } 72 | 73 | 74 | /** 75 | * @api 76 | */ 77 | addOverlay(overlay: Overlay) { 78 | if (!overlay) { 79 | return; 80 | } 81 | const cesiumOverlay = new SynchronizedOverlay({ 82 | scene: this.scene, 83 | synchronizer: this, 84 | parent: overlay 85 | }); 86 | 87 | this.overlayMap_.set(getUid(overlay), cesiumOverlay); 88 | } 89 | 90 | 91 | /** 92 | * Removes an overlay from the scene 93 | * @api 94 | */ 95 | removeOverlay(overlay: Overlay) { 96 | const overlayId = getUid(overlay); 97 | const csOverlay = this.overlayMap_.get(overlayId); 98 | if (csOverlay) { 99 | csOverlay.destroy(); 100 | this.overlayMap_.delete(overlayId); 101 | } 102 | } 103 | 104 | /** 105 | * Destroys all the created Cesium objects. 106 | */ 107 | protected destroyAll() { 108 | this.overlayMap_.forEach((overlay: SynchronizedOverlay) => { 109 | overlay.destroy(); 110 | }); 111 | this.overlayMap_.clear(); 112 | olObservableUnByKey(this.listenerKeys_); 113 | this.listenerKeys_.length = 0; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /examples/overlay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Overlay example 7 | 8 | 9 | 10 | 66 | 67 | 68 |
69 | 70 |
71 | 72 |
73 |
74 | 75 |
76 | 80 | 81 |
82 |
83 | 84 |
85 |
86 | On Click... 87 | ... add a new Overlay
88 | ... re-position the Overlay
89 |
90 |
91 | 92 |
93 |
94 | What type of overlay to use? 95 | Default HTML Elements
96 | Bootstrap popover
97 |
98 |
99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /examples/layer-group.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ol-Cesium | Layergroup synchronization 7 | 8 | 9 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
Click on layer nodes below to change their properties.
29 |
    30 |
  • OSM layer 31 |
    32 | 36 | 37 | 39 |
    40 |
  • 41 |
  • Layer group 42 |
    43 | 47 | 48 | 50 |
    51 |
      52 |
    • Food insecurity layer 53 |
      54 | 58 | 59 | 61 |
      62 |
    • 63 |
    • World borders layer 64 |
      65 | 69 | 70 | 72 |
      73 |
    • 74 |
    75 |
  • 76 |
77 |
78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /examples/rotate.js: -------------------------------------------------------------------------------- 1 | import OLCesium, {computeSignedTiltAngleOnGlobe, pickBottomPoint, computeAngleToZenith, setHeadingUsingBottomCenter, rotateAroundAxis} from 'olcs'; 2 | import olView from 'ol/View.js'; 3 | import {defaults as olControlDefaults} from 'ol/control.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import olMap from 'ol/Map.js'; 7 | 8 | 9 | const map = new olMap({ 10 | layers: [ 11 | new olLayerTile({ 12 | source: new olSourceOSM() 13 | }) 14 | ], 15 | target: 'mapCesium', 16 | controls: olControlDefaults({ 17 | attributionOptions: { 18 | collapsible: false 19 | } 20 | }), 21 | view: new olView({ 22 | center: [333333, 1500000], 23 | zoom: 6 24 | }) 25 | }); 26 | 27 | 28 | const ol3d = new OLCesium({map/*, target: 'map3d'*/}); 29 | 30 | ol3d.getCesiumScene(); 31 | ol3d.setEnabled(true); 32 | 33 | /** 34 | * @param {!olcs.OLCesium} ol3d 35 | * @constructor 36 | */ 37 | const OlcsControl = function(ol3d) { 38 | 39 | /** 40 | * @type {!olcs.OLCesium} 41 | * @private 42 | */ 43 | this.ol3d_ = ol3d; 44 | }; 45 | 46 | 47 | /** 48 | * Almost PI / 2. 49 | * @const 50 | * @type {number} 51 | */ 52 | OlcsControl.MAX_TILT = 7 * Math.PI / 16; 53 | 54 | 55 | /** 56 | * @const 57 | * @type {number} 58 | */ 59 | OlcsControl.MIN_TILT = 0; 60 | 61 | 62 | /** 63 | * @return {ol.View} 64 | */ 65 | OlcsControl.prototype.getOlView = function() { 66 | return this.ol3d_.getOlView(); 67 | }; 68 | 69 | 70 | /** 71 | * @return {Array.} 72 | */ 73 | OlcsControl.prototype.getTiltRange = function() { 74 | return [OlcsControl.MIN_TILT, OlcsControl.MAX_TILT]; 75 | }; 76 | 77 | 78 | /** 79 | * @return {number} 80 | */ 81 | OlcsControl.prototype.getHeading = function() { 82 | return this.getOlView().getRotation() || 0; 83 | }; 84 | 85 | 86 | /** 87 | * @return {number|undefined} 88 | */ 89 | OlcsControl.prototype.getTiltOnGlobe = function() { 90 | const scene = this.ol3d_.getCesiumScene(); 91 | const tiltOnGlobe = computeSignedTiltAngleOnGlobe(scene); 92 | return -tiltOnGlobe; 93 | }; 94 | 95 | 96 | /** 97 | * @param {function()} callback 98 | */ 99 | OlcsControl.prototype.resetToNorthZenith = function(callback) { 100 | const scene = this.ol3d_.getCesiumScene(); 101 | const camera = scene.camera; 102 | const pivot = pickBottomPoint(scene); 103 | 104 | if (!pivot) { 105 | callback(); 106 | return; 107 | } 108 | 109 | const currentHeading = this.getHeading(); 110 | const angle = computeAngleToZenith(scene, pivot); 111 | 112 | // Point to North 113 | setHeadingUsingBottomCenter(scene, currentHeading, pivot); 114 | 115 | // Go to zenith 116 | const transform = Cesium.Matrix4.fromTranslation(pivot); 117 | const axis = camera.right; 118 | const options = {callback}; 119 | rotateAroundAxis(camera, -angle, axis, transform, options); 120 | }; 121 | 122 | 123 | OlcsControl.prototype.rotate = function(angle) { 124 | const view = this.ol3d_.getOlView(); 125 | const current = view.getRotation(); 126 | view.animate({ 127 | rotation: current + angle, 128 | duration: 250 129 | }); 130 | }; 131 | 132 | 133 | /** 134 | * @param {number} angle 135 | */ 136 | OlcsControl.prototype.setHeading = function(angle) { 137 | const scene = this.ol3d_.getCesiumScene(); 138 | const bottom = pickBottomPoint(scene); 139 | if (bottom) { 140 | setHeadingUsingBottomCenter(scene, angle, bottom); 141 | } 142 | }; 143 | 144 | 145 | /** 146 | * @param {number} angle 147 | */ 148 | OlcsControl.prototype.tiltOnGlobe = function(angle) { 149 | const scene = this.ol3d_.getCesiumScene(); 150 | const camera = scene.camera; 151 | const pivot = pickBottomPoint(scene); 152 | if (!pivot) { 153 | // Could not find the bottom point 154 | return; 155 | } 156 | 157 | const options = {}; 158 | const transform = Cesium.Matrix4.fromTranslation(pivot); 159 | const axis = camera.right; 160 | rotateAroundAxis(camera, -angle, axis, transform, options); 161 | }; 162 | 163 | 164 | 165 | window['control'] = new OlcsControl(ol3d); // eslint-disable-line no-unused-vars 166 | 167 | //##REMOVE## Keep this tag, split code here for code sandbox 168 | 169 | import {initCodeSandbox} from './_code-sandbox.js'; 170 | initCodeSandbox('rawjs/rotate.js'); 171 | -------------------------------------------------------------------------------- /gh-pages-template/stylesheets/pygment_trac.css: -------------------------------------------------------------------------------- 1 | .highlight { background: #ffffff; } 2 | .highlight .c { color: #999988; font-style: italic } /* Comment */ 3 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 4 | .highlight .k { font-weight: bold } /* Keyword */ 5 | .highlight .o { font-weight: bold } /* Operator */ 6 | .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ 7 | .highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ 8 | .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ 9 | .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ 10 | .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 11 | .highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #aa0000 } /* Generic.Error */ 14 | .highlight .gh { color: #999999 } /* Generic.Heading */ 15 | .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 16 | .highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ 17 | .highlight .go { color: #888888 } /* Generic.Output */ 18 | .highlight .gp { color: #555555 } /* Generic.Prompt */ 19 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 20 | .highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ 21 | .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 22 | .highlight .kc { font-weight: bold } /* Keyword.Constant */ 23 | .highlight .kd { font-weight: bold } /* Keyword.Declaration */ 24 | .highlight .kn { font-weight: bold } /* Keyword.Namespace */ 25 | .highlight .kp { font-weight: bold } /* Keyword.Pseudo */ 26 | .highlight .kr { font-weight: bold } /* Keyword.Reserved */ 27 | .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 28 | .highlight .m { color: #009999 } /* Literal.Number */ 29 | .highlight .s { color: #d14 } /* Literal.String */ 30 | .highlight .na { color: #008080 } /* Name.Attribute */ 31 | .highlight .nb { color: #0086B3 } /* Name.Builtin */ 32 | .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ 33 | .highlight .no { color: #008080 } /* Name.Constant */ 34 | .highlight .ni { color: #800080 } /* Name.Entity */ 35 | .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ 36 | .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ 37 | .highlight .nn { color: #555555 } /* Name.Namespace */ 38 | .highlight .nt { color: #000080 } /* Name.Tag */ 39 | .highlight .nv { color: #008080 } /* Name.Variable */ 40 | .highlight .ow { font-weight: bold } /* Operator.Word */ 41 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .highlight .mf { color: #009999 } /* Literal.Number.Float */ 43 | .highlight .mh { color: #009999 } /* Literal.Number.Hex */ 44 | .highlight .mi { color: #009999 } /* Literal.Number.Integer */ 45 | .highlight .mo { color: #009999 } /* Literal.Number.Oct */ 46 | .highlight .sb { color: #d14 } /* Literal.String.Backtick */ 47 | .highlight .sc { color: #d14 } /* Literal.String.Char */ 48 | .highlight .sd { color: #d14 } /* Literal.String.Doc */ 49 | .highlight .s2 { color: #d14 } /* Literal.String.Double */ 50 | .highlight .se { color: #d14 } /* Literal.String.Escape */ 51 | .highlight .sh { color: #d14 } /* Literal.String.Heredoc */ 52 | .highlight .si { color: #d14 } /* Literal.String.Interpol */ 53 | .highlight .sx { color: #d14 } /* Literal.String.Other */ 54 | .highlight .sr { color: #009926 } /* Literal.String.Regex */ 55 | .highlight .s1 { color: #d14 } /* Literal.String.Single */ 56 | .highlight .ss { color: #990073 } /* Literal.String.Symbol */ 57 | .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ 58 | .highlight .vc { color: #008080 } /* Name.Variable.Class */ 59 | .highlight .vg { color: #008080 } /* Name.Variable.Global */ 60 | .highlight .vi { color: #008080 } /* Name.Variable.Instance */ 61 | .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ 62 | 63 | .type-csharp .highlight .k { color: #0000FF } 64 | .type-csharp .highlight .kt { color: #0000FF } 65 | .type-csharp .highlight .nf { color: #000000; font-weight: normal } 66 | .type-csharp .highlight .nc { color: #2B91AF } 67 | .type-csharp .highlight .nn { color: #000000 } 68 | .type-csharp .highlight .s { color: #A31515 } 69 | .type-csharp .highlight .sc { color: #A31515 } 70 | -------------------------------------------------------------------------------- /gh-pages-template/stylesheets/print.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | body { 45 | font-size: 13px; 46 | line-height: 1.5; 47 | font-family: 'Helvetica Neue', Helvetica, Arial, serif; 48 | color: #000; 49 | } 50 | 51 | a { 52 | color: #d5000d; 53 | font-weight: bold; 54 | } 55 | 56 | header { 57 | padding-top: 35px; 58 | padding-bottom: 10px; 59 | } 60 | 61 | header h1 { 62 | font-weight: bold; 63 | letter-spacing: -1px; 64 | font-size: 48px; 65 | color: #303030; 66 | line-height: 1.2; 67 | } 68 | 69 | header h2 { 70 | letter-spacing: -1px; 71 | font-size: 24px; 72 | color: #aaa; 73 | font-weight: normal; 74 | line-height: 1.3; 75 | } 76 | #downloads { 77 | display: none; 78 | } 79 | #main_content { 80 | padding-top: 20px; 81 | } 82 | 83 | code, pre { 84 | font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal; 85 | color: #222; 86 | margin-bottom: 30px; 87 | font-size: 12px; 88 | } 89 | 90 | code { 91 | padding: 0 3px; 92 | } 93 | 94 | pre { 95 | border: solid 1px #ddd; 96 | padding: 20px; 97 | overflow: auto; 98 | } 99 | pre code { 100 | padding: 0; 101 | } 102 | 103 | ul, ol, dl { 104 | margin-bottom: 20px; 105 | } 106 | 107 | 108 | /* COMMON STYLES */ 109 | 110 | table { 111 | width: 100%; 112 | border: 1px solid #ebebeb; 113 | } 114 | 115 | th { 116 | font-weight: 500; 117 | } 118 | 119 | td { 120 | border: 1px solid #ebebeb; 121 | text-align: center; 122 | font-weight: 300; 123 | } 124 | 125 | form { 126 | background: #f2f2f2; 127 | padding: 20px; 128 | 129 | } 130 | 131 | 132 | /* GENERAL ELEMENT TYPE STYLES */ 133 | 134 | h1 { 135 | font-size: 2.8em; 136 | } 137 | 138 | h2 { 139 | font-size: 22px; 140 | font-weight: bold; 141 | color: #303030; 142 | margin-bottom: 8px; 143 | } 144 | 145 | h3 { 146 | color: #d5000d; 147 | font-size: 18px; 148 | font-weight: bold; 149 | margin-bottom: 8px; 150 | } 151 | 152 | h4 { 153 | font-size: 16px; 154 | color: #303030; 155 | font-weight: bold; 156 | } 157 | 158 | h5 { 159 | font-size: 1em; 160 | color: #303030; 161 | } 162 | 163 | h6 { 164 | font-size: .8em; 165 | color: #303030; 166 | } 167 | 168 | p { 169 | font-weight: 300; 170 | margin-bottom: 20px; 171 | } 172 | 173 | a { 174 | text-decoration: none; 175 | } 176 | 177 | p a { 178 | font-weight: 400; 179 | } 180 | 181 | blockquote { 182 | font-size: 1.6em; 183 | border-left: 10px solid #e9e9e9; 184 | margin-bottom: 20px; 185 | padding: 0 0 0 30px; 186 | } 187 | 188 | ul li { 189 | list-style: disc inside; 190 | padding-left: 20px; 191 | } 192 | 193 | ol li { 194 | list-style: decimal inside; 195 | padding-left: 3px; 196 | } 197 | 198 | dl dd { 199 | font-style: italic; 200 | font-weight: 100; 201 | } 202 | 203 | footer { 204 | margin-top: 40px; 205 | padding-top: 20px; 206 | padding-bottom: 30px; 207 | font-size: 13px; 208 | color: #aaa; 209 | } 210 | 211 | footer a { 212 | color: #666; 213 | } 214 | 215 | /* MISC */ 216 | .clearfix:after { 217 | clear: both; 218 | content: '.'; 219 | display: block; 220 | visibility: hidden; 221 | height: 0; 222 | } 223 | 224 | .clearfix {display: inline-block;} 225 | * html .clearfix {height: 1%;} 226 | .clearfix {display: block;} -------------------------------------------------------------------------------- /examples/groundvectors.js: -------------------------------------------------------------------------------- 1 | import OLCesium from 'olcs'; 2 | import olView from 'ol/View.js'; 3 | import {defaults as olControlDefaults} from 'ol/control.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import olStyleText from 'ol/style/Text.js'; 7 | import olStyleStyle from 'ol/style/Style.js'; 8 | import olStyleStroke from 'ol/style/Stroke.js'; 9 | import olStyleFill from 'ol/style/Fill.js'; 10 | import olMap from 'ol/Map.js'; 11 | import olStyleCircle from 'ol/style/Circle.js'; 12 | import olSourceVector from 'ol/source/Vector.js'; 13 | import olFormatGeoJSON from 'ol/format/GeoJSON.js'; 14 | import olLayerVector from 'ol/layer/Vector.js'; 15 | import olFeature from 'ol/Feature.js'; 16 | import olCircle from 'ol/geom/Circle.js'; 17 | 18 | const image = new olStyleCircle({ 19 | radius: 5, 20 | fill: null, 21 | stroke: new olStyleStroke({color: 'red', width: 1}) 22 | }); 23 | 24 | const styles = { 25 | 'Point': [new olStyleStyle({ 26 | image 27 | })], 28 | 'LineString': [new olStyleStyle({ 29 | stroke: new olStyleStroke({ 30 | color: 'green', 31 | width: 2 32 | }) 33 | })], 34 | 'MultiLineString': [new olStyleStyle({ 35 | stroke: new olStyleStroke({ 36 | color: 'green', 37 | width: 2 38 | }) 39 | })], 40 | 'MultiPoint': [new olStyleStyle({ 41 | image, 42 | text: new olStyleText({ 43 | text: 'MP', 44 | stroke: new olStyleStroke({ 45 | color: 'purple' 46 | }) 47 | }) 48 | })], 49 | 'MultiPolygon': [new olStyleStyle({ 50 | stroke: new olStyleStroke({ 51 | color: 'yellow', 52 | width: 1 53 | }), 54 | fill: new olStyleFill({ 55 | color: 'rgba(255, 255, 0, 0.1)' 56 | }) 57 | })], 58 | 'Polygon': [new olStyleStyle({ 59 | stroke: new olStyleStroke({ 60 | color: 'blue', 61 | width: 3 62 | }), 63 | fill: new olStyleFill({ 64 | color: 'rgba(0, 0, 255, 0.1)' 65 | }) 66 | })], 67 | 'GeometryCollection': [new olStyleStyle({ 68 | stroke: new olStyleStroke({ 69 | color: 'magenta', 70 | width: 2 71 | }), 72 | fill: new olStyleFill({ 73 | color: 'magenta' 74 | }), 75 | image: new olStyleCircle({ 76 | radius: 10, // pixels 77 | fill: null, 78 | stroke: new olStyleStroke({ 79 | color: 'magenta' 80 | }) 81 | }) 82 | })], 83 | 'Circle': [new olStyleStyle({ 84 | stroke: new olStyleStroke({ 85 | color: 'red', 86 | width: 2 87 | }), 88 | fill: new olStyleFill({ 89 | color: 'rgba(255,0,0,0.2)' 90 | }) 91 | })] 92 | }; 93 | 94 | const styleFunction = function(feature, resolution) { 95 | const geo = feature.getGeometry(); 96 | // always assign a style to prevent feature skipping 97 | return geo ? styles[geo.getType()] : styles['Point']; 98 | }; 99 | 100 | const vectorSource = new olSourceVector({ 101 | format: new olFormatGeoJSON(), 102 | url: 'data/geojson/ground_vector_data.geojson' // la mettre en ref 103 | }); 104 | 105 | const vectorLayer = new olLayerVector({ 106 | source: vectorSource, 107 | style: styleFunction 108 | }); 109 | 110 | vectorLayer.getSource().addFeature(new olFeature({ 111 | geometry: new olCircle([16880133.570042003, -3565441.544459192], 200) 112 | })); 113 | 114 | const map = new olMap({ 115 | layers: [ 116 | new olLayerTile({ 117 | source: new olSourceOSM() 118 | }), 119 | vectorLayer, 120 | ], 121 | target: 'map2d', 122 | controls: olControlDefaults({ 123 | attributionOptions: { 124 | collapsible: false 125 | } 126 | }), 127 | view: new olView({ 128 | center: [16880670.33392873, -3565966.2275828626], 129 | zoom: 15 130 | }) 131 | }); 132 | 133 | vectorLayer.set('altitudeMode', 'clampToGround'); 134 | const ol3d = new OLCesium({map, target: 'map3d'}); 135 | const scene = ol3d.getCesiumScene(); 136 | ol3d.setEnabled(true); 137 | 138 | window['toggleClampToGround'] = function() { 139 | let altitudeMode; 140 | if (!vectorLayer.get('altitudeMode')) { 141 | altitudeMode = 'clampToGround'; 142 | } 143 | vectorLayer.set('altitudeMode', altitudeMode); 144 | map.removeLayer(vectorLayer); 145 | map.addLayer(vectorLayer); 146 | }; 147 | 148 | window['ol3d'] = ol3d; 149 | window['scene'] = scene; 150 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 151 | 152 | ol3d.enableAutoRenderLoop(); 153 | 154 | //##REMOVE## Keep this tag, split code here for code sandbox 155 | 156 | import {initCodeSandbox} from './_code-sandbox.js'; 157 | initCodeSandbox('rawjs/groundvectors.js', 'data/geojson/ground_vector_data.geojson'); 158 | 159 | // faire à la demande 160 | // définir un objet 161 | // ex Ground vector titlre + urls list 162 | // introduire 163 | 164 | 165 | // voir 166 | //https://github.com/geoblocks/mapfishprint/tree/main/demo 167 | 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenLayers - Cesium library 2 | 3 | OLCS is an opensource JS library for making [OpenLayers](https://openlayers.org/) and [CesiumJS](https://cesium.com/platform/cesiumjs/) works together, in the same application. 4 | It addresses several use-cases: 5 | 6 | - [Adding 3D to an existing OpenLayers map](#Adding 3D to an existing OpenLayers map) 7 | - [Extending CesiumJS with new capabilities](#Extending CesiumJS with new capabilities) 8 | - [Cherry-picking the pieces you need](#Cherry-picking the pieces you need) 9 | 10 | See [live examples](https://openlayers.org/ol-cesium/examples/). 11 | 12 | The npm package is called [olcs](https://www.npmjs.com/package/olcs). 13 | Note that CesiumJS is accessed through the global `window.Cesium` object. 14 | 15 | ## Features 16 | 17 | Switch smoothly between 2D and 3D and synchronize: 18 | 19 | - Map context (bounding box and zoom level); 20 | - Raster data sources; 21 | - Vector data sources in 2D and 3D; 22 | - Map selection (selected items); 23 | - Animated transitions between map and globe view. 24 | 25 | The library is configurable and extensible and allows: 26 | 27 | - Lazy or eager loading of Cesium 28 | - Limiting Cesium resource consumption (idle detection) 29 | 30 | For synchronization of maps in projections other than EPSG:4326 and EPSG:3857 you need 2 datasets, see the customProj example. 31 | 32 | ## Adding 3D to an existing OpenLayers map 33 | 34 | ```js 35 | // Create an OpenLayers map or start from an existing one. 36 | import Map from 'ol/Map.js'; 37 | const ol2dMap = new Map({ 38 | ... 39 | }); 40 | ol2dMap.addLayer(....) 41 | ``` 42 | 43 | ```js 44 | // Pass the map to the OL-Cesium constructor 45 | // OL-Cesium will create and synchronize a 3D CesiumJs globe from your layers and data. 46 | import OLCesium from 'olcs'; 47 | const ol3d = new OLCesium({map: ol2dMap}); 48 | ``` 49 | 50 | ```js 51 | ol3d.setEnabled(true); // switch to 3D - show the globe 52 | ol3d.setEnabled(false); // switch to 2D - show the map 53 | ``` 54 | 55 | Build with your prefered bundler. 56 | 57 | You can use any version of CesiumJS: latest upstream, a fork... 58 | Simply provide it as `window.Cesium` global: 59 | 60 | ```html 61 | 62 | ``` 63 | 64 | ## Extending CesiumJS with new capabilities 65 | 66 | ```js 67 | // Start from a CesiumJS globe 68 | const viewer = getYourCesiumJSViewer(); 69 | 70 | // Add OpenLayers imagery provider 71 | import {OLImageryProvider} from 'olcs'; 72 | viewer.scene.imageryLayers.addImageryProvider(new OLImageryProvider(...)); 73 | 74 | // Add Mapbox MVT imagery provider (client side rendering) 75 | import {MVTImageryProvider} from 'olcs'; 76 | viewer.scene.imageryLayers.addImageryProvider(new MVTImageryProvider(...)); 77 | ``` 78 | 79 | This is a bit limited at the moment but idea would be to implement: 80 | 81 | - client side reprojection; 82 | - full client side MVT rendering; 83 | - GeoTIFF rendering; 84 | - ... any feature available in OpenLayers. 85 | 86 | ## Cherry-picking the pieces you need 87 | 88 | Specific low level functionnalities can be cherry-picked from the library. 89 | For example: 90 | 91 | ```js 92 | // GoogleMap rotating effect 93 | import {rotateAroundBottomCenter} from 'olcs'; 94 | rotateAroundBottomCenter(viewer.scene, someAngle); 95 | ``` 96 | 97 | ```ts 98 | // convert OpenLayers Vector Layer to CesiumJS primitives 99 | import {FeatureConverter} from 'olcs'; 100 | const converter = new FeatureConverter(viewer.scene); 101 | const featurePrimitiveMap: Record = {}; 102 | const counterpart: VectorLayerCounterpart = this.converter.olVectorLayerToCesium(olLayer, view, featurePrimitiveMap); 103 | const csPrimitives = counterpart.getRootPrimitive(); 104 | viewer.scene.primitives.add(csPrimitives); 105 | ``` 106 | 107 | ```js 108 | // Even more powerful, use a synchronizer 109 | import {VectorSynchronizer} from 'olcs'; 110 | const synchronizer = new VectorSynchronizer(ol2dMtheap, viewer.scene); 111 | ``` 112 | 113 | If you think some low level features should be spotlited here, open an issue and let's discuss it. 114 | 115 | ## Configuration 116 | 117 | Use properties to control specific aspects of OL-Cesium integration, see the [PROPERTIES.MD](https://github.com/openlayers/ol-cesium/blob/master/PROPERTIES.md). 118 | 119 | Also, check the [api doc](https://openlayers.org/ol-cesium/doc/). 120 | 121 | ## Limitations due to OpenLayers 122 | 123 | There are a few limitations due to decisions on 124 | 125 | - OpenLayers unmanaged layers are not discoverable and as a consequence not 126 | supported. Plain layers should be used instead of the synchronization managed 127 | manually. See https://github.com/openlayers/ol-cesium/issues/350. 128 | 129 | - OpenLayers interactions are not supported in 3d. See https://github.com/openlayers/ol-cesium/issues/655. 130 | 131 | ## Contributing 132 | 133 | See [CONTRIBUTING.md](./CONTRIBUTING.md). 134 | -------------------------------------------------------------------------------- /src/olcs/print/drawCesiumMask.ts: -------------------------------------------------------------------------------- 1 | import type {Scene} from 'cesium'; 2 | 3 | let postUnlistener: Function = null; 4 | 5 | interface ProgramInfo { 6 | program: WebGLProgram, 7 | attribLocations: { 8 | vertexPosition: number 9 | }, 10 | uniformLocations: { 11 | uScaling: WebGLUniformLocation 12 | } 13 | } 14 | 15 | 16 | // CC0 from https://github.com/mdn/dom-examples/tree/main/webgl-examples/tutorial/sample2 17 | 18 | 19 | export class MaskDrawer { 20 | private programInfo: ProgramInfo; 21 | private positionBuffer: WebGLBuffer; 22 | 23 | constructor(private gl: WebGL2RenderingContext | WebGLRenderingContext) { 24 | const shaderProgram = this.initShaderProgram(); 25 | 26 | this.programInfo = { 27 | program: shaderProgram, 28 | attribLocations: { 29 | vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition') 30 | }, 31 | uniformLocations: { 32 | uScaling: gl.getUniformLocation( 33 | shaderProgram, 34 | 'uScaling' 35 | ) 36 | } 37 | }; 38 | 39 | this.positionBuffer = gl.createBuffer(); 40 | const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; 41 | 42 | gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); 43 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); 44 | } 45 | 46 | getVertexShaderSource() { 47 | return ` 48 | attribute vec4 aVertexPosition; 49 | uniform vec2 uScaling; 50 | void main() { 51 | gl_Position = vec4(aVertexPosition[0] * uScaling[0], aVertexPosition[1] * uScaling[1], -1.0, 1.0); 52 | } 53 | `; 54 | } 55 | 56 | getFragmentShaderSource() { 57 | return ` 58 | precision highp float; 59 | void main() { 60 | gl_FragColor = vec4(.5, .5, .5, .6); 61 | } 62 | `; 63 | } 64 | 65 | /** 66 | * 67 | */ 68 | private initShaderProgram(): WebGLProgram { 69 | const gl = this.gl; 70 | const vsSource = this.getVertexShaderSource(); 71 | const fsSource = this.getFragmentShaderSource(); 72 | const vertexShader = MaskDrawer.loadShader(gl, gl.VERTEX_SHADER, vsSource), 73 | fragmentShader = MaskDrawer.loadShader(gl, gl.FRAGMENT_SHADER, fsSource), 74 | shaderProgram = gl.createProgram(); 75 | 76 | gl.attachShader(shaderProgram, vertexShader); 77 | gl.attachShader(shaderProgram, fragmentShader); 78 | gl.linkProgram(shaderProgram); 79 | 80 | // If creating the shader program failed, alert 81 | 82 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 83 | throw new Error( 84 | `Unable to initialize the shader program: ${gl.getProgramInfoLog( 85 | shaderProgram 86 | )}` 87 | ); 88 | } 89 | 90 | return shaderProgram; 91 | } 92 | 93 | 94 | /** 95 | * 96 | * @param {number[]} scaling scaling 97 | */ 98 | drawMask(scaling: number[]) { 99 | const gl = this.gl; 100 | const programInfo = this.programInfo; 101 | // Blend 102 | gl.enable(gl.BLEND); 103 | 104 | gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); 105 | gl.vertexAttribPointer( 106 | programInfo.attribLocations.vertexPosition, 107 | 2, 108 | gl.FLOAT, 109 | false, 110 | 0, 111 | 0 112 | ); 113 | gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition); 114 | gl.useProgram(programInfo.program); 115 | 116 | 117 | // Draw a first time to fill the stencil area while keeping the destination color 118 | gl.enable(gl.STENCIL_TEST); 119 | gl.stencilFunc( 120 | gl.ALWAYS, 121 | 1, 122 | 0xFF 123 | ); 124 | gl.stencilOp( 125 | gl.KEEP, 126 | gl.KEEP, 127 | gl.REPLACE 128 | ); 129 | gl.uniform2fv( 130 | programInfo.uniformLocations.uScaling, 131 | scaling 132 | ); 133 | gl.blendFunc(gl.ZERO, gl.ONE); 134 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 135 | 136 | 137 | // Now draw again the whole viewport and darken the pixels that are not on the stencil 138 | gl.stencilFunc( 139 | gl.EQUAL, 140 | 0, 141 | 0xFF 142 | ); 143 | gl.stencilOp( 144 | gl.KEEP, 145 | gl.KEEP, 146 | gl.KEEP 147 | ); 148 | gl.uniform2fv( 149 | programInfo.uniformLocations.uScaling, 150 | [1, 1] 151 | ); 152 | gl.blendFunc(gl.ZERO, gl.SRC_ALPHA); 153 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 154 | } 155 | 156 | 157 | 158 | /** 159 | */ 160 | private static loadShader(gl: WebGL2RenderingContext | WebGLRenderingContext, type: number, source: string): WebGLShader { 161 | const shader = gl.createShader(type); 162 | 163 | gl.shaderSource(shader, source); 164 | gl.compileShader(shader); 165 | 166 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 167 | throw new Error( 168 | `An error occurred compiling the shaders: ${gl.getShaderInfoLog(shader)}` 169 | ); 170 | // gl.deleteShader(shader); 171 | } 172 | 173 | return shader; 174 | } 175 | } 176 | 177 | /** 178 | * 179 | */ 180 | export function autoDrawMask(scene: Scene, getScalings: () => number[]) { 181 | const canvas = scene.canvas; 182 | const ctx = canvas.getContext('webgl2') || canvas.getContext('webgl'); 183 | 184 | if (getScalings) { 185 | if (!postUnlistener) { 186 | const drawer = new MaskDrawer(ctx); 187 | postUnlistener = scene.postRender.addEventListener(() => { 188 | drawer.drawMask(getScalings()); 189 | }); 190 | } 191 | } 192 | else if (postUnlistener) { 193 | postUnlistener(); 194 | // FIXME: destroy program 195 | postUnlistener = null; 196 | } 197 | scene.requestRender(); 198 | } 199 | -------------------------------------------------------------------------------- /.eslintrc-es6.yaml: -------------------------------------------------------------------------------- 1 | { 2 | env: { 3 | es6: true 4 | }, 5 | parser: "@typescript-eslint/parser", 6 | plugins: ["@typescript-eslint"], 7 | parserOptions: { 8 | ecmaVersion: 6, 9 | sourceType: 'module', 10 | ecmaFeatures: { 11 | generators: false, 12 | objectLiteralDuplicateProperties: false 13 | } 14 | }, 15 | 16 | rules: { 17 | # enforces no braces where they can be omitted 18 | # http://eslint.org/docs/rules/arrow-body-style 19 | 'arrow-body-style': ['error', 'as-needed', { 20 | requireReturnForObjectLiteral: true, 21 | }], 22 | 23 | # require parens in arrow function arguments 24 | # http://eslint.org/docs/rules/arrow-parens 25 | 'arrow-parens': ['error', 'as-needed', { 26 | requireForBlockBody: true, 27 | }], 28 | 29 | # require space before/after arrow function's arrow 30 | # http://eslint.org/docs/rules/arrow-spacing 31 | 'arrow-spacing': ['error', { before: true, after: true }], 32 | 33 | # verify super() callings in constructors 34 | 'constructor-super': 'error', 35 | 36 | # enforce the spacing around the * in generator functions 37 | # http://eslint.org/docs/rules/generator-star-spacing 38 | 'generator-star-spacing': ['error', { before: false, after: true }], 39 | 40 | # disallow modifying variables of class declarations 41 | # http://eslint.org/docs/rules/no-class-assign 42 | 'no-class-assign': 'error', 43 | 44 | # disallow arrow functions where they could be confused with comparisons 45 | # http://eslint.org/docs/rules/no-confusing-arrow 46 | 'no-confusing-arrow': ['error', { 47 | allowParens: true, 48 | }], 49 | 50 | # disallow modifying variables that are declared using const 51 | 'no-const-assign': 'error', 52 | 53 | # disallow duplicate class members 54 | # http://eslint.org/docs/rules/no-dupe-class-members 55 | 'no-dupe-class-members': 'error', 56 | 57 | # disallow importing from the same path more than once 58 | # http://eslint.org/docs/rules/no-duplicate-imports 59 | 'no-duplicate-imports': 'error', 60 | 61 | # disallow symbol constructor 62 | # http://eslint.org/docs/rules/no-new-symbol 63 | 'no-new-symbol': 'error', 64 | 65 | # disallow specific imports 66 | # http://eslint.org/docs/rules/no-restricted-imports 67 | 'no-restricted-imports': 'off', 68 | 69 | # disallow to use this/super before super() calling in constructors. 70 | # http://eslint.org/docs/rules/no-this-before-super 71 | 'no-this-before-super': 'error', 72 | 73 | # disallow useless computed property keys 74 | # http://eslint.org/docs/rules/no-useless-computed-key 75 | 'no-useless-computed-key': 'error', 76 | 77 | # disallow unnecessary constructor 78 | # http://eslint.org/docs/rules/no-useless-constructor 79 | 'no-useless-constructor': 'error', 80 | 81 | # disallow renaming import, export, and destructured assignments to the same name 82 | # http://eslint.org/docs/rules/no-useless-rename 83 | 'no-useless-rename': ['error', { 84 | ignoreDestructuring: false, 85 | ignoreImport: false, 86 | ignoreExport: false, 87 | }], 88 | 89 | # require let or const instead of var 90 | 'no-var': 'error', 91 | 92 | # require method and property shorthand syntax for object literals 93 | # http://eslint.org/docs/rules/object-shorthand 94 | 'object-shorthand': ['error', 'always', { 95 | ignoreConstructors: false, 96 | avoidQuotes: true, 97 | }], 98 | 99 | # suggest using arrow functions as callbacks 100 | 'prefer-arrow-callback': ['error', { 101 | allowNamedFunctions: false, 102 | allowUnboundThis: true, 103 | }], 104 | 105 | # suggest using of const declaration for variables that are never modified after declared 106 | 'prefer-const': ['error', { 107 | destructuring: 'any', 108 | ignoreReadBeforeAssign: true, 109 | }], 110 | 111 | # disallow parseInt() in favor of binary, octal, and hexadecimal literals 112 | # http://eslint.org/docs/rules/prefer-numeric-literals 113 | 'prefer-numeric-literals': 'error', 114 | 115 | # use rest parameters instead of arguments 116 | # http://eslint.org/docs/rules/prefer-rest-params 117 | 'prefer-rest-params': 'error', 118 | 119 | # suggest using the spread operator instead of .apply() 120 | # http://eslint.org/docs/rules/prefer-spread 121 | 'prefer-spread': 'error', 122 | 123 | # suggest using template literals instead of string concatenation 124 | # http://eslint.org/docs/rules/prefer-template 125 | 'prefer-template': 'error', 126 | 127 | # disallow generator functions that do not have yield 128 | # http://eslint.org/docs/rules/require-yield 129 | 'require-yield': 'error', 130 | 131 | # enforce spacing between object rest-spread 132 | # http://eslint.org/docs/rules/rest-spread-spacing 133 | 'rest-spread-spacing': ['error', 'never'], 134 | 135 | # import sorting 136 | # http://eslint.org/docs/rules/sort-imports 137 | 'sort-imports': ['off', { 138 | ignoreCase: false, 139 | ignoreMemberSort: false, 140 | memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], 141 | }], 142 | 143 | # require a Symbol description 144 | # http://eslint.org/docs/rules/symbol-description 145 | 'symbol-description': 'error', 146 | 147 | # enforce usage of spacing in template strings 148 | # http://eslint.org/docs/rules/template-curly-spacing 149 | 'template-curly-spacing': 'error', 150 | 151 | # enforce spacing around the * in yield* expressions 152 | # http://eslint.org/docs/rules/yield-star-spacing 153 | 'yield-star-spacing': ['error', 'after'], 154 | 155 | # Enforce the consistent use of single quotes 156 | # https://eslint.org/docs/latest/rules/quotes 157 | quotes: [ error, single, { avoidEscape: true } ] 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /examples/overlay.js: -------------------------------------------------------------------------------- 1 | /* global $ */ 2 | import OLCesium from 'olcs'; 3 | import olMap from 'ol/Map.js'; 4 | import olSourceOSM from 'ol/source/OSM.js'; 5 | import olLayerTile from 'ol/layer/Tile.js'; 6 | import {transform, get as getProjection} from 'ol/proj.js'; 7 | import olView from 'ol/View.js'; 8 | import {defaults as olControlDefaults} from 'ol/control.js'; 9 | import olOverlay from 'ol/Overlay.js'; 10 | import {toStringHDMS} from 'ol/coordinate.js'; 11 | 12 | const source = new olSourceOSM(); 13 | 14 | const ol2d = new olMap({ 15 | layers: [ 16 | new olLayerTile({ 17 | source 18 | }) 19 | ], 20 | controls: olControlDefaults({ 21 | attributionOptions: { 22 | collapsible: false 23 | } 24 | }), 25 | target: 'map', 26 | view: new olView({ 27 | center: transform([-112.2, 36.06], 'EPSG:4326', 'EPSG:3857'), 28 | zoom: 11 29 | }) 30 | }); 31 | const ol3d = new OLCesium({ 32 | map: ol2d, 33 | target: 'mapCesium' 34 | }); 35 | const scene = ol3d.getCesiumScene(); 36 | 37 | class OverlayHandler { 38 | constructor(ol2d, ol3d, scene) { 39 | this.ol2d = ol2d; 40 | this.ol3d = ol3d; 41 | this.scene = scene; 42 | 43 | this.staticOverlay = new olOverlay({ 44 | element: document.getElementById('popup') 45 | }); 46 | 47 | this.staticBootstrapPopup = new olOverlay({ 48 | element: document.getElementById('popup-bootstrap') 49 | }); 50 | this.ol2d.addOverlay(this.staticOverlay); 51 | this.ol2d.addOverlay(this.staticBootstrapPopup); 52 | 53 | this.options = { 54 | boostrap: false, 55 | add: true 56 | }; 57 | 58 | this.ol2d.on('click', this.onClickHandlerOL.bind(this)); 59 | const eventHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); 60 | eventHandler.setInputAction(this.onClickHandlerCS.bind(this), Cesium.ScreenSpaceEventType['LEFT_CLICK']); 61 | 62 | const clickForm = document.getElementById('click-action-form'); 63 | clickForm.onchange = () => { 64 | const checked = document.querySelector('input[name="click-action"]:checked')?.value; 65 | this.options.add = checked === 'add'; 66 | }; 67 | 68 | const typeForm = document.getElementById('overlay-type-form'); 69 | typeForm.onchange = (event) => { 70 | const checked = document.querySelector('input[name="overlay-type"]:checked')?.value; 71 | this.options.boostrap = checked === 'popover'; 72 | }; 73 | } 74 | 75 | onClickHandlerOL(event) { 76 | const coordinates = event.coordinate; 77 | const hdms = toStringHDMS( 78 | transform(coordinates, 'EPSG:3857', 'EPSG:4326') 79 | ); 80 | const overlay = this.getOverlay(); 81 | overlay.setPosition(coordinates); 82 | this.setOverlayContent(overlay, hdms); 83 | } 84 | 85 | onClickHandlerCS(event) { 86 | if (event.position.x === 0 && event.position.y === 0) { 87 | return; 88 | } 89 | 90 | const ray = this.scene.camera.getPickRay(event.position); 91 | const cartesian = this.scene.globe.pick(ray, scene); 92 | if (!cartesian) { 93 | return; 94 | } 95 | const cartographic = scene.globe.ellipsoid.cartesianToCartographic(cartesian); 96 | let coords = [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]; 97 | 98 | const height = scene.globe.getHeight(cartographic); 99 | if (height) { 100 | coords = coords.concat([height]); 101 | } 102 | 103 | const transformedCoords = transform(coords, getProjection('EPSG:4326'), 'EPSG:3857'); 104 | const hdms = toStringHDMS(coords); 105 | const overlay = this.getOverlay(); 106 | overlay.setPosition(transformedCoords); 107 | this.setOverlayContent(overlay, hdms); 108 | } 109 | 110 | getOverlay() { 111 | if (this.options.add) { 112 | return this.addOverlay(); 113 | } 114 | 115 | if (this.options.boostrap) { 116 | return this.staticBootstrapPopup; 117 | } 118 | return this.staticOverlay; 119 | } 120 | 121 | setOverlayContent(overlay, hdms) { 122 | const element = overlay.getElement(); 123 | if (this.options.boostrap) { 124 | const div = document.createElement('div'); 125 | div.onclick = this.onCloseClick.bind(this, overlay, this.options.add); 126 | div.innerHTML = `

The location you clicked was:

${hdms}`; 127 | $(element).popover('destroy'); 128 | $(element).popover({ 129 | 'placement': 'top', 130 | 'animation': false, 131 | 'html': true, 132 | 'content': div 133 | }); 134 | $(element).popover('show'); 135 | } else { 136 | element.childNodes.forEach((child) => { 137 | if (child.id === 'popup-content') { 138 | child.innerHTML = `

The location you clicked was:

${hdms}`; 139 | } else if (child.id === 'popup-closer') { 140 | child.onclick = this.onCloseClick.bind(this, overlay, this.options.add); 141 | } 142 | }); 143 | } 144 | } 145 | 146 | onCloseClick(overlay, add) { 147 | if (add) { 148 | this.ol2d.removeOverlay(overlay); 149 | } else { 150 | overlay.setPosition(undefined); 151 | } 152 | } 153 | 154 | addOverlay() { 155 | let element; 156 | if (this.options.boostrap) { 157 | element = document.getElementById('popup-bootstrap').cloneNode(true); 158 | } else { 159 | element = document.getElementById('popup').cloneNode(true); 160 | } 161 | const overlay = new olOverlay({element}); 162 | this.ol2d.addOverlay(overlay); 163 | return overlay; 164 | } 165 | } 166 | 167 | new OverlayHandler(ol2d, ol3d, scene); 168 | 169 | document.getElementById('enable').addEventListener('click', () => ol3d.setEnabled(!ol3d.getEnabled())); 170 | 171 | //##REMOVE## Keep this tag, split code here for code sandbox 172 | 173 | import {initCodeSandbox} from './_code-sandbox.js'; 174 | initCodeSandbox('rawjs/overlay.js'); 175 | -------------------------------------------------------------------------------- /examples/_code-sandbox.js: -------------------------------------------------------------------------------- 1 | const lzScript = document.createElement('script'); 2 | lzScript.src = 'https://cdn.jsdelivr.net/npm/lz-string@1.4.4/libs/lz-string.min.js'; 3 | document.head.appendChild(lzScript); 4 | 5 | function compress(json) { 6 | return window.LZString.compressToBase64(JSON.stringify(json)) 7 | .replace(/\+/g, '-') 8 | .replace(/\//g, '_') 9 | .replace(/=+$/, ''); 10 | } 11 | 12 | export async function initCodeSandbox(indexJsPath, ...filesPathes) { 13 | const response = await fetch(indexJsPath); 14 | const txtData = await response.text(); 15 | const indexJsContent = txtData.split('//##REMOVE##')[0]; 16 | const additionalJsFiles = {}; 17 | const resourcesFiles = filesPathes 18 | .filter(path => path.indexOf('data/') === 0) 19 | // eslint-disable-next-line arrow-body-style 20 | .map(path => ({ 21 | [path]: { 22 | 'isBinary': true, 23 | content: `https://openlayers.org/ol-cesium/examples/${path}` 24 | } 25 | })); 26 | const jsFiles = filesPathes.filter(path => !path.startsWith('data/')); 27 | 28 | for (const filePath of jsFiles) { 29 | const responseFile = await fetch(filePath); 30 | const txtDataFile = await responseFile.text(); 31 | 32 | additionalJsFiles[filePath.replace('./', '').replace('rawjs', '')] = {content: txtDataFile}; 33 | } 34 | 35 | initCodeSandboxButton({indexJsContent, additionalJsFiles, resourcesFiles}); 36 | } 37 | 38 | function initCodeSandboxButton(options) { 39 | const {indexJsContent, additionalJsFiles, resourcesFiles} = options; 40 | 41 | let indexHtmlContent = ''; 42 | const button = document.getElementById('sandbox-button'); 43 | const form = document.querySelector('#sandbox-form'); 44 | 45 | if (!button || !form) { 46 | return; 47 | } 48 | 49 | const divExampleCodeSource = document.createElement('div'); 50 | divExampleCodeSource.innerHTML = document.getElementById('example-html-source').innerHTML; 51 | divExampleCodeSource.querySelectorAll('.clear-map-sandbox').forEach(map => map.innerHTML = ''); 52 | indexHtmlContent = divExampleCodeSource.innerHTML; 53 | 54 | const indexHtml = ` 55 | 56 | 57 | 58 | Ol-Cesium example in sandbox 59 | 60 | 61 | 62 | 63 | 117 | 118 | 119 | ${indexHtmlContent} 120 | 121 | 122 | 123 | 124 | `; 125 | const parameters = { 126 | template: 'parcel', 127 | files: { 128 | 'package.json': { 129 | content: { 130 | 'source': 'index.html', 131 | 'main': 'index.html', 132 | 'scripts': { 133 | 'build': 'vite build', 134 | 'start': 'npm run build && serve dist', 135 | }, 136 | 'devDependencies': { 137 | 'serve': '14.2.3', 138 | 'vite': '^3.2.3', 139 | '@babel/core': '^7.24.4', 140 | '@babel/plugin-proposal-class-properties': '^7.18.6' 141 | }, 142 | 'dependencies': { 143 | 'olcs': '2.20.0', 144 | 'proj4': '2.11.0', 145 | 'cesium': '1.122.0', 146 | 'ol': '10.1.0' 147 | } 148 | }, 149 | }, 150 | '.babelrc': { 151 | content: '{ "plugins": ["@babel/plugin-proposal-class-properties"] }' 152 | }, 153 | 'index.js': { 154 | content: indexJsContent, 155 | }, 156 | 'index.html': { 157 | content: indexHtml 158 | }, 159 | ...resourcesFiles.reduce((acc, curr) => { 160 | const key = Object.keys(curr)[0]; // Récupérer la clé de l'objet 161 | acc[key] = curr[key]; // Ajouter la propriété à l'objet accumulé 162 | return acc; 163 | }, {}), 164 | ...additionalJsFiles 165 | } 166 | }; 167 | 168 | button.onclick = function(event) { 169 | event.preventDefault(); 170 | form.parameters.value = compress(parameters); 171 | form.submit(); 172 | }; 173 | } 174 | 175 | // dans npm prepare : extraire les versions à utiliser et faire un fichier json avec ces versions 176 | // Ce fichier json doit etre lu, faire un fetch de ce fichier 177 | // ce fichier ne peut pas etre commit 178 | // à rajouter dans gitinore 179 | -------------------------------------------------------------------------------- /src/olcs/VectorSynchronizer.ts: -------------------------------------------------------------------------------- 1 | import olSourceVector, {type VectorSourceEvent} from 'ol/source/Vector.js'; 2 | import olLayerLayer from 'ol/layer/Layer.js'; 3 | import olSourceCluster from 'ol/source/Cluster.js'; 4 | import {getUid} from './util.js'; 5 | import olLayerVector from 'ol/layer/Vector.js'; 6 | import olLayerVectorTile from 'ol/layer/VectorTile.js'; 7 | import olcsAbstractSynchronizer from './AbstractSynchronizer.js'; 8 | import olcsFeatureConverter from './FeatureConverter.js'; 9 | import type VectorLayerCounterpart from './core/VectorLayerCounterpart.js'; 10 | // eslint-disable-next-line no-duplicate-imports 11 | import { 12 | type OlFeatureToCesiumContext, 13 | type PrimitiveCollectionCounterpart 14 | } from './core/VectorLayerCounterpart.js'; 15 | import type Map from 'ol/Map.js'; 16 | import {type LayerWithParents} from './core.js'; 17 | import type Feature from 'ol/Feature.js'; 18 | import type BaseLayer from 'ol/layer/Base.js'; 19 | import type {PrimitiveCollection, Scene} from 'cesium'; 20 | 21 | export default class VectorSynchronizer extends olcsAbstractSynchronizer { 22 | protected converter: olcsFeatureConverter; 23 | private csAllPrimitives_: PrimitiveCollection; 24 | /** 25 | * Unidirectionally synchronize OpenLayers vector layers to Cesium. 26 | */ 27 | constructor(map: Map, scene: Scene, opt_converter?: olcsFeatureConverter) { 28 | super(map, scene); 29 | 30 | this.converter = opt_converter || new olcsFeatureConverter(scene); 31 | this.csAllPrimitives_ = new Cesium.PrimitiveCollection(); 32 | scene.primitives.add(this.csAllPrimitives_); 33 | this.csAllPrimitives_.destroyPrimitives = false; 34 | } 35 | 36 | addCesiumObject(counterpart: VectorLayerCounterpart) { 37 | console.assert(counterpart); 38 | const collection = counterpart.getRootPrimitive(); 39 | collection.counterpart = counterpart; 40 | this.csAllPrimitives_.add(counterpart.getRootPrimitive()); 41 | } 42 | 43 | destroyCesiumObject(object: VectorLayerCounterpart) { 44 | object.getRootPrimitive().destroy(); 45 | } 46 | 47 | removeSingleCesiumObject(object: VectorLayerCounterpart, destroy: boolean) { 48 | object.destroy(); 49 | this.csAllPrimitives_.destroyPrimitives = destroy; 50 | this.csAllPrimitives_.remove(object.getRootPrimitive()); 51 | this.csAllPrimitives_.destroyPrimitives = false; 52 | } 53 | 54 | removeAllCesiumObjects(destroy: boolean) { 55 | this.csAllPrimitives_.destroyPrimitives = destroy; 56 | if (destroy) { 57 | for (let i = 0; i < this.csAllPrimitives_.length; ++i) { 58 | this.csAllPrimitives_.get(i)['counterpart'].destroy(); 59 | } 60 | } 61 | this.csAllPrimitives_.removeAll(); 62 | this.csAllPrimitives_.destroyPrimitives = false; 63 | } 64 | 65 | /** 66 | * Synchronizes the layer visibility properties 67 | * to the given Cesium Primitive. 68 | */ 69 | updateLayerVisibility(olLayerWithParents: LayerWithParents, csPrimitive: PrimitiveCollection) { 70 | let visible = true; 71 | [olLayerWithParents.layer].concat(olLayerWithParents.parents).forEach((olLayer) => { 72 | const layerVisible = olLayer.getVisible(); 73 | if (layerVisible !== undefined) { 74 | visible = visible && layerVisible; 75 | } else { 76 | visible = false; 77 | } 78 | }); 79 | csPrimitive.show = visible; 80 | } 81 | 82 | createSingleLayerCounterparts(olLayerWithParents: LayerWithParents): VectorLayerCounterpart[] { 83 | const olLayer: BaseLayer = olLayerWithParents.layer; 84 | if (!(olLayer instanceof olLayerVector) || olLayer instanceof olLayerVectorTile) { 85 | return null; 86 | } 87 | console.assert(olLayer instanceof olLayerLayer); 88 | 89 | let source = olLayer.getSource(); 90 | if (source instanceof olSourceCluster) { 91 | source = source.getSource(); 92 | } 93 | 94 | if (!source) { 95 | return null; 96 | } 97 | 98 | console.assert(source instanceof olSourceVector); 99 | console.assert(this.view); 100 | 101 | const view = this.view; 102 | const featurePrimitiveMap: Record = {}; 103 | const counterpart: VectorLayerCounterpart = this.converter.olVectorLayerToCesium(olLayer, view, featurePrimitiveMap); 104 | const csPrimitives = counterpart.getRootPrimitive(); 105 | const olListenKeys = counterpart.olListenKeys; 106 | 107 | [olLayerWithParents.layer].concat(olLayerWithParents.parents).forEach((olLayerItem) => { 108 | olListenKeys.push(olLayerItem.on('change:visible', () => { 109 | this.updateLayerVisibility(olLayerWithParents, csPrimitives); 110 | })); 111 | }); 112 | this.updateLayerVisibility(olLayerWithParents, csPrimitives); 113 | 114 | const onAddFeature = (feature: Feature) => { 115 | const context = counterpart.context; 116 | const prim: PrimitiveCollection = this.converter.convert(olLayer, view, feature, context); 117 | if (prim) { 118 | featurePrimitiveMap[getUid(feature)] = prim; 119 | csPrimitives.add(prim); 120 | } 121 | }; 122 | 123 | const onRemoveFeature = (feature: Feature) => { 124 | const id = getUid(feature); 125 | const context: OlFeatureToCesiumContext = counterpart.context; 126 | const bbs = context.featureToCesiumMap[id]; 127 | if (bbs) { 128 | delete context.featureToCesiumMap[id]; 129 | bbs.forEach((bb) => { 130 | if (bb instanceof Cesium.Billboard) { 131 | context.billboards.remove(bb); 132 | } 133 | }); 134 | } 135 | const csPrimitive = featurePrimitiveMap[id]; 136 | delete featurePrimitiveMap[id]; 137 | if (csPrimitive) { 138 | csPrimitives.remove(csPrimitive); 139 | } 140 | }; 141 | 142 | olListenKeys.push(source.on('addfeature', (e: VectorSourceEvent) => { 143 | console.assert(e.feature); 144 | onAddFeature(e.feature); 145 | })); 146 | 147 | olListenKeys.push(source.on('removefeature', (e: VectorSourceEvent) => { 148 | console.assert(e.feature); 149 | onRemoveFeature(e.feature); 150 | })); 151 | 152 | olListenKeys.push(source.on('changefeature', (e: VectorSourceEvent) => { 153 | const feature = e.feature; 154 | console.assert(feature); 155 | onRemoveFeature(feature); 156 | onAddFeature(feature); 157 | })); 158 | 159 | return counterpart ? [counterpart] : null; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /examples/data/arrow5.dae: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blender User 6 | Blender 2.79.0 commit date:2018-01-22, commit time:14:48, hash:61335d853d1 7 | 8 | 2018-02-26T13:54:44 9 | 2018-02-26T13:54:44 10 | 11 | Z_UP 12 | 13 | 14 | 15 | 16 | 17 | 18 | 0 19 | 1.777778 20 | 1 21 | 1000 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 0 0 0 1 42 | 43 | 44 | 0 0 0 1 45 | 46 | 47 | 1 1 1 1 48 | 49 | 50 | 0.5 0.5 0.5 1 51 | 52 | 53 | 50 54 | 55 | 56 | 1 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 268.298 4015.004 0 0 2112.5 0 -689.456 2319.986 0 0 0 0 440.625 0 0 536.596 1951.016 0 1226.052 1743.53 0 0 2112.5 572.3125 0 0 0 0 0 572.3125 0 2112.5 0 0 2112.5 0 -689.456 2319.986 572.3125 -689.456 2319.986 0 0 2112.5 572.3125 268.298 4015.004 572.3125 -689.456 2319.986 0 -689.456 2319.986 572.3125 268.298 4015.004 0 268.298 4015.004 0 1226.052 1743.53 572.3125 1226.052 1743.53 0 268.298 4015.004 572.3125 1226.052 1743.53 0 536.596 1951.016 572.3125 536.596 1951.016 0 1226.052 1743.53 572.3125 440.625 0 0 536.596 1951.016 572.3125 440.625 0 572.3125 536.596 1951.016 0 440.625 0 572.3125 0 0 0 440.625 0 0 0 0 572.3125 440.625 0 572.3125 0 2112.5 572.3125 0 0 572.3125 268.298 4015.004 572.3125 536.596 1951.016 572.3125 1226.052 1743.53 572.3125 -689.456 2319.986 572.3125 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 -1 -1 0 0 -0.2881751 -0.9575778 0 -0.2881751 -0.9575778 0 -0.8706287 0.4919406 0 0.9214403 0.38852 0 0.9214403 0.3885201 0 -0.288175 -0.9575778 0 -0.2881749 -0.9575778 0 0.9987924 -0.04913085 0 0.9987924 -0.04913079 0 0 -1 0 0 0 1 3.46483e-7 0 1 0 0 1 0 0 1 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |

0 0 1 0 2 0 1 1 0 1 3 1 3 2 0 2 4 2 4 3 0 3 5 3 5 4 0 4 6 4 7 5 8 5 9 5 8 5 7 5 10 5 11 6 12 6 13 6 12 7 11 7 14 7 15 8 16 8 17 8 16 8 15 8 18 8 19 9 20 9 21 9 20 10 19 10 22 10 23 11 24 11 25 11 24 12 23 12 26 12 27 13 28 13 29 13 28 14 27 14 30 14 31 15 32 15 33 15 32 15 31 15 34 15 35 16 36 16 37 16 36 16 35 16 38 16 38 17 35 17 39 17 38 18 39 18 40 18 38 19 41 19 36 19

98 |
99 |
100 |
101 |
102 | 103 | 104 | 105 | 106 | 3.79835e-4 0 0 0 0 3.79835e-4 0 0 0 0 3.79835e-4 0 0 0 0 1 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 0.8477411 -0.3321087 0.4135681 1813.019 0.5304099 0.5308011 -0.6609957 -1927.848 6.01469e-9 0.779714 0.6261358 3011.32 0 0 0 1 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
-------------------------------------------------------------------------------- /src/olcs/RasterSynchronizer.ts: -------------------------------------------------------------------------------- 1 | import type Map from 'ol/Map.js'; 2 | import {getUid} from './util.js'; 3 | import olcsAbstractSynchronizer from './AbstractSynchronizer.js'; 4 | import {type LayerWithParents, tileLayerToImageryLayer, updateCesiumLayerProperties} from './core.js'; 5 | import type {Scene, ImageryLayer, ImageryLayerCollection} from 'cesium'; 6 | import type BaseLayer from 'ol/layer/Base.js'; 7 | import type Projection from 'ol/proj/Projection.js'; 8 | import BaseVectorLayer from 'ol/layer/BaseVector.js'; 9 | import LayerGroup from 'ol/layer/Group.js'; 10 | 11 | export default class RasterSynchronizer extends olcsAbstractSynchronizer { 12 | private cesiumLayers_: ImageryLayerCollection; 13 | private ourLayers_: ImageryLayerCollection; 14 | /** 15 | * This object takes care of one-directional synchronization of 16 | * Openlayers raster layers to the given Cesium globe. 17 | */ 18 | constructor(map: Map, scene: Scene) { 19 | super(map, scene); 20 | 21 | this.cesiumLayers_ = scene.imageryLayers; 22 | this.ourLayers_ = new Cesium.ImageryLayerCollection(); 23 | } 24 | 25 | addCesiumObject(object: ImageryLayer): void { 26 | this.cesiumLayers_.add(object); 27 | this.ourLayers_.add(object); 28 | } 29 | 30 | destroyCesiumObject(object: ImageryLayer): void { 31 | object.destroy(); 32 | } 33 | 34 | removeSingleCesiumObject(object: ImageryLayer, destroy: boolean): void { 35 | this.cesiumLayers_.remove(object, destroy); 36 | this.ourLayers_.remove(object, false); 37 | } 38 | 39 | removeAllCesiumObjects(destroy: boolean): void { 40 | for (let i = 0; i < this.ourLayers_.length; ++i) { 41 | this.cesiumLayers_.remove(this.ourLayers_.get(i), destroy); 42 | } 43 | this.ourLayers_.removeAll(false); 44 | } 45 | 46 | /** 47 | * Creates an array of Cesium.ImageryLayer. 48 | * May be overriden by child classes to implement custom behavior. 49 | * The default implementation handles tiled imageries in EPSG:4326 or 50 | * EPSG:3859. 51 | */ 52 | protected convertLayerToCesiumImageries(olLayer: BaseLayer, viewProj: Projection): ImageryLayer[] { 53 | const result = tileLayerToImageryLayer(this.map, olLayer, viewProj); 54 | return result ? [result] : null; 55 | } 56 | 57 | createSingleLayerCounterparts(olLayerWithParents: LayerWithParents): ImageryLayer[] { 58 | const olLayer = olLayerWithParents.layer; 59 | const uid = getUid(olLayer).toString(); 60 | const viewProj = this.view.getProjection(); 61 | console.assert(viewProj); 62 | const cesiumObjects = this.convertLayerToCesiumImageries(olLayer, viewProj); 63 | if (cesiumObjects) { 64 | const listenKeyArray = []; 65 | [olLayerWithParents.layer].concat(olLayerWithParents.parents).forEach((olLayerItem) => { 66 | listenKeyArray.push(olLayerItem.on(['change:opacity', 'change:visible'], () => { 67 | // the compiler does not seem to be able to infer this 68 | console.assert(cesiumObjects); 69 | for (let i = 0; i < cesiumObjects.length; ++i) { 70 | updateCesiumLayerProperties(olLayerWithParents, cesiumObjects[i]); 71 | } 72 | })); 73 | }); 74 | 75 | if (olLayer instanceof BaseVectorLayer) { 76 | let previousStyleFunction = olLayer.getStyleFunction(); 77 | // there is no convenient way to detect a style function change in OL 78 | listenKeyArray.push(olLayer.on('change', () => { 79 | const currentStyleFunction = olLayer.getStyleFunction(); 80 | if (previousStyleFunction === currentStyleFunction) { 81 | return; 82 | } 83 | previousStyleFunction = currentStyleFunction; 84 | for (let i = 0; i < cesiumObjects.length; ++i) { 85 | const csObj = cesiumObjects[i]; 86 | // clear cache and set new style 87 | // @ts-ignore TS2341 88 | if (csObj._imageryCache) { 89 | // @ts-ignore TS2341 90 | csObj._imageryCache = {}; 91 | } 92 | 93 | const ip = csObj.imageryProvider; 94 | if (ip) { 95 | // @ts-ignore TS2341 96 | ip.tileCache?.clear(); 97 | // @ts-ignore TS2341 98 | ip.styleFunction_ = currentStyleFunction; 99 | } 100 | } 101 | this.scene.requestRender(); 102 | })); 103 | } 104 | 105 | for (let i = 0; i < cesiumObjects.length; ++i) { 106 | updateCesiumLayerProperties(olLayerWithParents, cesiumObjects[i]); 107 | } 108 | 109 | // there is no way to modify Cesium layer extent, 110 | // we have to recreate when OpenLayers layer extent changes: 111 | listenKeyArray.push(olLayer.on('change:extent', (e) => { 112 | for (let i = 0; i < cesiumObjects.length; ++i) { 113 | this.cesiumLayers_.remove(cesiumObjects[i], true); // destroy 114 | this.ourLayers_.remove(cesiumObjects[i], false); 115 | } 116 | delete this.layerMap[getUid(olLayer)]; // invalidate the map entry 117 | this.synchronize(); 118 | })); 119 | 120 | listenKeyArray.push(olLayer.on('change', (e) => { 121 | // when the source changes, re-add the layer to force update 122 | for (let i = 0; i < cesiumObjects.length; ++i) { 123 | const position = this.cesiumLayers_.indexOf(cesiumObjects[i]); 124 | if (position >= 0) { 125 | this.cesiumLayers_.remove(cesiumObjects[i], false); 126 | this.cesiumLayers_.add(cesiumObjects[i], position); 127 | } 128 | } 129 | })); 130 | 131 | this.olLayerListenKeys[uid].push(...listenKeyArray); 132 | } 133 | 134 | return Array.isArray(cesiumObjects) ? cesiumObjects : null; 135 | } 136 | 137 | /** 138 | * Order counterparts using the same algorithm as the Openlayers renderer: 139 | * z-index then original sequence order. 140 | * @override 141 | * @protected 142 | */ 143 | orderLayers() { 144 | const layers = []; 145 | const zIndices: Record = {}; 146 | const queue: Array = [this.mapLayerGroup]; 147 | 148 | while (queue.length > 0) { 149 | const olLayer = queue.splice(0, 1)[0]; 150 | layers.push(olLayer); 151 | zIndices[getUid(olLayer)] = olLayer.getZIndex() || 0; 152 | 153 | if (olLayer instanceof LayerGroup) { 154 | const sublayers = olLayer.getLayers(); 155 | if (sublayers) { 156 | // Prepend queue with sublayers in order 157 | queue.unshift(...sublayers.getArray()); 158 | } 159 | } 160 | } 161 | 162 | // We assume sort is stable (which has been in the spec since a long time already). 163 | // See https://caniuse.com/mdn-javascript_builtins_array_sort_stable 164 | layers.sort((layer1, layer2) => 165 | zIndices[getUid(layer1)] - zIndices[getUid(layer2)] 166 | ); 167 | 168 | layers.forEach((olLayer) => { 169 | const olLayerId = getUid(olLayer).toString(); 170 | const cesiumObjects = this.layerMap[olLayerId]; 171 | if (cesiumObjects) { 172 | cesiumObjects.forEach((cesiumObject) => { this.raiseToTop(cesiumObject); }); 173 | } 174 | }); 175 | } 176 | 177 | raiseToTop(counterpart: ImageryLayer) { 178 | this.cesiumLayers_.raiseToTop(counterpart); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /examples/ol.css: -------------------------------------------------------------------------------- 1 | :root, 2 | :host { 3 | --ol-background-color: white; 4 | --ol-accent-background-color: #F5F5F5; 5 | --ol-subtle-background-color: rgba(128, 128, 128, 0.25); 6 | --ol-partial-background-color: rgba(255, 255, 255, 0.75); 7 | --ol-foreground-color: #333333; 8 | --ol-subtle-foreground-color: #666666; 9 | --ol-brand-color: #00AAFF; 10 | } 11 | 12 | .ol-box { 13 | box-sizing: border-box; 14 | border-radius: 2px; 15 | border: 1.5px solid var(--ol-background-color); 16 | background-color: var(--ol-partial-background-color); 17 | } 18 | 19 | .ol-mouse-position { 20 | top: 8px; 21 | right: 8px; 22 | position: absolute; 23 | } 24 | 25 | .ol-scale-line { 26 | background: var(--ol-partial-background-color); 27 | border-radius: 4px; 28 | bottom: 8px; 29 | left: 8px; 30 | padding: 2px; 31 | position: absolute; 32 | } 33 | 34 | .ol-scale-line-inner { 35 | border: 1px solid var(--ol-subtle-foreground-color); 36 | border-top: none; 37 | color: var(--ol-foreground-color); 38 | font-size: 10px; 39 | text-align: center; 40 | margin: 1px; 41 | will-change: contents, width; 42 | transition: all 0.25s; 43 | } 44 | 45 | .ol-scale-bar { 46 | position: absolute; 47 | bottom: 8px; 48 | left: 8px; 49 | } 50 | 51 | .ol-scale-bar-inner { 52 | display: flex; 53 | } 54 | 55 | .ol-scale-step-marker { 56 | width: 1px; 57 | height: 15px; 58 | background-color: var(--ol-foreground-color); 59 | float: right; 60 | z-index: 10; 61 | } 62 | 63 | .ol-scale-step-text { 64 | position: absolute; 65 | bottom: -5px; 66 | font-size: 10px; 67 | z-index: 11; 68 | color: var(--ol-foreground-color); 69 | text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color); 70 | } 71 | 72 | .ol-scale-text { 73 | position: absolute; 74 | font-size: 12px; 75 | text-align: center; 76 | bottom: 25px; 77 | color: var(--ol-foreground-color); 78 | text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color); 79 | } 80 | 81 | .ol-scale-singlebar { 82 | position: relative; 83 | height: 10px; 84 | z-index: 9; 85 | box-sizing: border-box; 86 | border: 1px solid var(--ol-foreground-color); 87 | } 88 | 89 | .ol-scale-singlebar-even { 90 | background-color: var(--ol-subtle-foreground-color); 91 | } 92 | 93 | .ol-scale-singlebar-odd { 94 | background-color: var(--ol-background-color); 95 | } 96 | 97 | .ol-unsupported { 98 | display: none; 99 | } 100 | 101 | .ol-viewport, 102 | .ol-unselectable { 103 | -webkit-touch-callout: none; 104 | -webkit-user-select: none; 105 | -moz-user-select: none; 106 | user-select: none; 107 | -webkit-tap-highlight-color: transparent; 108 | } 109 | 110 | .ol-viewport canvas { 111 | all: unset; 112 | overflow: hidden; 113 | } 114 | 115 | .ol-viewport { 116 | touch-action: pan-x pan-y; 117 | } 118 | 119 | .ol-selectable { 120 | -webkit-touch-callout: default; 121 | -webkit-user-select: text; 122 | -moz-user-select: text; 123 | user-select: text; 124 | } 125 | 126 | .ol-grabbing { 127 | cursor: -webkit-grabbing; 128 | cursor: -moz-grabbing; 129 | cursor: grabbing; 130 | } 131 | 132 | .ol-grab { 133 | cursor: move; 134 | cursor: -webkit-grab; 135 | cursor: -moz-grab; 136 | cursor: grab; 137 | } 138 | 139 | .ol-control { 140 | position: absolute; 141 | background-color: var(--ol-subtle-background-color); 142 | border-radius: 4px; 143 | } 144 | 145 | .ol-zoom { 146 | top: .5em; 147 | left: .5em; 148 | } 149 | 150 | .ol-rotate { 151 | top: .5em; 152 | right: .5em; 153 | transition: opacity .25s linear, visibility 0s linear; 154 | } 155 | 156 | .ol-rotate.ol-hidden { 157 | opacity: 0; 158 | visibility: hidden; 159 | transition: opacity .25s linear, visibility 0s linear .25s; 160 | } 161 | 162 | .ol-zoom-extent { 163 | top: 4.643em; 164 | left: .5em; 165 | } 166 | 167 | .ol-full-screen { 168 | right: .5em; 169 | top: .5em; 170 | } 171 | 172 | .ol-control button { 173 | display: block; 174 | margin: 1px; 175 | padding: 0; 176 | color: var(--ol-subtle-foreground-color); 177 | font-weight: bold; 178 | text-decoration: none; 179 | font-size: inherit; 180 | text-align: center; 181 | height: 1.375em; 182 | width: 1.375em; 183 | line-height: .4em; 184 | background-color: var(--ol-background-color); 185 | border: none; 186 | border-radius: 2px; 187 | } 188 | 189 | .ol-control button::-moz-focus-inner { 190 | border: none; 191 | padding: 0; 192 | } 193 | 194 | .ol-zoom-extent button { 195 | line-height: 1.4em; 196 | } 197 | 198 | .ol-compass { 199 | display: block; 200 | font-weight: normal; 201 | will-change: transform; 202 | } 203 | 204 | .ol-touch .ol-control button { 205 | font-size: 1.5em; 206 | } 207 | 208 | .ol-touch .ol-zoom-extent { 209 | top: 5.5em; 210 | } 211 | 212 | .ol-control button:hover, 213 | .ol-control button:focus { 214 | text-decoration: none; 215 | outline: 1px solid var(--ol-subtle-foreground-color); 216 | color: var(--ol-foreground-color); 217 | } 218 | 219 | .ol-zoom .ol-zoom-in { 220 | border-radius: 2px 2px 0 0; 221 | } 222 | 223 | .ol-zoom .ol-zoom-out { 224 | border-radius: 0 0 2px 2px; 225 | } 226 | 227 | .ol-attribution { 228 | text-align: right; 229 | bottom: .5em; 230 | right: .5em; 231 | max-width: calc(100% - 1.3em); 232 | display: flex; 233 | flex-flow: row-reverse; 234 | align-items: center; 235 | } 236 | 237 | .ol-attribution a { 238 | color: var(--ol-subtle-foreground-color); 239 | text-decoration: none; 240 | } 241 | 242 | .ol-attribution ul { 243 | margin: 0; 244 | padding: 1px .5em; 245 | color: var(--ol-foreground-color); 246 | text-shadow: 0 0 2px var(--ol-background-color); 247 | font-size: 12px; 248 | } 249 | 250 | .ol-attribution li { 251 | display: inline; 252 | list-style: none; 253 | } 254 | 255 | .ol-attribution li:not(:last-child):after { 256 | content: " "; 257 | } 258 | 259 | .ol-attribution img { 260 | max-height: 2em; 261 | max-width: inherit; 262 | vertical-align: middle; 263 | } 264 | 265 | .ol-attribution button { 266 | flex-shrink: 0; 267 | } 268 | 269 | .ol-attribution.ol-collapsed ul { 270 | display: none; 271 | } 272 | 273 | .ol-attribution:not(.ol-collapsed) { 274 | background: var(--ol-partial-background-color); 275 | } 276 | 277 | .ol-attribution.ol-uncollapsible { 278 | bottom: 0; 279 | right: 0; 280 | border-radius: 4px 0 0; 281 | } 282 | 283 | .ol-attribution.ol-uncollapsible img { 284 | margin-top: -.2em; 285 | max-height: 1.6em; 286 | } 287 | 288 | .ol-attribution.ol-uncollapsible button { 289 | display: none; 290 | } 291 | 292 | .ol-zoomslider { 293 | top: 4.5em; 294 | left: .5em; 295 | height: 200px; 296 | } 297 | 298 | .ol-zoomslider button { 299 | position: relative; 300 | height: 10px; 301 | } 302 | 303 | .ol-touch .ol-zoomslider { 304 | top: 5.5em; 305 | } 306 | 307 | .ol-overviewmap { 308 | left: 0.5em; 309 | bottom: 0.5em; 310 | } 311 | 312 | .ol-overviewmap.ol-uncollapsible { 313 | bottom: 0; 314 | left: 0; 315 | border-radius: 0 4px 0 0; 316 | } 317 | 318 | .ol-overviewmap .ol-overviewmap-map, 319 | .ol-overviewmap button { 320 | display: block; 321 | } 322 | 323 | .ol-overviewmap .ol-overviewmap-map { 324 | border: 1px solid var(--ol-subtle-foreground-color); 325 | height: 150px; 326 | width: 150px; 327 | } 328 | 329 | .ol-overviewmap:not(.ol-collapsed) button { 330 | bottom: 0; 331 | left: 0; 332 | position: absolute; 333 | } 334 | 335 | .ol-overviewmap.ol-collapsed .ol-overviewmap-map, 336 | .ol-overviewmap.ol-uncollapsible button { 337 | display: none; 338 | } 339 | 340 | .ol-overviewmap:not(.ol-collapsed) { 341 | background: var(--ol-subtle-background-color); 342 | } 343 | 344 | .ol-overviewmap-box { 345 | border: 1.5px dotted var(--ol-subtle-foreground-color); 346 | } 347 | 348 | .ol-overviewmap .ol-overviewmap-box:hover { 349 | cursor: move; 350 | } 351 | --------------------------------------------------------------------------------