@abi-software/flatmap-viewer 2.5.0-a.1 → 2.5.1

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/README.rst CHANGED
@@ -38,7 +38,7 @@ The map server endpoint is specified as ``MAP_ENDPOINT`` in ``src/main.js``. It
38
38
  Package Installation
39
39
  ====================
40
40
 
41
- * ``npm install @abi-software/flatmap-viewer@2.5.0-a.1``
41
+ * ``npm install @abi-software/flatmap-viewer@2.5.1``
42
42
 
43
43
  Documentation
44
44
  -------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/flatmap-viewer",
3
- "version": "2.5.0-a.1",
3
+ "version": "2.5.1",
4
4
  "description": "Flatmap viewer using Maplibre GL",
5
5
  "repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
6
6
  "main": "src/main.js",
@@ -17,11 +17,12 @@
17
17
  "author": "David Brooks",
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
+ "@abi-software/mapbox-gl-draw-freehand-mode": "^2.2.0",
20
21
  "@babel/runtime": "^7.10.4",
21
22
  "@deck.gl/core": "^8.9.33",
22
23
  "@deck.gl/layers": "^8.9.33",
23
24
  "@deck.gl/mapbox": "^8.9.33",
24
- "@fortawesome/fontawesome-free": "^6.4.0",
25
+ "@mapbox/mapbox-gl-draw": "^1.4.3",
25
26
  "@turf/area": "^6.5.0",
26
27
  "@turf/bbox": "^6.5.0",
27
28
  "@turf/helpers": "^6.5.0",
