@abi-software/flatmap-viewer 2.2.4 → 2.2.6

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.2.4``
41
+ * ``npm install @abi-software/flatmap-viewer@2.2.6``
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.4",
3
+ "version": "2.2.6",
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
+ "@turf/projection": "^6.5.0",
24
25
  "bezier-js": "^6.1.0",
25
26
  "maplibre-gl": ">=1.15.3",
26
27
  "minisearch": "^2.2.1",
@@ -60,9 +60,9 @@ class FlatMap
60
60
  this._baseUrl = mapBaseUrl;
61
61
  this._id = mapDescription.id;
62
62
  this._details = mapDescription.details;
63
- this._source = mapDescription.source;
64
63
  this._created = mapDescription.created;
65
- this._describes = mapDescription.describes;
64
+ this.__taxon = mapDescription.taxon;
65
+ this.__biologicalSex = mapDescription.biologicalSex;
66
66
  this._mapNumber = mapDescription.number;
67
67
  this._callback = mapDescription.callback;
68
68
  this._layers = mapDescription.layers;
@@ -75,7 +75,7 @@ class FlatMap
75
75
  this.__idToAnnotation = new Map();
76
76
  this.__datasetToFeatureIds = new Map();
77
77
  this.__modelToFeatureIds = new Map();
78
- this.__sourceToFeatureIds = new Map();
78
+ this.___mapIdToFeatureIds = new Map();
79
79
  for (const [featureId, annotation] of Object.entries(mapDescription.annotations)) {
80
80
  this.__addAnnotation(featureId, annotation);
81
81
  this.__searchIndex.indexMetadata(featureId, annotation);
@@ -366,10 +366,21 @@ class FlatMap
366
366
  *
367
367
  * @type string
368
368
  */
369
- get describes()
370
- //=============
369
+ get taxon()
370
+ //=========
371
+ {
372
+ return this.__taxon;
373
+ }
374
+
375
+ /**
376
+ * The biological sex identifier of the species described by the map.
377
+ *
378
+ * @type string
379
+ */
380
+ get biologicalSex()
381
+ //=================
371
382
  {
372
- return this._describes;
383
+ return this.__biologicalSex;
373
384
  }
374
385
 
375
386
  /**
@@ -455,7 +466,7 @@ class FlatMap
455
466
  this.__idToAnnotation.set(featureId, ann);
456
467
  this.__updateFeatureIdMap('dataset', this.__datasetToFeatureIds, ann);
457
468
  this.__updateFeatureIdMap('models', this.__modelToFeatureIds, ann);
458
- this.__updateFeatureIdMap('source', this.__sourceToFeatureIds, ann);
469
+ this.__updateFeatureIdMap('source', this.___mapIdToFeatureIds, ann);
459
470
  }
460
471
 
461
472
  modelFeatureIds(anatomicalId)
@@ -479,7 +490,7 @@ class FlatMap
479
490
  if (featureIds.length == 0) {
480
491
  // We couldn't find a feature by anatomical id, so check dataset and source
481
492
  featureIds.extend(this.__datasetToFeatureIds.get(anatomicalIds));
482
- featureIds.extend(this.__sourceToFeatureIds.get(anatomicalIds));
493
+ featureIds.extend(this.___mapIdToFeatureIds.get(anatomicalIds));
483
494
  }
484
495
  if (featureIds.length == 0 && this._userInteractions !== null) {
485
496
  // We still haven't found a feature, so check connectivity
@@ -621,8 +632,9 @@ class FlatMap
621
632
  // Return identifiers for reloading the map
622
633
 
623
634
  return {
624
- describes: this._describes,
625
- source: this._source
635
+ taxon: this.__taxon,
636
+ biologicalSex: this.__biologicalSex,
637
+ id: this._id
626
638
  };
627
639
  }
628
640
 
@@ -1055,35 +1067,13 @@ export class MapManager
1055
1067
  await this.ensureInitialised_();
1056
1068
  const allMaps = {};
1057
1069
  for (const map of this._mapList) {
1058
- allMaps[map.id] = map;
1070
+ const id = ('uuid' in map) ? map.uuid : map.id;
1071
+ allMaps[id] = map;
1059
1072
  }
1060
1073
  resolve(allMaps);
1061
1074
  });
1062
1075
  }
1063
1076
 
1064
- latestMaps()
1065
- //==========
1066
- {
1067
- return new Promise(async(resolve, reject) => {
1068
- await this.ensureInitialised_();
1069
- const latestMaps = {};
1070
- for (const map of this._mapList) {
1071
- const describes = ('taxon' in map) ? map.taxon
1072
- : ('describes' in map) ? map.describes
1073
- : map.id;
1074
- if (!(describes in latestMaps)) {
1075
- latestMaps[describes] = map;
1076
- } else if ('created' in map) {
1077
- if (!('created' in latestMaps[describes])
1078
- || (latestMaps[describes].created < map.created)) {
1079
- latestMaps[describes] = map;
1080
- }
1081
- }
1082
- }
1083
- resolve(latestMaps);
1084
- });
1085
- }
1086
-
1087
1077
  findMap_(identifier)
1088
1078
  //==================
1089
1079
  {
@@ -1093,16 +1083,21 @@ export class MapManager
1093
1083
  });
1094
1084
  }
1095
1085
 
1096
- latestMap_(mapDescribes)
1097
- //======================
1086
+ latestMap_(identifier)
1087
+ //====================
1098
1088
  {
1089
+ const mapDescribes = ('uuid' in identifier) ? identifier.uuid
1090
+ : ('taxon' in identifier) ? identifier.taxon
1091
+ : null;
1092
+ if (mapDescribes === null) {
1093
+ return null;
1094
+ }
1099
1095
  let latestMap = null;
1100
1096
  let lastCreatedTime = '';
1101
1097
  for (const map of this._mapList) {
1102
- if (mapDescribes === (('taxon' in map) ? map.taxon
1103
- : ('describes' in map) ? map.describes
1104
- : map.id)
1098
+ if ('uuid' in map && mapDescribes === map.uuid
1105
1099
  || mapDescribes === map.id
1100
+ || 'taxon' in map && mapDescribes === map.taxon
1106
1101
  || mapDescribes === map.source) {
1107
1102
  if ('created' in map) {
1108
1103
  if (lastCreatedTime < map.created) {
@@ -1110,48 +1105,44 @@ export class MapManager
1110
1105
  latestMap = map;
1111
1106
  }
1112
1107
  } else {
1113
- return map;
1108
+ latestMap = map;
1109
+ break;
1114
1110
  }
1115
1111
  }
1116
1112
  }
1113
+ if (latestMap !== null) {
1114
+ if ('biologicalSex' in identifier
1115
+ && 'biologicalSex' in latestMap
1116
+ && identifier.biologicalSex !== latestMap.biologicalSex) {
1117
+ latestMap = null;
1118
+ }
1119
+ }
1117
1120
  return latestMap;
1118
1121
  }
1119
1122
 
1120
1123
  lookupMap_(identifier)
1121
1124
  //====================
1122
1125
  {
1123
- let mapDescribes = null;
1124
- if (typeof identifier === 'object') {
1125
- if ('source' in identifier) {
1126
- const flatmap = this.latestMap_(identifier.source);
1127
- if (flatmap !== null) {
1128
- return flatmap;
1129
- }
1130
- }
1131
- if ('taxon' in identifier) {
1132
- mapDescribes = identifier.taxon;
1133
- } else if ('describes' in identifier) {
1134
- mapDescribes = identifier.describes;
1135
- }
1136
- } else {
1137
- mapDescribes = identifier;
1126
+ if (typeof identifier === 'object') {
1127
+ return this.latestMap_(identifier);
1138
1128
  }
1139
- return this.latestMap_(mapDescribes);
1129
+ return this.latestMap_({uuid: identifier});
1140
1130
  }
1141
1131
 
1142
1132
  /**
1143
1133
  * Load and display a FlatMap.
1144
1134
  *
1145
1135
  * @arg identifier {string|Object} A string or object identifying the map to load. If a string its
1146
- * value can be either the map's ``id``, assigned at generation time,
1147
- * or a taxon identifier of the species that the map represents. The
1148
- * latest version of a map is loaded unless it has been identified
1149
- * by ``source`` (see below).
1150
- * @arg identifier.taxon {string} The taxon identifier of the map. This is specified as metadata
1151
- * in the map's source file.)
1152
- * @arg identifier.source {string} The URL of the source file from which the map has
1153
- * been generated. If given then this exact map will be
1154
- * loaded.
1136
+ * value can be either the map's ``uuid``, assigned at generation time,
1137
+ * or taxon and biological sex identifiers of the species that the map
1138
+ * represents. The latest version of a map is loaded unless it has been
1139
+ * identified using a ``uuid`` (see below).
1140
+ * @arg identifier.taxon {string} The taxon identifier of the species represented by the map. This is
1141
+ * specified as metadata in the map's source file.)
1142
+ * @arg identifier.biologicalSex {string} The biological sex of the species represented by the map.
1143
+ * This is specified as metadatain the map's source file.)
1144
+ * @arg identifier.uuid {string} The unique uuid the flatmap. If given then this exact map will
1145
+ * be loaded, overriding ``taxon`` and ``biologicalSex``.
1155
1146
  * @arg container {string} The id of the HTML container in which to display the map.
1156
1147
  * @arg callback {function(string, Object)} A callback function, invoked when events occur with the map. The
1157
1148
  * first parameter gives the type of event, the second provides
@@ -1178,6 +1169,7 @@ export class MapManager
1178
1169
  * @arg options.navigationControl {boolean} Add navigation controls (zoom buttons) to the map.
1179
1170
  * @arg options.pathControl {boolean} Add buttons to control pathways including via a color-coded legend.
1180
1171
  * @arg options.searchable {boolean} Add a control to search for features on a map.
1172
+ * @arg options.showPosition {boolean} Show ``position`` of tooltip.
1181
1173
  * @example
1182
1174
  * const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
1183
1175
  *
@@ -1186,7 +1178,7 @@ export class MapManager
1186
1178
  * const humanMap3 = mapManager.loadMap({taxon: 'NCBITaxon:9606'}, 'div-3');
1187
1179
  *
1188
1180
  * const humanMap4 = mapManager.loadMap(
1189
- * {source: 'https://models.physiomeproject.org/workspace/585/rawfile/650adf9076538a4bf081609df14dabddd0eb37e7/Human_Body.pptx'},
1181
+ * {uuid: 'a563be90-9225-51c1-a84d-00ed2d03b7dc'},
1190
1182
  * 'div-4');
1191
1183
  */
1192
1184
  loadMap(identifier, container, callback, options={})
@@ -1201,11 +1193,12 @@ export class MapManager
1201
1193
 
1202
1194
  // Load the maps index file
1203
1195
 
1204
- const mapIndex = await this._mapServer.loadJSON(`flatmap/${map.id}/`);
1205
- if (map.id !== mapIndex.id) {
1206
- throw new Error(`Map '${map.id}' has wrong ID in index`);
1196
+ const mapId = ('uuid' in map) ? map.uuid : map.id;
1197
+ const mapIndex = await this._mapServer.loadJSON(`flatmap/${mapId}/`);
1198
+ const mapIndexId = ('uuid' in mapIndex) ? mapIndex.uuid : mapIndex.id;
1199
+ if (mapId !== mapIndexId) {
1200
+ throw new Error(`Map '${mapId}' has wrong ID in index`);
1207
1201
  }
1208
-
1209
1202
  const mapOptions = Object.assign({}, this._options, options);
1210
1203
 
1211
1204
  // If bounds are not specified in options then set them
@@ -1243,12 +1236,12 @@ export class MapManager
1243
1236
  }
1244
1237
  }
