@abi-software/flatmap-viewer 2.4.0-a.1 → 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.1``
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.1",
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;
@@ -60,8 +60,9 @@ class FlatMap
60
60
  this._baseUrl = mapBaseUrl;
61
61
  this.__id = mapDescription.id;
62
62
  this.__uuid = mapDescription.uuid;
63
- this._details = mapDescription.details;
64
- this._created = mapDescription.created;
63
+ this.__details = mapDescription.details;
64
+ this.__provenance = mapDescription.provenance;
65
+ this.__created = mapDescription.created;
65
66
  this.__taxon = mapDescription.taxon;
66
67
  this.__biologicalSex = mapDescription.biologicalSex;
67
68
  this._mapNumber = mapDescription.number;
@@ -78,6 +79,7 @@ class FlatMap
78
79
  this.__modelToFeatureIds = new Map();
79
80
  this.__mapSourceToFeatureIds = new Map();
80
81
  this.__annIdToFeatureId = new Map();
82
+ this.__taxonToFeatureIds = new Map();
81
83
 
82
84
  for (const [featureId, annotation] of Object.entries(mapDescription.annotations)) {
83
85
  this.__addAnnotation(featureId, annotation);
@@ -330,6 +332,26 @@ class FlatMap
330
332
  }
331
333
  }
332
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
+
333
355
  /**
334
356
  * Hide or show centrelines and nodes.
335
357
  *
@@ -424,7 +446,7 @@ class FlatMap
424
446
  get created()
425
447
  //===========
426
448
  {
427
- return this._created;
449
+ return this.__created;
428
450
  }
429
451
 
430
452
  /**
@@ -460,7 +482,18 @@ class FlatMap
460
482
  get details()
461
483
  //===========
462
484
  {
463
- return this._details;
485
+ return this.__details;
486
+ }
487
+
488
+ /**
489
+ * The map's provenance as returned from the map server.
490
+ *
491
+ * @type Object
492
+ */
493
+ get provenance()
494
+ //==============
495
+ {
496
+ return this.__provenance;
464
497
  }
465
498
 
466
499
  /**
@@ -499,16 +532,28 @@ class FlatMap
499
532
  }
500
533
  }
501
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
+
502
546
  __updateFeatureIdMap(property, featureIdMap, annotation)
503
547
  //======================================================
504
548
  {
505
549
  if (property in annotation) {
506
- const id = utils.normaliseId(annotation[property]);
507
- const featureIds = featureIdMap.get(id);
508
- if (featureIds) {
509
- 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
+ }
510
555
  } else {
511
- featureIdMap.set(id, [annotation.featureId]);
556
+ this.__updateFeatureIdMapEntry(propertyId, featureIdMap, annotation.featureId)
512
557
  }
513
558
  }
514
559
  }
@@ -521,6 +566,7 @@ class FlatMap
521
566
  this.__updateFeatureIdMap('dataset', this.__datasetToFeatureIds, ann);
522
567
  this.__updateFeatureIdMap('models', this.__modelToFeatureIds, ann);
523
568
  this.__updateFeatureIdMap('source', this.__mapSourceToFeatureIds, ann);
569
+ this.__updateFeatureIdMap('taxons', this.__taxonToFeatureIds, ann);
524
570
  this.__annIdToFeatureId.set(ann.id, featureId);
525
571
  }
526
572
 
@@ -598,6 +644,17 @@ class FlatMap
598
644
  return [...this.__modelToFeatureIds.keys()]
599
645
  }
600
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
+
601
658
  /**
602
659
  * Datasets associated with the map.
603
660
  *
@@ -805,7 +862,7 @@ class FlatMap
805
862
  /**
806
863
  * Get a list of the flatmap's layers.
807
864
  *
808
- * @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
809
866
  */
810
867
  getLayers()
811
868
  //=========
@@ -833,7 +890,7 @@ class FlatMap
833
890
  /**
834
891
  * Get a list of a FC flatmap's systems.
835
892
  *
836
- * @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
837
894
  */
838
895
  getSystems()
839
896
  //==========
@@ -958,6 +1015,7 @@ class FlatMap
958
1015
  'models',
959
1016
  'nodeId',
960
1017
  'source',
1018
+ 'taxons',
961
1019
  'hyperlinks'
962
1020
  ];
963
1021
  const jsonProperties = [
@@ -1405,6 +1463,10 @@ export class MapManager
1405
1463
 
1406
1464
  const annotations = await this._mapServer.loadJSON(`flatmap/${mapId}/annotations`);
1407
1465
 
1466
+ // Get the map's provenance
1467
+
1468
+ const provenance = await this._mapServer.loadJSON(`flatmap/${mapId}/metadata`);
1469
+
1408
1470
  // Get additional marker details for the map
1409
1471
 
1410
1472
  const mapMarkers = await this._mapServer.loadJSON(`flatmap/${mapId}/markers`);
@@ -1458,6 +1520,7 @@ export class MapManager
1458
1520
  annotations: annotations,
1459
1521
  number: this._mapNumber,
1460
1522
  pathways: pathways,
1523
+ provenance, provenance,
1461
1524
  callback: callback
1462
1525
  },
1463
1526
  resolve);
@@ -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/main.js CHANGED
@@ -62,7 +62,6 @@ export async function standaloneViewer(map_endpoint=null, options={})
62
62
  showId: true,
63
63
  showPosition: false,
64
64
  standalone: true,
65
- annotator: true
66
65
  }, options);
67
66
 
68
67
  function loadMap(id, taxon, sex)
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');