@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.
- package/dist/assets/index.css +1 -0
- package/dist/index.js +80577 -0
- package/dist/lib/index.d.ts +4 -0
- package/package.json +10 -7
- package/lib/index.ts +0 -10
- package/src/contextmenu.js +0 -97
- package/src/controls/annotation.js +0 -302
- package/src/controls/controls.js +0 -645
- package/src/controls/flightpaths.js +0 -95
- package/src/controls/info.js +0 -291
- package/src/controls/minimap.js +0 -442
- package/src/controls/paths.js +0 -143
- package/src/controls/search.js +0 -113
- package/src/controls/systems.js +0 -75
- package/src/controls/taxons.js +0 -73
- package/src/flatmap-viewer.js +0 -1789
- package/src/images.js +0 -66
- package/src/interactions.js +0 -1569
- package/src/layers/cluster.js +0 -177
- package/src/layers/filter.js +0 -310
- package/src/layers/flightpaths.js +0 -383
- package/src/layers/index.js +0 -478
- package/src/layers/styling.js +0 -1077
- package/src/main.js +0 -272
- package/src/mapserver.js +0 -64
- package/src/mathjax.js +0 -100
- package/src/pathways.js +0 -427
- package/src/search.js +0 -137
- package/src/systems.js +0 -146
- package/src/utils.js +0 -152
- package/static/css/flatmap-viewer.css +0 -238
- package/static/icons/favicon.ico +0 -0
- package/static/images/active.png +0 -0
- package/static/images/inactive.png +0 -0
- package/static/images/reset-map-active.png +0 -0
- package/static/images/reset-map-button.png +0 -0
- package/static/images/rounded-background.png +0 -0
- package/static/images/zoom-in-active.png +0 -0
- package/static/images/zoom-in-button.png +0 -0
- package/static/images/zoom-out-active.png +0 -0
- package/static/images/zoom-out-button.png +0 -0
- package/thirdParty/maplibre-gl-svg/CHANGELOG.md +0 -13
- package/thirdParty/maplibre-gl-svg/LICENSE +0 -21
- package/thirdParty/maplibre-gl-svg/LICENSE.md +0 -21
- package/thirdParty/maplibre-gl-svg/README.md +0 -24
- package/thirdParty/maplibre-gl-svg/assets/Add custom SVG template to template manager.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/All built-in SVG templates as HTML markers.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/All built-in SVG templates as symbols.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/Fill polygon with built-in SVG template.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/HTML Marker with Custom SVG Template.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/HTML Marker with built-in SVG template.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/Line layer with built-in SVG template.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/Load SVG from URL.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/SVG template options.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/Smiley_face_changed.svg +0 -37
- package/thirdParty/maplibre-gl-svg/assets/Symbol layer with built-in SVG template.jpg +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/arrow-up-thin.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/arrow-up.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/car.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/checker-rotated.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/checker.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/circles-spaced.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/circles.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-lines-down.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-lines-up.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-stripes-down.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/diagonal-stripes-up.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/dots.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/flag-triangle.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/flag.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/grid-lines.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon-rounded-thick.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon-rounded.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon-thick.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/hexagon.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-arrow.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-ball-pin.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-circle.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-flat.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square-cluster.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square-rounded-cluster.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square-rounded.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-square.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker-thick.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/marker.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/pin-round.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/pin.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/rotated-grid-lines.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/rotated-grid-stripes.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/rounded-square-thick.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/rounded-square.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle-arrow-left.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle-arrow-up.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle-thick.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/triangle.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/x-fill.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/zig-zag-vertical.png +0 -0
- package/thirdParty/maplibre-gl-svg/assets/image-templates/zig-zag.png +0 -0
- package/thirdParty/maplibre-gl-svg/build/build.js +0 -210
- package/thirdParty/maplibre-gl-svg/dist/maplibre-gl-svg.js +0 -339
- package/thirdParty/maplibre-gl-svg/dist/maplibre-gl-svg.min.js +0 -4
- package/thirdParty/maplibre-gl-svg/docs/docs.md +0 -375
- package/thirdParty/maplibre-gl-svg/examples/Add custom SVG template to template manager.html +0 -101
- package/thirdParty/maplibre-gl-svg/examples/All built-in SVG templates as HTML markers.html +0 -82
- package/thirdParty/maplibre-gl-svg/examples/All built-in SVG templates as symbols.html +0 -124
- package/thirdParty/maplibre-gl-svg/examples/Fill polygon with built-in SVG template.html +0 -94
- package/thirdParty/maplibre-gl-svg/examples/HTML Marker with Custom SVG Template.html +0 -86
- package/thirdParty/maplibre-gl-svg/examples/HTML Marker with built-in SVG template.html +0 -83
- package/thirdParty/maplibre-gl-svg/examples/Line layer with built-in SVG template.html +0 -129
- package/thirdParty/maplibre-gl-svg/examples/Load SVG from URL.html +0 -96
- package/thirdParty/maplibre-gl-svg/examples/SVG template options.html +0 -264
- package/thirdParty/maplibre-gl-svg/examples/Symbol layer with built-in SVG template.html +0 -93
- package/thirdParty/maplibre-gl-svg/index.html +0 -151
- package/thirdParty/maplibre-gl-svg/package-lock.json +0 -5882
- package/thirdParty/maplibre-gl-svg/package.json +0 -49
- package/thirdParty/maplibre-gl-svg/src/SvgManager.ts +0 -186
- package/thirdParty/maplibre-gl-svg/src/SvgTemplateManager.ts +0 -144
- package/thirdParty/maplibre-gl-svg/src/index.ts +0 -4
- package/thirdParty/maplibre-gl-svg/tsconfig.json +0 -31
- package/thirdParty/maplibre-gl-svg/typings/index.d.ts +0 -111
package/src/interactions.js
DELETED
|
@@ -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
|
-
//==============================================================================
|