From f3c868a797247ea2dd7b2179f2eecb9d58f8f33b Mon Sep 17 00:00:00 2001 From: Ponlawat W Date: Sat, 25 Nov 2023 14:32:07 +0700 Subject: [PATCH] 0.1.0 (#1) - Fixed loop way handling - Added unit tests - Added GitHub actions --- .github/workflows/pr.yml | 10 + .github/workflows/publish.yml | 42 + .github/workflows/unit-tests.yml | 19 + README.md | 212 +-- examples/index.html | 51 +- package-lock.json | 2106 +++++++++++++++++++++++++++++- package.json | 18 +- src/index.ts | 4 +- src/interaction.ts | 307 +++-- src/line-string-utils.ts | 86 ++ src/osm/osm-ways.ts | 50 - src/osm/overpass-api.ts | 88 -- src/osm/response.ts | 30 - src/source.ts | 119 -- src/utils/line-string.ts | 46 - tests/common.ts | 92 ++ tests/interaction.test.ts | 308 +++++ tests/osmoverpass.test.ts | 17 + tsconfig.json | 13 +- vitest.config.ts | 13 + webpack.config.js | 36 +- 21 files changed, 3013 insertions(+), 654 deletions(-) create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/unit-tests.yml create mode 100644 src/line-string-utils.ts delete mode 100644 src/osm/osm-ways.ts delete mode 100644 src/osm/overpass-api.ts delete mode 100644 src/osm/response.ts delete mode 100644 src/source.ts delete mode 100644 src/utils/line-string.ts create mode 100644 tests/common.ts create mode 100644 tests/interaction.test.ts create mode 100644 tests/osmoverpass.test.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..7928c0d --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,10 @@ +name: Pull Request Check +on: + pull_request: + branches: + - dev/* + - master +jobs: + unit-tests: + name: Unit Tests + uses: ./.github/workflows/unit-tests.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..54eea83 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,42 @@ +name: Publish +on: + push: + branches: master +jobs: + unit-tests: + name: Unit Tests + uses: ./.github/workflows/unit-tests.yml + publish: + name: Publish + needs: unit-tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + - name: Get package.json + id: get-package + run: echo PACKAGE=$(cat ./package.json) >> $GITHUB_OUTPUT + - name: Get package version + id: get-package-version + run: echo VERSION="${{ fromJson(steps.get-package.outputs.PACKAGE).version }}" >> $GITHUB_OUTPUT + - name: Install + run: npm ci + - name: Build + run: npm run build + - name: Publish + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Tag + run: | + git config --global user.name "ponlawat-w" + git config --global user.email "ponlawat_w@outlook.co.th" + git tag -fa ${{ steps.get-package-version.outputs.VERSION }} + git push --force origin ${{ steps.get-package-version.outputs.VERSION }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..52b15e4 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,19 @@ +name: Unit Tests +on: workflow_call +jobs: + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Install + run: npm ci + - name: Test + run: npm run test diff --git a/README.md b/README.md index 79abc79..c4c447d 100644 --- a/README.md +++ b/README.md @@ -4,186 +4,112 @@ ## Instructions -This extension is an interaction type in OpenLayers which requires a few setup steps: - -### 1. Vector layer for OSM ways +```bash +npm install ol-osmwaysnap +``` -To begin with, it is necessary to setup a vector layer that contains OSM ways. Using builtin `OSMWaySource` class that automatically fetches ways from OpenStreetMap using OverpassAPI, or alternatively, it can also be used with other linestring feature layer. +Create an instance of class `OSMWaySnap` and add it to map. (Default snapping to OSM roads) ```ts import VectorLayer from 'ol/layer/Vector'; -import { OSMWaySource } from 'ol-osmwaysnap'; +import VectorSource from 'ol/source/Vector'; +import { OSMWaySnap } from 'ol-osmwaysnap'; + +const targetLayer = new VectorLayer>>({ + source: new VectorSource>() +}); +map.addLayer(targetLayer); // Default: Snap to roads (OSM highway) -const osmWayLayer = new VectorLayer({ - source: new OSMWaySource({ - maximumResolution: 5, - fetchBufferSize: 250, - overpassEndpointURL: 'https://...' // Choose one instance from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances - }), - style: OSMWaySource.getDefaultStyle() +const interaction = new OSMWaySnap({ + source: targetLayer.getSource(), + maximumResolution: 5, + fetchBufferSize: 250, + overpassEndpointURL: 'https://...' // Choose one instance from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances }); +map.addInteraction(interaction); +``` +Or specify a custom OverpassQL for different way elements, for example to railways. + +```ts // Snap to railways -const osmWayLayer = new VectorLayer({ - source: new OSMWaySource({ - maximumResolution: 5, - fetchBufferSize: 250, - overpassQuery: '(way["railway"];>;);', - overpassEndpointURL: 'https://...' // Choose one instance from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances - }), - style: OSMWaySource.getDefaultStyle() +const interaction = new OSMWaySnap({ + source: targetLayer.getSource(), + maximumResolution: 5, + fetchBufferSize: 250, + overpassQuery: '(way["railway"];>;);', + overpassEndpointURL: 'https://...' // Choose one instance from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances }); +map.addInteraction(interaction); ``` -### 2. Add interactions - -There are at least 2 interactions to be added in order for the extension to work 1) `OSMWaySnap` and 2) `Snap` interaction from default OpenLayers interactions. +Or use a custom vector source (not OSM) for snapping. ```ts -import { OSMWaySnap } from 'ol-osmwaysnap'; -import Snap from 'ol/interaction/Snap'; - -map.addInteraction(new OSMWaySnap({ - source: targetFeatureLayer.getSource(), - waySource: osmWayLayer.getSource() -})); -map.addInteraction(new Snap({ - source: osmWayLayer.getSource() -})); +const interaction = new OSMWaySnap({ + source: targetLayer.getSource(), + waySource: someVectorSource, + maximumResolution: 5, + fetchBufferSize: 250 +}); +map.addInteraction(interaction); ``` +## Constructor Options + +- `autoFocus?: boolean` - True to automatically fit map view to next candidantes. (default: true) +- `focusPadding?: number` - Used with autoFocus, specify number to add padding to view fitting. (default: 50 !PROJECTION SENSITIVE!) +- `sketchStyle?: StyleLike` - Style of sketch features (default is predefined, overwrite if necessary) +- `source: VectorSource>` - Target source of edition +- `waySource?: VectorSource>` - Ways source for snapping (default to a new instance of OSMOverpassWaySource) +- `createAndAddWayLayer?: boolean` - Create a new way layer from way source (if provided) and add to map (default: true) +- `wrapX?: boolean` - WrapX + +If `waySource` is not provided, `OSMOverpass` will be used as source for snapping, so the constructor options for `OSMWaySnap` will be extended to include [thoses options from `OSMOverpassSourceBase`](https://github.com/ponlawat-w/ol-osmoverpass#constructor-options). + ## Examples +[Full page example using the library from CDN](./examples/index.html) + ### Using as module ```ts -import Map from 'ol/Map'; -import View from 'ol/View'; +import { Map, View } from 'ol'; import TileLayer from 'ol/layer/Tile'; import VectorLayer from 'ol/layer/Vector'; -import VectorSource from 'ol/source/Vector'; import OSM from 'ol/source/OSM'; -import Snap from 'ol/interaction/Snap'; -import LineString from 'ol/geom/LineString'; +import VectorSource from 'ol/source/Vector'; import Feature from 'ol/Feature'; -import { OSMWaySource, OSMWaySnap } from 'ol-osmwaysnap'; +import LineString from 'ol/geom/LineString'; +import OSMWaySnap from 'ol-osmwaysnap'; const basemap = new TileLayer({ source: new OSM() }); - -const osmWaySource = new OSMWaySource({ - maximumResolution: 5, - fetchBufferSize: 250, - overpassEndpointURL: 'https://...' // Choose one instance from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances +const targetLayer = new VectorLayer>>({ + source: new VectorSource() }); -const targetFeaturesLayer = new VectorLayer>>({ - source: new VectorSource>() +const view = new View({ + center: [11018989, 2130015], + zoom: 16 }); -const osmWayLayer = new VectorLayer({source: osmWaySource, style: OSMWaySource.getDefaultStyle()}); - const map = new Map({ target: 'map', - layers: [basemap, osmWayLayer, targetFeaturesLayer], - view: new View({ - center: [11018989, 2130015], - zoom: 16 - }) + layers: [basemap, targetLayer], + view }); -map.addInteraction(new OSMWaySnap({ - source: targetFeaturesLayer.getSource()!, - waySource: osmWayLayer.getSource()! -})); -map.addInteraction(new Snap({ - source: osmWayLayer.getSource()! -})); +const interaction = new OSMWaySnap({ + source: targetLayer.getSource(), + maximumResolution: 5, + fetchBufferSize: 250, + overpassEndpointURL: 'https://...' // Choose one instance from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances +}); +mao.addInteraction(interaction); ``` ### Using as CDN -```html - - - - - - - - -
-
- - - - - -``` - -## Options - -### `OSMWaySource` constructor options - -- `cachedFeaturesCount: number` - The number of features to store before getting cleared. This is to prevent heavy memory consumption. -- `fetchBufferSize: number` - Buffer size to apply to the extent of fetching OverpassAPI. This is to prevent excessive call despite slight map view panning. **USE THE SAME PROJECTION WITH THE LAYER**. -- `maximumResolution: number` - Map view resolution to start fetching OverpassAPI. This is to prevent fetching elements in too big extent. **USE THE SAME PROJECTION WITH THE LAYER** -- `overpassEndpointURL?: string` - OverpassAPI endpoint URL (https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances) -- `overpassQuery: string` - OverpassQL statement for ways to fetch, default to OSM highways. - -### `OSMWaySnap` constructor options - -- `autoFocus?: boolean` - True to automatically fit map view to next candidantes. -- `focusPadding?: number` - Used with autoFocus, specify number to add padding to view fitting. -- `sketchStyle?: StyleLike` - Style of sketch features. -- `source: VectorSource>` - Target source of edition. -- `waySource: VectorSource>` - Source to OSMWays for snapping. -- `wrapX?: boolean` +[HTML Example](./examples/index.html) --- diff --git a/examples/index.html b/examples/index.html index b2c286d..d9d4f2e 100644 --- a/examples/index.html +++ b/examples/index.html @@ -41,7 +41,7 @@
- + diff --git a/package-lock.json b/package-lock.json index 86638d2..9f137d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,21 @@ { - "name": "ol-osm-way-snap", - "version": "0.0.0", + "name": "ol-osmwaysnap", + "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "ol-osm-way-snap", - "version": "0.0.0", + "name": "ol-osmwaysnap", + "version": "0.1.0", "license": "ISC", + "dependencies": { + "ol-osmoverpass": "^0.2.0" + }, "devDependencies": { + "happy-dom": "^12.10.3", "ol": "^8.2.0", "typescript": "^5.3.2", + "vitest": "^0.34.6", "webpack": "^5.74.0", "webpack-cli": "^4.10.0" } @@ -24,6 +29,370 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.7.tgz", + "integrity": "sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz", + "integrity": "sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.7.tgz", + "integrity": "sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz", + "integrity": "sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz", + "integrity": "sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz", + "integrity": "sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz", + "integrity": "sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz", + "integrity": "sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz", + "integrity": "sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz", + "integrity": "sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz", + "integrity": "sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz", + "integrity": "sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz", + "integrity": "sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz", + "integrity": "sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz", + "integrity": "sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz", + "integrity": "sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz", + "integrity": "sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz", + "integrity": "sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz", + "integrity": "sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz", + "integrity": "sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz", + "integrity": "sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz", + "integrity": "sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -88,6 +457,183 @@ "integrity": "sha512-kB+NJ5Br56ZhElKsf0pM7/PQfrDdDVMRz8f0JM6eVOGE+L89z9hwcst9QvWBBnazzuqGTGtPsJNZoQ1JdNiGSQ==", "dev": true }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.5.2.tgz", + "integrity": "sha512-ee7BudTwwrglFYSc3UnqInDDjCLWHKrFmGNi4aK7jlEyg4CyPa1DCMrZfsN1O13YT76UFEqXz2CoN7BCGpUlJw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.5.2.tgz", + "integrity": "sha512-xOuhj9HHtn8128ir8veoQsBbAUBasDbHIBniYTEx02pAmu9EXL+ZjJqngnNEy6ZgZ4h1JwL33GMNu3yJL5Mzow==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.5.2.tgz", + "integrity": "sha512-NTGJWoL8bKyqyWFn9/RzSv4hQ4wTbaAv0lHHRwf4OnpiiP4P8W0jiXbm8Nc5BCXKmWAwuvJY82mcIU2TayC20g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.5.2.tgz", + "integrity": "sha512-hlKqj7bpPvU15sZo4za14u185lpMzdwWLMc9raMqPK4wywt0wR23y1CaVQ4oAFXat3b5/gmRntyfpwWTKl+vvA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.5.2.tgz", + "integrity": "sha512-7ZIZx8c3u+pfI0ohQsft/GywrXez0uR6dUP0JhBuCK3sFO5TfdLn/YApnVkvPxuTv3+YKPIZend9Mt7Cz6sS3Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.5.2.tgz", + "integrity": "sha512-7Pk/5mO11JW/cH+a8lL/i0ZxmRGrbpYqN0VwO2DHhU+SJWWOH2zE1RAcPaj8KqiwC8DCDIJOSxjV9+9lLb6aeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.5.2.tgz", + "integrity": "sha512-KrRnuG5phJx756e62wxvWH2e+TK84MP2IVuPwfge+GBvWqIUfVzFRn09TKruuQBXzZp52Vyma7FjMDkwlA9xpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.5.2.tgz", + "integrity": "sha512-My+53GasPa2D2tU5dXiyHYwrELAUouSfkNlZ3bUKpI7btaztO5vpALEs3mvFjM7aKTvEbc7GQckuXeXIDKQ0fg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.5.2.tgz", + "integrity": "sha512-/f0Q6Sc+Vw54Ws6N8fxaEe4R7at3b8pFyv+O/F2VaQ4hODUJcRUcCBJh6zuqtgQQt7w845VTkGLFgWZkP3tUoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.5.2.tgz", + "integrity": "sha512-NCKuuZWLht6zj7s6EIFef4BxCRX1GMr83S2W4HPCA0RnJ4iHE4FS1695q6Ewoa6A9nFjJe1//yUu0kgBU07Edw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.5.2.tgz", + "integrity": "sha512-J5zL3riR4AOyU/J3M/i4k/zZ8eP1yT+nTmAKztCXJtnI36jYH0eepvob22mAQ/kLwfsK2TB6dbyVY1F8c/0H5A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.5.2.tgz", + "integrity": "sha512-pL0RXRHuuGLhvs7ayX/SAHph1hrDPXOM5anyYUQXWJEENxw3nfHkzv8FfVlEVcLyKPAEgDRkd6RKZq2SMqS/yg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", + "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.7", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", @@ -129,6 +675,89 @@ "undici-types": "~5.26.4" } }, + "node_modules/@vitest/expect": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", + "dev": true, + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -344,6 +973,15 @@ "acorn": "^8" } }, + "node_modules/acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -369,6 +1007,27 @@ "ajv": "^6.9.1" } }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/browserslist": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", @@ -407,6 +1066,15 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001563", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", @@ -427,6 +1095,36 @@ } ] }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -507,6 +1205,50 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/earcut": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", @@ -529,7 +1271,19 @@ "tapable": "^2.2.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/envinfo": { @@ -550,6 +1304,43 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, + "node_modules/esbuild": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.7.tgz", + "integrity": "sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.7", + "@esbuild/android-arm64": "0.19.7", + "@esbuild/android-x64": "0.19.7", + "@esbuild/darwin-arm64": "0.19.7", + "@esbuild/darwin-x64": "0.19.7", + "@esbuild/freebsd-arm64": "0.19.7", + "@esbuild/freebsd-x64": "0.19.7", + "@esbuild/linux-arm": "0.19.7", + "@esbuild/linux-arm64": "0.19.7", + "@esbuild/linux-ia32": "0.19.7", + "@esbuild/linux-loong64": "0.19.7", + "@esbuild/linux-mips64el": "0.19.7", + "@esbuild/linux-ppc64": "0.19.7", + "@esbuild/linux-riscv64": "0.19.7", + "@esbuild/linux-s390x": "0.19.7", + "@esbuild/linux-x64": "0.19.7", + "@esbuild/netbsd-x64": "0.19.7", + "@esbuild/openbsd-x64": "0.19.7", + "@esbuild/sunos-x64": "0.19.7", + "@esbuild/win32-arm64": "0.19.7", + "@esbuild/win32-ia32": "0.19.7", + "@esbuild/win32-x64": "0.19.7" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -654,6 +1445,20 @@ "flat": "cli.js" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -682,6 +1487,15 @@ "node": ">=10.19" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -694,6 +1508,20 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/happy-dom": { + "version": "12.10.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-12.10.3.tgz", + "integrity": "sha512-JzUXOh0wdNGY54oKng5hliuBkq/+aT1V3YpTM+lrN/GoLQTANZsMaIvmHiHe612rauHvPJnDZkZ+5GZR++1Abg==", + "dev": true, + "dependencies": { + "css.escape": "^1.5.1", + "entities": "^4.5.0", + "iconv-lite": "^0.6.3", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -715,6 +1543,18 @@ "node": ">= 0.4" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -828,6 +1668,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -852,6 +1698,18 @@ "node": ">=6.11.5" } }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -864,6 +1722,27 @@ "node": ">=8" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -891,6 +1770,42 @@ "node": ">= 0.6" } }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -921,6 +1836,11 @@ "url": "https://opencollective.com/openlayers" } }, + "node_modules/ol-osmoverpass": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ol-osmoverpass/-/ol-osmoverpass-0.2.0.tgz", + "integrity": "sha512-WkmEhgMgLyo2M2oScSf/e3/hrhE7d9CM/kwiivUPBGsSJBKjGurJPZVUKWfT7qVI4cE8uT+oTKcxwR/bIJWRyA==" + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -993,6 +1913,21 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pbf": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", @@ -1024,6 +1959,59 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -1075,6 +2063,12 @@ "quickselect": "^2.0.0" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -1134,6 +2128,34 @@ "protocol-buffers-schema": "^3.3.1" } }, + "node_modules/rollup": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.5.2.tgz", + "integrity": "sha512-CRK1uoROBfkcqrZKyaFcqCcZWNsvJ6yVYZkqTlRocZhO2s5yER6Z3f/QaYtO8RGyloPnmhwgzuPQpNGeK210xQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.5.2", + "@rollup/rollup-android-arm64": "4.5.2", + "@rollup/rollup-darwin-arm64": "4.5.2", + "@rollup/rollup-darwin-x64": "4.5.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.5.2", + "@rollup/rollup-linux-arm64-gnu": "4.5.2", + "@rollup/rollup-linux-arm64-musl": "4.5.2", + "@rollup/rollup-linux-x64-gnu": "4.5.2", + "@rollup/rollup-linux-x64-musl": "4.5.2", + "@rollup/rollup-win32-arm64-msvc": "4.5.2", + "@rollup/rollup-win32-ia32-msvc": "4.5.2", + "@rollup/rollup-win32-x64-msvc": "4.5.2", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1154,6 +2176,12 @@ } ] }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -1214,6 +2242,12 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1223,6 +2257,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -1233,6 +2276,30 @@ "source-map": "^0.6.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.5.0.tgz", + "integrity": "sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==", + "dev": true + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -1321,6 +2388,39 @@ } } }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/typescript": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", @@ -1334,6 +2434,12 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -1379,6 +2485,161 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.2.tgz", + "integrity": "sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.31", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -1398,6 +2659,15 @@ "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", "dev": true }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/webpack": { "version": "5.89.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", @@ -1524,6 +2794,27 @@ "node": ">=10.13.0" } }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1539,6 +2830,22 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", @@ -1551,6 +2858,18 @@ "integrity": "sha512-bWB489+RQQclC7A9OW8e5BzbT8Tu//jtAOvkYwewFr+Q9T9KDGvfzC1lp0pYPEQPEoPQLDkmxkepSC/2gIAZGw==", "dev": true }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zstddec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz", @@ -1565,6 +2884,169 @@ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, + "@esbuild/android-arm": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.7.tgz", + "integrity": "sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz", + "integrity": "sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.7.tgz", + "integrity": "sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz", + "integrity": "sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz", + "integrity": "sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz", + "integrity": "sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz", + "integrity": "sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz", + "integrity": "sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz", + "integrity": "sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz", + "integrity": "sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz", + "integrity": "sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz", + "integrity": "sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz", + "integrity": "sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz", + "integrity": "sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz", + "integrity": "sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz", + "integrity": "sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz", + "integrity": "sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz", + "integrity": "sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz", + "integrity": "sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz", + "integrity": "sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz", + "integrity": "sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz", + "integrity": "sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==", + "dev": true, + "optional": true + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, "@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1620,6 +3102,111 @@ "integrity": "sha512-kB+NJ5Br56ZhElKsf0pM7/PQfrDdDVMRz8f0JM6eVOGE+L89z9hwcst9QvWBBnazzuqGTGtPsJNZoQ1JdNiGSQ==", "dev": true }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.5.2.tgz", + "integrity": "sha512-ee7BudTwwrglFYSc3UnqInDDjCLWHKrFmGNi4aK7jlEyg4CyPa1DCMrZfsN1O13YT76UFEqXz2CoN7BCGpUlJw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.5.2.tgz", + "integrity": "sha512-xOuhj9HHtn8128ir8veoQsBbAUBasDbHIBniYTEx02pAmu9EXL+ZjJqngnNEy6ZgZ4h1JwL33GMNu3yJL5Mzow==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.5.2.tgz", + "integrity": "sha512-NTGJWoL8bKyqyWFn9/RzSv4hQ4wTbaAv0lHHRwf4OnpiiP4P8W0jiXbm8Nc5BCXKmWAwuvJY82mcIU2TayC20g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.5.2.tgz", + "integrity": "sha512-hlKqj7bpPvU15sZo4za14u185lpMzdwWLMc9raMqPK4wywt0wR23y1CaVQ4oAFXat3b5/gmRntyfpwWTKl+vvA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.5.2.tgz", + "integrity": "sha512-7ZIZx8c3u+pfI0ohQsft/GywrXez0uR6dUP0JhBuCK3sFO5TfdLn/YApnVkvPxuTv3+YKPIZend9Mt7Cz6sS3Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.5.2.tgz", + "integrity": "sha512-7Pk/5mO11JW/cH+a8lL/i0ZxmRGrbpYqN0VwO2DHhU+SJWWOH2zE1RAcPaj8KqiwC8DCDIJOSxjV9+9lLb6aeA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.5.2.tgz", + "integrity": "sha512-KrRnuG5phJx756e62wxvWH2e+TK84MP2IVuPwfge+GBvWqIUfVzFRn09TKruuQBXzZp52Vyma7FjMDkwlA9xpg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.5.2.tgz", + "integrity": "sha512-My+53GasPa2D2tU5dXiyHYwrELAUouSfkNlZ3bUKpI7btaztO5vpALEs3mvFjM7aKTvEbc7GQckuXeXIDKQ0fg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.5.2.tgz", + "integrity": "sha512-/f0Q6Sc+Vw54Ws6N8fxaEe4R7at3b8pFyv+O/F2VaQ4hODUJcRUcCBJh6zuqtgQQt7w845VTkGLFgWZkP3tUoQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.5.2.tgz", + "integrity": "sha512-NCKuuZWLht6zj7s6EIFef4BxCRX1GMr83S2W4HPCA0RnJ4iHE4FS1695q6Ewoa6A9nFjJe1//yUu0kgBU07Edw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.5.2.tgz", + "integrity": "sha512-J5zL3riR4AOyU/J3M/i4k/zZ8eP1yT+nTmAKztCXJtnI36jYH0eepvob22mAQ/kLwfsK2TB6dbyVY1F8c/0H5A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.5.2.tgz", + "integrity": "sha512-pL0RXRHuuGLhvs7ayX/SAHph1hrDPXOM5anyYUQXWJEENxw3nfHkzv8FfVlEVcLyKPAEgDRkd6RKZq2SMqS/yg==", + "dev": true, + "optional": true + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, + "@types/chai-subset": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", + "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, "@types/eslint": { "version": "8.44.7", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", @@ -1661,6 +3248,70 @@ "undici-types": "~5.26.4" } }, + "@vitest/expect": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", + "dev": true, + "requires": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + } + }, + "@vitest/runner": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", + "dev": true, + "requires": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "dependencies": { + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + } + } + }, + "@vitest/snapshot": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", + "dev": true, + "requires": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + } + }, + "@vitest/spy": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", + "dev": true, + "requires": { + "tinyspy": "^2.1.1" + } + }, + "@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dev": true, + "requires": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + } + }, "@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -1855,6 +3506,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1874,6 +3531,18 @@ "dev": true, "requires": {} }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "browserslist": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", @@ -1892,12 +3561,42 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, "caniuse-lite": { "version": "1.0.30001563", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", "dev": true }, + "chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + } + }, + "check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -1969,6 +3668,36 @@ "which": "^2.0.1" } }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, "earcut": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", @@ -1991,6 +3720,12 @@ "tapable": "^2.2.0" } }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, "envinfo": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", @@ -2003,6 +3738,36 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, + "esbuild": { + "version": "0.19.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.7.tgz", + "integrity": "sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.19.7", + "@esbuild/android-arm64": "0.19.7", + "@esbuild/android-x64": "0.19.7", + "@esbuild/darwin-arm64": "0.19.7", + "@esbuild/darwin-x64": "0.19.7", + "@esbuild/freebsd-arm64": "0.19.7", + "@esbuild/freebsd-x64": "0.19.7", + "@esbuild/linux-arm": "0.19.7", + "@esbuild/linux-arm64": "0.19.7", + "@esbuild/linux-ia32": "0.19.7", + "@esbuild/linux-loong64": "0.19.7", + "@esbuild/linux-mips64el": "0.19.7", + "@esbuild/linux-ppc64": "0.19.7", + "@esbuild/linux-riscv64": "0.19.7", + "@esbuild/linux-s390x": "0.19.7", + "@esbuild/linux-x64": "0.19.7", + "@esbuild/netbsd-x64": "0.19.7", + "@esbuild/openbsd-x64": "0.19.7", + "@esbuild/sunos-x64": "0.19.7", + "@esbuild/win32-arm64": "0.19.7", + "@esbuild/win32-ia32": "0.19.7", + "@esbuild/win32-x64": "0.19.7" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2082,6 +3847,13 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2104,6 +3876,12 @@ "zstddec": "^0.1.0" } }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -2116,6 +3894,20 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "happy-dom": { + "version": "12.10.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-12.10.3.tgz", + "integrity": "sha512-JzUXOh0wdNGY54oKng5hliuBkq/+aT1V3YpTM+lrN/GoLQTANZsMaIvmHiHe612rauHvPJnDZkZ+5GZR++1Abg==", + "dev": true, + "requires": { + "css.escape": "^1.5.1", + "entities": "^4.5.0", + "iconv-lite": "^0.6.3", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2131,6 +3923,15 @@ "function-bind": "^1.1.2" } }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2206,6 +4007,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2224,6 +4031,12 @@ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, + "local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -2233,6 +4046,24 @@ "p-locate": "^4.1.0" } }, + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.1" + } + }, + "magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -2254,6 +4085,30 @@ "mime-db": "1.52.0" } }, + "mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "requires": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -2280,6 +4135,11 @@ "rbush": "^3.0.1" } }, + "ol-osmoverpass": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ol-osmoverpass/-/ol-osmoverpass-0.2.0.tgz", + "integrity": "sha512-WkmEhgMgLyo2M2oScSf/e3/hrhE7d9CM/kwiivUPBGsSJBKjGurJPZVUKWfT7qVI4cE8uT+oTKcxwR/bIJWRyA==" + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -2334,6 +4194,18 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "pbf": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", @@ -2359,6 +4231,39 @@ "find-up": "^4.0.0" } }, + "pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, "protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -2401,6 +4306,12 @@ "quickselect": "^2.0.0" } }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -2445,12 +4356,39 @@ "protocol-buffers-schema": "^3.3.1" } }, + "rollup": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.5.2.tgz", + "integrity": "sha512-CRK1uoROBfkcqrZKyaFcqCcZWNsvJ6yVYZkqTlRocZhO2s5yER6Z3f/QaYtO8RGyloPnmhwgzuPQpNGeK210xQ==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.5.2", + "@rollup/rollup-android-arm64": "4.5.2", + "@rollup/rollup-darwin-arm64": "4.5.2", + "@rollup/rollup-darwin-x64": "4.5.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.5.2", + "@rollup/rollup-linux-arm64-gnu": "4.5.2", + "@rollup/rollup-linux-arm64-musl": "4.5.2", + "@rollup/rollup-linux-x64-gnu": "4.5.2", + "@rollup/rollup-linux-x64-musl": "4.5.2", + "@rollup/rollup-win32-arm64-msvc": "4.5.2", + "@rollup/rollup-win32-ia32-msvc": "4.5.2", + "@rollup/rollup-win32-x64-msvc": "4.5.2", + "fsevents": "~2.3.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -2495,12 +4433,24 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -2511,6 +4461,27 @@ "source-map": "^0.6.0" } }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "std-env": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.5.0.tgz", + "integrity": "sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==", + "dev": true + }, + "strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "requires": { + "acorn": "^8.10.0" + } + }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -2557,12 +4528,42 @@ "terser": "^5.16.8" } }, + "tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "dev": true + }, + "tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "typescript": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true }, + "ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -2588,6 +4589,64 @@ "punycode": "^2.1.0" } }, + "vite": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.2.tgz", + "integrity": "sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==", + "dev": true, + "requires": { + "esbuild": "^0.19.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.31", + "rollup": "^4.2.0" + } + }, + "vite-node": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, + "vitest": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", + "dev": true, + "requires": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + } + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -2604,6 +4663,12 @@ "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", "dev": true }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, "webpack": { "version": "5.89.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", @@ -2681,6 +4746,21 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2690,6 +4770,16 @@ "isexe": "^2.0.0" } }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", @@ -2702,6 +4792,12 @@ "integrity": "sha512-bWB489+RQQclC7A9OW8e5BzbT8Tu//jtAOvkYwewFr+Q9T9KDGvfzC1lp0pYPEQPEoPQLDkmxkepSC/2gIAZGw==", "dev": true }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + }, "zstddec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz", diff --git a/package.json b/package.json index 8d43796..38becb4 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,20 @@ { "name": "ol-osmwaysnap", - "version": "0.0.3", + "type": "module", + "version": "0.1.0", "description": "OpenLayers Interaction Extension for Snapping Ways from OSM using Overpass API", "keywords": [ - "osm", "openstreetmap", "openlayers", "gis", "map", "road-snapping" + "osm", + "openstreetmap", + "openlayers", + "gis", + "map", + "road-snapping" ], "main": "dist/index.js", "scripts": { - "build": "npx tsc && npx webpack" + "build": "npx tsc && npx webpack", + "test": "npm run build && npx vitest run --config ./vitest.config.ts" }, "repository": { "type": "git", @@ -26,9 +33,14 @@ }, "homepage": "https://github.com/ponlawat-w/ol-osmwaysnap#readme", "devDependencies": { + "happy-dom": "^12.10.3", "ol": "^8.2.0", "typescript": "^5.3.2", + "vitest": "^0.34.6", "webpack": "^5.74.0", "webpack-cli": "^4.10.0" + }, + "dependencies": { + "ol-osmoverpass": "^0.2.0" } } diff --git a/src/index.ts b/src/index.ts index 32009bb..9291f2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export { default as OSMWaySnap } from './interaction'; -export { default as OSMWaySource } from './source'; +export { default as OSMWaySnap, type OSMWaySnapOptions } from './interaction'; +export { default as LineStringUtils } from './line-string-utils'; diff --git a/src/interaction.ts b/src/interaction.ts index 94fa502..d5f5d2c 100644 --- a/src/interaction.ts +++ b/src/interaction.ts @@ -1,76 +1,53 @@ -import Map from 'ol/Map'; +import { Map } from 'ol'; +import { boundingExtent } from 'ol/extent'; import Feature from 'ol/Feature'; -import VectorLayer from 'ol/layer/Vector'; -import Point from 'ol/geom/Point'; -import LineString from 'ol/geom/LineString'; -import MultiPoint from 'ol/geom/MultiPoint'; -import VectorSource from 'ol/source/Vector'; -import PointerInteraction from 'ol/interaction/Pointer'; -import Circle from 'ol/style/Circle'; -import Fill from 'ol/style/Fill'; -import Stroke from 'ol/style/Stroke'; +import { Point, LineString } from 'ol/geom'; +import { Snap, Pointer as PointerInteraction } from 'ol/interaction'; +import { Vector as VectorLayer } from 'ol/layer'; +import { Vector as VectorSource } from 'ol/source'; +import { Circle, Fill, Stroke } from 'ol/style'; import Style, { createEditingStyle } from 'ol/style/Style'; -import LineStringUtils from './utils/line-string'; +import { OSMOverpassWaySource, type OSMOverpassSourceOptions } from 'ol-osmoverpass'; +import LineStringUtils from './line-string-utils'; import type { Coordinate } from 'ol/coordinate'; import type { StyleLike } from 'ol/style/Style'; import type { MapBrowserEvent } from 'ol'; /** Options for OSMWaySnap interaction */ -export type OSMWaySnapOptions = { - /** True to automatically fit map view to next candidantes. */ +type SnapOptions = { + /** True to automatically fit map view to next candidantes. (default: true) */ autoFocus?: boolean, - /** Used with autoFocus, specify number to add padding to view fitting. */ + /** Used with autoFocus, specify number to add padding to view fitting. (default: 50 !PROJECTION SENSITIVE!) */ focusPadding?: number, - /** Style of sketch features */ + /** Style of sketch features (default is predefined, overwrite if necessary) */ sketchStyle?: StyleLike, /** Target source of edition */ source: VectorSource>, - /** Source to OSMWays for snapping */ + /** Ways source for snapping (default to a new instance of OSMOverpassWaySource) */ waySource: VectorSource>, + /** Create a new way layer from way source (if provided) and add to map (default: true) */ + createAndAddWayLayer?: boolean, + /** WrapX */ wrapX?: boolean }; -const DEFAULT_SKETCH_STYLE: StyleLike = feature => { - if (feature.getProperties().candidate) { - return feature.getGeometry()!.getType() === 'Point' ? - new Style({ - image: new Circle({ - fill: new Fill({ color: '#00ffff' }), - stroke: new Stroke({ color: '#ff0000', width: 1 }), - radius: 2 - }) - }) - : new Style({ - stroke: new Stroke({ - width: 2, - color: 'rgba(245,128,2,0.5)' - }) - }); - } else if (feature.getGeometry()!.getType() === 'Point') { - return createEditingStyle()['Point']; - } - return new Style({ - stroke: new Stroke({ - width: 4, - color: '#02c0f5' - }) - }); -}; +type SnapOptionsOSMOverpassWaySource = Omit & { + waySource?: undefined +} & Partial; + +export type OSMWaySnapOptions = SnapOptions | SnapOptionsOSMOverpassWaySource; /** * Interaction for snapping linestring to way elements from OSM * This is designed to use with Snap interaction. */ export default class OSMWaySnap extends PointerInteraction { - /** Options */ - private options: OSMWaySnapOptions; - /** Coordinates of active linestring */ private coordinates: Coordinate[] = []; /** Feature that is being edited */ @@ -87,23 +64,57 @@ export default class OSMWaySnap extends PointerInteraction { /** Candidate lines for selection on map */ private candidateLines: Feature[] = []; + /** True to automatically fit map view to next candidantes. */ + private autoFocus: boolean; + /** Used with autoFocus, specify number to add padding to view fitting. */ + private focusPadding: number; + /** Target source of edition */ + private source: VectorSource>; + /** Ways source for snapping */ + private waySource: VectorSource>; + /** Create a new way layer from way source (if provided) and add to map */ + private createAndAddWayLayer: boolean; + /** WrapX */ + private wrapX: boolean|undefined = undefined; + + /** Map */ + private map: Map|undefined = undefined; + + /** Layer of snapping ways */ + private wayLayer: VectorLayer>>|undefined = undefined; + /** Snap interaction */ + private snapInteraction: Snap; + /** * Constructor * @param options Options */ public constructor(options: OSMWaySnapOptions) { super(); - this.options = { - ...options, - autoFocus: options.autoFocus === undefined ? true : options.autoFocus, - focusPadding: options.focusPadding ?? 50, - }; + this.autoFocus = options.autoFocus === undefined ? true: options.autoFocus; + this.focusPadding = options.focusPadding ?? 50; + this.source = options.source; + this.waySource = options.waySource ?? new OSMOverpassWaySource({ + cachedFeaturesCount: options.cachedFeaturesCount ?? undefined, + fetchBufferSize: options.fetchBufferSize ?? undefined, + maximumResolution: options.maximumResolution ?? undefined, + overpassQuery: options.overpassQuery ?? '(way["highway"];>;);', + overpassEndpointURL: options.overpassEndpointURL ?? undefined + }); + this.createAndAddWayLayer = options.createAndAddWayLayer === undefined ? true : options.createAndAddWayLayer; + this.wrapX = options.wrapX; + this.snapInteraction = new Snap({ source: this.waySource }); + + if (this.createAndAddWayLayer) { + this.wayLayer = new VectorLayer({ source: this.waySource, style: OSMOverpassWaySource.getDefaultStyle() }); + } this.overlayLayer = new VectorLayer({ - source: new VectorSource({ useSpatialIndex: false, wrapX: this.options.wrapX }), + source: new VectorSource({ useSpatialIndex: false, wrapX: this.wrapX }), updateWhileInteracting: true, - style: this.options.sketchStyle ?? DEFAULT_SKETCH_STYLE + style: options.sketchStyle ?? OSMWaySnap.getDefaultSketchStyle() }); - this.addChangeListener('active', this.updateState); + + this.addChangeListener('active', this.activeChanged.bind(this)); } /** @@ -113,8 +124,51 @@ export default class OSMWaySnap extends PointerInteraction { * @param map Map. */ public setMap(map: Map|null) { + if (this.map) { + this.waySource.un('featuresloadend', this.waysFeaturesLoadEnded.bind(this)); + this.wayLayer && this.map.removeLayer(this.wayLayer); + this.map.removeLayer(this.overlayLayer); + this.map.removeInteraction(this.snapInteraction); + } super.setMap(map); - this.updateState(); + this.map = map ?? undefined; + if (this.map) { + this.waySource.on('featuresloadend', this.waysFeaturesLoadEnded.bind(this)); + this.wayLayer && this.map.getAllLayers().indexOf(this.wayLayer) < 0 && this.map.addLayer(this.wayLayer); + this.map.addLayer(this.overlayLayer); + this.map.getInteractions().getArray().indexOf(this.snapInteraction) < 0 && this.map.addInteraction(this.snapInteraction); + } + } + + /** + * Handler on active property changed. + */ + public activeChanged() { + this.setMap(this.getActive() ? (this.map ?? null) : null); + } + + /** + * Get way vector source + */ + public getWaySource(): VectorSource> { + return this.waySource; + } + + /** + * Get active feature + */ + public getActiveFeature(): Feature|undefined { + return this.activeFeature; + } + + /** Called when the editing is finished, clear all the sketching and candidates. */ + public finishEditing() { + this.activeFeature = undefined; + this.coordinates = []; + this.removeSketchPoint(); + this.removeSketchLine(); + this.removeCandidateLines(); + this.removeCandidatePoints(); } /** @@ -164,8 +218,8 @@ export default class OSMWaySnap extends PointerInteraction { this.activeFeature = new Feature(new LineString(this.coordinates)); } else { this.activeFeature.getGeometry()!.setCoordinates(this.coordinates); - if (this.coordinates.length > 1 && !this.options.source.hasFeature(this.activeFeature)) { - this.options.source.addFeature(this.activeFeature); + if (this.coordinates.length > 1 && !this.source.hasFeature(this.activeFeature)) { + this.source.addFeature(this.activeFeature); } } this.calculateCandidates(); @@ -175,39 +229,40 @@ export default class OSMWaySnap extends PointerInteraction { * Given a candidate linestring, return coordinate set from the tip of the active feature following that line until the cursor coordinate, * or return an empty coordinate set if the candidate is not valid according to cursor position. * @param mouseCoor Coordinate of user cursor - * @param lineString A candidate way linestring + * @param candidate A candidate way linestring * @returns Coordinates set of sketch line */ - private getSketchLineCoordinates(mouseCoor: Coordinate, lineString: LineString): Coordinate[] { - const lastFeatureCoors = this.activeFeature!.getGeometry()!.getLastCoordinate(); - if (!lineString.getCoordinates().some(c => c[0] === lastFeatureCoors[0] && c[1] === lastFeatureCoors[1])) { - lineString = LineStringUtils.split(lineString, lastFeatureCoors); + private getSketchLineCoordinates(mouseCoor: Coordinate, candidate: LineString): Coordinate[] { + const candidateIsLoop: boolean = candidate.getFirstCoordinate()[0] === candidate.getLastCoordinate()[0] + && candidate.getFirstCoordinate()[1] === candidate.getLastCoordinate()[1]; + + const activeFeatureLastCoor = this.activeFeature!.getGeometry()!.getLastCoordinate(); + + if (candidateIsLoop && activeFeatureLastCoor[0] === mouseCoor[0] && activeFeatureLastCoor[1] === mouseCoor[1]) { + return []; } - if (!lineString.getCoordinates().some(c => c[0] === mouseCoor[0] && c[1] === mouseCoor[1])) { - lineString = LineStringUtils.split(lineString, mouseCoor); + + if (!candidate.getCoordinates().some(c => c[0] === activeFeatureLastCoor[0] && c[1] === activeFeatureLastCoor[1])) { + candidate = LineStringUtils.split(candidate, activeFeatureLastCoor); } + if (!candidate.getCoordinates().some(c => c[0] === mouseCoor[0] && c[1] === mouseCoor[1])) { + candidate = LineStringUtils.split(candidate, mouseCoor); + } + + const startIdx = candidate.getCoordinates().findIndex(c => c[0] === activeFeatureLastCoor[0] && c[1] === activeFeatureLastCoor[1]); + if (startIdx < 0) return []; + const endIdx = candidate.getCoordinates().findIndex(c => c[0] === mouseCoor[0] && c[1] === mouseCoor[1]); - const lastFeatureCoorsIdx = lineString.getCoordinates() - .findIndex(c => c[0] === lastFeatureCoors[0] && c[1] === lastFeatureCoors[1]); - if (lastFeatureCoorsIdx < 0) return []; - - const mouseCoorsIdx = ( - lineString.getFirstCoordinate()[0] === lineString.getLastCoordinate()[0] - && lineString.getLastCoordinate()[1] === lineString.getLastCoordinate()[1] - && lineString.getFirstCoordinate()[0] === mouseCoor[0] - && lineString.getFirstCoordinate()[1] === mouseCoor[1] - ) ? (lastFeatureCoorsIdx + 1 > lineString.getCoordinates().length / 2 ? lineString.getCoordinates().length - 1 : 0) - : lineString.getCoordinates().findIndex(c => c[0] === mouseCoor[0] && c[1] === mouseCoor[1]);; - if (mouseCoorsIdx < 0) return []; - if (mouseCoorsIdx === lastFeatureCoorsIdx) return []; - - if (lastFeatureCoorsIdx < mouseCoorsIdx) { - return lineString.getCoordinates() - .slice(lastFeatureCoorsIdx, mouseCoorsIdx + 1) + if (candidateIsLoop) return LineStringUtils.getShorterPathOnLoop(candidate, startIdx, endIdx); + if (endIdx < 0) return []; + if (endIdx === startIdx) return []; + if (startIdx < endIdx) { + return candidate.getCoordinates() + .slice(startIdx, endIdx + 1) .map(c => [...c]); } - return lineString.getCoordinates() - .slice(mouseCoorsIdx, lastFeatureCoorsIdx + 1) + return candidate.getCoordinates() + .slice(endIdx, startIdx + 1) .reverse() .map(c => [...c]); } @@ -238,8 +293,8 @@ export default class OSMWaySnap extends PointerInteraction { || !this.activeFeature!.getGeometry()!.intersectsCoordinate(p) ); if (fitCoordinates.length > 1) { - map.getView().fit(new MultiPoint(fitCoordinates), { - padding: Array(4).fill(this.options.focusPadding) + map.getView().fit(boundingExtent(fitCoordinates), { + padding: Array(4).fill(this.focusPadding) }); } } @@ -252,16 +307,17 @@ export default class OSMWaySnap extends PointerInteraction { */ private calculateCandidates(fit: boolean = true) { const lastFeatureCoors = this.activeFeature!.getGeometry()!.getLastCoordinate(); - this.candidateLines = this.options.waySource.getFeatures().filter( + this.candidateLines = this.waySource.getFeatures().filter( feature => feature.getGeometry()!.containsXY(lastFeatureCoors[0], lastFeatureCoors[1]) - ).map(f => { - f = new Feature(f.getGeometry()); + ).map(c => { + const f = new Feature(c.getGeometry()); f.setProperties({ candidate: true }); + f.setId(c.getId()); return f; }); this.candidatePoints = this.candidateLines.map(l => l.getGeometry()!.getCoordinates()).flat() .map(c => new Feature({ candidate: true, geometry: new Point(c) })); - if (fit && this.options.autoFocus) { + if (fit && this.autoFocus) { this.fitCandidatesToMapView(); } this.createOrUpdateSketchLine(lastFeatureCoors); @@ -288,23 +344,25 @@ export default class OSMWaySnap extends PointerInteraction { * @param coordinate Coordinate */ private createOrUpdateSketchLine(coordinate: Coordinate) { - const candidates = new VectorSource({ - features: this.candidateLines.filter( - feature => feature.getGeometry()!.containsXY(coordinate[0], coordinate[1]) - ) - }); - const closestCandidate = candidates.getClosestFeatureToCoordinate(coordinate); - const lineCoors = closestCandidate ? - this.getSketchLineCoordinates(coordinate, closestCandidate.getGeometry()!) - : [this.activeFeature!.getGeometry()!.getLastCoordinate(), coordinate]; - if (!lineCoors.length) { + const candidates = this.candidateLines.filter( + feature => feature.getGeometry()!.containsXY(coordinate[0], coordinate[1]) + ); + let sketchCoors: Coordinate[] = candidates.length ? [] : [this.activeFeature!.getGeometry()!.getLastCoordinate(), coordinate]; + for (const candidate of candidates) { + const candidateSketchCoors = this.getSketchLineCoordinates(coordinate, candidate.getGeometry()!); + if (candidateSketchCoors.length) { + sketchCoors = candidateSketchCoors; + break; + } + } + if (!sketchCoors.length) { return this.removeSketchLine(); } if (this.sketchLine) { - this.sketchLine.getGeometry()!.setCoordinates(lineCoors); + this.sketchLine.getGeometry()!.setCoordinates(sketchCoors); } else { - this.sketchLine = new Feature(new LineString(lineCoors)); + this.sketchLine = new Feature(new LineString(sketchCoors)); } this.updateSketchLayer(); } @@ -335,16 +393,6 @@ export default class OSMWaySnap extends PointerInteraction { this.updateSketchLayer(); } - /** Called when the editing is finished, clear all the sketching and candidates. */ - private finishEditing() { - this.activeFeature = undefined; - this.coordinates = []; - this.removeSketchPoint(); - this.removeSketchLine(); - this.removeCandidateLines(); - this.removeCandidatePoints(); - } - /** * Callback function when snapping OSM way features are loaded. */ @@ -353,17 +401,32 @@ export default class OSMWaySnap extends PointerInteraction { this.calculateCandidates(false); } - /** - * When the interaction state is changed (assigned or unassigned to maps). - */ - private updateState() { - const map = this.getMap(); - const active = this.getActive(); - this.overlayLayer.setMap(active ? map : null); - if (map) { - this.options.waySource.on('featuresloadend', this.waysFeaturesLoadEnded.bind(this)); - } else { - this.options.waySource.un('featuresloadend', this.waysFeaturesLoadEnded.bind(this)); - } + private static getDefaultSketchStyle(): StyleLike { + return feature => { + if (feature.getProperties().candidate) { + return feature.getGeometry()!.getType() === 'Point' ? + new Style({ + image: new Circle({ + fill: new Fill({ color: '#00ffff' }), + stroke: new Stroke({ color: '#ff0000', width: 1 }), + radius: 2 + }) + }) + : new Style({ + stroke: new Stroke({ + width: 2, + color: 'rgba(245,128,2,0.5)' + }) + }); + } else if (feature.getGeometry()!.getType() === 'Point') { + return createEditingStyle()['Point']; + } + return new Style({ + stroke: new Stroke({ + width: 4, + color: '#02c0f5' + }) + }); + }; } }; diff --git a/src/line-string-utils.ts b/src/line-string-utils.ts new file mode 100644 index 0000000..92a3932 --- /dev/null +++ b/src/line-string-utils.ts @@ -0,0 +1,86 @@ +import { LineString } from 'ol/geom'; +import type { Coordinate } from 'ol/coordinate'; + +/** + * Static class for LineString calculation utilities + */ +export default class LineStringUtils { + private constructor() {} + + /** + * Given a linestring and a coordinate, if the coordinate lies on the linestring then return the index of coordinates that contains the coordinate, + * otherwise -1 when the coordinate does not lie on the linestring + * @param lineString LineString geometry + * @param coordinate Coordinate to split + * @returns index of coordinate on linestring, before which contains the given coordinate, -1 if the coordinate does not lie on the linestring + */ + private static getSplitIndex(lineString: LineString, coordinate: Coordinate): number { + const coordinates = lineString.getCoordinates(); + if (coordinates[0][0] === coordinate[0] && coordinates[0][1] === coordinate[1]) return -1; + for (let i = 1; i < coordinates.length; i++) { + if (coordinates[i][0] === coordinate[0] && coordinates[i][1] === coordinate[1]) return -1; + if (new LineString([coordinates[i - 1], coordinates[i]]).intersectsCoordinate(coordinate)) return i; + } + return -1; + } + + /** + * Given a loop linestring with a number of coordinates of `length`, travel with increasing index from `fromIdx` until `toIdx` inclusively, + * return the array of indices visited in the travel. + * Example: + * length = 5, fromIdx = 1, toIdx = 3, return [1, 2, 3]; + * length = 6, fromIdx = 4, toIdx = 3, return [4, 5, 0, 1, 2, 3]; + * @param length Coordinates count of a loop linestring + * @param fromIdx Index to travel from + * @param toIdx Index to travel to (inclusive) + * @returns Array of indices traveled + */ + private static getLoopTravelIndices(length: number, fromIdx: number, toIdx: number): number[] { + if (fromIdx < toIdx) return Array(toIdx - fromIdx + 1).fill(-1).map((_, i) => fromIdx + i); + return [ + ...Array(length - fromIdx).fill(-1).map((_, i) => fromIdx + i), + ...Array(toIdx).fill(-1).map((_, i) => i + 1) + ]; + } + + /** + * Given a linestring and a coordinate that lies on the linestring but not in the vertex coordinates set of the linestring, + * split the linestring to include that coordinate. + * If the coordinate does not lie on the linestring, then return the original input as is. + * @param lineString LineString geometry + * @param coordinate Coordinate to split + * @returns New linestring that contains the given spliting coordinate + */ + public static split(lineString: LineString, coordinate: Coordinate): LineString { + const idx = LineStringUtils.getSplitIndex(lineString, coordinate); + if (idx < 0) return lineString; + + const coordinates = lineString.getCoordinates(); + return new LineString([ + ...coordinates.slice(0, idx), + coordinate, + ...coordinates.slice(idx) + ]); + } + + /** + * Given a loop `lineString`, return partial coordinates of the loop traveling from coordinate at index `fromIdx` to `toIdx` inclusively, + * in either original or reverted vertex direction depending on which one gives a shorter distance. + * @param lineString Loop linestring (with the first and the last coordinate is the same) + * @param fromIdx Traveling from index + * @param toIdx Traveling until index + * @returns Array of coordinate that travels from `fromIdx` to `toIdx` on the given linestring in the direction of whether + * clockwise or counter-clockwise depends on which gives a shorter length + */ + public static getShorterPathOnLoop(lineString: LineString, fromIdx: number, toIdx: number): Coordinate[] { + if (fromIdx === toIdx) return []; + + const forwardCoorsIndices = LineStringUtils.getLoopTravelIndices(lineString.getCoordinates().length, fromIdx, toIdx); + const revertedCoorsIndices = LineStringUtils.getLoopTravelIndices(lineString.getCoordinates().length, toIdx, fromIdx).reverse(); + + const forwardLineString = new LineString(forwardCoorsIndices.map(i => lineString.getCoordinates()[i])); + const revertedLineString = new LineString(revertedCoorsIndices.map(i => lineString.getCoordinates()[i])); + return (forwardLineString.getLength() < revertedLineString.getLength() ? forwardLineString : revertedLineString) + .getCoordinates().map(c => [...c]); + } +}; diff --git a/src/osm/osm-ways.ts b/src/osm/osm-ways.ts deleted file mode 100644 index 9d5248c..0000000 --- a/src/osm/osm-ways.ts +++ /dev/null @@ -1,50 +0,0 @@ -import Feature from 'ol/Feature'; -import LineString from 'ol/geom/LineString'; -import OverpassAPI from './overpass-api'; -import { transform, transformExtent } from 'ol/proj'; -import type { Extent } from 'ol/extent'; -import type { OSMNode, OSMWay } from './response'; -import type { Projection } from 'ol/proj'; - -/** - * OSM way elements fetcher from Overpass API -*/ -export default class OSMWays { - private constructor() {} - - /** - * Fetch ways from OSM in the specified extent and query, transform to geometric objects of linestring and return the array of them - * @param extent fetch extent - * @param query OverpassQL for querying ways (excluding settings and out statements) - * @param projection Projection - * @param endpoint URL endpoint, if specify it will overwrite the static settings in OverpassAPI class - * @returns Promise of linestring features array - */ - public static async fetch(extent: Extent, query: string, projection: Projection, endpoint?: string): Promise[]> { - if (projection.getCode() !== 'EPSG:4326') { - extent = transformExtent(extent, projection, 'EPSG:4326'); - } - - const response = await OverpassAPI.fetchInExtent(extent, query, 'out;', endpoint); - - const nodes: { [id: number]: OSMNode } = (response.elements.filter(x => x.type === 'node') as OSMNode[]) - .reduce((dict, node: OSMNode) => { - dict[node.id] = node; - return dict; - }, {} as { [id: number]: OSMNode }); - - return (response.elements.filter(x => x.type === 'way') as OSMWay[]) - .map(x => { - const coordinates = projection.getCode() === 'EPSG:4326' ? - x.nodes.map(id => [nodes[id].lon, nodes[id].lat]) - : x.nodes.map(id => transform([nodes[id].lon, nodes[id].lat], 'EPSG:4326', projection)); - const feature = new Feature(new LineString(coordinates)); - feature.setProperties({ - osmid: x.id, - name: x.tags.name ?? undefined - }); - feature.setId(x.id); - return feature; - }); - } -}; diff --git a/src/osm/overpass-api.ts b/src/osm/overpass-api.ts deleted file mode 100644 index 5e0723e..0000000 --- a/src/osm/overpass-api.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { Extent } from 'ol/extent'; -import type { OverpassResponse } from './response'; - -/** - * Exception for not specifying OverpassAPI endpoint - */ -class OverpassAPIEndpointURLUnsetError extends Error { - public constructor() { - const msg = 'No endpoint URL for Overpass API specified for static property endpointURL of class OverpassAPI.' - + ' Please specify one from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances'; - console.error(msg); - super(msg); - } -} - -/** - * OverpassAPI fetcher - */ -export default class OverpassAPI { - /** - * Default endpoint URL for all queries if not specified in each call. - * Public instances can be found in https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances - */ - public static endpointURL: string|undefined = undefined; - - private constructor() {} - - /** - * Fetch data from OverpassAPI - * @param settings OverpassQL settings statement - * @param query OverpassQL query statement(s) - * @param out OverpassQL out statement - * @param endpoint OverpassAPI endpoint URL - * @returns Promise of OverpassAPI response object - */ - public static async fetch( - settings: string, - query: string, - out?: string, - endpoint?: string - ): Promise { - if (!endpoint && !OverpassAPI.endpointURL) { - throw new OverpassAPIEndpointURLUnsetError(); - } - - out = out ?? 'out;'; - - const body = new URLSearchParams({ data: settings + query + out }).toString(); - const result = await fetch( - endpoint ?? OverpassAPI.endpointURL!, - { method: 'POST', body } - ); - - return (await result.json()); - } - - /** - * Convert OpenLayers extent into OverpassQL bbox setting statement - * @param extent OpenLayers extent - * @returns OverpassQL bbox setting statement - */ - public static extentToBBox(extent: Extent): string { - return '[bbox:' + [ - Math.min(extent[1], extent[3]), - Math.min(extent[0], extent[2]), - Math.max(extent[1], extent[3]), - Math.max(extent[0], extent[2]) - ].join(',') + ']'; - } - - /** - * Create setting statements from OpenLayers extent and fetch data specified in query from OverpassAPI - * @param extent OpenLayers Extent - * @param query OverpassQL query statement - * @param out OverpassQL out statement - * @param endpoint OverpassAPI endpoint URL - * @returns Promise of OverpassAPI response object - */ - public static async fetchInExtent( - extent: Extent, - query: string, - out: string = 'out;', - endpoint?: string - ): Promise { - const settings = OverpassAPI.extentToBBox(extent) + '[out:json];'; - return OverpassAPI.fetch(settings, query, out, endpoint); - } -}; diff --git a/src/osm/response.ts b/src/osm/response.ts deleted file mode 100644 index 0318081..0000000 --- a/src/osm/response.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Type definition of OverpassAPI response with out:json settings - -export type OSMElementBase = { - type: 'node'|'way'|'relation', - id: number, - tags: { name?: string } & Record -}; - -export type OSMNode = OSMElementBase & { - type: 'node', - lat: number, - lon: number, -}; - -export type OSMWay = OSMElementBase & { - type: 'way', - nodes: number[] -}; - -export type OSMElement = OSMNode | OSMWay; - -export type OverpassResponse = { - version: number, - generator: string, - elements: OSMElement[], - osm3s: { - timestamp_osm_base: string, - copyright: string - } -}; diff --git a/src/source.ts b/src/source.ts deleted file mode 100644 index 8b10594..0000000 --- a/src/source.ts +++ /dev/null @@ -1,119 +0,0 @@ -import OSMWays from './osm/osm-ways'; -import VectorSource from 'ol/source/Vector'; -import Style from 'ol/style/Style'; -import { bbox } from 'ol/loadingstrategy'; -import { buffer, containsExtent } from 'ol/extent'; -import type { Feature } from 'ol'; -import type RBush from 'ol/structs/RBush'; -import type { LineString } from 'ol/geom'; -import type { Projection } from 'ol/proj'; -import type { Extent } from 'ol/extent'; - -export type LoaderSuccessFn = (features: Feature[]) => void; -export type LoaderFailedFn = () => void; - -export type OSMWaySourceOptions = { - /** The number of features to store before getting cleared. This is to prevent heavy memory consumption. (Default: 20000) */ - cachedFeaturesCount: number, - - /** Buffer size to apply to the extent of fetching OverpassAPI. This is to prevent excessive call despite slight map view panning. (Default: 0) */ - fetchBufferSize: number, - - /** Map view resolution to start fetching OverpassAPI. This is to prevent fetching elements in too big extent. */ - maximumResolution: number, - - /** OverpassAPI endpoint URL (https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances) */ - overpassEndpointURL?: string, - - /** OverpassQL statement for ways to fetch, default to OSM highways. */ - overpassQuery: string -}; - -/** - * VectorSource that automatically fetching OSM way elements from OverpassAPI - */ -export default class OSMWaySource extends VectorSource> { - /** Source options */ - public options: OSMWaySourceOptions; - - /** Indicating if the source is busy fetching data. This is to prevent excessive call to the API. */ - private _busy: boolean = false; - - /** Extents in which data were fetched. Inherited from VectorSource class. */ - private get loadedExtents(): RBush<{ extent: Extent }> { - return (this as any).loadedExtentsRtree_; - } - - /** - * Constructor - * @param options Options - */ - public constructor(options?: Partial) { - super({ strategy: bbox }); - this.options = { - cachedFeaturesCount: 20000, - maximumResolution: 0, - fetchBufferSize: 0, - overpassQuery: '(way["highway"];>;);', - ...(options ?? {}) - }; - this.setLoader(this.fetchFeatures); - } - - /** - * Load features in the specified extents - * @param extent Extent to load features - * @param resolution Resolution - * @param projection Projection - */ - public loadFeatures(extent: Extent, resolution: number, projection: Projection) { - if (this._busy) return; - if (resolution > this.options.maximumResolution) return; - if (this.loadedExtents.getAll().some(e => containsExtent(e.extent, extent))) return; - super.loadFeatures(extent, resolution, projection); - this.removeLoadedExtent(extent); - } - - /** - * Fetch features from OverpassAPI - * @param extent Extent to fetch (before buffering) - * @param resolution Resolution - * @param projection Projection - * @param success Callback success function - * @param failure Callback failure function - */ - private async fetchFeatures( - extent: Extent, - resolution: number, - projection: Projection, - success?: LoaderSuccessFn, - failure?: LoaderFailedFn - ) { - const fetchExtent = this.options.fetchBufferSize ? buffer(extent, this.options.fetchBufferSize) : extent; - try { - this._busy = true; - if (this.getFeatures().length > this.options.cachedFeaturesCount) { - this.clear(true); - this.loadedExtents.clear(); - } - const features = await OSMWays.fetch(fetchExtent, this.options.overpassQuery, projection, this.options.overpassEndpointURL); - this.addFeatures(features.filter(x => !this.getFeatureById(x.getId()!))); - this.loadedExtents.insert(fetchExtent, { extent: fetchExtent }); - return success && success(features); - } catch { - return failure && failure(); - } finally { - this._busy = false; - } - } - - /** - * Get default style of OSM source to the layer, default to be invisible as it is used for snapping only. - * @returns Style - */ - public static getDefaultStyle(): Style { - return new Style({ - stroke: undefined - }); - } -} diff --git a/src/utils/line-string.ts b/src/utils/line-string.ts deleted file mode 100644 index 1956724..0000000 --- a/src/utils/line-string.ts +++ /dev/null @@ -1,46 +0,0 @@ -import LineString from 'ol/geom/LineString'; -import type { Coordinate } from 'ol/coordinate'; - -/** - * Static class for LineString calculation utilities - */ -export default class LineStringUtils { - private constructor() {} - - /** - * Given a linestring and a coordinate, if the coordinate lies on the linestring then return the index of coordinates that contains the coordinate, - * otherwise -1 when the coordinate does not lie on the linestring - * @param lineString LineString geometry - * @param coordinate Coordinate to split - * @returns index of coordinate on linestring, before which contains the given coordinate, -1 if the coordinate does not lie on the linestring - */ - private static getSplitIndex(lineString: LineString, coordinate: Coordinate): number { - const coordinates = lineString.getCoordinates(); - if (coordinates[0][0] === coordinate[0] && coordinates[0][1] === coordinate[1]) return -1; - for (let i = 1; i < coordinates.length; i++) { - if (coordinates[i][0] === coordinate[0] && coordinates[i][1] === coordinate[1]) return -1; - if (new LineString([coordinates[i - 1], coordinates[i]]).intersectsCoordinate(coordinate)) return i; - } - return -1; - } - - /** - * Given a linestring and a coordinate that lies on the linestring but not in the vertex coordinates set of the linestring, - * split the linestring to include that coordinate. - * If the coordinate does not lie on the linestring, then return the original input as is. - * @param lineString LineString geometry - * @param coordinate Coordinate to split - * @returns New linestring that contains the given spliting coordinate - */ - public static split(lineString: LineString, coordinate: Coordinate): LineString { - const idx = LineStringUtils.getSplitIndex(lineString, coordinate); - if (idx < 0) return lineString; - - const coordinates = lineString.getCoordinates(); - return new LineString([ - ...coordinates.slice(0, idx), - coordinate, - ...coordinates.slice(idx) - ]); - } -}; diff --git a/tests/common.ts b/tests/common.ts new file mode 100644 index 0000000..48d7f57 --- /dev/null +++ b/tests/common.ts @@ -0,0 +1,92 @@ +import { Feature, MapBrowserEvent } from 'ol'; +import { LineString } from 'ol/geom'; +import VectorSource from 'ol/source/Vector'; +import EventType from 'ol/MapBrowserEventType' +import { MouseEvent } from 'happy-dom'; +import type { Map } from 'ol'; +import type { Coordinate } from 'ol/coordinate'; +import type { OSMWaySnap } from '../dist'; + +/** + * Drawing a linestring network that looks more or less like this: + * + * │ + * ┌─┴─┐ + * │ │ + * └─┬─┘ + * ──┼── + * │ + * │ + * @returns vector source + */ +export const getDefaultWaySource = (): VectorSource> => { + const fDownToCenter = new Feature(new LineString([ + [0, -50], + [0, 0] + ])); + const fCenterTurnLeft = new Feature(new LineString([ + [0, 0], + [-25, 25], + [-50, 25] + ])); + const fCenterToBeforeLoop = new Feature(new LineString([ + [0, 0], + [0, 10] + ])); + const fLoop = new Feature(new LineString([ + [0, 10], + [-10, 10], + [-10, 20], + [0, 20], + [10, 20], + [10, 10], + [0, 10] + ])); + const fLoopExtended = new Feature(new LineString([ + [0, 20], + [0, 30] + ])); + const fCenterTurnRight = new Feature(new LineString([ + [0, 0], + [25, 25], + [50, 25] + ])); + + fDownToCenter.setId('fDownToCenter'); + fCenterTurnLeft.setId('fCenterTurnLeft'); + fCenterToBeforeLoop.setId('fCenterToBeforeLoop'); + fLoop.setId('fLoop'); + fLoopExtended.setId('fLoopExtended'); + fCenterTurnRight.setId('fCenterTurnRight'); + + const features: Feature[] = [ + fDownToCenter, + fCenterTurnLeft, + fCenterToBeforeLoop, + fLoop, + fLoopExtended, + fCenterTurnRight + ]; + + return new VectorSource>({ features }); +}; + +export const mouseMove = (map: Map, interaction: OSMWaySnap, targetCoordinate: Coordinate) => { + const event = new MapBrowserEvent( + EventType.POINTERMOVE, + map, + new MouseEvent('mousemove') as any + ); + event.coordinate = targetCoordinate; + interaction.handleEvent(event); +}; + +export const mouseClick = (map: Map, interaction: OSMWaySnap, targetCoordinate: Coordinate) => { + const event = new MapBrowserEvent( + EventType.CLICK, + map, + new MouseEvent('click') as any + ); + event.coordinate = targetCoordinate; + interaction.handleEvent(event); +}; diff --git a/tests/interaction.test.ts b/tests/interaction.test.ts new file mode 100644 index 0000000..42840af --- /dev/null +++ b/tests/interaction.test.ts @@ -0,0 +1,308 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { Map, View } from 'ol'; +import VectorLayer from 'ol/layer/Vector'; +import VectorSource from 'ol/source/Vector'; +import { Projection } from 'ol/proj'; +import { getDefaultWaySource, mouseClick, mouseMove } from './common'; +import { OSMWaySnap } from '../dist'; +import type { Feature } from 'ol'; +import type { LineString, Point } from 'ol/geom'; + +describe('Test OSMWaySnap Interaction', () => { + let map: Map; + let interaction: OSMWaySnap; + let targetLayer: VectorLayer>>; + const getSketchLine = (): Feature|undefined => (interaction as any).sketchLine; + + beforeEach(() => { + window.document.body.innerHTML = '
'; + + const view = new View({ + center: [0, 0], + zoom: 10, + projection: new Projection({ + code: 'CUSTOM', + extent: [-100, -100, 100, 100] + }) + }); + + targetLayer = new VectorLayer({ source: new VectorSource() }); + + const waySource = getDefaultWaySource(); + const wayLayer = new VectorLayer({ source: waySource }); + + map = new Map({ + target: 'map', + view, + layers: [ + targetLayer, wayLayer + ] + }); + interaction = new OSMWaySnap({ + source: targetLayer.getSource()!, + waySource + }); + map.addInteraction(interaction); + }); + + it('draws sketch point on mouse moving', () => { + mouseMove(map, interaction, [-50, -75]); + + const sketchPoint = (interaction as any).sketchPoint as Feature; + expect(sketchPoint).toBeTruthy(); + + const coor = sketchPoint.getGeometry()!.getFirstCoordinate(); + expect(coor).toEqual([-50, -75]); + }); + + it('creates a feature when click on points', () => { + expect(interaction.getActiveFeature()).toBeUndefined(); + + mouseMove(map, interaction, [0, -50]); + mouseClick(map, interaction, [0, -50]); + mouseMove(map, interaction, [0, 0]); + mouseClick(map, interaction, [0, 0]); + + expect(interaction.getActiveFeature()).toBeTruthy(); + + const coors = interaction.getActiveFeature()!.getGeometry()!.getCoordinates(); + expect(coors.length).toEqual(2); + expect(coors[0]).toEqual([0, -50]); + expect(coors[1]).toEqual([0, 0]); + + expect( + targetLayer.getSource()!.getFeatures() + .some(f => f === interaction.getActiveFeature()) + ).toBeTruthy(); + }); + + it('draws sketch line on mouse moving after started', () => { + mouseMove(map, interaction, [0, -50]); + let sketchLine = getSketchLine(); + expect(sketchLine).toBeUndefined(); + + mouseClick(map, interaction, [0, -50]); + mouseMove(map, interaction, [0, 0]); + sketchLine = getSketchLine(); + expect(sketchLine).toBeTruthy(); + expect(sketchLine!.getGeometry()!.getCoordinates().length).toEqual(2); + expect(sketchLine!.getGeometry()!.getFirstCoordinate()).toEqual([0, -50]); + expect(sketchLine!.getGeometry()!.getLastCoordinate()).toEqual([0, 0]); + + mouseClick(map, interaction, [0, 0]); + mouseMove(map, interaction, [0, 10]); + sketchLine = getSketchLine(); + expect(sketchLine!.getGeometry()!.getCoordinates().length).toEqual(2); + expect(sketchLine!.getGeometry()!.getFirstCoordinate()).toEqual([0, 0]); + expect(sketchLine!.getGeometry()!.getLastCoordinate()).toEqual([0, 10]); + }); + + it('traces sketch line and appends line following way source', () => { + mouseMove(map, interaction, [0, -50]); + mouseClick(map, interaction, [0, -50]); + mouseMove(map, interaction, [0, 0]); + mouseClick(map, interaction, [0, 0]); + mouseMove(map, interaction, [-50, 25]); + const sketchLine = getSketchLine(); + expect(sketchLine!.getGeometry()!.getCoordinates().length).toEqual(3); + expect(sketchLine!.getGeometry()!.getCoordinates()[0]).toEqual([0, 0]); + expect(sketchLine!.getGeometry()!.getCoordinates()[1]).toEqual([-25, 25]); + expect(sketchLine!.getGeometry()!.getCoordinates()[2]).toEqual([-50, 25]); + + mouseClick(map, interaction, [-50, 25]); + const activeGeometry = interaction.getActiveFeature()!.getGeometry()!; + expect(activeGeometry.getCoordinates().length).toEqual(4); + expect(activeGeometry.getCoordinates()[0]).toEqual([0, -50]); + expect(activeGeometry.getCoordinates()[1]).toEqual([0, 0]); + expect(activeGeometry.getCoordinates()[2]).toEqual([-25, 25]); + expect(activeGeometry.getCoordinates()[3]).toEqual([-50, 25]); + }); + + it('displays candidate lines and points on a junction as active feature tip', () => { + mouseMove(map, interaction, [0, -50]); + mouseClick(map, interaction, [0, -50]); + mouseMove(map, interaction, [0, 0]); + mouseClick(map, interaction, [0, 0]); + mouseMove(map, interaction, [-1, -1]); + + const candidateLines = (interaction as any).candidateLines as Feature[]; + expect(candidateLines.length).toEqual(4); + expect( + [ + 'fDownToCenter', + 'fCenterTurnLeft', + 'fCenterToBeforeLoop', + 'fCenterTurnRight' + ].every(id => candidateLines.some(l => l.getId() === id)) + ).toBeTruthy(); + + const candidateCoordinates = ((interaction as any).candidatePoints as Feature[]) + .map(p => p.getGeometry()!.getFirstCoordinate()); + expect( + [ + [-25, 25], + [-50, 25], + [0, -50], + [0, 0], + [0, 10], + [25, 25], + [50, 25] + ].every( + c => candidateCoordinates.some(cc => c[0] === cc[0] && c[1] === cc[1]) + ) + ); + }); + + it('reverts sketch line when creation order is opposite with the vertex sequence', () => { + mouseMove(map, interaction, [-50, 25]); + mouseClick(map, interaction, [-50, 25]); + mouseMove(map, interaction, [0, 0]); + + const sketchLine = getSketchLine(); + expect(sketchLine).toBeTruthy(); + expect(sketchLine!.getGeometry()!.getCoordinates().length).toEqual(3); + expect(sketchLine!.getGeometry()!.getCoordinates()[0]).toEqual([-50, 25]); + expect(sketchLine!.getGeometry()!.getCoordinates()[1]).toEqual([-25, 25]); + expect(sketchLine!.getGeometry()!.getCoordinates()[2]).toEqual([0, 0]); + + mouseClick(map, interaction, [0, 0]); + + const activeFeature = interaction.getActiveFeature(); + expect(activeFeature).toBeTruthy(); + expect(activeFeature!.getGeometry()!.getCoordinates().length).toEqual(3); + expect(activeFeature!.getGeometry()!.getCoordinates()[0]).toEqual([-50, 25]); + expect(activeFeature!.getGeometry()!.getCoordinates()[1]).toEqual([-25, 25]); + expect(activeFeature!.getGeometry()!.getCoordinates()[2]).toEqual([0, 0]); + }); + + it('partially creates from candidate line when user click from an intermediate vertex', () => { + mouseMove(map, interaction, [-20, 10]); + mouseClick(map, interaction, [-20, 10]); + mouseMove(map, interaction, [-10, 10]); + mouseClick(map, interaction, [-10, 10]); + mouseMove(map, interaction, [0, 20]); + + const sketchLine = getSketchLine(); + expect(sketchLine).toBeTruthy(); + expect(sketchLine!.getGeometry()!.getCoordinates().length).toEqual(3); + expect(sketchLine!.getGeometry()!.getCoordinates()[0]).toEqual([-10, 10]); + expect(sketchLine!.getGeometry()!.getCoordinates()[1]).toEqual([-10, 20]); + expect(sketchLine!.getGeometry()!.getCoordinates()[2]).toEqual([0, 20]); + + mouseClick(map, interaction, [10, 10]); + const activeFeature = interaction.getActiveFeature(); + expect(activeFeature).toBeTruthy(); + expect(activeFeature!.getGeometry()!.getCoordinates().length).toEqual(4); + expect(activeFeature!.getGeometry()!.getCoordinates()[0]).toEqual([-20, 10]); + expect(activeFeature!.getGeometry()!.getCoordinates()[1]).toEqual([-10, 10]); + expect(activeFeature!.getGeometry()!.getCoordinates()[2]).toEqual([-10, 20]); + expect(activeFeature!.getGeometry()!.getCoordinates()[3]).toEqual([0, 20]); + }); + + it('correctly handles when user click on a candidate line but not on its vertex', () => { + mouseMove(map, interaction, [0, -25]); + mouseClick(map, interaction, [0, -25]); + mouseMove(map, interaction, [0, 0]); + + let candidateLines = (interaction as any).candidateLines as Feature[]; + expect(candidateLines.length).toEqual(1); + expect(candidateLines[0].getId()).toEqual('fDownToCenter'); + + mouseClick(map, interaction, [0, 0]); + mouseMove(map, interaction, [-1, -1]); + candidateLines = (interaction as any).candidateLines as Feature[]; + expect(candidateLines.length).toEqual(4); + + const activeFeatureCoors = interaction.getActiveFeature()!.getGeometry()!.getCoordinates(); + expect(activeFeatureCoors.length).toEqual(2); + expect(activeFeatureCoors[0]).toEqual([0, -25]); + expect(activeFeatureCoors[1]).toEqual([0, 0]); + }); + + it('disables sketching when candidate is a loop', () => { + mouseMove(map, interaction, [0, 0]); + mouseClick(map, interaction, [0, 0]); + mouseMove(map, interaction, [0, 10]); + mouseClick(map, interaction, [0, 10]); + mouseMove(map, interaction, [0, 0]); + mouseMove(map, interaction, [0, 10]); + let sketchLine = getSketchLine(); + expect(sketchLine).toBeUndefined(); + + interaction.finishEditing(); + + mouseMove(map, interaction, [10, 20]); + mouseClick(map, interaction, [10, 20]); + mouseMove(map, interaction, [0, 0]); + mouseMove(map, interaction, [10, 20]); + sketchLine = getSketchLine(); + expect(sketchLine).toBeUndefined(); + }); + + it('sketches shortest partial of loop when user starts from an intermediate vertex', () => { + mouseMove(map, interaction, [0, 30]); + mouseClick(map, interaction, [0, 30]); + mouseMove(map, interaction, [0, 20]); + mouseClick(map, interaction, [0, 20]); + + mouseMove(map, interaction, [10, 10]); + let sketchLine = getSketchLine(); + expect(sketchLine).toBeTruthy(); + let sketchLineCoors = sketchLine!.getGeometry()!.getCoordinates(); + expect(sketchLineCoors.length).toEqual(3); + expect(sketchLineCoors[0]).toEqual([0, 20]); + expect(sketchLineCoors[1]).toEqual([10, 20]); + expect(sketchLineCoors[2]).toEqual([10, 10]); + + mouseMove(map, interaction, [-10, 10]); + sketchLine = getSketchLine(); + expect(sketchLine).toBeTruthy(); + sketchLineCoors = sketchLine!.getGeometry()!.getCoordinates(); + expect(sketchLineCoors.length).toEqual(3); + expect(sketchLineCoors[0]).toEqual([0, 20]); + expect(sketchLineCoors[1]).toEqual([-10, 20]); + expect(sketchLineCoors[2]).toEqual([-10, 10]); + + interaction.finishEditing(); + + mouseMove(map, interaction, [10, 10]); + mouseClick(map, interaction, [10, 10]); + mouseMove(map, interaction, [-10, 10]); + sketchLine = getSketchLine(); + expect(sketchLine).toBeTruthy(); + sketchLineCoors = sketchLine!.getGeometry()!.getCoordinates(); + expect(sketchLineCoors.length).toEqual(3); + expect(sketchLineCoors[0]).toEqual([10, 10]); + expect(sketchLineCoors[1]).toEqual([0, 10]); + expect(sketchLineCoors[2]).toEqual([-10, 10]); + + interaction.finishEditing(); + mouseMove(map, interaction, [-10, 10]); + mouseClick(map, interaction, [-10, 10]); + mouseMove(map, interaction, [10, 10]); + sketchLine = getSketchLine(); + expect(sketchLine).toBeTruthy(); + sketchLineCoors = sketchLine!.getGeometry()!.getCoordinates(); + expect(sketchLineCoors.length).toEqual(3); + expect(sketchLineCoors[0]).toEqual([-10, 10]); + expect(sketchLineCoors[1]).toEqual([0, 10]); + expect(sketchLineCoors[2]).toEqual([10, 10]); + }); + + it('finished drawing when the last vertex is clicked twice', () => { + mouseMove(map, interaction, [0, 0]); + mouseClick(map, interaction, [0, 0]); + mouseMove(map, interaction, [50, 25]); + mouseClick(map, interaction, [50, 25]); + mouseClick(map, interaction, [50, 25]); + + expect(interaction.getActiveFeature()).toBeUndefined(); + expect(targetLayer.getSource()!.getFeatures().length).toEqual(1); + + const coors = targetLayer.getSource()!.getFeatures()[0].getGeometry()!.getCoordinates(); + expect(coors.length).toEqual(3); + expect(coors[0]).toEqual([0, 0]); + expect(coors[1]).toEqual([25, 25]); + expect(coors[2]).toEqual([50, 25]); + }); +}); diff --git a/tests/osmoverpass.test.ts b/tests/osmoverpass.test.ts new file mode 100644 index 0000000..ead64e3 --- /dev/null +++ b/tests/osmoverpass.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest'; +import VectorSource from 'ol/source/Vector'; +import { OSMWaySnap } from '../dist'; +import { OSMOverpassWaySource } from 'ol-osmoverpass'; +import type Feature from 'ol/Feature'; +import type LineString from 'ol/geom/LineString'; + +test('Test default initialisation with OSMOverpass', () => { + const osmWaySnap = new OSMWaySnap({ + source: new VectorSource>(), + maximumResolution: 5, + fetchBufferSize: 250, + overpassEndpointURL: 'https://overpass-api.de/api/interpreter' + }); + + expect(osmWaySnap.getWaySource()).toBeInstanceOf(OSMOverpassWaySource); +}); diff --git a/tsconfig.json b/tsconfig.json index b8b263b..d46df14 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { - "module": "CommonJS", - "target": "es6", + "module": "ES2020", + "moduleResolution": "Node", + "target": "ES2020", "rootDir": "./src", "declaration": true, "declarationMap": true, @@ -11,5 +12,11 @@ "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true - } + }, + "include": [ + "./src/**/*.ts" + ], + "exclude": [ + "./tests/*.ts" + ] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..969930f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['**\/tests\/*.test.ts'], + environment: 'happy-dom', + server: { + deps: { + inline: ['ol-osmoverpass'] + } + } + } +}); diff --git a/webpack.config.js b/webpack.config.js index f309047..df2d82b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,32 +1,38 @@ -const path = require('path'); +import path from 'path'; +import { fileURLToPath } from 'url'; -module.exports = { +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export default { mode: 'production', entry: './dist/index.js', + module: { + rules: [{ + test: /\.(js)$/, + resolve: { + fullySpecified: false + } + }] + }, resolve: { alias: { ol: false } }, externalsType: 'var', externals: { + 'ol': 'ol', 'ol/extent': 'ol.extent', 'ol/Feature': 'ol.Feature', - 'ol/geom/LineString': 'ol.geom.LineString', - 'ol/geom/MultiPoint': 'ol.geom.MultiPoint', - 'ol/geom/Point': 'ol.geom.Point', - 'ol/interaction/Pointer': 'ol.interaction.Pointer', - 'ol/interaction/Property': 'ol.interaction.Property', - 'ol/layer/Vector': 'ol.layer.Vector', + 'ol/geom': 'ol.geom', + 'ol/interaction': 'ol.interaction', + 'ol/layer': 'ol.layer', 'ol/loadingstrategy': 'ol.loadingstrategy', - 'ol/Map': 'ol.Map', 'ol/proj': 'ol.proj', - 'ol/source/Vector': 'ol.source.Vector', - 'ol/style/Circle': 'ol.style.Circle', - 'ol/style/Fill': 'ol.style.Fill', - 'ol/style/Stroke': 'ol.style.Stroke', - 'ol/style/Style': 'ol.style.Style' + 'ol/source': 'ol.source', + 'ol/style': 'ol.style', + 'ol/style/Style': 'ol.style.Style', }, output: { - path: path.resolve(__dirname, 'dist', 'webpack'), + path: path.resolve(__dirname, 'dist', 'bundle'), filename: 'index.js', library: { type: 'var',