@sakuzu/maplibre-gl-draw 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -1
- package/README.ja.md +48 -35
- package/README.md +52 -36
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/api/datasets.d.ts +5 -0
- package/dist/api/draw.d.ts +6 -4
- package/dist/api/draw.js +6 -4
- package/dist/api/events.d.ts +10 -0
- package/dist/api/impl/create-draw.js +1 -1
- package/dist/api/impl/datasets.js +1 -0
- package/dist/api/impl/events.js +2 -1
- package/dist/api/impl/extension-host.js +4 -1
- package/dist/api/impl/options.js +58 -29
- package/dist/dataset/dataset.js +3 -0
- package/dist/dataset/types.d.ts +2 -0
- package/dist/dispatcher/input-router.js +2 -1
- package/dist/dispatcher/normalizer.js +19 -2
- package/dist/geometry/geojson.d.ts +4 -0
- package/dist/geometry/geojson.js +20 -11
- package/dist/index.d.ts +14 -0
- package/dist/view/layer/attach.d.ts +11 -5
- package/dist/view/layer/attach.js +85 -9
- package/package.json +22 -10
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,100 @@ the project follows semantic versioning.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.1.0] - 2026-10-01
|
|
10
|
+
|
|
11
|
+
The standard UI package, the documentation site with twenty examples and
|
|
12
|
+
the playground on it, the event `options.changed`,
|
|
13
|
+
`Dataset.getStyleRule()`, and fixes to the order of the layers after a
|
|
14
|
+
diffed `setStyle`, to a drag across the antimeridian on the globe, to the
|
|
15
|
+
`/geometry` functions given a feature, and to `scaleWithZoom` changed at
|
|
16
|
+
runtime. Nothing changes incompatibly.
|
|
17
|
+
|
|
18
|
+
### Added in 2.1.0
|
|
19
|
+
|
|
20
|
+
- The event `options.changed`, with `{ options, previous }`: what
|
|
21
|
+
`draw.options.get()` returns after and before a `draw.options.update`
|
|
22
|
+
that changed a value. An update that changes nothing fires nothing,
|
|
23
|
+
and the event is not a change of the document, so it fires no
|
|
24
|
+
`document.changed`.
|
|
25
|
+
- Twenty examples built with the standard UI, each with its page
|
|
26
|
+
on the site: `get-started`, `style-features`, `feature-properties`,
|
|
27
|
+
`layers-and-groups`, `style-rules-and-legend`, `snapping-and-tracing`,
|
|
28
|
+
`geometry-operations`, `images`, `editing-shapes`, `zoom-and-scale`,
|
|
29
|
+
`save-and-load`, `terrain`, `globe`, `200000-features`, `datasets`,
|
|
30
|
+
`columnar-data-in-a-worker`, `read-only-viewer`, `plugins`,
|
|
31
|
+
`custom-feature-types` and `custom-ui`. Their basemap menu offers the
|
|
32
|
+
OpenFreeMap styles.
|
|
33
|
+
- A sample of Overture Maps data for the examples, in
|
|
34
|
+
`examples/public/data/` (not in the package): the 10,477 buildings and
|
|
35
|
+
the 17,558 places of central Tokyo east of the station, as GeoJSON and
|
|
36
|
+
GeoParquet. `npm run data:overture` (`scripts/fetch-overture-sample.mjs`)
|
|
37
|
+
writes them again. The buildings are under the ODbL and the places
|
|
38
|
+
under the CDLA Permissive 2.0;
|
|
39
|
+
`THIRD_PARTY_NOTICES.md` and `examples/public/data/README.md` record
|
|
40
|
+
the attribution and the licenses of every source. The `datasets`,
|
|
41
|
+
`columnar-data-in-a-worker` and `style-rules-and-legend` examples show
|
|
42
|
+
them.
|
|
43
|
+
- `Dataset.getStyleRule()`, the style rule of a dataset: the
|
|
44
|
+
one last given to `setStyleRule`, or the one given to `datasets.add`,
|
|
45
|
+
or `undefined` when there is none. A legend of the datasets reads it.
|
|
46
|
+
|
|
47
|
+
### Changed in 2.1.0
|
|
48
|
+
|
|
49
|
+
- The site (<https://sakuzu.github.io/maplibre-gl-draw/>) is
|
|
50
|
+
the documentation site: getting started and the guides in English and
|
|
51
|
+
Japanese, a gallery of the examples, each on a page that runs it
|
|
52
|
+
beside its code, the playground under `/playground/` and the API
|
|
53
|
+
reference under `/api/`, which opens with where each resource of
|
|
54
|
+
`Draw` is. The examples with buttons of their own (`basic`,
|
|
55
|
+
`save-load`, `style-rules`, `snapping-and-geometry`, `read-only`,
|
|
56
|
+
`plugin`, `custom-feature-type`, `large-data` and `table-worker`) are
|
|
57
|
+
replaced by the examples of the gallery, and their addresses, like
|
|
58
|
+
those of the HTML pages of the API reference of 1.0 and 2.0, redirect
|
|
59
|
+
to the pages that took their place. `npm run site:dev` serves the site
|
|
60
|
+
with the examples and the playground for a local check.
|
|
61
|
+
- The playground is built on the standard UI, with the tools
|
|
62
|
+
and the inspector sections of a plugin and of a custom feature type,
|
|
63
|
+
and the switches that are not tools on Shift and a letter. It opens on
|
|
64
|
+
an overview of every look one layer can hold; `?plain` opens it empty.
|
|
65
|
+
- The dev server of the examples (`npm run dev`) moves from
|
|
66
|
+
port 3000 to 3200, and that of the playground
|
|
67
|
+
(`npm run dev:playground`) from 3001 to 3300. They and
|
|
68
|
+
`npm run test:e2e` need the standard UI built first
|
|
69
|
+
(`npm run ui:build`).
|
|
70
|
+
- No sample geometry of the examples, the scenes of the
|
|
71
|
+
playground or the development page of the standard UI lies over the
|
|
72
|
+
Imperial Palace, its East Gardens or Kitanomaru Park. Each page keeps
|
|
73
|
+
its data in a `data.ts` of its own, and a test walks all of it and
|
|
74
|
+
fails when anything reaches into that area.
|
|
75
|
+
|
|
76
|
+
### Fixed in 2.1.0
|
|
77
|
+
|
|
78
|
+
- After `map.setStyle` in its default diff mode, the drawing is
|
|
79
|
+
on top of the map again. maplibre leaves custom layers out of the
|
|
80
|
+
style it diffs, so the layers of the new style were added above the
|
|
81
|
+
drawing and hid it. The layers of the instance now move back on top on
|
|
82
|
+
every `style.load`, in their order, with the map layers placed between
|
|
83
|
+
them; a full `setStyle` (`diff: false`) works as before.
|
|
84
|
+
- `draw.options.update({ scaleWithZoom })` reaches the drawing
|
|
85
|
+
modes at once. They kept the value given to `createDraw`, so a feature
|
|
86
|
+
drawn after the option was turned off still got a reference zoom
|
|
87
|
+
(`maplibre-gl-draw:createdZoom`), and one drawn after it was turned on
|
|
88
|
+
got none.
|
|
89
|
+
- The functions of `/geometry` take a feature of the drawing as it
|
|
90
|
+
is, as documented. A feature whose `type` names a geometry type
|
|
91
|
+
(`Polygon`, `LineString` and so on) was read as a geometry and threw a
|
|
92
|
+
`GeometryError` (`invalid-input`); an object with a `geometry` member
|
|
93
|
+
that is a geometry is now read as a feature first, whatever its own
|
|
94
|
+
`type`.
|
|
95
|
+
- On the globe, a freehand stroke drawn across the antimeridian is
|
|
96
|
+
drawn as the stroke alone. The globe gives the pointer longitudes in
|
|
97
|
+
[-180, 180], so where the pointer crossed the line (or slid along the
|
|
98
|
+
edge of the sphere past it) the next point of the stroke was 360
|
|
99
|
+
degrees from the one before, and a line went once round the globe
|
|
100
|
+
along the parallel. The coordinates of a press now run on past 180 as
|
|
101
|
+
they do on the flat map.
|
|
102
|
+
|
|
9
103
|
## [2.0.0] - 2026-09-30
|
|
10
104
|
|
|
11
105
|
2.0.0 redesigns the public API around resources and their collections.
|
|
@@ -1052,6 +1146,5 @@ available from Kasika, Inc.
|
|
|
1052
1146
|
- A read-only mode and an interaction lock.
|
|
1053
1147
|
- Plugins, custom modes and custom feature types.
|
|
1054
1148
|
|
|
1055
|
-
[Unreleased]: https://github.com/sakuzu/maplibre-gl-draw/compare/v2.0.0...HEAD
|
|
1056
1149
|
[2.0.0]: https://github.com/sakuzu/maplibre-gl-draw/compare/v1.0.0...v2.0.0
|
|
1057
1150
|
[1.0.0]: https://github.com/sakuzu/maplibre-gl-draw/releases/tag/v1.0.0
|
package/README.ja.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
このページの正は英語版 ([README.md](README.md)) です。
|
|
12
12
|
|
|
13
|
-

|
|
14
14
|
|
|
15
15
|
## 機能
|
|
16
16
|
|
|
@@ -122,28 +122,36 @@ GeoJSON の properties を持つので、ファイルはコードで読む地物
|
|
|
122
122
|
ブラウザーですぐに試せます。インストールは要りません。
|
|
123
123
|
|
|
124
124
|
- [プレイグラウンド][demo]
|
|
125
|
-
- すべての機能を 1
|
|
126
|
-
- [
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
- すべての機能を 1 つの画面で。標準の UI 付き
|
|
126
|
+
- [例][examples]
|
|
127
|
+
- 20 の例。どれも、例を動かすページにそのコードを添えています
|
|
128
|
+
|
|
129
|
+
例の一部を挙げます。
|
|
130
|
+
|
|
131
|
+
- [Get started][ex-get-started]
|
|
132
|
+
- 地物を描き、選び、パネルで直す
|
|
133
|
+
- [Save and load][ex-save-and-load]
|
|
134
|
+
- GeoJSON と独自形式、地図に落としたファイル、読み込みで外された地物
|
|
135
|
+
- [Style rules and legend][ex-style-rules-and-legend]
|
|
131
136
|
- 属性の値で色を分ける 4 種類の規則と凡例
|
|
132
|
-
- [
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
- [
|
|
136
|
-
- 3D
|
|
137
|
-
- [
|
|
138
|
-
-
|
|
139
|
-
- [
|
|
140
|
-
-
|
|
141
|
-
- [
|
|
137
|
+
- [Snapping and tracing][ex-snapping-and-tracing] と
|
|
138
|
+
[Geometry operations][ex-geometry-operations]
|
|
139
|
+
- 吸着、境界のなぞり、結合、交差、差、分割、バッファー
|
|
140
|
+
- [Terrain][ex-terrain]
|
|
141
|
+
- 3D 地形の上での描画と編集
|
|
142
|
+
- [Read-only viewer][ex-read-only-viewer]
|
|
143
|
+
- 見るための描画。閲覧専用、操作のロック、クリックした地物の属性
|
|
144
|
+
- [Plugins][ex-plugins]
|
|
145
|
+
- 独自のモードを持つプラグインと、標準の UI に足すその道具
|
|
146
|
+
- [Custom feature types][ex-custom-feature-types]
|
|
142
147
|
- 独自の描き方、当たり判定、範囲選択を持つ地物の型
|
|
143
|
-
- [
|
|
144
|
-
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
- [Datasets][ex-datasets]
|
|
149
|
+
- Overture Maps の東京都心の建物と場所を描画の下と上に。場所は
|
|
150
|
+
見えている範囲の分を取り寄せる
|
|
151
|
+
- [Columnar data in a Worker][ex-columnar-data-in-a-worker]
|
|
152
|
+
- 同じ建物を Worker で GeoParquet から読み込み、列のまま描く
|
|
153
|
+
- [Build your own UI][ex-custom-ui]
|
|
154
|
+
- 標準の UI を使わない、自分の道具のバーとパネル
|
|
147
155
|
|
|
148
156
|
## インストール
|
|
149
157
|
|
|
@@ -191,8 +199,9 @@ document.querySelector('#save')?.addEventListener('click', () => {
|
|
|
191
199
|
});
|
|
192
200
|
```
|
|
193
201
|
|
|
194
|
-
|
|
195
|
-
[
|
|
202
|
+
[はじめかた](docs/getting-started.ja.md) では、このページを順を追って
|
|
203
|
+
作ります。[Get started][ex-get-started] の例は、自分のボタンの代わりに
|
|
204
|
+
標準の UI を地図に重ねます。
|
|
196
205
|
|
|
197
206
|
## 入口
|
|
198
207
|
|
|
@@ -231,7 +240,8 @@ document.querySelector('#save')?.addEventListener('click', () => {
|
|
|
231
240
|
[docs/README.ja.md](docs/README.ja.md) に、はじめかた、手引き、
|
|
232
241
|
リファレンス、内部の文書を読む順に並べています。1.0、mapbox-gl-draw、
|
|
233
242
|
terra-draw から移る場合は [移行](docs/guides/migrating.ja.md) を参照して
|
|
234
|
-
ください。
|
|
243
|
+
ください。[API リファレンス][api] のメインのエントリーのページは、
|
|
244
|
+
インスタンスの資源とそれぞれのメソッドの一覧から始まります。
|
|
235
245
|
|
|
236
246
|
## 開発に参加する
|
|
237
247
|
|
|
@@ -254,15 +264,18 @@ AGPL が製品に合わない場合は、Kasika, Inc. (可視化技研株式会
|
|
|
254
264
|
|
|
255
265
|
[maplibre]: https://maplibre.org/maplibre-gl-js/docs/
|
|
256
266
|
[custom-layer]: https://maplibre.org/maplibre-gl-js/docs/API/interfaces/CustomLayerInterface/
|
|
257
|
-
[demo]: https://sakuzu.github.io/maplibre-gl-draw/
|
|
267
|
+
[demo]: https://sakuzu.github.io/maplibre-gl-draw/playground/
|
|
258
268
|
[api]: https://sakuzu.github.io/maplibre-gl-draw/api/
|
|
259
|
-
[
|
|
260
|
-
[ex-
|
|
261
|
-
[ex-
|
|
262
|
-
[ex-
|
|
263
|
-
[ex-
|
|
264
|
-
[ex-
|
|
265
|
-
[ex-
|
|
266
|
-
[ex-
|
|
267
|
-
[ex-
|
|
268
|
-
[ex-
|
|
269
|
+
[examples]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/
|
|
270
|
+
[ex-get-started]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/get-started.html
|
|
271
|
+
[ex-save-and-load]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/save-and-load.html
|
|
272
|
+
[ex-style-rules-and-legend]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/style-rules-and-legend.html
|
|
273
|
+
[ex-snapping-and-tracing]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/snapping-and-tracing.html
|
|
274
|
+
[ex-geometry-operations]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/geometry-operations.html
|
|
275
|
+
[ex-terrain]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/terrain.html
|
|
276
|
+
[ex-read-only-viewer]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/read-only-viewer.html
|
|
277
|
+
[ex-plugins]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/plugins.html
|
|
278
|
+
[ex-custom-feature-types]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/custom-feature-types.html
|
|
279
|
+
[ex-datasets]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/datasets.html
|
|
280
|
+
[ex-columnar-data-in-a-worker]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/columnar-data-in-a-worker.html
|
|
281
|
+
[ex-custom-ui]: https://sakuzu.github.io/maplibre-gl-draw/ja/examples/custom-ui.html
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ it does on a flat map.
|
|
|
8
8
|
[Demos][demo] | [Documentation](docs/README.md) | [API reference][api] |
|
|
9
9
|
[日本語](README.ja.md)
|
|
10
10
|
|
|
11
|
-

|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
@@ -126,31 +126,41 @@ returns ([plugins](docs/guides/plugins.md),
|
|
|
126
126
|
Try them in the browser, with nothing to install.
|
|
127
127
|
|
|
128
128
|
- [Playground][demo]
|
|
129
|
-
- Every feature in one editor, with
|
|
130
|
-
- [
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
- [
|
|
129
|
+
- Every feature in one editor, with the standard UI
|
|
130
|
+
- [Examples][examples]
|
|
131
|
+
- Twenty examples, each on a page that runs it beside its code
|
|
132
|
+
|
|
133
|
+
Among the examples:
|
|
134
|
+
|
|
135
|
+
- [Get started][ex-get-started]
|
|
136
|
+
- Drawing, selecting and changing features in the panel
|
|
137
|
+
- [Save and load][ex-save-and-load]
|
|
138
|
+
- GeoJSON and the native format, files dropped on the map, the
|
|
139
|
+
features a load leaves out
|
|
140
|
+
- [Style rules and legend][ex-style-rules-and-legend]
|
|
136
141
|
- The four kinds of style rule that color features by a property, with
|
|
137
142
|
a legend
|
|
138
|
-
- [Snapping and
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
- [
|
|
143
|
+
- [Snapping and tracing][ex-snapping-and-tracing] and
|
|
144
|
+
[geometry operations][ex-geometry-operations]
|
|
145
|
+
- Snapping, tracing a boundary, union, intersection, difference, split
|
|
146
|
+
and buffer
|
|
147
|
+
- [Terrain][ex-terrain]
|
|
148
|
+
- Drawing and editing on 3D terrain
|
|
149
|
+
- [Read-only viewer][ex-read-only-viewer]
|
|
150
|
+
- A drawing to look at: read-only, the interaction lock, the attributes
|
|
151
|
+
of the feature clicked
|
|
152
|
+
- [Plugins][ex-plugins]
|
|
153
|
+
- A plugin with a mode of its own, and its tool in the standard UI
|
|
154
|
+
- [Custom feature types][ex-custom-feature-types]
|
|
148
155
|
- A kind of feature with its own drawing, hit test and box selection
|
|
149
|
-
- [Datasets][ex-
|
|
150
|
-
-
|
|
151
|
-
|
|
152
|
-
- [
|
|
153
|
-
-
|
|
156
|
+
- [Datasets][ex-datasets]
|
|
157
|
+
- The buildings and places of central Tokyo from Overture Maps, under
|
|
158
|
+
and over the drawing, with places fetched for the part in view
|
|
159
|
+
- [Columnar data in a Worker][ex-columnar-data-in-a-worker]
|
|
160
|
+
- The same buildings read from GeoParquet in a Worker and drawn from
|
|
161
|
+
their columns
|
|
162
|
+
- [Build your own UI][ex-custom-ui]
|
|
163
|
+
- A toolbar and a panel of your own, without the standard UI
|
|
154
164
|
|
|
155
165
|
## Installation
|
|
156
166
|
|
|
@@ -198,8 +208,9 @@ document.querySelector('#save')?.addEventListener('click', () => {
|
|
|
198
208
|
});
|
|
199
209
|
```
|
|
200
210
|
|
|
201
|
-
|
|
202
|
-
[
|
|
211
|
+
[Getting started](docs/getting-started.md) builds this page step by
|
|
212
|
+
step, and the [Get started][ex-get-started] example lays the standard UI
|
|
213
|
+
over the map in place of your own buttons.
|
|
203
214
|
|
|
204
215
|
## Entry points
|
|
205
216
|
|
|
@@ -239,6 +250,8 @@ Import from the main entry unless you need one of the others.
|
|
|
239
250
|
[docs/README.md](docs/README.md) lists every document in reading order:
|
|
240
251
|
getting started, the guides, the reference and the internals. Moving from
|
|
241
252
|
1.0, mapbox-gl-draw or terra-draw? See [migrating](docs/guides/migrating.md).
|
|
253
|
+
The page of the main entry in the [API reference][api] opens with a list
|
|
254
|
+
of the resources of the instance and the methods each one has.
|
|
242
255
|
|
|
243
256
|
## Contributing
|
|
244
257
|
|
|
@@ -261,15 +274,18 @@ The notices of the third-party code this package contains are in
|
|
|
261
274
|
|
|
262
275
|
[maplibre]: https://maplibre.org/maplibre-gl-js/docs/
|
|
263
276
|
[custom-layer]: https://maplibre.org/maplibre-gl-js/docs/API/interfaces/CustomLayerInterface/
|
|
264
|
-
[demo]: https://sakuzu.github.io/maplibre-gl-draw/
|
|
277
|
+
[demo]: https://sakuzu.github.io/maplibre-gl-draw/playground/
|
|
265
278
|
[api]: https://sakuzu.github.io/maplibre-gl-draw/api/
|
|
266
|
-
[
|
|
267
|
-
[ex-
|
|
268
|
-
[ex-
|
|
269
|
-
[ex-
|
|
270
|
-
[ex-
|
|
271
|
-
[ex-
|
|
272
|
-
[ex-
|
|
273
|
-
[ex-
|
|
274
|
-
[ex-
|
|
275
|
-
[ex-
|
|
279
|
+
[examples]: https://sakuzu.github.io/maplibre-gl-draw/examples/
|
|
280
|
+
[ex-get-started]: https://sakuzu.github.io/maplibre-gl-draw/examples/get-started.html
|
|
281
|
+
[ex-save-and-load]: https://sakuzu.github.io/maplibre-gl-draw/examples/save-and-load.html
|
|
282
|
+
[ex-style-rules-and-legend]: https://sakuzu.github.io/maplibre-gl-draw/examples/style-rules-and-legend.html
|
|
283
|
+
[ex-snapping-and-tracing]: https://sakuzu.github.io/maplibre-gl-draw/examples/snapping-and-tracing.html
|
|
284
|
+
[ex-geometry-operations]: https://sakuzu.github.io/maplibre-gl-draw/examples/geometry-operations.html
|
|
285
|
+
[ex-terrain]: https://sakuzu.github.io/maplibre-gl-draw/examples/terrain.html
|
|
286
|
+
[ex-read-only-viewer]: https://sakuzu.github.io/maplibre-gl-draw/examples/read-only-viewer.html
|
|
287
|
+
[ex-plugins]: https://sakuzu.github.io/maplibre-gl-draw/examples/plugins.html
|
|
288
|
+
[ex-custom-feature-types]: https://sakuzu.github.io/maplibre-gl-draw/examples/custom-feature-types.html
|
|
289
|
+
[ex-datasets]: https://sakuzu.github.io/maplibre-gl-draw/examples/datasets.html
|
|
290
|
+
[ex-columnar-data-in-a-worker]: https://sakuzu.github.io/maplibre-gl-draw/examples/columnar-data-in-a-worker.html
|
|
291
|
+
[ex-custom-ui]: https://sakuzu.github.io/maplibre-gl-draw/examples/custom-ui.html
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -273,3 +273,32 @@ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
|
273
273
|
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
274
274
|
THIS SOFTWARE.
|
|
275
275
|
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Sample data of the examples (Overture Maps)
|
|
280
|
+
|
|
281
|
+
The examples of the repository (`examples/`, not part of the package)
|
|
282
|
+
show buildings and places of central Tokyo from Overture Maps, in
|
|
283
|
+
`examples/public/data/`. `scripts/fetch-overture-sample.mjs` extracts
|
|
284
|
+
them, and `examples/public/data/README.md` records the release, the
|
|
285
|
+
extent, the counts and the sources of the rows.
|
|
286
|
+
|
|
287
|
+
Attribution: © OpenStreetMap contributors, Overture Maps Foundation.
|
|
288
|
+
|
|
289
|
+
- The buildings (`tokyo-buildings.geojson` and `tokyo-buildings.parquet`)
|
|
290
|
+
are a database derived from Overture's buildings theme, under the Open
|
|
291
|
+
Database License 1.0 (ODbL): <https://opendatacommons.org/licenses/odbl/1-0/>.
|
|
292
|
+
Its rows come from OpenStreetMap and Microsoft's Global ML Building
|
|
293
|
+
Footprints (ODbL), and from Qian Shi, et al., A First High-quality
|
|
294
|
+
Vector Data of Buildings in East Asian Countries Based on a
|
|
295
|
+
Comprehensive Large-scale Mapping Framework, Zenodo,
|
|
296
|
+
doi:10.5281/zenodo.8174931 (CC BY 4.0)
|
|
297
|
+
- The places (`tokyo-places.geojson`) come from Overture's places theme,
|
|
298
|
+
under the Community Data License Agreement Permissive 2.0:
|
|
299
|
+
<https://cdla.dev/permissive-2-0/>. Rows from Foursquare are under the
|
|
300
|
+
Apache License 2.0 (Copyright 2024 Foursquare Labs, Inc.), and rows from
|
|
301
|
+
AllThePlaces under CC0 1.0
|
|
302
|
+
|
|
303
|
+
The terms of every source are on
|
|
304
|
+
<https://docs.overturemaps.org/attribution/>.
|
package/dist/api/datasets.d.ts
CHANGED
|
@@ -154,6 +154,11 @@ export interface Dataset {
|
|
|
154
154
|
setTable(table: Table | PreparedTable): void;
|
|
155
155
|
/** Replaces the style rule; `undefined` removes it. */
|
|
156
156
|
setStyleRule(rule: StyleRule | undefined): void;
|
|
157
|
+
/**
|
|
158
|
+
* The style rule, or `undefined` when none is set: the rule last given to `setStyleRule`, or
|
|
159
|
+
* the one given to `datasets.add` when it was not replaced.
|
|
160
|
+
*/
|
|
161
|
+
getStyleRule(): StyleRule | undefined;
|
|
157
162
|
/** Replaces the scale factor and the opacity by zoom; `null` removes them. */
|
|
158
163
|
setZoomScale(zoomScale: DatasetZoomScale | null): void;
|
|
159
164
|
/** The scale factor and the opacity by zoom, or `null` when none is set. */
|
package/dist/api/draw.d.ts
CHANGED
|
@@ -123,10 +123,12 @@ export interface Draw {
|
|
|
123
123
|
/**
|
|
124
124
|
* Puts a draw instance on a map and returns it.
|
|
125
125
|
*
|
|
126
|
-
* It can be called before, while or after the map loads. The layers are added
|
|
127
|
-
* style accepts them
|
|
128
|
-
*
|
|
129
|
-
*
|
|
126
|
+
* It can be called before, while or after the map loads. The layers are added on top of the map
|
|
127
|
+
* as soon as the style accepts them. After `setStyle`, with or without `diff`, they are on top
|
|
128
|
+
* again: added again after a full replacement, and moved above the layers of the new style after
|
|
129
|
+
* a diff. A layer added to the map later goes above them and stays there until the style changes.
|
|
130
|
+
* Unless `initDefaultLayer: false` is given, the document starts with one empty layer. Several
|
|
131
|
+
* instances can share a page, each on its own map.
|
|
130
132
|
*
|
|
131
133
|
* @param map - The map to draw on
|
|
132
134
|
* @param options - The options; every one can be omitted
|
package/dist/api/draw.js
CHANGED
|
@@ -4,10 +4,12 @@ import { createDraw as createDrawInstance } from './impl/create-draw.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Puts a draw instance on a map and returns it.
|
|
6
6
|
*
|
|
7
|
-
* It can be called before, while or after the map loads. The layers are added
|
|
8
|
-
* style accepts them
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* It can be called before, while or after the map loads. The layers are added on top of the map
|
|
8
|
+
* as soon as the style accepts them. After `setStyle`, with or without `diff`, they are on top
|
|
9
|
+
* again: added again after a full replacement, and moved above the layers of the new style after
|
|
10
|
+
* a diff. A layer added to the map later goes above them and stays there until the style changes.
|
|
11
|
+
* Unless `initDefaultLayer: false` is given, the document starts with one empty layer. Several
|
|
12
|
+
* instances can share a page, each on its own map.
|
|
11
13
|
*
|
|
12
14
|
* @param map - The map to draw on
|
|
13
15
|
* @param options - The options; every one can be omitted
|
package/dist/api/events.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { DrawError } from './errors.js';
|
|
|
10
10
|
import type { Hit } from './extension/provider.js';
|
|
11
11
|
import type { UpdateSource } from './extension/store.js';
|
|
12
12
|
import type { Feature, FeatureInput, FileData, Group, Layer, LoadResult, Metadata, MoveTarget } from './model.js';
|
|
13
|
+
import type { RuntimeOptions } from './options.js';
|
|
13
14
|
import type { LayerStackEntry, Mode, Selection, SelectionType, SnapResult, VertexSelection } from './state.js';
|
|
14
15
|
/** A point on the screen, as `[x, y]` in CSS pixels. */
|
|
15
16
|
export type ScreenPoint = [number, number];
|
|
@@ -231,6 +232,15 @@ export interface DrawEvents {
|
|
|
231
232
|
'interactionLock.changed': {
|
|
232
233
|
locked: boolean;
|
|
233
234
|
};
|
|
235
|
+
/**
|
|
236
|
+
* `draw.options.update` changed the options; `options` and `previous` are what
|
|
237
|
+
* `draw.options.get()` returns after and before the change. An update that leaves every
|
|
238
|
+
* value as it was does not fire it
|
|
239
|
+
*/
|
|
240
|
+
'options.changed': {
|
|
241
|
+
options: Readonly<RuntimeOptions>;
|
|
242
|
+
previous: Readonly<RuntimeOptions>;
|
|
243
|
+
};
|
|
234
244
|
/** The snapping target changed */
|
|
235
245
|
'snap.changed': {
|
|
236
246
|
result: SnapResult | null;
|
|
@@ -24,7 +24,7 @@ const ASYNC_MEMBERS = new Set(['document.load', 'document.loadMany']);
|
|
|
24
24
|
* @internal
|
|
25
25
|
*/
|
|
26
26
|
export function createDrawOnEngine(engine, options = {}, setExternalEntry = () => { }) {
|
|
27
|
-
const drawOptions = createOptions(engine, options, setExternalEntry);
|
|
27
|
+
const drawOptions = createOptions(engine, options, setExternalEntry, (payload) => engine.events.emit('options.changed', payload));
|
|
28
28
|
drawOptions.applyCreation();
|
|
29
29
|
const { context, modeManager, events, map } = engine;
|
|
30
30
|
const { store } = context;
|
|
@@ -258,6 +258,7 @@ export function wrapDataset(dataset) {
|
|
|
258
258
|
throw invalidInput('The rule must be an object');
|
|
259
259
|
dataset.setStyleRule(rule);
|
|
260
260
|
},
|
|
261
|
+
getStyleRule: () => dataset.getStyleRule(),
|
|
261
262
|
setZoomScale(zoomScale) {
|
|
262
263
|
if (zoomScale !== null)
|
|
263
264
|
checkFunction(zoomScale, 'zoomScale');
|
package/dist/api/impl/events.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* `document.changed` with the whole
|
|
11
11
|
* change when the transaction changed the document. The engine announces the rest
|
|
12
12
|
* on its internal emitter (snapping, clicks, images, the stacking order, failed loads,
|
|
13
|
-
* drags), and each is passed on under its name here.
|
|
13
|
+
* drags), and each is passed on under its name here. `options.changed` comes from
|
|
14
|
+
* `draw.options`, which emits it on the same hub.
|
|
14
15
|
*/
|
|
15
16
|
import { DRAW_PROPERTY_KEYS } from '../../shared/properties.js';
|
|
16
17
|
import { geometryFromCoordinates } from '../../shared/utils/coordinates.js';
|
|
@@ -164,7 +164,10 @@ export function createExtensionHost(deps) {
|
|
|
164
164
|
},
|
|
165
165
|
getWritableLayerId: context.getWritableLayerId,
|
|
166
166
|
generateId: context.generateFeatureId,
|
|
167
|
-
|
|
167
|
+
// Read when a feature is committed, so a change of the option applies at once
|
|
168
|
+
get scaleWithZoom() {
|
|
169
|
+
return context.options.scaleWithZoom;
|
|
170
|
+
},
|
|
168
171
|
selectionStyle: context.selectionStyle,
|
|
169
172
|
boxSelectionStyle: context.renderingConfig.boxSelectionStyle,
|
|
170
173
|
notifyDrawCommit: (feature) => announceDrawCommit(feature),
|
package/dist/api/impl/options.js
CHANGED
|
@@ -656,9 +656,10 @@ function assignInPlace(target, source) {
|
|
|
656
656
|
* @param options - The options the instance was created with
|
|
657
657
|
* @param setExternalEntry - Replaces the function that tells the entries from outside the
|
|
658
658
|
* document
|
|
659
|
+
* @param onChanged - Receives the options after and before an update that changed them
|
|
659
660
|
* @internal
|
|
660
661
|
*/
|
|
661
|
-
export function createOptions(engine, options, setExternalEntry) {
|
|
662
|
+
export function createOptions(engine, options, setExternalEntry, onChanged = () => { }) {
|
|
662
663
|
const { context, map, customLayer } = engine;
|
|
663
664
|
let current = runtimePart(options);
|
|
664
665
|
const apply = (patch) => {
|
|
@@ -758,39 +759,44 @@ export function createOptions(engine, options, setExternalEntry) {
|
|
|
758
759
|
else if (redraw)
|
|
759
760
|
map.triggerRepaint();
|
|
760
761
|
};
|
|
762
|
+
const get = () => {
|
|
763
|
+
const { snapService, trace, topology, pixelRatioSource, renderingConfig } = context;
|
|
764
|
+
const snap = snapService.getOptions();
|
|
765
|
+
const values = {
|
|
766
|
+
...current,
|
|
767
|
+
scaleWithZoom: context.options.scaleWithZoom,
|
|
768
|
+
clickTolerance: context.options.clickTolerance,
|
|
769
|
+
dragThreshold: context.options.dragThreshold,
|
|
770
|
+
snapping: {
|
|
771
|
+
...current.snapping,
|
|
772
|
+
enabled: snap.enabled,
|
|
773
|
+
tolerancePx: snap.tolerancePx,
|
|
774
|
+
disableKey: snap.disableKey,
|
|
775
|
+
kinds: { ...snap.kinds },
|
|
776
|
+
datasets: snap.datasets,
|
|
777
|
+
guideStepDegrees: snap.guideStepDegrees,
|
|
778
|
+
},
|
|
779
|
+
tracing: { enabled: trace.enabled },
|
|
780
|
+
topology: { sharedVertexDrag: topology.sharedVertexDrag },
|
|
781
|
+
rendering: {
|
|
782
|
+
...current.rendering,
|
|
783
|
+
renderScale: pixelRatioSource.getScaleFactor(),
|
|
784
|
+
cacheGeometry: renderingConfig.storeRetained !== false,
|
|
785
|
+
timeSlicing: renderingConfig.timeSlicing !== false,
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
return structuredCloneOptions(values);
|
|
789
|
+
};
|
|
761
790
|
return {
|
|
762
|
-
get
|
|
763
|
-
const { snapService, trace, topology, pixelRatioSource, renderingConfig } = context;
|
|
764
|
-
const snap = snapService.getOptions();
|
|
765
|
-
const values = {
|
|
766
|
-
...current,
|
|
767
|
-
scaleWithZoom: context.options.scaleWithZoom,
|
|
768
|
-
clickTolerance: context.options.clickTolerance,
|
|
769
|
-
dragThreshold: context.options.dragThreshold,
|
|
770
|
-
snapping: {
|
|
771
|
-
...current.snapping,
|
|
772
|
-
enabled: snap.enabled,
|
|
773
|
-
tolerancePx: snap.tolerancePx,
|
|
774
|
-
disableKey: snap.disableKey,
|
|
775
|
-
kinds: { ...snap.kinds },
|
|
776
|
-
datasets: snap.datasets,
|
|
777
|
-
guideStepDegrees: snap.guideStepDegrees,
|
|
778
|
-
},
|
|
779
|
-
tracing: { enabled: trace.enabled },
|
|
780
|
-
topology: { sharedVertexDrag: topology.sharedVertexDrag },
|
|
781
|
-
rendering: {
|
|
782
|
-
...current.rendering,
|
|
783
|
-
renderScale: pixelRatioSource.getScaleFactor(),
|
|
784
|
-
cacheGeometry: renderingConfig.storeRetained !== false,
|
|
785
|
-
timeSlicing: renderingConfig.timeSlicing !== false,
|
|
786
|
-
},
|
|
787
|
-
};
|
|
788
|
-
return structuredCloneOptions(values);
|
|
789
|
-
},
|
|
791
|
+
get,
|
|
790
792
|
update(patch) {
|
|
791
793
|
checkPatch(patch);
|
|
794
|
+
const previous = get();
|
|
792
795
|
current = mergeOptions(current, patch);
|
|
793
796
|
apply(patch);
|
|
797
|
+
const next = get();
|
|
798
|
+
if (!sameOptions(previous, next))
|
|
799
|
+
onChanged({ options: next, previous });
|
|
794
800
|
},
|
|
795
801
|
getStyle() {
|
|
796
802
|
return current.style;
|
|
@@ -809,6 +815,29 @@ export function createOptions(engine, options, setExternalEntry) {
|
|
|
809
815
|
},
|
|
810
816
|
};
|
|
811
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Whether two copies of the options hold the same values: plain objects and arrays are
|
|
820
|
+
* compared item by item, a function by identity, and a key given as `undefined` is the same
|
|
821
|
+
* as a key left out
|
|
822
|
+
*/
|
|
823
|
+
function sameOptions(a, b) {
|
|
824
|
+
if (Object.is(a, b))
|
|
825
|
+
return true;
|
|
826
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
827
|
+
return (Array.isArray(a) &&
|
|
828
|
+
Array.isArray(b) &&
|
|
829
|
+
a.length === b.length &&
|
|
830
|
+
a.every((item, i) => sameOptions(item, b[i])));
|
|
831
|
+
}
|
|
832
|
+
if (!isRecord(a) || !isRecord(b))
|
|
833
|
+
return false;
|
|
834
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
835
|
+
for (const key of keys) {
|
|
836
|
+
if (!sameOptions(a[key], b[key]))
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
812
841
|
/** A copy of the options that shares the functions and nothing else */
|
|
813
842
|
function structuredCloneOptions(value) {
|
|
814
843
|
if (Array.isArray(value))
|
package/dist/dataset/dataset.js
CHANGED
package/dist/dataset/types.d.ts
CHANGED
|
@@ -306,6 +306,8 @@ export interface Dataset {
|
|
|
306
306
|
setTable(table: Table | PreparedTable): void;
|
|
307
307
|
/** Replaces the style rule (undefined clears it) */
|
|
308
308
|
setStyleRule(rule: StyleRule | undefined): void;
|
|
309
|
+
/** The style rule in effect (undefined when not set) */
|
|
310
|
+
getStyleRule(): StyleRule | undefined;
|
|
309
311
|
/**
|
|
310
312
|
* Replaces the zoom-dependent drawing factors (null clears them)
|
|
311
313
|
*
|
|
@@ -39,7 +39,8 @@ function firstTentativeCoordinate(tentative) {
|
|
|
39
39
|
* little beyond ±180; the rendering and the export deal with that
|
|
40
40
|
* - A drag moves every coordinate of its events by the one shift decided from where it started
|
|
41
41
|
* (`dragStartLngLat`), so the distance dragged never jumps by 360 degrees when the pointer
|
|
42
|
-
* crosses the antimeridian during the drag
|
|
42
|
+
* crosses the antimeridian during the drag. The normalizer gives the positions of a press
|
|
43
|
+
* continuously (the globe's jump from 180 to -180 included), so one shift keeps them so
|
|
43
44
|
*
|
|
44
45
|
* Away from the antimeridian, on the main copy of the world, nothing changes and the event is
|
|
45
46
|
* returned as it is.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 SAKAIDA Atsushi
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
import { nearestLongitude } from '../shared/math/longitude.js';
|
|
3
4
|
import { getModifiers } from './types.js';
|
|
4
5
|
const DEFAULT_OPTIONS = {
|
|
5
6
|
dragThreshold: 3,
|
|
@@ -51,6 +52,20 @@ function createProfiles(opts) {
|
|
|
51
52
|
},
|
|
52
53
|
};
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The position of the pointer, on the copy of the world nearest to where it was last
|
|
57
|
+
*
|
|
58
|
+
* The flat map gives the pointer unwrapped longitudes, which run on past 180 as it crosses the
|
|
59
|
+
* antimeridian. The globe gives them in [-180, 180], so they jump by 360 degrees there: a
|
|
60
|
+
* freehand stroke drawn from them went once round the world along the parallel, and the shift
|
|
61
|
+
* of a feature dragged across jumped by a turn. Within a press each position is therefore
|
|
62
|
+
* taken on the copy nearest to the last one, so the pointer moves on continuously, as on the
|
|
63
|
+
* flat map. A position within 180 degrees of the last one is returned unchanged.
|
|
64
|
+
*/
|
|
65
|
+
function continuing(lngLat, last) {
|
|
66
|
+
const lng = nearestLongitude(lngLat.lng, last.lng);
|
|
67
|
+
return lng === lngLat.lng ? lngLat : { lng, lat: lngLat.lat };
|
|
68
|
+
}
|
|
54
69
|
/**
|
|
55
70
|
* Whether a mouse event is the compatibility event a browser emits for a touch
|
|
56
71
|
*/
|
|
@@ -184,7 +199,8 @@ export function createInputNormalizer(map, options = {}) {
|
|
|
184
199
|
*
|
|
185
200
|
* Once the press has become a drag it stays one until it is released.
|
|
186
201
|
*/
|
|
187
|
-
function movePress(current, point,
|
|
202
|
+
function movePress(current, point, rawLngLat, originalEvent) {
|
|
203
|
+
const lngLat = continuing(rawLngLat, current.lastLngLat);
|
|
188
204
|
current.lastPoint = point;
|
|
189
205
|
current.lastLngLat = lngLat;
|
|
190
206
|
current.lastEvent = originalEvent;
|
|
@@ -204,7 +220,8 @@ export function createInputNormalizer(map, options = {}) {
|
|
|
204
220
|
* Releases the press: dragend (when dragging) and mouseup. Returns whether the release
|
|
205
221
|
* is a click candidate.
|
|
206
222
|
*/
|
|
207
|
-
function releasePress(current, point,
|
|
223
|
+
function releasePress(current, point, rawLngLat, originalEvent) {
|
|
224
|
+
const lngLat = continuing(rawLngLat, current.lastLngLat);
|
|
208
225
|
clearLongPress(current);
|
|
209
226
|
press = null;
|
|
210
227
|
if (current.dragging) {
|
|
@@ -23,6 +23,10 @@ export type LineInput = LineString | MultiLineString | {
|
|
|
23
23
|
* Returns the geometry of a geometry, or of anything that carries one in a `geometry` field
|
|
24
24
|
* (a GeoJSON feature, a feature of the drawing)
|
|
25
25
|
*
|
|
26
|
+
* A value whose `geometry` member is a geometry is read as a feature first, whatever its own
|
|
27
|
+
* `type` says: a feature of the drawing names its feature type there (`'Polygon'`,
|
|
28
|
+
* `'Circle'`, `'Freehand'` and so on), which is not the type of a geometry it is.
|
|
29
|
+
*
|
|
26
30
|
* @throws GeometryError (`invalid-input`) for a value that is neither, and for a feature
|
|
27
31
|
* without a geometry
|
|
28
32
|
*/
|
package/dist/geometry/geojson.js
CHANGED
|
@@ -24,6 +24,10 @@ function isPosition(value) {
|
|
|
24
24
|
* Returns the geometry of a geometry, or of anything that carries one in a `geometry` field
|
|
25
25
|
* (a GeoJSON feature, a feature of the drawing)
|
|
26
26
|
*
|
|
27
|
+
* A value whose `geometry` member is a geometry is read as a feature first, whatever its own
|
|
28
|
+
* `type` says: a feature of the drawing names its feature type there (`'Polygon'`,
|
|
29
|
+
* `'Circle'`, `'Freehand'` and so on), which is not the type of a geometry it is.
|
|
30
|
+
*
|
|
27
31
|
* @throws GeometryError (`invalid-input`) for a value that is neither, and for a feature
|
|
28
32
|
* without a geometry
|
|
29
33
|
*/
|
|
@@ -32,26 +36,31 @@ export function geometryOf(input, operation) {
|
|
|
32
36
|
if (!isObject(value)) {
|
|
33
37
|
throw invalidInput(operation, 'the input is not a geometry or a feature');
|
|
34
38
|
}
|
|
35
|
-
if (
|
|
36
|
-
if (!('geometry' in value) && value.type !== 'Feature') {
|
|
37
|
-
throw invalidInput(operation, 'the input is not a geometry or a feature');
|
|
38
|
-
}
|
|
39
|
+
if ('geometry' in value || value.type === 'Feature') {
|
|
39
40
|
const geometry = value.geometry;
|
|
40
|
-
if (
|
|
41
|
+
if (isObject(geometry) && isGeometryType(geometry.type)) {
|
|
42
|
+
return geometryOf(geometry, operation);
|
|
43
|
+
}
|
|
44
|
+
if (!isGeometry(value)) {
|
|
41
45
|
throw invalidInput(operation, 'the feature has no geometry');
|
|
42
46
|
}
|
|
43
|
-
return geometryOf(geometry, operation);
|
|
44
47
|
}
|
|
45
|
-
if (value.type === 'GeometryCollection') {
|
|
46
|
-
|
|
47
|
-
throw invalidInput(operation, 'the geometry collection has no geometries');
|
|
48
|
-
}
|
|
48
|
+
if (value.type === 'GeometryCollection' && !Array.isArray(value.geometries)) {
|
|
49
|
+
throw invalidInput(operation, 'the geometry collection has no geometries');
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
+
if (!isGeometry(value)) {
|
|
51
52
|
throw invalidInput(operation, 'the input is not a geometry or a feature');
|
|
52
53
|
}
|
|
53
54
|
return value;
|
|
54
55
|
}
|
|
56
|
+
/** Whether an object has the type of a geometry and the member that type carries */
|
|
57
|
+
function isGeometry(value) {
|
|
58
|
+
if (!isGeometryType(value.type))
|
|
59
|
+
return false;
|
|
60
|
+
if (value.type === 'GeometryCollection')
|
|
61
|
+
return Array.isArray(value.geometries);
|
|
62
|
+
return Array.isArray(value.coordinates);
|
|
63
|
+
}
|
|
55
64
|
/** Whether a value names a GeoJSON geometry type */
|
|
56
65
|
function isGeometryType(type) {
|
|
57
66
|
return typeof type === 'string' && (COORDINATE_TYPES.has(type) || type === 'GeometryCollection');
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,20 @@
|
|
|
17
17
|
* });
|
|
18
18
|
* ```
|
|
19
19
|
*
|
|
20
|
+
* Where to find things: every resource of {@link Draw} is a field with its own methods.
|
|
21
|
+
*
|
|
22
|
+
* - `draw.features` (create, update, delete, move, union, split): {@link FeaturesCollection}
|
|
23
|
+
* - `draw.layers` (create, update, reorder, setActive): {@link LayersCollection}
|
|
24
|
+
* - `draw.groups` (create, update, move): {@link GroupsCollection}
|
|
25
|
+
* - `draw.datasets` (add, remove, move): {@link DatasetsCollection}
|
|
26
|
+
* - `draw.selection` (set, add, clear, delete, group): {@link SelectionResource}
|
|
27
|
+
* - `draw.vertexSelection` (set, clear, delete): {@link VertexSelectionResource}
|
|
28
|
+
* - `draw.metadata` and `draw.options` (get, update): {@link MetadataResource}, {@link OptionsResource}
|
|
29
|
+
* - `draw.document` (load, toJSON, toGeoJSON): {@link DocumentResource}
|
|
30
|
+
* - `draw.drawing` (addVertex, finish, cancel): {@link DrawingResource}
|
|
31
|
+
* - `draw.extensions` (plugins, modes, feature types): {@link ExtensionsCollections}
|
|
32
|
+
* - events with `draw.on`: {@link DrawEvents}; errors: {@link DrawError}
|
|
33
|
+
*
|
|
20
34
|
* Read next: [getting started](https://sakuzu.github.io/maplibre-gl-draw/getting-started),
|
|
21
35
|
* then the guides for [drawing](https://sakuzu.github.io/maplibre-gl-draw/guides/drawing) and
|
|
22
36
|
* [saving and loading](https://sakuzu.github.io/maplibre-gl-draw/guides/save-load). The
|
|
@@ -5,13 +5,19 @@ import type { CustomLayerInterface, Map as MapLibreMap } from 'maplibre-gl';
|
|
|
5
5
|
*
|
|
6
6
|
* One rule covers every timing: the slots are added whenever the style accepts layers and a slot
|
|
7
7
|
* is missing. It is checked once now (for a map whose style is already parsed, even while tiles
|
|
8
|
-
* are still loading) and again on every `styledata` event. maplibre fires
|
|
9
|
-
* style finishes loading (before `style.load` and `load`) and after changes to
|
|
10
|
-
* slots are also restored after
|
|
11
|
-
*
|
|
8
|
+
* are still loading) and again on every `styledata` and `style.load` event. maplibre fires
|
|
9
|
+
* `styledata` when a style finishes loading (before `style.load` and `load`) and after changes to
|
|
10
|
+
* the style, so the slots are also restored after a full `setStyle`. A slot that is already on
|
|
11
|
+
* the map is never added twice.
|
|
12
12
|
*
|
|
13
13
|
* The slots are added in order from the first (the backmost), which lines them up in the same
|
|
14
|
-
* order in maplibre (layers added later are on top).
|
|
14
|
+
* order in maplibre (layers added later are on top). A slot missing while a later one is on the
|
|
15
|
+
* map is added just behind it.
|
|
16
|
+
*
|
|
17
|
+
* On `style.load` the slots are also put back on top of the map (see `placeSlotsOnTop`). maplibre
|
|
18
|
+
* fires it when a style is loaded and at the end of a diffed `setStyle` that changed something,
|
|
19
|
+
* and only then: a layer the host adds or moves itself fires `styledata` alone, so a layer the
|
|
20
|
+
* host put above the drawing stays there until the style is changed.
|
|
15
21
|
*
|
|
16
22
|
* @param map The map
|
|
17
23
|
* @param getSlotLayers Returns the current list of slots (first = backmost)
|
|
@@ -11,19 +11,82 @@
|
|
|
11
11
|
function styleAcceptsLayers(map) {
|
|
12
12
|
return map.style?._loaded === true;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Puts the slots back on top of the map after a style change
|
|
16
|
+
*
|
|
17
|
+
* The slots, and the native layers the host placed between them (the separators), form one
|
|
18
|
+
* block that belongs at the top of the map, with the slots in their order (the first is the
|
|
19
|
+
* backmost). A full `setStyle` gives that by itself: the old layers are gone and the slots are
|
|
20
|
+
* added again on top. A diffed `setStyle` does not: maplibre's `Style.serialize()` leaves custom
|
|
21
|
+
* layers out, so the diff neither removes nor adds the slots, and the layers of the new style are
|
|
22
|
+
* added above them, which hides the drawing under the basemap.
|
|
23
|
+
*
|
|
24
|
+
* Nothing is moved when the block is already in place, so the `styledata` that follows a move
|
|
25
|
+
* finds nothing to do. When it is not, the block is moved to the top: each slot, followed by the
|
|
26
|
+
* native layers that sat just above it (up to the next slot), keeps its neighbours. The layers
|
|
27
|
+
* above the frontmost slot are taken as part of the new style and stay below the block.
|
|
28
|
+
*
|
|
29
|
+
* @param map The map
|
|
30
|
+
* @param slotLayers The slots (first = backmost)
|
|
31
|
+
*/
|
|
32
|
+
function placeSlotsOnTop(map, slotLayers) {
|
|
33
|
+
const order = map.getLayersOrder();
|
|
34
|
+
const slotIds = slotLayers.map((layer) => layer.id).filter((id) => order.includes(id));
|
|
35
|
+
if (slotIds.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
const isSlot = new Set(slotIds);
|
|
38
|
+
// The block runs from the backmost slot on the map to the frontmost one
|
|
39
|
+
let first = -1;
|
|
40
|
+
let last = -1;
|
|
41
|
+
order.forEach((id, index) => {
|
|
42
|
+
if (!isSlot.has(id))
|
|
43
|
+
return;
|
|
44
|
+
if (first < 0)
|
|
45
|
+
first = index;
|
|
46
|
+
last = index;
|
|
47
|
+
});
|
|
48
|
+
const block = order.slice(first, last + 1);
|
|
49
|
+
const slotsInBlock = block.filter((id) => isSlot.has(id));
|
|
50
|
+
const inOrder = slotsInBlock.every((id, index) => id === slotIds[index]);
|
|
51
|
+
if (inOrder && last === order.length - 1)
|
|
52
|
+
return;
|
|
53
|
+
// Each slot keeps the native layers that followed it
|
|
54
|
+
const following = new Map();
|
|
55
|
+
let current = '';
|
|
56
|
+
for (const id of block) {
|
|
57
|
+
if (isSlot.has(id)) {
|
|
58
|
+
current = id;
|
|
59
|
+
following.set(id, []);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
following.get(current)?.push(id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const slotId of slotIds) {
|
|
66
|
+
map.moveLayer(slotId);
|
|
67
|
+
for (const id of following.get(slotId) ?? [])
|
|
68
|
+
map.moveLayer(id);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
14
71
|
/**
|
|
15
72
|
* Keeps the frames of the stacking order on the map for as long as the returned detach function has not
|
|
16
73
|
* been called
|
|
17
74
|
*
|
|
18
75
|
* One rule covers every timing: the slots are added whenever the style accepts layers and a slot
|
|
19
76
|
* is missing. It is checked once now (for a map whose style is already parsed, even while tiles
|
|
20
|
-
* are still loading) and again on every `styledata` event. maplibre fires
|
|
21
|
-
* style finishes loading (before `style.load` and `load`) and after changes to
|
|
22
|
-
* slots are also restored after
|
|
23
|
-
*
|
|
77
|
+
* are still loading) and again on every `styledata` and `style.load` event. maplibre fires
|
|
78
|
+
* `styledata` when a style finishes loading (before `style.load` and `load`) and after changes to
|
|
79
|
+
* the style, so the slots are also restored after a full `setStyle`. A slot that is already on
|
|
80
|
+
* the map is never added twice.
|
|
24
81
|
*
|
|
25
82
|
* The slots are added in order from the first (the backmost), which lines them up in the same
|
|
26
|
-
* order in maplibre (layers added later are on top).
|
|
83
|
+
* order in maplibre (layers added later are on top). A slot missing while a later one is on the
|
|
84
|
+
* map is added just behind it.
|
|
85
|
+
*
|
|
86
|
+
* On `style.load` the slots are also put back on top of the map (see `placeSlotsOnTop`). maplibre
|
|
87
|
+
* fires it when a style is loaded and at the end of a diffed `setStyle` that changed something,
|
|
88
|
+
* and only then: a layer the host adds or moves itself fires `styledata` alone, so a layer the
|
|
89
|
+
* host put above the drawing stays there until the style is changed.
|
|
27
90
|
*
|
|
28
91
|
* @param map The map
|
|
29
92
|
* @param getSlotLayers Returns the current list of slots (first = backmost)
|
|
@@ -33,15 +96,28 @@ export function attachSlotLayers(map, getSlotLayers) {
|
|
|
33
96
|
const ensureSlots = () => {
|
|
34
97
|
if (!styleAcceptsLayers(map))
|
|
35
98
|
return;
|
|
36
|
-
|
|
37
|
-
|
|
99
|
+
const slotLayers = getSlotLayers();
|
|
100
|
+
slotLayers.forEach((slotLayer, index) => {
|
|
101
|
+
if (map.getLayer(slotLayer.id))
|
|
102
|
+
return;
|
|
103
|
+
// Behind the next slot that is on the map, so that the slots stay in order
|
|
104
|
+
const next = slotLayers.slice(index + 1).find((later) => map.getLayer(later.id));
|
|
105
|
+
if (next)
|
|
106
|
+
map.addLayer(slotLayer, next.id);
|
|
107
|
+
else
|
|
38
108
|
map.addLayer(slotLayer);
|
|
39
|
-
|
|
40
|
-
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const restoreSlots = () => {
|
|
112
|
+
ensureSlots();
|
|
113
|
+
if (styleAcceptsLayers(map))
|
|
114
|
+
placeSlotsOnTop(map, getSlotLayers());
|
|
41
115
|
};
|
|
42
116
|
map.on('styledata', ensureSlots);
|
|
117
|
+
map.on('style.load', restoreSlots);
|
|
43
118
|
ensureSlots();
|
|
44
119
|
return () => {
|
|
45
120
|
map.off('styledata', ensureSlots);
|
|
121
|
+
map.off('style.load', restoreSlots);
|
|
46
122
|
};
|
|
47
123
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sakuzu/maplibre-gl-draw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "WebGL2 drawing and editing engine for MapLibre GL JS: points, lines, and polygons rendered in a custom layer, with selection, hit testing, and geometry utilities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"maplibre",
|
|
@@ -66,32 +66,44 @@
|
|
|
66
66
|
"workspaces": [
|
|
67
67
|
"examples",
|
|
68
68
|
"playground",
|
|
69
|
-
"bench"
|
|
69
|
+
"bench",
|
|
70
|
+
"ui"
|
|
70
71
|
],
|
|
71
72
|
"scripts": {
|
|
72
73
|
"build": "rm -rf dist tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json",
|
|
73
74
|
"typecheck": "tsc --noEmit",
|
|
74
75
|
"check:spdx": "node scripts/spdx.mjs",
|
|
75
|
-
"lint": "biome check src examples playground bench scripts docs/.vitepress/config.mts && npm run check:layers && npm run check:terms && npm run check:spdx",
|
|
76
|
+
"lint": "biome check src examples playground bench ui scripts docs/.vitepress/config.mts docs/.vitepress/theme && npm run check:layers && npm run check:terms && npm run check:spdx && node ui/scripts/check-imports.mjs",
|
|
76
77
|
"check:layers": "node scripts/check-layers.mjs",
|
|
77
78
|
"check:terms": "node scripts/check-terms.mjs",
|
|
78
79
|
"docs:api": "typedoc --options typedoc.site.json",
|
|
79
80
|
"docs:check": "node scripts/docs-check.mjs",
|
|
80
81
|
"docs:image": "node scripts/capture-readme-image.mjs",
|
|
81
|
-
"
|
|
82
|
+
"data:overture": "node scripts/fetch-overture-sample.mjs",
|
|
83
|
+
"site:thumbnails": "node scripts/need-ui.mjs && node scripts/capture-example-thumbnails.mjs",
|
|
84
|
+
"build:site": "npm run build && npm run ui:build && node scripts/build-site.mjs",
|
|
82
85
|
"deploy:pages": "node scripts/deploy-pages.mjs",
|
|
83
|
-
"lint:fix": "biome check --write src examples playground bench",
|
|
84
|
-
"format": "biome format --write src examples playground bench",
|
|
86
|
+
"lint:fix": "biome check --write src examples playground bench ui",
|
|
87
|
+
"format": "biome format --write src examples playground bench ui",
|
|
85
88
|
"test": "vitest run",
|
|
86
|
-
"test:e2e": "vitest run --config src/e2e/vitest.config.ts",
|
|
89
|
+
"test:e2e": "node scripts/need-ui.mjs && vitest run --config src/e2e/vitest.config.ts",
|
|
87
90
|
"test:watch": "vitest",
|
|
88
|
-
"dev": "npm run dev -w examples",
|
|
91
|
+
"dev": "node scripts/need-ui.mjs && npm run dev -w examples",
|
|
89
92
|
"dev:playground": "npm run dev -w playground",
|
|
90
93
|
"dev:bench": "npm run dev -w bench",
|
|
94
|
+
"ui:build": "npm run build -w ui",
|
|
95
|
+
"ui:typecheck": "npm run typecheck -w ui",
|
|
96
|
+
"ui:test": "npm run test -w ui",
|
|
97
|
+
"ui:test:e2e": "npm run test:e2e -w ui",
|
|
98
|
+
"ui:lint": "npm run lint -w ui",
|
|
99
|
+
"ui:dev": "npm run dev -w ui",
|
|
100
|
+
"ui:check": "npm run ui:typecheck && npm run ui:test && npm run ui:test:e2e && npm run ui:build",
|
|
101
|
+
"ui:check:package": "publint ui && rm -rf ui/.pack && mkdir -p ui/.pack && npm pack -w ui --ignore-scripts --pack-destination ui/.pack >/dev/null && attw ui/.pack/*.tgz --profile esm-only --exclude-entrypoints ./style.css && rm -rf ui/.pack",
|
|
91
102
|
"check:package": "publint && rm -rf .pack && mkdir -p .pack && npm pack --ignore-scripts --pack-destination .pack >/dev/null && attw .pack/*.tgz --profile esm-only && node --input-type=module -e \"await import('./dist/index.js'); await import('./dist/geometry/index.js'); await import('./dist/table/index.js'); await import('./dist/webgl/index.js'); console.log('dist imports ok')\" && rm -rf .pack",
|
|
92
103
|
"prepack": "npm run build",
|
|
93
|
-
"site:dev": "
|
|
94
|
-
"site:
|
|
104
|
+
"site:dev": "node scripts/site-dev.mjs",
|
|
105
|
+
"site:dev:docs": "npm run docs:api && vitepress dev docs --port 5173 --strictPort",
|
|
106
|
+
"site:build": "node scripts/need-ui.mjs && npm run docs:api && vitepress build docs",
|
|
95
107
|
"site:preview": "vitepress preview docs"
|
|
96
108
|
},
|
|
97
109
|
"dependencies": {
|