@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.
@@ -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 {PATH_TYPES, PATHWAYS_LAYER, Pathways} from './pathways.js';
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.__centralPositions = new Map();
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 a control to search annotations if option set
127
+ // Add and manage our layers
134
128
 
135
- if (flatmap.options.searchable) {
136
- this._map.addControl(new SearchControl(flatmap));
137
- }
129
+ this._layerManager = new LayerManager(flatmap);
138
130
 
139
- // Show information about features
131
+ // Path visibility is either controlled externally or by a local control
140
132
 
141
- if (flatmap.options.featureInfo || flatmap.options.searchable) {
142
- this._infoControl = new InfoControl(flatmap);
143
- if (flatmap.options.featureInfo) {
144
- this._map.addControl(this._infoControl);
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 and manage our layers
145
+ // Add various controls when running standalone
149
146
 
150
- this._layerManager = new LayerManager(flatmap);
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
- // Control background colour (NB. this depends on having map layers created)
151
+ // Show information about features
152
+ this._infoControl = new InfoControl(flatmap);
153
+ this._map.addControl(this._infoControl);
153
154
 
154
- if (flatmap.options.backgroundControl) {
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
- // Add a control to manage our pathways
158
+ // Add a control to manage our paths
159
+ this._map.addControl(new PathControl(flatmap, mapPathTypes));
164
160
 
165
- if (flatmap.options.pathControls) {
166
- // Restrict to path types that are on the map...
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
- // Add a control to manage our layers
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
- if (flatmap.options.layerControl) {
173
- this._map.addControl(new LayerControl(flatmap, this._layerManager));
174
- // ************************************
175
- //this._map.addControl(new SCKANControl(flatmap));
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.activeLayerNames
230
+ layers: this.layers
230
231
  };
231
232
  }
232
233
 
@@ -251,17 +252,23 @@ export class UserInteractions
251
252
  }
252
253
  }
253
254
 
254
- setColour(options)
255
- //================
255
+ setPaint(options)
256
+ //===============
256
257
  {
257
258
  this.__colourOptions = options;
258
- this._layerManager.setColour(options);
259
+ this._layerManager.setPaint(options);
259
260
  }
260
261
 
261
- get activeLayerNames()
262
- //====================
262
+ getLayers()
263
+ //=========
264
+ {
265
+ return this._layerManager.layers;
266
+ }
267
+
268
+ enableLayer(layerId, enable=true)
269
+ //===============================
263
270
  {
264
- return this._layerManager.activeLayerNames;
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.setColour({...this.__colourOptions, dimmed: dim});
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.setColour({...this.__colourOptions, dimmed: false});
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.setColour({...this.__colourOptions, dimmed: false});
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.__centralPosition(featureId, ann);
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 ('label' in properties
655
- && (!('tooltip' in properties) || properties.tooltip)
656
- && !('labelled' in properties)) {
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
- if (tooltips.length === 0) {
666
- return '';
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
- && (forceLabel || !('tooltip' in properties) || properties.tooltip)
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
- return `<div class='flatmap-feature-label'><a href='${properties.hyperlink}'>${properties.hyperlink}</a></div>`;
696
+ tooltip.push(`<a href='${properties.hyperlink}'>${properties.hyperlink}</a>`);
683
697
  } else {
684
- return `<div class='flatmap-feature-label'><a href='${properties.hyperlink}'>${label}</a></div>`;
698
+ tooltip.push(`<a href='${properties.hyperlink}'>${label}</a></div>`);
685
699
  }
686
700
  } else {
687
- return `<div class='flatmap-feature-label'>${label}</div>`;
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
- showSckanPaths(validity, enable=true)
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
- // Find where to place a label or popup on a feature
1039
+ // Marker handling
1043
1040
 
1044
- __centralPosition(featureId, annotation)
1045
- //======================================
1041
+ __markerPosition(featureId, annotation)
1046
1042
  {
1047
- if (this.__centralPositions.has(featureId)) {
1048
- return this.__centralPositions.get(featureId);
1043
+ if (this.__markerPositions.has(featureId)) {
1044
+ return this.__markerPositions.get(featureId);
1049
1045
  }
1050
- let position = annotation.centroid;
1051
- const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
1052
- 'sourceLayer': this._flatmap.options.separateLayers
1053
- ? `${annotation['layer']}_${annotation['tile-layer']}`
1054
- : annotation['tile-layer'],
1055
- 'filter': [
1056
- 'all',
1057
- [ '==', ['id'], parseInt(featureId) ],
1058
- [ '==', ['geometry-type'], 'Polygon' ]
1059
- ]
1060
- });
1061
- if (features.length > 0) {
1062
- const feature = features[0];
1063
- const polygon = feature.geometry.coordinates;
1064
- // Rough heuristic. Area is in km^2; below appears to be good enough.
1065
- const precision = ('area' in feature.properties)
1066
- ? Math.sqrt(feature.properties.area)/500000
1067
- : 0.1;
1068
- position = polylabel(polygon, precision);
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.__centralPositions.set(featureId, position);
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
- if (markerType === 'simulation') {
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.geometry === 'Polygon')
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);