1245
1238
  } else {
1246
- mapLayers = await this._mapServer.loadJSON(`flatmap/${map.id}/layers`);
1239
+ mapLayers = await this._mapServer.loadJSON(`flatmap/${mapId}/layers`);
1247
1240
  }
1248
1241
 
1249
1242
  // Get the map's style file
1250
1243
 
1251
- const mapStyle = await this._mapServer.loadJSON(`flatmap/${map.id}/style`);
1244
+ const mapStyle = await this._mapServer.loadJSON(`flatmap/${mapId}/style`);
1252
1245
 
1253
1246
  // Make sure the style has glyphs defined
1254
1247
 
@@ -1258,15 +1251,15 @@ export class MapManager
1258
1251
 
1259
1252
  // Get the map's pathways
1260
1253
 
1261
- const pathways = await this._mapServer.loadJSON(`flatmap/${map.id}/pathways`);
1254
+ const pathways = await this._mapServer.loadJSON(`flatmap/${mapId}/pathways`);
1262
1255
 
1263
1256
  // Get the map's annotations
1264
1257
 
1265
- const annotations = await this._mapServer.loadJSON(`flatmap/${map.id}/annotations`);
1258
+ const annotations = await this._mapServer.loadJSON(`flatmap/${mapId}/annotations`);
1266
1259
 