@@ -0,0 +1,351 @@
1
+ /******************************************************************************
2
+
3
+ Flatmap viewer and annotation tool
4
+
5
+ Copyright (c) 2019 - 2023 David Brooks
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ **/
20
+
21
+ /*
22
+ * Annotation drawing mode is enabled/disabled by:
23
+ *
24
+ * 1. A call to ``Flatmap.enableAnnotation()``
25
+ * 2. An on-map control button calls this when in standalone viewing mode.
26
+ *
27
+ * Drawn features include a GeoJSON geometry. Existing geometries of annotated
28
+ * features are added to the MapboxDraw control when the map is loaded. These
29
+ * should only be visible on the map when the draw control is active.
30
+ *
31
+ * We listen for drawn features being created, updated and deleted, and notify
32
+ * the external annotator, first assigning new features and ID wrt the flatmap.
33
+ * The external annotator may reject a new feature (the user's cancelled the
34
+ * resulting dialog) which results in the newly drawn feature being removed from
35
+ * the control.
36
+ *
37
+ * The external annotator is responsible for saving/obtaining drawn geometries
38
+ * from an annotation service.
39
+ *
40
+ */
41
+
42
+ //==============================================================================
43
+
44
+ import MapboxDraw from "@mapbox/mapbox-gl-draw"
45
+ import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
46
+
47
+ // NB: https://github.com/bemky/mapbox-gl-draw-freehand-mode/issues/25
48
+ import FreehandMode from 'mapbox-gl-draw-freehand-mode'
49
+
50
+ //==============================================================================
51
+
52
+ const drawStyles = [
53
+ {
54
+ 'id': 'highlight-active-points',
55
+ 'type': 'circle',
56
+ 'filter': ['all',
57
+ ['==', '$type', 'Point'],
58
+ ['==', 'meta', 'feature'],
59
+ ['==', 'active', 'true']],
60
+ 'paint': {
61
+ 'circle-radius': 7,
62
+ 'circle-color': '#080'
63
+ }
64
+ },
65
+ {
66
+ 'id': 'points-are-red',
67
+ 'type': 'circle',
68
+ 'filter': ['all',
69
+ ['==', '$type', 'Point'],
70
+ ['==', 'meta', 'feature'],
71
+ ['==', 'active', 'false']],
72
+ 'paint': {
73
+ 'circle-radius': 5,
74
+ 'circle-color': '#800'
75
+ }
76
+ },
77
+ // ACTIVE (being drawn)
78
+ // line stroke
79
+ {
80
+ "id": "gl-draw-line",
81
+ "type": "line",
82
+ "filter": ["all", ["==", "$type", "LineString"], ["!=", "mode", "static"]],
83
+ "layout": {
84
+ "line-cap": "round",
85
+ "line-join": "round"
86
+ },
87
+ "paint": {
88
+ "line-color": "#D20C0C",
89
+ "line-dasharray": [0.2, 2],
90
+ "line-width": 2
91
+ }
92
+ },
93
+ // polygon fill
94
+ {
95
+ "id": "gl-draw-polygon-fill",
96
+ "type": "fill",
97
+ "filter": ["all", ["==", "$type", "Polygon"], ["!=", "mode", "static"]],
98
+ "paint": {
99
+ 'fill-color': [
100
+ 'case',
101
+ ['boolean', ['feature-state', 'active'], false], '#D88',
102
+ '#020C0C'
103
+ ],
104
+ "fill-outline-color": "#D20C0C",
105
+ "fill-opacity": 0.1
106
+ }
107
+ },
108
+ // polygon mid points
109
+ {
110
+ 'id': 'gl-draw-polygon-midpoint',
111
+ 'type': 'circle',
112
+ 'filter': ['all',
113
+ ['==', '$type', 'Point'],
114
+ ['==', 'meta', 'midpoint']],
115
+ 'paint': {
116
+ 'circle-radius': 3,
117
+ 'circle-color': '#fbb03b'
118
+ }
119
+ },
120
+ // polygon outline stroke
121
+ // This doesn't style the first edge of the polygon, which uses the line stroke styling instead
122
+ {
123
+ "id": "gl-draw-polygon-stroke-active",
124
+ "type": "line",
125
+ "filter": ["all", ["==", "$type", "Polygon"], ["!=", "mode", "static"]],
126
+ "layout": {
127
+ "line-cap": "round",
128
+ "line-join": "round"
129
+ },
130
+ "paint": {
131
+ "line-color": "#D20C0C",
132
+ "line-dasharray": [0.2, 2],
133
+ "line-width": 2
134
+ }
135
+ },
136
+ // vertex point halos
137
+ {
138
+ "id": "gl-draw-polygon-and-line-vertex-halo-active",
139
+ "type": "circle",
140
+ "filter": ["all", ["==", "meta", "vertex"], ["==", "$type", "Point"], ["!=", "mode", "static"]],
141
+ "paint": {
142
+ "circle-radius": 5,
143
+ "circle-color": "#FFF"
144
+ }
145
+ },
146
+ // vertex points
147
+ {
148
+ "id": "gl-draw-polygon-and-line-vertex-active",
149
+ "type": "circle",
150
+ "filter": ["all", ["==", "meta", "vertex"], ["==", "$type", "Point"], ["!=", "mode", "static"]],
151
+ "paint": {
152
+ "circle-radius": 3,
153
+ "circle-color": "#D20C0C",
154
+ }
155
+ },
156
+
157
+ // INACTIVE (static, already drawn)
158
+ // line stroke
159
+ {
160
+ "id": "gl-draw-line-static",
161
+ "type": "line",
162
+ "filter": ["all", ["==", "$type", "LineString"], ["==", "mode", "static"]],
163
+ "layout": {
164
+ "line-cap": "round",
165
+ "line-join": "round"
166
+ },
167
+ "paint": {
168
+ "line-color": "#000",
169
+ "line-width": 3
170
+ }
171
+ },
172
+ // polygon fill
173
+ {
174
+ "id": "gl-draw-polygon-fill-static",
175
+ "type": "fill",
176
+ "filter": ["all", ["==", "$type", "Polygon"], ["==", "mode", "static"]],
177
+ "paint": {
178
+ "fill-color": "#000",
179
+ "fill-outline-color": "#000",
180
+ "fill-opacity": 0.1
181
+ }
182
+ },
183
+ // polygon outline
184
+ {
185
+ "id": "gl-draw-polygon-stroke-static",
186
+ "type": "line",
187
+ "filter": ["all", ["==", "$type", "Polygon"], ["==", "mode", "static"]],
188
+ "layout": {
189
+ "line-cap": "round",
190
+ "line-join": "round"
191
+ },
192
+ "paint": {
193
+ "line-color": "#000",
194
+ "line-width": 3
195
+ }
196
+ }
197
+ ]
198
+
199
+ //==============================================================================
200
+
201
+ const drawStyleIds = drawStyles.map(s => s.id)
202
+
203
+ export const DRAW_ANNOTATION_LAYERS = [...drawStyleIds.map(id => `${id}.cold`),
204
+ ...drawStyleIds.map(id => `${id}.hot`)]
205
+
206
+ //==============================================================================
207
+
208
+ export class AnnotationDrawControl
209
+ {
210
+ #visible
211
+
212
+ constructor(flatmap, visible=false)
213
+ {
214
+ MapboxDraw.constants.classes.CONTROL_BASE = 'maplibregl-ctrl'
215
+ MapboxDraw.constants.classes.CONTROL_PREFIX = 'maplibregl-ctrl-'
216
+ MapboxDraw.constants.classes.CONTROL_GROUP = 'maplibregl-ctrl-group'
217
+
218
+ this.__flatmap = flatmap
219
+ this.#visible = visible
220
+ this.__draw = new MapboxDraw({
221
+ displayControlsDefault: false,
222
+ controls: {
223
+ point: true,
224
+ line_string: true,
225
+ polygon: true,
226
+ trash: true
227
+ },
228
+ userProperties: true,
229
+ keybindings: true,
230
+ modes: {
231
+ ...MapboxDraw.modes,
232
+ draw_polygon: FreehandMode
233
+ },
234
+ styles: drawStyles
235
+ })
236
+ this.__map = null
237
+ }
238
+
239
+ onAdd(map)
240
+ //========
241
+ {
242
+ this.__map = map
243
+ this.__container = this.__draw.onAdd(map)
244
+
245
+ // Fix to allow deletion with Del Key when default trash icon is not shown.
246
+ // See https://github.com/mapbox/mapbox-gl-draw/issues/989
247
+ this.__draw.options.controls.trash = true
248
+
249
+ // Prevent firefox menu from appearing on Alt key up
250
+ window.addEventListener('keyup', function (e) {
251
+ if (e.key === "Alt") {
252
+ e.preventDefault();
253
+ }
254
+ }, false)
255
+ map.on('draw.create', this.createdFeature.bind(this))
256
+ map.on('draw.delete', this.deletedFeature.bind(this))
257
+ map.on('draw.update', this.updatedFeature.bind(this))
258
+ this.show(this.#visible)
259
+ return this.__container
260
+ }
261
+
262
+ onRemove()
263
+ //========
264
+ {
265
+ this.__container.parentNode.removeChild(this.__container)
266
+ this.__container = null
267
+ this.__map = null
268
+ }
269
+
270
+ show(visible=true)
271
+ //================
272
+ {
273
+ if (this.__container) {
274
+ this.__container.style.display = visible ? 'block' : 'none'
275
+ if (visible && !this.#visible) {
276
+ for (const layerId of DRAW_ANNOTATION_LAYERS) {
277
+ this.__map.setLayoutProperty(layerId, 'visibility', 'visible')
278
+ }
279
+ } else if (!visible && this.#visible) {
280
+ for (const layerId of DRAW_ANNOTATION_LAYERS) {
281
+ this.__map.setLayoutProperty(layerId, 'visibility', 'none')
282
+ }
283
+ }
284
+ }
285
+ this.#visible = visible
286
+ }
287
+
288
+ #cleanFeature(event)
289
+ //==================
290
+ {
291
+ const features = event.features.filter(f => f.type === 'Feature')
292
+ .map(f => {
293
+ return {
294
+ id: f.id,
295
+ geometry: f.geometry
296
+ // properties
297
+ }
298
+ })
299
+ return features.length ? features[0] : null
300
+ }
301
+
302
+ createdFeature(event)
303
+ //===================
304
+ {
305
+ const feature = this.#cleanFeature(event)
306
+ if (feature) {
307
+ // Set properties to indicate that this is a drawn annotation
308
+ this.__draw.setFeatureProperty(feature.id, 'drawn', true)
309
+ this.__draw.setFeatureProperty(feature.id, 'label', 'Drawn annotation')
310
+ // They also need to be on the feature passed to the annotator
311
+ // for storage
312
+ feature.properties = {
313
+ user_drawn: true,
314
+ user_label: 'Drawn annotation'
315
+ }
316
+ this.__flatmap.annotationDrawEvent('created', feature)
317
+ }
318
+ }
319
+
320
+ deletedFeature(event)
321
+ //===================
322
+ {
323
+ const feature = this.#cleanFeature(event)
324
+ if (feature) {
325
+ this.__flatmap.annotationDrawEvent('deleted', feature.id)
326
+ }
327
+ }
328
+
329
+ updatedFeature(event)
330
+ //===================
331
+ {
332
+ const feature = this.#cleanFeature(event)
333
+ if (feature) {
334
+ this.__flatmap.annotationDrawEvent('updated', feature)
335
+ }
336
+ }
337
+
338
+ addFeature(feature)
339
+ //=================
340
+ {
341
+ this.__draw.add(feature)
342
+ }
343
+
344
+ removeFeature(feature)
345
+ //====================
346
+ {
347
+ this.__draw.delete(feature.id)
348
+ }
349
+ }
350
+
351
+ //==============================================================================
@@ -526,6 +526,74 @@ export class NerveControl
526
526
 
527
527
  //==============================================================================
528
528
 
529
+ export class AnnotatorControl
530
+ {
531
+ #enabled = false
532
+
533
+ constructor(flatmap)
534
+ {
535
+ this.__flatmap = flatmap
536
+ this.__map = null
537
+ }
538
+
539
+ getDefaultPosition()
540
+ //==================
541
+ {
542
+ return 'top-right'
543
+ }
544
+
545
+ onAdd(map)
546
+ //========
547
+ {
548
+ this.__map = map;
549
+ this.__container = document.createElement('div');
550
+ this.__container.className = 'maplibregl-ctrl';
551
+
552
+ this.__button = document.createElement('button');
553
+ this.__button.id = 'map-annotated-button';
554
+ this.__button.className = 'control-button text-button';
555
+ this.__button.setAttribute('type', 'button');
556
+ this.__button.setAttribute('aria-label', 'Draw on map for annotation');
557
+ this.__button.textContent = 'DRAW';
558
+ this.__button.title = 'Draw on map for annotation';
559
+ this.__container.appendChild(this.__button);
560
+
561
+ this.__container.addEventListener('click', this.onClick_.bind(this));
562
+ this.__setBackground();
563
+ return this.__container;
564
+ }
565
+
566
+ __setBackground()
567
+ //===============
568
+ {
569
+ if (this.#enabled) {
570
+ this.__button.setAttribute('style', 'background: red');
571
+ } else {
572
+ this.__button.removeAttribute('style');
573
+ }
574
+ }
575
+
576
+ onRemove()
577
+ //========
578
+ {
579
+ this.__container.parentNode.removeChild(this.__container)
580
+ this.__map = null
581
+ }
582
+
583
+ onClick_(event)
584
+ //=============
585
+ {
586
+ if (event.target.id === 'map-annotated-button') {
587
+ this.#enabled = !this.#enabled
588
+ this.__setBackground()
589
+ this.__flatmap.showAnnotator(this.#enabled)
590
+ }
591
+ event.stopPropagation();
592
+ }
593
+ }
594
+
595
+ //==============================================================================
596
+
529
597
  export class BackgroundControl
530
598
  {
531
599
  constructor(flatmap)
@@ -151,13 +151,8 @@ export class MinimapControl
151
151
  container: container,
152
152
  style: map.getStyle(),
153
153
  bounds: map.getBounds()
154
-
155
154
  });
156
155
 
157
- // Finish initialising once the map has loaded
158
-
159
- this._miniMap.on('load', this.load_.bind(this));
160
-
161
156
  return this._container;
162
157
  }
