@abi-software/flatmap-viewer 2.4.0-a.2 → 2.4.1-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 CHANGED
@@ -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@2.4.0-a.2``
41
+ * ``npm install @abi-software/flatmap-viewer@2.4.1-b.1``
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.4.0-a.2",
3
+ "version": "2.4.1-b.1",
4
4
  "description": "Flatmap viewer using Maplibre GL",
5
5
  "repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
6
6
  "main": "src/main.js",
@@ -26,7 +26,7 @@
26
26
  "bezier-js": "^6.1.0",
27
27
  "html-es6cape": "^2.0.2",
28
28
  "jspanel4": "^4.16.1",
29
- "maplibre-gl": ">=2.4.0",
29
+ "maplibre-gl": ">=3.0.0",
30
30
  "minisearch": "^2.2.1",
31
31
  "polylabel": "^1.1.0"
32
32
  },
@@ -22,7 +22,7 @@ limitations under the License.
22
22
 
23
23
  //==============================================================================
24
24
 
25
- import maplibre from 'maplibre-gl';
25
+ import maplibregl from 'maplibre-gl';
26
26
 
27
27
  //==============================================================================
28
28
 
@@ -64,7 +64,7 @@ export class ContextMenu
64
64
  this._flatmap = flatmap;
65
65
  this._map = flatmap.map;
66
66
  this._closeCallback = closeCallback;
