├── .eslintignore ├── .github └── workflows │ ├── deploy.yml │ ├── publish.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── DEVELOPING.md ├── LICENSE.md ├── README.md ├── config ├── jsdoc │ ├── api │ │ ├── conf.json │ │ ├── index.md │ │ ├── readme.md │ │ └── template │ │ │ ├── README.md │ │ │ ├── publish.js │ │ │ ├── static │ │ │ ├── scripts │ │ │ │ ├── linenumber.js │ │ │ │ ├── main.js │ │ │ │ └── prettify │ │ │ │ │ ├── Apache-License-2.0.txt │ │ │ │ │ ├── lang-css.js │ │ │ │ │ └── prettify.js │ │ │ ├── styles │ │ │ │ ├── carbon.css │ │ │ │ ├── jaguar.css │ │ │ │ ├── prettify-jsdoc.css │ │ │ │ └── prettify-tomorrow.css │ │ │ └── theme │ │ │ └── tmpl │ │ │ ├── container.tmpl │ │ │ ├── details.tmpl │ │ │ ├── example.tmpl │ │ │ ├── examples.tmpl │ │ │ ├── exceptions.tmpl │ │ │ ├── layout.tmpl │ │ │ ├── mainpage.tmpl │ │ │ ├── members.tmpl │ │ │ ├── method.tmpl │ │ │ ├── navigation.tmpl │ │ │ ├── observables.tmpl │ │ │ ├── params.tmpl │ │ │ ├── properties.tmpl │ │ │ ├── returns.tmpl │ │ │ ├── source.tmpl │ │ │ ├── stability.tmpl │ │ │ ├── tutorial.tmpl │ │ │ └── type.tmpl │ ├── info │ │ ├── conf.json │ │ └── publish.js │ ├── package.json │ └── plugins │ │ ├── api.cjs │ │ ├── default-export.cjs │ │ ├── define-plugin.cjs │ │ ├── events.cjs │ │ ├── inline-options.cjs │ │ ├── markdown.cjs │ │ └── virtual-plugin.cjs └── tsconfig-build.json ├── examples ├── .eslintrc ├── custom.html ├── custom.js ├── index.html ├── index.js ├── planetary-computer.html ├── planetary-computer.js ├── resources │ ├── Jugl.js │ └── common.js ├── stac-collection-pmtiles-raster.html ├── stac-collection-pmtiles-raster.js ├── stac-collection-pmtiles-vector.html ├── stac-collection-pmtiles-vector.js ├── stac-collection-with-items.html ├── stac-collection-with-items.js ├── stac-collection-wms.html ├── stac-collection-wms.js ├── stac-item-from-object.html ├── stac-item-from-object.js ├── stac-item-geojson.html ├── stac-item-geojson.js ├── stac-item-labels.html ├── stac-item-labels.js ├── stac-item-tileserver.html ├── stac-item-tileserver.js ├── stac-item.html ├── stac-item.js ├── stac-itemcollection.html ├── stac-itemcollection.js ├── templates │ ├── example.html │ └── readme.md └── webpack │ ├── config.mjs │ └── example-builder.js ├── package-lock.json ├── package.json ├── site ├── .gitignore ├── build.js ├── layouts │ └── default.hbs └── src │ ├── doc │ ├── faq.md │ ├── index.md │ ├── quickstart.md │ └── tutorials │ │ └── index.md │ ├── download │ └── index.hbs │ ├── favicon.ico │ ├── index.hbs │ └── theme │ ├── index.css │ └── site.css ├── src └── ol │ ├── events │ └── ErrorEvent.js │ ├── layer │ └── STAC.js │ ├── source │ └── type.js │ └── util.js ├── tasks ├── .eslintrc ├── build-website.sh ├── create-release.js ├── get-latest-release.js ├── newest-tag.js ├── prepare-package.js └── publish.sh ├── test ├── README.md └── browser │ ├── .eslintrc │ ├── karma.config.cjs │ ├── spec │ └── ol │ │ └── layer │ │ └── STAC.test.js │ └── test-extensions.js └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | examples/resources/ 2 | config/jsdoc/api/template/static/scripts/ -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Website 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*.*.*' 9 | 10 | concurrency: 11 | group: "deploy" 12 | 13 | jobs: 14 | deploy-branch: 15 | if: startsWith(github.ref, 'refs/heads/') 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: '20' 21 | - uses: actions/checkout@v4 22 | - name: Install dependencies 23 | run: npm ci 24 | - name: Build Website 25 | run: ./tasks/build-website.sh -l $(node tasks/get-latest-release.js) 26 | - uses: actions/checkout@v4 27 | with: 28 | ref: gh-pages 29 | clean: false 30 | - name: Commit to GitHub Pages 31 | run: | 32 | cp -r build/site/* . 33 | if [ -n "$(git status --porcelain)" ]; then 34 | git config user.name "$(git --no-pager log --format=format:'%an' -n 1)" 35 | git config user.email "$(git --no-pager log --format=format:'%ae' -n 1)" 36 | git add . 37 | git commit -m "Website updates" 38 | git push origin gh-pages 39 | fi 40 | deploy-tag: 41 | if: startsWith(github.ref, 'refs/tags/') 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/setup-node@v4 45 | with: 46 | node-version: '20' 47 | - uses: actions/checkout@v4 48 | - name: Install dependencies 49 | run: npm ci 50 | - name: Assert Latest Release 51 | run: node tasks/newest-tag.js --tag ${GITHUB_REF_NAME} 52 | - name: Build Website 53 | run: ./tasks/build-website.sh -l ${GITHUB_REF_NAME} -v ${GITHUB_REF_NAME} 54 | - uses: actions/checkout@v4 55 | with: 56 | ref: gh-pages 57 | clean: false 58 | - name: Commit to GitHub Pages 59 | run: | 60 | cp -r build/site/* . 61 | if [ -n "$(git status --porcelain)" ]; then 62 | git config user.name "$(git --no-pager log --format=format:'%an' -n 1)" 63 | git config user.email "$(git --no-pager log --format=format:'%ae' -n 1)" 64 | git add . 65 | git commit -m "Website updates" 66 | git push origin gh-pages 67 | fi 68 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | permissions: 9 | contents: read 10 | id-token: write 11 | 12 | jobs: 13 | publish-tag: 14 | if: startsWith(github.ref, 'refs/tags/') 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: '20' 21 | registry-url: 'https://registry.npmjs.org' 22 | - name: Install dependencies 23 | run: npm ci 24 | - name: Assert Latest Release 25 | run: node tasks/newest-tag.js --tag ${GITHUB_REF_NAME} 26 | - name: Publish 27 | run: | 28 | npm run build-package 29 | cd build/ol 30 | npm publish --provenance 31 | env: 32 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | release: 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: Install dependencies 17 | run: npm ci 18 | - name: Build Release Assets 19 | run: ./tasks/build-website.sh -l ${GITHUB_REF_NAME} -v ${GITHUB_REF_NAME} 20 | - name: Create Release 21 | run: node tasks/create-release.js --token ${{secrets.GITHUB_TOKEN}} --tag ${GITHUB_REF_NAME} --package build/${GITHUB_REF_NAME}-package.zip --site build/${GITHUB_REF_NAME}-site.zip 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | CI: true 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | pretest: 19 | name: Pre-Test 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | 25 | steps: 26 | - name: Clone Repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Set Node.js Version 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: '20' 33 | 34 | - name: Install Dependencies 35 | run: npm ci 36 | 37 | - name: Run Tests 38 | run: npm run pretest 39 | 40 | browser: 41 | name: Browser 42 | runs-on: ubuntu-latest 43 | 44 | strategy: 45 | fail-fast: false 46 | 47 | steps: 48 | - name: Clone Repository 49 | uses: actions/checkout@v4 50 | 51 | - name: Set Node.js Version 52 | uses: actions/setup-node@v4 53 | with: 54 | node-version: '20' 55 | 56 | - name: Install Dependencies 57 | run: npm ci 58 | 59 | - name: Run Tests 60 | run: npm run test-browser 61 | 62 | build: 63 | name: Build 64 | runs-on: ubuntu-latest 65 | 66 | strategy: 67 | fail-fast: false 68 | 69 | steps: 70 | - name: Clone Repository 71 | uses: actions/checkout@v4 72 | 73 | - name: Set Node.js Version 74 | uses: actions/setup-node@v4 75 | with: 76 | node-version: '20' 77 | 78 | - name: Install Dependencies 79 | run: npm ci 80 | 81 | - name: Build the Package 82 | run: npm run build-package 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /coverage/ 3 | /dist/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [1.0.0-rc.10] - 2025-03-06 11 | 12 | - `getStacObjectsForEvent`: Detection works correctly for polygons without a fill 13 | 14 | ## [1.0.0-rc.9] - 2025-02-14 15 | 16 | - Pass `useTileLayerAsFallback` and `buildTileUrlTemplate` options to the layer of childrens 17 | 18 | ## [1.0.0-rc.8] - 2025-02-03 19 | 20 | - `getExtent`: Compute extent correctly when children are provided but no parent 21 | - `buildTileUrlTemplate` supports async functions 22 | 23 | ## [1.0.0-rc.7] - 2025-01-29 24 | 25 | - Parameter to retrieve the features through `getStacObjectsForEvent` 26 | - Parameter to set the hit tolerance for `getStacObjectsForEvent` 27 | - Properly visualize (GeoJSON) points and multi-points 28 | - Don't import from barrel files, see 29 | 30 | ## [1.0.0-rc.6] - 2025-01-28 31 | 32 | - Fix adding children through `setChildren` 33 | 34 | ## [1.0.0-rc.5] - 2025-01-27 35 | 36 | - Fix `isEmpty` 37 | 38 | ## [1.0.0-rc.4] - 2025-01-27 39 | 40 | - Fix GeoJSON coordinates before projection 41 | - Ensure bounding boxes are valid 42 | - STAC Collection with Items example shows all items correctly 43 | - Support for visualizing Items that implement the label extension (vector only) 44 | 45 | ## [1.0.0-rc.3] - 2025-01-26 46 | 47 | - Support visualizing GeoJSON 48 | - Replaced event `assetsready` with `layersready` 49 | - Add methods `isEmpty` 50 | - Make events always fire the earliest after the contstructor has been executed 51 | 52 | ## [1.0.0-rc.2] - 2025-01-25 53 | 54 | - Fix setting the `stac` property for children 55 | - Allow to set specific options for children 56 | - Update stac-js to only return 2D bounding boxes 57 | - Better prioritize what is shown from user input or by default 58 | 59 | ## [1.0.0-rc.1] - 2025-01-25 60 | 61 | - Add `disableMigration` option 62 | - Only show preview image if a single bbox is given 63 | - Set `stac` property for the layer group 64 | - Don't add children multiple times on subsequent `setChildren` calls 65 | 66 | ## [1.0.0-beta.12] - 2025-01-24 67 | 68 | - STAC 1.1 support 69 | - Handling of default GeoTiff and previews improved 70 | - `displayPreview` and `displayOverview` have no influence on setting specific assets via `assets` 71 | - Fallback mechanism to tile servers improved 72 | - Doesn't filter previews with additional role `example` by default 73 | - Fix PMTiles CORS errors in examples 74 | 75 | ## [1.0.0-beta.11] - 2025-01-23 76 | 77 | - Allow to pass in children that are visualized as part of the Catalog/Collection 78 | - Added parameter to `getStacObjectsForEvent` to exclude a STAC entity 79 | - Default stroke width for children is 2 instead of 1 80 | - Added methods `getChildren` and `setChildren` 81 | - Added example that shows a collection with its children 82 | - Add `getAttirbutions` to get attribution from STAC entity 83 | 84 | ## [1.0.0-beta.10] - 2024-04-10 85 | 86 | - Correctly create WMS links if styles and/or layers are not provided 87 | 88 | ## [1.0.0-beta.9] - 2024-03-08 89 | 90 | - Allow a list of web map links to be shown through the `displayWebMapLink` option, 91 | which includes selecting links by id. 92 | - Require ol >= 7.5.2 (before: >= 7.0.0) 93 | 94 | ## [1.0.0-beta.8] - 2024-01-30 95 | 96 | - Fixes the broken 1.0.0-beta.7 release 97 | - Update stac-js dependency to 0.0.9 98 | 99 | ## [1.0.0-beta.7] - 2024-01-26 100 | 101 | - Added an option to hide footprints (geometry/bounding box) by default 102 | - Added support for image formats in WMS and WMTS 103 | - Expose `SourceType` as public API 104 | - Removed note about Firefox issues in the examples 105 | 106 | ## [1.0.0-beta.6] - 2023-08-28 107 | 108 | - OpenLayers is now a peer dependency 109 | - Add support for PMTiles (via Web Map Links extension) 110 | - New general purpose option `getSourceOptions(type, options, ref)` to customize source options. 111 | It also applies to all web-map-link source options now. 112 | It replaces: 113 | - `getGeoTIFFSourceOptions(options, ref)` 114 | - `getImageStaticSourceOptions(options, ref)` 115 | - `getXYZSourceOptions(options, ref)` 116 | - Added `SourceType` enum for `getSourceOptions` 117 | 118 | ## [1.0.0-beta.5] - 2023-08-23 119 | 120 | - Don't enforce the nodata value to be `NaN` if not present in STAC metadata 121 | 122 | ## [1.0.0-beta.4] - 2023-08-22 123 | 124 | - Fix the default entry point (you can now really use `import STAC from 'ol-stac';`) 125 | 126 | ## [1.0.0-beta.3] - 2023-08-22 127 | 128 | - Pass `properties` option to the LayerGroup. 129 | 130 | ## [1.0.0-beta.2] - 2023-08-22 131 | 132 | - Move the `stacUtils.js` from `ol/layer` to `ol-stac/utils.js` 133 | - Provide a default entry point (you can now use `import STAC from 'ol-stac';`) 134 | - Documentation improvements 135 | 136 | ## [1.0.0-beta.1] - 2023-08-22 137 | 138 | - First release 139 | 140 | [Unreleased]: 141 | [1.0.0-rc.10]: 142 | [1.0.0-rc.9]: 143 | [1.0.0-rc.8]: 144 | [1.0.0-rc.7]: 145 | [1.0.0-rc.6]: 146 | [1.0.0-rc.5]: 147 | [1.0.0-rc.4]: 148 | [1.0.0-rc.3]: 149 | [1.0.0-rc.2]: 150 | [1.0.0-rc.1]: 151 | [1.0.0-beta.12]: 152 | [1.0.0-beta.11]: 153 | [1.0.0-beta.10]: 154 | [1.0.0-beta.9]: 155 | [1.0.0-beta.8]: 156 | [1.0.0-beta.7]: 157 | [1.0.0-beta.6]: 158 | [1.0.0-beta.5]: 159 | [1.0.0-beta.4]: 160 | [1.0.0-beta.3]: 161 | [1.0.0-beta.2]: 162 | [1.0.0-beta.1]: 163 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | ## Setting up development environment 4 | 5 | You will start by [forking](https://github.com/m-mohr/ol-stac/fork) the OL STAC repository. 6 | 7 | ### Installing dependencies 8 | 9 | The minimum requirements are: 10 | 11 | * Git 12 | * [Node.js](https://nodejs.org/) (version 16 and above) 13 | 14 | The executables `git` and `node` should be in your `PATH`. 15 | 16 | To install the project dependencies run 17 | 18 | ```shell 19 | npm install 20 | ``` 21 | 22 | 23 | ## Style guidelines 24 | 25 | We use [ESLint](https://eslint.org/) rules to ensure a consistent coding style and catch potential bugs. ESLint and the rules used by the project are installed as part of the development dependencies in the step above – so you don't need to have any additional executables installed globally. 26 | 27 | When you submit a pull request, the styling rules are enforced by running the `npm run lint` task. This happens as part of an automated workflow, so you don't need to run it yourself. However, it can be useful to run the `npm run lint` task before submitting a pull request so that you can fix any styling issues ahead of time. 28 | 29 | The best way to conform with the style guidelines is to configure your editor to detect the ESLint configuration from the repository's `package.json` file. See the [ESLint integration documentation](https://eslint.org/docs/latest/use/integrations) for details on configuring your editor. If you don't already have a preferred editor that is capable of running ESLint rules, we recommend using [VS Code](https://code.visualstudio.com/) with the [ESLint plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint). 30 | 31 | In addition to having your editor warn you when the style guidelines are not being followed, you can set things up so many of the violations are automatically fixed. This saves you from having to think about tedious things like spacing and whitespace while developing. Using the ESLint plugin for VS Code, you can add the following to your settings to automatically fix issues when you save a file: 32 | 33 | ```json 34 | { 35 | "editor.codeActionsOnSave": { 36 | "source.fixAll": true 37 | } 38 | } 39 | ``` 40 | 41 | 42 | ## Running examples 43 | 44 | To run the examples you first need to start the dev server: 45 | 46 | ```shell 47 | npm run serve-examples 48 | ``` 49 | 50 | Then, load in your browser. 51 | 52 | 53 | ## Running tests 54 | 55 | To run the tests once: 56 | 57 | ```shell 58 | npm test 59 | ``` 60 | 61 | To run the tests continuously during development: 62 | 63 | ```shell 64 | npm run karma 65 | ``` 66 | 67 | 68 | ## Adding examples 69 | 70 | Adding functionality often implies adding one or several examples. This 71 | section provides explanations related to adding examples. 72 | 73 | The examples are located in the `examples` directory. Adding a new example 74 | implies creating two or three files in this directory, an `.html` file, a `.js` 75 | file, and, optionally, a `.css` file. 76 | 77 | You can use `simple.js` and `simple.html` as templates for new examples. 78 | 79 | 80 | ## Linking Package 81 | 82 | The `ol-stac` package is published from the `build/ol` folder of the `ol-stac` repo. 83 | 84 | After you've cloned the `ol-stac` repo locally run the `npm build-package` to prepare the build then use the `npm link` command to connect it your project. 85 | 86 | Below is an example of how to build and link it to `sample-project`. 87 | 88 | ```shell 89 | cd ol-stac 90 | npm run build-package 91 | cd build/ol 92 | npm link 93 | cd /sample-project 94 | npm link ol-stac 95 | ``` 96 | 97 | To remove the link run the following commands 98 | 99 | ```shell 100 | cd sample-project 101 | npm unlink --no-save ol-stac 102 | cd ../ol-stac 103 | npm unlink 104 | ``` 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ol-stac 2 | 3 | An "automagical" STAC LayerGroup for OpenLayers released under the [Apache 2.0 license](LICENSE.md). 4 | 5 | ![Test Status](https://github.com/m-mohr/ol-stac/workflows/Test/badge.svg) 6 | 7 | ## Getting Started 8 | 9 | Please read the Quick Start first: 10 | 11 | 12 | Explore the examples to get started with some more code: 13 | 14 | 15 | Dig into the API documentation for all details: 16 | 17 | 18 | **ol-stac requires OpenLayers version >=7.5.2** 19 | 20 | ## Sponsors 21 | 22 | ol-stac appreciates contributions of all kinds. 23 | 24 | The development of this package was financially supported by: 25 | - [Planet Labs PBC](https://planet.com) 26 | - [EOX IT Services GmbH](https://eox.at) 27 | 28 | Many thanks also to OpenLayers and its contributors. 29 | 30 | [If you need any help, please contact me.](https://mohr.ws) 31 | 32 | ## TypeScript support 33 | 34 | The [ol-stac package](https://npmjs.com/package/ol-stac) includes auto-generated TypeScript declarations as `*.d.ts` files. 35 | 36 | ## Documentation 37 | 38 | Check out the [hosted examples](https://m-mohr.github.io/ol-stac/en/latest/examples/) or the [API documentation](https://m-mohr.github.io/ol-stac/en/latest/apidoc/). 39 | 40 | ## Bugs 41 | 42 | Please use the [GitHub issue tracker](https://github.com/m-mohr/ol-stac/issues) for all bugs and feature requests. Before creating a new issue, do a quick search to see if the problem has been reported already. 43 | 44 | ## Related work 45 | 46 | A similar package for Leaflet and authored by Daniel Dufour and Matthias Mohr is 47 | [stac-layer](https://github.com/stac-utils/stac-layer). 48 | It has a very similar interface and functionality. 49 | -------------------------------------------------------------------------------- /config/jsdoc/api/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "recurse": true, 4 | "template": "config/jsdoc/api/template" 5 | }, 6 | "tags": { 7 | "allowUnknownTags": true 8 | }, 9 | "source": { 10 | "includePattern": ".+\\.js$", 11 | "excludePattern": "(^|\\/|\\\\)_", 12 | "include": [ 13 | "src/ol" 14 | ] 15 | }, 16 | "plugins": [ 17 | "jsdoc-plugin-intersection", 18 | "config/jsdoc/plugins/markdown.cjs", 19 | "jsdoc-plugin-typescript", 20 | "config/jsdoc/plugins/inline-options.cjs", 21 | "config/jsdoc/plugins/events.cjs", 22 | "config/jsdoc/plugins/api.cjs", 23 | "config/jsdoc/plugins/default-export.cjs" 24 | ], 25 | "typescript": { 26 | "moduleRoot": "src" 27 | }, 28 | "templates": { 29 | "cleverLinks": true, 30 | "monospaceLinks": true, 31 | "default": { 32 | "outputSourceFiles": false 33 | }, 34 | "applicationName": "OL STAC" 35 | }, 36 | "jsVersion": 180 37 | } 38 | -------------------------------------------------------------------------------- /config/jsdoc/api/index.md: -------------------------------------------------------------------------------- 1 | This is the API documentation for `ol-stac`. 2 | 3 | The class you likely want to look at is the STACLayer class, which is an advanced version of an OpenLayers LayerGroup. -------------------------------------------------------------------------------- /config/jsdoc/api/readme.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | This directory contains configuration (`conf.json`), static content (`index.md`), template (`template/`) and plugins (`plugins/`) for the [JSDoc3](https://jsdoc.app/) API generator. 4 | 5 | ## Documenting the source code 6 | 7 | JSDoc annotations are used for metadata used by the compiler, for defining the user facing API, and for user documentation. 8 | 9 | In the simplest case, a JSDoc block can look like this: 10 | ```js 11 | /** 12 | * Add the given control to the map. 13 | * @param {ol.control.Control} control Control. 14 | * @api 15 | */ 16 | ol.Map.prototype.addControl = function(control) { 17 | // ... 18 | }; 19 | ``` 20 | The first line is text for the user documentation. This can be long, and it can 21 | contain Markdown. 22 | 23 | The second line tells the Closure compiler the type of the argument. 24 | 25 | The third line (`@api`) marks the method as part of the api and thus exportable. Without such an api annotation, the method will not be documented in the generated API documentation. Symbols without an api annotation will also not be exportable. 26 | 27 | In general, `@api` annotations should never be used on abstract methods (only on their implementations). 28 | 29 | ### Events 30 | 31 | Events are documented using `@fires` and `@event` annotations: 32 | ```js 33 | /** 34 | * Constants for event names. 35 | * @enum {string} 36 | */ 37 | ol.MapBrowserEventType = { 38 | /** 39 | * A true single click with no dragging and no double click. Note that this 40 | * event is delayed by 250 ms to ensure that it is not a double click. 41 | * @event ol.MapBrowserEvent#singleclick 42 | * @api 43 | */ 44 | SINGLECLICK: 'singleclick', 45 | // ... 46 | }; 47 | ``` 48 | Note the value of the `@event` annotation. The text before the hash refers to the event class that the event belongs to, and the text after the hash is the type of the event. 49 | 50 | To document which events are fired by a class or method, the `@fires` annotation is used: 51 | ```js 52 | /** 53 | * @fires ol.MapBrowserEvent 54 | * @fires ol.MapEvent 55 | * @fires ol.render.Event 56 | * ... 57 | */ 58 | ol.Map = function(options) { 59 | // ... 60 | }; 61 | ``` 62 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/README.md: -------------------------------------------------------------------------------- 1 | This template is based on the [Jaguar](https://github.com/davidshimjs/jaguarjs/tree/master/docs/templates/jaguar) template. [JaguarJS](https://github.com/davidshimjs/jaguarjs) is licensed under the [LGPL license](https://github.com/davidshimjs/jaguarjs/tree/master/LICENSE). 2 | 3 | The default template for JSDoc 3 uses: [the Salty Database library](https://www.npmjs.com/package/@jsdoc/salty) and the [Underscore Template library](https://underscorejs.org/#template). 4 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/static/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const source = document.querySelector('.prettyprint.source > code'); 3 | if (source) { 4 | source.innerHTML = source.innerHTML 5 | .split('\n') 6 | .map(function (item, i) { 7 | return '' + item; 8 | }) 9 | .join('\n'); 10 | } 11 | })(); 12 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/static/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/static/styles/carbon.css: -------------------------------------------------------------------------------- 1 | /* Carbon adds (see https://sell.buysellads.com) */ 2 | 3 | #ad { 4 | padding:0.5rem; 5 | min-height: 125px; 6 | } 7 | 8 | #carbonads { 9 | font-family: "Quattrocento Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 10 | } 11 | 12 | #carbonads { 13 | display: flex; 14 | } 15 | 16 | #carbonads a { 17 | color: inherit; 18 | text-decoration: none; 19 | } 20 | 21 | #carbonads a:hover { 22 | color: inherit; 23 | } 24 | 25 | #carbonads span { 26 | position: relative; 27 | display: block; 28 | overflow: hidden; 29 | } 30 | 31 | #carbonads .carbon-wrap { 32 | display: flex; 33 | } 34 | 35 | .carbon-img { 36 | display: block; 37 | margin: 0; 38 | line-height: 1; 39 | } 40 | 41 | .carbon-img img { 42 | display: block; 43 | } 44 | 45 | .carbon-text { 46 | font-size: 13px; 47 | padding: 10px; 48 | line-height: 1.5; 49 | text-align: left; 50 | } 51 | 52 | .carbon-poweredby { 53 | display: block; 54 | padding: 8px 10px; 55 | text-align: center; 56 | text-transform: uppercase; 57 | letter-spacing: .5px; 58 | font-weight: 600; 59 | font-size: 9px; 60 | line-height: 1; 61 | } 62 | 63 | #carbonads a.carbon-poweredby { 64 | color: #aaa; 65 | } 66 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/static/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/static/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Menlo, Monaco, Consolas, monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/static/theme: -------------------------------------------------------------------------------- 1 | ../../../../../site/src/theme -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/container.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |

