@abi-software/flatmap-viewer 2.2.5 → 2.2.7

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.5``
41
+ * ``npm install @abi-software/flatmap-viewer@2.2.7``
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.5",
3
+ "version": "2.2.7",
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
371
  {
372
- return this._describes;
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
+ //=================
382
+ {
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,24 +1083,33 @@ 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
1106
- || mapDescribes === map.source) {
1100
+ || 'taxon' in map && mapDescribes === map.taxon
1101
+ || mapDescribes === map.source)
1102
+ && (!('biologicalSex' in identifier)
1103
+ || ('biologicalSex' in map
1104
+ && identifier.biologicalSex === map.biologicalSex))) {
1107
1105
  if ('created' in map) {
1108
1106
  if (lastCreatedTime < map.created) {
1109
1107
  lastCreatedTime = map.created;
1110
1108
  latestMap = map;
1111
1109
  }
1112
1110
  } else {
1113
- return map;
1111
+ latestMap = map;
1112
+ break;
1114
1113
  }
1115
1114
  }
1116
1115
  }
@@ -1120,38 +1119,26 @@ export class MapManager
1120
1119
  lookupMap_(identifier)
1121
1120
  //====================
1122
1121
  {
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;
1122
+ if (typeof identifier === 'object') {
1123
+ return this.latestMap_(identifier);
1138
1124
  }
1139
- return this.latestMap_(mapDescribes);
1125
+ return this.latestMap_({uuid: identifier});
1140
1126
  }
1141
1127
 
1142
1128
  /**
1143
1129
  * Load and display a FlatMap.
1144
1130
  *
1145
1131
  * @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.
1132
+ * value can be either the map's ``uuid``, assigned at generation time,
1133
+ * or taxon and biological sex identifiers of the species that the map
1134
+ * represents. The latest version of a map is loaded unless it has been
1135
+ * identified using a ``uuid`` (see below).
1136
+ * @arg identifier.taxon {string} The taxon identifier of the species represented by the map. This is
1137
+ * specified as metadata in the map's source file.)
1138
+ * @arg identifier.biologicalSex {string} The biological sex of the species represented by the map.
1139
+ * This is specified as metadata in the map's source file.)
1140
+ * @arg identifier.uuid {string} The unique uuid the flatmap. If given then this exact map will
1141
+ * be loaded, overriding ``taxon`` and ``biologicalSex``.
1155
1142
  * @arg container {string} The id of the HTML container in which to display the map.
1156
1143
  * @arg callback {function(string, Object)} A callback function, invoked when events occur with the map. The
1157
1144
  * first parameter gives the type of event, the second provides
@@ -1178,6 +1165,7 @@ export class MapManager
1178
1165
  * @arg options.navigationControl {boolean} Add navigation controls (zoom buttons) to the map.
1179
1166
  * @arg options.pathControl {boolean} Add buttons to control pathways including via a color-coded legend.
1180
1167
  * @arg options.searchable {boolean} Add a control to search for features on a map.
1168
+ * @arg options.showPosition {boolean} Show ``position`` of tooltip.
1181
1169
  * @example
1182
1170
  * const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
1183
1171
  *
@@ -1186,7 +1174,7 @@ export class MapManager
1186
1174
  * const humanMap3 = mapManager.loadMap({taxon: 'NCBITaxon:9606'}, 'div-3');
1187
1175
  *
1188
1176
  * const humanMap4 = mapManager.loadMap(
1189
- * {source: 'https://models.physiomeproject.org/workspace/585/rawfile/650adf9076538a4bf081609df14dabddd0eb37e7/Human_Body.pptx'},
1177
+ * {uuid: 'a563be90-9225-51c1-a84d-00ed2d03b7dc'},
1190
1178
  * 'div-4');
1191
1179
  */
1192
1180
  loadMap(identifier, container, callback, options={})
@@ -1201,11 +1189,12 @@ export class MapManager
1201
1189
 
1202
1190
  // Load the maps index file
1203
1191
 
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`);
1192
+ const mapId = ('uuid' in map) ? map.uuid : map.id;
1193
+ const mapIndex = await this._mapServer.loadJSON(`flatmap/${mapId}/`);
1194
+ const mapIndexId = ('uuid' in mapIndex) ? mapIndex.uuid : mapIndex.id;
1195
+ if (mapId !== mapIndexId) {
1196
+ throw new Error(`Map '${mapId}' has wrong ID in index`);
1207
1197
  }
1208
-
1209
1198
  const mapOptions = Object.assign({}, this._options, options);
1210
1199
 
1211
1200
  // If bounds are not specified in options then set them
@@ -1243,12 +1232,12 @@ export class MapManager
1243
1232
  }
1244
1233
  }
1245
1234
  } else {
1246
- mapLayers = await this._mapServer.loadJSON(`flatmap/${map.id}/layers`);
1235
+ mapLayers = await this._mapServer.loadJSON(`flatmap/${mapId}/layers`);
1247
1236
  }
1248
1237
 
1249
1238
  // Get the map's style file
1250
1239
 
1251
- const mapStyle = await this._mapServer.loadJSON(`flatmap/${map.id}/style`);
1240
+ const mapStyle = await this._mapServer.loadJSON(`flatmap/${mapId}/style`);
1252
1241
 
1253
1242
  // Make sure the style has glyphs defined
1254
1243
 
@@ -1258,15 +1247,15 @@ export class MapManager
1258
1247
 
1259
1248
  // Get the map's pathways
1260
1249
 
1261
- const pathways = await this._mapServer.loadJSON(`flatmap/${map.id}/pathways`);
1250
+ const pathways = await this._mapServer.loadJSON(`flatmap/${mapId}/pathways`);
1262
1251
 
1263
1252
  // Get the map's annotations
1264
1253
 
1265
- const annotations = await this._mapServer.loadJSON(`flatmap/${map.id}/annotations`);
1254
+ const annotations = await this._mapServer.loadJSON(`flatmap/${mapId}/annotations`);
1266
1255
 
1267
1256
  // Get additional marker details for the map
1268
1257
 
1269
- const mapMarkers = await this._mapServer.loadJSON(`flatmap/${map.id}/markers`);
1258
+ const mapMarkers = await this._mapServer.loadJSON(`flatmap/${mapId}/markers`);
1270
1259
 
1271
1260
  // Set zoom range if not specified as an option
1272
1261
 
@@ -1311,10 +1300,10 @@ export class MapManager
1311
1300
  this._mapNumber += 1;
1312
1301
  const flatmap = new FlatMap(container, this._mapServer.url(),
1313
1302
  {
1314
- id: map.id,
1303
+ id: mapId,
1315
1304
  details: mapIndex,
1316
- source: map.source,
1317
- describes: map.describes,
1305
+ taxon: map.taxon,
1306
+ biologicalSex: map.biologicalSex,
1318
1307
  style: mapStyle,
1319
1308
  options: mapOptions,
1320
1309
  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)
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/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');