@abi-software/flatmap-viewer 2.7.2 → 2.7.3-a.2

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 (120) hide show
  1. package/dist/assets/index.css +1 -0
  2. package/dist/index.js +80577 -0
  3. package/dist/lib/index.d.ts +4 -0
  4. package/package.json +10 -7
  5. package/lib/index.ts +0 -10
  6. package/src/contextmenu.js +0 -97
  7. package/src/controls/annotation.js +0 -302
  8. package/src/controls/controls.js +0 -645
  9. package/src/controls/flightpaths.js +0 -95
  10. package/src/controls/info.js +0 -291
  11. package/src/controls/minimap.js +0 -442
  12. package/src/controls/paths.js +0 -143
  13. package/src/controls/search.js +0 -113
  14. package/src/controls/systems.js +0 -75
  15. package/src/controls/taxons.js +0 -73
  16. package/src/flatmap-viewer.js +0 -1789
  17. package/src/images.js +0 -66
  18. package/src/interactions.js +0 -1569
  19. package/src/layers/cluster.js +0 -177
  20. package/src/layers/filter.js +0 -310
  21. package/src/layers/flightpaths.js +0 -383
  22. package/src/layers/index.js +0 -478
  23. package/src/layers/styling.js +0 -1077
  24. package/src/main.js +0 -272
  25. package/src/mapserver.js +0 -64
  26. package/src/mathjax.js +0 -100
  27. package/src/pathways.js +0 -427
  28. package/src/search.js +0 -137
  29. package/src/systems.js +0 -146
  30. package/src/utils.js +0 -152
  31. package/static/css/flatmap-viewer.css +0 -238
  32. package/static/icons/favicon.ico +0 -0
  33. package/static/images/active.png +0 -0
  34. package/static/images/inactive.png +0 -0
  35. package/static/images/reset-map-active.png +0 -0
  36. package/static/images/reset-map-button.png +0 -0
  37. package/static/images/rounded-background.png +0 -0
  38. package/static/images/zoom-in-active.png +0 -0
  39. package/static/images/zoom-in-button.png +0 -0
  40. package/static/images/zoom-out-active.png +0 -0
  41. package/static/images/zoom-out-button.png +0 -0
  42. package/thirdParty/maplibre-gl-svg/CHANGELOG.md +0 -13
  43. package/thirdParty/maplibre-gl-svg/LICENSE +0 -21
  44. package/thirdParty/maplibre-gl-svg/LICENSE.md +0 -21
  45. package/thirdParty/maplibre-gl-svg/README.md +0 -24
  46. package/thirdParty/maplibre-gl-svg/assets/Add custom SVG template to template manager.jpg +0 -0
  47. package/thirdParty/maplibre-gl-svg/assets/All built-in SVG templates as HTML markers.jpg +0 -0
  48. package/thirdParty/maplibre-gl-svg/assets/All built-in SVG templates as symbols.jpg +0 -0
  49. package/thirdParty/maplibre-gl-svg/assets/Fill polygon with built-in SVG template.jpg +0 -0
  50. package/thirdParty/maplibre-gl-svg/assets/HTML Marker with Custom SVG Template.jpg +0 -0
  51. package/thirdParty/maplibre-gl-svg/assets/HTML Marker with built-in SVG template.jpg +0 -0
  52. package/thirdParty/maplibre-gl-svg/assets/Line layer with built-in SVG template.jpg +0 -0
  53. package/thirdParty/maplibre-gl-svg/assets/Load SVG from URL.jpg +0 -0
  54. package/thirdParty/maplibre-gl-svg/assets/SVG template options.jpg +0 -0
  55. package/thirdParty/maplibre-gl-svg/assets/Smiley_face_changed.svg +0 -37
  56. package/thirdParty/maplibre-gl-svg/assets/Symbol layer with built-in SVG template.jpg +0 -0
  57. package/thirdParty/maplibre-gl-svg/assets/image-templates/arrow-up-thin.png +0 -0
  58. package/thirdParty/maplibre-gl-svg/assets/image-templates/arrow-up.png +0 -0
  59. package/thirdParty/maplibre-gl-svg/assets/image-templates/car.png +0 -0
  60. package/thirdParty/maplibre-gl-svg/assets/image-templates/checker-rotated.png +0 -0
  61. package/thirdParty/maplibre-gl-svg/assets/image-templates/checker.png +0 -0
  62. package/thirdParty/maplibre-gl-svg/assets/image-templates/circles-spaced.png +0 -0
  63. package/thirdParty/maplibre-gl-svg/assets/image-templates/circles.png +0 -0
  64. package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-lines-down.png +0 -0
  65. package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-lines-up.png +0 -0
  66. package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-stripes-down.png +0 -0
  67. package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-stripes-up.png +0 -0
  68. package/thirdParty/maplibre-gl-svg/assets/image-templates/dots.png +0 -0
  69. package/thirdParty/maplibre-gl-svg/assets/image-templates/flag-triangle.png +0 -0
  70. package/thirdParty/maplibre-gl-svg/assets/image-templates/flag.png +0 -0
  71. package/thirdParty/maplibre-gl-svg/assets/image-templates/grid-lines.png +0 -0
  72. package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon-rounded-thick.png +0 -0
  73. package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon-rounded.png +0 -0
  74. package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon-thick.png +0 -0
  75. package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon.png +0 -0
  76. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-arrow.png +0 -0
  77. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-ball-pin.png +0 -0
  78. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-circle.png +0 -0
  79. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-flat.png +0 -0
  80. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square-cluster.png +0 -0
  81. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square-rounded-cluster.png +0 -0
  82. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square-rounded.png +0 -0
  83. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square.png +0 -0
  84. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-thick.png +0 -0
  85. package/thirdParty/maplibre-gl-svg/assets/image-templates/marker.png +0 -0
  86. package/thirdParty/maplibre-gl-svg/assets/image-templates/pin-round.png +0 -0
  87. package/thirdParty/maplibre-gl-svg/assets/image-templates/pin.png +0 -0
  88. package/thirdParty/maplibre-gl-svg/assets/image-templates/rotated-grid-lines.png +0 -0
  89. package/thirdParty/maplibre-gl-svg/assets/image-templates/rotated-grid-stripes.png +0 -0
  90. package/thirdParty/maplibre-gl-svg/assets/image-templates/rounded-square-thick.png +0 -0
  91. package/thirdParty/maplibre-gl-svg/assets/image-templates/rounded-square.png +0 -0
  92. package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle-arrow-left.png +0 -0
  93. package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle-arrow-up.png +0 -0
  94. package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle-thick.png +0 -0
  95. package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle.png +0 -0
  96. package/thirdParty/maplibre-gl-svg/assets/image-templates/x-fill.png +0 -0
  97. package/thirdParty/maplibre-gl-svg/assets/image-templates/zig-zag-vertical.png +0 -0
  98. package/thirdParty/maplibre-gl-svg/assets/image-templates/zig-zag.png +0 -0
  99. package/thirdParty/maplibre-gl-svg/build/build.js +0 -210
  100. package/thirdParty/maplibre-gl-svg/dist/maplibre-gl-svg.js +0 -339
  101. package/thirdParty/maplibre-gl-svg/dist/maplibre-gl-svg.min.js +0 -4
  102. package/thirdParty/maplibre-gl-svg/docs/docs.md +0 -375
  103. package/thirdParty/maplibre-gl-svg/examples/Add custom SVG template to template manager.html +0 -101
  104. package/thirdParty/maplibre-gl-svg/examples/All built-in SVG templates as HTML markers.html +0 -82
  105. package/thirdParty/maplibre-gl-svg/examples/All built-in SVG templates as symbols.html +0 -124
  106. package/thirdParty/maplibre-gl-svg/examples/Fill polygon with built-in SVG template.html +0 -94
  107. package/thirdParty/maplibre-gl-svg/examples/HTML Marker with Custom SVG Template.html +0 -86
  108. package/thirdParty/maplibre-gl-svg/examples/HTML Marker with built-in SVG template.html +0 -83
  109. package/thirdParty/maplibre-gl-svg/examples/Line layer with built-in SVG template.html +0 -129
  110. package/thirdParty/maplibre-gl-svg/examples/Load SVG from URL.html +0 -96
  111. package/thirdParty/maplibre-gl-svg/examples/SVG template options.html +0 -264
  112. package/thirdParty/maplibre-gl-svg/examples/Symbol layer with built-in SVG template.html +0 -93
  113. package/thirdParty/maplibre-gl-svg/index.html +0 -151
  114. package/thirdParty/maplibre-gl-svg/package-lock.json +0 -5882
  115. package/thirdParty/maplibre-gl-svg/package.json +0 -49
  116. package/thirdParty/maplibre-gl-svg/src/SvgManager.ts +0 -186
  117. package/thirdParty/maplibre-gl-svg/src/SvgTemplateManager.ts +0 -144
  118. package/thirdParty/maplibre-gl-svg/src/index.ts +0 -4
  119. package/thirdParty/maplibre-gl-svg/tsconfig.json +0 -31
  120. package/thirdParty/maplibre-gl-svg/typings/index.d.ts +0 -111
