@abi-software/flatmap-viewer 2.6.2 → 2.7.0-a.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 +1 -1
- package/lib/index.ts +9 -0
- package/package.json +11 -21
- package/src/controls/annotation.js +1 -1
- package/src/flatmap-viewer.js +28 -10
- package/src/interactions.js +30 -11
- package/src/layers/cluster.js +191 -0
- package/src/layers/flightpaths.js +0 -2
- package/src/layers/index.js +22 -1
- package/src/main.js +41 -0
- package/src/pathways.js +1 -1
- package/src/types.ts +26 -0
- package/src/utils.js +1 -0
package/README.rst
CHANGED
package/lib/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//==============================================================================
|
|
2
|
+
|
|
3
|
+
import {FlatMap, MapManager} from '../src/flatmap-viewer'
|
|
4
|
+
|
|
5
|
+
//==============================================================================
|
|
6
|
+
|
|
7
|
+
export {FlatMap, MapManager}
|
|
8
|
+
|
|
9
|
+
//==============================================================================
|
package/package.json
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abi-software/flatmap-viewer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0-a.1",
|
|
4
4
|
"description": "Flatmap viewer using Maplibre GL",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/AnatomicMaps/flatmap-viewer.git"
|
|
8
8
|
},
|
|
9
|
+
"type": "module",
|
|
9
10
|
"main": "src/main.js",
|
|
10
11
|
"files": [
|
|
12
|
+
"lib",
|
|
11
13
|
"src",
|
|
12
14
|
"static"
|
|
13
15
|
],
|
|
14
16
|
"scripts": {
|
|
15
17
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
16
|
-
"
|
|
17
|
-
"build": "
|
|
18
|
+
"dev": "vite serve app --port 3000",
|
|
19
|
+
"build": "tsc --p ./tsconfig-build.json && vite build",
|
|
20
|
+
"preview": "vite preview",
|
|
18
21
|
"docs": "cd docs; poetry run make html"
|
|
19
22
|
},
|
|
20
23
|
"author": "David Brooks",
|
|
21
24
|
"license": "MIT",
|
|
22
25
|
"dependencies": {
|
|
23
|
-
"@babel/runtime": "^7.10.4",
|
|
24
26
|
"@deck.gl/core": "^8.9.35",
|
|
25
27
|
"@deck.gl/layers": "^8.9.35",
|
|
26
28
|
"@deck.gl/mapbox": "^8.9.35",
|
|
@@ -33,28 +35,16 @@
|
|
|
33
35
|
"colord": "^2.9.3",
|
|
34
36
|
"core-js-pure": "^3.36.1",
|
|
35
37
|
"html-es6cape": "^2.0.2",
|
|
36
|
-
"maplibre-gl": ">=
|
|
38
|
+
"maplibre-gl": ">=4.1.0",
|
|
37
39
|
"mathjax-full": "^3.2.2",
|
|
38
40
|
"minisearch": "^2.2.1",
|
|
39
41
|
"polylabel": "^1.1.0"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@babel/plugin-transform-runtime": "^7.5.5",
|
|
44
|
-
"@babel/preset-env": "^7.10.4",
|
|
45
|
-
"babel-loader": "^8.1.0",
|
|
46
|
-
"browser-sync": "^2.26.7",
|
|
47
|
-
"bs-fullscreen-message": "^1.1.0",
|
|
48
|
-
"clean-webpack-plugin": "^3.0.0",
|
|
49
|
-
"css-loader": "^6.7.3",
|
|
44
|
+
"@types/node": "^20.12.7",
|
|
50
45
|
"eslint": "^8.7.0",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"style-loader": "^3.3.2",
|
|
55
|
-
"webpack": "^5.16.0",
|
|
56
|
-
"webpack-cli": "^4.4.0",
|
|
57
|
-
"webpack-dev-middleware": "^4.1.0",
|
|
58
|
-
"webpack-node-externals": "^1.7.2"
|
|
46
|
+
"typescript": "^5.2.2",
|
|
47
|
+
"vite": "^5.1.4",
|
|
48
|
+
"vite-plugin-dts": "^3.8.1"
|
|
59
49
|
}
|
|
60
50
|
}
|
package/src/flatmap-viewer.js
CHANGED
|
@@ -33,12 +33,14 @@ import '../static/css/flatmap-viewer.css';
|
|
|
33
33
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
|
-
import {MapServer
|
|
36
|
+
import {MapServer} from './mapserver.js';
|
|
37
37
|
import {SearchIndex} from './search.js';
|
|
38
38
|
import {UserInteractions} from './interactions.js';
|
|
39
39
|
|
|
40
40
|
import {APINATOMY_PATH_PREFIX} from './pathways';
|
|
41
41
|
|
|
42
|
+
import {loadClusterIcons} from './layers/cluster'
|
|
43
|
+
|
|
42
44
|
import * as images from './images.js';
|
|
43
45
|
import * as utils from './utils.js';
|
|
44
46
|
|
|
@@ -61,7 +63,7 @@ export const UNCLASSIFIED_TAXON_ID = 'NCBITaxon:2787823'; // unclassified entr
|
|
|
61
63
|
* Maps are not created directly but instead are created and loaded by
|
|
62
64
|
* :meth:`LoadMap` of :class:`MapManager`.
|
|
63
65
|
*/
|
|
64
|
-
class FlatMap
|
|
66
|
+
export class FlatMap
|
|
65
67
|
{
|
|
66
68
|
constructor(container, mapBaseUrl, mapDescription, resolve)
|
|
67
69
|
{
|
|
@@ -204,6 +206,9 @@ class FlatMap
|
|
|
204
206
|
await this.addImage(image.id, image.url, '', image.options);
|
|
205
207
|
}
|
|
206
208
|
|
|
209
|
+
// Load icons used for clustered markers
|
|
210
|
+
await loadClusterIcons(this._map)
|
|
211
|
+
|
|
207
212
|
// Layers have now loaded so finish setting up
|
|
208
213
|
this._userInteractions = new UserInteractions(this);
|
|
209
214
|
}
|
|
@@ -966,6 +971,21 @@ class FlatMap
|
|
|
966
971
|
return -1;
|
|
967
972
|
}
|
|
968
973
|
|
|
974
|
+
addMarkers(anatomicalIds, options={})
|
|
975
|
+
//====================================
|
|
976
|
+
{
|
|
977
|
+
const markerIds = []
|
|
978
|
+
for (const anatomicalId of anatomicalIds) {
|
|
979
|
+
if (this._userInteractions !== null) {
|
|
980
|
+
markerIds.push(this._userInteractions.addMarker(anatomicalId, options))
|
|
981
|
+
} else {
|
|
982
|
+
markerIds.push(-1)
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return markerIds
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
|
|
969
989
|
/**
|
|
970
990
|
* Remove a marker from the map.
|
|
971
991
|
*
|
|
@@ -975,7 +995,7 @@ class FlatMap
|
|
|
975
995
|
removeMarker(markerId)
|
|
976
996
|
//====================
|
|
977
997
|
{
|
|
978
|
-
if (this._userInteractions !== null) {
|
|
998
|
+
if (markerId > -1 && this._userInteractions !== null) {
|
|
979
999
|
this._userInteractions.removeMarker(markerId);
|
|
980
1000
|
}
|
|
981
1001
|
}
|
|
@@ -1046,19 +1066,17 @@ class FlatMap
|
|
|
1046
1066
|
'alert',
|
|
1047
1067
|
'biological-sex'
|
|
1048
1068
|
];
|
|
1049
|
-
const jsonProperties = [
|
|
1050
|
-
'hyperlinks'
|
|
1051
|
-
];
|
|
1052
1069
|
for (const property of exportedProperties) {
|
|
1053
1070
|
if (property in properties) {
|
|
1054
1071
|
const value = properties[property];
|
|
1055
1072
|
if (value !== undefined) {
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1073
|
+
if ((Array.isArray(value) && value.length)
|
|
1074
|
+
|| (value.constructor === Object && Object.keys(value).length)) {
|
|
1075
|
+
data[property] = value
|
|
1058
1076
|
} else if (property === 'featureId') {
|
|
1059
|
-
data[property] = +
|
|
1077
|
+
data[property] = +value; // Ensure numeric
|
|
1060
1078
|
} else {
|
|
1061
|
-
data[property] =
|
|
1079
|
+
data[property] = value;
|
|
1062
1080
|
}
|
|
1063
1081
|
}
|
|
1064
1082
|
}
|
package/src/interactions.js
CHANGED
|
@@ -1373,11 +1373,13 @@ export class UserInteractions
|
|
|
1373
1373
|
}
|
|
1374
1374
|
const markerPosition = this.__markerPosition(featureId, annotation);
|
|
1375
1375
|
if (options.cluster && this._layerManager) {
|
|
1376
|
-
this._layerManager.
|
|
1376
|
+
this._layerManager.addMarker(markerId, markerPosition, annotation)
|
|
1377
1377
|
} else {
|
|
1378
1378
|
const marker = new maplibregl.Marker(markerOptions)
|
|
1379
1379
|
.setLngLat(markerPosition)
|
|
1380
1380
|
.addTo(this._map);
|
|
1381
|
+
|
|
1382
|
+
|
|
1381
1383
|
markerElement.addEventListener('mouseenter',
|
|
1382
1384
|
this.markerMouseEvent_.bind(this, marker, anatomicalId));
|
|
1383
1385
|
markerElement.addEventListener('mousemove',
|
|
@@ -1441,28 +1443,46 @@ export class UserInteractions
|
|
|
1441
1443
|
return anatomicalIds;
|
|
1442
1444
|
}
|
|
1443
1445
|
|
|
1446
|
+
// Separate out MapLibre specific code and result of mouse event (tooltip,
|
|
1447
|
+
// client message, etc) so clustering code can also use this to process
|
|
1448
|
+
// events.
|
|
1449
|
+
|
|
1444
1450
|
markerMouseEvent_(marker, anatomicalId, event)
|
|
1445
1451
|
//============================================
|
|
1446
1452
|
{
|
|
1447
1453
|
// No tooltip when context menu is open
|
|
1448
1454
|
if (this._modal
|
|
1449
1455
|
|| (this.__activeMarker !== null && event.type === 'mouseleave')) {
|
|
1450
|
-
return
|
|
1456
|
+
return
|
|
1451
1457
|
}
|
|
1452
1458
|
|
|
1453
1459
|
if (['mouseenter', 'mouseleave', 'click'].includes(event.type)) {
|
|
1454
|
-
this.__activeMarker = marker
|
|
1460
|
+
this.__activeMarker = marker
|
|
1455
1461
|
|
|
1456
|
-
// Remove any
|
|
1457
|
-
|
|
1458
|
-
marker.setPopup(null);
|
|
1462
|
+
// Remove any tooltip
|
|
1463
|
+
marker.setPopup(null)
|
|
1459
1464
|
|
|
1460
1465
|
// Reset cursor
|
|
1461
1466
|
marker.getElement().style.cursor = 'default';
|
|
1462
1467
|
|
|
1468
|
+
|
|
1469
|
+
const markerId = this.__markerIdByMarker.get(marker)
|
|
1470
|
+
const annotation = this.__annotationByMarkerId.get(markerId)
|
|
1471
|
+
|
|
1472
|
+
this.markerEvent_(event, markerId, marker.getLngLat(),
|
|
1473
|
+
anatomicalId, annotation)
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
markerEvent_(event, markerId, markerPosition, anatomicalId, annotation)
|
|
1478
|
+
//=====================================================================
|
|
1479
|
+
{
|
|
1480
|
+
if (['mouseenter', 'mouseleave', 'click'].includes(event.type)) {
|
|
1481
|
+
|
|
1482
|
+
// Remove any existing tooltips
|
|
1483
|
+
this.removeTooltip_();
|
|
1484
|
+
|
|
1463
1485
|
if (['mouseenter', 'click'].includes(event.type)) {
|
|
1464
|
-
const markerId = this.__markerIdByMarker.get(marker);
|
|
1465
|
-
const annotation = this.__annotationByMarkerId.get(markerId);
|
|
1466
1486
|
// The marker's feature
|
|
1467
1487
|
const feature = this.mapFeature(annotation.featureId);
|
|
1468
1488
|
if (feature !== undefined) {
|
|
@@ -1476,13 +1496,12 @@ export class UserInteractions
|
|
|
1476
1496
|
}
|
|
1477
1497
|
// Show tooltip
|
|
1478
1498
|
const html = this.tooltipHtml_(annotation, true);
|
|
1479
|
-
this.__showToolTip(html,
|
|
1499
|
+
this.__showToolTip(html, markerPosition);
|
|
1480
1500
|
|
|
1481
1501
|
// Send marker event message
|
|
1482
1502
|
this._flatmap.markerEvent(event.type, markerId, anatomicalId);
|
|
1483
1503
|
}
|
|
1484
1504
|
}
|
|
1485
|
-
event.stopPropagation();
|
|
1486
1505
|
}
|
|
1487
1506
|
|
|
1488
1507
|
__clearActiveMarker()
|
|
@@ -1531,7 +1550,7 @@ export class UserInteractions
|
|
|
1531
1550
|
.setLngLat(location)
|
|
1532
1551
|
.setDOMContent(element);
|
|
1533
1552
|
|
|
1534
|
-
// Set the
|
|
1553
|
+
// Set the marker tooltip and show it
|
|
1535
1554
|
marker.setPopup(this._tooltip);
|
|
1536
1555
|
marker.togglePopup();
|
|
1537
1556
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
|
|
3
|
+
Flatmap viewer and annotation tool
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 - 2023 David Brooks
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
|
19
|
+
******************************************************************************/
|
|
20
|
+
|
|
21
|
+
import {SvgManager, SvgTemplateManager} from '../../thirdParty/maplibre-gl-svg/src'
|
|
22
|
+
|
|
23
|
+
//==============================================================================
|
|
24
|
+
|
|
25
|
+
const markerLargeCircle = `<svg xmlns="http://www.w3.org/2000/svg" width="calc(28 * {scale})" height="calc(39 * {scale})" viewBox="-1 -1 27 42">
|
|
26
|
+
<ellipse style="fill: rgb(0, 0, 0); fill-opacity: 0.2;" cx="12" cy="36" rx="8" ry="4"/>
|
|
27
|
+
<path d="M12.25.25a12.254 12.254 0 0 0-12 12.494c0 6.444 6.488 12.109 11.059 22.564.549 1.256 1.333 1.256 1.882 0
|
|
28
|
+
C17.762 24.853 24.25 19.186 24.25 12.744A12.254 12.254 0 0 0 12.25.25Z"
|
|
29
|
+
style="fill:{color};stroke:{secondaryColor};stroke-width:1"/>
|
|
30
|
+
<circle cx="12.5" cy="12.5" r="9" fill="{secondaryColor}"/>
|
|
31
|
+
<text x="12" y="17.5" style="font-size:14px;fill:#000;text-anchor:middle">{text}</text>
|
|
32
|
+
</svg>`
|
|
33
|
+
|
|
34
|
+
const markerSmallCircle = `<svg xmlns="http://www.w3.org/2000/svg" width="calc(28 * {scale})" height="calc(39 * {scale})" viewBox="-1 -1 27 42">
|
|
35
|
+
<ellipse style="fill: rgb(0, 0, 0); fill-opacity: 0.2;" cx="12" cy="36" rx="8" ry="4"/>
|
|
36
|
+
<path d="M12.25.25a12.254 12.254 0 0 0-12 12.494c0 6.444 6.488 12.109 11.059 22.564.549 1.256 1.333 1.256 1.882 0
|
|
37
|
+
C17.762 24.853 24.25 19.186 24.25 12.744A12.254 12.254 0 0 0 12.25.25Z"
|
|
38
|
+
style="fill:{color};stroke:{secondaryColor};stroke-width:1"/>
|
|
39
|
+
<circle cx="12.5" cy="12.5" r="5" fill="{secondaryColor}"/>
|
|
40
|
+
</svg>`
|
|
41
|
+
|
|
42
|
+
//==============================================================================
|
|
43
|
+
|
|
44
|
+
export async function loadClusterIcons(map)
|
|
45
|
+
{
|
|
46
|
+
SvgTemplateManager.addTemplate('marker-large-circle', markerLargeCircle, false)
|
|
47
|
+
SvgTemplateManager.addTemplate('marker-small-circle', markerSmallCircle, false)
|
|
48
|
+
|
|
49
|
+
const svgManager = new SvgManager(map)
|
|
50
|
+
await svgManager.createFromTemplate('clustered-marker', 'marker-large-circle', '#EE5900', '#fff')
|
|
51
|
+
await svgManager.createFromTemplate('unclustered-marker', 'marker-small-circle', '#005974', '#fff')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//==============================================================================
|
|
55
|
+
|
|
56
|
+
export class ClusteredMarkerLayer
|
|
57
|
+
{
|
|
58
|
+
#flatmap
|
|
59
|
+
#map
|
|
60
|
+
#points = {
|
|
61
|
+
type: 'FeatureCollection',
|
|
62
|
+
features: []
|
|
63
|
+
}
|
|
64
|
+
#ui
|
|
65
|
+
|
|
66
|
+
seenmove = false
|
|
67
|
+
|
|
68
|
+
constructor(flatmap, ui)
|
|
69
|
+
{
|
|
70
|
+
this.#flatmap = flatmap
|
|
71
|
+
this.#ui = ui
|
|
72
|
+
this.#map = flatmap.map
|
|
73
|
+
|
|
74
|
+
this.#map.addSource('markers', {
|
|
75
|
+
type: 'geojson',
|
|
76
|
+
data: this.#points,
|
|
77
|
+
cluster: true, // Adds the ``point_count`` property to source data
|
|
78
|
+
clusterMaxZoom: 9, // Max zoom to cluster points on
|
|
79
|
+
clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
this.#map.addLayer({
|
|
83
|
+
id: 'clustered-markers',
|
|
84
|
+
type: 'symbol',
|
|
85
|
+
source: 'markers',
|
|
86
|
+
filter: ['has', 'point_count'],
|
|
87
|
+
'layout': {
|
|
88
|
+
'icon-image': 'clustered-marker',
|
|
89
|
+
'icon-allow-overlap': true,
|
|
90
|
+
'icon-ignore-placement': true,
|
|
91
|
+
'icon-offset': [0, -17],
|
|
92
|
+
'icon-size': 0.8,
|
|
93
|
+
'text-field': '{point_count_abbreviated}',
|
|
94
|
+
'text-size': 10,
|
|
95
|
+
'text-offset': [0, -1.93]
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
this.#map.addLayer({
|
|
100
|
+
id: 'single-points',
|
|
101
|
+
type: 'symbol',
|
|
102
|
+
source: 'markers',
|
|
103
|
+
filter: ['!', ['has', 'point_count']],
|
|
104
|
+
'layout': {
|
|
105
|
+
'icon-image': 'unclustered-marker',
|
|
106
|
+
'icon-allow-overlap': true,
|
|
107
|
+
'icon-ignore-placement': true,
|
|
108
|
+
'icon-offset': [0, -17],
|
|
109
|
+
'icon-size': 0.6
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// inspect a cluster on click
|
|
114
|
+
this.#map.on('click', 'clustered-markers', async (e) => {
|
|
115
|
+
const features = this.#map.queryRenderedFeatures(e.point, {
|
|
116
|
+
layers: ['clustered-markers']
|
|
117
|
+
})
|
|
118
|
+
console.log('Cluster marker', features)
|
|
119
|
+
const clusterId = features[0].properties.cluster_id
|
|
120
|
+
const zoom = await this.#map.getSource('markers').getClusterExpansionZoom(clusterId)
|
|
121
|
+
this.#map.easeTo({
|
|
122
|
+
center: features[0].geometry.coordinates,
|
|
123
|
+
zoom
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
this.#map.on('click', 'single-points', this.singleMarkerEvent.bind(this))
|
|
128
|
+
this.#map.on('mouseenter', 'single-points', this.singleMarkerEvent.bind(this))
|
|
129
|
+
this.#map.on('mousemove', 'single-points', this.singleMarkerEvent.bind(this))
|
|
130
|
+
// this.#map.on('mouseleave', 'single-points', this.singleMarkerEvent.bind(this))
|
|
131
|
+
|
|
132
|
+
this.#map.on('mouseenter', 'clustered-markers', () => {
|
|
133
|
+
this.#map.getCanvas().style.cursor = 'pointer'
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
this.#map.on('mouseleave', 'clustered-markers', () => {
|
|
137
|
+
this.#map.getCanvas().style.cursor = ''
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Also 'mousemove'...
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
singleMarkerEvent(event)
|
|
144
|
+
//======================
|
|
145
|
+
{
|
|
146
|
+
const features = this.#map.queryRenderedFeatures(event.point, {
|
|
147
|
+
layers: ['single-points']
|
|
148
|
+
})
|
|
149
|
+
for (const feature of features) {
|
|
150
|
+
if (event.type === 'mousemove' && !this.seenMove) {
|
|
151
|
+
console.log('Single marker', event.type, feature)
|
|
152
|
+
this.seenMove = true
|
|
153
|
+
} else if (event.type !== 'mousemove') {
|
|
154
|
+
console.log('Single marker', event.type, feature)
|
|
155
|
+
this.seenMove = false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const properties = feature.properties
|
|
159
|
+
const position = properties.markerPosition.slice(1, -1).split(',').map(p => +p)
|
|
160
|
+
this.#ui.markerEvent_(event, feature.id, position, properties.models, properties)
|
|
161
|
+
}
|
|
162
|
+
event.originalEvent.stopPropagation()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
addMarker(id, position, properties={})
|
|
166
|
+
//====================================
|
|
167
|
+
{
|
|
168
|
+
this.#points.features.push({
|
|
169
|
+
type: 'Feature',
|
|
170
|
+
id,
|
|
171
|
+
properties,
|
|
172
|
+
geometry: {
|
|
173
|
+
type: 'Point',
|
|
174
|
+
coordinates: position
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
this.#map.getSource('markers')
|
|
178
|
+
.setData(this.#points)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
clearMarkers()
|
|
182
|
+
//============
|
|
183
|
+
{
|
|
184
|
+
this.#points.features = []
|
|
185
|
+
this.#map.getSource('markers')
|
|
186
|
+
.setData(this.#points)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//==============================================================================
|
|
191
|
+
|
|
@@ -344,14 +344,12 @@ export class FlightPathLayer
|
|
|
344
344
|
id: `arc-${pathType}`,
|
|
345
345
|
data: pathData,
|
|
346
346
|
pickable: true,
|
|
347
|
-
autoHighlight: true,
|
|
348
347
|
numSegments: 400,
|
|
349
348
|
// Styles
|
|
350
349
|
getSourcePosition: f => f.pathStartPosition,
|
|
351
350
|
getTargetPosition: f => f.pathEndPosition,
|
|
352
351
|
getSourceColor: this.#pathColour.bind(this),
|
|
353
352
|
getTargetColor: this.#pathColour.bind(this),
|
|
354
|
-
highlightColor: o => this.#pathColour(o.object),
|
|
355
353
|
opacity: 1.0,
|
|
356
354
|
getWidth: 3,
|
|
357
355
|
}
|
package/src/layers/index.js
CHANGED
|
@@ -25,6 +25,7 @@ limitations under the License.
|
|
|
25
25
|
import {PATHWAYS_LAYER} from '../pathways.js';
|
|
26
26
|
import * as utils from '../utils.js';
|
|
27
27
|
|
|
28
|
+
import {ClusteredMarkerLayer} from './cluster'
|
|
28
29
|
import * as style from './styling.js';
|
|
29
30
|
|
|
30
31
|
import {FlightPathLayer} from './flightpaths'
|
|
@@ -266,6 +267,7 @@ class MapRasterLayers extends MapStylingLayers
|
|
|
266
267
|
export class LayerManager
|
|
267
268
|
{
|
|
268
269
|
#featureLayers = new Map()
|
|
270
|
+
#markerLayer = null
|
|
269
271
|
#flightPathLayer = null
|
|
270
272
|
#rasterLayer = null
|
|
271
273
|
|
|
@@ -308,6 +310,9 @@ export class LayerManager
|
|
|
308
310
|
|
|
309
311
|
// Support flight path view
|
|
310
312
|
this.#flightPathLayer = new FlightPathLayer(flatmap, ui)
|
|
313
|
+
|
|
314
|
+
// Show clustered markers in a layer
|
|
315
|
+
this.#markerLayer = new ClusteredMarkerLayer(flatmap, ui)
|
|
311
316
|
}
|
|
312
317
|
|
|
313
318
|
get layers()
|
|
@@ -356,6 +361,18 @@ export class LayerManager
|
|
|
356
361
|
}
|
|
357
362
|
}
|
|
358
363
|
|
|
364
|
+
addMarker(id, position, properties={})
|
|
365
|
+
//====================================
|
|
366
|
+
{
|
|
367
|
+
this.#markerLayer.addMarker(id, position, properties)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
clearMarkers()
|
|
371
|
+
//============
|
|
372
|
+
{
|
|
373
|
+
this.#markerLayer.clearMarkers()
|
|
374
|
+
}
|
|
375
|
+
|
|
359
376
|
featuresAtPoint(point)
|
|
360
377
|
//====================
|
|
361
378
|
{
|
|
@@ -408,10 +425,14 @@ export class LayerManager
|
|
|
408
425
|
mapLayer.setFilter(this.__layerOptions);
|
|
409
426
|
}
|
|
410
427
|
if (this.#flightPathLayer) {
|
|
428
|
+
// * @arg options.layerOptions.sckan {string} Show neuron paths known to SCKAN: values are ``valid`` (default),
|
|
429
|
+
// * ``invalid``, ``all`` or ``none``.
|
|
430
|
+
|
|
431
|
+
|
|
411
432
|
const sckanState = options.sckan || 'valid'
|
|
412
433
|
const sckanFilter = (sckanState == 'none') ? {NOT: {HAS: 'sckan'}} :
|
|
413
434
|
(sckanState == 'valid') ? {sckan: true} :
|
|
414
|
-
(sckanState == 'invalid') ? {NOT: {sckan: true}} :
|
|
435
|
+
(sckanState == 'invalid') ? {NOT: {sckan: true}} : // {sckan: false} is different...
|
|
415
436
|
true
|
|
416
437
|
const featureFilter = new PropertiesFilter(sckanFilter)
|
|
417
438
|
if ('taxons' in options) {
|
package/src/main.js
CHANGED
|
@@ -25,6 +25,41 @@ limitations under the License.
|
|
|
25
25
|
import { MapManager } from './flatmap-viewer';
|
|
26
26
|
export { MapManager };
|
|
27
27
|
|
|
28
|
+
const ALL_MARKERS = [
|
|
29
|
+
// Most of these are around the heart
|
|
30
|
+
'UBERON:0003382',
|
|
31
|
+
'UBERON:0002348',
|
|
32
|
+
'UBERON:0002349',
|
|
33
|
+
'UBERON:0003379',
|
|
34
|
+
'UBERON:0001986',
|
|
35
|
+
'UBERON:0001074',
|
|
36
|
+
'UBERON:0003381',
|
|
37
|
+
'UBERON:0002165',
|
|
38
|
+
'UBERON:0002080',
|
|
39
|
+
'UBERON:0000948',
|
|
40
|
+
'UBERON:0002084',
|
|
41
|
+
'UBERON:0002078',
|
|
42
|
+
'UBERON:0002079',
|
|
43
|
+
'UBERON:0002349',
|
|
44
|
+
'UBERON:0002408',
|
|
45
|
+
'UBERON:0007240',
|
|
46
|
+
'UBERON:0002359',
|
|
47
|
+
|
|
48
|
+
'UBERON:0001508',
|
|
49
|
+
'UBERON:0037094',
|
|
50
|
+
'ILX:0738305',
|
|
51
|
+
|
|
52
|
+
'UBERON:0000948', // {className: 'heart-marker'}); // Heart
|
|
53
|
+
'UBERON:0002048', // Lung
|
|
54
|
+
'UBERON:0000945', // Stomach
|
|
55
|
+
'UBERON:0001155', // Colon
|
|
56
|
+
'UBERON:0001255', // Bladder
|
|
57
|
+
'UBERON:0001759', // Vagus
|
|
58
|
+
|
|
59
|
+
'UBERON:0016508', // Pelvic ganglion
|
|
60
|
+
|
|
61
|
+
]
|
|
62
|
+
|
|
28
63
|
//==============================================================================
|
|
29
64
|
|
|
30
65
|
class DrawControl
|
|
@@ -110,6 +145,7 @@ export async function standaloneViewer(map_endpoint=null, options={})
|
|
|
110
145
|
showPosition: false,
|
|
111
146
|
standalone: true,
|
|
112
147
|
annotator: true,
|
|
148
|
+
flightPaths: true
|
|
113
149
|
}, options);
|
|
114
150
|
|
|
115
151
|
function loadMap(id, taxon, sex)
|
|
@@ -139,15 +175,20 @@ export async function standaloneViewer(map_endpoint=null, options={})
|
|
|
139
175
|
mapOptions.background = args[0].value;
|
|
140
176
|
} else if (eventType === 'annotation') {
|
|
141
177
|
drawControl.handleEvent(...args)
|
|
178
|
+
} else {
|
|
179
|
+
//console.log(eventType, ...args)
|
|
142
180
|
}
|
|
143
181
|
}, mapOptions)
|
|
144
182
|
.then(map => {
|
|
183
|
+
/*
|
|
145
184
|
map.addMarker('UBERON:0000948', {className: 'heart-marker'}); // Heart
|
|
146
185
|
map.addMarker('UBERON:0002048'); // Lung
|
|
147
186
|
map.addMarker('UBERON:0000945'); // Stomach
|
|
148
187
|
map.addMarker('UBERON:0001155'); // Colon
|
|
149
188
|
map.addMarker('UBERON:0001255'); // Bladder
|
|
150
189
|
map.addMarker('UBERON:0001759'); // Vagus
|
|
190
|
+
*/
|
|
191
|
+
map.addMarkers(ALL_MARKERS, {cluster: true})
|
|
151
192
|
currentMap = map;
|
|
152
193
|
drawControl = new DrawControl(map)
|
|
153
194
|
})
|
package/src/pathways.js
CHANGED
package/src/types.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
|
|
3
|
+
Flatmap viewer and annotation tool
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 - 2024 David Brooks
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
|
19
|
+
******************************************************************************/
|
|
20
|
+
|
|
21
|
+
export type Constructor<T> = new(...args: any[]) => T
|
|
22
|
+
|
|
23
|
+
export type ObjectRecord = Record<string, any>
|
|
24
|
+
|
|
25
|
+
//==============================================================================
|
|
26
|
+
|
package/src/utils.js
CHANGED