@abi-software/flatmap-viewer 2.2.11-devel.1 → 2.2.12-b.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.rst +1 -1
- package/package.json +2 -2
- package/src/controls.js +101 -37
- package/src/flatmap-viewer.js +79 -49
- package/src/info.js +5 -5
- package/src/interactions.js +129 -134
- package/src/layers.js +105 -53
- package/src/main.js +1 -7
- package/src/minimap.js +1 -1
- package/src/pathways.js +21 -8
- package/src/search.js +0 -1
- package/src/styling.js +239 -44
- package/static/flatmap-viewer.css +3 -30
- package/src/editor.js +0 -198
- package/src/newcontrols.js +0 -617
package/src/interactions.js
CHANGED
|
@@ -37,8 +37,8 @@ import {ContextMenu} from './contextmenu.js';
|
|
|
37
37
|
import {displayedProperties} from './info.js';
|
|
38
38
|
import {InfoControl} from './info.js';
|
|
39
39
|
import {LayerManager} from './layers.js';
|
|
40
|
-
import {
|
|
41
|
-
import {BackgroundControl, LayerControl, PathControl, SCKANControl} from './controls.js';
|
|
40
|
+
import {PATHWAYS_LAYER, Pathways} from './pathways.js';
|
|
41
|
+
import {BackgroundControl, LayerControl, NerveControl, PathControl, SCKANControl} from './controls.js';
|
|
42
42
|
import {SearchControl} from './search.js';
|
|
43
43
|
import {VECTOR_TILES_SOURCE} from './styling.js';
|
|
44
44
|
|
|
@@ -118,61 +118,59 @@ export class UserInteractions
|
|
|
118
118
|
this.__annotationByMarkerId = new Map();
|
|
119
119
|
|
|
120
120
|
// Where to put labels and popups on a feature
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
// MapLibre dynamically sets a transform on marker elements so in
|
|
124
|
-
// order to apply a scale transform we need to create marker icons
|
|
125
|
-
// inside the marker container <div>.
|
|
126
|
-
this._defaultMarkerHTML = new maplibre.Marker().getElement().innerHTML;
|
|
127
|
-
this._simulationMarkerHTML = new maplibre.Marker({color: '#005974'}).getElement().innerHTML;
|
|
121
|
+
this.__markerPositions = new Map();
|
|
128
122
|
|
|
129
123
|
// Fit the map to its initial position
|
|
130
124
|
|
|
131
125
|
flatmap.setInitialPosition();
|
|
132
126
|
|
|
133
|
-
// Add
|
|
127
|
+
// Add and manage our layers
|
|
134
128
|
|
|
135
|
-
|
|
136
|
-
this._map.addControl(new SearchControl(flatmap));
|
|
137
|
-
}
|
|
129
|
+
this._layerManager = new LayerManager(flatmap);
|
|
138
130
|
|
|
139
|
-
//
|
|
131
|
+
// Path visibility is either controlled externally or by a local control
|
|
140
132
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
133
|
+
this._pathways = new Pathways(flatmap);
|
|
134
|
+
|
|
135
|
+
// The path types in this map
|
|
136
|
+
const mapPathTypes = this._pathways.pathTypes();
|
|
137
|
+
|
|
138
|
+
// Disable paths that are not initially shown
|
|
139
|
+
for (const path of mapPathTypes) {
|
|
140
|
+
if ('enabled' in path && !path.enabled) {
|
|
141
|
+
this.enablePath(path.type, false);
|
|
145
142
|
}
|
|
146
143
|
}
|
|
147
144
|
|
|
148
|
-
// Add
|
|
145
|
+
// Add various controls when running standalone
|
|
149
146
|
|
|
150
|
-
|
|
147
|
+
if (flatmap.options.standalone) {
|
|
148
|
+
// Add a control to search annotations if option set
|
|
149
|
+
this._map.addControl(new SearchControl(flatmap));
|
|
151
150
|
|
|
152
|
-
|
|
151
|
+
// Show information about features
|
|
152
|
+
this._infoControl = new InfoControl(flatmap);
|
|
153
|
+
this._map.addControl(this._infoControl);
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
// Control background colour (NB. this depends on having map layers created)
|
|
155
156
|
this._map.addControl(new BackgroundControl(flatmap));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Neural pathways which are either controlled externally
|
|
159
|
-
// or by our local controls
|
|
160
|
-
|
|
161
|
-
this._pathways = new Pathways(flatmap);
|
|
162
157
|
|
|
163
|
-
|
|
158
|
+
// Add a control to manage our paths
|
|
159
|
+
this._map.addControl(new PathControl(flatmap, mapPathTypes));
|
|
164
160
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this._map.addControl(new PathControl(flatmap, this._pathways.pathTypes));
|
|
168
|
-
}
|
|
161
|
+
// Add a control to manage our layers
|
|
162
|
+
this._map.addControl(new LayerControl(flatmap, this._layerManager));
|
|
169
163
|
|
|
170
|
-
|
|
164
|
+
// Add a control for nerve centrelines if they are present
|
|
165
|
+
if (this._pathways.haveCentrelines) {
|
|
166
|
+
this._map.addControl(new NerveControl(flatmap, this._layerManager, {showCentrelines: false}));
|
|
167
|
+
this.enableCentrelines(false);
|
|
168
|
+
}
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
// A SCKAN path control for FC maps
|
|
171
|
+
if (flatmap.options.style === 'functional') {
|
|
172
|
+
this._map.addControl(new SCKANControl(flatmap, flatmap.options.layerOptions));
|
|
173
|
+
}
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
// Flag features that have annotations
|
|
@@ -180,9 +178,6 @@ export class UserInteractions
|
|
|
180
178
|
|
|
181
179
|
for (const [id, ann] of flatmap.annotations) {
|
|
182
180
|
const feature = this.mapFeature_(id);
|
|
183
|
-
if (id == 118) {
|
|
184
|
-
console.log(feature, ann);
|
|
185
|
-
}
|
|
186
181
|
if (feature !== undefined) {
|
|
187
182
|
this._map.setFeatureState(feature, { 'annotated': true });
|
|
188
183
|
}
|
|
@@ -218,6 +213,12 @@ export class UserInteractions
|
|
|
218
213
|
this.__pan_zoom_enabled = false;
|
|
219
214
|
}
|
|
220
215
|
|
|
216
|
+
get pathways()
|
|
217
|
+
//============
|
|
218
|
+
{
|
|
219
|
+
return this._pathways;
|
|
220
|
+
}
|
|
221
|
+
|
|
221
222
|
getState()
|
|
222
223
|
//========
|
|
223
224
|
{
|
|
@@ -226,7 +227,7 @@ export class UserInteractions
|
|
|
226
227
|
return {
|
|
227
228
|
center: this._map.getCenter().toArray(),
|
|
228
229
|
zoom: this._map.getZoom(),
|
|
229
|
-
layers: this.
|
|
230
|
+
layers: this.layers
|
|
230
231
|
};
|
|
231
232
|
}
|
|
232
233
|
|
|
@@ -251,17 +252,23 @@ export class UserInteractions
|
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
setPaint(options)
|
|
256
|
+
//===============
|
|
256
257
|
{
|
|
257
258
|
this.__colourOptions = options;
|
|
258
|
-
this._layerManager.
|
|
259
|
+
this._layerManager.setPaint(options);
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
getLayers()
|
|
263
|
+
//=========
|
|
264
|
+
{
|
|
265
|
+
return this._layerManager.layers;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
enableLayer(layerId, enable=true)
|
|
269
|
+
//===============================
|
|
263
270
|
{
|
|
264
|
-
|
|
271
|
+
this._layerManager.activate(layerId, enable);
|
|
265
272
|
}
|
|
266
273
|
|
|
267
274
|
mapFeature_(featureId)
|
|
@@ -290,7 +297,7 @@ export class UserInteractions
|
|
|
290
297
|
{
|
|
291
298
|
featureId = +featureId; // Ensure numeric
|
|
292
299
|
if (this._selectedFeatureIds.size === 0) {
|
|
293
|
-
this._layerManager.
|
|
300
|
+
this._layerManager.setPaint({...this.__colourOptions, dimmed: dim});
|
|
294
301
|
}
|
|
295
302
|
if (this._selectedFeatureIds.has(featureId)) {
|
|
296
303
|
this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
|
|
@@ -320,7 +327,7 @@ export class UserInteractions
|
|
|
320
327
|
}
|
|
321
328
|
}
|
|
322
329
|
if (this._selectedFeatureIds.size === 0) {
|
|
323
|
-
this._layerManager.
|
|
330
|
+
this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
|
|
324
331
|
}
|
|
325
332
|
}
|
|
326
333
|
|
|
@@ -334,7 +341,7 @@ export class UserInteractions
|
|
|
334
341
|
}
|
|
335
342
|
}
|
|
336
343
|
this._selectedFeatureIds.clear();
|
|
337
|
-
this._layerManager.
|
|
344
|
+
this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
|
|
338
345
|
}
|
|
339
346
|
|
|
340
347
|
activeFeaturesAtEvent_(event)
|
|
@@ -561,7 +568,6 @@ export class UserInteractions
|
|
|
561
568
|
}
|
|
562
569
|
bbox = expandBounds(bbox, annotation.bounds);
|
|
563
570
|
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
564
|
-
// FC may have lines that are not pathways features, esp. when authoring...
|
|
565
571
|
for (const pathFeatureId of this._pathways.lineFeatureIds([featureId])) {
|
|
566
572
|
if (select) {
|
|
567
573
|
this.selectFeature_(pathFeatureId);
|
|
@@ -609,7 +615,7 @@ export class UserInteractions
|
|
|
609
615
|
location = this.__lastClickLngLat;
|
|
610
616
|
} else {
|
|
611
617
|
// Position popup at the feature's 'centre'
|
|
612
|
-
location = this.
|
|
618
|
+
location = this.__markerPosition(featureId, ann);
|
|
613
619
|
}
|
|
614
620
|
|
|
615
621
|
// Make sure the feature is on screen
|
|
@@ -651,9 +657,13 @@ export class UserInteractions
|
|
|
651
657
|
const tooltips = [];
|
|
652
658
|
for (const lineFeature of lineFeatures) {
|
|
653
659
|
const properties = lineFeature.properties;
|
|
654
|
-
if ('
|
|
655
|
-
|
|
656
|
-
|
|
660
|
+
if ('error' in properties) {
|
|
661
|
+
tooltips.push(`<div class="feature-error">Error: ${properties.error}</div>`)
|
|
662
|
+
}
|
|
663
|
+
if ('warning' in properties) {
|
|
664
|
+
tooltips.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
665
|
+
}
|
|
666
|
+
if ('label' in properties && (!('tooltip' in properties) || properties.tooltip)) {
|
|
657
667
|
let tooltip = '';
|
|
658
668
|
const label = properties.label;
|
|
659
669
|
const cleanLabel = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
|
|
@@ -662,32 +672,37 @@ export class UserInteractions
|
|
|
662
672
|
}
|
|
663
673
|
}
|
|
664
674
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
return `<div class='flatmap-feature-label'>${tooltips.join('<hr/>')}</div>`;
|
|
675
|
+
return (tooltips.length === 0) ? ''
|
|
676
|
+
: `<div class='flatmap-feature-label'>${tooltips.join('<hr/>')}</div>`;
|
|
669
677
|
}
|
|
670
678
|
|
|
671
679
|
tooltipHtml_(properties, forceLabel=false)
|
|
672
680
|
//========================================
|
|
673
681
|
{
|
|
682
|
+
const tooltip = [];
|
|
683
|
+
if ('error' in properties) {
|
|
684
|
+
tooltip.push(`<div class="feature-error">Error: ${properties.error}</div>`)
|
|
685
|
+
}
|
|
686
|
+
if ('warning' in properties) {
|
|
687
|
+
tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
688
|
+
}
|
|
674
689
|
if (('label' in properties || 'hyperlink' in properties)
|
|
675
|
-
|
|
676
|
-
&& !('labelled' in properties)) {
|
|
690
|
+
&& (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
|
|
677
691
|
const label = ('label' in properties) ? (properties.label.substr(0, 1).toUpperCase()
|
|
678
692
|
+ properties.label.substr(1)).replaceAll("\n", "<br/>")
|
|
679
693
|
: '';
|
|
680
694
|
if ('hyperlink' in properties) {
|
|
681
695
|
if (label === '') {
|
|
682
|
-
|
|
696
|
+
tooltip.push(`<a href='${properties.hyperlink}'>${properties.hyperlink}</a>`);
|
|
683
697
|
} else {
|
|
684
|
-
|
|
698
|
+
tooltip.push(`<a href='${properties.hyperlink}'>${label}</a></div>`);
|
|
685
699
|
}
|
|
686
700
|
} else {
|
|
687
|
-
|
|
701
|
+
tooltip.push(label);
|
|
688
702
|
}
|
|
689
703
|
}
|
|
690
|
-
return ''
|
|
704
|
+
return (tooltip.length === 0) ? ''
|
|
705
|
+
: `<div class='flatmap-feature-label'>${tooltip.join('<hr/>')}</div>`;
|
|
691
706
|
}
|
|
692
707
|
|
|
693
708
|
__featureEvent(type, feature)
|
|
@@ -783,10 +798,6 @@ export class UserInteractions
|
|
|
783
798
|
}
|
|
784
799
|
}
|
|
785
800
|
} else {
|
|
786
|
-
// Allow tooltip for body but no highlighting/activation??
|
|
787
|
-
// More generally, only highlight a feature if all "within" the viewport??
|
|
788
|
-
// -- then as we zoom outermost features wouldn't highlight (but still show tooltip)
|
|
789
|
-
// -- or highlight in a more subtle way (eg. opacity change and not colour change)??
|
|
790
801
|
let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
|
|
791
802
|
|| 'label' in feature.properties
|
|
792
803
|
|| 'node' in feature.properties)
|
|
@@ -980,6 +991,7 @@ export class UserInteractions
|
|
|
980
991
|
togglePaths()
|
|
981
992
|
//===========
|
|
982
993
|
{
|
|
994
|
+
console.log('Depracated API function called: togglePaths()')
|
|
983
995
|
if (this._disabledPathFeatures){
|
|
984
996
|
this.enablePathFeatures_(true, this._pathways.allFeatureIds());
|
|
985
997
|
this._disabledPathFeatures = false;
|
|
@@ -994,29 +1006,6 @@ export class UserInteractions
|
|
|
994
1006
|
this.enablePathFeatures_(enable, this._pathways.typeFeatureIds(pathType));
|
|
995
1007
|
}
|
|
996
1008
|
|
|
997
|
-
showPaths(pathTypes, enable=true)
|
|
998
|
-
//===============================
|
|
999
|
-
{
|
|
1000
|
-
// Disable/enable all paths except those with `pathTypes`
|
|
1001
|
-
if (Array.isArray(pathTypes)) {
|
|
1002
|
-
for (const pathType of pathways.PATH_TYPES) {
|
|
1003
|
-
if (pathTypes.indexOf(pathType.type) >= 0) {
|
|
1004
|
-
this.enablePath(pathType.type, enable)
|
|
1005
|
-
} else {
|
|
1006
|
-
this.enablePath(pathType.type, !enable)
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
} else {
|
|
1010
|
-
for (const pathType of pathways.PATH_TYPES) {
|
|
1011
|
-
if (pathType.type === pathTypes) {
|
|
1012
|
-
this.enablePath(pathType.type, enable)
|
|
1013
|
-
} else {
|
|
1014
|
-
this.enablePath(pathType.type, !enable)
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
1009
|
pathwaysFeatureIds(externalIds)
|
|
1021
1010
|
//=============================
|
|
1022
1011
|
{
|
|
@@ -1032,51 +1021,57 @@ export class UserInteractions
|
|
|
1032
1021
|
return this._pathways.nodePathModels(nodeId);
|
|
1033
1022
|
}
|
|
1034
1023
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1024
|
+
enableCentrelines(show=true)
|
|
1025
|
+
//==========================
|
|
1026
|
+
{
|
|
1027
|
+
this.enablePath('centreline', show);
|
|
1028
|
+
this._layerManager.setPaint({showCentrelines: show});
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
enableSckanPath(sckanState, enable=true)
|
|
1032
|
+
//======================================
|
|
1037
1033
|
{
|
|
1034
|
+
this._layerManager.enableSckanPath(sckanState, enable);
|
|
1038
1035
|
}
|
|
1039
1036
|
|
|
1040
1037
|
//==============================================================================
|
|
1041
1038
|
|
|
1042
|
-
//
|
|
1039
|
+
// Marker handling
|
|
1043
1040
|
|
|
1044
|
-
|
|
1045
|
-
//======================================
|
|
1041
|
+
__markerPosition(featureId, annotation)
|
|
1046
1042
|
{
|
|
1047
|
-
if (this.
|
|
1048
|
-
return this.
|
|
1043
|
+
if (this.__markerPositions.has(featureId)) {
|
|
1044
|
+
return this.__markerPositions.get(featureId);
|
|
1049
1045
|
}
|
|
1050
|
-
let position = annotation.centroid;
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1046
|
+
let position = annotation.markerPosition || annotation.centroid;
|
|
1047
|
+
if (position === null || position == undefined) {
|
|
1048
|
+
// Find where to place a label or popup on a feature
|
|
1049
|
+
const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
|
|
1050
|
+
'sourceLayer': this._flatmap.options.separateLayers
|
|
1051
|
+
? `${annotation['layer']}_${annotation['tile-layer']}`
|
|
1052
|
+
: annotation['tile-layer'],
|
|
1053
|
+
'filter': [
|
|
1054
|
+
'all',
|
|
1055
|
+
[ '==', ['id'], parseInt(featureId) ],
|
|
1056
|
+
[ '==', ['geometry-type'], 'Polygon' ]
|
|
1057
|
+
]
|
|
1058
|
+
});
|
|
1059
|
+
if (features.length > 0) {
|
|
1060
|
+
const feature = features[0];
|
|
1061
|
+
const polygon = feature.geometry.coordinates;
|
|
1062
|
+
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
1063
|
+
const precision = ('area' in feature.properties)
|
|
1064
|
+
? Math.sqrt(feature.properties.area)/500000
|
|
1065
|
+
: 0.1;
|
|
1066
|
+
position = polylabel(polygon, precision);
|
|
1067
|
+
}
|
|
1069
1068
|
}
|
|
1070
|
-
this.
|
|
1069
|
+
this.__markerPositions.set(featureId, position);
|
|
1071
1070
|
return position;
|
|
1072
1071
|
}
|
|
1073
1072
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
// Marker handling
|
|
1077
|
-
|
|
1078
|
-
addMarker(anatomicalId, markerType='')
|
|
1079
|
-
//====================================
|
|
1073
|
+
addMarker(anatomicalId, htmlElement=null)
|
|
1074
|
+
//=======================================
|
|
1080
1075
|
{
|
|
1081
1076
|
const featureIds = this._flatmap.modelFeatureIds(anatomicalId);
|
|
1082
1077
|
let markerId = -1;
|
|
@@ -1092,19 +1087,19 @@ export class UserInteractions
|
|
|
1092
1087
|
markerId = this.__lastMarkerId;
|
|
1093
1088
|
}
|
|
1094
1089
|
|
|
1090
|
+
// MapLibre dynamically sets a transform on marker elements so in
|
|
1091
|
+
// order to apply a scale transform we need to create marker icons
|
|
1092
|
+
// inside the marker container <div>.
|
|
1093
|
+
const markerHTML = htmlElement ? new maplibre.Marker({element: htmlElement})
|
|
1094
|
+
: new maplibre.Marker();
|
|
1095
|
+
|
|
1095
1096
|
const markerElement = document.createElement('div');
|
|
1096
1097
|
const markerIcon = document.createElement('div');
|
|
1097
|
-
|
|
1098
|
-
markerIcon.innerHTML = this._simulationMarkerHTML;
|
|
1099
|
-
} else {
|
|
1100
|
-
markerIcon.innerHTML = this._defaultMarkerHTML;
|
|
1101
|
-
}
|
|
1098
|
+
markerIcon.innerHTML = markerHTML.getElement().innerHTML;
|
|
1102
1099
|
markerIcon.className = 'flatmap-marker';
|
|
1103
1100
|
markerElement.appendChild(markerIcon);
|
|
1104
1101
|
|
|
1105
|
-
const markerPosition = (annotation
|
|
1106
|
-
? this.__centralPosition(featureId, annotation)
|
|
1107
|
-
: annotation.centroid;
|
|
1102
|
+
const markerPosition = this.__markerPosition(featureId, annotation);
|
|
1108
1103
|
const marker = new maplibre.Marker(markerElement)
|
|
1109
1104
|
.setLngLat(markerPosition)
|
|
1110
1105
|
.addTo(this._map);
|