@abi-software/flatmap-viewer 2.3.3-b.4 → 2.3.4

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.3.3-b.4``
41
+ * ``npm install @abi-software/flatmap-viewer@2.3.4``
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.3.3-b.4",
3
+ "version": "2.3.4",
4
4
  "description": "Flatmap viewer using Maplibre GL",
5
5
  "repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
6
6
  "main": "src/main.js",
package/src/annotation.js CHANGED
@@ -508,7 +508,7 @@ export class Annotator
508
508
  border: '2px solid #080',
509
509
  borderRadius: '.5rem',
510
510
  panelSize: 'auto auto',
511
- position: 'left-top',
511
+ position: 'left-top 50 70',
512
512
  content: panelContent,
513
513
  data: features[0].properties,
514
514
  closeOnEscape: true,
@@ -588,7 +588,7 @@ export class Annotator
588
588
  border: '2px solid #080',
589
589
  borderRadius: '.5rem',
590
590
  panelSize: '725px auto',
591
- position: 'left-top',
591
+ position: 'left-top 50 70',
592
592
  data: {
593
593
  flatmap: this.__flatmap
594
594
  },
@@ -27,6 +27,7 @@ export const displayedProperties = [
27
27
  'class',
28
28
  'fc-class',
29
29
  'fc-kind',
30
+ 'name',
30
31
  ...indexedProperties
31
32
  ];
32
33
 
@@ -33,7 +33,7 @@ import '../static/css/flatmap-viewer.css';
33
33
 
34
34
  //==============================================================================
35
35
 
36
- import {MapServer} from './mapserver.js';
36
+ import {MapServer, loadJSON} from './mapserver.js';
37
37
  import {SearchIndex} from './search.js';
38
38
  import {UserInteractions} from './interactions.js';
39
39
 
@@ -77,11 +77,15 @@ class FlatMap
77
77
  this.__datasetToFeatureIds = new Map();
78
78
  this.__modelToFeatureIds = new Map();
79
79
  this.__mapSourceToFeatureIds = new Map();
80
+ this.__annIdToFeatureId = new Map();
80
81
 
81
82
  for (const [featureId, annotation] of Object.entries(mapDescription.annotations)) {
82
83
  this.__addAnnotation(featureId, annotation);
83
84
  this.__searchIndex.indexMetadata(featureId, annotation);
84
85
  }
86
+ if (this.options.annotator) {
87
+ this.__addAnnotatedComments();
88
+ }
85
89
 
86
90
  // Set base of source URLs in map's style
87
91
 
@@ -207,6 +211,25 @@ class FlatMap
207
211
  });
208
212
  }
209
213
 
