@abi-software/flatmap-viewer 2.2.1-alpha.1 → 2.2.1-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.rst CHANGED
@@ -2,7 +2,7 @@
2
2
  Flatmap Viewer
3
3
  ==============
4
4
 
5
- A viewer for anatomical flatmaps generated by `flatmap-maker <https://github.com/dbrnz/flatmap-maker>`_. The viewer is intended to be a component of a larger Javascript web application, although may be used standalone for local flatmap development and testing. Flatmap content is obtained from a `flatmap-server <https://github.com/dbrnz/flatmap-server>`_.
5
+ A viewer for anatomical flatmaps generated by `flatmap-maker <https://github.com/AnatomicMaps/flatmap-maker>`_. The viewer is intended to be a component of a larger Javascript web application, although may be used standalone for local flatmap development and testing. Flatmap content is obtained from a `flatmap-server <https://github.com/AnatomicMaps/flatmap-server>`_.
6
6
 
7
7
 
8
8
  Standalone Use
@@ -38,7 +38,7 @@ The map server endpoint is specified as ``MAP_ENDPOINT`` in ``src/main.js``. It
38
38
  Package Installation
39
39
  ====================
40
40
 
41
- * ``npm install @abi-software/flatmap-viewer``
41
+ * ``npm install @abi-software/flatmap-viewer@2.2.1-beta.10``
42
42
 
43
43
  Documentation
44
44
  -------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/flatmap-viewer",
3
- "version": "2.2.1-alpha.1",
3
+ "version": "2.2.1-beta.10",
4
4
  "description": "Flatmap viewer using Maplibre GL",
5
5
  "repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
6
6
  "main": "src/main.js",
@@ -21,6 +21,7 @@
21
21
  "@turf/area": "^6.0.1",
22
22
  "@turf/bbox": "^6.0.1",
23
23
  "@turf/helpers": "^6.1.4",
24
+ "bezier-js": "^6.1.0",
24
25
  "maplibre-gl": ">=1.15.3",
25
26
  "minisearch": "^2.2.1",
26
27
  "polylabel": "^1.1.0"
@@ -45,6 +45,10 @@ import * as utils from './utils.js';
45
45
 
46
46
  //==============================================================================
47
47
 