1267
1260
  // Get additional marker details for the map
1268
1261
 
1269
- const mapMarkers = await this._mapServer.loadJSON(`flatmap/${map.id}/markers`);
1262
+ const mapMarkers = await this._mapServer.loadJSON(`flatmap/${mapId}/markers`);
1270
1263
 
1271
1264
  // Set zoom range if not specified as an option
1272
1265
 
@@ -1311,10 +1304,10 @@ export class MapManager
1311
1304
  this._mapNumber += 1;
1312
1305
  const flatmap = new FlatMap(container, this._mapServer.url(),
1313
1306
  {
1314
- id: map.id,
1307
+ id: mapId,
1315
1308
  details: mapIndex,
1316
- source: map.source,
1317
- describes: map.describes,
1309
+ taxon: map.taxon,
1310
+ biologicalSex: map.biologicalSex,
1318
1311
  style: mapStyle,
1319
1312
  options: mapOptions,
1320
1313
  layers: mapLayers,
@@ -27,6 +27,7 @@ import maplibre from 'maplibre-gl';
27
27
  import {default as turfArea} from '@turf/area';
28
28
  import {default as turfBBox} from '@turf/bbox';
29
29
  import * as turf from '@turf/helpers';
30
+ import * as turfProjection from '@turf/projection';
30
31
 
31
32
  import polylabel from 'polylabel';
32
33
 
@@ -824,6 +825,12 @@ export class UserInteractions
824
825
  maxWidth: 'none',
825
826
  className: 'flatmap-tooltip-popup'
826
827
  });
