@planet/maps 8.0.0 → 8.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.
Files changed (95) hide show
  1. package/LICENSE +202 -0
  2. package/config.js +32 -0
  3. package/lib/Map.js +73 -0
  4. package/lib/Overlay.js +24 -0
  5. package/lib/View.js +24 -0
  6. package/lib/control/Attribution.js +24 -0
  7. package/lib/control/Control.js +24 -0
  8. package/lib/control/FullScreen.js +24 -0
  9. package/lib/control/MousePosition.js +24 -0
  10. package/lib/control/OverviewMap.js +24 -0
  11. package/lib/control/Rotate.js +24 -0
  12. package/lib/control/ScaleLine.js +24 -0
  13. package/lib/control/Zoom.js +24 -0
  14. package/lib/control/ZoomSlider.js +24 -0
  15. package/lib/control/ZoomToExtent.js +24 -0
  16. package/lib/interaction/DoubleClickZoom.js +24 -0
  17. package/lib/interaction/DragAndDrop.js +24 -0
  18. package/lib/interaction/DragBox.js +24 -0
  19. package/lib/interaction/DragPan.js +24 -0
  20. package/lib/interaction/DragRotate.js +24 -0
  21. package/lib/interaction/DragRotateAndZoom.js +28 -0
  22. package/lib/interaction/DragZoom.js +24 -0
  23. package/lib/interaction/Draw.js +24 -0
  24. package/lib/interaction/Extent.js +24 -0
  25. package/lib/interaction/Interaction.js +24 -0
  26. package/lib/interaction/KeyboardPan.js +24 -0
  27. package/lib/interaction/KeyboardZoom.js +24 -0
  28. package/lib/interaction/Link.js +24 -0
  29. package/lib/interaction/Modify.js +24 -0
  30. package/lib/interaction/MouseWheelZoom.js +24 -0
  31. package/lib/interaction/PinchRotate.js +24 -0
  32. package/lib/interaction/PinchZoom.js +24 -0
  33. package/lib/interaction/Pointer.js +24 -0
  34. package/lib/interaction/Property.js +24 -0
  35. package/lib/interaction/Select.js +24 -0
  36. package/lib/interaction/Snap.js +24 -0
  37. package/lib/interaction/Translate.js +24 -0
  38. package/lib/layer/Base.js +29 -0
  39. package/lib/layer/BaseImage.js +29 -0
  40. package/lib/layer/BaseTile.js +29 -0
  41. package/lib/layer/BaseVector.js +29 -0
  42. package/lib/layer/Graticule.js +29 -0
  43. package/lib/layer/Group.js +29 -0
  44. package/lib/layer/Heatmap.js +29 -0
  45. package/lib/layer/Image.js +29 -0
  46. package/lib/layer/Layer.js +29 -0
  47. package/lib/layer/MapboxVector.js +29 -0
  48. package/lib/layer/Tile.js +29 -0
  49. package/lib/layer/Vector.js +29 -0
  50. package/lib/layer/VectorImage.js +29 -0
  51. package/lib/layer/VectorTile.js +29 -0
  52. package/lib/layer/WebGLPoints.js +29 -0
  53. package/lib/layer/WebGLTile.js +29 -0
  54. package/lib/source/BingMaps.js +24 -0
  55. package/lib/source/CartoDB.js +24 -0
  56. package/lib/source/Cluster.js +24 -0
  57. package/lib/source/DataTile.js +24 -0
  58. package/lib/source/GeoTIFF.js +24 -0
  59. package/lib/source/IIIF.js +24 -0
  60. package/lib/source/Image.js +24 -0
  61. package/lib/source/ImageArcGISRest.js +24 -0
  62. package/lib/source/ImageCanvas.js +24 -0
  63. package/lib/source/ImageMapGuide.js +24 -0
  64. package/lib/source/ImageStatic.js +24 -0
  65. package/lib/source/ImageWMS.js +24 -0
  66. package/lib/source/OGCMapTile.js +24 -0
  67. package/lib/source/OGCVectorTile.js +24 -0
  68. package/lib/source/OSM.js +24 -0
  69. package/lib/source/Raster.js +24 -0
  70. package/lib/source/Source.js +24 -0
  71. package/lib/source/Stamen.js +24 -0
  72. package/lib/source/Tile.js +24 -0
  73. package/lib/source/TileArcGISRest.js +24 -0
  74. package/lib/source/TileDebug.js +24 -0
  75. package/lib/source/TileImage.js +24 -0
  76. package/lib/source/TileJSON.js +24 -0
  77. package/lib/source/TileWMS.js +24 -0
  78. package/lib/source/UTFGrid.js +24 -0
  79. package/lib/source/UrlTile.js +24 -0
  80. package/lib/source/Vector.js +24 -0
  81. package/lib/source/VectorTile.js +24 -0
  82. package/lib/source/WMTS.js +24 -0
  83. package/lib/source/XYZ.js +24 -0
  84. package/lib/source/Zoomify.js +24 -0
  85. package/package.json +103 -17
  86. package/readme.md +44 -35
  87. package/renderer/render.js +313 -0
  88. package/renderer/update.js +98 -0
  89. package/.npmignore +0 -2
  90. package/common.js +0 -582
  91. package/debug.js +0 -91520
  92. package/embed.js +0 -376
  93. package/explorer.js +0 -517
  94. package/ol.css +0 -241
  95. package/ol.min.js +0 -1012