67
- this._popup = new maplibre.Popup({
67
+ this._popup = new maplibregl.Popup({
68
68
  closeButton: true,
69
69
  closeOnClick: true,
70
70
  className: 'flatmap-contextmenu-popup',
@@ -45,7 +45,7 @@ limitations under the License.
45
45
 
46
46
  //==============================================================================
47
47
 
48
- import maplibre from 'maplibre-gl';
48
+ import maplibregl from 'maplibre-gl';
49
49
 
50
50
  //==============================================================================
51
51
 
@@ -146,7 +146,7 @@ export class MinimapControl
146
146
 
147
147
  // Create the actual minimap
148
148
 
149
- this._miniMap = new maplibre.Map({
149
+ this._miniMap = new maplibregl.Map({
150
150
  attributionControl: false,
151
151
  container: container,
152
152
  style: map.getStyle(),
@@ -32,7 +32,7 @@ export class SystemsControl extends Control
32
32
  }
33
33
 
34
34
  _addControlDetails()
35
- //===============
35
+ //==================
36
36
  {
37
37
  let lines = 0;
38
38
  let enabled = 0;
@@ -79,6 +79,7 @@ class FlatMap
79
79
  this.__modelToFeatureIds = new Map();
80
80
  this.__mapSourceToFeatureIds = new Map();
81
81
  this.__annIdToFeatureId = new Map();
82
+ this.__taxonToFeatureIds = new Map();
82
83
 
83
84
  for (const [featureId, annotation] of Object.entries(mapDescription.annotations)) {
84
85
  this.__addAnnotation(featureId, annotation);
@@ -331,6 +332,26 @@ class FlatMap
331
332
  }
332
333
  }
333
334
 
335
+ /**
336
+ * Show or hide connectivity features observed in particular species.
337
+ *
338
+ * @param {string | Array.<string>} taxonId(s) A single taxon identifier
339
+ * or an array of identifiers.
340
+ * @param {boolean} enable Show or hide connectivity paths and features.
341
+ * Defaults to ``true`` (show)
342
+ */
343
+ enableConnectivityByTaxonIds(taxonIds, enable=true)
344
+ //=================================================
345
+ {
346
+ if (this._userInteractions !== null) {
347
+ if (Array.isArray(taxonIds)) {
348
+ this._userInteractions.enableConnectivityByTaxonIds(taxonIds, enable);
349
+ } else {
350
+ this._userInteractions.enableConnectivityByTaxonIds([taxonIds], enable);
351
+ }
352
+ }
353
+ }
354
+
334
355
  /**
335
356
  * Hide or show centrelines and nodes.
336
357
  *
@@ -511,16 +532,28 @@ class FlatMap
511
532
  }
512
533
  }
513
534
 
535
+ __updateFeatureIdMapEntry(propertyId, featureIdMap, featureId)
536
+ //======================================================
537
+ {
538
+ const featureIds = featureIdMap.get(propertyId);
539
+ if (featureIds) {
540
+ featureIds.push(featureId);
541
+ } else {
542
+ featureIdMap.set(propertyId, [featureId]);
543
+ }
544
+ }
545
+
514
546
  __updateFeatureIdMap(property, featureIdMap, annotation)
515
547
  //======================================================
516
548
  {
517
549
  if (property in annotation) {
518
- const id = utils.normaliseId(annotation[property]);
519
- const featureIds = featureIdMap.get(id);
520
- if (featureIds) {
521
- featureIds.push(annotation.featureId);
550
+ const propertyId = annotation[property];
551
+ if (Array.isArray(propertyId)) {
552
+ for (const id of propertyId) {
553
+ this.__updateFeatureIdMapEntry(id, featureIdMap, annotation.featureId);
554
+ }
522
555
  } else {
523
- featureIdMap.set(id, [annotation.featureId]);
556
+ this.__updateFeatureIdMapEntry(propertyId, featureIdMap, annotation.featureId)
524
557
  }
525
558
  }
526
559
  }
@@ -533,6 +566,7 @@ class FlatMap
533
566
  this.__updateFeatureIdMap('dataset', this.__datasetToFeatureIds, ann);
534
567
  this.__updateFeatureIdMap('models', this.__modelToFeatureIds, ann);
535
568
  this.__updateFeatureIdMap('source', this.__mapSourceToFeatureIds, ann);
569
+ this.__updateFeatureIdMap('taxons', this.__taxonToFeatureIds, ann);
536
570
  this.__annIdToFeatureId.set(ann.id, featureId);
537
571
  }
538
572
 
@@ -610,6 +644,17 @@ class FlatMap
610
644
  return [...this.__modelToFeatureIds.keys()]
611
645
  }
612
646
 
647
+ /**
648
+ * The taxon identifiers of species which the map's connectivity has been observed in.
649
+ *
650
+ * @type {string|Array.<string>}
651
+ */
652
+ get taxonIdentifiers()
653
+ //====================
654
+ {
655
+ return [...this.__taxonToFeatureIds.keys()]
656
+ }
657
+
613
658
  /**
614
659
  * Datasets associated with the map.
615
660
  *
@@ -817,7 +862,7 @@ class FlatMap
817
862
  /**
818
863
  * Get a list of the flatmap's layers.
819
864
  *
820
- * @return {Array.Object.<{id: string, description: string, enabled: boolean}>} An array with layer details
865
+ * @return {Array.<{id: string, description: string, enabled: boolean}>} An array with layer details
821
866
  */
822
867
  getLayers()
823
868
  //=========
@@ -845,7 +890,7 @@ class FlatMap
845
890
  /**
846
891
  * Get a list of a FC flatmap's systems.
847
892
  *
848
- * @return {Array.Object.<{id: string, name: string, colour: string, enabled: boolean}>} An array with system details
893
+ * @return {Array.<{id: string, name: string, colour: string, enabled: boolean}>} An array with system details
849
894
  */
850
895
  getSystems()
851
896
  //==========
@@ -970,7 +1015,7 @@ class FlatMap
970
1015
  'models',
971
1016
  'nodeId',
972
1017
  'source',
973
- 'taxon',
1018
+ 'taxons',
974
1019
  'hyperlinks'
975
1020
  ];
976
1021
  const jsonProperties = [
@@ -22,7 +22,7 @@ limitations under the License.
22
22
 
23
23
  //==============================================================================
24
24
 
25
- import maplibre from 'maplibre-gl';
25
+ import maplibregl from 'maplibre-gl';
26
26
 
27
27
  import {default as turfArea} from '@turf/area';
28
28
  import {default as turfBBox} from '@turf/bbox';
@@ -80,12 +80,13 @@ function bounds(feature)
80
80
 
81
81
  //==============================================================================
82
82
 
83
- function expandBounds(bbox1, bbox2)
84
- //=================================
83
+ function expandBounds(bbox1, bbox2, padding)
84
+ //==========================================
85
85
  {
86
- return (bbox1 === null) ? bbox2
87
- : [Math.min(bbox1[0], bbox2[0]), Math.min(bbox1[1], bbox2[1]),
88
- Math.max(bbox1[2], bbox2[2]), Math.max(bbox1[3], bbox2[3])
86
+ return (bbox1 === null) ? [bbox2[0]-padding.lng, bbox2[1]-padding.lat,
87
+ bbox2[2]+padding.lng, bbox2[3]+padding.lat]
88
+ : [Math.min(bbox1[0], bbox2[0]-padding.lng), Math.min(bbox1[1], bbox2[1]-padding.lat),
89
+ Math.max(bbox1[2], bbox2[2]+padding.lng), Math.max(bbox1[3], bbox2[3]+padding.lat)
89
90
  ];
90
91
  }
91
92
 
@@ -615,7 +616,7 @@ export class UserInteractions
615
616
  {
616
617
  options = utils.setDefaults(options, {
617
618
  noZoomIn: false,
618
- padding: 10
619
+ padding: 10 // pixels
619
620
  });
620
621
  if (featureIds.length) {
621
622
  this.unselectFeatures();
@@ -624,16 +625,20 @@ export class UserInteractions
624
625
  const bounds = this._map.getBounds().toArray();
625
626
  bbox = [...bounds[0], ...bounds[1]];
626
627
  }
628
+ // Convert pixel padding to LngLat and apply it to a feature's bounds
629
+ const padding = this._map.unproject({x: options.padding, y: options.padding});
630
+ padding.lng -= bbox[0];
631
+ padding.lat = bbox[3] - padding.lat;
627
632
  for (const featureId of featureIds) {
628
633
  const annotation = this._flatmap.annotation(featureId);
629
634
  if (annotation) {
630
635
  if (this.selectFeature(featureId)) {
631
- bbox = expandBounds(bbox, annotation.bounds);
636
+ bbox = expandBounds(bbox, annotation.bounds, padding);
632
637
  if ('type' in annotation && annotation.type.startsWith('line')) {
633
638
  for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
634
639
  if (this.selectFeature(pathFeatureId)) {
635
640
  const pathAnnotation = this._flatmap.annotation(pathFeatureId)
636
- bbox = expandBounds(bbox, pathAnnotation.bounds);
641
+ bbox = expandBounds(bbox, pathAnnotation.bounds, padding);
637
642
  }
638
643
  }
639
644
  }
@@ -642,7 +647,7 @@ export class UserInteractions
642
647
  }
643
648
  if (bbox !== null) {
644
649
  this._map.fitBounds(bbox, {
645
- padding: options.padding,
650
+ padding: 0,
646
651
  animate: false
647
652
  });
648
653
  }
@@ -694,7 +699,7 @@ export class UserInteractions
694
699
  this._map.panTo(location);
695
700
  }
696
701
  this.setModal_();
697
- this._currentPopup = new maplibre.Popup(options).addTo(this._map);
702
+ this._currentPopup = new maplibregl.Popup(options).addTo(this._map);
698
703
  this._currentPopup.on('close', this.__onCloseCurrentPopup.bind(this));
699
704
  this._currentPopup.setLngLat(location);
700
705
  if (typeof content === 'object') {
@@ -955,7 +960,7 @@ export class UserInteractions
955
960
  html = `<span>${header}</span><br/>${html}`;
956
961
  }
957
962
  if (html !== '') {
958
- this._tooltip = new maplibre.Popup({
963
+ this._tooltip = new maplibregl.Popup({
959
964
  closeButton: false,
960
965
  closeOnClick: false,
961
966
  maxWidth: 'none',
@@ -1127,6 +1132,16 @@ export class UserInteractions
1127
1132
  this._layerManager.enableSckanPaths(sckanState, enable);
1128
1133
  }
1129
1134
 
1135
+ enableConnectivityByTaxonIds(taxonIds, enable=true)
1136
+ //=================================================
1137
+ {
1138
+ if (enable) {
1139
+ this._layerManager.setFilter({taxons: taxonIds});
1140
+ } else {
1141
+ this._layerManager.setFilter({taxons: []});
1142
+ }
1143
+ }
1144
+
1130
1145
  excludeAnnotated(exclude=false)
1131
1146
  //=============================
1132
1147
  {
@@ -1190,8 +1205,8 @@ export class UserInteractions
1190
1205
  // order to apply a scale transform we need to create marker icons
1191
1206
  // inside the marker container <div>.
1192
1207
  const colour = options.colour || '#005974';
1193
- const markerHTML = options.element ? new maplibre.Marker({element: options.element})
1194
- : new maplibre.Marker({color: colour});
1208
+ const markerHTML = options.element ? new maplibregl.Marker({element: options.element})
1209
+ : new maplibregl.Marker({color: colour});
1195
1210
 
1196
1211
  const markerElement = document.createElement('div');
1197
1212
  const markerIcon = document.createElement('div');
@@ -1201,9 +1216,9 @@ export class UserInteractions
1201
1216
  markerElement.appendChild(markerIcon);
1202
1217
 
1203
1218
  const markerPosition = this.__markerPosition(featureId, annotation);
1204
- const marker = new maplibre.Marker(markerElement)
1205
- .setLngLat(markerPosition)
1206
- .addTo(this._map);
1219
+ const marker = new maplibregl.Marker(markerElement)
1220
+ .setLngLat(markerPosition)
1221
+ .addTo(this._map);
1207
1222
  markerElement.addEventListener('mouseenter',
1208
1223
  this.markerMouseEvent_.bind(this, marker, anatomicalId));
1209
1224
  markerElement.addEventListener('mousemove',
@@ -1345,7 +1360,7 @@ export class UserInteractions
1345
1360
 
1346
1361
  element.addEventListener('click', e => this.__clearActiveMarker());
1347
1362
 
1348
- this._tooltip = new maplibre.Popup({
1363
+ this._tooltip = new maplibregl.Popup({
1349
1364
  closeButton: false,
1350
1365
  closeOnClick: false,
1351
1366
  maxWidth: 'none',
package/src/styling.js CHANGED
@@ -374,24 +374,24 @@ function sckanFilter(options)
374
374
  : options.sckan.toLowerCase();
375
375
  const sckanFilter =
376
376
  sckanState == 'none' ? [
377
- ['!has', 'sckan']
377
+ ['!', ['has', 'sckan']]
378
378
  ] :
379
379
  sckanState == 'valid' ? [[
380
380
  'any',
381
- ['!has', 'sckan'],
381
+ ['!', ['has', 'sckan']],
382
382
  [
383
383
  'all',
384
384
  ['has', 'sckan'],
385
- ['==', 'sckan', true]
385
+ ['==', ['get', 'sckan'], true]
386
386
  ]
387
387
  ]] :
388
388
  sckanState == 'invalid' ? [[
389
389
  'any',
390
- ['!has', 'sckan'],
390
+ ['!', ['has', 'sckan']],
391
391
  [
392
392
  'all',
393
393
  ['has', 'sckan'],
394
- ['!=', 'sckan', true]
394
+ ['!=', ['get', 'sckan'], true]
395
395
  ]
396
396
  ]] :
397
397
  [ ];
@@ -411,7 +411,6 @@ export class AnnotatedPathLayer extends VectorStyleLayer
411
411
  {
412
412
  return [
413
413
  'all',
414
- ['==', '$type', 'LineString'],
415
414
  ...sckanFilter(options)
416
415
  ];
417
416
  }
@@ -481,22 +480,33 @@ export class PathLineLayer extends VectorStyleLayer
481
480
  makeFilter(options={})
482
481
  {
483
482
  const sckan_filter = sckanFilter(options);
483
+ let taxonFilter = [];
484
+ if ('taxons' in options) {
485
+ taxonFilter.push('all');
486
+ taxonFilter.push(['has', 'taxons']);
487
+ const filterList = ['any'];
488
+ for (const taxon of options.taxons) {
489
+ filterList.push(['in', taxon, ['get', 'taxons']]);
490
+ }
491
+ taxonFilter.push(filterList);
492
+ taxonFilter = [taxonFilter];
493
+ }
484
494
 
485
495
  return this.__dashed ? [
486
496
  'all',
487
- ['==', '$type', 'LineString'],
488
- ['==', 'type', `line-dash`],
489
- ...sckan_filter
497
+ ['==', ['get', 'type'], 'line-dash'],
498
+ ...sckan_filter,
499
+ ...taxonFilter
490
500
  ] : [
491
501
  'all',
492
- ['==', '$type', 'LineString'],
493
502
  [
494
503
  'any',
495
- ['==', 'type', 'bezier'],
504
+ ['==', ['get', 'type'], 'bezier'],
496
505
  [
497
506
  'all',
498
- ['==', 'type', `line`],
499
- ...sckan_filter
507
+ ['==', ['get', 'type'], 'line'],
508
+ ...sckan_filter,
509
+ ...taxonFilter
500
510
  ]
501
511
  ]
502
512
  ];
package/src/utils.js CHANGED
@@ -22,6 +22,10 @@ limitations under the License.
22
22
 
23
23
  //==============================================================================
24
24
 
25
+ const NO_NORMALISATION = ['http', 'https', 'urn', 'NCBITaxon'];
26
+
27
+ //==============================================================================
28
+
25
29
  export class List extends Array {
26
30
  constructor(iterable=null) {
27
31
  super();
@@ -101,7 +105,7 @@ export function normaliseId(id)
101
105
  }
102
106
  const parts = id.split(':')
103
107
  const lastPart = parts[parts.length - 1]
104
- if (['http', 'https', 'urn'].includes(parts[0]) || !'0123456789'.includes(lastPart[0])) {
108
+ if (NO_NORMALISATION.includes(parts[0]) || !'0123456789'.includes(lastPart[0])) {
105
109
  return id;
106
110
  }
107
111
  parts[parts.length - 1] = lastPart.padStart(8, '0');