214
+ async __addAnnotatedComments()
215
+ //============================
216
+ {
217
+ const url = this.makeServerUrl('', 'annotator/')
218
+ const annotatedFeatures = await loadJSON(url);
219
+ for (const annotatedId of annotatedFeatures) {
220
+ const featureId = this.__annIdToFeatureId.get(annotatedId);
221
+ if (featureId) {
222
+ const url = this.makeServerUrl(annotatedId, 'annotator/')
223
+ const annotations = await loadJSON(url);
224
+ for (const annotation of annotations) { // In order of most recent to oldest
225
+ if ('rdfs:comment' in annotation) {
226
+ this.__searchIndex.indexText(featureId, annotation['rdfs:comment']);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+
210
233
  async setupUserInteractions_()
211
234
  //============================
212
235
  {
@@ -498,6 +521,7 @@ class FlatMap
498
521
  this.__updateFeatureIdMap('dataset', this.__datasetToFeatureIds, ann);
499
522
  this.__updateFeatureIdMap('models', this.__modelToFeatureIds, ann);
500
523
  this.__updateFeatureIdMap('source', this.__mapSourceToFeatureIds, ann);
524
+ this.__annIdToFeatureId.set(ann.id, featureId);
501
525
  }
502
526
 
503
527
  modelFeatureIds(anatomicalId)
@@ -402,19 +402,36 @@ export class UserInteractions
402
402
  selectFeature(featureId, dim=true)
403
403
  //================================
404
404
  {
405
- featureId = +featureId; // Ensure numeric
406
- if (this._selectedFeatureIds.size === 0) {
407
- this._layerManager.setPaint({...this.__colourOptions, dimmed: dim});
405
+ const ann = this._flatmap.annotation(featureId);
406
+ if ('sckan' in ann) {
407
+ const sckanState = this._layerManager.sckanState;
408
+ if (sckanState === 'none'
409
+ || sckanState === 'valid' && !ann.sckan
410
+ || sckanState === 'invalid' && ann.sckan) {
411
+ return false;
412
+ }
408
413
  }
414
+ featureId = +featureId; // Ensure numeric
415
+ let result = false;
416
+ const noSelection = (this._selectedFeatureIds.size === 0);
409
417
  if (this._selectedFeatureIds.has(featureId)) {
410
418
  this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
419
+ result = true;
411
420
  } else {
412
421
  const feature = this.mapFeature(featureId);
413
422
  if (feature !== undefined) {
414
- this._map.setFeatureState(feature, { 'selected': true });
415
- this._selectedFeatureIds.set(featureId, 1);
423
+ const state = this._map.getFeatureState(feature);
424
+ if (state !== undefined && (!('hidden' in state) || !state.hidden)) {
425
+ this._map.setFeatureState(feature, { 'selected': true });
426
+ this._selectedFeatureIds.set(featureId, 1);
427
+ result = true;
428
+ }
416
429
  }
417
430
  }
431
+ if (result && noSelection) {
432
+ this._layerManager.setPaint({...this.__colourOptions, dimmed: dim});
433
+ }
434
+ return result;
418
435
  }
419
436
 
420
437
  unselectFeature(featureId)
@@ -566,10 +583,11 @@ export class UserInteractions
566
583
  for (const featureId of featureIds) {
567
584
  const annotation = this._flatmap.annotation(featureId);
568
585
  if (annotation) {
569
- this.selectFeature(featureId);
570
- if ('type' in annotation && annotation.type.startsWith('line')) {
571
- for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
572
- this.selectFeature(pathFeatureId);
586
+ if (this.selectFeature(featureId)) {
587
+ if ('type' in annotation && annotation.type.startsWith('line')) {
588
+ for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
589
+ this.selectFeature(pathFeatureId);
590
+ }
573
591
  }
574
592
  }
575
593
  }
@@ -609,13 +627,15 @@ export class UserInteractions
609
627
  for (const featureId of featureIds) {
610
628
  const annotation = this._flatmap.annotation(featureId);
611
629
  if (annotation) {
612
- this.selectFeature(featureId);
613
- bbox = expandBounds(bbox, annotation.bounds);
614
- if ('type' in annotation && annotation.type.startsWith('line')) {
615
- for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
616
- this.selectFeature(pathFeatureId);
617
- const pathAnnotation = this._flatmap.annotation(pathFeatureId)
618
- bbox = expandBounds(bbox, pathAnnotation.bounds);
630
+ if (this.selectFeature(featureId)) {
631
+ bbox = expandBounds(bbox, annotation.bounds);
632
+ if ('type' in annotation && annotation.type.startsWith('line')) {
633
+ for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
634
+ if (this.selectFeature(pathFeatureId)) {
635
+ const pathAnnotation = this._flatmap.annotation(pathFeatureId)
636
+ bbox = expandBounds(bbox, pathAnnotation.bounds);
637
+ }
638
+ }
619
639
  }
620
640
  }
621
641
  }
@@ -918,26 +938,34 @@ export class UserInteractions
918
938
  //=======================================
919
939
  {
920
940
  // Show a tooltip
921
- if (html !== '') {
922
- this._tooltip = new maplibre.Popup({
923
- closeButton: false,
924
- closeOnClick: false,
925
- maxWidth: 'none',
926
- className: 'flatmap-tooltip-popup'
927
- });
941
+ if (html !== '' || this._flatmap.options.showId && feature !== null) {
942
+ let header = '';
928
943
  if (this._flatmap.options.showPosition) {
929
944
  const pt = turf.point(lngLat.toArray());
930
945
  const gps = turfProjection.toMercator(pt);
931
946
  const coords = gps.geometry.coordinates;
932
- const header = (feature === null)
947
+ header = (feature === null)
933
948
  ? JSON.stringify(coords)
934
- : `${JSON.stringify(coords)} (${feature.id} ${feature.properties['id']})`;
949
+ : `${JSON.stringify(coords)} (${feature.id})`;
950
+ }
951
+ if (this._flatmap.options.showId && feature !== null && 'id' in feature.properties) {
952
+ header = `${header} ${feature.properties.id}`;
953
+ }
954
+ if (header !== '') {
935
955
  html = `<span>${header}</span><br/>${html}`;
936
956
  }
937
- this._tooltip
938
- .setLngLat(lngLat)
939
- .setHTML(html)
940
- .addTo(this._map);
957
+ if (html !== '') {
958
+ this._tooltip = new maplibre.Popup({
959
+ closeButton: false,
960
+ closeOnClick: false,
961
+ maxWidth: 'none',
962
+ className: 'flatmap-tooltip-popup'
963
+ });
964
+ this._tooltip
965
+ .setLngLat(lngLat)
966
+ .setHTML(html)
967
+ .addTo(this._map);
968
+ }
941
969
  }
942
970
  }
943
971
 
package/src/layers.js CHANGED
@@ -298,6 +298,12 @@ export class LayerManager
298
298
  return layers;
299
299
  }
300
300
 
301
+ get sckanState()
302
+ //==============
303
+ {
304
+ return this.__layerOptions.sckan;
305
+ }
306
+
301
307
  activate(layerId, enable=true)
302
308
  //============================
303
309
  {
package/src/main.js CHANGED
@@ -59,6 +59,7 @@ export async function standaloneViewer(map_endpoint=null, options={})
59
59
  background: defaultBackground,
60
60
  debug: false,
61
61
  minimap: false,
62
+ showId: true,
62
63
  showPosition: false,
63
64
  standalone: true,
64
65
  annotator: true
package/src/mapserver.js CHANGED
@@ -22,6 +22,24 @@ limitations under the License.
22
22
 
23
23
  //==============================================================================
24
24
 
25
+ export async function loadJSON(url)
26
+ //=================================
27
+ {
28
+ const response = await fetch(url, {
29
+ method: 'GET',
30
+ headers: {
31
+ "Accept": "application/json; charset=utf-8",
32
+ "Cache-Control": "no-store"
33
+ }
34
+ });
35
+ if (!response.ok) {
36
+ throw new Error(`Cannot access ${url}`);
37
+ }
38
+ return response.json();
39
+ }
40
+
41
+ //==============================================================================
42
+
25
43
  export class MapServer
26
44
  {
27
45
  constructor(url)
@@ -39,18 +57,7 @@ export class MapServer
39
57
  async loadJSON(relativePath)
40
58
  //==========================
41
59
  {
42
- const url = this.url(relativePath);
43
- const response = await fetch(url, {
44
- method: 'GET',
45
- headers: {
46
- "Accept": "application/json; charset=utf-8",
47
- "Cache-Control": "no-store"
48
- }
49
- });
50
- if (!response.ok) {
51
- throw new Error(`Cannot access ${url}`);
52
- }
53
- return response.json();
60
+ return loadJSON(this.url(relativePath));
54
61
  }
55
62
  }
56
63
 
package/src/pathways.js CHANGED
@@ -42,7 +42,7 @@ const PATH_TYPES = [
42
42
  { type: "arterial", label: "Arterial blood vessel", colour: "#F00", enabled: false},
43
43
  { type: "venous", label: "Venous blood vessel", colour: "#2F6EBA", enabled: false},
44
44
  { type: "centreline", label: "Nerve centrelines", colour: "#CCC", enabled: false},
45
- { type: "error", label: "Paths with errors or warnings", colour: "#FF0"}
45
+ { type: "error", label: "Paths with errors or warnings", colour: "#FF0", enabled: false}
46
46
  ];
47
47
 
48
48
  export const PATH_STYLE_RULES =
@@ -65,12 +65,11 @@ export class PathManager
65
65
  }
66
66
  }
67
67
  }
68
- this.__pathModelPaths = {}; // pathModelId: [pathIds]
69
- this.__pathToPathModel = {};
70
-
71
- this.__paths = {};
72
- const pathLines = {}; // pathId: [lineIds]
73
- const pathNerves = {}; // pathId: [nerveIds]
68
+ this.__pathModelPaths = {}; // pathModelId: [pathIds]
69
+ this.__pathToPathModel = {}; // pathId: pathModelId
70
+ this.__paths = {}; // pathId: path
71
+ const pathLines = {}; // pathId: [lineIds]
72
+ const pathNerves = {}; // pathId: [nerveIds]
74
73
  if ('paths' in flatmap.pathways) {
75
74
  for (const [pathId, path] of Object.entries(flatmap.pathways.paths)) {
76
75
  pathLines[pathId] = path.lines;
package/src/search.js CHANGED
@@ -56,18 +56,19 @@ export class SearchIndex
56
56
  if (prop in metadata) {
57
57
  const text = metadata[prop];
58
58
  if (!textSeen.includes(text)) {
59
- this.addTerm_(featureId, text);
59
+ this.indexText(featureId, text);
60
60
  textSeen.push(text);
61
61
  }
62
62
  }
63
63
  }
64
64
  }
65
65
 
66
- addTerm_(featureId, text)
67
- //=======================
66
+ indexText(featureId, text)
67
+ //========================
68
68
  {
69
69
  text = text.replace(new RegExp('<br/>', 'g'), ' ')
70
- .replace('\n', ' ');
70
+ .replace(new RegExp('\n', 'g'), ' ')
71
+ ;
71
72
  if (text) {
72
73
  this._searchEngine.add({
73
74
  id: this._featureIds.length,
package/src/styling.js CHANGED
@@ -31,7 +31,7 @@ import {PATH_STYLE_RULES} from './pathways.js';
31
31
  //==============================================================================
32
32
 
33
33
  const COLOUR_ACTIVE = 'blue';
34
- const COLOUR_ANNOTATED = '#0F0';
34
+ const COLOUR_ANNOTATED = '#C8F';
35
35
  const COLOUR_SELECTED = '#0F0';
36
36
  const COLOUR_HIDDEN = '#D8D8D8';
37
37
 
@@ -418,15 +418,18 @@ export class AnnotatedPathLayer extends VectorStyleLayer
418
418
 
419
419
  paintStyle(options={}, changes=false)
420
420
  {
421
+ const dimmed = 'dimmed' in options && options.dimmed;
421
422
  const exclude = 'excludeAnnotated' in options && options.excludeAnnotated;
422
423
  const paintStyle = {
423
424
  'line-color': COLOUR_ANNOTATED,
424
425
  'line-dasharray': [5, 0.5, 3, 0.5],
425
426
  'line-opacity': [
426
427
  'case',
428
+ ['boolean', ['feature-state', 'active'], false], 0.8,
429
+ ['boolean', ['feature-state', 'selected'], false], 0.8,
427
430
  ['boolean', ['feature-state', 'hidden'], false], 0.05,
428
431
  ['boolean', ['feature-state', 'annotated'], false],
429
- (exclude ? 0.05 : 0.8),
432
+ ((exclude || dimmed) ? 0.05 : 0.8),
430
433
  0.6
431
434
  ],
432
435
  'line-width': [
@@ -435,7 +438,11 @@ export class AnnotatedPathLayer extends VectorStyleLayer
435
438
  ['case',
436
439
  ['boolean', ['feature-state', 'hidden'], false], 0.0,
437
440
  ['boolean', ['feature-state', 'annotated'], false],
438
- exclude ? 0.0 : (['*', 1.2, ['case', ['has', 'stroke-width'], ['get', 'stroke-width'], 1.0]]),
441
+ exclude ? 0.0 : (['*', 1.1, ['case',
442
+ ['has', 'stroke-width'], ['get', 'stroke-width'],
443
+ ['boolean', ['feature-state', 'active'], false], 1.1,
444
+ ['boolean', ['feature-state', 'active'], false], 1.1,
445
+ 1.0]]),
439
446
  0.0
440
447
  ],
441
448
  STROKE_INTERPOLATION
@@ -446,7 +453,6 @@ export class AnnotatedPathLayer extends VectorStyleLayer
446
453
 
447
454
  style(options)
448
455
  {
449
- const dimmed = 'dimmed' in options && options.dimmed;
450
456
  return {
451
457
  ...super.style(),
452
458
  'type': 'line',