@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 +1 -1
- package/package.json +1 -1
- package/src/annotation.js +2 -2
- package/src/controls/info.js +1 -0
- package/src/flatmap-viewer.js +25 -1
- package/src/interactions.js +57 -29
- package/src/layers.js +6 -0
- package/src/main.js +1 -0
- package/src/mapserver.js +19 -12
- package/src/pathways.js +6 -7
- package/src/search.js +5 -4
- package/src/styling.js +10 -4
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.
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.3.4``
|
|
42
42
|
|
|
43
43
|
Documentation
|
|
44
44
|
-------------
|
package/package.json
CHANGED
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
|
},
|
package/src/controls/info.js
CHANGED
package/src/flatmap-viewer.js
CHANGED
|
@@ -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)
|
package/src/interactions.js
CHANGED
|
@@ -402,19 +402,36 @@ export class UserInteractions
|
|
|
402
402
|
selectFeature(featureId, dim=true)
|
|
403
403
|
//================================
|
|
404
404
|
{
|
|
405
|
-
|
|
406
|
-
if (
|
|
407
|
-
this._layerManager.
|
|
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.
|
|
415
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
947
|
+
header = (feature === null)
|
|
933
948
|
? JSON.stringify(coords)
|
|
934
|
-
: `${JSON.stringify(coords)} (${feature.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
|
-
|
|
938
|
-
.
|
|
939
|
-
|
|
940
|
-
|
|
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
package/src/main.js
CHANGED
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
|
-
|
|
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 = {};
|
|
69
|
-
this.__pathToPathModel = {};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
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.
|
|
59
|
+
this.indexText(featureId, text);
|
|
60
60
|
textSeen.push(text);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
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 = '#
|
|
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.
|
|
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',
|