828
+ if (this._flatmap.options.showPosition) {
829
+ const pt = turf.point(lngLat.toArray());
830
+ const gps = turfProjection.toMercator(pt);
831
+ const coords = gps.geometry.coordinates;
832
+ html = `<span>${JSON.stringify(coords)}</span><br/>${html}`;
833
+ }
827
834
  this._tooltip
828
835
  .setLngLat(lngLat)
829
836
  .setHTML(html)
@@ -913,7 +920,7 @@ export class UserInteractions
913
920
  //=======================
914
921
  {
915
922
  const state = this._map.getFeatureState(feature);
916
- return !(('hidden' in state && state.hidden));
923
+ return true;
917
924
  }
918
925
 
919
926
  enablePaths_(enable, event)
package/src/main.js CHANGED
@@ -53,19 +53,22 @@ export async function standaloneViewer(map_endpoint=null, map_options={})
53
53
 
54
54
  let currentMap = null;
55
55
 
56
- function loadMap(id, taxon)
57
- //=========================
56
+ function loadMap(id, taxon, sex)
57
+ //==============================
58
58
  {
59
59
  if (currentMap !== null) {
60
60
  currentMap.close();
61
61
  }
62
-
63
62
  if (id !== null) {
64
63
  requestUrl.searchParams.set('id', id);
65
64
  requestUrl.searchParams.delete('taxon');
65
+ requestUrl.searchParams.delete('sex');
66
66
  } else if (taxon !== null) {
67
67
  id = taxon;
68
68
  requestUrl.searchParams.set('taxon', taxon);
69
+ if (sex !== null) {
70
+ requestUrl.searchParams.set('sex', sex);
71
+ }
69
72
  requestUrl.searchParams.delete('id');
70
73
  }
71
74
  window.history.pushState('data', document.title, requestUrl);
@@ -77,7 +80,8 @@ export async function standaloneViewer(map_endpoint=null, map_options={})
77
80
  minimap: false,
78
81
  navigationControl: 'top-right',
79
82
  searchable: true,
80
- featureInfo: true
83
+ featureInfo: true,
84
+ showPosition: false
81
85
  }, map_options);
82
86
 