48
+ const MAP_MAKER_SEPARATE_LAYERS_VERSION = 1.4;
49
+
50
+ //==============================================================================
51
+
48
52
  /**
49
53
  * Maps are not created directly but instead are created and loaded by
50
54
  * :meth:`LoadMap` of :class:`MapManager`.
@@ -1001,9 +1005,15 @@ export class MapManager
1001
1005
  {
1002
1006
  return await this._initialisingMutex.dispatch(async () => {
1003
1007
  if (!this._initialised) {
1004
- this._mapList = await this._mapServer.loadJSON('');
1008
+ this._mapList = [];
1009
+ const maps = await this._mapServer.loadJSON('');
1005
1010
  // Check map schema version (set by mapmaker) and
1006
1011
  // remove maps we can't view (giving a console warning...)
1012
+ for (const map of maps) {
1013
+ // Are features in separate vector tile source layers?
1014
+ map.separateLayers = ('version' in map && map.version >= MAP_MAKER_SEPARATE_LAYERS_VERSION);
1015
+ this._mapList.push(map);
1016
+ }
1007
1017
  this._initialised = true;
1008
1018
  }
1009
1019
  });
@@ -1157,7 +1167,7 @@ export class MapManager
1157
1167
  try {
1158
1168
  const map = await this.findMap_(identifier);
1159
1169
  if (map === null) {
1160
- reject(`Unknown map for ${JSON.stringify(identifier)}`);
1170
+ reject(`Unknown map: ${JSON.stringify(identifier)}`);
1161
1171
  };
1162
1172
 
1163
1173
  // Load the maps index file
@@ -1181,6 +1191,12 @@ export class MapManager
1181
1191
  mapOptions['pathControls'] = true;
1182
1192
  }
1183
1193
 
1194
+ // Mapmaker's changed the name of the field to indicate that indicates if
1195
+ // there are raster layers
1196
+ if (!('image-layers' in mapIndex) && ('image_layer' in mapIndex)) {
1197
+ mapIndex['image-layers'] = mapIndex['image_layer'];
1198
+ }
1199
+
1184
1200
  // Get details about the map's layers
1185
1201
 
1186
1202
  let mapLayers = [];
@@ -1249,7 +1265,17 @@ export class MapManager
1249
1265
  outline: true
1250
1266
  };
1251
1267
  }
1252
- mapOptions.layerOptions.authoring = ('authoring' in mapIndex && mapIndex.authoring);
1268
+ if ('authoring' in mapIndex) {
1269
+ mapOptions.layerOptions.style == 'authoring'
1270
+ } else if ('style' in mapIndex) {
1271
+ mapOptions.layerOptions.style = mapIndex.style;
1272
+ } else {
1273
+ mapOptions.layerOptions.style = 'flatmap';
1274
+ }
1275
+
1276
+ // Are features in separate vector tile source layers?
1277
+
1278
+ mapOptions.separateLayers = map.separateLayers;
1253
1279
 
1254
1280
  // Display the map
1255
1281
 
@@ -162,34 +162,6 @@ export class UserInteractions
162
162
 
163
163
  this._layerManager = new LayerManager(flatmap);
164
164
 
165
- // Add the map's layers
166
-
167
- // Layers have an id, either layer-N or an assigned name
168
- // Some layers might have a description. These are the selectable layers,
169
- // unless they are flagged as `no-select`
170
- // Selectable layers have opacity 0 unless active, in which case they have opacity 1.
171
- // `no-select` layers have opacity 0.5
172
- // Background layer has opacity 0.2
173
-
174
- const layersById = new Map();
175
- const layerBackgroundIds = [];
176
- for (const layer of flatmap.layers) {
177
- layer.backgroundLayers = [];
178
- layersById.set(layer.id, layer);
179
- }
180
- for (const layer of flatmap.layers) {
181
- if (layer.background_for) {
182
- const l = layersById.get(layer.background_for);
183
- l.backgroundLayers.push(layer);
184
- layerBackgroundIds.push(layer.id);
185
- }
186
- }
187
- for (const layer of flatmap.layers) {
188
- if (layerBackgroundIds.indexOf(layer.id) < 0) {
189
- this._layerManager.addLayer(layer, flatmap.options.layerOptions);
190
- }
191
- }
192
-
193
165
  // Flag features that have annotations
194
166
  // Also flag those features that are models of something
195
167
 
@@ -205,7 +177,7 @@ export class UserInteractions
205
177
  // Display a context menu on right-click
206
178
 
207
179
  this._lastContextTime = 0;
208
- this._contextMenu = new ContextMenu(flatmap, this.clearModal_.bind(this));
180
+ this._contextMenu = new ContextMenu(flatmap, this.__clearModal.bind(this));
209
181
  this._map.on('contextmenu', this.contextMenuEvent_.bind(this));
210
182
 
211
183
  // Display a context menu with a touch longer than 0.5 second
@@ -285,7 +257,9 @@ export class UserInteractions
285
257
  return {
286
258
  id: featureId,
287
259
  source: VECTOR_TILES_SOURCE,
288
- sourceLayer: ann['tile-layer']
260
+ sourceLayer: this._flatmap.options.separateLayers
261
+ ? `${ann['layer']}_${ann['tile-layer']}`
262
+ : ann['tile-layer']
289
263
  };
290
264
  }
291
265
 
@@ -330,8 +304,8 @@ export class UserInteractions
330
304
  }
331
305
  }
332
306
 
333
- unselectFeatures_()
334
- //=================
307
+ __unselectFeatures()
308
+ //==================
335
309
  {
336
310
  for (const featureId of this._selectedFeatureIds.keys()) {
337
311
  const feature = this.mapFeature_(featureId);
@@ -447,8 +421,8 @@ export class UserInteractions
447
421
  this._modal = true;
448
422
  }
449
423
 
450
- clearModal_(event)
451
- //================
424
+ __clearModal(event)
425
+ //=================
452
426
  {
453
427
  this._modal = false;
454
428
  }
@@ -459,7 +433,7 @@ export class UserInteractions
459
433
  this._contextMenu.hide();
460
434
  const nodeId = event.target.getAttribute('featureId');
461
435
  this.enablePathFeatures_(enable, this._pathways.pathFeatureIds(nodeId));
462
- this.clearModal_();
436
+ this.__clearModal();
463
437
  }
464
438
 
465
439
  enablePathFeatures_(enable, featureIds)
@@ -490,9 +464,9 @@ export class UserInteractions
490
464
  reset()
491
465
  //=====
492
466
  {
493
- this.clearModal_();
467
+ this.__clearModal();
494
468
  this.clearActiveMarker_();
495
- this.unselectFeatures_();
469
+ this.__unselectFeatures();
496
470
  this.enablePathFeatures_(true, this._pathways.allFeatureIds());
497
471
  this._disabledPathFeatures = false;
498
472
  }
@@ -500,7 +474,7 @@ export class UserInteractions
500
474
  clearSearchResults(reset=true)
501
475
  //============================
502
476
  {
503
- this.unselectFeatures_();
477
+ this.__unselectFeatures();
504
478
  }
505
479
 
506
480
  /**
@@ -536,7 +510,7 @@ export class UserInteractions
536
510
  //========================
537
511
  {
538
512
  if (featureIds.length) {
539
- this.unselectFeatures_();
513
+ this.__unselectFeatures();
540
514
  for (const featureId of featureIds) {
541
515
  const annotation = this._flatmap.annotation(featureId);
542
516
  if (annotation) {
@@ -569,7 +543,7 @@ export class UserInteractions
569
543
  const padding = options.padding || 100;
570
544
  if (featureIds.length) {
571
545
  this.unhighlightFeatures_();
572
- if (select) this.unselectFeatures_();
546
+ if (select) this.__unselectFeatures();
573
547
  let bbox = null;
574
548
  for (const featureId of featureIds) {
575
549
  const annotation = this._flatmap.annotation(featureId);
@@ -616,7 +590,7 @@ export class UserInteractions
616
590
 
617
591
  // Highlight the feature
618
592
 
619
- this.unselectFeatures_();
593
+ this.__unselectFeatures();
620
594
  this.selectFeature_(featureId);
621
595
 
622
596
  // Find the pop-up's postion
@@ -638,7 +612,7 @@ export class UserInteractions
638
612
  }
639
613
  this.setModal_();
640
614
  this._currentPopup = new maplibre.Popup(options).addTo(this._map);
641
- this._currentPopup.on('close', this.clearModal_.bind(this));
615
+ this._currentPopup.on('close', this.__clearPopup.bind(this));
642
616
  this._currentPopup.setLngLat(location);
643
617
  if (typeof content === 'object') {
644
618
  this._currentPopup.setDOMContent(content);
@@ -648,6 +622,13 @@ export class UserInteractions
648
622
  }
649
623
  }
650
624
 
625
+ __clearPopup()
626
+ //============
627
+ {
628
+ this.__clearModal();
629
+ this.__unselectFeatures();
630
+ }
631
+
651
632
  removeTooltip_()
652
633
  //==============
653
634
  {
@@ -657,15 +638,47 @@ export class UserInteractions
657
638
  }
658
639
  }
659
640
 
641
+ lineTooltip_(lineFeatures)
642
+ //========================
643
+ {
644
+ const tooltips = [];
645
+ for (const lineFeature of lineFeatures) {
646
+ const properties = lineFeature.properties;
647
+ if ('label' in properties
648
+ && (!('tooltip' in properties) || properties.tooltip)
649
+ && !('labelled' in properties)) {
650
+ let tooltip = '';
651
+ const label = properties.label;
652
+ const cleanLabel = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
653
+ if (!tooltips.includes(cleanLabel)) {
654
+ tooltips.push(cleanLabel);
655
+ }
656
+ }
657
+ }
658
+ if (tooltips.length === 0) {
659
+ return '';
660
+ }
661
+ return `<div class='flatmap-feature-label'>${tooltips.join('<hr/>')}</div>`;
662
+ }
663
+
660
664
  tooltipHtml_(properties, forceLabel=false)
661
665
  //========================================
662
666
  {
663
- if ('label' in properties
667
+ if (('label' in properties || 'hyperlink' in properties)
664
668
  && (forceLabel || !('tooltip' in properties) || properties.tooltip)
665
669
  && !('labelled' in properties)) {
666
- const label = properties.label;
667
- const capitalisedLabel = label.substr(0, 1).toUpperCase() + label.substr(1);
668
- return `<div class='flatmap-feature-label'>${capitalisedLabel.replaceAll("\n", "<br/>")}</div>`;
670
+ let tooltip = '';
671
+ if ('label' in properties) {
672
+ const label = properties.label;
673
+ tooltip = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
674
+ } else {
675
+ tooltip = properties.hyperlink
676
+ }
677
+ if ('hyperlink' in properties) {
678
+ return `<div class='flatmap-feature-label'><a href='{properties.hyperlink}'>${tooltip}</a></div>`;
679
+ } else {
680
+ return `<div class='flatmap-feature-label'>${tooltip}</div>`;
681
+ }
669
682
  }
670
683
  return '';
671
684
  }
@@ -681,36 +694,37 @@ export class UserInteractions
681
694
  return false;
682
695
  }
683
696
 
684
- mouseMoveEvent_(event)
685
- //====================
697
+ __resetFeatureDisplay()
698
+ //=====================
686
699
  {
687
- // No tooltip when context menu is open
688
-
689
- if (this._modal) {
690
- return;
691
- }
692
-
693
700
  // Remove any existing tooltip
694
-
695
701
  this.removeTooltip_();
696
702
 
697
703
  // Reset cursor
698
-
699
704
  this._map.getCanvas().style.cursor = 'default';
700
705
 
701
706
  // Reset any active features
702
-
703
707
  this.resetActiveFeatures_();
708
+ }
704
709
 
705
- // Reset any info display
710
+ mouseMoveEvent_(event)
711
+ //====================
712
+ {
713
+ // No tooltip when context menu is open
714
+ if (this._modal) {
715
+ return;
716
+ }
706
717
 
718
+ // Remove tooltip, reset active features, etc
719
+ this.__resetFeatureDisplay();
720
+
721
+ // Reset any info display
707
722
  const displayInfo = (this._infoControl && this._infoControl.active);
708
723
  if (displayInfo) {
709
724
  this._infoControl.reset()
710
725
  }
711
726
 
712
727
  // Get all the features at the current point
713
-
714
728
  const features = this._map.queryRenderedFeatures(event.point);
715
729
  if (features.length === 0) {
716
730
  this._lastFeatureMouseEntered = null;
@@ -748,18 +762,20 @@ export class UserInteractions
748
762
  && feature.properties.type.startsWith('line'))
749
763
  || 'centreline' in feature.properties));
750
764
  if (lineFeatures.length > 0) {
751
- const lineFeature = lineFeatures[0];
752
- const lineFeatureId = +lineFeature.properties.featureId; // Ensure numeric
753
- tooltip = this.tooltipHtml_(lineFeature.properties);
754
- this.activateFeature_(lineFeature);
755
- const lineIds = new Set(lineFeatures.map(f => f.properties.featureId));
756
- for (const featureId of this._pathways.lineFeatureIds(lineIds)) {
757
- if (+featureId !== lineFeatureId) {
758
- this.activateFeature_(this.mapFeature_(featureId));
765
+ tooltip = this.lineTooltip_(lineFeatures);
766
+ for (const lineFeature of lineFeatures) {
767
+ const lineFeatureId = +lineFeature.properties.featureId; // Ensure numeric
768
+ this.activateFeature_(lineFeature);
769
+ const lineIds = new Set(lineFeatures.map(f => f.properties.featureId));
770
+ for (const featureId of this._pathways.lineFeatureIds(lineIds)) {
771
+ if (+featureId !== lineFeatureId) {
772
+ this.activateFeature_(this.mapFeature_(featureId));
773
+ }
759
774
  }
760
775
  }
761
776
  } else {
762
- let labelledFeatures = features.filter(feature => (('label' in feature.properties
777
+ let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
778
+ || 'label' in feature.properties
763
779
  || 'node' in feature.properties)
764
780
  && (!('tooltip' in feature.properties)
765
781
  || feature.properties.tooltip)))
@@ -801,11 +817,20 @@ export class UserInteractions
801
817
  }
802
818
  info = `<div id="info-control-info">${htmlList.join('\n')}</div>`;
803
819
  }
804
- this.activateFeature_(feature);
805
820
  if ('nerveId' in feature.properties) {
821
+ if (feature.properties.active) {
822
+ this.activateFeature_(feature);
823
+ } else {
824
+ tooltip = '';
825
+ }
806
826
  if (feature.properties.nerveId !== feature.properties.featureId) {
807
827
  this.activateNerveFeatures_(feature.properties.nerveId);
808
828
  }
829
+ } else {
830
+ this.activateFeature_(feature);
831
+ }
832
+ if ('hyperlink' in feature.properties) {
833
+ this._map.getCanvas().style.cursor = 'pointer';
809
834
  }
810
835
  }
811
836
  }
@@ -839,7 +864,7 @@ export class UserInteractions
839
864
  {
840
865
  const multipleSelect = event.ctrlKey || event.metaKey;
841
866
  if (!multipleSelect) {
842
- this.unselectFeatures_();
867
+ this.__unselectFeatures();
843
868
  }
844
869
  if (feature !== undefined) {
845
870
  const featureId = feature.id;
@@ -872,8 +897,17 @@ export class UserInteractions
872
897
  this.clearActiveMarker_();
873
898
  const feature = this._activeFeatures[0]
874
899
  this.selectionEvent_(event.originalEvent, feature);
875
- if (feature !== undefined) {
900
+ if (this._modal) {
901
+ // Remove tooltip, reset active features, etc
902
+ this.__resetFeatureDisplay();
903
+ this.__unselectFeatures();
904
+ this.__clearModal();
905
+ } else if (feature !== undefined) {
906
+ this.__lastClickLngLat = event.lngLat;
876
907
  this.__featureEvent('click', feature);
908
+ if ('hyperlink' in feature.properties) {
909
+ window.open(feature.properties.hyperlink, '_blank');
910
+ }
877
911
  }
878
912
  }
879
913
 
@@ -930,7 +964,9 @@ export class UserInteractions
930
964
  }
931
965
  let position = annotation.centroid;
932
966
  const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
933
- 'sourceLayer': annotation['tile-layer'],
967
+ 'sourceLayer': this._flatmap.options.separateLayers
968
+ ? `${annotation['layer']}_${annotation['tile-layer']}`
969
+ : annotation['tile-layer'],
934
970
  'filter': [
935
971
  'all',
936
972
  [ '==', ['id'], parseInt(featureId) ],
package/src/layers.js CHANGED
@@ -27,41 +27,58 @@ import {PATHWAYS_LAYER} from './pathways.js';
27
27
  import * as style from './styling.js';
28
28
  import * as utils from './utils.js';
29
29
 
30
+ const FEATURES_LAYER = 'features'
31
+
30
32
  //==============================================================================
31
33
 
32
34
  class MapFeatureLayer
33
35
  {
34
- constructor(flatmap, layer, options)
36
+ constructor(flatmap, layer, background_layers=true)
35
37
  {
36
38
  this.__map = flatmap.map;
39
+ this.__separateLayers = flatmap.options.separateLayers;
37
40
  this.__id = layer.id;
38
41
  this.__rasterLayers = [];
39
42
  this.__styleLayers = [];
40
43
 
41
- const haveVectorLayers = (typeof this.__map.getSource('vector-tiles') !== 'undefined');
42
- if (haveVectorLayers) {
43
- this.__addStyleLayer(style.BodyLayer, options);
44
- }
45
- if (flatmap.details['image_layer']) {
46
- for (const raster_layer_id of layer['image-layers']) {
47
- const layerId = this.__addRasterLayer(raster_layer_id, options);
44
+ const layerOptions = flatmap.options.layerOptions;
45
+ const vectorTileSource = this.__map.getSource('vector-tiles');
46
+ const haveVectorLayers = (typeof vectorTileSource !== 'undefined');
47
+ const featuresVectorLayerId = this.__separateLayers
48
+ ? `${this.__id}_${FEATURES_LAYER}`
49
+ : FEATURES_LAYER;
50
+ const vectorFeatures = haveVectorLayers
51
+ && vectorTileSource.vectorLayerIds.indexOf(featuresVectorLayerId) >= 0;
52
+ if (background_layers) {
53
+ if (vectorFeatures) {
54
+ this.__addStyleLayer(style.BodyLayer, layerOptions);
55
+ }
56
+ if (flatmap.details['image-layers']) {
57
+ for (const raster_layer_id of layer['image-layers']) {
58
+ this.__addRasterLayer(raster_layer_id, layerOptions);
59
+ }
48
60
  }
49
61
  }
50
62
  // if no image layers then make feature borders (and lines?) more visible...??
51
63
  if (haveVectorLayers) {
52
- this.__addStyleLayer(style.FeatureFillLayer, options);
53
- this.__addStyleLayer(style.FeatureLineLayer, options);
54
- this.__addStyleLayer(style.FeatureBorderLayer, options);
55
- this.__addPathwayStyleLayers(options);
56
- this.__addStyleLayer(style.FeatureLargeSymbolLayer, options);
57
- if (!flatmap.options.tooltips) {
58
- this.__addStyleLayer(style.FeatureSmallSymbolLayer, options);
64
+ if (vectorFeatures) {
65
+ this.__addStyleLayer(style.FeatureFillLayer, layerOptions);
66
+ this.__addStyleLayer(style.FeatureDashLineLayer, layerOptions);
67
+ this.__addStyleLayer(style.FeatureLineLayer, layerOptions);
68
+ this.__addStyleLayer(style.FeatureBorderLayer, layerOptions);
69
+ }
70
+ this.__addPathwayStyleLayers(layerOptions);
71
+ if (vectorFeatures) {
72
+ this.__addStyleLayer(style.FeatureLargeSymbolLayer, layerOptions);
73
+ if (!flatmap.options.tooltips) {
74
+ this.__addStyleLayer(style.FeatureSmallSymbolLayer, layerOptions);
75
+ }
59
76
  }
60
77
  }
61
78
 
62
- // Make sure our colpur options are set properly, in particular raster layer visibility
79
+ // Make sure our colour options are set properly, in particular raster layer visibility
63
80
 
64
- this.setColour(options);
81
+ this.setColour(layerOptions);
65
82
  }
66
83
 
67
84
  get id()
@@ -81,9 +98,12 @@ class MapFeatureLayer
81
98
  __addPathwayStyleLayers(options)
82
99
  //==============================
83
100
  {
101
+ const pathwaysVectorLayerId = this.__separateLayers
102
+ ? `${this.__id}_${PATHWAYS_LAYER}`
103
+ : PATHWAYS_LAYER;
84
104
  if (this.__map.getSource('vector-tiles')
85
105
  .vectorLayerIds
86
- .indexOf(PATHWAYS_LAYER) >= 0) {
106
+ .indexOf(pathwaysVectorLayerId) >= 0) {
87
107
  this.__addStyleLayer(style.PathLineLayer, options, PATHWAYS_LAYER);
88
108
  this.__addStyleLayer(style.PathDashlineLayer, options, PATHWAYS_LAYER);
89
109
  this.__addStyleLayer(style.NervePolygonBorder, options, PATHWAYS_LAYER);
@@ -92,10 +112,12 @@ class MapFeatureLayer
92
112
  }
93
113
  }
94
114
 
95
- __addStyleLayer(styleClass, options, sourceLayer='features')
96
- //==========================================================
115
+ __addStyleLayer(styleClass, options, sourceLayer=FEATURES_LAYER)
116
+ //==============================================================
97
117
  {
98
- const styleLayer = new styleClass(this.__id, sourceLayer);
118
+ const layerId = `${this.__id}_${sourceLayer}`;
119
+ const source = this.__separateLayers ? layerId : sourceLayer;
120
+ const styleLayer = new styleClass(layerId, source);
99
121
  this.__map.addLayer(styleLayer.style(options));
100
122
  this.__styleLayers.push(styleLayer);
101
123
  }
@@ -129,12 +151,31 @@ export class LayerManager
129
151
  this.__mapLayers = new Map;
130
152
  this.__activeLayers = [];
131
153
  this.__activeLayerNames = [];
154
+ this.__rasterLayers = [];
155
+ const layerOptions = flatmap.options.layerOptions;
156
+ const fcDiagram = ('style' in layerOptions && layerOptions.style == 'fcdiagram');
132
157
  const backgroundLayer = new style.BackgroundLayer();
133
- if ('background' in flatmap.options) {
158
+ if (fcDiagram) {
159
+ this.__map.addLayer(backgroundLayer.style('black', 1));
160
+ }
161
+ else if ('background' in flatmap.options) {
134
162
  this.__map.addLayer(backgroundLayer.style(flatmap.options.background));
135
163
  } else {
136
164
  this.__map.addLayer(backgroundLayer.style('white'));
137
165
  }
166
+ // Add the map's layers
167
+ if (fcDiagram && flatmap.details['image-layers']) {
168
+ for (const layer of flatmap.layers) {
169
+ for (const raster_layer_id of layer['image-layers']) {
170
+ const rasterLayer = new style.RasterLayer(raster_layer_id);
171
+ this.__map.addLayer(rasterLayer.style(layerOptions));
172
+ this.__rasterLayers.push(rasterLayer);
173
+ }
174
+ }
175
+ }
176
+ for (const layer of flatmap.layers) {
177
+ this.addLayer(layer, !fcDiagram);
178
+ }
138
179
  }
139
180
 
140
181
  get activeLayerNames()
@@ -143,12 +184,12 @@ export class LayerManager
143
184
  return this.__activeLayerNames;
144
185
  }
145
186
 
146
- addLayer(layer, options)
147
- //======================
187
+ addLayer(layer, background_layers=true)
188
+ //=====================================
148
189
  {
149
190
  this.__mapLayers.set(layer.id, layer);
150
191
 
151
- const layers = new MapFeatureLayer(this.__flatmap, layer, options);
192
+ const layers = new MapFeatureLayer(this.__flatmap, layer, background_layers);
152
193
  const layerId = this.__flatmap.mapLayerId(layer.id);
153
194
  this.__layers.set(layerId, layers);
154
195
  }
package/src/styling.js CHANGED
@@ -28,9 +28,9 @@ export const VECTOR_TILES_SOURCE = 'vector-tiles';
28
28
 
29
29
  class VectorStyleLayer
30
30
  {
31
- constructor(mapLayerId, sourceLayer, idPrefix)
31
+ constructor(id, suffix, sourceLayer)
32
32
  {
33
- this.__id = `${mapLayerId}_${sourceLayer}_${idPrefix}`;
33
+ this.__id = `${id}_${suffix}`;
34
34
  this.__sourceLayer = sourceLayer;
35
35
  this.__lastPaintStyle = {};
36
36
  }
@@ -78,9 +78,9 @@ class VectorStyleLayer
78
78
 
79
79
  export class BodyLayer extends VectorStyleLayer
80
80
  {
81
- constructor(mapLayerId, sourceLayer)
81
+ constructor(id, sourceLayer)
82
82
  {
83
- super(mapLayerId, sourceLayer, 'body');
83
+ super(id, 'body', sourceLayer);
84
84
  }
85
85
 
86
86
  style(options)
@@ -105,9 +105,9 @@ export class BodyLayer extends VectorStyleLayer
105
105
 
106
106
  export class FeatureFillLayer extends VectorStyleLayer
107
107
  {
108
- constructor(mapLayerId, sourceLayer)
108
+ constructor(id, sourceLayer)
109
109
  {
110
- super(mapLayerId, sourceLayer, 'fill');
110
+ super(id, 'fill', sourceLayer);
111
111
  }
112
112
 
113
113
  paintStyle(options, changes=false)
@@ -117,12 +117,12 @@ export class FeatureFillLayer extends VectorStyleLayer
117
117
  const paintStyle = {
118
118
  'fill-color': [
119
119
  'case',
120
- ['boolean', ['feature-state', 'active'], false], coloured ? '#D88' : '#CCC',
121
120
  ['boolean', ['feature-state', 'selected'], false], '#0F0',
121
+ ['has', 'colour'], ['get', 'colour'],
122
+ ['boolean', ['feature-state', 'active'], false], coloured ? '#D88' : '#CCC',
122
123
  ['any',
123
124
  ['==', ['get', 'kind'], 'scaffold']
124
125
  ], 'white',
125
- ['has', 'colour'], ['get', 'colour'],
126
126
  ['has', 'node'], '#AFA202',
127
127
  'white' // background colour? body colour ??
128
128
  ],
@@ -133,10 +133,10 @@ export class FeatureFillLayer extends VectorStyleLayer
133
133
  ['==', ['get', 'kind'], 'tissue'],
134
134
  ['==', ['get', 'kind'], 'cell-type'],
135
135
  ], 0.1,
136
- ['has', 'colour'], 0.008,
137
136
  ['has', 'node'], 0.3,
138
137
  ['boolean', ['feature-state', 'selected'], false], 1.0,
139
138
  ['boolean', ['feature-state', 'active'], false], 0.8,
139
+ ['has', 'colour'], 0.008,
140
140
  (coloured && !dimmed) ? 0.01 : 0.5
141
141
  ]
142
142
  };
@@ -165,9 +165,9 @@ export class FeatureFillLayer extends VectorStyleLayer
165
165
 
166
166
  export class FeatureBorderLayer extends VectorStyleLayer
167
167
  {
168
- constructor(mapLayerId, sourceLayer)
168
+ constructor(id, sourceLayer)
169
169
  {
170
- super(mapLayerId, sourceLayer, 'border');
170
+ super(id, 'border', sourceLayer);
171
171
  }
172
172
 
173
173
  paintStyle(options, changes=false)
@@ -236,68 +236,105 @@ export class FeatureBorderLayer extends VectorStyleLayer
236
236
 
237
237
  export class FeatureLineLayer extends VectorStyleLayer
238
238
  {
239
- constructor(mapLayerId, sourceLayer)
239
+ constructor(id, sourceLayer, dashed=false)
240
240
  {
241
- super(mapLayerId, sourceLayer, 'divider-line');
241
+ const filterType = dashed ? 'line-dash' : 'line';
242
+ super(id, `feature-${filterType}`, sourceLayer);
243
+ this.__filter = dashed ?
244
+ [
245
+ 'any',
246
+ ['==', 'type', `line-dash`]
247
+ ]
248
+ :
249
+ [
250
+ 'any',
251
+ ['has', 'centreline'],
252
+ ['==', 'type', 'bezier'],
253
+ ['==', 'type', `line`]
254
+ ];
255
+ this.__dashed = dashed;
242
256
  }
243
257
 
244
- style(options)
258
+ paintStyle(options)
245
259
  {
246
260
  const coloured = !('colour' in options) || options.colour;
261
+ const paintStyle = {
262
+ 'line-color': [
263
+ 'case',
264
+ ['boolean', ['feature-state', 'selected'], false], '#0F0',
265
+ ['has', 'colour'], ['get', 'colour'],
266
+ ['boolean', ['feature-state', 'active'], false], coloured ? '#888' : '#CCC',
267
+ ['==', ['get', 'type'], 'network'], '#AFA202',
268
+ ['has', 'centreline'], '#888',
269
+ ('style' in options && options.style === 'authoring') ? '#C44' : '#444'
270
+ ],
271
+ 'line-opacity': [
272
+ 'case',
273
+ ['boolean', ['feature-state', 'selected'], false], 1.0,
274
+ ['boolean', ['feature-state', 'active'], false], 1.0,
275
+ 0.3
276
+ ],
277
+ 'line-width': [
278
+ 'let',
279
+ 'width', [
280
+ 'case',
281
+ ['has', 'centreline'], 1.2,
282
+ ['==', ['get', 'type'], 'network'], 1.2,
283
+ ['boolean', ['feature-state', 'selected'], false], 1.2,
284
+ ['boolean', ['feature-state', 'active'], false], 1.2,
285
+ ('style' in options && options.style === 'authoring') ? 0.7 : 0.5
286
+ ], [
287
+ 'interpolate',
288
+ ['exponential', 2],
289
+ ['zoom'],
290
+ 2, ["*", ['var', 'width'], ["^", 2, -0.5]],
291
+ 7, ["*", ['var', 'width'], ["^", 2, 2.5]],
292
+ 9, ["*", ['var', 'width'], ["^", 2, 4.0]]
293
+ ]
294
+ ]
295
+ // Need to vary width based on zoom??
296
+ // Or opacity??
297
+ };
298
+ if (this.__dashed) {
299
+ paintStyle['line-dasharray'] = [3, 2];
300
+ }
301
+ return paintStyle;
302
+ }
303
+
304
+ style(options)
305
+ {
247
306
  return {
248
307
  ...super.style(),
249
308
  'type': 'line',
250
309
  'filter': [
251
- 'all',
252
- ['==', '$type', 'LineString']
310
+ 'all',
311
+ ['==', '$type', 'LineString'],
312
+ this.__filter
253
313
  // not for paths...
254
314
  ],
255
- 'paint': {
256
- 'line-color': [
257
- 'case',
258
- ['boolean', ['feature-state', 'active'], false], coloured ? '#D88' : '#CCC',
259
- ['boolean', ['feature-state', 'selected'], false], '#0F0',
260
- ['==', ['get', 'type'], 'network'], '#AFA202',
261
- ['has', 'centreline'], '#888',
262
- ('authoring' in options && options.authoring) ? '#C44' : '#444'
263
- ],
264
- 'line-opacity': [
265
- 'case',
266
- ['boolean', ['feature-state', 'active'], false], 1.0,
267
- 0.3
268
- ],
269
- 'line-width': [
270
- 'let',
271
- 'width', [
272
- 'case',
273
- ['has', 'centreline'], 1.2,
274
- ['==', ['get', 'type'], 'network'], 1.2,
275
- ['boolean', ['feature-state', 'active'], false], 1.2,
276
- ('authoring' in options && options.authoring) ? 0.7 : 0.1
277
- ], [
278
- 'interpolate',
279
- ['exponential', 2],
280
- ['zoom'],
281
- 2, ["*", ['var', 'width'], ["^", 2, -0.5]],
282
- 7, ["*", ['var', 'width'], ["^", 2, 2.5]],
283
- 9, ["*", ['var', 'width'], ["^", 2, 4.0]]
284
- ]
285
- ]
286
- // Need to vary width based on zoom??
287
- // Or opacity??
288
- }
315
+ 'paint': this.paintStyle(options)
289
316
  };
290
317
  }
291
318
  }
292
319
 
293
320
  //==============================================================================
294
321
 
322
+ export class FeatureDashLineLayer extends FeatureLineLayer
323
+ {
324
+ constructor(id, sourceLayer)
325
+ {
326
+ super(id, sourceLayer, true);
327
+ }
328
+ }
329
+
330
+ //==============================================================================
331
+
295
332
  export class PathLineLayer extends VectorStyleLayer
296
333
  {
297
- constructor(mapLayerId, sourceLayer, dashed=false)
334
+ constructor(id, sourceLayer, dashed=false)
298
335
  {
299
336
  const filterType = dashed ? 'line-dash' : 'line';
300
- super(mapLayerId, sourceLayer, filterType);
337
+ super(id, `path-${filterType}`, sourceLayer);
301
338
  this.__filter = dashed ?
302
339
  [
303
340
  'any',
@@ -386,9 +423,9 @@ export class PathLineLayer extends VectorStyleLayer
386
423
 
387
424
  export class PathDashlineLayer extends PathLineLayer
388
425
  {
389
- constructor(mapLayerId, sourceLayer)
426
+ constructor(id, sourceLayer)
390
427
  {
391
- super(mapLayerId, sourceLayer, true);
428
+ super(id, sourceLayer, true);
392
429
  }
393
430
  }
394
431
 
@@ -396,9 +433,9 @@ export class PathDashlineLayer extends PathLineLayer
396
433
 
397
434
  export class FeatureNerveLayer extends VectorStyleLayer
398
435
  {
399
- constructor(mapLayerId, sourceLayer)
436
+ constructor(id, sourceLayer)
400
437
  {
401
- super(mapLayerId, sourceLayer, 'nerve-path');
438
+ super(id, 'nerve-path', sourceLayer);
402
439
  }
403
440
 
404
441
  style(options)
@@ -417,7 +454,8 @@ export class FeatureNerveLayer extends VectorStyleLayer
417
454
  ['boolean', ['feature-state', 'active'], false], '#222',
418
455
  ['boolean', ['feature-state', 'selected'], false], 'red',
419
456
  ['boolean', ['feature-state', 'hidden'], false], '#CCC',
420
- '#888'
457
+ ['boolean', ['get', 'active'], false], '#888',
458
+ '#FFF'
421
459
  ],
422
460
  'line-opacity': [
423
461
  'case',
@@ -449,9 +487,9 @@ export class FeatureNerveLayer extends VectorStyleLayer
449
487
 
450
488
  export class NervePolygonBorder extends VectorStyleLayer
451
489
  {
452
- constructor(mapLayerId, sourceLayer)
490
+ constructor(id, sourceLayer)
453
491
  {
454
- super(mapLayerId, sourceLayer, 'nerve-border');
492
+ super(id, 'nerve-border', sourceLayer);
455
493
  }
456
494
 
457
495
  style(options)
@@ -493,9 +531,9 @@ export class NervePolygonBorder extends VectorStyleLayer
493
531
 
494
532
  export class NervePolygonFill extends VectorStyleLayer
495
533
  {
496
- constructor(mapLayerId, sourceLayer)
534
+ constructor(id, sourceLayer)
497
535
  {
498
- super(mapLayerId, sourceLayer, 'nerve-fill');
536
+ super(id, 'nerve-fill', sourceLayer);
499
537
  }
500
538
 
501
539
  style(options)
@@ -543,9 +581,9 @@ export class NervePolygonFill extends VectorStyleLayer
543
581
 
544
582
  export class FeatureLargeSymbolLayer extends VectorStyleLayer
545
583
  {
546
- constructor(mapLayerId, sourceLayer)
584
+ constructor(id, sourceLayer)
547
585
  {
548
- super(mapLayerId, sourceLayer, 'large-symbol');
586
+ super(id, 'large-symbol', sourceLayer);
549
587
  }
550
588
 
551
589
  style(options)
@@ -587,9 +625,9 @@ export class FeatureLargeSymbolLayer extends VectorStyleLayer
587
625
 
588
626
  export class FeatureSmallSymbolLayer extends VectorStyleLayer
589
627
  {
590
- constructor(mapLayerId, sourceLayer)
628
+ constructor(id, sourceLayer)
591
629
  {
592
- super(mapLayerId, sourceLayer, 'small-symbol');
630
+ super(id, 'small-symbol', sourceLayer);
593
631
  }
594
632
 
595
633
  style(options)
@@ -630,7 +668,7 @@ export class FeatureSmallSymbolLayer extends VectorStyleLayer
630
668
 
631
669
  export class BackgroundLayer
632
670
  {
633
- constructor(rasterLayerId)
671
+ constructor()
634
672
  {
635
673
  this.__id = 'background';
636
674
  }
@@ -640,14 +678,14 @@ export class BackgroundLayer
640
678
  return this.__id;
641
679
  }
642
680
 
643
- style(backgroundColour)
681
+ style(backgroundColour, opacity=0.1)
644
682
  {
645
683
  return {
646
- 'id': 'background',
684
+ 'id': this.__id,
647
685
  'type': 'background',
648
686
  'paint': {
649
687
  'background-color': backgroundColour,
650
- 'background-opacity': 0.1
688
+ 'background-opacity': opacity
651
689
  }
652
690
  };
653
691
  }
@@ -657,9 +695,9 @@ export class BackgroundLayer
657
695
 
658
696
  export class RasterLayer
659
697
  {
660
- constructor(rasterLayerId)
698
+ constructor(id)
661
699
  {
662
- this.__id = rasterLayerId;
700
+ this.__id = id;
663
701
  }
664
702
 
665
703
  get id()