16 | 17 | 18 | 19 |

20 |
21 | a.replace(/>\./g, '>').replace(/\. 29 | 38 | 39 |
import * as ol from '';
40 | 41 |
import  from '';
42 | 43 | 44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 | 54 |
55 | 56 |
57 | 58 | 59 | 60 |
61 | 62 |
63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 |

Example 1? 's':'' ?>

72 | 73 | 74 | 75 |
76 | 77 | 83 |

Subclasses

84 |
    85 |
  • 86 | 87 |
  • 88 |
89 | 90 | 91 | 92 |

Extends

93 | 94 |
    95 |
  • 96 |
97 | 98 | 99 | 100 |

Mixes In

101 | 102 |
    103 |
  • 104 |
105 | 106 | 107 | 108 |

Requires

109 | 110 |
    111 |
  • 112 |
113 | 114 | 115 | 119 |

Classes

120 | 121 |
122 |
123 |
124 |
125 | 126 | 127 | 131 |

Namespaces

132 | 133 |
134 |
135 |
136 |
137 | 138 | 139 | 143 |

Members

144 | 145 |
146 | 147 |
148 | 149 | 150 | 154 |

155 | 156 |
157 | 158 | 159 |
160 | 161 | 162 | 166 |

Type Definitions

167 | 168 |
171 | 172 | 176 | 177 |
180 | 181 | 182 | 186 |

Events

187 | 188 |
189 | 190 |
191 | 192 |
193 | 194 |
195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/details.tmpl: -------------------------------------------------------------------------------- 1 | 5 |
6 | 10 | 11 |
Properties:
12 | 13 |
14 | 15 | 16 | 17 | 18 |
Version:
19 |
20 | 21 | 22 | 23 |
Since:
24 |
25 | 26 | 27 | 28 |
Deprecated
  • Yes
    32 | 33 | 34 | 35 |
    Author:
    36 |
    37 |
      38 |
    • 39 |
    40 |
    41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
    License:
    50 |
    51 | 52 | 53 | 54 |
    Default Value:
    55 |
    56 | 57 | 58 | 59 |
    Tutorials:
    60 |
    61 |
      62 |
    • 63 |
    64 |
    65 | 66 | 67 | 68 |
    See:
    69 |
    70 |
      71 |
    • 72 |
    73 |
    74 | 75 | 76 | 77 |
    TODO
    78 |
    79 |
      80 |
    • 81 |
    82 |
    83 | 84 |
    85 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/example.tmpl: -------------------------------------------------------------------------------- 1 | 2 |
    3 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/examples.tmpl: -------------------------------------------------------------------------------- 1 | 6 |

    7 | 8 | 9 | ')) { ?> 10 |
    11 | 12 | /g, '
    ') ?>
    13 |     
    14 | 
    17 | 
    
    
    --------------------------------------------------------------------------------
    /config/jsdoc/api/template/tmpl/exceptions.tmpl:
    --------------------------------------------------------------------------------
     1 | 
     4 | 
     5 | 
    6 |
    7 |
    8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 | Type 15 |
    16 |
    17 | 18 |
    19 |
    20 |
    21 |
    22 | 23 |
    24 | 25 | 26 | 27 | 28 | 29 |
    30 | 31 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/layout.tmpl: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | OL STAC v<?js= version ?> API - <?js= title ?> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 55 | 56 |
    57 |
    58 | 78 | 79 |
    80 |

    81 | 85 | 86 |
    87 |
    88 |
    89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/mainpage.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |

    8 | 9 | 10 | 11 |
    12 |
    13 |
    14 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/members.tmpl: -------------------------------------------------------------------------------- 1 | {' + self.linkto(name) + '} '; 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 |
    Example 1? 's':'' ?>
    36 | 37 | 38 |
    39 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/method.tmpl: -------------------------------------------------------------------------------- 1 | 11 |
    12 | 13 |
    14 |
    15 |
    16 |

    17 | 18 | 19 | 20 | 21 | 22 |

    23 | 24 |
    25 | , 26 | line 27 |
    28 | 29 |
    30 | 31 | 32 |

    33 | 34 |
    35 |
    36 | 37 | 38 | 39 |
    import  from '';
    40 | 41 | 42 | 43 |
    44 | 45 |
    46 | 47 | 48 | 49 |
    Type:
    50 |
      51 |
    • 52 | 53 |
    • 54 |
    55 | 56 | 57 | 58 |
    This:
    59 |
    60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
    Fires:
    72 |
      ]*>/g, ''); 82 | } 83 | ?> 84 |
    • 85 | 86 | 87 | () 88 | 89 | 90 | - 91 | 92 | 93 |
    • 94 |
    95 | 96 | 97 | 98 |
    Listens to Events:
    99 |
      100 |
    • 101 |
    102 | 103 | 104 | 105 |
    Listeners of This Event:
    106 |
      107 |
    • 108 |
    109 | 110 | 111 | 112 |
    Throws:
    113 | 1) { ?>
      115 |
    • 116 |
    119 | 120 | 122 | 123 | 124 | 1) { ?>
    Returns:
    125 | 126 | 127 | 128 | 129 |
    Example 1? 's':'' ?>
    130 | 131 | 132 |
    133 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/navigation.tmpl: -------------------------------------------------------------------------------- 1 | { 18 | const shortName = toShortName(member.name); ?> 19 |
  • { 22 | const shortName = toShortName(member.name); 23 | const cls = member.stability && member.stability !== 'stable' ? ' class="unstable"' : ''; ?> 24 |
  • > { 27 | const ancestor = self.find({longname: eventName})[0] || 28 | {longname: eventName, name: eventName.split(/#?event:/)[1]}; 29 | const eventEnum = ancestor.longname.split(/#?event:/)[0]; 30 | if (self.find({longname: eventEnum})[0]) { 31 | printListItemWithStability(ancestor); 32 | } else { 33 | const cls = ancestor.stability && ancestor.stability !== 'stable' ? ' class="unstable"' : ''; 34 | const shortName = toShortName(ancestor.name); ?> 35 |
  • > 42 |
    43 | 44 |
      listItemPrinter(v)); ?> 46 |
    47 |
    50 |
  • 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/observables.tmpl: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
    NameTypeSettableObjectEvent typeDescription
    29 | 30 | 31 | 32 | change:
    40 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/params.tmpl: -------------------------------------------------------------------------------- 1 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 95 | 96 | 97 | 98 | class="description last"> 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
    NameTypeDescription
    88 | 89 | 90 | 91 | (defaults to ) 92 | 93 | 94 |
    -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/properties.tmpl: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 74 | 75 | 81 | 82 | 83 | 84 | 85 |
    NameTypeDescription
    67 | 68 | 69 | 70 |
    (defaults to ) 71 | 72 | 73 |
    76 | 77 | 78 |
    Properties
    79 | 80 |
    -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/returns.tmpl: -------------------------------------------------------------------------------- 1 | /g, ''); 8 | 9 | var isNamed = ret.name ? true : false; 10 | var name = ret.name || ret.description; 11 | var startSpacePos = name.indexOf(" "); 12 | 13 | if (parentReturn !== null && name.startsWith(parentReturn.name + '.')) { 14 | ret.name = isNamed ? name.substr(parentReturn.name.length + 1) : name.substr(parentReturn.name.length + 1, startSpacePos - (parentReturn.name.length + 1)); 15 | 16 | parentReturn.subReturns = parentReturn.subReturns || []; 17 | parentReturn.subReturns.push(ret); 18 | returns[i] = null; 19 | } else { 20 | if (!isNamed) { 21 | ret.name = ret.description.substr(0, startSpacePos !== -1 ? startSpacePos : ret.description.length); 22 | } 23 | 24 | parentReturn = ret; 25 | } 26 | } 27 | }); 28 | ?> 29 | 30 | 1) { 32 | ?> 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | 51 | 61 | 64 | 65 | 66 | 67 |
    NameTypeDescription
    52 | 55 | 56 | | 57 | 60 | 62 | 63 |
    68 | 69 |
    Returns:
    70 | 71 |
    72 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/source.tmpl: -------------------------------------------------------------------------------- 1 | 4 |
    5 |
    6 |
    7 |
    8 |
    -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/stability.tmpl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/tutorial.tmpl: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 0) { ?> 5 |
      8 |
    • 9 |
    10 | 11 | 12 |

    13 |
    14 | 15 |
    16 | 17 |
    18 | 19 |
    20 | -------------------------------------------------------------------------------- /config/jsdoc/api/template/tmpl/type.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 | | 7 | -------------------------------------------------------------------------------- /config/jsdoc/info/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "recurse": true, 4 | "template": "config/jsdoc/info" 5 | }, 6 | "tags": { 7 | "allowUnknownTags": true 8 | }, 9 | "source": { 10 | "includePattern": "\\.js$" 11 | }, 12 | "plugins": [ 13 | "jsdoc-plugin-intersection", 14 | "jsdoc-plugin-typescript", 15 | "config/jsdoc/plugins/define-plugin.cjs", 16 | "config/jsdoc/plugins/virtual-plugin.cjs", 17 | "config/jsdoc/plugins/default-export.cjs" 18 | ], 19 | "typescript": { 20 | "moduleRoot": "src" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /config/jsdoc/info/publish.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | /** 4 | * @fileoverview Generates JSON output based on exportable symbols. 5 | */ 6 | const assert = require('assert'); 7 | const path = require('path'); 8 | 9 | /** 10 | * Publish hook for the JSDoc template. Writes to JSON stdout. 11 | * @param {Function} data The root of the Taffy DB containing doclet records. 12 | * @param {Object} opts Options. 13 | * @return {Promise} A promise that resolves when writing is complete. 14 | */ 15 | exports.publish = function (data, opts) { 16 | function getTypes(data) { 17 | return data.map((name) => name.replace(/^function$/, 'Function')); 18 | } 19 | 20 | // get all doclets that have exports 21 | const classes = {}; 22 | const docs = data(function () { 23 | if (this.kind == 'class') { 24 | classes[this.longname] = this; 25 | return true; 26 | } 27 | return ( 28 | !['file', 'event', 'module'].includes(this.kind) && 29 | this.meta && 30 | this.meta.path && 31 | !this.longname.startsWith('') && 32 | this.longname !== 'module:ol' 33 | ); 34 | }).get(); 35 | 36 | // get symbols data, filter out those that are members of private classes 37 | const symbols = []; 38 | const defines = []; 39 | const typedefs = []; 40 | const externs = []; 41 | let base = []; 42 | const augments = {}; 43 | const symbolsByName = {}; 44 | docs 45 | .filter(function (doc) { 46 | let include = true; 47 | const constructor = doc.memberof; 48 | if ( 49 | constructor && 50 | constructor.substr(-1) === '_' && 51 | !constructor.includes('module:') 52 | ) { 53 | assert.strictEqual( 54 | doc.inherited, 55 | true, 56 | 'Unexpected export on private class: ' + doc.longname 57 | ); 58 | include = false; 59 | } 60 | return include; 61 | }) 62 | .forEach(function (doc) { 63 | const isExterns = /[\\\/]externs$/.test(doc.meta.path); 64 | if (doc.define) { 65 | defines.push({ 66 | name: doc.longname, 67 | description: doc.description, 68 | path: path.join(doc.meta.path, doc.meta.filename), 69 | default: doc.define.default, 70 | }); 71 | } else if (doc.type && (doc.kind == 'typedef' || doc.isEnum === true)) { 72 | typedefs.push({ 73 | name: doc.longname, 74 | types: getTypes(doc.type.names), 75 | }); 76 | } else { 77 | const symbol = { 78 | name: doc.longname, 79 | kind: doc.kind, 80 | description: doc.classdesc || doc.description, 81 | path: path.join(doc.meta.path, doc.meta.filename), 82 | }; 83 | if (doc.augments) { 84 | symbol.extends = doc.augments[0]; 85 | } 86 | if (doc.virtual) { 87 | symbol.virtual = true; 88 | } 89 | if (doc.type) { 90 | symbol.types = getTypes(doc.type.names); 91 | } 92 | if (doc.params) { 93 | const params = []; 94 | doc.params.forEach(function (param) { 95 | const paramInfo = { 96 | name: param.name, 97 | }; 98 | params.push(paramInfo); 99 | paramInfo.types = getTypes(param.type.names); 100 | if (typeof param.variable == 'boolean') { 101 | paramInfo.variable = param.variable; 102 | } 103 | if (typeof param.optional == 'boolean') { 104 | paramInfo.optional = param.optional; 105 | } 106 | if (typeof param.nullable == 'boolean') { 107 | paramInfo.nullable = param.nullable; 108 | } 109 | }); 110 | symbol.params = params; 111 | } 112 | if (doc.returns) { 113 | symbol.returns = { 114 | types: getTypes(doc.returns[0].type.names), 115 | }; 116 | if (typeof doc.returns[0].nullable == 'boolean') { 117 | symbol.returns.nullable = doc.returns[0].nullable; 118 | } 119 | } 120 | if (doc.tags) { 121 | doc.tags.every(function (tag) { 122 | if (tag.title == 'template') { 123 | symbol.template = tag.value; 124 | return false; 125 | } 126 | return true; 127 | }); 128 | } 129 | if (doc.isDefaultExport) { 130 | symbol.isDefaultExport = true; 131 | } 132 | 133 | const target = isExterns ? externs : symbols; 134 | const existingSymbol = symbolsByName[symbol.name]; 135 | if (existingSymbol) { 136 | const idx = target.indexOf(existingSymbol); 137 | target.splice(idx, 1); 138 | } 139 | target.push(symbol); 140 | symbolsByName[symbol.name] = symbol; 141 | 142 | if (symbol.extends) { 143 | while ( 144 | symbol.extends in classes && 145 | classes[symbol.extends].augments 146 | ) { 147 | symbol.extends = classes[symbol.extends].augments[0]; 148 | } 149 | if (symbol.extends) { 150 | augments[symbol.extends] = true; 151 | } 152 | } 153 | } 154 | }); 155 | 156 | base = base.filter(function (symbol) { 157 | return symbol.name in augments || symbol.virtual; 158 | }); 159 | 160 | return new Promise(function (resolve, reject) { 161 | process.stdout.write( 162 | JSON.stringify( 163 | { 164 | symbols: symbols, 165 | defines: defines, 166 | typedefs: typedefs, 167 | externs: externs, 168 | base: base, 169 | }, 170 | null, 171 | 2 172 | ) 173 | ); 174 | }); 175 | }; 176 | -------------------------------------------------------------------------------- /config/jsdoc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "JSDoc loads publish.js files with require(), so we need to configure everything under this path as a CommonJS module.", 3 | "type": "commonjs" 4 | } -------------------------------------------------------------------------------- /config/jsdoc/plugins/api.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | /** 4 | * Define an @api tag 5 | * @param {Object} dictionary The tag dictionary. 6 | */ 7 | exports.defineTags = function (dictionary) { 8 | dictionary.defineTag('api', { 9 | mustNotHaveValue: true, 10 | canHaveType: false, 11 | canHaveName: false, 12 | onTagged: function (doclet, tag) { 13 | includeTypes(doclet); 14 | doclet.stability = 'stable'; 15 | }, 16 | }); 17 | }; 18 | 19 | /* 20 | * Based on @api annotations, and assuming that items with no @api annotation 21 | * should not be documented, this plugin removes undocumented symbols 22 | * from the documentation. 23 | */ 24 | 25 | const api = {}; 26 | const classes = {}; 27 | const types = {}; 28 | const modules = {}; 29 | 30 | function includeAugments(doclet) { 31 | // Make sure that `fires` are taken from an already processed `class` doclet. 32 | // This is necessary because JSDoc generates multiple doclets with the same longname. 33 | const cls = classes[doclet.longname]; 34 | if (doclet.fires && cls.fires) { 35 | for (let i = 0, ii = cls.fires.length; i < ii; ++i) { 36 | const fires = cls.fires[i]; 37 | if (!doclet.fires.includes(fires)) { 38 | doclet.fires.push(fires); 39 | } 40 | } 41 | } 42 | if (cls.fires && !doclet.fires) { 43 | doclet.fires = cls.fires; 44 | } 45 | 46 | const augments = doclet.augments; 47 | if (augments) { 48 | let cls; 49 | for (let i = augments.length - 1; i >= 0; --i) { 50 | cls = classes[augments[i]]; 51 | if (cls) { 52 | includeAugments(cls); 53 | if (cls.fires) { 54 | if (!doclet.fires) { 55 | doclet.fires = []; 56 | } 57 | cls.fires.forEach(function (f) { 58 | if (!doclet.fires.includes(f)) { 59 | doclet.fires.push(f); 60 | } 61 | }); 62 | } 63 | cls._hideConstructor = true; 64 | } 65 | } 66 | } 67 | } 68 | 69 | function extractTypes(item) { 70 | item.type.names.forEach(function (type) { 71 | const match = type.match(/^(?:.*<)?([^>]*)>?$/); 72 | if (match) { 73 | modules[match[1]] = true; 74 | types[match[1]] = true; 75 | } 76 | }); 77 | } 78 | 79 | function includeTypes(doclet) { 80 | if (doclet.params) { 81 | doclet.params.forEach(extractTypes); 82 | } 83 | if (doclet.returns) { 84 | doclet.returns.forEach(extractTypes); 85 | } 86 | if (doclet.properties) { 87 | doclet.properties.forEach(extractTypes); 88 | } 89 | if (doclet.type && doclet.meta.code.type == 'MemberExpression') { 90 | extractTypes(doclet); 91 | } 92 | } 93 | 94 | function sortOtherMembers(doclet) { 95 | if (doclet.fires) { 96 | doclet.fires.sort(function (a, b) { 97 | return a.split(/#?event:/)[1] < b.split(/#?event:/)[1] ? -1 : 1; 98 | }); 99 | } 100 | } 101 | 102 | exports.handlers = { 103 | newDoclet: function (e) { 104 | const doclet = e.doclet; 105 | if (doclet.stability) { 106 | modules[doclet.longname.split(/[~\.]/).shift()] = true; 107 | api[doclet.longname.split('#')[0]] = true; 108 | } 109 | if (doclet.kind == 'class') { 110 | if (!(doclet.longname in classes)) { 111 | classes[doclet.longname] = doclet; 112 | } else if ('augments' in doclet) { 113 | classes[doclet.longname].augments = doclet.augments; 114 | } 115 | } 116 | if (doclet.name === doclet.longname && !doclet.memberof) { 117 | // Make sure anonymous default exports are documented 118 | doclet.setMemberof(doclet.longname); 119 | } 120 | }, 121 | 122 | parseComplete: function (e) { 123 | const doclets = e.doclets; 124 | const byLongname = doclets.index.longname; 125 | for (let i = doclets.length - 1; i >= 0; --i) { 126 | const doclet = doclets[i]; 127 | if (doclet.stability) { 128 | if (doclet.kind == 'class') { 129 | includeAugments(doclet); 130 | } 131 | sortOtherMembers(doclet); 132 | // Always document namespaces and items with stability annotation 133 | continue; 134 | } 135 | if (doclet.kind == 'module' && doclet.longname in modules) { 136 | // Document all modules that are referenced by the API 137 | continue; 138 | } 139 | if (doclet.isEnum || doclet.kind == 'typedef') { 140 | continue; 141 | } 142 | if (doclet.kind == 'class' && doclet.longname in api) { 143 | // Mark undocumented classes with documented members as unexported. 144 | // This is used in ../template/tmpl/container.tmpl to hide the 145 | // constructor from the docs. 146 | doclet._hideConstructor = true; 147 | includeAugments(doclet); 148 | sortOtherMembers(doclet); 149 | } else if (!doclet._hideConstructor) { 150 | // Remove all other undocumented symbols 151 | doclet.undocumented = true; 152 | } 153 | if ( 154 | doclet.memberof && 155 | byLongname[doclet.memberof] && 156 | byLongname[doclet.memberof][0].isEnum && 157 | !byLongname[doclet.memberof][0].properties.some((p) => p.stability) 158 | ) { 159 | byLongname[doclet.memberof][0].undocumented = true; 160 | } 161 | } 162 | }, 163 | }; 164 | -------------------------------------------------------------------------------- /config/jsdoc/plugins/default-export.cjs: -------------------------------------------------------------------------------- 1 | const defaultExports = {}; 2 | const path = require('path'); 3 | const moduleRoot = path.join(process.cwd(), 'src'); 4 | 5 | // Tag default exported Identifiers because their name should be the same as the module name. 6 | exports.astNodeVisitor = { 7 | visitNode: function (node, e, parser, currentSourceName) { 8 | if (node.parent && node.parent.type === 'ExportDefaultDeclaration') { 9 | const modulePath = path 10 | .relative(moduleRoot, currentSourceName) 11 | .replace(/\.js$/, ''); 12 | const exportName = 13 | 'module:' + 14 | modulePath.replace(/\\/g, '/') + 15 | (node.name ? '~' + node.name : ''); 16 | defaultExports[exportName] = true; 17 | } 18 | }, 19 | }; 20 | 21 | exports.handlers = { 22 | processingComplete(e) { 23 | const byLongname = e.doclets.index.longname; 24 | for (const name in defaultExports) { 25 | if (!(name in byLongname)) { 26 | throw new Error( 27 | `missing ${name} in doclet index, did you forget a @module tag?` 28 | ); 29 | } 30 | byLongname[name].forEach(function (doclet) { 31 | doclet.isDefaultExport = true; 32 | }); 33 | } 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /config/jsdoc/plugins/define-plugin.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | /** 4 | * @fileoverview This plugin extracts info from boolean defines. This only 5 | * handles boolean defines with the default value in the description. Default 6 | * is assumed to be provided with something like "default is `true`" (case 7 | * insensitive, with or without ticks). 8 | */ 9 | 10 | const DEFAULT_VALUE = /default\s+is\s+`?(true|false)`?/i; 11 | 12 | /** 13 | * Hook to define new tags. 14 | * @param {Object} dictionary The tag dictionary. 15 | */ 16 | exports.defineTags = function (dictionary) { 17 | dictionary.defineTag('define', { 18 | canHaveType: true, 19 | mustHaveValue: true, 20 | onTagged: function (doclet, tag) { 21 | const types = tag.value.type.names; 22 | if (types.length === 1 && types[0] === 'boolean') { 23 | const match = tag.value.description.match(DEFAULT_VALUE); 24 | if (match) { 25 | doclet.define = { 26 | default: match[1] === 'true', 27 | }; 28 | doclet.description = tag.value.description; 29 | } 30 | } 31 | }, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /config/jsdoc/plugins/events.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | const events = {}; 4 | 5 | exports.handlers = { 6 | newDoclet: function (e) { 7 | const doclet = e.doclet; 8 | if (doclet.kind !== 'event') { 9 | return; 10 | } 11 | 12 | const cls = doclet.longname.split('#')[0]; 13 | if (!(cls in events)) { 14 | events[cls] = []; 15 | } 16 | events[cls].push(doclet.longname); 17 | }, 18 | 19 | parseComplete: function (e) { 20 | const doclets = e.doclets; 21 | for (let i = 0, ii = doclets.length - 1; i < ii; ++i) { 22 | const doclet = doclets[i]; 23 | if (doclet.fires) { 24 | if (doclet.kind == 'class') { 25 | const fires = []; 26 | for (let j = 0, jj = doclet.fires.length; j < jj; ++j) { 27 | const event = doclet.fires[j].replace('event:', ''); 28 | if (events[event]) { 29 | fires.push.apply(fires, events[event]); 30 | } else if (doclet.fires[j] !== 'event:ObjectEvent') { 31 | fires.push(doclet.fires[j]); 32 | } 33 | } 34 | doclet.fires = fires; 35 | } 36 | } 37 | } 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /config/jsdoc/plugins/inline-options.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | /** 4 | * @fileoverview 5 | * Inlines option params from typedefs 6 | */ 7 | 8 | const properties = {}; 9 | 10 | /** 11 | * This parses the comment for `@template` annotations and returns an object with name / type pairs for all template 12 | * values 13 | * @param {string} comment a jsdoc comment to parse 14 | * @return {Object} results 15 | */ 16 | function parseCommentForTemplates(comment) { 17 | let remainingText = comment; 18 | const results = {}; 19 | while (true) { 20 | const templateMatch = remainingText.match(/\* @template\s*([\s\S]*)/); 21 | 22 | if (!templateMatch) { 23 | return results; 24 | } 25 | 26 | remainingText = templateMatch[1]; 27 | 28 | if (remainingText[0] !== '{') { 29 | continue; 30 | } 31 | 32 | let index = 1; 33 | let openParenthesis = 1; 34 | while (openParenthesis > 0) { 35 | if (remainingText[index] === '{') { 36 | openParenthesis++; 37 | } else if (remainingText[index] === '}') { 38 | openParenthesis--; 39 | } 40 | index++; 41 | } 42 | const type = remainingText.slice(1, index - 1); 43 | remainingText = remainingText.slice(index); 44 | 45 | const name = remainingText.match(/\s*(\S*)/)[1]; 46 | 47 | results[name] = type; 48 | } 49 | } 50 | 51 | exports.handlers = { 52 | /** 53 | * Collects all typedefs, keyed by longname 54 | * @param {Object} e Event object. 55 | */ 56 | newDoclet: function (e) { 57 | if (e.doclet.kind == 'typedef' && e.doclet.properties) { 58 | properties[e.doclet.longname] = e.doclet.properties; 59 | } 60 | }, 61 | 62 | /** 63 | * Adds `options.*` params for options that match the longname of one of the 64 | * collected typedefs. 65 | * @param {Object} e Event object. 66 | */ 67 | parseComplete: function (e) { 68 | const doclets = e.doclets; 69 | 70 | for (let i = 0, ii = doclets.length; i < ii; ++i) { 71 | const doclet = doclets[i]; 72 | if (doclet.params) { 73 | const params = doclet.params; 74 | for (let j = 0, jj = params.length; j < jj; ++j) { 75 | const param = params[j]; 76 | if (param.type && param.type.names) { 77 | let type = param.type.names[0]; 78 | const genericMatches = type.match(/(^.*?)\.?<.*>/); 79 | if (genericMatches) { 80 | type = genericMatches[1]; 81 | } 82 | if (type in properties) { 83 | const templateInfo = parseCommentForTemplates(doclet.comment); 84 | params.push.apply( 85 | params, 86 | properties[type].map((p) => { 87 | const property = Object.assign({}, p); 88 | property.name = `${param.name}.${property.name}`; 89 | if (property.type.names[0] in templateInfo) { 90 | property.type.names[0] = 91 | templateInfo[property.type.names[0]]; 92 | } 93 | return property; 94 | }) 95 | ); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | }, 102 | }; 103 | -------------------------------------------------------------------------------- /config/jsdoc/plugins/markdown.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | /** 4 | * Modified from JSDoc's plugins/markdown and lib/jsdoc/util/markdown modules 5 | * (see https://github.com/jsdoc3/jsdoc/), which are licensed under the Apache 2 6 | * license (see https://www.apache.org/licenses/LICENSE-2.0). 7 | * 8 | * This version does not protect http(s) urls from being turned into links, and 9 | * works around an issue with `~` characters in module paths by escaping them. 10 | */ 11 | 12 | const {marked} = require('marked'); 13 | const format = require('util').format; 14 | 15 | const tags = [ 16 | 'author', 17 | 'classdesc', 18 | 'description', 19 | 'exceptions', 20 | 'params', 21 | 'properties', 22 | 'returns', 23 | 'see', 24 | 'summary', 25 | ]; 26 | 27 | const hasOwnProp = Object.prototype.hasOwnProperty; 28 | 29 | const markedRenderer = new marked.Renderer(); 30 | 31 | // Allow prettyprint to work on inline code samples 32 | markedRenderer.code = function (code, language) { 33 | const langClass = language ? ' lang-' + language : ''; 34 | 35 | return format( 36 | '
    %s
    ', 37 | langClass, 38 | escapeCode(code) 39 | ); 40 | }; 41 | 42 | function escapeCode(source) { 43 | return source 44 | .replace(/ 6 | Provide your own STAC entity and see it rendered as a layer on the map. 7 | tags: "stac" 8 | --- 9 |
    10 | 11 | 12 | 13 | 14 |
    15 |
    16 | 17 | -------------------------------------------------------------------------------- /examples/custom.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const background = new TileLayer({ 12 | source: new OSM(), 13 | }); 14 | 15 | const map = new Map({ 16 | target: 'map', 17 | layers: [background], 18 | view: new View({ 19 | center: [0, 0], 20 | zoom: 0, 21 | }), 22 | }); 23 | 24 | window.onload = function () { 25 | document.getElementById('load-btn').addEventListener('click', showUrl); 26 | document.getElementById('reset-btn').addEventListener('click', resetUrl); 27 | showUrl(); 28 | }; 29 | 30 | let layer; 31 | 32 | function resetUrl() { 33 | document.getElementById('url-input').value = ''; 34 | showUrl(); 35 | } 36 | 37 | function showUrl() { 38 | if (layer) { 39 | map.removeLayer(layer); 40 | } 41 | 42 | const url = document.getElementById('url-input').value; 43 | if (!url) { 44 | return; 45 | } 46 | 47 | try { 48 | layer = new STAC({ 49 | url, 50 | displayPreview: true, 51 | displayGeoTiffByDefault: true, 52 | displayWebMapLink: true, 53 | }); 54 | layer.on('sourceready', () => { 55 | const view = map.getView(); 56 | view.fit(layer.getExtent()); 57 | }); 58 | layer.on('layersready', () => { 59 | if (layer.isEmpty()) { 60 | alert('No spatial information available in the data source'); 61 | } 62 | }); 63 | map.addLayer(layer); 64 | } catch (error) { 65 | alert(error.message); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OL STAC Examples 7 | 8 | 9 | 10 | 11 | 12 | 13 | 41 | 42 | 43 | 80 | 81 |
    82 |
    83 |
    84 |
    85 | 92 |
    93 |
    94 |
    95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | /* global info, jugl */ 4 | 5 | const template = new jugl.Template('template'); 6 | const target = document.getElementById('examples'); 7 | 8 | function listExamples(examples) { 9 | target.innerHTML = ''; 10 | template.process({ 11 | context: {examples: examples}, 12 | clone: true, 13 | parent: target, 14 | }); 15 | document.getElementById('count').innerHTML = String(examples.length); 16 | } 17 | 18 | let timerId; 19 | function inputChange() { 20 | if (timerId) { 21 | window.clearTimeout(timerId); 22 | } 23 | const text = this.value; 24 | timerId = window.setTimeout(function () { 25 | filterList(text); 26 | }, 500); 27 | } 28 | 29 | function getMatchingExamples(text) { 30 | text = text.trim(); 31 | if (text.length === 0) { 32 | return info.examples; 33 | } 34 | const words = text.toLowerCase().split(/\W+/); 35 | const scores = {}; 36 | const updateScores = function (dict, word) { 37 | // eslint-disable-next-line prefer-const 38 | for (let exIndex in dict) { 39 | let exScore = scores[exIndex]; 40 | if (!exScore) { 41 | exScore = {}; 42 | scores[exIndex] = exScore; 43 | } 44 | exScore[word] = (exScore[word] || 0) + dict[exIndex]; 45 | } 46 | }; 47 | words.forEach(function (word) { 48 | const dict = info.wordIndex[word]; 49 | if (dict) { 50 | updateScores(dict, word); 51 | } else { 52 | const r = new RegExp(word); 53 | // eslint-disable-next-line prefer-const 54 | for (let idx in info.wordIndex) { 55 | if (r.test(idx)) { 56 | updateScores(info.wordIndex[idx], word); 57 | } 58 | } 59 | } 60 | }); 61 | const examples = []; 62 | // eslint-disable-next-line prefer-const 63 | for (let exIndex in scores) { 64 | const ex = info.examples[exIndex]; 65 | ex.score = 0; 66 | ex.words = 0; 67 | // eslint-disable-next-line prefer-const 68 | for (let word in scores[exIndex]) { 69 | ex.score += scores[exIndex][word]; 70 | ex.words++; 71 | } 72 | examples.push(ex); 73 | } 74 | // sort examples, first by number of words matched, then 75 | // by word frequency 76 | examples.sort(function (a, b) { 77 | return b.score - a.score || b.words - a.words; 78 | }); 79 | return examples; 80 | } 81 | 82 | function filterList(text) { 83 | const examples = getMatchingExamples(text); 84 | listExamples(examples); 85 | updateHistoryState(text); 86 | } 87 | 88 | function updateHistoryState(text) { 89 | text = text.trim(); 90 | const params = new URLSearchParams(window.location.search); 91 | if (text.length === 0) { 92 | params.delete('q'); 93 | } else { 94 | params.set('q', text); 95 | } 96 | let fullUrl = window.location.pathname; 97 | if (params.toString().length !== 0) { 98 | fullUrl += `?${params.toString()}`; 99 | } 100 | history.replaceState(null, '', fullUrl); 101 | } 102 | 103 | const params = new URLSearchParams(window.location.search); 104 | const text = params.get('q') || ''; 105 | const input = document.getElementById('keywords'); 106 | input.addEventListener('input', inputChange); 107 | input.value = text; 108 | filterList(text); 109 | })(); 110 | -------------------------------------------------------------------------------- /examples/planetary-computer.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: Planetary Computer 4 | shortdesc: Rendering a STAC Item from Planetary Computer. 5 | docs: > 6 | This example uses the STAC layer to render an item from Planetary Computer. 7 | Planetary Computer requres that asset URLs are signed with a token. The example 8 | uses the `getSourceOptions` callback to sign the asset URL. 9 | tags: "stac, stac item, geotiff, cog, signed url" 10 | cloak: 11 | - key: get_your_own_D6rA4zTHduk6KOKTXzGB 12 | value: 13 | --- 14 |
    15 | -------------------------------------------------------------------------------- /examples/planetary-computer.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import SourceType from '../src/ol/source/type.js'; 5 | import TileLayer from 'ol/layer/WebGLTile.js'; 6 | import View from 'ol/View.js'; 7 | import proj4 from 'proj4'; 8 | import {register} from 'ol/proj/proj4.js'; 9 | 10 | register(proj4); // required to support source reprojection 11 | 12 | /** 13 | * Get a Shared Access Signature Token to authorize asset requests. 14 | * See https://planetarycomputer.microsoft.com/docs/concepts/sas/ 15 | * @param {string} href The unsigned URL. 16 | * @return {Promise} A promise for the signed URL. 17 | */ 18 | async function sign(href) { 19 | const params = new URLSearchParams({href}); 20 | const response = await fetch( 21 | `https://planetarycomputer.microsoft.com/api/sas/v1/sign?${params}` 22 | ); 23 | const body = await response.json(); 24 | return body.href; 25 | } 26 | 27 | const layer = new STAC({ 28 | url: 'https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items/S2B_MSIL2A_20220909T185929_R013_T10TES_20220910T222807', 29 | assets: ['visual'], 30 | async getSourceOptions(type, options) { 31 | if (type === SourceType.GeoTIFF) { 32 | for (const source of options.sources) { 33 | source.url = await sign(source.url); 34 | } 35 | } 36 | return options; 37 | }, 38 | }); 39 | 40 | const background = new TileLayer({ 41 | source: new OSM(), 42 | }); 43 | 44 | const map = new Map({ 45 | target: 'map', 46 | layers: [background, layer], 47 | view: new View({ 48 | center: [0, 0], 49 | zoom: 0, 50 | }), 51 | }); 52 | 53 | layer.on('sourceready', () => { 54 | const view = map.getView(); 55 | view.fit(layer.getExtent()); 56 | }); 57 | -------------------------------------------------------------------------------- /examples/resources/Jugl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Jugl.js -- JavaScript Template Library 3 | * 4 | * Copyright 2007-2010 Tim Schaub 5 | * Released under the MIT license. Please see 6 | * http://github.com/tschaub/jugl/blob/master/license.txt for the full license. 7 | */ 8 | /* 9 | * Jugl.js -- JavaScript Template Library 10 | * 11 | * Copyright 2007-2010 Tim Schaub 12 | * Released under the MIT license. Please see 13 | * http://github.com/tschaub/jugl/blob/master/license.txt for the full license. 14 | */ 15 | (function(){var f={prefix:"jugl",namespaceURI:null,loadTemplate:function(h){var i=function(l){var m,k,n=!l.status||(l.status>=200&&l.status<300);if(n){try{m=l.responseXML;k=new e(m.documentElement)}catch(j){m=document.createElement("div");m.innerHTML=l.responseText;k=new e(m.firstChild)}if(h.callback){h.callback.call(h.scope,k)}}else{if(h.failure){h.failure.call(h.scope,l)}}};d(h.url,i)}};var g=function(h,j){h=h||{};j=j||{};for(var i in j){h[i]=j[i]}return h};var a=function(l,o){var m,n,k,j,h;if(typeof(l)==="string"){m=document.getElementById(l);if(!m){throw Error("Element id not found: "+l)}l=m}if(typeof(o)==="string"){m=document.getElementById(o);if(!m){throw Error("Element id not found: "+o)}o=m}if(o.namespaceURI&&o.xml){n=document.createElement("div");n.innerHTML=o.xml;k=n.childNodes;for(j=0,h=k.length;j 6 | Geometry and web map links (PMTiles) from SpatioTemporal Asset Catalog (STAC) Collections can be rendered as layer group. 7 | tags: "stac, stac collection, pmtiles, vector, web map links" 8 | --- 9 |
    10 | -------------------------------------------------------------------------------- /examples/stac-collection-pmtiles-raster.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const layer = new STAC({ 12 | displayWebMapLink: 'pmtiles', 13 | data: { 14 | 'stac_version': '1.0.0', 15 | 'stac_extensions': [ 16 | 'https://stac-extensions.github.io/web-map-links/v1.2.0/schema.json', 17 | ], 18 | 'type': 'Collection', 19 | 'id': 'Mt. Whitney', 20 | 'description': 'Topographic raster map as PMTiles for Mt. Whitney, US', 21 | 'license': 'proprietary', 22 | 'attribution': 'USGS', 23 | 'extent': { 24 | 'spatial': { 25 | 'bbox': [[-118.31982, 36.56109, -118.26069, 36.59301]], 26 | }, 27 | 'temporal': { 28 | 'interval': [[null, null]], 29 | }, 30 | }, 31 | 'links': [ 32 | { 33 | 'href': 'https://pmtiles.io/usgs-mt-whitney-8-15-webp-512.pmtiles', 34 | 'rel': 'pmtiles', 35 | 'type': 'application/vnd.pmtiles', 36 | }, 37 | ], 38 | }, 39 | }); 40 | 41 | const background = new TileLayer({ 42 | source: new OSM(), 43 | }); 44 | 45 | const map = new Map({ 46 | target: 'map', 47 | layers: [background, layer], 48 | view: new View({ 49 | center: [0, 0], 50 | zoom: 0, 51 | }), 52 | }); 53 | const view = map.getView(); 54 | view.fit(layer.getExtent()); 55 | -------------------------------------------------------------------------------- /examples/stac-collection-pmtiles-vector.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Collection with PMTiles (Vector) 4 | shortdesc: Rendering a STAC Collection with a selection of web mapping services. 5 | docs: > 6 | Geometry and web map links (PMTiles) from SpatioTemporal Asset Catalog (STAC) Collections can be rendered as layer group. 7 | tags: "stac, stac collection, pmtiles, vector, web map links" 8 | --- 9 |
    10 | -------------------------------------------------------------------------------- /examples/stac-collection-pmtiles-vector.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const layer = new STAC({ 12 | displayWebMapLink: 'pmtiles', 13 | displayFootprint: false, 14 | data: { 15 | 'stac_version': '1.0.0', 16 | 'stac_extensions': [ 17 | 'https://stac-extensions.github.io/web-map-links/v1.2.0/schema.json', 18 | ], 19 | 'type': 'Collection', 20 | 'id': 'Firenze', 21 | 'description': 'Vector data as PMTiles for Firenze, Italy', 22 | 'license': 'ODbL', 23 | 'attribution': '© OpenStreetMap Contributors', 24 | 'extent': { 25 | 'spatial': { 26 | 'bbox': [[11.154026, 43.7270125, 11.3289395, 43.8325455]], 27 | }, 28 | 'temporal': { 29 | 'interval': [['2023-01-18T07:49:39Z', '2023-01-18T07:49:39Z']], 30 | }, 31 | }, 32 | 'links': [ 33 | { 34 | 'href': 'https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles', 35 | 'rel': 'pmtiles', 36 | 'type': 'application/vnd.pmtiles', 37 | 'title': 'Firenze Preview', 38 | 'pmtiles:layers': ['buildings', 'natural', 'roads', 'transit', 'water'], 39 | }, 40 | ], 41 | }, 42 | }); 43 | 44 | const background = new TileLayer({ 45 | source: new OSM(), 46 | }); 47 | 48 | const map = new Map({ 49 | target: 'map', 50 | layers: [background, layer], 51 | view: new View({ 52 | center: [0, 0], 53 | zoom: 0, 54 | }), 55 | }); 56 | const view = map.getView(); 57 | view.fit(layer.getExtent()); 58 | -------------------------------------------------------------------------------- /examples/stac-collection-with-items.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Collection with child items 4 | shortdesc: Rendering a STAC Collection with its child items 5 | docs: > 6 | Geometries and Thumbnails for various SpatioTemporal Asset Catalog (STAC) Items in an ItemCollection can be rendered as part of its parent collection. 7 | Adds a click event to select individual items. 8 | tags: "stac, stac api, staticimage" 9 | --- 10 |
    11 |
    Selected Product Identifier:
    -------------------------------------------------------------------------------- /examples/stac-collection-with-items.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {getStacObjectsForEvent} from '../src/ol/util.js'; 8 | import {register} from 'ol/proj/proj4.js'; 9 | 10 | register(proj4); // required to support source reprojection 11 | 12 | const layer = new STAC({ 13 | url: 'https://planetarycomputer.microsoft.com/api/stac/v1/collections/esa-cci-lc', 14 | children: 15 | 'https://planetarycomputer.microsoft.com/api/stac/v1/collections/esa-cci-lc/items?datetime=2020-01-01T00%3A00%3A00.000Z%2F2020-12-31T00%3A00%3A00.000Z&limit=32', 16 | displayPreview: true, 17 | displayGeoTiffByDefault: false, 18 | }); 19 | 20 | const background = new TileLayer({ 21 | source: new OSM(), 22 | }); 23 | 24 | const map = new Map({ 25 | target: 'map', 26 | layers: [background, layer], 27 | view: new View({ 28 | center: [0, 0], 29 | zoom: 0, 30 | showFullExtent: true, 31 | }), 32 | }); 33 | map.on('singleclick', async (event) => { 34 | const objects = await getStacObjectsForEvent(event, layer.getData()); 35 | if (objects.length > 0) { 36 | const ids = objects.map((obj) => obj.id); 37 | document.getElementById('ids').innerText = ids.join(', '); 38 | } 39 | }); 40 | layer.on('sourceready', () => { 41 | const view = map.getView(); 42 | view.fit(layer.getExtent()); 43 | }); 44 | -------------------------------------------------------------------------------- /examples/stac-collection-wms.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Collection with WMS 4 | shortdesc: Rendering a STAC Collection with a selection of web mapping services. 5 | docs: > 6 | Geometry and web map links (WMS) from SpatioTemporal Asset Catalog (STAC) Collections can be rendered as layer group. 7 | tags: "stac, stac collection, wms, web map links" 8 | --- 9 |
    10 | -------------------------------------------------------------------------------- /examples/stac-collection-wms.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const layer = new STAC({ 12 | displayWebMapLink: true, 13 | data: { 14 | 'stac_version': '1.0.0', 15 | 'stac_extensions': [ 16 | 'https://stac-extensions.github.io/web-map-links/v1.2.0/schema.json', 17 | ], 18 | 'type': 'Collection', 19 | 'id': 'averaged_PM10', 20 | 'description': 'Particulate matter 10µm', 21 | 'license': 'CC-BY-4.0', 22 | 'attribution': 23 | 'Copyright "© 2023 European Centre for Medium-Range Weather Forecasts (ECMWF)', 24 | 'extent': { 25 | 'spatial': { 26 | 'bbox': [[-25, 30, 45, 71]], 27 | }, 28 | 'temporal': { 29 | 'interval': [['2022-09-22T00:00:00Z', '2023-07-31T00:00:00Z']], 30 | }, 31 | }, 32 | 'links': [ 33 | { 34 | 'href': 'https://eccharts.ecmwf.int/wms/?token=public', 35 | 'rel': 'wms', 36 | 'type': 'image/png', 37 | 'wms:layers': ['composition_europe_pm10_analysis_surface'], 38 | }, 39 | ], 40 | }, 41 | }); 42 | 43 | const background = new TileLayer({ 44 | source: new OSM(), 45 | }); 46 | 47 | const map = new Map({ 48 | target: 'map', 49 | layers: [background, layer], 50 | view: new View({ 51 | center: [0, 0], 52 | zoom: 0, 53 | }), 54 | }); 55 | const view = map.getView(); 56 | view.fit(layer.getExtent()); 57 | -------------------------------------------------------------------------------- /examples/stac-item-from-object.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Item from an object 4 | shortdesc: Rendering a STAC Item as a layer that is provided as a JS object. 5 | docs: > 6 | Geometry and Assets from SpatioTemporal Asset Catalog (STAC) Items can be rendered as layer group. 7 | tags: "stac, stac item, geotiff, cog" 8 | --- 9 |
    10 | -------------------------------------------------------------------------------- /examples/stac-item-from-object.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import proj4 from 'proj4'; 6 | import {register} from 'ol/proj/proj4.js'; 7 | 8 | register(proj4); // required to support source reprojection 9 | 10 | const layer = new STAC({ 11 | url: 'https://www.planet.com/data/stac/open-skysat-data/angkor-wat/dsm.json', 12 | data: { 13 | 'assets': { 14 | 'dsm': { 15 | 'href': 16 | 'https://storage.googleapis.com/open-cogs/planet-stac/angkor-wat/3d-geofox.ai/angkor-wat-dsm.tif', 17 | 'proj:epsg': 32648, 18 | 'proj:shape': [8675, 36244], 19 | 'proj:transform': [ 20 | 374283.8729316911, 0.699999988079071, 0, 1498553.1898692613, 0, 21 | -0.699999988079071, 22 | ], 23 | 'raster:bands': [ 24 | { 25 | 'data_type': 'float32', 26 | 'statistics': { 27 | 'maximum': 96.446, 28 | 'mean': 26.839, 29 | 'minimum': 2.095, 30 | 'stddev': 10.117, 31 | }, 32 | 'nodata': 'nan', 33 | }, 34 | ], 35 | 'roles': ['data', 'surface-model'], 36 | 'title': 'Digital Surface Model', 37 | 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 38 | }, 39 | }, 40 | 'bbox': [103.8381461, 13.3233018, 103.8953104, 13.5529222], 41 | 'collection': 'angkor-wat', 42 | 'geometry': { 43 | 'coordinates': [ 44 | [ 45 | [103.8381461, 13.5526676], 46 | [103.83925, 13.3233018], 47 | [103.8953104, 13.323552], 48 | [103.8942598, 13.5529222], 49 | [103.8381461, 13.5526676], 50 | ], 51 | ], 52 | 'type': 'Polygon', 53 | }, 54 | 'id': 'angkor-wat-3d-dsm', 55 | 'links': [ 56 | { 57 | 'href': 58 | 'https://www.planet.com/data/stac/open-skysat-data/angkor-wat/item.json', 59 | 'rel': 'self', 60 | 'type': 'application/json', 61 | }, 62 | ], 63 | 'properties': { 64 | 'datetime': null, 65 | 'description': 66 | 'Digital Surface Model (DSM) of Angkor Wat, Cambodia, created from stereo images captured by a [Planet](https://planet.com) SkySat satellite. It is distributed as a single band Cloud-optimized GeoTiff, with each pixel representing the height at that location.', 67 | 'end_datetime': '2020-12-14T03:22:28.5Z', 68 | 'providers': [ 69 | { 70 | 'description': 71 | 'Contact Planet at [planet.com/contact-sales](https://www.planet.com/contact-sales/)', 72 | 'name': 'Planet Labs PBC', 73 | 'roles': ['producer', 'licensor'], 74 | 'url': 'https://www.planet.com', 75 | }, 76 | { 77 | 'name': 'Geofox.ai', 78 | 'roles': ['processor'], 79 | 'url': 'https://www.geofox.ai', 80 | }, 81 | ], 82 | 'start_datetime': '2020-12-14T03:21:56.5Z', 83 | 'title': 'Digital Surface Model', 84 | }, 85 | 'stac_extensions': [ 86 | 'https://stac-extensions.github.io/projection/v1.1.0/schema.json', 87 | 'https://stac-extensions.github.io/raster/v1.1.0/schema.json', 88 | ], 89 | 'stac_version': '1.0.0', 90 | 'type': 'Feature', 91 | }, 92 | }); 93 | 94 | const background = new TileLayer({ 95 | source: new OSM(), 96 | }); 97 | 98 | const map = new Map({ 99 | target: 'map', 100 | layers: [background, layer], 101 | }); 102 | map.getView().fit(layer.getExtent()); 103 | -------------------------------------------------------------------------------- /examples/stac-item-geojson.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Item with GeoJSON assets 4 | shortdesc: Rendering a STAC Item with two GeoJSON assets 5 | docs: > 6 | Geometry and GeoJSON Assets from SpatioTemporal Asset Catalog (STAC) Items can be rendered as layer group. 7 | tags: "stac, stac item, geojson, assets" 8 | --- 9 |
    10 | -------------------------------------------------------------------------------- /examples/stac-item-geojson.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const layer = new STAC({ 12 | url: 'https://transfer.data.aad.gov.au/aadc-aerial-photography/casc8916/collection.json', 13 | assets: ['flightline-1', 'flightline-2'], 14 | }); 15 | 16 | const background = new TileLayer({ 17 | source: new OSM(), 18 | }); 19 | 20 | const map = new Map({ 21 | target: 'map', 22 | layers: [background, layer], 23 | view: new View({ 24 | center: [0, 0], 25 | zoom: 0, 26 | }), 27 | }); 28 | 29 | layer.on('sourceready', () => { 30 | const view = map.getView(); 31 | view.fit(layer.getExtent()); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/stac-item-labels.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Item with Label Extension 4 | shortdesc: Rendering a STAC Item with labels and source imagery 5 | docs: > 6 | SpatioTemporal Asset Catalog (STAC) Items with the label extension can show additional information. 7 | 8 | This example doesn't contain source imagery, so the visualization is incomplete. 9 | If you have a better example, please let me know via GitHub issues. 10 | tags: "stac, stac item, labels, label, geotiff, cog" 11 | --- 12 |
    13 | -------------------------------------------------------------------------------- /examples/stac-item-labels.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const layer = new STAC({ 12 | url: 'https://raw.githubusercontent.com/stac-extensions/label/refs/heads/main/examples/spacenet-roads/roads_item.json', 13 | displayGeoTiffByDefault: true, 14 | }); 15 | 16 | const background = new TileLayer({ 17 | source: new OSM(), 18 | }); 19 | 20 | const map = new Map({ 21 | target: 'map', 22 | layers: [background, layer], 23 | view: new View({ 24 | center: [0, 0], 25 | zoom: 0, 26 | }), 27 | }); 28 | 29 | layer.on('sourceready', () => { 30 | const view = map.getView(); 31 | view.fit(layer.getExtent()); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/stac-item-tileserver.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Item with COG rendered by a tile server 4 | shortdesc: Rendering a STAC Item as a layer, but uses a tile server instead of client-side GeoTiff rendering. 5 | docs: > 6 | Geometry and Assets from SpatioTemporal Asset Catalog (STAC) Items can be rendered as layer group using a tile server. 7 | tags: "stac, stac item, geotiff, cog, xyz" 8 | --- 9 |
    10 | -------------------------------------------------------------------------------- /examples/stac-item-tileserver.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const layer = new STAC({ 12 | url: 'https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/10/T/ES/2022/7/S2A_10TES_20220726_0_L2A/S2A_10TES_20220726_0_L2A.json', 13 | buildTileUrlTemplate: (asset) => 14 | 'https://tiles.rdnt.io/tiles/{z}/{x}/{y}@2x?url=' + 15 | encodeURIComponent(asset.getAbsoluteUrl()), 16 | }); 17 | 18 | const background = new TileLayer({ 19 | source: new OSM(), 20 | }); 21 | 22 | const map = new Map({ 23 | target: 'map', 24 | layers: [background, layer], 25 | view: new View({ 26 | center: [0, 0], 27 | zoom: 0, 28 | }), 29 | }); 30 | 31 | layer.on('sourceready', () => { 32 | const view = map.getView(); 33 | view.fit(layer.getExtent()); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/stac-item.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Item 4 | shortdesc: Rendering a STAC Item as a layer. 5 | docs: > 6 | Geometry and Assets from SpatioTemporal Asset Catalog (STAC) Items can be rendered as layer group. 7 | tags: "stac, stac item, geotiff, cog" 8 | --- 9 |
    10 | -------------------------------------------------------------------------------- /examples/stac-item.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {register} from 'ol/proj/proj4.js'; 8 | 9 | register(proj4); // required to support source reprojection 10 | 11 | const layer = new STAC({ 12 | url: 'https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/10/T/ES/2022/7/S2A_10TES_20220726_0_L2A/S2A_10TES_20220726_0_L2A.json', 13 | }); 14 | 15 | const background = new TileLayer({ 16 | source: new OSM(), 17 | }); 18 | 19 | const map = new Map({ 20 | target: 'map', 21 | layers: [background, layer], 22 | view: new View({ 23 | center: [0, 0], 24 | zoom: 0, 25 | }), 26 | }); 27 | 28 | layer.on('sourceready', () => { 29 | const view = map.getView(); 30 | view.fit(layer.getExtent()); 31 | }); 32 | 33 | layer.on('layersready', () => { 34 | // Assign titles for e.g. a layerswitcher 35 | for (const sublayer of layer.getLayersArray()) { 36 | const stac = sublayer.get('stac'); 37 | let title; 38 | if (stac.isAsset() || stac.isLink()) { 39 | title = stac.getMetadata('title') || stac.getKey(); 40 | } else { 41 | title = 'Footprint'; 42 | } 43 | sublayer.set('title', title); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /examples/stac-itemcollection.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.html 3 | title: STAC Item Collection 4 | shortdesc: Rendering a STAC Item Collection as a layer. 5 | docs: > 6 | Geometries and Thumbnails for various SpatioTemporal Asset Catalog (STAC) Items in an ItemCollection can be rendered as layer group. 7 | Adds a click event to select individual items. 8 | tags: "stac, stac api, staticimage" 9 | --- 10 |
    11 |
    Selected Product Identifier:
    -------------------------------------------------------------------------------- /examples/stac-itemcollection.js: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map.js'; 2 | import OSM from 'ol/source/OSM.js'; 3 | import STAC from '../src/ol/layer/STAC.js'; 4 | import TileLayer from 'ol/layer/WebGLTile.js'; 5 | import View from 'ol/View.js'; 6 | import proj4 from 'proj4'; 7 | import {getStacObjectsForEvent} from '../src/ol/util.js'; 8 | import {register} from 'ol/proj/proj4.js'; 9 | 10 | register(proj4); // required to support source reprojection 11 | 12 | const layer = new STAC({ 13 | url: 'https://tamn.snapplanet.io/search?bbox=125.727770,-29.514858,133.412707,-23.673395&collections=S2', 14 | displayPreview: true, 15 | }); 16 | 17 | const background = new TileLayer({ 18 | source: new OSM(), 19 | }); 20 | 21 | const map = new Map({ 22 | target: 'map', 23 | layers: [background, layer], 24 | view: new View({ 25 | center: [0, 0], 26 | zoom: 0, 27 | }), 28 | }); 29 | map.on('singleclick', async (event) => { 30 | const objects = await getStacObjectsForEvent(event); 31 | if (objects.length > 0) { 32 | const ids = objects.map((obj) => obj.properties.productIdentifier); 33 | document.getElementById('ids').innerText = ids.join(', '); 34 | } 35 | }); 36 | layer.on('sourceready', () => { 37 | const view = map.getView(); 38 | view.fit(layer.getExtent()); 39 | }); 40 | -------------------------------------------------------------------------------- /examples/templates/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{#each css.local}} 14 | 15 | {{/each}} 16 | 17 | 18 | 49 | 50 |
    51 | 55 | 56 | {{#if ./experimental}} 57 | 62 | {{/if}} 63 | 64 |
    65 | 66 | Edit 67 | 68 |
    69 |

    {{ title }}

    70 |

    71 | {{#each tags}} 72 | 73 | {{ ./tag }}{{ ./examples.length }} 87 | 88 | {{/each}} 89 |

    90 | 103 | {{{ contents }}} 104 |
    105 |
    106 | 107 |
    108 |
    109 | 110 |
    111 |
    112 |

    {{ shortdesc }}

    113 |
    {{ md docs }}
    114 |
    115 |
    116 | 117 |
    118 |
    main.js
    119 |
    {{ js.source }}
    120 |
    121 | 122 |
    123 |
    index.html
    124 |
    <!DOCTYPE html>
    125 | <html lang="en">
    126 |   <head>
    127 |     <meta charset="UTF-8">
    128 |     <title>{{ title }}</title>
    129 | {{#each css.remote}}
    130 |     <link rel="stylesheet" href="{{ . }}">
    131 | {{/each}}
    132 |     <link rel="stylesheet" href="node_modules/ol/ol.css">
    133 |     <style>
    134 |       .map {
    135 |         width: 100%;
    136 |         height: 400px;
    137 |       }
    138 | {{#if css.source}}{{ indent css.source spaces=6 }}{{/if}}    </style>
    139 |   </head>
    140 |   <body>
    141 | {{ indent contents spaces=4 }}
    142 | {{#each js.remote}}
    143 |     <script src="{{ . }}"></script>
    144 | {{/each}}
    145 |     <script type="module" src="main.js"></script>
    146 |   </body>
    147 | </html>
    148 |
    149 | 150 | {{#each extraSources}} 151 |
    152 |
    {{./name}}
    153 |
    {{ ./source }}
    154 |
    155 | {{/each}} 156 |
    157 |
    package.json
    158 |
    {{ pkgJson }}
    159 |
    160 |
    161 | 162 | 163 | {{#each js.local}} 164 | 165 | {{/each}} 166 | 167 | 168 | 169 | 170 | 171 | 179 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /examples/templates/readme.md: -------------------------------------------------------------------------------- 1 | This folder contains example templates. These templates are used to build the examples in the `examples/` folder. The resulting examples are written to the `build/examples` folder. 2 | 3 | At the bottom of each example generated in the `build/examples` folder, a modified version of its source code is shown. That modified version can be run standalone and is usually used as starting point for users to extend examples into their own application. 4 | -------------------------------------------------------------------------------- /examples/webpack/config.mjs: -------------------------------------------------------------------------------- 1 | import CopyPlugin from 'copy-webpack-plugin'; 2 | import ExampleBuilder from './example-builder.js'; 3 | import TerserPlugin from 'terser-webpack-plugin'; 4 | import fs from 'fs'; 5 | import path, {dirname} from 'path'; 6 | import {fileURLToPath} from 'url'; 7 | 8 | const src = path.join(dirname(fileURLToPath(import.meta.url)), '..'); 9 | const root = path.join(src, '..'); 10 | 11 | export default { 12 | context: src, 13 | target: ['browserslist'], 14 | entry: () => { 15 | const entry = {}; 16 | fs.readdirSync(src) 17 | .filter((name) => /^(?!index).*\.html$/.test(name)) 18 | .map((name) => name.replace(/\.html$/, '')) 19 | .forEach((example) => { 20 | entry[example] = `./${example}.js`; 21 | }); 22 | return entry; 23 | }, 24 | stats: 'minimal', 25 | optimization: { 26 | minimizer: [ 27 | new TerserPlugin({ 28 | extractComments: false, 29 | }), 30 | ], 31 | runtimeChunk: { 32 | name: 'common', 33 | }, 34 | splitChunks: { 35 | name: 'common', 36 | chunks: 'initial', 37 | minChunks: 2, 38 | }, 39 | }, 40 | plugins: [ 41 | new ExampleBuilder({ 42 | templates: path.join(src, 'templates'), 43 | common: 'common', 44 | }), 45 | new CopyPlugin({ 46 | patterns: [ 47 | { 48 | from: path.join(root, 'site', 'src', 'theme'), 49 | to: 'ol-stac/theme', 50 | }, 51 | {from: 'resources', to: 'resources'}, 52 | {from: 'index.html', to: 'index.html'}, 53 | {from: 'index.js', to: 'index.js'}, 54 | ], 55 | }), 56 | ], 57 | devtool: 'source-map', 58 | output: { 59 | filename: '[name].js', 60 | path: path.join(root, 'build', 'examples'), 61 | }, 62 | resolve: { 63 | fallback: { 64 | fs: false, 65 | http: false, 66 | https: false, 67 | }, 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ol-stac", 3 | "version": "1.0.0-rc.10", 4 | "description": "An \"automagical\" STAC LayerGroup for OpenLayers", 5 | "homepage": "https://mohr.ws", 6 | "license": "Apache-2.0", 7 | "type": "module", 8 | "main": "src/ol/layer/STAC.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/m-mohr/ol-stac.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/m-mohr/ol-stac/issues" 15 | }, 16 | "funding": { 17 | "type": "github", 18 | "url": "https://github.com/sponsors/m-mohr" 19 | }, 20 | "scripts": { 21 | "lint": "eslint tasks test src/ol examples config", 22 | "pretest": "npm run lint && npm run typecheck", 23 | "test-browser": "npm run karma -- --single-run --log-level error", 24 | "test": "npm run test-browser -- --force", 25 | "karma": "karma start test/browser/karma.config.cjs", 26 | "start": "npm run serve-examples", 27 | "serve-examples": "webpack serve --config examples/webpack/config.mjs --mode development", 28 | "build-examples": "shx rm -rf build/examples && webpack --config examples/webpack/config.mjs --mode production", 29 | "build-package": "npm run generate-types && node tasks/prepare-package.js", 30 | "generate-types": "tsc --project config/tsconfig-build.json --declaration --declarationMap --outdir build/ol", 31 | "typecheck": "tsc --pretty", 32 | "apidoc-debug": "shx rm -rf build/apidoc && node --inspect-brk=9229 ./node_modules/jsdoc/jsdoc.js --readme config/jsdoc/api/index.md --configure config/jsdoc/api/conf.json --package package.json --destination build/apidoc", 33 | "apidoc": "shx rm -rf build/apidoc && jsdoc --readme config/jsdoc/api/index.md --configure config/jsdoc/api/conf.json --package package.json --destination build/apidoc" 34 | }, 35 | "dependencies": { 36 | "ol-pmtiles": "~0.2.0", 37 | "stac-js": "~0.1.4" 38 | }, 39 | "peerDependencies": { 40 | "ol": "*" 41 | }, 42 | "devDependencies": { 43 | "@metalsmith/in-place": "^5.0.0", 44 | "@metalsmith/layouts": "^2.5.1", 45 | "@metalsmith/markdown": "^1.6.0", 46 | "@octokit/rest": "^20.0.1", 47 | "@openlayers/eslint-plugin": "^4.0.0", 48 | "copy-webpack-plugin": "^11.0.0", 49 | "es-main": "^1.0.2", 50 | "eslint": "^8.0.1", 51 | "eslint-config-openlayers": "^18.0.0", 52 | "expect.js": "0.3.1", 53 | "front-matter": "^4.0.0", 54 | "fs-extra": "^11.1.0", 55 | "handlebars": "4.7.8", 56 | "jsdoc": "4.0.2", 57 | "jsdoc-plugin-intersection": "^1.0.4", 58 | "jsdoc-plugin-typescript": "^2.2.0", 59 | "json-stringify-safe": "^5.0.1", 60 | "jstransformer-handlebars": "^1.2.0", 61 | "karma": "^6.3.8", 62 | "karma-chrome-launcher": "^3.2.0", 63 | "karma-firefox-launcher": "^2.1.2", 64 | "karma-mocha": "2.0.1", 65 | "karma-source-map-support": "^1.4.0", 66 | "karma-webpack": "^5.0.0", 67 | "marked": "7.0.2", 68 | "metalsmith": "^2.5.0", 69 | "mocha": "10.8.2", 70 | "pixelmatch": "^5.1.0", 71 | "pngjs": "^7.0.0", 72 | "proj4": "2.9.0", 73 | "puppeteer": "23.5.3", 74 | "semver": "^7.3.7", 75 | "shx": "^0.3.2", 76 | "sinon": "^15.0.0", 77 | "source-map-loader": "^4.0.0", 78 | "typescript": "5.1.6", 79 | "walk": "^2.3.9", 80 | "webpack": "^5.27.2", 81 | "webpack-cli": "^5.0.0", 82 | "webpack-dev-middleware": "^6.0.0", 83 | "webpack-dev-server": "^4.0.0-beta.2", 84 | "webpack-sources": "^3.2.0", 85 | "yargs": "^17.0.0" 86 | }, 87 | "eslintConfig": { 88 | "extends": "openlayers", 89 | "plugins": [ 90 | "@openlayers" 91 | ], 92 | "rules": { 93 | "jsdoc/no-bad-blocks": "off", 94 | "import/no-commonjs": "error", 95 | "@openlayers/no-exclusive-tests": [ 96 | "error", 97 | { 98 | "include": "test/**/*.test.js" 99 | } 100 | ] 101 | } 102 | }, 103 | "browserslist": [ 104 | "> 1%", 105 | "last 2 versions", 106 | "not dead" 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | -------------------------------------------------------------------------------- /site/build.js: -------------------------------------------------------------------------------- 1 | import Metalsmith from 'metalsmith'; 2 | import inPlace from '@metalsmith/in-place'; 3 | import layouts from '@metalsmith/layouts'; 4 | import markdown from '@metalsmith/markdown'; 5 | import {dirname} from 'node:path'; 6 | import {env} from 'node:process'; 7 | import {fileURLToPath} from 'node:url'; 8 | 9 | const baseDir = dirname(fileURLToPath(import.meta.url)); 10 | 11 | const builder = Metalsmith(baseDir) 12 | .source('./src') 13 | .destination('./build') 14 | .clean(true) 15 | .metadata({ 16 | version: env.OL_VERSION || 'dev', 17 | }) 18 | .use(inPlace({transform: 'handlebars'})) 19 | .use(markdown()) 20 | .use(layouts()); 21 | 22 | builder.build(async (err) => { 23 | if (err) { 24 | throw err; 25 | } 26 | }); -------------------------------------------------------------------------------- /site/layouts/default.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OL STAC - {{ title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{#each head}} 19 | {{{ this }}} 20 | {{/each}} 21 | 22 | 23 | 65 | 66 | {{#if uncontained}} 67 | {{{ contents }}} 68 | {{else}} 69 |
    70 | {{{ contents }}} 71 |
    72 | {{/if}} 73 | 77 | 78 | {{#each scripts}} 79 | {{{this}}} 80 | {{/each}} 81 | 82 | 83 | -------------------------------------------------------------------------------- /site/src/doc/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Frequently Asked Questions (FAQ) 3 | layout: default.hbs 4 | --- 5 | 6 | # Frequently Asked Questions (FAQ) 7 | 8 | ## What STAC elements can I show? 9 | 10 | - STAC Collections 11 | - STAC Items 12 | - STAC ItemCollection / STAC API Items (response of `GET /collections/{id}/items`) 13 | - STAC API Collections (response of `GET /collections`) 14 | - STAC Asset 15 | 16 | In principle you can also pass in STAC Catalogs, but unless there's a link to a web map included, it won't show anything. 17 | 18 | ## What STAC extensions are supported? 19 | 20 | The web-map-links extension is supported to show links for: 21 | - TileJSON 22 | - WMS (tiled only) 23 | - WMTS 24 | - XYZ 25 | 26 | For GeoTiff rendering the extension (through the [stac-js library](https://github.com/m-mohr/stac-js)) makes use of: 27 | - classification 28 | - eo 29 | - file 30 | - projection 31 | - raster 32 | 33 | ## Is there an alternative for other mapping libraries? 34 | 35 | Yes, there is a similar library for Leaflet: [stac-layer](https://github.com/stac-utils/stac-layer) 36 | I'm not aware of similar libraries for other web mapping libraries. 37 | 38 | ## Is TypeScript supported? 39 | 40 | Yes, the library exports (auto-generated) TypeScript bindings. 41 | 42 | ## What versions of OpenLayers are supported? 43 | 44 | Ol-Stac is supporting OpenLayers version >=7.5.2. 45 | -------------------------------------------------------------------------------- /site/src/doc/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Documentation 3 | layout: default.hbs 4 | --- 5 | 6 | # Documentation 7 | 8 | If you're eager to get your first map based on OpenLayers and enriched with STAC on a page, dive into the [quick start](./quickstart.html). 9 | 10 | For a more in-depth overview of OL STAC core concepts, check out the [tutorials](./tutorials/). 11 | 12 | Find additional reference material in the [API docs](/ol-stac/en/latest/apidoc/) and [examples](/ol-stac/en/latest/examples/). 13 | 14 | # Frequently Asked Questions (FAQ) 15 | 16 | We have put together a document that lists [Frequently Asked Questions (FAQ)](./faq.html) and our answers. Common problems that may arise when using OL STAC are explained there, and chances are you'll find an appropriate solution in this document. 17 | -------------------------------------------------------------------------------- /site/src/doc/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start 3 | layout: default.hbs 4 | --- 5 | 6 | # Quick Start 7 | 8 | To use OL STAC in your application, the simplest way is to add it as a dependency to it: 9 | ```bash 10 | npm install ol-stac 11 | ``` 12 | 13 | Afterwards, you can import the STAC Layer (Group) into your application code: 14 | ```js 15 | import STACLayer from 'ol-stac'; 16 | ``` 17 | 18 | Then create the layer with either a URL (option `url` as in th example) or inline JSON data (option `data`): 19 | ```js 20 | const stac = new STAC({ 21 | url: 'https://example.com/path/to/stac/item.json', 22 | }); 23 | ``` 24 | 25 | Now you can add the STAC layer to your map. 26 | I'm assuming your OpenLayers map variable is named `map`. 27 | ```js 28 | map.addLayer(stac); 29 | ``` 30 | 31 | To center the map on the STAC bounds, you should add the following: 32 | ```js 33 | stac.on('sourceready', () => { 34 | map.getView().fit(stac.getExtent()); 35 | }); 36 | ``` 37 | 38 | If you need support for reprojection, you also need to make proj4 available: 39 | ```js 40 | import proj4 from 'proj4'; 41 | import {register} from 'ol/proj/proj4.js'; 42 | register(proj4); 43 | ``` 44 | 45 | For full code examples, please see the **[examples](/ol-stac/en/latest/examples/)**. 46 | -------------------------------------------------------------------------------- /site/src/doc/tutorials/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tutorials 3 | layout: default.hbs 4 | --- 5 | 6 | # Tutorials 7 | 8 | * No tutorials available yet, sorry. Please have a look at the [examples](/ol-stac/en/latest/examples/). 9 | -------------------------------------------------------------------------------- /site/src/download/index.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Get the Code' 3 | layout: default.hbs 4 | --- 5 |
    6 |
    7 |

    The ol-stac package

    8 |
    9 |

    10 | The recommended way to use OL STAC is to work with the ol-stac package. 11 |

    12 |

    13 | To add OL STAC to an existing project, install the latest with npm: 14 |

    npm install ol-stac
    15 |

    16 |

    17 | If you are starting a new project from scratch, see the quick start docs for more information. 18 |

    19 |
    20 |
    21 |
    22 |

    Downloads for the {{ version }} release

    23 |
    24 |
    25 |
    26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
    ArchiveDescription
    {{ version }}-site.zipIncludes examples and documentation.
    {{ version }}-package.zipIncludes sources of the library.
    39 |

    40 | See the {{ version }} release page for a changelog and any special upgrade notes. 41 |

    42 |

    43 | For archives of previous releases, see the complete list of releases. 44 |

    45 |
    46 |
    47 |
    48 | -------------------------------------------------------------------------------- /site/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-mohr/ol-stac/8ebc9244ec78499a946a501c4aae00e063cd8881/site/src/favicon.ico -------------------------------------------------------------------------------- /site/src/index.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: Welcome 3 | layout: default.hbs 4 | uncontained: true 5 | head: 6 | - 7 | --- 8 |
    9 |
    10 |
    11 |

    Latest

    12 |

    OL STAC {{ version }} is here! Check out the docs and the examples to get started. The full distribution can be downloaded from the release page.

    13 |
    14 |
    15 |
    16 |

    Overview

    17 |
    18 |
    19 |
    20 |

    OL STAC makes it easy to put STAC resources on a dynamic OpenLayers map. It can display geometries, GeoTIFF files, web map layers and more in an "automagical" way. It is completely free, Open Source JavaScript, released under the Apache 2.0 license.

    21 |
    22 |
    23 |
    24 |

    Learn More

    25 |
    26 |
    27 |
    28 | 29 |
    Quick Start
    30 |
    31 |

    Seen enough already? Go here to get started.

    32 |
    33 |
    34 | 35 |
    Tutorials
    36 |
    37 |

    Spend time learning the basics and graduate up to advanced mapping techniques.

    38 |
    39 |
    40 |
    41 |
    42 | 43 |
    Examples
    44 |
    45 |

    Browse through the examples to get an impression of what is possible.

    46 |
    47 |
    48 | 49 |
    API Docs
    50 |
    51 |

    Read through the API docs for details on code usage.

    52 |
    53 |
    54 |
    55 |

    Get Involved

    56 |
    57 |
    58 | 63 | 68 |
    69 |
    70 |

    Support

    71 |
    72 |
    73 |
    74 | 75 |
    Funding
    76 |
    77 |

    78 | The development of this package was financially supported by: 79 |

    80 | 84 |

    Many thanks also to OpenLayers and its contributors.

    85 |

    If you need any help, please contact me.

    86 |
    87 |
    88 | 89 |
    Create a PR
    90 |
    91 |

    We are always happy to receive and review pull requests to improve this library even further.

    92 |
    93 |
    94 |
    95 | -------------------------------------------------------------------------------- /site/src/theme/index.css: -------------------------------------------------------------------------------- 1 | #news { 2 | background-color: var(--ol-accent-background-color); 3 | border-radius: 4px; 4 | padding: 1rem 0 0; 5 | } 6 | 7 | #news h1 { 8 | margin: 0; 9 | } 10 | 11 | img.thumb { 12 | float: right; 13 | margin-left: 10px; 14 | } 15 | 16 | /* ol customizations */ 17 | 18 | .ol-overlaycontainer-stopevent { 19 | opacity: 0; 20 | transition: opacity 300ms ease; 21 | } 22 | 23 | /* not done with :hover because the overlay blocks events and has no height */ 24 | .over .ol-overlaycontainer-stopevent { 25 | opacity: 1; 26 | } 27 | -------------------------------------------------------------------------------- /site/src/theme/site.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Quattrocento+Sans:400,400italic,700); 2 | 3 | body { 4 | font-family: 'Quattrocento Sans', sans-serif; 5 | font-size: 16px; 6 | color: var(--ol-foreground-color); 7 | padding-top: 54px; 8 | } 9 | 10 | h1, 11 | h2, 12 | h3, 13 | h4, 14 | h5, 15 | h6, 16 | .h1, 17 | .h2, 18 | .h3, 19 | .h4, 20 | .h5, 21 | .h6 { 22 | font-weight: 700 23 | } 24 | 25 | h1, 26 | .row h1 { 27 | margin-top: 30px; 28 | margin-bottom: 20px; 29 | } 30 | 31 | h1.topic { 32 | font-size: 1.5rem; 33 | color: var(--ol-subtle-foreground-color); 34 | text-transform: uppercase; 35 | } 36 | 37 | code { 38 | color: var(--ol-foreground-color); 39 | background-color: var(--ol-accent-background-color); 40 | padding: 0.2em 0.4em; 41 | border-radius: 3px; 42 | } 43 | 44 | a>code, 45 | pre>code { 46 | color: inherit; 47 | background-color: initial; 48 | padding: initial; 49 | border-radius: initial; 50 | } 51 | 52 | a { 53 | color: var(--ol-brand-color); 54 | text-decoration: none 55 | } 56 | 57 | a :not(:first-child) { 58 | color: var(--ol-foreground-color); 59 | } 60 | 61 | a:hover, 62 | a:focus { 63 | color: var(--ol-brand-color); 64 | text-decoration: underline; 65 | } 66 | 67 | .navbar { 68 | background-color: var(--ol-foreground-color); 69 | color: var(--ol-background-color); 70 | border: 0; 71 | border-radius: 0; 72 | } 73 | 74 | .navbar-brand { 75 | color: var(--ol-background-color); 76 | font-weight: bold; 77 | font-size: 160%; 78 | padding: 8px 0; 79 | } 80 | 81 | .navbar-brand img { 82 | height: 35px; 83 | width: 35px; 84 | vertical-align: middle; 85 | margin-right: 5px; 86 | display: inline-block; 87 | } 88 | 89 | .navbar-dark .navbar-nav .nav-link { 90 | color: var(--ol-background-color); 91 | } 92 | 93 | .navbar-dark .navbar-nav .nav-link:hover { 94 | color: var(--ol-brand-color); 95 | } 96 | 97 | .navbar-brand:focus, 98 | .navbar-brand:hover, 99 | .nav-link:focus, 100 | .nav-link:hover { 101 | text-decoration: none; 102 | color: var(--ol-brand-color); 103 | } 104 | 105 | footer { 106 | background-color: var(--ol-foreground-color); 107 | color: var(--ol-background-color); 108 | margin-top: 40px; 109 | padding: 10px; 110 | text-align: center; 111 | } 112 | 113 | a.dropdown-item { 114 | color: var(--ol-foreground-color); 115 | } 116 | 117 | .dropdown-item.active, 118 | .dropdown-item:hover { 119 | color: var(--ol-brand-color); 120 | text-decoration: none; 121 | background-color: var(--ol-background-color); 122 | } 123 | 124 | .navbar-nav>li>a { 125 | color: var(--ol-background-color); 126 | } 127 | 128 | .display-table { 129 | display: table; 130 | } 131 | 132 | .btn-link { 133 | font-weight: 400; 134 | color: var(--ol-subtle-foreground-color); 135 | text-decoration: none; 136 | } 137 | 138 | .btn-link:hover { 139 | color: var(--ol-brand-color); 140 | text-decoration: none; 141 | } 142 | 143 | .version-form, 144 | .navbar-form { 145 | display: table-cell; 146 | vertical-align: middle; 147 | } 148 | 149 | .version-form { 150 | color: var(--ol-foreground-color); 151 | } 152 | 153 | #title { 154 | margin-top: 1em; 155 | } 156 | 157 | .badge-group { 158 | display: inline-block; 159 | } 160 | 161 | .badge-group>.badge:not(:last-child) { 162 | border-top-right-radius: 0; 163 | border-bottom-right-radius: 0; 164 | } 165 | 166 | .badge-group>.badge:not(:first-child) { 167 | border-top-left-radius: 0; 168 | border-bottom-left-radius: 0; 169 | } 170 | 171 | .badge-info { 172 | background-color: var(--ol-subtle-foreground-color); 173 | } 174 | 175 | a.badge-info:focus, 176 | a.badge-info:hover { 177 | background-color: var(--ol-foreground-color); 178 | color: var(--ol-brand-color); 179 | text-decoration: none; 180 | } 181 | 182 | .tag-modal-toggle { 183 | cursor: pointer; 184 | } 185 | 186 | .modal-tag-example .modal-body { 187 | padding: 0; 188 | } 189 | 190 | .modal-tag-example .list-group-item:focus, 191 | .modal-tag-example .list-group-item:hover, 192 | .modal-tag-example .list-group-item:active { 193 | background-color: var(--ol-background-color); 194 | color: var(--ol-brand-color); 195 | } 196 | 197 | .modal-tag-example .list-group-item.active { 198 | background-color: var(--ol-subtle-foreground-color); 199 | color: var(--ol-background-color); 200 | border: none; 201 | } 202 | 203 | #docs { 204 | margin-top: 1em; 205 | } 206 | 207 | ul.inline, 208 | ol.inline { 209 | margin-left: 0; 210 | padding-left: 0; 211 | list-style: none; 212 | } 213 | 214 | ul.inline>li, 215 | ol.inline>li { 216 | display: inline-block; 217 | padding-left: 5px; 218 | padding-right: 5px; 219 | } 220 | 221 | .map { 222 | height: 400px; 223 | width: 100%; 224 | background: var(--ol-background-color); 225 | margin-bottom: 10px; 226 | } 227 | 228 | .ol-control { 229 | line-height: normal; 230 | } 231 | 232 | .ol-attribution.ol-logo-only, 233 | .ol-attribution.ol-uncollapsible { 234 | max-width: calc(100% - 3em); 235 | } 236 | 237 | .iframe-info iframe { 238 | width: 100%; 239 | } 240 | 241 | .source-heading { 242 | margin-top: 1em; 243 | margin-bottom: 0; 244 | padding-left: .6em; 245 | font-weight: bold; 246 | } 247 | 248 | #tags, 249 | #shortdesc, 250 | .hidden { 251 | display: none; 252 | } 253 | 254 | #api-links ul { 255 | display: inline; 256 | } 257 | 258 | #latest-check { 259 | margin-top: -10px; 260 | margin-bottom: 10px; 261 | } 262 | 263 | /* START PRISM THEME */ 264 | 265 | /** 266 | * VS theme by Andrew Lock (https://andrewlock.net) 267 | * Inspired by Visual Studio syntax coloring 268 | */ 269 | 270 | code[class*="language-"], 271 | pre[class*="language-"] { 272 | color: #393A34; 273 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 274 | direction: ltr; 275 | text-align: left; 276 | white-space: pre; 277 | word-spacing: normal; 278 | word-break: normal; 279 | font-size: .9em; 280 | line-height: 1.2em; 281 | 282 | -moz-tab-size: 4; 283 | -o-tab-size: 4; 284 | tab-size: 4; 285 | 286 | -webkit-hyphens: none; 287 | -moz-hyphens: none; 288 | -ms-hyphens: none; 289 | hyphens: none; 290 | } 291 | 292 | pre>code[class*="language-"] { 293 | font-size: 1em; 294 | } 295 | 296 | pre[class*="language-"]::-moz-selection, 297 | pre[class*="language-"] ::-moz-selection, 298 | code[class*="language-"]::-moz-selection, 299 | code[class*="language-"] ::-moz-selection { 300 | background: #C1DEF1; 301 | } 302 | 303 | pre[class*="language-"]::selection, 304 | pre[class*="language-"] ::selection, 305 | code[class*="language-"]::selection, 306 | code[class*="language-"] ::selection { 307 | background: #C1DEF1; 308 | } 309 | 310 | /* Code blocks */ 311 | pre[class*="language-"] { 312 | padding: 1em; 313 | margin: .5em 0; 314 | overflow: auto; 315 | border: 1px solid #dddddd; 316 | background-color: white; 317 | } 318 | 319 | /* Inline code */ 320 | :not(pre)>code[class*="language-"] { 321 | padding: .2em; 322 | padding-top: 1px; 323 | padding-bottom: 1px; 324 | background: #f8f8f8; 325 | border: 1px solid #dddddd; 326 | } 327 | 328 | .token.comment, 329 | .token.prolog, 330 | .token.doctype, 331 | .token.cdata { 332 | color: #008000; 333 | font-style: italic; 334 | } 335 | 336 | .token.namespace { 337 | opacity: .7; 338 | } 339 | 340 | .token.string { 341 | color: #A31515; 342 | } 343 | 344 | .token.punctuation, 345 | .token.operator { 346 | color: #393A34; 347 | /* no highlight */ 348 | } 349 | 350 | .token.url, 351 | .token.symbol, 352 | .token.number, 353 | .token.boolean, 354 | .token.variable, 355 | .token.constant, 356 | .token.inserted { 357 | color: #36acaa; 358 | } 359 | 360 | .token.atrule, 361 | .token.keyword, 362 | .token.attr-value, 363 | .language-autohotkey .token.selector, 364 | .language-json .token.boolean, 365 | .language-json .token.number, 366 | code[class*="language-css"] { 367 | color: #0000ff; 368 | } 369 | 370 | .token.function { 371 | color: #393A34; 372 | } 373 | 374 | .token.deleted, 375 | .language-autohotkey .token.tag { 376 | color: #9a050f; 377 | } 378 | 379 | .token.selector, 380 | .language-autohotkey .token.keyword { 381 | color: #00009f; 382 | } 383 | 384 | .token.important { 385 | color: #e90; 386 | } 387 | 388 | .token.important, 389 | .token.bold { 390 | font-weight: bold; 391 | } 392 | 393 | .token.italic { 394 | font-style: italic; 395 | } 396 | 397 | .token.class-name, 398 | .language-json .token.property { 399 | color: #2B91AF; 400 | } 401 | 402 | .token.tag, 403 | .token.selector { 404 | color: #800000; 405 | } 406 | 407 | .token.attr-name, 408 | .token.property, 409 | .token.regex, 410 | .token.entity { 411 | color: #ff0000; 412 | } 413 | 414 | .token.directive.tag .tag { 415 | background: #ffff00; 416 | color: #393A34; 417 | } 418 | 419 | /* overrides color-values for the Line Numbers plugin 420 | * http://prismjs.com/plugins/line-numbers/ 421 | */ 422 | .line-numbers.line-numbers .line-numbers-rows { 423 | border-right-color: #a5a5a5; 424 | } 425 | 426 | .line-numbers .line-numbers-rows>span:before { 427 | color: #2B91AF; 428 | } 429 | 430 | /* overrides color-values for the Line Highlight plugin 431 | * http://prismjs.com/plugins/line-highlight/ 432 | */ 433 | .line-highlight.line-highlight { 434 | background: rgba(193, 222, 241, 0.2); 435 | background: -webkit-linear-gradient(left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0)); 436 | background: linear-gradient(to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0)); 437 | } 438 | 439 | /* END PRISM THEME */ 440 | 441 | /* restyle prism copy button */ 442 | div.code-toolbar { 443 | position: relative; 444 | } 445 | 446 | div.code-toolbar>.toolbar { 447 | opacity: 0; 448 | position: absolute; 449 | right: 0; 450 | top: 0; 451 | transition: opacity .2s ease-in-out; 452 | } 453 | 454 | div.code-toolbar:hover>.toolbar, 455 | div.code-toolbar:focus-within>.toolbar { 456 | opacity: 1; 457 | } 458 | 459 | div.code-toolbar>.toolbar button { 460 | font-size: 16px; 461 | color: var(--ol-subtle-foreground-color); 462 | background-color: transparent; 463 | box-shadow: none; 464 | padding: .615rem .75rem; 465 | border: none; 466 | } 467 | 468 | div.code-toolbar>.toolbar button:hover, 469 | div.code-toolbar>.toolbar button:focus { 470 | color: var(--ol-brand-color); 471 | } 472 | 473 | div.code-toolbar>.toolbar button:focus { 474 | outline: 0; 475 | box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, .25); 476 | } 477 | 478 | div.code-toolbar>.toolbar button { 479 | background-color: rgba(255, 255, 255, .625); 480 | border-radius: 0 0 0 10px; 481 | margin: 1px; 482 | } 483 | div.code-toolbar>.toolbar button:before { 484 | font: var(--fa-font-solid); 485 | font-size: 1.33333333em; 486 | line-height: .75em; 487 | content: "\f0ea"; 488 | text-rendering: auto; 489 | -webkit-font-smoothing: antialiased; 490 | margin-right: .184em; 491 | vertical-align: middle; 492 | } 493 | 494 | /* jarguar overrides for jsdoc */ 495 | .navigation { 496 | background-color: var(--ol-foreground-color); 497 | color: var(--ol-background-color); 498 | } 499 | 500 | .navigation li.item .subtitle { 501 | color: var(--ol-background-color); 502 | } 503 | 504 | .navigation li.item a:hover, 505 | .navigation li.item .title a:hover { 506 | color: var(--ol-brand-color); 507 | } 508 | 509 | .main .subsection-title { 510 | color: var(--ol-foreground-color); 511 | } 512 | 513 | .main .nameContainer, 514 | .main .nameContainer.inherited { 515 | border-top-color: var(--ol-subtle-foreground-color); 516 | color: var(--ol-foreground-color); 517 | } 518 | 519 | .nameContainer .anchor:target+h4 { 520 | background-color: inherit; 521 | } 522 | 523 | @media (min-width: 1400px) { 524 | .container { 525 | max-width: 1140px; 526 | } 527 | } 528 | 529 | table.featureInfo { 530 | caption-side: initial; 531 | } 532 | -------------------------------------------------------------------------------- /src/ol/events/ErrorEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module ol/events/ErrorEvent 3 | */ 4 | import BaseEvent from 'ol/events/Event.js'; 5 | import EventType from 'ol/events/EventType.js'; 6 | 7 | /** 8 | * @classdesc 9 | * Event emitted on configuration or loading error. 10 | * @api 11 | */ 12 | class ErrorEvent extends BaseEvent { 13 | /** 14 | * @param {Error} error error object. 15 | * @api 16 | */ 17 | constructor(error) { 18 | super(EventType.ERROR); 19 | 20 | /** 21 | * @type {Error} 22 | * @api 23 | */ 24 | this.error = error; 25 | } 26 | } 27 | 28 | export default ErrorEvent; 29 | -------------------------------------------------------------------------------- /src/ol/source/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module ol/source/type 3 | */ 4 | 5 | /** 6 | * @typedef {import("ol/source/GeoTIFF.js").Options} GeoTIFFOptions 7 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_GeoTIFF-GeoTIFFSource.html} 8 | */ 9 | /** 10 | * @typedef {import("ol/source/ImageStatic.js").Options} ImageStaticOptions 11 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_ImageStatic-Static.html} 12 | */ 13 | /** 14 | * @typedef {import("ol/source/TileJSON.js").Options} TileJSONOptions 15 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_TileJSON-TileJSON.html} 16 | */ 17 | /** 18 | * @typedef {import("ol/source/TileWMS.js").Options} TileWMSOptions 19 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_TileWMS-TileWMS.html} 20 | */ 21 | /** 22 | * @typedef {import("ol/source/WMTS.js").Options} WMTSOptions 23 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_WMTS-WMTS.html} 24 | */ 25 | /** 26 | * @typedef {import("ol/source/XYZ.js").Options} XYZOptions 27 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_XYZ-XYZ.html} 28 | */ 29 | /** 30 | * @typedef {GeoTIFFOptions|ImageStaticOptions|TileJSONOptions|TileWMSOptions|WMTSOptions|XYZOptions|Object} SourceOptions 31 | */ 32 | 33 | /** 34 | * @classdesc 35 | * The source type for `getSourceOptions`. 36 | * @api 37 | */ 38 | class SourceType { 39 | /** 40 | * GeoTiff 41 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_GeoTIFF.html} 42 | * @api 43 | */ 44 | static GeoTIFF = new SourceType('GeoTIFF'); 45 | /** 46 | * Static Image (`ImageStatic`) 47 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_ImageStatic.html} 48 | * @api 49 | */ 50 | static ImageStatic = new SourceType('ImageStatic'); 51 | /** 52 | * PMTilesRaster 53 | * @see {@link https://protomaps.com/docs/pmtiles/} 54 | * @api 55 | */ 56 | static PMTilesRaster = new SourceType('PMTilesRaster'); 57 | /** 58 | * GeoTiff 59 | * @see {@link https://protomaps.com/docs/pmtiles/} 60 | * @api 61 | */ 62 | static PMTilesVector = new SourceType('PMTilesVector'); 63 | /** 64 | * TileJSON 65 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_TileJSON.html} 66 | * @api 67 | */ 68 | static TileJSON = new SourceType('TileJSON'); 69 | /** 70 | * WMS (`TileWMS`) 71 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_TileWMS.html} 72 | * @api 73 | */ 74 | static TileWMS = new SourceType('TileWMS'); 75 | /** 76 | * WMTS 77 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_WMTS.html} 78 | * @api 79 | */ 80 | static WMTS = new SourceType('WMTS'); 81 | /** 82 | * XYZ 83 | * @see {@link https://openlayers.org/en/latest/apidoc/module-ol_source_XYZ.html} 84 | * @api 85 | */ 86 | static XYZ = new SourceType('XYZ'); 87 | 88 | /** 89 | * Creates a new SourceType. 90 | * @param {string} name The internal string identifier. 91 | * @protected 92 | * @api 93 | */ 94 | constructor(name) { 95 | this.name = name; 96 | } 97 | 98 | /** 99 | * Converts to a string. 100 | * @return {string} The internal string identifier. 101 | * @api 102 | */ 103 | toString() { 104 | return this.name; 105 | } 106 | } 107 | 108 | export default SourceType; 109 | -------------------------------------------------------------------------------- /src/ol/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module ol/util 3 | */ 4 | 5 | import Circle from 'ol/style/Circle.js'; 6 | import Fill from 'ol/style/Fill.js'; 7 | import Stroke from 'ol/style/Stroke.js'; 8 | import Style from 'ol/style/Style.js'; 9 | import VectorLayer from 'ol/layer/Vector.js'; 10 | import {STAC} from 'stac-js'; 11 | import { 12 | fromEPSGCode, 13 | isRegistered as isProj4Registered, 14 | } from 'ol/proj/proj4.js'; 15 | 16 | /** 17 | * @typedef {import('ol/colorlike.js').ColorLike} ColorLike 18 | */ 19 | /** 20 | * @typedef {import('ol/Collection.js').default} Collection 21 | * @template T 22 | */ 23 | /** 24 | * @typedef {import('ol/Feature.js').default} Feature 25 | */ 26 | 27 | /** 28 | * The pattern for the supported versions of the label extension. 29 | * @type {string} 30 | */ 31 | export const LABEL_EXTENSION = 32 | 'https://stac-extensions.github.io/label/v1.*/schema.json'; 33 | 34 | const transparentFill = new Fill({color: 'rgba(0,0,0,0)'}); 35 | 36 | /** 37 | * Creates a style for visualization. 38 | * 39 | * @param {ColorLike} strokeColor Stroke color 40 | * @param {number} strokeWidth Stroke with 41 | * @param {ColorLike} fillColor Fill color 42 | * @param {number} circleRadius Circle/Point radius 43 | * @return {Style} The style for visualization. 44 | * @api 45 | */ 46 | export function getStyle( 47 | strokeColor, 48 | strokeWidth, 49 | fillColor = 'rgba(255,255,255,0.4)', 50 | circleRadius = 5 51 | ) { 52 | let fill = transparentFill; 53 | if (fillColor) { 54 | fill = new Fill({ 55 | color: fillColor, 56 | }); 57 | } 58 | const stroke = new Stroke({ 59 | color: strokeColor, 60 | width: strokeWidth, 61 | }); 62 | return new Style({ 63 | image: new Circle({ 64 | fill, 65 | stroke, 66 | radius: circleRadius, 67 | }), 68 | fill, 69 | stroke, 70 | }); 71 | } 72 | 73 | /** 74 | * The default style for rendering bounds of the STAC main entities. 75 | * @type {Style} 76 | * @api 77 | */ 78 | export const defaultBoundsStyle = getStyle('#3399CC', 3); 79 | 80 | /** 81 | * The default style for rendering collection list children. 82 | * @type {Style} 83 | * @api 84 | */ 85 | export const defaultCollectionStyle = getStyle('#ff9933', 2, null); 86 | 87 | /** 88 | * Get the STAC objects associated with this event, if any. Excludes API Collections. 89 | * @param {import('ol/MapBrowserEvent.js').default} event The asset to read the information from. 90 | * @param {STAC} [exclude=null] Excludes the given STAC entity from the list. 91 | * @param {Collection} [selectedFeatures=null] A collection to add the selected features to. 92 | * @param {number} [hitTolerance=0] The hit tolerance in pixels. 93 | * @return {Promise>} A list of STAC objects 94 | * @api 95 | */ 96 | export async function getStacObjectsForEvent( 97 | event, 98 | exclude = null, 99 | selectedFeatures = null, 100 | hitTolerance = 0 101 | ) { 102 | const objects = new Set(); 103 | event.map.forEachFeatureAtPixel( 104 | event.pixel, 105 | // Callback for all features that were found 106 | (feature, layer) => { 107 | if (selectedFeatures) { 108 | selectedFeatures.push(feature); 109 | } 110 | objects.add(layer.get('stac')); 111 | }, 112 | { 113 | // Options for forEachFeatureAtPixel 114 | hitTolerance, 115 | // Filter the layers upfront, this ensures the presence of a STAC object 116 | // so that we don't need to check in the callback above 117 | layerFilter(layer) { 118 | if (layer instanceof VectorLayer && layer.get('bounds') === true) { 119 | const stac = layer.get('stac'); 120 | if (stac instanceof STAC && (!exclude || !stac.equals(exclude))) { 121 | return true; 122 | } 123 | } 124 | return false; 125 | }, 126 | } 127 | ); 128 | return [...objects]; 129 | } 130 | 131 | /** 132 | * Get the source info for the GeoTiff from the asset. 133 | * @param {import('stac-js').Asset} asset The asset to read the information from. 134 | * @param {Array} bands The (one-based) bands to show. 135 | * @return {import('ol/source/GeoTIFF.js').SourceInfo} The source info for the GeoTiff asset 136 | */ 137 | export function getGeoTiffSourceInfoFromAsset(asset, bands) { 138 | const sourceInfo = { 139 | url: asset.getAbsoluteUrl(), 140 | }; 141 | 142 | let source = asset; 143 | // If there's just one band, we can also read the information from there. 144 | if (asset.getBands().length === 1) { 145 | source = asset.getBand(0); 146 | } 147 | 148 | // TODO: It would be useful if OL would allow min/max values per band 149 | const {minimum, maximum} = source.getMinMaxValues(); 150 | if (typeof minimum === 'number') { 151 | sourceInfo.min = minimum; 152 | } 153 | if (typeof maximum === 'number') { 154 | sourceInfo.max = maximum; 155 | } 156 | 157 | // TODO: It would be useful if OL would allow multiple no-data values 158 | const nodata = source.getNoDataValues(); 159 | if (nodata.length > 0) { 160 | sourceInfo.nodata = nodata[0]; 161 | } 162 | 163 | if (bands.length > 0) { 164 | sourceInfo.bands = bands; 165 | } 166 | 167 | return sourceInfo; 168 | } 169 | 170 | /** 171 | * Gets the projection from the asset or link. 172 | * @param {import('stac-js').STACReference} reference The asset or link to read the information from. 173 | * @param {import('ol/proj.js').ProjectionLike} defaultProjection A default projection to use. 174 | * @return {Promise} The projection, if any. 175 | */ 176 | export async function getProjection(reference, defaultProjection = undefined) { 177 | let projection = defaultProjection; 178 | if (isProj4Registered()) { 179 | // TODO: It would be great to handle WKT2 and PROJJSON, but is not supported yet by proj4js. 180 | const code = reference.getMetadata('proj:code'); 181 | if (code) { 182 | try { 183 | if (code.startsWith('EPSG:')) { 184 | const id = parseInt(code.replace('EPSG:', ''), 10); 185 | projection = await fromEPSGCode(id); 186 | } 187 | } catch (_) { 188 | // pass 189 | } 190 | } 191 | } 192 | return projection; 193 | } 194 | 195 | /** 196 | * Returns the style for the footprint. 197 | * Removes the fill if anything is meant to be shown in the bounds. 198 | * 199 | * @param {Style} [originalStyle] The original style for the footprint. 200 | * @param {import('./layer/STAC.js').default} [layerGroup] The associated STAC layergroup to check. 201 | * @return {Style} The adapted style for the footprint. 202 | * @api 203 | */ 204 | export function getBoundsStyle(originalStyle, layerGroup) { 205 | const style = originalStyle.clone(); 206 | if (!layerGroup.hasOnlyBounds()) { 207 | style.setFill(transparentFill); 208 | } 209 | return style; 210 | } 211 | 212 | /** 213 | * Get a URL from a web-map-link that is specific enough, i.e. 214 | * replaces any occurances of {s} if possible, otherwise returns null. 215 | * @param {import('./layer/STAC.js').Link} link The web map link. 216 | * @return {string|null} Specific URL 217 | */ 218 | export function getSpecificWebMapUrl(link) { 219 | let url = link.href; 220 | if (url.includes('{s}')) { 221 | if ( 222 | Array.isArray(link['href:servers']) && 223 | link['href:servers'].length > 0 224 | ) { 225 | const i = (Math.random() * link['href:servers'].length) | 0; 226 | url = url.replace('{s}', link['href:servers'][i]); 227 | } else { 228 | return null; 229 | } 230 | } 231 | return url; 232 | } 233 | -------------------------------------------------------------------------------- /tasks/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tasks/build-website.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Run this script to build the website. 5 | # 6 | set -o errexit 7 | 8 | # 9 | # Destination directory for the website. 10 | # 11 | build=build/site 12 | 13 | usage() { 14 | cat <<-EOF 15 | 16 | Usage: ${1} [options] 17 | 18 | Options: 19 | -v 20 | Version identifier. If omitted, the current branch name will be used. 21 | 22 | -l 23 | The latest release version. If provided, the root of the website will be 24 | rebuilt using this version identifier. If the -l value matches the -v value, 25 | the examples and API docs will be copied to en/latest. If the -l option 26 | is omitted, only the examples and API docs will be rebuilt (not the root of the site). 27 | EOF 28 | exit 1; 29 | } 30 | 31 | root=false 32 | 33 | while getopts ":v:l:" o; do 34 | case "${o}" in 35 | v) 36 | version=${OPTARG} 37 | ;; 38 | l) 39 | latest=${OPTARG} 40 | ;; 41 | *) 42 | usage 43 | ;; 44 | esac 45 | done 46 | shift $((OPTIND-1)) 47 | 48 | if [ -z "${version}" ]; then 49 | version=$(git branch --show-current) 50 | fi 51 | 52 | root=$(cd -P -- "$(dirname -- "${0}")" && pwd -P)/.. 53 | cd ${root} 54 | 55 | rm -rf ${build} 56 | 57 | if [ -n "${latest}" ] ; then 58 | echo "Building the website root with ${latest}" 59 | mkdir -p ${build} 60 | OL_VERSION=${latest} node site/build.js 61 | cp -r site/build/* ${build}/ 62 | fi 63 | 64 | mkdir -p ${build}/en/${version}/ 65 | 66 | echo "Building examples for ${version}" 67 | npm run build-examples 68 | mv build/examples ${build}/en/${version}/ 69 | 70 | echo "Building API docs for ${version}" 71 | npm run apidoc 72 | mv build/apidoc ${build}/en/${version}/ 73 | 74 | echo "Building the package ${version}" 75 | npm run build-package 76 | mv build/ol ${build}/en/${version}/ 77 | 78 | if [[ "${latest}" == "${version}" ]] ; then 79 | echo "Copying to en/latest" 80 | cp -r ${build}/en/${version} ${build}/en/latest 81 | 82 | echo "Building release artifacts" 83 | pushd ${build} 84 | zip -r ${OLDPWD}/build/${version}-site.zip . -x "en/${version}/*" 85 | popd 86 | 87 | pushd ${build}/en/${version}/ol 88 | zip -r ${OLDPWD}/build/${version}-package.zip . 89 | popd 90 | fi 91 | -------------------------------------------------------------------------------- /tasks/create-release.js: -------------------------------------------------------------------------------- 1 | import esMain from 'es-main'; 2 | import yargs from 'yargs'; 3 | import {Octokit} from '@octokit/rest'; 4 | import {basename} from 'node:path'; 5 | import {hideBin} from 'yargs/helpers'; 6 | import {readFile, stat} from 'node:fs/promises'; 7 | 8 | /** 9 | * @typedef {Object} Options 10 | * @property {string} token The bearer token. 11 | * @property {string} tag The tag. 12 | * @property {boolean} draft Create a draft release. 13 | * @property {boolean} notes Generate release notes. 14 | * @property {string} site Path to zip archive with site contents. 15 | * @property {string} package Path to zip archive with sources. 16 | */ 17 | 18 | const owner = 'm-mohr'; 19 | const repo = 'ol-stac'; 20 | 21 | /** 22 | * Create a release. 23 | * @param {Options} options The release options. 24 | */ 25 | async function createRelease(options) { 26 | const client = new Octokit({ 27 | auth: options.token, 28 | }); 29 | 30 | const response = await client.rest.repos.createRelease({ 31 | owner, 32 | repo, 33 | tag_name: options.tag, 34 | generate_release_notes: options.notes, 35 | draft: options.draft, 36 | }); 37 | 38 | await uploadAsset( 39 | client, 40 | response.data, 41 | options.site, 42 | 'Examples and docs (zip)' 43 | ); 44 | 45 | await uploadAsset( 46 | client, 47 | response.data, 48 | options.package, 49 | 'Package archive (zip)' 50 | ); 51 | } 52 | 53 | async function uploadAsset(client, release, assetPath, label) { 54 | const name = basename(assetPath); 55 | const stats = await stat(assetPath); 56 | const data = await readFile(assetPath); 57 | 58 | await client.rest.repos.uploadReleaseAsset({ 59 | url: release.upload_url, 60 | name, 61 | label, 62 | headers: { 63 | 'content-type': 'application/zip', 64 | 'content-length': stats.size, 65 | }, 66 | data, 67 | }); 68 | } 69 | 70 | if (esMain(import.meta)) { 71 | const options = yargs(hideBin(process.argv)) 72 | .option('token', { 73 | describe: 'The token for auth', 74 | type: 'string', 75 | }) 76 | .demandOption('token') 77 | .option('tag', { 78 | describe: 'The release tag (e.g. v7.0.0)', 79 | type: 'string', 80 | }) 81 | .demandOption('tag') 82 | .option('package', { 83 | describe: 'Path to the archive with the source package', 84 | type: 'string', 85 | }) 86 | .demandOption('package') 87 | .option('site', { 88 | describe: 'Path to the archive with the site contents', 89 | type: 'string', 90 | }) 91 | .demandOption('site') 92 | .option('draft', { 93 | describe: 'Create a draft release', 94 | type: 'boolean', 95 | default: true, 96 | }) 97 | .option('notes', { 98 | describe: 'Generate release notes', 99 | type: 'boolean', 100 | default: true, 101 | }) 102 | .parse(); 103 | 104 | createRelease(options).catch((err) => { 105 | process.stderr.write(`${err.stack}\n`, () => process.exit(1)); 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /tasks/get-latest-release.js: -------------------------------------------------------------------------------- 1 | import esMain from 'es-main'; 2 | import semver from 'semver'; 3 | import {Octokit} from '@octokit/rest'; 4 | 5 | export async function getLatestRelease() { 6 | const client = new Octokit(); 7 | 8 | let latest = '0.0.1'; 9 | await client.paginate( 10 | client.rest.repos.listReleases, 11 | { 12 | owner: 'm-mohr', 13 | repo: 'ol-stac', 14 | }, 15 | (response) => { 16 | for (const release of response.data) { 17 | const version = semver.valid(release.name); 18 | if (version && semver.gt(version, latest)) { 19 | latest = version; 20 | } 21 | } 22 | } 23 | ); 24 | 25 | return latest; 26 | } 27 | 28 | if (esMain(import.meta)) { 29 | getLatestRelease() 30 | .then((latest) => { 31 | process.stdout.write(`v${latest}\n`, () => process.exit(0)); 32 | }) 33 | .catch((err) => { 34 | process.stderr.write(`${err.message}\n`, () => process.exit(1)); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /tasks/newest-tag.js: -------------------------------------------------------------------------------- 1 | import esMain from 'es-main'; 2 | import semver from 'semver'; 3 | import yargs from 'yargs'; 4 | import {getLatestRelease} from './get-latest-release.js'; 5 | import {hideBin} from 'yargs/helpers'; 6 | 7 | /** 8 | * @typedef {Object} Options 9 | * @property {string} tag The tag. 10 | */ 11 | 12 | /** 13 | * Check if a tag is ahead of the latest release. 14 | * @param {Options} options The options. 15 | * @return {boolean} The provided tag is ahead of or equal to the latest release. 16 | */ 17 | async function main(options) { 18 | const version = semver.valid(options.tag); 19 | if (!version) { 20 | return false; 21 | } 22 | 23 | const latest = await getLatestRelease(); 24 | return semver.gte(version, latest); 25 | } 26 | 27 | if (esMain(import.meta)) { 28 | const options = yargs(hideBin(process.argv)) 29 | .option('tag', { 30 | describe: 'The tag to test (e.g. v1.2.3)', 31 | type: 'string', 32 | }) 33 | .demandOption('tag') 34 | .parse(); 35 | 36 | main(options) 37 | .then((newest) => { 38 | if (newest) { 39 | process.stdout.write('true\n', () => process.exit(0)); 40 | } else { 41 | process.stderr.write('false\n', () => process.exit(1)); 42 | } 43 | }) 44 | .catch((err) => { 45 | process.stderr.write(`${err.stack}\n`, () => process.exit(1)); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /tasks/prepare-package.js: -------------------------------------------------------------------------------- 1 | import esMain from 'es-main'; 2 | import fse from 'fs-extra'; 3 | import path, {dirname} from 'path'; 4 | import {fileURLToPath} from 'url'; 5 | 6 | const baseDir = dirname(fileURLToPath(import.meta.url)); 7 | const buildDir = path.resolve(baseDir, '../build/ol'); 8 | 9 | async function main() { 10 | const pkg = await fse.readJSON(path.resolve(baseDir, '../package.json')); 11 | 12 | // write out simplified package.json 13 | pkg.main = 'layer/STAC.js'; 14 | delete pkg.scripts; 15 | delete pkg.devDependencies; 16 | delete pkg.style; 17 | delete pkg.eslintConfig; 18 | delete pkg.private; 19 | await fse.writeJSON(path.join(buildDir, 'package.json'), pkg, {spaces: 2}); 20 | 21 | // copy in readme and license files 22 | await fse.copyFile( 23 | path.resolve(baseDir, '../README.md'), 24 | path.join(buildDir, 'README.md') 25 | ); 26 | 27 | await fse.copyFile( 28 | path.resolve(baseDir, '../LICENSE.md'), 29 | path.join(buildDir, 'LICENSE.md') 30 | ); 31 | } 32 | 33 | /** 34 | * If running this module directly, read the config file, call the main 35 | * function, and write the output file. 36 | */ 37 | if (esMain(import.meta)) { 38 | main().catch((err) => { 39 | process.stderr.write(`${err.message}\n`, () => process.exit(1)); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /tasks/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Run this script to publish a new version of the library to npm. This requires 5 | # that you have a clean working directory and have created a tag that matches 6 | # the version number in package.json. 7 | # 8 | set -o errexit 9 | 10 | # 11 | # Destination directory for the package. 12 | # 13 | BUILT_PACKAGE=build/ol 14 | 15 | # 16 | # URL for canonical repo. 17 | # 18 | REMOTE=https://github.com/m-mohr/ol-stac.git 19 | 20 | # 21 | # Display usage and exit. 22 | # 23 | display_usage() { 24 | cat <<-EOF 25 | 26 | Usage: ${1} [options] 27 | 28 | To publish a new release, update the version number in package.json and 29 | create a tag for the release. 30 | 31 | The tag name must match the version number prefixed by a "v" (for example, 32 | version 3.2.1 would be tagged v3.2.1). 33 | 34 | The tag must be pushed to ${REMOTE} before the release can be published. 35 | 36 | Additional args after will be passed to "npm publish" (e.g. "--tag beta"). 37 | 38 | EOF 39 | } 40 | 41 | # 42 | # Exit if the current working tree is not clean. 43 | # 44 | assert_clean() { 45 | source `git --exec-path`/git-sh-setup && \ 46 | require_clean_work_tree "publish" "Please commit or stash them." 47 | } 48 | 49 | # 50 | # Exit if the requested version doesn't match package.json. 51 | # 52 | assert_version_match() { 53 | v=`grep -o '"version":.*' package.json | sed 's/"version": *"\(.*\)",/\1/'` 54 | if test "${1}" != "${v}"; then 55 | echo "Version mismatch: requested '${1}', but package.json specifies '${v}'" 56 | exit 1 57 | fi 58 | } 59 | 60 | # 61 | # Check out the provided tag. This ensures that the tag has been pushed to 62 | # the canonical remote. 63 | # 64 | checkout_tag() { 65 | git fetch ${REMOTE} refs/tags/v${1}:refs/tags/v${1} 66 | git checkout refs/tags/v${1} 67 | } 68 | 69 | # 70 | # Build the package and publish. 71 | # 72 | main() { 73 | root=$(cd -P -- "$(dirname -- "${0}")" && pwd -P)/.. 74 | cd ${root} 75 | assert_clean 76 | checkout_tag ${1} 77 | assert_version_match ${1} 78 | npm install 79 | npm run build-package 80 | cd ${BUILT_PACKAGE} 81 | shift 82 | npm publish ${@} 83 | } 84 | 85 | if test ${#} -lt 1; then 86 | display_usage ${0} 87 | exit 1 88 | else 89 | main ${@} 90 | fi 91 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ## Included in this directory 2 | 3 | - browser - Unit/integration tests run in a browser 4 | - node - Unit tests run with Node.js 5 | - rendering - Tests that make assertions about rendered map output 6 | 7 | 8 | ## Run the test suite 9 | 10 | Install the test dependencies (from the root of the repository): 11 | 12 | npm install 13 | 14 | Run the tests once: 15 | 16 | npm test 17 | 18 | This will run tests in (headless) Chrome. If you do not have Chrome installed, you can run tests on Firefox instead: 19 | 20 | npm test -- --browsers Firefox 21 | 22 | To run tests in other browsers, you need to install [additional Karma launchers](https://karma-runner.github.io/1.0/config/browsers.html). 23 | 24 | To run the tests continuously: 25 | 26 | npm run karma 27 | 28 | After this, you can attach any browser and debug the tests like this: 29 | 30 | * open http://localhost:9876/debug.html 31 | * open the developer tools 32 | * add a breakpoint 33 | * refresh the page 34 | 35 | # Rendering tests 36 | 37 | The `test/rendering` directory contains rendering tests which compare a rendered map with a 38 | reference image using [pixelmatch](https://github.com/mapbox/pixelmatch). 39 | 40 | To run the tests in the browser, make sure the development server is running 41 | (`make serve`) and open the URL 42 | [http://localhost:3000/test_rendering/index.html](http://localhost:3000/test_rendering/index.html). 43 | 44 | From the command-line the tests can be run with the build target `make test-rendering`. 45 | 46 | ## Adding new tests 47 | When creating a new test case, a reference image has to be created. By appending `?generate` 48 | to the URL, a canvas with the rendered map will be shown on the page when calling 49 | `expectResemble`. Then the reference image can simply be created with a right-click 50 | and "Save image as". 51 | 52 | It is recommended to only run a single test case when generating the reference image. 53 | 54 | ## Image difference 55 | When a test fails, an image showing the difference between the reference image and the 56 | rendered map can be displayed by appending `?showdiff` to the URL. 57 | -------------------------------------------------------------------------------- /test/browser/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "afterLoadText": false, 7 | "createMapDiv": true, 8 | "disposeMap": true, 9 | "expect": false, 10 | "proj4": false, 11 | "sinon": false, 12 | "where": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/browser/karma.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | const path = require('path'); 4 | const puppeteer = require('puppeteer'); 5 | 6 | process.env.CHROME_BIN = puppeteer.executablePath(); 7 | 8 | const flags = ['--headless=new']; 9 | if (process.env.CI) { 10 | flags.push('--no-sandbox'); 11 | } 12 | 13 | module.exports = function (karma) { 14 | karma.set({ 15 | browsers: ['ChromeHeadless'], 16 | customLaunchers: { 17 | ChromeHeadless: { 18 | base: 'Chrome', 19 | flags, 20 | }, 21 | }, 22 | browserDisconnectTolerance: 2, 23 | frameworks: ['webpack', 'mocha', 'source-map-support'], 24 | client: { 25 | runInParent: true, 26 | mocha: { 27 | timeout: 2500, 28 | }, 29 | }, 30 | files: [ 31 | { 32 | pattern: path.resolve(__dirname, require.resolve('expect.js/index.js')), 33 | watched: false, 34 | }, 35 | { 36 | pattern: path.resolve( 37 | __dirname, 38 | require.resolve('../../node_modules/sinon/pkg/sinon.js') 39 | ), 40 | watched: false, 41 | }, 42 | { 43 | pattern: path.resolve( 44 | __dirname, 45 | require.resolve('ol/dist/ol.js') 46 | ), 47 | watched: false, 48 | }, 49 | { 50 | pattern: path.resolve( 51 | __dirname, 52 | require.resolve('proj4/dist/proj4.js') 53 | ), 54 | watched: false, 55 | }, 56 | { 57 | pattern: path.resolve(__dirname, './test-extensions.js'), 58 | }, 59 | { 60 | pattern: 'spec/**/*.test.js', 61 | watched: false, 62 | }, 63 | { 64 | pattern: '**/*', 65 | included: false, 66 | watched: false, 67 | }, 68 | ], 69 | proxies: { 70 | '/spec/': '/base/spec/' 71 | }, 72 | preprocessors: { 73 | '**/*.js': ['webpack'], //, 'sourcemap'], 74 | }, 75 | reporters: ['dots'], 76 | webpack: { 77 | devtool: 'inline-source-map', 78 | mode: 'development', 79 | resolve: { 80 | fallback: { 81 | fs: false, 82 | http: false, 83 | https: false, 84 | }, 85 | }, 86 | module: { 87 | rules: [ 88 | { 89 | test: /\.js$/, 90 | enforce: 'pre', 91 | use: ['source-map-loader'], 92 | }, 93 | ], 94 | }, 95 | }, 96 | webpackMiddleware: { 97 | noInfo: true, 98 | }, 99 | }); 100 | 101 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 102 | }; 103 | -------------------------------------------------------------------------------- /test/browser/spec/ol/layer/STAC.test.js: -------------------------------------------------------------------------------- 1 | import STAC from '../../../../../src/ol/layer/STAC.js'; 2 | 3 | describe('ol/layer/STAC', function () { 4 | describe('constructor (defaults)', function () { 5 | /** @type {STAC} */ 6 | let group; 7 | 8 | beforeEach(function () { 9 | group = new STAC({ 10 | url: 'https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/10/T/ES/2022/7/S2A_10TES_20220726_0_L2A/S2A_10TES_20220726_0_L2A.json', 11 | }); 12 | }); 13 | 14 | it('creates an instance', function () { 15 | expect(group).to.be.a(STAC); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 5 | "lib": ["es2019", "dom", "webworker"], /* Specify library files to be included in the compilation. */ 6 | "allowJs": true, /* Allow javascript files to be compiled. */ 7 | "checkJs": true, /* Report errors in .js files. */ 8 | "skipLibCheck": true, 9 | "noEmit": true, /* Do not emit outputs. */ 10 | "strict": false, /* Enable all strict type-checking options. */ 11 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 12 | }, 13 | "include": [ 14 | "types/**/*.ts", 15 | "src/ol/**/*.js" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------