83
87
  mapManager.loadMap(id, 'map-canvas', (...args) => console.log(...args), options)
@@ -97,9 +101,8 @@ export async function standaloneViewer(map_endpoint=null, map_options={})
97
101
 
98
102
  const viewMapId = requestUrl.searchParams.get('id');
99
103
  const viewMapTaxon = requestUrl.searchParams.get('taxon');
104
+ const viewMapSex = requestUrl.searchParams.get('sex');
100
105
 
101
- let mapId = null;
102
- let mapTaxon = null;
103
106
  const latestMaps = new Map();
104
107
  const maps = await mapManager.allMaps();
105
108
  for (const map of Object.values(maps)) {
@@ -123,18 +126,26 @@ export async function standaloneViewer(map_endpoint=null, map_options={})
123
126
  const sortedMaps = new Map([...latestMaps].sort((a, b) => (a[1].created < b[1].created) ? 1
124
127
  : (a[1].created > b[1].created) ? -1
125
128
  : 0));
129
+ let mapId = null;
130
+ let mapTaxon = null;
131
+ let mapSex = null;
126
132
  const options = [];
127
133
  for (const [name, map] of sortedMaps.entries()) {
128
134
  const text = [ name, map.created ];
129
135
  let selected = '';
130
- if (mapId === null && map.id === viewMapId) {
131
- mapId = map.id;
136
+ const id = ('uuid' in map) ? map.uuid : map.id;
137
+ if (mapId === null && id === viewMapId) {
138
+ mapId = id;
132
139
  selected = 'selected';
133
- } else if (mapId === null && mapTaxon === null && map.describes === viewMapTaxon) {
140
+ } else if (mapId === null
141
+ && mapTaxon === null
142
+ && map.taxon === viewMapTaxon
143
+ && !('biologicalSex' in map || map.biologicalSex === viewMapSex)) {
134
144
  mapTaxon = viewMapTaxon;
145
+ mapSex = viewMapSex;
135
146
  selected = 'selected';
136
147
  }
137
- options.push(`<option value="${map.id}" ${selected}>${text.join(' -- ')}</option>`);
148
+ options.push(`<option value="${id}" ${selected}>${text.join(' -- ')}</option>`);
138
149
  }
139
150
  options.splice(0, 0, '<option value="">Select flatmap...</option>');
140
151
 
@@ -146,16 +157,19 @@ export async function standaloneViewer(map_endpoint=null, map_options={})
146
157
  }
147
158
  }
148
159
 
149
- if (mapId == null) {
160
+ if (mapId === null) {
150
161
  mapId = viewMapId;
151
162
  }
152
- if (mapTaxon == null) {
163
+ if (mapTaxon === null) {
153
164
  mapTaxon = viewMapTaxon;
154
165
  }
166
+ if (mapSex === null) {
167
+ mapTaxon = viewMapSex;
168
+ }
155
169
  if (mapId === null && mapTaxon == null) {
156
170
  mapId = selector.options[1].value;
157
171
  selector.options[1].selected = true;
158
172
  }
159
173
 
160
- loadMap(mapId, mapTaxon);
174
+ loadMap(mapId, mapTaxon, mapSex);
161
175
  }
package/src/styling.js CHANGED
@@ -117,6 +117,7 @@ export class FeatureFillLayer extends VectorStyleLayer
117
117
  const paintStyle = {
118
118
  'fill-color': [
119
119
  'case',
120
+ ['boolean', ['feature-state', 'selected'], false], '#0F0',
120
121
  ['has', 'colour'], ['get', 'colour'],
121
122
  ['boolean', ['feature-state', 'active'], false], coloured ? '#D88' : '#CCC',
122
123
  ['any',
package/src/utils.js CHANGED
@@ -101,7 +101,7 @@ export function normaliseId(id)
101
101
  }
102
102
  const parts = id.split(':')
103
103
  const lastPart = parts[parts.length - 1]
104
- if ('0123456789'.indexOf(lastPart[0]) < 0) {
104
+ if (['http', 'https', 'urn'].indexOf(parts[0]) >= 0 || '0123456789'.indexOf(lastPart[0]) < 0) {
105
105
  return id;
106
106
  }
107
107
  parts[parts.length - 1] = lastPart.padStart(8, '0');