@@ -1,1569 +0,0 @@
1
- /******************************************************************************
2
-
3
- Flatmap viewer and annotation tool
4
-
5
- Copyright (c) 2019 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
- 'use strict';
22
-
23
- //==============================================================================
24
-
25
- import maplibregl from 'maplibre-gl';
26
-
27
- import {default as turfArea} from '@turf/area';
28
- import {default as turfBBox} from '@turf/bbox';
29
- import * as turf from '@turf/helpers';
30
- import * as turfProjection from '@turf/projection';
31
-
32
- import polylabel from 'polylabel';
33
-
34
- //==============================================================================
35
-
36
- import {LayerManager} from './layers';
37
- import {PATHWAYS_LAYER, PathManager} from './pathways';
38
- import {VECTOR_TILES_SOURCE} from './layers/styling';
39
- import {SystemsManager} from './systems';
40
-
41
- import {displayedProperties, InfoControl} from './controls/info';
42
- import {AnnotatorControl, BackgroundControl, LayerControl, NerveControl,
43
- SCKANControl} from './controls/controls';
44
- import {AnnotationDrawControl, DRAW_ANNOTATION_LAYERS} from './controls/annotation'
45
- import {PathControl} from './controls/paths';
46
- import {FlightPathControl} from './controls/flightpaths'
47
- import {SearchControl} from './controls/search';
48
- import {MinimapControl} from './controls/minimap';
49
- import {SystemsControl} from './controls/systems';
50
- import {TaxonsControl} from './controls/taxons';
51
- import {latex2Svg} from './mathjax';
52
-
53
- import * as utils from './utils';
54
-
55
- //==============================================================================
56
-
57
-
58
- // smallest `group` features when zoom < SHOW_DETAILS_ZOOM if there are some, otherwise smallest feature
59
- // if no non-group features then smallest group one
60
-
61
- const SHOW_DETAILS_ZOOM = 6;
62
-
63
- //==============================================================================
64
-
65
- function bounds(feature)
66
- //======================
67
- {
68
- // Find the feature's bounding box
69
-
70
- let bounds = ('bounds' in feature.properties) ? feature.properties.bounds
71
- : feature.properties.bbox;
72
- if (bounds) {
73
- // Bounding box is defined in GeoJSON
74
-
75
- return JSON.parse(bounds);
76
- } else {
77
- // Get the bounding box of the current polygon. This won't neccessary
78
- // be the full feature because of tiling
79
-
80
- const polygon = turf.geometry(feature.geometry.type, feature.geometry.coordinates);
81
- return turfBBox(polygon);
82
- }
83
- }
84
-
85
- //==============================================================================
86
-
87
- function expandBounds(bbox1, bbox2, padding)
88
- //==========================================
89
- {
90
- return (bbox1 === null) ? [bbox2[0]-padding.lng, bbox2[1]-padding.lat,
91
- bbox2[2]+padding.lng, bbox2[3]+padding.lat]
92
- : [Math.min(bbox1[0], bbox2[0]-padding.lng), Math.min(bbox1[1], bbox2[1]-padding.lat),
93
- Math.max(bbox1[2], bbox2[2]+padding.lng), Math.max(bbox1[3], bbox2[3]+padding.lat)
94
- ];
95
- }
96
-
97
- //==============================================================================
98
-
99
- function labelPosition(feature)
100
- {
101
- if (feature.geometry.type === 'Point') {
102
- return feature.geometry.coordinates
103
- }
104
- const polygon = feature.geometry.coordinates;
105
- // Rough heuristic. Area is in km^2; below appears to be good enough.
106
- const precision = ('area' in feature.properties)
107
- ? Math.sqrt(feature.properties.area)/500000
108
- : 0.1;
109
- return polylabel(polygon, precision);
110
- }
111
-
112
- //==============================================================================
113
-
114
- function getRenderedLabel(properties)
115
- {
116
- if (!('renderedLabel' in properties)) {
117
- const label = ('label' in properties) ? properties.label
118
- : ('user_label' in properties) ? properties.user_label
119
- : ''
120
- const uppercaseLabel = (label !== '') ? (label.substr(0, 1).toUpperCase()
121
- + label.substr(1)).replaceAll("\n", "<br/>")
122
- : ''
123
- properties.renderedLabel = uppercaseLabel.replaceAll(/`\$([^\$]*)\$`/g, math => latex2Svg(math.slice(2, -2)));
124
- }
125
- return properties.renderedLabel;
126
- }
127
-
128
- //==============================================================================
129
-
130
- export class UserInteractions
131
- {
132
- #annotationDrawControl = null
133
- #minimap = null
134
-
135
- constructor(flatmap)
136
- {
137
- this._flatmap = flatmap;
138
- this._map = flatmap.map;
139
-
140
- this._activeFeatures = new Set()
141
- this._selectedFeatureIds = new Map();
142
- this._currentPopup = null;
143
- this._infoControl = null;
144
- this._tooltip = null;
145
-
146
- this._inQuery = false;
147
- this._modal = false;
148
-
149
- // Default colour settings
150
- this.__colourOptions = {colour: true, outline: true};
151
-
152
- // Marker placement and interaction
153
-
154
- this.__activeMarker = null;
155
- this.__lastMarkerId = 900000;
156
- this.__markerIdByMarker = new Map();
157
- this.__markerIdByFeatureId = new Map();
158
- this.__annotationByMarkerId = new Map();
159
-
160
- // Where to put labels and popups on a feature
161
- this.__markerPositions = new Map();
162
-
163
- // Fit the map to its initial position
164
-
165
- flatmap.setInitialPosition();
166
-
167
- // Track enabled features
168
-
169
- this.__featureEnabledCount = new Map(Array.from(this._flatmap.annotations.keys()).map(k => [+k, 0]));
170
-
171
- const featuresEnabled = flatmap.options.style !== 'functional';
172
-
173
- // Path visibility is either controlled externally or by a local control
174
- // FC path visiblitity is determined by system visiblity
175
-
176
- this.__pathManager = new PathManager(flatmap, this, featuresEnabled);
177
-
178
- // The path types in this map
179
-
180
- const mapPathTypes = this.__pathManager.pathTypes();
181
-
182
- // Add and manage our layers. NB. this needs to after we have a
183
- // path manager but before paths are enabled
184
-
185
- this._layerManager = new LayerManager(flatmap, this);
186
-
187
- // Set initial enabled state of paths
188
-
189
- this.__pathManager.enablePathLines(true, true)
190
-
191
- for (const path of mapPathTypes) {
192
- this.__pathManager.enablePathsByType(path.type, path.enabled, true);
193
- }
194
- if (this.__pathManager.haveCentrelines) {
195
- this.enableCentrelines(this.__pathManager.enabledCentrelines, true);
196
- }
197
-
198
- // Note features that are FC systems
199
- this.__systemsManager = new SystemsManager(this._flatmap, this, featuresEnabled);
200
-
201
- // All taxons of connectivity paths are enabled by default
202
- this.__enabledConnectivityTaxons = new Set(this._flatmap.taxonIdentifiers);
203
-
204
- // Add a minimap if option set
205
- if (flatmap.options.minimap) {
206
- this.#minimap = new MinimapControl(flatmap, flatmap.options.minimap);
207
- this._map.addControl(this.#minimap);
208
- }
209
-
210
- // Do we want a fullscreen control?
211
- if (flatmap.options.fullscreenControl === true) {
212
- this._map.addControl(new maplibregl.FullscreenControl(), 'top-right');
213
- }
214
-
215
- // Add navigation controls if option set
216
- if (flatmap.options.navigationControl) {
217
- const value = flatmap.options.navigationControl;
218
- const position = ((typeof value === 'string')
219
- && ['top-left', 'top-right', 'bottom-right', 'bottom-left'].includes(value))
220
- ? value : 'bottom-right';
221
- this._map.addControl(new NavigationControl(flatmap), position);
222
- }
223
-
224
- // Add various controls when running standalone
225
- if (flatmap.options.standalone) {
226
- // Add a control to search annotations if option set
227
- this._map.addControl(new SearchControl(flatmap));
228
-
229
- // Show information about features
230
- this._infoControl = new InfoControl(flatmap);
231
- this._map.addControl(this._infoControl);
232
-
233
- // Control background colour (NB. this depends on having map layers created)
234
- this._map.addControl(new BackgroundControl(flatmap));
235
-
236
- // Add a control to manage our paths
237
- this._map.addControl(new PathControl(flatmap, mapPathTypes));
238
-
239
- // Add a control to manage our layers
240
- this._map.addControl(new LayerControl(flatmap, this._layerManager));
241
-
242
- // Add a control for nerve centrelines if they are present
243
- if (this.__pathManager.haveCentrelines) {
244
- this._map.addControl(new NerveControl(flatmap, this._layerManager, {showCentrelines: false}));
245
- }
246
-
247
- if (flatmap.options.style === 'functional') {
248
- // SCKAN path and SYSTEMS controls for FC maps
249
- this._map.addControl(new SystemsControl(flatmap, this.__systemsManager.systems));
250
- this._map.addControl(new SCKANControl(flatmap, flatmap.options.layerOptions));
251
- } else {
252
- // Connectivity taxon control for AC maps
253
- this._map.addControl(new TaxonsControl(flatmap));
254
- }
255
-
256
- this._map.addControl(new FlightPathControl(this, flatmap.options.flightPaths));
257
-
258
- if (flatmap.options.annotator) {
259
- this._map.addControl(new AnnotatorControl(flatmap));
260
- }
261
- }
262
-
263
- // Initialise map annotation
264
- this.__setupAnnotation()
265
-
266
- // Add an initially hidden tool for drawing on the map.
267
- this.#annotationDrawControl = new AnnotationDrawControl(flatmap, false)
268
- this._map.addControl(this.#annotationDrawControl)
269
-
270
- // Set initial path viewing mode
271
- if (flatmap.options.flightPaths === true) {
272
- this._layerManager.setFlightPathMode(true)
273
- }
274
-
275
- // Handle mouse events
276
-
277
- this._map.on('click', this.clickEvent_.bind(this));
278
- this._map.on('touchend', this.clickEvent_.bind(this));
279
- this._map.on('mousemove', this.mouseMoveEvent_.bind(this));
280
- this._lastFeatureMouseEntered = null;
281
- this._lastFeatureModelsMouse = null;
282
- this.__lastClickLngLat = null;
283
-
284
- // Handle pan/zoom events
285
- this._map.on('move', this.panZoomEvent_.bind(this, 'pan'));
286
- this._map.on('zoom', this.panZoomEvent_.bind(this, 'zoom'));
287
- this.__pan_zoom_enabled = false;
288
- }
289
-
290
- get minimap()
291
- //===========
292
- {
293
- return this.#minimap
294
- }
295
-
296
- get pathManager()
297
- //===============
298
- {
299
- return this.__pathManager;
300
- }
301
-
302
- getState()
303
- //========
304
- {
305
- // Return the map's centre, zoom, and active layers
306
- // Can only be called when the map is fully loaded
307
- return {
308
- center: this._map.getCenter().toArray(),
309
- zoom: this._map.getZoom(),
310
- bearing: this._map.getBearing(),
311
- pitch: this._map.getPitch()
312
- };
313
- }
314
-
315
- setState(state)
316
- //=============
317
- {
318
- // Restore the map to a saved state
319
-
320
- const options = Object.assign({}, state)
321
- if ('zoom' in options) {
322
- if ('center' in options) {
323
- options['around'] = options.center;
324
- } else {
325
- options['around'] = [0, 0];
326
- }
327
- }
328
- if (Object.keys(options).length > 0) {
329
- this._map.jumpTo(options);
330
- }
331
- }
332
-
333
- showAnnotator(visible=true)
334
- //=========================
335
- {
336
- if (this.#annotationDrawControl) {
337
- this.#annotationDrawControl.show(visible)
338
- }
339
- }
340
-
341
- inDrawingAnnotationMode()
342
- //=======================
343
- {
344
- if (this.#annotationDrawControl) {
345
- return this.#annotationDrawControl.inDrawingMode()
346
- }
347
- }
348
-
349
- commitAnnotationEvent(event)
350
- //==========================
351
- {
352
- if (this.#annotationDrawControl) {
353
- this.#annotationDrawControl.commitEvent(event)
354
- }
355
- }
356
-
357
- abortAnnotationEvent(event)
358
- //=========================
359
- {
360
- if (this.#annotationDrawControl) {
361
- this.#annotationDrawControl.abortEvent(event)
362
- }
363
- }
364
-
365
- rollbackAnnotationEvent(event)
366
- //============================
367
- {
368
- if (this.#annotationDrawControl) {
369
- this.#annotationDrawControl.rollbackEvent(event)
370
- }
371
- }
372
-
373
- clearAnnotationFeatures()
374
- //=======================
375
- {
376
- if (this.#annotationDrawControl) {
377
- this.#annotationDrawControl.clearFeatures()
378
- }
379
- }
380
-
381
- removeAnnotationFeature()
382
- //=======================
383
- {
384
- if (this.#annotationDrawControl) {
385
- this.#annotationDrawControl.removeFeature()
386
- }
387
- }
388
-
389
- addAnnotationFeature(feature)
390
- //===========================
391
- {
392
- if (this.#annotationDrawControl) {
393
- this.#annotationDrawControl.addFeature(feature)
394
- }
395
- }
396
-
397
- refreshAnnotationFeatureGeometry(feature)
398
- //=======================================
399
- {
400
- if (this.#annotationDrawControl) {
401
- return this.#annotationDrawControl.refreshGeometry(feature)
402
- }
403
- }
404
-
405
- changeAnnotationDrawMode(type)
406
- //=============================
407
- {
408
- if (this.#annotationDrawControl) {
409
- this.#annotationDrawControl.changeMode(type)
410
- }
411
- }
412
-
413
- __setupAnnotation()
414
- //=================
415
- {
416
- // Relate external annotation identifiers to map (GeoJSON) ids
417
- this.__featureIdToMapId = new Map([...this._flatmap.annotations.entries()]
418
- .map(idAnn => [idAnn[1].id, idAnn[0]]))
419
- // Flag features that have annotations
420
- for (const mapId of this.__featureIdToMapId.values()) {
421
- const feature = this.mapFeature(mapId)
422
- if (feature !== undefined) {
423
- this._map.setFeatureState(feature, { 'map-annotation': true })
424
- }
425
- }
426
- }
427
-
428
- setFeatureAnnotated(featureId)
429
- //============================
430
- {
431
- // External feature id to map's GeoJSON id
432
- const mapId = this.__featureIdToMapId.get(featureId)
433
- if (mapId) {
434
- const feature = this.mapFeature(mapId)
435
- if (feature !== undefined) {
436
- this._map.setFeatureState(feature, { 'annotated': true })
437
- }
438
- }
439
- }
440
-
441
- #setPaint(options)
442
- //================
443
- {
444
- this._layerManager.setPaint(options)
445
- }
446
-
447
- setPaint(options)
448
- //===============
449
- {
450
- this.__colourOptions = options;
451
- this.#setPaint(options);
452
- }
453
-
454
- getLayers()
455
- //=========
456
- {
457
- return this._layerManager.layers;
458
- }
459
-
460
- enableLayer(layerId, enable=true)
461
- //===============================
462
- {
463
- this._layerManager.activate(layerId, enable);
464
- }
465
-
466
- enableFlightPaths(enable=true)
467
- //============================
468
- {
469
- this._layerManager.setFlightPathMode(enable)
470
- }
471
-
472
- getSystems()
473
- //==========
474
- {
475
- return this.__systemsManager.systems;
476
- }
477
-
478
- enableSystem(systemId, enable=true)
479
- //=================================
480
- {
481
- this.__systemsManager.enable(systemId, enable);
482
- }
483
-
484
- mapFeature(featureId)
485
- //===================
486
- {
487
- const ann = this._flatmap.annotation(featureId);
488
- if (ann !== undefined) {
489
- return {
490
- id: featureId,
491
- source: VECTOR_TILES_SOURCE,
492
- sourceLayer: (this._flatmap.options.separateLayers
493
- ? `${ann['layer']}_${ann['tile-layer']}`
494
- : ann['tile-layer']).replaceAll('/', '_'),
495
- children: ann.children || []
496
- };
497
- }
498
- return undefined;
499
- }
500
-
501
- #getFeatureState(feature)
502
- //=======================
503
- {
504
- return this._map.getFeatureState(feature)
505
- }
506
-
507
- #removeFeatureState(feature, key)
508
- //===============================
509
- {
510
- this._map.removeFeatureState(feature, key)
511
- this._layerManager.removeFeatureState(feature, key)
512
- }
513
-
514
- #setFeatureState(feature, state)
515
- //==============================
516
- {
517
- this._map.setFeatureState(feature, state)
518
- this._layerManager.setFeatureState(feature, state)
519
- }
520
-
521
- enableMapFeature(feature, enable=true)
522
- //====================================
523
- {
524
- if (feature !== undefined) {
525
- const state = this.#getFeatureState(feature);
526
- if ('hidden' in state) {
527
- if (enable) {
528
- this.#removeFeatureState(feature, 'hidden');
529
- } else if (!state.hidden) {
530
- this.#setFeatureState(feature, { hidden: true });
531
- }
532
- } else if (!enable) {
533
- this.#setFeatureState(feature, { hidden: true });
534
- }
535
- this.__enableFeatureMarker(feature.id, enable);
536
- }
537
- }
538
-
539
- enableFeature(featureId, enable=true, force=false)
540
- //================================================
541
- {
542
- const enabledCount = this.__featureEnabledCount.get(+featureId)
543
- if (force || enable && enabledCount === 0 || !enable && enabledCount == 1) {
544
- this.enableMapFeature(this.mapFeature(featureId), enable)
545
- }
546
- if (force) {
547
- this.__featureEnabledCount.set(+featureId, enable ? 1 : 0);
548
- } else {
549
- this.__featureEnabledCount.set(+featureId, enabledCount + (enable ? 1 : -1));
550
- }
551
- }
552
-
553
- enableFeatureWithChildren(featureId, enable=true, force=false)
554
- //============================================================
555
- {
556
- const feature = this.mapFeature(featureId);
557
- if (feature !== undefined) {
558
- this.enableFeature(featureId, enable, force);
559
- for (const childFeatureId of feature.children) {
560
- this.enableFeatureWithChildren(childFeatureId, enable, force);
561
- }
562
- }
563
- }
564
-
565
- __enableFeatureMarker(featureId, enable=true)
566
- //===========================================
567
- {
568
- const markerId = this.__markerIdByFeatureId.get(+featureId);
569
- if (markerId !== undefined) {
570
- const markerDiv = document.getElementById(`marker-${markerId}`);
571
- if (markerDiv) {
572
- markerDiv.style.visibility = enable ? 'visible' : 'hidden';
573
- }
574
- }
575
- }
576
-
577
- __featureEnabled(feature)
578
- //=======================
579
- {
580
- if (feature.id) {
581
- const state = this.#getFeatureState(feature);
582
- return (state !== undefined
583
- && (!('hidden' in state) || !state.hidden));
584
- }
585
- return DRAW_ANNOTATION_LAYERS.includes(feature.layer.id)
586
- }
587
-
588
- featureSelected_(featureId)
589
- //=========================
590
- {
591
- return this._selectedFeatureIds.has(+featureId);
592
- }
593
-
594
- selectFeature(featureId, dim=true)
595
- //================================
596
- {
597
- const ann = this._flatmap.annotation(featureId);
598
- if (ann && 'sckan' in ann) {
599
- const sckanState = this._layerManager.sckanState;
600
- if (sckanState === 'none'
601
- || sckanState === 'valid' && !ann.sckan
602
- || sckanState === 'invalid' && ann.sckan) {
603
- return false;
604
- }
605
- }
606
- featureId = +featureId; // Ensure numeric
607
- let result = false;
608
- const noSelection = (this._selectedFeatureIds.size === 0);
609
- if (this._selectedFeatureIds.has(featureId)) {
610
- this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
611
- result = true;
612
- } else {
613
- const feature = this.mapFeature(featureId);
614
- if (feature !== undefined) {
615
- const state = this.#getFeatureState(feature);
616
- if (state !== undefined && (!('hidden' in state) || !state.hidden)) {
617
- this.#setFeatureState(feature, { selected: true });
618
- this._selectedFeatureIds.set(featureId, 1);
619
- result = true;
620
- }
621
- }
622
- }
623
- if (result && noSelection) {
624
- this.#setPaint({...this.__colourOptions, dimmed: dim});
625
- }
626
- return result;
627
- }
628
-
629
- unselectFeature(featureId)
630
- //========================
631
- {
632
- featureId = +featureId; // Ensure numeric
633
- if (this._selectedFeatureIds.has(featureId)) {
634
- const references = this._selectedFeatureIds.get(featureId);
635
- if (references > 1) {
636
- this._selectedFeatureIds.set(featureId, references - 1);
637
- } else {
638
- const feature = this.mapFeature(featureId);
639
- if (feature !== undefined) {
640
- this.#removeFeatureState(feature, 'selected');
641
- this._selectedFeatureIds.delete(+featureId);
642
- }
643
- }
644
- }
645
- if (this._selectedFeatureIds.size === 0) {
646
- this.#setPaint({...this.__colourOptions, dimmed: false});
647
- }
648
- }
649
-
650
- unselectFeatures()
651
- //================
652
- {
653
- for (const featureId of this._selectedFeatureIds.keys()) {
654
- const feature = this.mapFeature(featureId);
655
- if (feature !== undefined) {
656
- this.#removeFeatureState(feature, 'selected');
657
- }
658
- }
659
- this._selectedFeatureIds.clear();
660
- this.#setPaint({...this.__colourOptions, dimmed: false});
661
- }
662
-
663
- activateFeature(feature)
664
- //======================
665
- {
666
- if (feature !== undefined) {
667
- this.#setFeatureState(feature, { active: true });
668
- this._activeFeatures.add(feature);
669
- }
670
- }
671
-
672
- activateLineFeatures(lineFeatures)
673
- //================================
674
- {
675
- for (const lineFeature of lineFeatures) {
676
- const lineFeatureId = +lineFeature.properties.featureId // Ensure numeric
677
- this.activateFeature(lineFeature)
678
- const lineIds = new Set(lineFeatures.map(f => f.properties.featureId))
679
- for (const featureId of this.__pathManager.lineFeatureIds(lineIds)) {
680
- this.activateFeature(this.mapFeature(featureId))
681
- }
682
- }
683
- }
684
-
685
- resetActiveFeatures_()
686
- //====================
687
- {
688
- for (const feature of this._activeFeatures) {
689
- this.#removeFeatureState(feature, 'active');
690
- }
691
- this._activeFeatures.clear()
692
- }
693
-
694
- smallestAnnotatedPolygonFeature_(features)
695
- //========================================
696
- {
697
- // Get the smallest feature from a list of features
698
-
699
- let smallestArea = 0;
700
- let smallestFeature = null;
701
- for (const feature of features) {
702
- if (feature.geometry.type.includes('Polygon')
703
- && this.#getFeatureState(feature)['map-annotation']) {
704
- const polygon = turf.geometry(feature.geometry.type, feature.geometry.coordinates);
705
- const area = turfArea(polygon);
706
- if (smallestFeature === null || smallestArea > area) {
707
- smallestFeature = feature;
708
- smallestArea = area;
709
- }
710
- }
711
- }
712
- return smallestFeature;
713
- }
714
-
715
- setModal_(event)
716
- //==============
717
- {
718
- this._modal = true;
719
- }
720
-
721
- __clearModal(event)
722
- //=================
723
- {
724
- this._modal = false;
725
- }
726
-
727
- reset()
728
- //=====
729
- {
730
- this.__clearModal();
731
- this.__clearActiveMarker();
732
- this.unselectFeatures();
733
- }
734
-
735
- clearSearchResults(reset=true)
736
- //============================
737
- {
738
- this.unselectFeatures();
739
- }
740
-
741
- /**
742
- * Select features on the map.
743
- *
744
- * @param {Array.<string>} featureIds An array of feature identifiers to highlight
745
- */
746
- selectFeatures(featureIds)
747
- //========================
748
- {
749
- if (featureIds.length) {
750
- this.unselectFeatures();
751
- for (const featureId of featureIds) {
752
- const annotation = this._flatmap.annotation(featureId);
753
- if (annotation) {
754
- if (this.selectFeature(featureId)) {
755
- if ('type' in annotation && annotation.type.startsWith('line')) {
756
- for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
757
- this.selectFeature(pathFeatureId);
758
- }
759
- }
760
- }
761
- }
762
- }
763
- }
764
- }
765
-
766
- showSearchResults(featureIds)
767
- //===========================
768
- {
769
- this.unselectFeatures();
770
- this.zoomToFeatures(featureIds, {noZoomIn: true});
771
- }
772
-
773
- /**
774
- * Select features and zoom the map to them.
775
- *
776
- * @param {Array.<string>} featureIds An array of feature identifiers
777
- * @param {Object} [options]
778
- * @param {boolean} [options.noZoomIn=false] Don't zoom in (although zoom out as necessary)
779
- * @param {number} [options.padding=10] Padding in pixels around the composite bounding box
780
- */
781
- zoomToFeatures(featureIds, options=null)
782
- //======================================
783
- {
784
- options = utils.setDefaults(options, {
785
- noZoomIn: false,
786
- padding: 10 // pixels
787
- });
788
- if (featureIds.length) {
789
- this.unselectFeatures();
790
- let bbox = null;
791
- if (options.noZoomIn) {
792
- const bounds = this._map.getBounds().toArray();
793
- bbox = [...bounds[0], ...bounds[1]];
794
- }
795
- // Convert pixel padding to LngLat and apply it to a feature's bounds
796
- const padding = this._map.unproject({x: options.padding, y: options.padding});
797
- padding.lng -= bbox[0];
798
- padding.lat = bbox[3] - padding.lat;
799
- for (const featureId of featureIds) {
800
- const annotation = this._flatmap.annotation(featureId);
801
- if (annotation) {
802
- if (this.selectFeature(featureId)) {
803
- bbox = expandBounds(bbox, annotation.bounds, padding);
804
- if ('type' in annotation && annotation.type.startsWith('line')) {
805
- for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
806
- if (this.selectFeature(pathFeatureId)) {
807
- const pathAnnotation = this._flatmap.annotation(pathFeatureId)
808
- bbox = expandBounds(bbox, pathAnnotation.bounds, padding);
809
- }
810
- }
811
- }
812
- }
813
- }
814
- }
815
- if (bbox !== null) {
816
- this._map.fitBounds(bbox, {
817
- padding: 0,
818
- animate: false
819
- });
820
- }
821
- }
822
- }
823
-
824
- showPopup(featureId, content, options={})
825
- //=======================================
826
- {
827
- const ann = this._flatmap.annotation(featureId);
828
- const drawn = options && options.annotationFeatureGeometry;
829
- if (ann || drawn) { // The feature exists or it is a drawn annotation
830
-
831
- // Remove any existing popup
832
-
833
- if (this._currentPopup) {
834
- if (options && options.preserveSelection) {
835
- this._currentPopup.options.preserveSelection = options.preserveSelection;
836
- }
837
- this._currentPopup.remove();
838
- }
839
-
840
- // Clear selection if we are not preserving it
841
-
842
- if (options && options.preserveSelection) {
843
- delete options.preserveSelection; // Don't pass to onClose()
844
- } else { // via the popup's options
845
- this.unselectFeatures();
846
- }
847
-
848
- // Select the feature
849
-
850
- this.selectFeature(featureId);
851
-
852
- // Find the pop-up's postion
853
-
854
- let location = null;
855
- if ('positionAtLastClick' in options
856
- && options.positionAtLastClick
857
- && this.__lastClickLngLat !== null) {
858
- location = this.__lastClickLngLat;
859
- } else if (drawn) {
860
- // Popup at the centroid of the feature
861
- // Calculated with the feature geometry coordinates
862
- location = options.annotationFeatureGeometry;
863
- } else {
864
- // Position popup at the feature's 'centre'
865
- location = this.__markerPosition(featureId, ann);
866
- }
867
-
868
- // Make sure the feature is on screen
869
-
870
- if (!this._map.getBounds().contains(location)) {
871
- this._map.panTo(location);
872
- }
873
- this.setModal_();
874
- this._currentPopup = new maplibregl.Popup(options).addTo(this._map);
875
- this._currentPopup.on('close', this.__onCloseCurrentPopup.bind(this));
876
- if (drawn) {
877
- this._currentPopup.on('close', this.abortAnnotationEvent.bind(this));
878
- }
879
- this._currentPopup.setLngLat(location);
880
- if (typeof content === 'object') {
881
- this._currentPopup.setDOMContent(content);
882
- } else {
883
- this._currentPopup.setText(content);
884
- }
885
- }
886
- }
887
-
888
- __onCloseCurrentPopup()
889
- //=====================
890
- {
891
- if (this._currentPopup) {
892
- this.__clearModal();
893
- if (!(this._currentPopup.options && this._currentPopup.options.preserveSelection)) {
894
- this.unselectFeatures();
895
- }
896
- this._currentPopup = null;
897
- }
898
- }
899
-
900
- removeTooltip_()
901
- //==============
902
- {
903
- if (this._tooltip) {
904
- this._tooltip.remove();
905
- this._tooltip = null;
906
- }
907
- }
908
-
909
- lineTooltip_(lineFeatures)
910
- //========================
911
- {
912
- const tooltips = [];
913
- for (const lineFeature of lineFeatures) {
914
- const properties = lineFeature.properties;
915
- if ('error' in properties) {
916
- tooltips.push(`<div class="feature-error">Error: ${properties.error}</div>`)
917
- }
918
- if ('warning' in properties) {
919
- tooltips.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
920
- }
921
- if ('label' in properties && (!('tooltip' in properties) || properties.tooltip)) {
922
- const label = properties.label;
923
- const cleanLabel = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
924
- if (!tooltips.includes(cleanLabel)) {
925
- tooltips.push(cleanLabel);
926
- }
927
- }
928
- }
929
- return (tooltips.length === 0) ? ''
930
- : `<div class='flatmap-feature-label'>${tooltips.join('<hr/>')}</div>`;
931
- }
932
-
933
- tooltipHtml_(properties, forceLabel=false)
934
- //========================================
935
- {
936
- const tooltip = [];
937
- if ('error' in properties) {
938
- tooltip.push(`<div class="feature-error">Error: ${properties.error}</div>`)
939
- }
940
- if ('warning' in properties) {
941
- tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
942
- }
943
- let renderedLabel;
944
- if (('label' in properties
945
- || 'hyperlink' in properties
946
- || 'user_label' in properties)
947
- && (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
948
- const renderedLabel = getRenderedLabel(properties);
949
- if ('hyperlink' in properties) {
950
- if (renderedLabel === '') {
951
- tooltip.push(`<a href='${properties.hyperlink}'>${properties.hyperlink}</a>`);
952
- } else {
953
- tooltip.push(`<a href='${properties.hyperlink}'>${renderedLabel}</a></div>`);
954
- }
955
- } else {
956
- tooltip.push(renderedLabel);
957
- }
958
- }
959
- return (tooltip.length === 0) ? ''
960
- : `<div class='flatmap-feature-label'>${tooltip.join('<hr/>')}</div>`;
961
- }
962
-
963
- __featureEvent(type, feature)
964
- //===========================
965
- {
966
- if (feature.sourceLayer === PATHWAYS_LAYER) { // I suspect this is never true as source layer
967
- // names are like `neural_routes_pathways`
968
- return this._flatmap.featureEvent(type, this.__pathManager.pathProperties(feature));
969
- } else if ('properties' in feature) {
970
- return this._flatmap.featureEvent(type, feature.properties);
971
- }
972
- return false;
973
- }
974
-
975
- __resetFeatureDisplay()
976
- //=====================
977
- {
978
- // Remove any existing tooltip
979
- this.removeTooltip_();
980
-
981
- // Reset cursor
982
- this._map.getCanvas().style.cursor = 'default';
983
-
984
- // Reset any active features
985
- this.resetActiveFeatures_();
986
- }
987
-
988
- #renderedFeatures(point)
989
- //======================
990
- {
991
- const features = this._layerManager.featuresAtPoint(point)
992
- return features.filter(feature => this.__featureEnabled(feature));
993
- }
994
-
995
- mouseMoveEvent_(event)
996
- //====================
997
- {
998
- // No tooltip when context menu is open
999
- if (this._modal) {
1000
- return;
1001
- }
1002
-
1003
- // Remove tooltip, reset active features, etc
1004
- this.__resetFeatureDisplay();
1005
-
1006
- // Reset any info display
1007
- const displayInfo = (this._infoControl && this._infoControl.active);
1008
- if (displayInfo) {
1009
- this._infoControl.reset()
1010
- }
1011
-
1012
- // Get all the features at the current point
1013
- const features = this.#renderedFeatures(event.point)
1014
- if (features.length === 0) {
1015
- this._lastFeatureMouseEntered = null;
1016
- this._lastFeatureModelsMouse = null;
1017
- return;
1018
- }
1019
-
1020
- // Simulate `mouseenter` events on features
1021
-
1022
- const feature = features[0];
1023
- const featureModels = ('properties' in feature && 'models' in feature.properties)
1024
- ? feature.properties.models
1025
- : null;
1026
- if (this._lastFeatureMouseEntered !== feature.id
1027
- && (this._lastFeatureModelsMouse === null
1028
- || this._lastFeatureModelsMouse !== featureModels)) {
1029
- if (this.__featureEvent('mouseenter', feature)) {
1030
- this._lastFeatureMouseEntered = feature.id;
1031
- this._lastFeatureModelsMouse = featureModels;
1032
- } else {
1033
- this._lastFeatureMouseEntered = null;
1034
- this._lastFeatureModelsMouse = null;
1035
- }
1036
- }
1037
-
1038
- let info = '';
1039
- let tooltip = '';
1040
- if (displayInfo) {
1041
- if (!('tooltip' in features[0].properties)) {
1042
- this.activateFeature(features[0]);
1043
- }
1044
- info = this._infoControl.featureInformation(features, event.lngLat);
1045
- }
1046
- const lineFeatures = features.filter(feature => ('centreline' in feature.properties
1047
- || ('type' in feature.properties
1048
- && feature.properties.type.startsWith('line')) ));
1049
- let tooltipFeature = null;
1050
- if (lineFeatures.length > 0) {
1051
- tooltip = this.lineTooltip_(lineFeatures);
1052
- tooltipFeature = lineFeatures[0];
1053
- this.activateLineFeatures(lineFeatures)
1054
- } else {
1055
- let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
1056
- || 'label' in feature.properties
1057
- || 'user_label' in feature.properties)
1058
- && (!('tooltip' in feature.properties)
1059
- || feature.properties.tooltip)))
1060
- .sort((a, b) => (a.properties.area - b.properties.area));
1061
- if (labelledFeatures.length > 0) {
1062
- // Favour group features at low zoom levels
1063
- const zoomLevel = this._map.getZoom();
1064
- const groupFeatures = labelledFeatures.filter(feature => (feature.properties.group
1065
- && zoomLevel < (feature.properties.scale + 1)));
1066
- if (groupFeatures.length > 0) {
1067
- labelledFeatures = groupFeatures;
1068
- }
1069
- const feature = labelledFeatures[0];
1070
- if (feature.properties.user_drawn) {
1071
- feature.id = feature.properties.id
1072
- }
1073
- tooltip = this.tooltipHtml_(feature.properties);
1074
- tooltipFeature = feature;
1075
- if (this._flatmap.options.debug) { // Do this when Info on and not debug??
1076
- const debugProperties = [
1077
- 'featureId',
1078
- 'nerveId',
1079
- 'tile-layer',
1080
- 'type',
1081
- ...displayedProperties
1082
- ];
1083
- const htmlList = [];
1084
- const featureIds = [];
1085
- for (const feature of labelledFeatures) {
1086
- if (!featureIds.includes(feature.id)) {
1087
- featureIds.push(feature.id);
1088
- for (const prop of debugProperties) {
1089
- if (prop in feature.properties) {
1090
- htmlList.push(`<span class="info-name">${prop}:</span>`);
1091
- htmlList.push(`<span class="info-value">${feature.properties[prop]}</span>`);
1092
- }
1093
- }
1094
- }
1095
- //htmlList.push(`<span class="info-name">Area:</span>`);
1096
- //htmlList.push(`<span class="info-value">${feature.properties.area/1000000000}</span>`);
1097
- //htmlList.push(`<span class="info-name">Scale:</span>`);
1098
- //htmlList.push(`<span class="info-value">${feature.properties.scale}</span>`);
1099
- }
1100
- if (!this._flatmap.options.debug) {
1101
- info = `<div id="info-control-info">${htmlList.join('\n')}</div>`;
1102
- }
1103
- }
1104
- this.activateFeature(feature);
1105
- this.__activateRelatedFeatures(feature);
1106
- if ('hyperlink' in feature.properties) {
1107
- this._map.getCanvas().style.cursor = 'pointer';
1108
- }
1109
- }
1110
- }
1111
-
1112
- if (info !== '') {
1113
- this._infoControl.show(info);
1114
- }
1115
- this.__showToolTip(tooltip, event.lngLat, tooltipFeature);
1116
- }
1117
-
1118
- __showToolTip(html, lngLat, feature=null)
1119
- //=======================================
1120
- {
1121
- // Show a tooltip
1122
- if (html !== '' || this._flatmap.options.showId && feature !== null) {
1123
- let header = '';
1124
- if (this._flatmap.options.showPosition) {
1125
- const pt = turf.point(lngLat.toArray());
1126
- const gps = turfProjection.toMercator(pt);
1127
- const coords = gps.geometry.coordinates;
1128
- header = (feature === null)
1129
- ? JSON.stringify(coords)
1130
- : `${JSON.stringify(coords)} (${feature.id})`;
1131
- }
1132
- if (this._flatmap.options.showId && feature !== null && 'id' in feature.properties) {
1133
- header = `${header} ${feature.properties.id}`;
1134
- }
1135
- if (header !== '') {
1136
- html = `<span>${header}</span><br/>${html}`;
1137
- }
1138
- if (html !== '') {
1139
- this._tooltip = new maplibregl.Popup({
1140
- closeButton: false,
1141
- closeOnClick: false,
1142
- maxWidth: 'none',
1143
- className: 'flatmap-tooltip-popup'
1144
- });
1145
- this._tooltip
1146
- .setLngLat(lngLat)
1147
- .setHTML(html)
1148
- .addTo(this._map);
1149
- }
1150
- }
1151
- }
1152
-
1153
- selectionEvent_(event, feature)
1154
- //=============================
1155
- {
1156
- if (feature !== undefined) {
1157
- const clickedFeatureId = +feature.id;
1158
- const dim = !('properties' in feature
1159
- && 'kind' in feature.properties
1160
- && ['cell-type', 'scaffold', 'tissue'].includes(feature.properties.kind));
1161
- if (!(event.ctrlKey || event.metaKey)) {
1162
- let selecting = true;
1163
- for (const featureId of this._selectedFeatureIds.keys()) {
1164
- if (featureId === clickedFeatureId) {
1165
- selecting = false;
1166
- break;
1167
- }
1168
- }
1169
- this.unselectFeatures();
1170
- if (selecting) {
1171
- for (const feature of this._activeFeatures) {
1172
- this.selectFeature(feature.id, dim);
1173
- }
1174
- }
1175
- } else {
1176
- const clickedSelected = this.featureSelected_(clickedFeatureId);
1177
- for (const feature of this._activeFeatures) {
1178
- if (clickedSelected) {
1179
- this.unselectFeature(feature.id);
1180
- } else {
1181
- this.selectFeature(feature.id, dim);
1182
- }
1183
- }
1184
- }
1185
- }
1186
- }
1187
-
1188
- clickEvent_(event)
1189
- //================
1190
- {
1191
- if (this._modal) {
1192
- return;
1193
- }
1194
-
1195
- this.__clearActiveMarker();
1196
-
1197
- const clickedFeatures = this.#renderedFeatures(event.point)
1198
- if (clickedFeatures.length == 0) {
1199
- this.unselectFeatures();
1200
- return;
1201
- }
1202
- const inDrawing = this.inDrawingAnnotationMode()
1203
- const clickedDrawnFeature = clickedFeatures.filter((f) => !f.id)[0];
1204
- const clickedFeature = clickedFeatures.filter((f) => f.id)[0];
1205
- this.selectionEvent_(event.originalEvent, clickedFeature);
1206
- if (this._modal) {
1207
- // Remove tooltip, reset active features, etc
1208
- this.__resetFeatureDisplay();
1209
- this.unselectFeatures();
1210
- this.__clearModal();
1211
- } else if (clickedDrawnFeature && !inDrawing) {
1212
- // When feature and drawn feature are coinciding, click on annotation layer by default
1213
- // While in drawing, DISABLE 'click' event on annotation layer
1214
- this.__featureEvent('click', clickedDrawnFeature);
1215
- } else if (clickedFeature) {
1216
- this.__lastClickLngLat = event.lngLat;
1217
- this.__featureEvent('click', clickedFeature);
1218
- if ('properties' in clickedFeature && 'hyperlink' in clickedFeature.properties) {
1219
- window.open(clickedFeature.properties.hyperlink, '_blank');
1220
- }
1221
- }
1222
- }
1223
-
1224
- __activateRelatedFeatures(feature)
1225
- //================================
1226
- {
1227
- if ('nerveId' in feature.properties) {
1228
- const nerveId = feature.properties.nerveId;
1229
- if (nerveId !== feature.id) {
1230
- this.activateFeature(this.mapFeature(nerveId));
1231
- }
1232
- for (const featureId of this.__pathManager.nerveFeatureIds(nerveId)) {
1233
- this.activateFeature(this.mapFeature(featureId));
1234
- }
1235
- }
1236
- if ('nodeId' in feature.properties) {
1237
- for (const featureId of this.__pathManager.pathFeatureIds(feature.properties.nodeId)) {
1238
- this.activateFeature(this.mapFeature(featureId));
1239
- }
1240
- }
1241
- }
1242
-
1243
- enablePathsBySystem(system, enable=true, force=false)
1244
- //===================================================
1245
- {
1246
- this.__pathManager.enablePathsBySystem(system, enable, force);
1247
- }
1248
-
1249
- enablePathsByType(pathType, enable=true)
1250
- //======================================
1251
- {
1252
- this.__pathManager.enablePathsByType(pathType, enable);
1253
- }
1254
-
1255
- pathFeatureIds(externalIds)
1256
- //=========================
1257
- {
1258
- const featureIds = new utils.List();
1259
- featureIds.extend(this.__pathManager.connectivityModelFeatureIds(externalIds));
1260
- featureIds.extend(this.__pathManager.pathModelFeatureIds(externalIds));
1261
- return featureIds;
1262
- }
1263
-
1264
- pathModelNodes(modelId)
1265
- //=====================
1266
- {
1267
- return this.__pathManager.pathModelNodes(modelId);
1268
- }
1269
-
1270
- nodePathModels(nodeId)
1271
- //====================
1272
- {
1273
- return this.__pathManager.nodePathModels(nodeId);
1274
- }
1275
-
1276
- enableCentrelines(enable=true, force=false)
1277
- //=========================================
1278
- {
1279
- this.__pathManager.enablePathsByType('centreline', enable, force);
1280
- this.#setPaint({showCentrelines: enable});
1281
- }
1282
-
1283
- enableSckanPaths(sckanState, enable=true)
1284
- //=======================================
1285
- {
1286
- this._layerManager.enableSckanPaths(sckanState, enable);
1287
- }
1288
-
1289
- enableConnectivityByTaxonIds(taxonIds, enable=true)
1290
- //=================================================
1291
- {
1292
- if (enable) {
1293
- for (const taxonId of taxonIds) {
1294
- this.__enabledConnectivityTaxons.add(taxonId);
1295
- }
1296
- } else {
1297
- for (const taxonId of taxonIds) {
1298
- this.__enabledConnectivityTaxons.delete(taxonId);
1299
- }
1300
- }
1301
- this._layerManager.setFilter({taxons: [...this.__enabledConnectivityTaxons.values()]});
1302
- }
1303
-
1304
- excludeAnnotated(exclude=false)
1305
- //=============================
1306
- {
1307
- this.#setPaint({excludeAnnotated: exclude});
1308
- }
1309
-
1310
- //==============================================================================
1311
-
1312
- // Marker handling
1313
-
1314
- __markerPosition(featureId, annotation)
1315
- //=====================================
1316
- {
1317
- if (this.__markerPositions.has(featureId)) {
1318
- return this.__markerPositions.get(featureId);
1319
- }
1320
- let position = annotation.markerPosition || annotation.centroid;
1321
- if (position === null || position == undefined) {
1322
- // Find where to place a label or popup on a feature
1323
- const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
1324
- 'sourceLayer': this._flatmap.options.separateLayers
1325
- ? `${annotation['layer']}_${annotation['tile-layer']}`
1326
- : annotation['tile-layer'],
1327
- 'filter': [
1328
- 'all',
1329
- [ '==', ['id'], parseInt(featureId) ],
1330
- [ '==', ['geometry-type'], 'Polygon' ]
1331
- ]
1332
- });
1333
- if (features.length > 0) {
1334
- position = labelPosition(features[0]);
1335
- }
1336
- }
1337
- this.__markerPositions.set(featureId, position);
1338
- return position;
1339
- }
1340
-
1341
- addMarker(anatomicalId, options={})
1342
- //=================================
1343
- {
1344
- const featureIds = this._flatmap.modelFeatureIds(anatomicalId);
1345
- let markerId = -1;
1346
-
1347
- for (const featureId of featureIds) {
1348
- const annotation = this._flatmap.annotation(featureId);
1349
- if (!('markerPosition' in annotation) && !annotation.geometry.includes('Polygon')) {
1350
- continue;
1351
- }
1352
- if (!('marker' in annotation)) {
1353
- if (markerId === -1) {
1354
- this.__lastMarkerId += 1;
1355
- markerId = this.__lastMarkerId;
1356
- }
1357
-
1358
- // MapLibre dynamically sets a transform on marker elements so in
1359
- // order to apply a scale transform we need to create marker icons
1360
- // inside the marker container <div>.
1361
- const colour = options.colour || '#005974';
1362
- const markerHTML = options.element ? new maplibregl.Marker({element: options.element})
1363
- : new maplibregl.Marker({color: colour, scale: 0.5});
1364
-
1365
- const markerElement = document.createElement('div');
1366
- const markerIcon = document.createElement('div');
1367
- markerIcon.innerHTML = markerHTML.getElement().innerHTML;
1368
- markerElement.id = `marker-${markerId}`;
1369
- markerElement.appendChild(markerIcon);
1370
- const markerOptions = {element: markerElement};
1371
- if ('className' in options) {
1372
- markerOptions.className = options.className;
1373
- }
1374
- const markerPosition = this.__markerPosition(featureId, annotation);
1375
- if (options.cluster && this._layerManager) {
1376
- this._layerManager.addMarker(markerId, markerPosition, annotation)
1377
- } else {
1378
- const marker = new maplibregl.Marker(markerOptions)
1379
- .setLngLat(markerPosition)
1380
- .addTo(this._map);
1381
- markerElement.addEventListener('mouseenter',
1382
- this.markerMouseEvent_.bind(this, marker, anatomicalId));
1383
- markerElement.addEventListener('mousemove',
1384
- this.markerMouseEvent_.bind(this, marker, anatomicalId));
1385
- markerElement.addEventListener('mouseleave',
1386
- this.markerMouseEvent_.bind(this, marker, anatomicalId));
1387
- markerElement.addEventListener('click',
1388
- this.markerMouseEvent_.bind(this, marker, anatomicalId));
1389
-
1390
- this.__markerIdByMarker.set(marker, markerId);
1391
- this.__markerIdByFeatureId.set(+featureId, markerId);
1392
- this.__annotationByMarkerId.set(markerId, annotation);
1393
- if (!this.__featureEnabled(this.mapFeature(+featureId))) {
1394
- markerElement.style.visibility = 'hidden';
1395
- }
1396
- }
1397
- }
1398
- }
1399
- if (markerId === -1) {
1400
- console.warn(`Unable to find feature '${anatomicalId}' on which to place marker`)
1401
- }
1402
- return markerId;
1403
- }
1404
-
1405
- clearMarkers()
1406
- //============
1407
- {
1408
- for (const marker of this.__markerIdByMarker.keys()) {
1409
- marker.remove();
1410
- }
1411
- this.__markerIdByMarker.clear();
1412
- this.__annotationByMarkerId.clear();
1413
- }
1414
-
1415
- removeMarker(markerId)
1416
- //====================
1417
- {
1418
- for (const [marker, id] of this.__markerIdByMarker.entries()) {
1419
- if (markerId === id) {
1420
- marker.remove();
1421
- this.__markerIdByMarker.remove(marker);
1422
- this.__annotationByMarkerId.remove(id);
1423
- break;
1424
- }
1425
- }
1426
- }
1427
-
1428
- visibleMarkerAnatomicalIds()
1429
- //==========================
1430
- {
1431
- const anatomicalIds = [];
1432
- const visibleBounds = this._map.getBounds();
1433
- for (const [marker, id] of this.__markerIdByMarker.entries()) {
1434
- if (visibleBounds.contains(marker.getLngLat())) {
1435
- const annotation = this.__annotationByMarkerId.get(id);
1436
- if (!anatomicalIds.includes(annotation.models)) {
1437
- anatomicalIds.push(annotation.models);
1438
- }
1439
- }
1440
- }
1441
- return anatomicalIds;
1442
- }
1443
-
1444
- markerMouseEvent_(marker, anatomicalId, event)
1445
- //============================================
1446
- {
1447
- // No tooltip when context menu is open
1448
- if (this._modal
1449
- || (this.__activeMarker !== null && event.type === 'mouseleave')) {
1450
- return
1451
- }
1452
-
1453
- if (['mouseenter', 'mousemove', 'click'].includes(event.type)) {
1454
- this.__activeMarker = marker
1455
-
1456
- // Remove any tooltip
1457
- marker.setPopup(null)
1458
-
1459
- // Reset cursor
1460
- marker.getElement().style.cursor = 'default';
1461
-
1462
- const markerId = this.__markerIdByMarker.get(marker)
1463
- const annotation = this.__annotationByMarkerId.get(markerId)
1464
-
1465
- this.markerEvent_(event, markerId, marker.getLngLat(),
1466
- anatomicalId, annotation)
1467
- event.stopPropagation()
1468
- }
1469
- }
1470
-
1471
- markerEvent_(event, markerId, markerPosition, anatomicalId, annotation)
1472
- //=====================================================================
1473
- {
1474
- if (['mousemove', 'click'].includes(event.type)) {
1475
-
1476
- // Remove any tooltips
1477
- this.removeTooltip_();
1478
-
1479
- if (['mouseenter', 'mousemove', 'click'].includes(event.type)) {
1480
- // The marker's feature
1481
- const feature = this.mapFeature(annotation.featureId);
1482
- if (feature !== undefined) {
1483
- if (event.type === 'mouseenter') {
1484
- // Highlight on mouse enter
1485
- this.resetActiveFeatures_();
1486
- this.activateFeature(feature);
1487
- } else {
1488
- this.selectionEvent_(event, feature)
1489
- }
1490
- }
1491
- // Show tooltip
1492
- const html = this.tooltipHtml_(annotation, true);
1493
- this.__showToolTip(html, markerPosition);
1494
-
1495
- // Send marker event message
1496
- this._flatmap.markerEvent(event.type, markerId, anatomicalId)
1497
- }
1498
- }
1499
- }
1500
-
1501
- __clearActiveMarker()
1502
- //==================
1503
- {
1504
- if (this.__activeMarker !== null) {
1505
- this.__activeMarker.setPopup(null);
1506
- this.__activeMarker = null;
1507
- }
1508
- }
1509
-
1510
- showMarkerPopup(markerId, content, options)
1511
- //=========================================
1512
- {
1513
- const marker = this.__activeMarker;
1514
- if (markerId !== this.__markerIdByMarker.get(marker)) {
1515
- this.__clearActiveMarker();
1516
- return false;
1517
- }
1518
-
1519
- const location = marker.getLngLat();
1520
-
1521
- // Make sure the marker is on screen
1522
-
1523
- if (!this._map.getBounds().contains(location)) {
1524
- this._map.panTo(location);
1525
- }
1526
-
1527
- const element = document.createElement('div');
1528
- if (typeof content === 'object') {
1529
- element.appendChild(content);
1530
- } else {
1531
- element.innerHTML = content;
1532
- }
1533
-
1534
- element.addEventListener('click', e => this.__clearActiveMarker());
1535
-
1536
- this._tooltip = new maplibregl.Popup({
1537
- closeButton: false,
1538
- closeOnClick: false,
1539
- maxWidth: 'none',
1540
- className: 'flatmap-marker-popup'
1541
- });
1542
-
1543
- this._tooltip
1544
- .setLngLat(location)
1545
- .setDOMContent(element);
1546
-
1547
- // Set the marker tooltip and show it
1548
- marker.setPopup(this._tooltip);
1549
- marker.togglePopup();
1550
-
1551
- return true;
1552
- }
1553
-
1554
- enablePanZoomEvents(enabled=true)
1555
- //===============================
1556
- {
1557
- this.__pan_zoom_enabled = enabled;
1558
- }
1559
-
1560
- panZoomEvent_(type)
1561
- //=================
1562
- {
1563
- if (this.__pan_zoom_enabled) {
1564
- this._flatmap.panZoomEvent(type);
1565
- }
1566
- }
1567
- }
1568
-
1569
- //==============================================================================