163
158
 
@@ -169,8 +164,8 @@ export class MinimapControl
169
164
  this._container = null;
170
165
  }
171
166
 
172
- load_()
173
- //=====
167
+ initialise()
168
+ //==========
174
169
  {
175
170
  const opts = this._options;
176
171
  const parentMap = this._map;
@@ -184,7 +179,7 @@ export class MinimapControl
184
179
  ];
185
180
  interactions.forEach(i => miniMap[i].disable());
186
181
 
187
- // Set background if specified (defaults is the parent map's)
182
+ // Set background if specified (default is the parent map's)
188
183
 
189
184
  if (this._background !== null) {
190
185
  miniMap.setPaintProperty('background', 'background-color', this._background);
@@ -0,0 +1,90 @@
1
+ /******************************************************************************
2
+
3
+ Flatmap viewer and annotation tool
4
+
5
+ Copyright (c) 2019 - 2024 David Brooks
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ ******************************************************************************/
20
+
21
+ export class Path3DControl
22
+ {
23
+ #button
24
+ #container
25
+ #enabled = false
26
+ #map = null
27
+ #flatmap
28
+
29
+ constructor(flatmap)
30
+ {
31
+ this.#flatmap = flatmap
32
+ }
33
+
34
+ getDefaultPosition()
35
+ //==================
36
+ {
37
+ return 'top-right'
38
+ }
39
+
40
+ onAdd(map)
41
+ //========
42
+ {
43
+ this.#map = map
44
+ this.#container = document.createElement('div')
45
+ this.#container.className = 'maplibregl-ctrl'
46
+ this.#button = document.createElement('button')
47
+ this.#button.className = 'control-button text-button'
48
+ this.#button.setAttribute('type', 'button')
49
+ this.#button.setAttribute('aria-label', 'Show 3D paths')
50
+ this.#button.textContent = '3D'
51
+ this.#button.title = 'Show/hide 3D paths'
52
+ this.#container.appendChild(this.#button)
53
+ this.#container.addEventListener('click', this.onClick.bind(this))
54
+ return this.#container
55
+ }
56
+
57
+ onRemove()
58
+ //========
59
+ {
60
+ this.#container.parentNode.removeChild(this.#container)
61
+ this.#map = undefined
62
+ }
63
+
64
+ __setBackground()
65
+ //===============
66
+ {
67
+ if (this.#enabled) {
68
+ this.#button.setAttribute('style', 'background: red');
69
+ } else {
70
+ this.#button.removeAttribute('style');
71
+ }
72
+ }
73
+
74
+ onClick(_event)
75
+ //=============
76
+ {
77
+ if (this.#button.classList.contains('control-active')) {
78
+ this.#flatmap.enable3dPaths(false)
79
+ this.#button.classList.remove('control-active')
80
+ this.#enabled = false
81
+ } else {
82
+ this.#flatmap.enable3dPaths(true)
83
+ this.#button.classList.add('control-active')
84
+ this.#enabled = true
85
+ }
86
+ this.__setBackground()
87
+ }
88
+ }
89
+
90
+ //==============================================================================