package/readme.md CHANGED
@@ -1,60 +1,69 @@
1
- # Planet Maps
1
+ # @planet/maps
2
2
 
3
- Custom builds of OpenLayers 3.
3
+ ![Test Status](https://github.com/planetlabs/maps/actions/workflows/test.yml/badge.svg)
4
+
5
+ Declarative mapping components
4
6
 
5
7
  ## Use
6
8
 
7
- Install `planet-maps` as a dependency with [`npm`](http://nodejs.org/).
9
+ ```
10
+ npm install @planet/maps
11
+ ```
8
12
 
9
- npm install planet-maps --save-dev
13
+ See the [planetlabs.github.io/maps](https://planetlabs.github.io/maps/) website for more details and examples using the library.
10
14
 
11
- Use [Browserify](http://browserify.org/) to `require` OpenLayers 3.
15
+ ## Background
12
16
 
13
- ```js
14
- // see below for a list of custom builds
15
- var ol = require('@planet/maps/common');
16
- ```
17
+ React lets you build interactive UIs with (mostly) declarative syntax. OpenLayers provides an imperative API for building mapping apps. It can be awkward to map React's component API to an imperative API. However, the React team provides a package for creating custom renderers: [`react-reconciler`](https://www.npmjs.com/package/react-reconciler). This is how they integrate with imperative APIs themselves. The `react-dom` package uses `react-reconciler` to provide a mapping to the imperative DOM API. `react-native` uses `react-reconciler` to map to imperative native APIs.
17
18
 
18
- You'll also want to import the stylesheet:
19
+ This library provides declarative mapping components representing imperative APIs from OpenLayers.
19
20
 
20
- ```css
21
- /* Make sure to use the path to your node_modules */
22
- @import url('./node_modules/planet-maps/ol.css');
23
- ```
21
+ ## Design Goals
24
22
 
25
- ## Builds
23
+ The purpose of this project is to provide a mapping between React's declarative components and OpenLayers' imperative API. In other words, this project provides a React renderer for OpenLayers.
26
24
 
27
- ### `common`
25
+ Here are some of the goals of this project:
28
26
 
29
- ```js
30
- var ol = require('@planet/maps/common');
31
- ```
27
+ * Ideally, the renderer would not have any additional dependencies beyond what is bundled with an OpenLayers map.
28
+ * Components exported by this package map 1:1 with classes exported by OpenLayers.
29
+ * Component props map directly to properties that are settable on instances of OpenLayers classes. Exceptions to this are props like `options` (passed to the constructor only), listener props (e.g. `onChange`), and `ref`.
30
+ * Components accept a `ref` that provide access to the underlying OpenLayers instance.
31
+ * The renderer should add negligible overhead in terms of bundle size, memory consumption, or processing.
32
+ * Code components can be generated by using OpenLayers sources. Exceptions to this are the map component (responsible for invoking the custom renderer) and components that map to classes without subclasses (e.g. the view).
32
33
 
33
- Support for everything required by Scenes, Mosaics, Labs, etc. See `config/common.json` for details on what is included.
34
+ ## Non-Goals
34
35
 
35
- ### `explorer`
36
+ This project does not intend to provide any of the following:
36
37
 
37
- ```js
38
- var ol = require('@planet/maps/explorer');
39
- ```
38
+ * Higher level abstractions or components that combine logic from multiple OpenLayers instances (e.g. an editable layer component that wraps a layer, source, and editing interactions).
39
+ * Styled widgets (e.g. a layer switcher/browser).
40
+ * Other utilities for working with vector data, projections, etc.
40
41
 
41
- Support for everything required by Planet Explorer. See `config/explorer.json` for details on what is included.
42
+ We hope that this package can provide the foundation for other, higher-level libraries with more opinions about the look and feel of mapping components.
42
43
 
43
- ### `debug`
44
+ ## Development
44
45
 
45
- This is a debug build that should never be used in production.
46
+ The development setup depends on [Node 18](https://nodejs.org/) (most things work on 16, but not all).
46
47
 
47
- ## Publishing a new release
48
+ Install project dependencies:
48
49
 
49
- Edit the `config` files to include what you need exported and commit the changes. Then you'll want to bump the version number in `package.json`, commit this change, and create a tag. This should be done with the `npm version` command (choose one of `patch`, `minor`, or `major`). E.g.
50
+ ```bash
51
+ npm install
52
+ ```
50
53
 
51
- npm version minor
54
+ Start the development server:
55
+ ```bash
56
+ npm start
57
+ ```
58
+
59
+ Run the tests:
60
+ ```bash
61
+ npm test
62
+ ```
52
63
 
53
- Next you'll want to push your commits (and the tag) and publish your changes to npmjs.org.
64
+ The rendering tests use Playwright for visual snapshot comparison. See the [`tests/rendering/readme.md`](tests/rendering/readme.md) for more detail.
54
65
 
55
- git push --tags origin master
56
- npm publish
57
66
 
58
- Before publishing, the `prepublish` step will run `make`. This will create builds that are *not* tracked by `git` but that are pushed to the npmjs.org repository for use by consuming packages.
67
+ ## Prior Art
59
68
 
60
- Note the new version number in `package.json` and use it in packages that depend on this one.
69
+ Other projects like [`react-openlayers-fiber`](https://github.com/crubier/react-openlayers-fiber) and [`react-ol-fiber`](https://www.npmjs.com/package/react-ol-fiber) use a similar approach and provided inspiration for this project. The major difference between this project and those is that this project provides importable components for OpenLayers layers, sources, controls, and interactions. The other projects bundle all of OpenLayers in their renderer. So a simple "hello world" map with one of the existing projects is about 1.5 MB while the same with this project is about 460 KB.
@@ -0,0 +1,313 @@
1
+ import BaseLayer from 'ol/layer/Base.js';
2
+ import Control from 'ol/control/Control.js';
3
+ import GroupLayer from 'ol/layer/Group.js';
4
+ import Interaction from 'ol/interaction/Interaction.js';
5
+ import Overlay from 'ol/Overlay.js';
6
+ import ReactReconciler from 'react-reconciler';
7
+ import Source from 'ol/source/Source.js';
8
+ import View from 'ol/View.js';
9
+ import {CONTROL, INTERACTION, LAYER, OVERLAY, SOURCE, VIEW} from '../config.js';
10
+ import {
11
+ ConcurrentRoot,
12
+ DefaultEventPriority,
13
+ } from 'react-reconciler/constants.js';
14
+ import {
15
+ prepareControlUpdate,
16
+ prepareInteractionUpdate,
17
+ prepareLayerUpdate,
18
+ prepareOverlayUpdate,
19
+ prepareSourceUpdate,
20
+ prepareViewUpdate,
21
+ reservedProps,
22
+ } from './update.js';
23
+
24
+ const listenerRegex = /^on([A-Z].*)/;
25
+
26
+ function upperFirst(str) {
27
+ return str[0].toUpperCase() + str.slice(1);
28
+ }
29
+
30
+ function setterName(str) {
31
+ return 'set' + upperFirst(str);
32
+ }
33
+
34
+ const knownTypes = {
35
+ [VIEW]: true,
36
+ [OVERLAY]: true,
37
+ [CONTROL]: true,
38
+ [INTERACTION]: true,
39
+ [LAYER]: true,
40
+ [SOURCE]: true,
41
+ };
42
+
43
+ export function updateInstanceFromProps(instance, props, oldProps = {}) {
44
+ for (const key in props) {
45
+ if (reservedProps[key]) {
46
+ continue;
47
+ }
48
+ if (listenerRegex.test(key)) {
49
+ const listener = props[key];
50
+ const type = key.replace(listenerRegex, '$1').toLowerCase();
51
+ instance.on(type, listener);
52
+ if (oldProps[key]) {
53
+ instance.un(type, oldProps[key]);
54
+ }
55
+ continue;
56
+ }
57
+
58
+ const setter = setterName(key);
59
+ if (typeof instance[setter] === 'function') {
60
+ instance[setter](props[key]);
61
+ continue;
62
+ }
63
+
64
+ if (key === 'features' && typeof instance.addFeatures === 'function') {
65
+ // TODO: there is likely a smarter way to diff features
66
+ instance.clear(true);
67
+ instance.addFeatures(props[key]);
68
+ continue;
69
+ }
70
+
71
+ if (
72
+ key === 'interactions' &&
73
+ typeof instance.addInteraction === 'function'
74
+ ) {
75
+ instance.getInteractions().clear();
76
+ props[key].forEach(interaction => instance.addInteraction(interaction));
77
+ continue;
78
+ }
79
+ if (key === 'controls' && typeof instance.addControl === 'function') {
80
+ instance.getControls().clear();
81
+ props[key].forEach(control => instance.addControl(control));
82
+ continue;
83
+ }
84
+
85
+ throw new Error(`Cannot update '${key}' property`);
86
+ }
87
+ }
88
+
89
+ function createInstance(type, {cls: Constructor, ...props}) {
90
+ if (!knownTypes[type]) {
91
+ throw new Error(`Unsupported element type: ${type}`);
92
+ }
93
+ if (!Constructor) {
94
+ throw new Error(`No constructor for type: ${type}`);
95
+ }
96
+
97
+ const instance = new Constructor(props.options || {});
98
+ updateInstanceFromProps(instance, props);
99
+ return instance;
100
+ }
101
+
102
+ function createTextInstance() {
103
+ throw new Error('Cannot add text to the map');
104
+ }
105
+
106
+ function appendChildToContainer(map, child) {
107
+ if (child instanceof View) {
108
+ map.setView(child);
109
+ return;
110
+ }
111
+ if (child instanceof Overlay) {
112
+ map.addOverlay(child);
113
+ return;
114
+ }
115
+ if (child instanceof Control) {
116
+ map.addControl(child);
117
+ return;
118
+ }
119
+ if (child instanceof Interaction) {
120
+ map.addInteraction(child);
121
+ return;
122
+ }
123
+ if (child instanceof BaseLayer) {
124
+ map.addLayer(child);
125
+ return;
126
+ }
127
+ throw new Error(`Cannot add child to the map: ${child}`);
128
+ }
129
+
130
+ function appendChild(parent, child) {
131
+ if (child instanceof Source) {
132
+ if (!(parent instanceof BaseLayer)) {
133
+ throw new Error(`Cannot add source to ${parent}`);
134
+ }
135
+ parent.setSource(child);
136
+ return;
137
+ }
138
+ // Layer groups are an instance of a layer
139
+ if (child instanceof BaseLayer && parent instanceof GroupLayer) {
140
+ parent.getLayers().push(child);
141
+ return;
142
+ }
143
+ throw new Error(`Cannot add ${child} to ${parent}`);
144
+ }
145
+
146
+ const updaters = {
147
+ [VIEW]: prepareViewUpdate,
148
+ [OVERLAY]: prepareOverlayUpdate,
149
+ [CONTROL]: prepareControlUpdate,
150
+ [INTERACTION]: prepareInteractionUpdate,
151
+ [LAYER]: prepareLayerUpdate,
152
+ [SOURCE]: prepareSourceUpdate,
153
+ };
154
+
155
+ function prepareUpdate(instance, type, oldProps, newProps) {
156
+ const updater = updaters[type];
157
+ if (!updater) {
158
+ throw new Error(`Unsupported element type: ${type}`);
159
+ }
160
+ return updater(instance, type, oldProps, newProps);
161
+ }
162
+
163
+ function commitUpdate(instance, updatePayload, type, oldProps) {
164
+ updateInstanceFromProps(instance, updatePayload, oldProps);
165
+ }
166
+
167
+ function removeChildFromContainer(map, child) {
168
+ if (child instanceof View) {
169
+ map.setView(child);
170
+ return;
171
+ }
172
+ if (child instanceof Overlay) {
173
+ map.removeOverlay(child);
174
+ return;
175
+ }
176
+ if (child instanceof Control) {
177
+ map.removeControl(child);
178
+ return;
179
+ }
180
+ if (child instanceof Interaction) {
181
+ map.removeInteraction(child);
182
+ return;
183
+ }
184
+ if (child instanceof BaseLayer) {
185
+ map.removeLayer(child);
186
+ return;
187
+ }
188
+ throw new Error(`Cannot remove child from the map: ${child}`);
189
+ }
190
+
191
+ function removeChild(parent, child) {
192
+ // this happens with group layers
193
+ if (child instanceof BaseLayer && parent.getLayers) {
194
+ parent.getLayers().remove(child);
195
+ return;
196
+ }
197
+ throw new Error(`TODO: implement removeChild for ${parent} and ${child}`);
198
+ }
199
+
200
+ function clearContainer(map) {
201
+ map.getLayers().clear();
202
+ // Need to leave the default controls and interactions.
203
+ // TODO: determine when this gets called.
204
+ }
205
+
206
+ function insertInCollection(collection, child, beforeChild) {
207
+ const index = collection.getArray().indexOf(beforeChild);
208
+ if (index < 0) {
209
+ collection.push(child);
210
+ } else {
211
+ collection.insertAt(index, child);
212
+ }
213
+ }
214
+
215
+ function insertInContainerBefore(map, child, beforeChild) {
216
+ if (child instanceof View) {
217
+ map.setView(child);
218
+ return;
219
+ }
220
+ let collection;
221
+ if (child instanceof Overlay) {
222
+ collection = map.getOverlays();
223
+ } else if (child instanceof Control) {
224
+ collection = map.getControls();
225
+ } else if (child instanceof Interaction) {
226
+ collection = map.getInteractions();
227
+ } else if (child instanceof BaseLayer) {
228
+ collection = map.getLayers();
229
+ }
230
+ if (collection) {
231
+ insertInCollection(collection, child, beforeChild);
232
+ }
233
+ }
234
+
235
+ function insertBefore(parent, child, beforeChild) {
236
+ if (child instanceof BaseLayer && parent instanceof GroupLayer) {
237
+ insertInCollection(parent.getLayers(), child, beforeChild);
238
+ return;
239
+ }
240
+ throw new Error(`Cannot insert child ${child} into parent ${parent}`);
241
+ }
242
+
243
+ const reconciler = ReactReconciler({
244
+ supportsMutation: true,
245
+ isPrimaryRenderer: false,
246
+ createInstance,
247
+ createTextInstance,
248
+ appendChildToContainer,
249
+ appendChild,
250
+ appendInitialChild: appendChild,
251
+ prepareUpdate,
252
+ commitUpdate,
253
+ clearContainer,
254
+ removeChildFromContainer,
255
+ removeChild,
256
+
257
+ insertInContainerBefore,
258
+ insertBefore,
259
+
260
+ finalizeInitialChildren() {
261
+ return false;
262
+ },
263
+
264
+ getChildHostContext() {},
265
+
266
+ getPublicInstance(instance) {
267
+ return instance;
268
+ },
269
+
270
+ getRootHostContext() {},
271
+
272
+ getCurrentEventPriority() {
273
+ return DefaultEventPriority;
274
+ },
275
+
276
+ prepareForCommit() {
277
+ return null;
278
+ },
279
+
280
+ resetAfterCommit() {},
281
+
282
+ shouldSetTextContent() {
283
+ return false;
284
+ },
285
+
286
+ detachDeletedInstance() {},
287
+ });
288
+
289
+ const roots = new Map();
290
+
291
+ export function render(element, container) {
292
+ let root = roots.get(container);
293
+ if (!root) {
294
+ const logRecoverableError =
295
+ typeof reportError === 'function'
296
+ ? reportError // eslint-disable-line no-undef
297
+ : console.error; // eslint-disable-line no-console
298
+
299
+ root = reconciler.createContainer(
300
+ container,
301
+ ConcurrentRoot,
302
+ null,
303
+ false,
304
+ null,
305
+ '',
306
+ logRecoverableError,
307
+ null
308
+ );
309
+ roots.set(container, root);
310
+ }
311
+
312
+ reconciler.updateContainer(element, root, null, null);
313
+ }
@@ -0,0 +1,98 @@
1
+ function arrayEquals(a, b) {
2
+ if (a.length !== b.length) {
3
+ return false;
4
+ }
5
+ for (let i = 0; i < a.length; i++) {
6
+ if (a[i] !== b[i]) {
7
+ return false;
8
+ }
9
+ }
10
+ return true;
11
+ }
12
+
13
+ export const reservedProps = {
14
+ children: true,
15
+ cls: true,
16
+ options: true,
17
+ };
18
+
19
+ export function prepareViewUpdate(view, type, oldProps, newProps) {
20
+ if (view.getAnimating()) {
21
+ return null;
22
+ }
23
+ const payload = {};
24
+ let needsUpdate = false;
25
+ for (const key in newProps) {
26
+ if (reservedProps[key]) {
27
+ continue;
28
+ }
29
+ if (key === 'center') {
30
+ if (
31
+ !(
32
+ arrayEquals(oldProps.center, newProps.center) ||
33
+ arrayEquals(view.getCenter(), newProps.center)
34
+ )
35
+ ) {
36
+ payload.center = newProps.center;
37
+ needsUpdate = true;
38
+ }
39
+ continue;
40
+ }
41
+ if (key === 'zoom') {
42
+ if (
43
+ !(oldProps.zoom === newProps.zoom || view.getZoom() === newProps.zoom)
44
+ ) {
45
+ payload.zoom = newProps.zoom;
46
+ needsUpdate = true;
47
+ }
48
+ continue;
49
+ }
50
+ if (key === 'rotation') {
51
+ if (
52
+ !(
53
+ oldProps.rotation === newProps.rotation ||
54
+ view.getRotation() === newProps.rotation
55
+ )
56
+ ) {
57
+ payload.rotation = newProps.rotation;
58
+ needsUpdate = true;
59
+ }
60
+ continue;
61
+ }
62
+ if (newProps[key] !== oldProps[key]) {
63
+ payload[key] = newProps[key];
64
+ needsUpdate = true;
65
+ }
66
+ }
67
+ if (!needsUpdate) {
68
+ return null;
69
+ }
70
+ return payload;
71
+ }
72
+
73
+ function prepareGenericUpdate(instance, type, oldProps, newProps) {
74
+ const payload = {};
75
+ let needsUpdate = false;
76
+ for (const key in newProps) {
77
+ if (reservedProps[key]) {
78
+ continue;
79
+ }
80
+
81
+ if (newProps[key] !== oldProps[key]) {
82
+ payload[key] = newProps[key];
83
+ needsUpdate = true;
84
+ }
85
+ }
86
+ if (!needsUpdate) {
87
+ return null;
88
+ }
89
+ return payload;
90
+ }
91
+
92
+ export {
93
+ prepareGenericUpdate as prepareOverlayUpdate,
94
+ prepareGenericUpdate as prepareLayerUpdate,
95
+ prepareGenericUpdate as prepareSourceUpdate,
96
+ prepareGenericUpdate as prepareControlUpdate,
97
+ prepareGenericUpdate as prepareInteractionUpdate,
98
+ };
package/.npmignore DELETED
@@ -1,2 +0,0 @@
1
- /Makefile
2
- /config/