@abi-software/flatmap-viewer 2.3.0-a.6 → 2.3.0-b.2
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 +3 -5
- package/src/annotation.js +26 -13
- package/src/{controls.js → controls/controls.js} +1 -8
- package/src/{info.js → controls/info.js} +2 -6
- package/src/{minimap.js → controls/minimap.js} +1 -5
- package/src/controls/search.js +113 -0
- package/src/controls/systems.js +66 -0
- package/src/flatmap-viewer.js +29 -12
- package/src/interactions.js +88 -60
- package/src/layers.js +2 -0
- package/src/main.js +2 -1
- package/src/search.js +0 -93
- package/src/styling.js +133 -37
- package/src/systems.js +57 -40
- /package/src/{newcontrols.js → controls/newcontrols.js} +0 -0
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.0-
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.3.0-b.2``
|
|
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.0-
|
|
3
|
+
"version": "2.3.0-b.2",
|
|
4
4
|
"description": "Flatmap viewer using Maplibre GL",
|
|
5
5
|
"repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
|
|
6
6
|
"main": "src/main.js",
|
|
@@ -38,14 +38,12 @@
|
|
|
38
38
|
"browser-sync": "^2.26.7",
|
|
39
39
|
"bs-fullscreen-message": "^1.1.0",
|
|
40
40
|
"clean-webpack-plugin": "^3.0.0",
|
|
41
|
-
"css-loader": "^6.
|
|
41
|
+
"css-loader": "^6.7.3",
|
|
42
42
|
"eslint": "^8.7.0",
|
|
43
43
|
"express": "^4.17.1",
|
|
44
|
-
"file-loader": "^6.2.0",
|
|
45
44
|
"html-webpack-plugin": "^4.5.2",
|
|
46
45
|
"strip-ansi": "^7.0.1",
|
|
47
|
-
"style-loader": "^
|
|
48
|
-
"url-loader": "^4.1.0",
|
|
46
|
+
"style-loader": "^3.3.2",
|
|
49
47
|
"webpack": "^5.16.0",
|
|
50
48
|
"webpack-cli": "^4.4.0",
|
|
51
49
|
"webpack-dev-middleware": "^4.1.0",
|
package/src/annotation.js
CHANGED
|
@@ -116,12 +116,6 @@ export class Annotator
|
|
|
116
116
|
async __authorise(panel)
|
|
117
117
|
//======================
|
|
118
118
|
{
|
|
119
|
-
/*
|
|
120
|
-
const testUser = {name: 'Testing...'};
|
|
121
|
-
this.__setUser(testUser);
|
|
122
|
-
callback(testUser);
|
|
123
|
-
|
|
124
|
-
*/
|
|
125
119
|
const abortController = new AbortController();
|
|
126
120
|
setTimeout((panel) => {
|
|
127
121
|
if (this.user === 'undefined') {
|
|
@@ -147,7 +141,7 @@ export class Annotator
|
|
|
147
141
|
} else {
|
|
148
142
|
this.__setUser(user_data);
|
|
149
143
|
this.__authorised = true;
|
|
150
|
-
return user_data;
|
|
144
|
+
return Promise.resolve(user_data);
|
|
151
145
|
}
|
|
152
146
|
} else {
|
|
153
147
|
return Promise.resolve({error: `${response.status} ${response.statusText}`});
|
|
@@ -174,7 +168,7 @@ export class Annotator
|
|
|
174
168
|
});
|
|
175
169
|
if (response.ok) {
|
|
176
170
|
this.__authorised = false;
|
|
177
|
-
return
|
|
171
|
+
return response.json();
|
|
178
172
|
} else {
|
|
179
173
|
return Promise.resolve({error: `${response.status} ${response.statusText}`});
|
|
180
174
|
}
|
|
@@ -307,8 +301,8 @@ export class Annotator
|
|
|
307
301
|
listValues.push(inputField.value.trim());
|
|
308
302
|
}
|
|
309
303
|
const lastValue = field.update ? provenanceData[field.key] || [] : [];
|
|
310
|
-
const oldValues = lastValue.map(v => v.trim()).filter(v => (v !== '')).sort();
|
|
311
|
-
const newValues = listValues.map(v => v.trim()).filter(v => (v !== '')).sort();
|
|
304
|
+
const oldValues = lastValue.map(v => v.trim()).filter(v => (v !== '')).sort(Intl.Collator().compare);
|
|
305
|
+
const newValues = listValues.map(v => v.trim()).filter(v => (v !== '')).sort(Intl.Collator().compare);
|
|
312
306
|
if (oldValues.length !== newValues.length
|
|
313
307
|
|| oldValues.filter(v => !newValues.includes(v)).length > 0) {
|
|
314
308
|
newProperties[field.key] = newValues;
|
|
@@ -336,7 +330,7 @@ export class Annotator
|
|
|
336
330
|
}
|
|
337
331
|
}, UPDATE_TIMEOUT, panel);
|
|
338
332
|
|
|
339
|
-
const url = this.__flatmap.
|
|
333
|
+
const url = this.__flatmap.makeServerUrl(this.__currentFeatureId, 'annotator/');
|
|
340
334
|
const response = await fetch(url, {
|
|
341
335
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
342
336
|
method: 'POST',
|
|
@@ -344,7 +338,7 @@ export class Annotator
|
|
|
344
338
|
signal: abortController.signal
|
|
345
339
|
});
|
|
346
340
|
if (response.ok) {
|
|
347
|
-
return
|
|
341
|
+
return response.json();
|
|
348
342
|
} else {
|
|
349
343
|
return Promise.resolve({error: `${response.status} ${response.statusText}`});
|
|
350
344
|
}
|
|
@@ -367,6 +361,7 @@ export class Annotator
|
|
|
367
361
|
if ('error' in response) {
|
|
368
362
|
this.__setStatusMessage(response.error);
|
|
369
363
|
} else {
|
|
364
|
+
this.__flatmap.setFeatureAnnotated(this.__currentFeatureId);
|
|
370
365
|
panel.close();
|
|
371
366
|
}
|
|
372
367
|
} else {
|
|
@@ -490,7 +485,7 @@ export class Annotator
|
|
|
490
485
|
'<span id="flatmap-annotation-lock" class="jsPanel-ftr-btn fa fa-lock"></span>',
|
|
491
486
|
],
|
|
492
487
|
contentFetch: {
|
|
493
|
-
resource: flatmap.
|
|
488
|
+
resource: flatmap.makeServerUrl(this.__currentFeatureId, 'annotator/'),
|
|
494
489
|
fetchInit: {
|
|
495
490
|
method: 'GET',
|
|
496
491
|
mode: 'cors',
|
|
@@ -525,6 +520,24 @@ export class Annotator
|
|
|
525
520
|
document.addEventListener('jspanelclosed', closedCallback, false);
|
|
526
521
|
}
|
|
527
522
|
|
|
523
|
+
async annotated_features()
|
|
524
|
+
//========================
|
|
525
|
+
{
|
|
526
|
+
const url = this.__flatmap.makeServerUrl('', 'annotator/');
|
|
527
|
+
const response = await fetch(url, {
|
|
528
|
+
headers: {
|
|
529
|
+
"Accept": "application/json; charset=utf-8",
|
|
530
|
+
"Cache-Control": "no-store"
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
if (response.ok) {
|
|
534
|
+
return response.json();
|
|
535
|
+
} else {
|
|
536
|
+
console.error(`Annotated features: ${response.status} ${response.statusText}`);
|
|
537
|
+
return Promise.resolve([]);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
528
541
|
}
|
|
529
542
|
|
|
530
543
|
//==============================================================================
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Flatmap viewer and annotation tool
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2019
|
|
5
|
+
Copyright (c) 2019 - 2023 David Brooks
|
|
6
6
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
you may not use this file except in compliance with the License.
|
|
@@ -18,13 +18,6 @@ limitations under the License.
|
|
|
18
18
|
|
|
19
19
|
******************************************************************************/
|
|
20
20
|
|
|
21
|
-
'use strict';
|
|
22
|
-
|
|
23
|
-
//==============================================================================
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//==============================================================================
|
|
27
|
-
|
|
28
21
|
// Make sure colour string is in `#rrggbb` form.
|
|
29
22
|
// Based on https://stackoverflow.com/a/47355187
|
|
30
23
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Flatmap viewer and annotation tool
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2019
|
|
5
|
+
Copyright (c) 2019 - 2023 David Brooks
|
|
6
6
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
you may not use this file except in compliance with the License.
|
|
@@ -18,11 +18,7 @@ limitations under the License.
|
|
|
18
18
|
|
|
19
19
|
******************************************************************************/
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
//==============================================================================
|
|
24
|
-
|
|
25
|
-
import { indexedProperties } from './search.js';
|
|
21
|
+
import { indexedProperties } from '../search.js';
|
|
26
22
|
|
|
27
23
|
//==============================================================================
|
|
28
24
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Flatmap viewer and annotation tool
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2019
|
|
5
|
+
Copyright (c) 2019 - 2023 David Brooks
|
|
6
6
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
you may not use this file except in compliance with the License.
|
|
@@ -45,10 +45,6 @@ limitations under the License.
|
|
|
45
45
|
|
|
46
46
|
//==============================================================================
|
|
47
47
|
|
|
48
|
-
'use strict';
|
|
49
|
-
|
|
50
|
-
//==============================================================================
|
|
51
|
-
|
|
52
48
|
import maplibre from 'maplibre-gl';
|
|
53
49
|
|
|
54
50
|
//==============================================================================
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
|
|
22
|
+
export class SearchControl
|
|
23
|
+
{
|
|
24
|
+
constructor(flatmap)
|
|
25
|
+
{
|
|
26
|
+
this.__flatmap = flatmap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onAdd(map)
|
|
30
|
+
//========
|
|
31
|
+
{
|
|
32
|
+
this._map = map;
|
|
33
|
+
this._container = document.createElement('div');
|
|
34
|
+
this._container.className = 'maplibregl-ctrl search-control';
|
|
35
|
+
|
|
36
|
+
this._input = document.createElement('input');
|
|
37
|
+
this._input.id = 'search-control-input';
|
|
38
|
+
this._input.setAttribute('type', 'search');
|
|
39
|
+
this._input.setAttribute('visible', 'false');
|
|
40
|
+
this._input.setAttribute('placeholder', 'Search...');
|
|
41
|
+
|
|
42
|
+
this._button = document.createElement('button');
|
|
43
|
+
this._button.id = 'search-control-button';
|
|
44
|
+
this._button.className = 'control-button';
|
|
45
|
+
this._button.title = 'Search flatmap';
|
|
46
|
+
this._button.setAttribute('type', 'button');
|
|
47
|
+
this._button.setAttribute('aria-label', 'Search flatmap');
|
|
48
|
+
// https://iconmonstr.com/magnifier-6-svg/
|
|
49
|
+
this._button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" id="search-control-icon" viewBox="0 0 24 24">
|
|
50
|
+
<path d="M21.172 24l-7.387-7.387c-1.388.874-3.024 1.387-4.785 1.387-4.971 0-9-4.029-9-9s4.029-9 9-9 9 4.029 9 9c0 1.761-.514 3.398-1.387 4.785l7.387 7.387-2.828 2.828zm-12.172-8c3.859 0 7-3.14 7-7s-3.141-7-7-7-7 3.14-7 7 3.141 7 7 7z"/>
|
|
51
|
+
</svg>`;
|
|
52
|
+
this._container.appendChild(this._button);
|
|
53
|
+
|
|
54
|
+
this._container.onclick = this.onClick_.bind(this);
|
|
55
|
+
return this._container;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getDefaultPosition()
|
|
59
|
+
//==================
|
|
60
|
+
{
|
|
61
|
+
return 'top-right';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onRemove()
|
|
65
|
+
//========
|
|
66
|
+
{
|
|
67
|
+
this._container.parentNode.removeChild(this._container);
|
|
68
|
+
this._map = undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
searchMap_(search=true)
|
|
72
|
+
//=====================
|
|
73
|
+
{
|
|
74
|
+
this._input = this._container.removeChild(this._input);
|
|
75
|
+
this._input.setAttribute('visible', 'false');
|
|
76
|
+
const text = this._input.value;
|
|
77
|
+
if (search && text !== '') {
|
|
78
|
+
const results = this.__flatmap.search(text);
|
|
79
|
+
this.__flatmap.showSearchResults(results);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onKeyDown_(e)
|
|
84
|
+
//===========
|
|
85
|
+
{
|
|
86
|
+
if (e.key === 'Enter') {
|
|
87
|
+
this.searchMap_();
|
|
88
|
+
} else if (e.key === 'Escape') {
|
|
89
|
+
this.searchMap_(false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onClick_(e)
|
|
94
|
+
//=========
|
|
95
|
+
{
|
|
96
|
+
const targetId = ('rangeTarget' in e) ? e.rangeTarget.id : e.target.id; // FF has rangeTarget
|
|
97
|
+
if (['search-control-button', 'search-control-icon'].includes(targetId)) {
|
|
98
|
+
if (this._input.getAttribute('visible') === 'false') {
|
|
99
|
+
this._container.appendChild(this._input);
|
|
100
|
+
this._container.appendChild(this._button);
|
|
101
|
+
this._input.setAttribute('visible', 'true');
|
|
102
|
+
this._input.onkeydown = this.onKeyDown_.bind(this);
|
|
103
|
+
this._input.value = '';
|
|
104
|
+
this.__flatmap.clearSearchResults();
|
|
105
|
+
this._input.focus();
|
|
106
|
+
} else {
|
|
107
|
+
this.searchMap_();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//==============================================================================
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
|
|
22
|
+
import { Control } from './controls';
|
|
23
|
+
|
|
24
|
+
//==============================================================================
|
|
25
|
+
|
|
26
|
+
export class SystemsControl extends Control
|
|
27
|
+
{
|
|
28
|
+
constructor(flatmap, systems)
|
|
29
|
+
{
|
|
30
|
+
super(flatmap, 'system', 'systems');
|
|
31
|
+
this.__systems = systems;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
__innerLinesHTML()
|
|
35
|
+
//================
|
|
36
|
+
{
|
|
37
|
+
const html = [];
|
|
38
|
+
for (const system of this.__systems) {
|
|
39
|
+
html.push(`<label for="${this.__prefix}${system.id}" style="background: ${system.colour};">${system.name}</label><input id="${this.__prefix}${system.id}" type="checkbox" checked/>`);
|
|
40
|
+
}
|
|
41
|
+
return html;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
__enableAll(enable)
|
|
45
|
+
//=================
|
|
46
|
+
{
|
|
47
|
+
for (const system of this.__systems) {
|
|
48
|
+
const checkbox = document.getElementById(`${this.__prefix}${system.id}`);
|
|
49
|
+
if (checkbox) {
|
|
50
|
+
checkbox.checked = enable;
|
|
51
|
+
this.__flatmap.enableSystem(system.name, enable);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
__enableControl(id, enable)
|
|
57
|
+
//=========================
|
|
58
|
+
{
|
|
59
|
+
for (const system of this.__systems) {
|
|
60
|
+
if (id === system.id) {
|
|
61
|
+
this.__flatmap.enableSystem(system.name, enable);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
package/src/flatmap-viewer.js
CHANGED
|
@@ -34,11 +34,12 @@ import '../static/css/flatmap-viewer.css';
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
36
|
import {MapServer} from './mapserver.js';
|
|
37
|
-
import {MinimapControl} from './minimap.js';
|
|
38
|
-
import {NavigationControl} from './controls.js';
|
|
39
37
|
import {SearchIndex} from './search.js';
|
|
40
38
|
import {UserInteractions} from './interactions.js';
|
|
41
39
|
|
|
40
|
+
import {MinimapControl} from './controls/minimap.js';
|
|
41
|
+
import {NavigationControl} from './controls/controls.js';
|
|
42
|
+
|
|
42
43
|
import * as images from './images.js';
|
|
43
44
|
import * as utils from './utils.js';
|
|
44
45
|
|
|
@@ -85,12 +86,12 @@ class FlatMap
|
|
|
85
86
|
|
|
86
87
|
for (const [id, source] of Object.entries(mapDescription.style.sources)) {
|
|
87
88
|
if (source.url) {
|
|
88
|
-
source.url = this.
|
|
89
|
+
source.url = this.makeServerUrl(source.url);
|
|
89
90
|
}
|
|
90
91
|
if (source.tiles) {
|
|
91
92
|
const tiles = [];
|
|
92
93
|
for (const tileUrl of source.tiles) {
|
|
93
|
-
tiles.push(this.
|
|
94
|
+
tiles.push(this.makeServerUrl(tileUrl));
|
|
94
95
|
}
|
|
95
96
|
source.tiles = tiles;
|
|
96
97
|
}
|
|
@@ -361,21 +362,23 @@ class FlatMap
|
|
|
361
362
|
{
|
|
362
363
|
if (!this._map.hasImage(id)) {
|
|
363
364
|
const image = await (path.startsWith('data:image') ? this.loadEncodedImage_(path)
|
|
364
|
-
: this.loadImage_(path.startsWith('/') ? this.
|
|
365
|
+
: this.loadImage_(path.startsWith('/') ? this.makeServerUrl(path)
|
|
365
366
|
: new URL(path, baseUrl)));
|
|
366
367
|
this._map.addImage(id, image, options);
|
|
367
368
|
}
|
|
368
369
|
}
|
|
369
370
|
|
|
370
|
-
|
|
371
|
-
|
|
371
|
+
makeServerUrl(url, resource='flatmap/')
|
|
372
|
+
//=====================================
|
|
372
373
|
{
|
|
373
|
-
if (url.startsWith('
|
|
374
|
-
return
|
|
375
|
-
} else if (
|
|
376
|
-
|
|
374
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
375
|
+
return url;
|
|
376
|
+
} else if (url.startsWith('/')) {
|
|
377
|
+
// We don't want embedded `{` and `}` characters escaped
|
|
378
|
+
return `${this._baseUrl}${resource}${this.__uuid}${url}`;
|
|
379
|
+
} else {
|
|
380
|
+
return `${this._baseUrl}${resource}${this.__uuid}/${url}`;
|
|
377
381
|
}
|
|
378
|
-
return url;
|
|
379
382
|
}
|
|
380
383
|
|
|
381
384
|
/**
|
|
@@ -470,6 +473,19 @@ class FlatMap
|
|
|
470
473
|
return this.__idToAnnotation.get(featureId.toString());
|
|
471
474
|
}
|
|
472
475
|
|
|
476
|
+
/**
|
|
477
|
+
* Flag the feature as having external annotation.
|
|
478
|
+
*
|
|
479
|
+
* @param {string} featureId The feature's identifier
|
|
480
|
+
*/
|
|
481
|
+
setFeatureAnnotated(featureId)
|
|
482
|
+
//============================
|
|
483
|
+
{
|
|
484
|
+
if (this._userInteractions !== null) {
|
|
485
|
+
this._userInteractions.setFeatureAnnotated(featureId);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
473
489
|
__updateFeatureIdMap(property, featureIdMap, annotation)
|
|
474
490
|
//======================================================
|
|
475
491
|
{
|
|
@@ -1286,6 +1302,7 @@ export class MapManager
|
|
|
1286
1302
|
* @arg options.showPosition {boolean} Show ``position`` of tooltip.
|
|
1287
1303
|
* @arg options.standalone {boolean} Viewer is running ``standalone``, as opposed to integrated into
|
|
1288
1304
|
* another application so show a number of controls. Defaults to ``false``.
|
|
1305
|
+
* @arg options.annotator {boolean} Allow interactive annotation of features and paths.
|
|
1289
1306
|
* @example
|
|
1290
1307
|
* const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
|
|
1291
1308
|
*
|
package/src/interactions.js
CHANGED
|
@@ -34,14 +34,16 @@ import polylabel from 'polylabel';
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
36
|
import {Annotator} from './annotation';
|
|
37
|
-
import {displayedProperties, InfoControl} from './info';
|
|
38
37
|
import {LayerManager} from './layers';
|
|
39
38
|
import {PATHWAYS_LAYER, Pathways} from './pathways';
|
|
39
|
+
import {COLOUR_ERROR, VECTOR_TILES_SOURCE} from './styling';
|
|
40
|
+
import {SystemsManager} from './systems';
|
|
41
|
+
|
|
42
|
+
import {displayedProperties, InfoControl} from './controls/info';
|
|
40
43
|
import {BackgroundControl, LayerControl, NerveControl,
|
|
41
|
-
PathControl, SCKANControl} from './controls';
|
|
42
|
-
import {SearchControl} from './search';
|
|
43
|
-
import {
|
|
44
|
-
import {SystemsControl, SystemsManager} from './systems';
|
|
44
|
+
PathControl, SCKANControl} from './controls/controls';
|
|
45
|
+
import {SearchControl} from './controls/search';
|
|
46
|
+
import {SystemsControl} from './controls/systems';
|
|
45
47
|
|
|
46
48
|
import * as pathways from './pathways';
|
|
47
49
|
import * as utils from './utils';
|
|
@@ -144,27 +146,17 @@ export class UserInteractions
|
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (feature !== undefined) {
|
|
153
|
-
this._map.setFeatureState(feature, { 'annotated': true });
|
|
154
|
-
}
|
|
155
|
-
if (ann['fc-class'] === 'fc-class:System') {
|
|
156
|
-
if (this.__systems.has(ann.name)) {
|
|
157
|
-
this.__systems.get(ann.name).featureIds.push(ann.featureId)
|
|
158
|
-
} else {
|
|
159
|
-
this.__systems.set(ann.name, {
|
|
160
|
-
id: ann.name.replaceAll(' ', '_'),
|
|
161
|
-
colour: ann.colour,
|
|
162
|
-
featureIds: [ ann.featureId ]
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
149
|
+
// Add annotation capability
|
|
150
|
+
if (flatmap.options.annotator) {
|
|
151
|
+
this.__setupAnnotation();
|
|
152
|
+
} else {
|
|
153
|
+
this.__annotator = null;
|
|
166
154
|
}
|
|
167
155
|
|
|
156
|
+
// Note features that are FC systems
|
|
157
|
+
|
|
158
|
+
this.__systemsManager = new SystemsManager(this._flatmap, this);
|
|
159
|
+
|
|
168
160
|
// Add various controls when running standalone
|
|
169
161
|
|
|
170
162
|
if (flatmap.options.standalone) {
|
|
@@ -192,15 +184,11 @@ export class UserInteractions
|
|
|
192
184
|
|
|
193
185
|
// SCKAN path and SYSTEMS controls for FC maps
|
|
194
186
|
if (flatmap.options.style === 'functional') {
|
|
195
|
-
this._map.addControl(new SystemsControl(flatmap, this.
|
|
187
|
+
this._map.addControl(new SystemsControl(flatmap, this.__systemsManager.systems));
|
|
196
188
|
this._map.addControl(new SCKANControl(flatmap, flatmap.options.layerOptions));
|
|
197
189
|
}
|
|
198
190
|
}
|
|
199
191
|
|
|
200
|
-
// Add annotation capabilities
|
|
201
|
-
|
|
202
|
-
this.__annotator = new Annotator(flatmap);
|
|
203
|
-
|
|
204
192
|
// Handle mouse events
|
|
205
193
|
|
|
206
194
|
this._map.on('click', this.clickEvent_.bind(this));
|
|
@@ -254,6 +242,41 @@ export class UserInteractions
|
|
|
254
242
|
}
|
|
255
243
|
}
|
|
256
244
|
|
|
245
|
+
async __setupAnnotation()
|
|
246
|
+
//=======================
|
|
247
|
+
{
|
|
248
|
+
// Add annotation capability
|
|
249
|
+
|
|
250
|
+
this.__annotator = new Annotator(this._flatmap);
|
|
251
|
+
const annotated_features = await this.__annotator.annotated_features();
|
|
252
|
+
|
|
253
|
+
// Flag features that have annotations
|
|
254
|
+
this.__featureIdToMapId = new Map();
|
|
255
|
+
for (const [mapId, ann] of this._flatmap.annotations) {
|
|
256
|
+
this.__featureIdToMapId.set(ann.id, mapId);
|
|
257
|
+
const feature = this.mapFeature(mapId);
|
|
258
|
+
if (feature !== undefined) {
|
|
259
|
+
this._map.setFeatureState(feature, { 'map-annotation': true });
|
|
260
|
+
if (annotated_features.indexOf(ann.id) >= 0) {
|
|
261
|
+
this._map.setFeatureState(feature, { 'annotated': true });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
setFeatureAnnotated(featureId)
|
|
268
|
+
//============================
|
|
269
|
+
{
|
|
270
|
+
if (this.__annotator) {
|
|
271
|
+
// featureId v's geoJSON id
|
|
272
|
+
const mapId = this.__featureIdToMapId.get(featureId);
|
|
273
|
+
const feature = this.mapFeature(mapId);
|
|
274
|
+
if (feature !== undefined) {
|
|
275
|
+
this._map.setFeatureState(feature, { 'annotated': true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
257
280
|
setPaint(options)
|
|
258
281
|
//===============
|
|
259
282
|
{
|
|
@@ -276,34 +299,35 @@ export class UserInteractions
|
|
|
276
299
|
getSystems()
|
|
277
300
|
//==========
|
|
278
301
|
{
|
|
279
|
-
|
|
280
|
-
for (const system of this.__systems.values()) {
|
|
281
|
-
systems.push({
|
|
282
|
-
name: system.name,
|
|
283
|
-
colour: system.colour,
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
return systems;
|
|
302
|
+
return this.__systemsManager.systems;
|
|
287
303
|
}
|
|
288
304
|
|
|
289
305
|
enableSystem(systemName, enable=true)
|
|
290
306
|
//===================================
|
|
291
307
|
{
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
308
|
+
this.__systemsManager.enable(systemName, enable);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
enableFeatureWithChildren(featureId, enable=true)
|
|
312
|
+
//===============================================
|
|
313
|
+
{
|
|
314
|
+
const feature = this.mapFeature(featureId);
|
|
315
|
+
if (feature !== undefined) {
|
|
316
|
+
this.enableFeature(feature, enable);
|
|
317
|
+
for (const childFeatureId of feature.children) {
|
|
318
|
+
this.enableFeatureWithChildren(childFeatureId, enable);
|
|
295
319
|
}
|
|
296
320
|
}
|
|
297
321
|
}
|
|
298
322
|
|
|
299
|
-
|
|
300
|
-
|
|
323
|
+
__enableFeatureWithParents(featureId, enable=true)
|
|
324
|
+
//================================================
|
|
301
325
|
{
|
|
302
|
-
const feature = this.
|
|
326
|
+
const feature = this.mapFeature(featureId);
|
|
303
327
|
if (feature !== undefined) {
|
|
304
|
-
this.
|
|
305
|
-
for (const childFeatureId of feature.
|
|
306
|
-
this.
|
|
328
|
+
this.enableFeature(feature, enable);
|
|
329
|
+
for (const childFeatureId of feature.parents) {
|
|
330
|
+
this.__enableFeatureWithParents(childFeatureId, enable);
|
|
307
331
|
}
|
|
308
332
|
}
|
|
309
333
|
}
|
|
@@ -320,8 +344,8 @@ export class UserInteractions
|
|
|
320
344
|
}
|
|
321
345
|
}
|
|
322
346
|
|
|
323
|
-
|
|
324
|
-
|
|
347
|
+
enableFeature(feature, enable=true)
|
|
348
|
+
//=================================
|
|
325
349
|
{
|
|
326
350
|
if (feature !== undefined) {
|
|
327
351
|
if (enable) {
|
|
@@ -333,8 +357,8 @@ export class UserInteractions
|
|
|
333
357
|
}
|
|
334
358
|
}
|
|
335
359
|
|
|
336
|
-
|
|
337
|
-
|
|
360
|
+
mapFeature(featureId)
|
|
361
|
+
//===================
|
|
338
362
|
{
|
|
339
363
|
const ann = this._flatmap.annotation(featureId);
|
|
340
364
|
if (ann !== undefined) {
|
|
@@ -366,7 +390,7 @@ export class UserInteractions
|
|
|
366
390
|
if (this._selectedFeatureIds.has(featureId)) {
|
|
367
391
|
this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
|
|
368
392
|
} else {
|
|
369
|
-
const feature = this.
|
|
393
|
+
const feature = this.mapFeature(featureId);
|
|
370
394
|
if (feature !== undefined) {
|
|
371
395
|
this._map.setFeatureState(feature, { 'selected': true });
|
|
372
396
|
this._selectedFeatureIds.set(featureId, 1);
|
|
@@ -383,7 +407,7 @@ export class UserInteractions
|
|
|
383
407
|
if (references > 1) {
|
|
384
408
|
this._selectedFeatureIds.set(featureId, references - 1);
|
|
385
409
|
} else {
|
|
386
|
-
const feature = this.
|
|
410
|
+
const feature = this.mapFeature(featureId);
|
|
387
411
|
if (feature !== undefined) {
|
|
388
412
|
this._map.removeFeatureState(feature, 'selected');
|
|
389
413
|
this._selectedFeatureIds.delete(+featureId);
|
|
@@ -399,7 +423,7 @@ export class UserInteractions
|
|
|
399
423
|
//==================
|
|
400
424
|
{
|
|
401
425
|
for (const featureId of this._selectedFeatureIds.keys()) {
|
|
402
|
-
const feature = this.
|
|
426
|
+
const feature = this.mapFeature(featureId);
|
|
403
427
|
if (feature !== undefined) {
|
|
404
428
|
this._map.removeFeatureState(feature, 'selected');
|
|
405
429
|
}
|
|
@@ -429,7 +453,7 @@ export class UserInteractions
|
|
|
429
453
|
//==========================
|
|
430
454
|
{
|
|
431
455
|
featureId = +featureId; // Ensure numeric
|
|
432
|
-
this.__activateFeature(this.
|
|
456
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
433
457
|
}
|
|
434
458
|
|
|
435
459
|
unhighlightFeatures_()
|
|
@@ -447,7 +471,7 @@ export class UserInteractions
|
|
|
447
471
|
let smallestFeature = null;
|
|
448
472
|
for (const feature of features) {
|
|
449
473
|
if (feature.geometry.type.includes('Polygon')
|
|
450
|
-
&& this._map.getFeatureState(feature)['
|
|
474
|
+
&& this._map.getFeatureState(feature)['map-annotation']) {
|
|
451
475
|
const polygon = turf.geometry(feature.geometry.type, feature.geometry.coordinates);
|
|
452
476
|
const area = turfArea(polygon);
|
|
453
477
|
if (smallestFeature === null || smallestArea > area) {
|
|
@@ -805,7 +829,7 @@ export class UserInteractions
|
|
|
805
829
|
const lineIds = new Set(lineFeatures.map(f => f.properties.featureId));
|
|
806
830
|
for (const featureId of this._pathways.lineFeatureIds(lineIds)) {
|
|
807
831
|
if (+featureId !== lineFeatureId) {
|
|
808
|
-
this.__activateFeature(this.
|
|
832
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
809
833
|
}
|
|
810
834
|
}
|
|
811
835
|
}
|
|
@@ -929,6 +953,10 @@ export class UserInteractions
|
|
|
929
953
|
__annotationEvent(feature)
|
|
930
954
|
//========================
|
|
931
955
|
{
|
|
956
|
+
if (!this.__annotator) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
|
|
932
960
|
event.preventDefault();
|
|
933
961
|
|
|
934
962
|
// Remove any tooltip
|
|
@@ -988,12 +1016,12 @@ export class UserInteractions
|
|
|
988
1016
|
{
|
|
989
1017
|
if ('nerveId' in feature.properties) {
|
|
990
1018
|
for (const featureId of this._pathways.nerveFeatureIds(feature.properties.nerveId)) {
|
|
991
|
-
this.__activateFeature(this.
|
|
1019
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
992
1020
|
}
|
|
993
1021
|
}
|
|
994
1022
|
if ('nodeId' in feature.properties) {
|
|
995
1023
|
for (const featureId of this._pathways.nodeFeatureIds(feature.properties.nodeId)) {
|
|
996
|
-
this.__activateFeature(this.
|
|
1024
|
+
this.__activateFeature(this.mapFeature(featureId));
|
|
997
1025
|
}
|
|
998
1026
|
}
|
|
999
1027
|
}
|
|
@@ -1018,7 +1046,7 @@ export class UserInteractions
|
|
|
1018
1046
|
//=====================================
|
|
1019
1047
|
{
|
|
1020
1048
|
for (const featureId of featureIds) {
|
|
1021
|
-
const feature = this.
|
|
1049
|
+
const feature = this.mapFeature(featureId);
|
|
1022
1050
|
if (feature !== undefined) {
|
|
1023
1051
|
if (enable) {
|
|
1024
1052
|
this._map.removeFeatureState(feature, 'hidden');
|
|
@@ -1229,7 +1257,7 @@ export class UserInteractions
|
|
|
1229
1257
|
const markerId = this.__markerIdByMarker.get(marker);
|
|
1230
1258
|
const annotation = this.__annotationByMarkerId.get(markerId);
|
|
1231
1259
|
// The marker's feature
|
|
1232
|
-
const feature = this.
|
|
1260
|
+
const feature = this.mapFeature(annotation.featureId);
|
|
1233
1261
|
if (feature !== undefined) {
|
|
1234
1262
|
if (event.type === 'mouseenter') {
|
|
1235
1263
|
// Highlight on mouse enter
|
package/src/layers.js
CHANGED
|
@@ -154,6 +154,8 @@ class MapFeatureLayers extends MapStylingLayers
|
|
|
154
154
|
if (this.__map.getSource('vector-tiles')
|
|
155
155
|
.vectorLayerIds
|
|
156
156
|
.indexOf(pathwaysVectorSource) >= 0) {
|
|
157
|
+
this.__addStyleLayer(style.AnnotatedPathLayer, PATHWAYS_LAYER);
|
|
158
|
+
|
|
157
159
|
this.__addStyleLayer(style.CentrelineEdgeLayer, PATHWAYS_LAYER);
|
|
158
160
|
this.__addStyleLayer(style.CentrelineTrackLayer, PATHWAYS_LAYER);
|
|
159
161
|
|
package/src/main.js
CHANGED
package/src/search.js
CHANGED
|
@@ -36,99 +36,6 @@ export const indexedProperties = [
|
|
|
36
36
|
|
|
37
37
|
//==============================================================================
|
|
38
38
|
|
|
39
|
-
export class SearchControl
|
|
40
|
-
{
|
|
41
|
-
constructor(flatmap)
|
|
42
|
-
{
|
|
43
|
-
this.__flatmap = flatmap;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
onAdd(map)
|
|
47
|
-
//========
|
|
48
|
-
{
|
|
49
|
-
this._map = map;
|
|
50
|
-
this._container = document.createElement('div');
|
|
51
|
-
this._container.className = 'maplibregl-ctrl search-control';
|
|
52
|
-
|
|
53
|
-
this._input = document.createElement('input');
|
|
54
|
-
this._input.id = 'search-control-input';
|
|
55
|
-
this._input.setAttribute('type', 'search');
|
|
56
|
-
this._input.setAttribute('visible', 'false');
|
|
57
|
-
this._input.setAttribute('placeholder', 'Search...');
|
|
58
|
-
|
|
59
|
-
this._button = document.createElement('button');
|
|
60
|
-
this._button.id = 'search-control-button';
|
|
61
|
-
this._button.className = 'control-button';
|
|
62
|
-
this._button.title = 'Search flatmap';
|
|
63
|
-
this._button.setAttribute('type', 'button');
|
|
64
|
-
this._button.setAttribute('aria-label', 'Search flatmap');
|
|
65
|
-
// https://iconmonstr.com/magnifier-6-svg/
|
|
66
|
-
this._button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" id="search-control-icon" viewBox="0 0 24 24">
|
|
67
|
-
<path d="M21.172 24l-7.387-7.387c-1.388.874-3.024 1.387-4.785 1.387-4.971 0-9-4.029-9-9s4.029-9 9-9 9 4.029 9 9c0 1.761-.514 3.398-1.387 4.785l7.387 7.387-2.828 2.828zm-12.172-8c3.859 0 7-3.14 7-7s-3.141-7-7-7-7 3.14-7 7 3.141 7 7 7z"/>
|
|
68
|
-
</svg>`;
|
|
69
|
-
this._container.appendChild(this._button);
|
|
70
|
-
|
|
71
|
-
this._container.onclick = this.onClick_.bind(this);
|
|
72
|
-
return this._container;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
getDefaultPosition()
|
|
76
|
-
//==================
|
|
77
|
-
{
|
|
78
|
-
return 'top-right';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
onRemove()
|
|
82
|
-
//========
|
|
83
|
-
{
|
|
84
|
-
this._container.parentNode.removeChild(this._container);
|
|
85
|
-
this._map = undefined;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
searchMap_(search=true)
|
|
89
|
-
//=====================
|
|
90
|
-
{
|
|
91
|
-
this._input = this._container.removeChild(this._input);
|
|
92
|
-
this._input.setAttribute('visible', 'false');
|
|
93
|
-
const text = this._input.value;
|
|
94
|
-
if (search && text !== '') {
|
|
95
|
-
const results = this.__flatmap.search(text);
|
|
96
|
-
this.__flatmap.showSearchResults(results);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
onKeyDown_(e)
|
|
101
|
-
//===========
|
|
102
|
-
{
|
|
103
|
-
if (e.key === 'Enter') {
|
|
104
|
-
this.searchMap_();
|
|
105
|
-
} else if (e.key === 'Escape') {
|
|
106
|
-
this.searchMap_(false);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
onClick_(e)
|
|
111
|
-
//=========
|
|
112
|
-
{
|
|
113
|
-
const targetId = ('rangeTarget' in e) ? e.rangeTarget.id : e.target.id; // FF has rangeTarget
|
|
114
|
-
if (['search-control-button', 'search-control-icon'].includes(targetId)) {
|
|
115
|
-
if (this._input.getAttribute('visible') === 'false') {
|
|
116
|
-
this._container.appendChild(this._input);
|
|
117
|
-
this._container.appendChild(this._button);
|
|
118
|
-
this._input.setAttribute('visible', 'true');
|
|
119
|
-
this._input.onkeydown = this.onKeyDown_.bind(this);
|
|
120
|
-
this._input.value = '';
|
|
121
|
-
this.__flatmap.clearSearchResults();
|
|
122
|
-
this._input.focus();
|
|
123
|
-
} else {
|
|
124
|
-
this.searchMap_();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
//==============================================================================
|
|
131
|
-
|
|
132
39
|
export class SearchIndex
|
|
133
40
|
{
|
|
134
41
|
constructor()
|
package/src/styling.js
CHANGED
|
@@ -30,6 +30,20 @@ import {PATH_STYLE_RULES} from './pathways.js';
|
|
|
30
30
|
|
|
31
31
|
//==============================================================================
|
|
32
32
|
|
|
33
|
+
const COLOUR_ACTIVE = 'blue';
|
|
34
|
+
const COLOUR_ANNOTATED = '#0F0';
|
|
35
|
+
const COLOUR_SELECTED = '#0F0';
|
|
36
|
+
|
|
37
|
+
const CENTRELINE_ACTIVE = '#444';
|
|
38
|
+
const CENTRELINE_COLOUR = '#CCC';
|
|
39
|
+
|
|
40
|
+
const FEATURE_SELECTED_BORDER = 'black';
|
|
41
|
+
|
|
42
|
+
const NERVE_ACTIVE = '#222';
|
|
43
|
+
const NERVE_SELECTED = 'red';
|
|
44
|
+
|
|
45
|
+
//==============================================================================
|
|
46
|
+
|
|
33
47
|
class VectorStyleLayer
|
|
34
48
|
{
|
|
35
49
|
constructor(id, suffix, sourceLayer)
|
|
@@ -121,19 +135,21 @@ export class FeatureFillLayer extends VectorStyleLayer
|
|
|
121
135
|
|
|
122
136
|
paintStyle(options, changes=false)
|
|
123
137
|
{
|
|
138
|
+
const ghostColour = '#D8D8D8'; // Function of BG colour?
|
|
124
139
|
const coloured = !('colour' in options) || options.colour;
|
|
125
140
|
const dimmed = 'dimmed' in options && options.dimmed;
|
|
126
141
|
const paintStyle = {
|
|
127
142
|
'fill-color': [
|
|
128
143
|
'case',
|
|
129
|
-
['boolean', ['feature-state', 'selected'], false],
|
|
144
|
+
['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
|
|
145
|
+
['boolean', ['feature-state', 'hidden'], false], ghostColour,
|
|
130
146
|
['has', 'colour'], ['get', 'colour'],
|
|
131
147
|
['boolean', ['feature-state', 'active'], false], coloured ? '#D88' : '#CCC',
|
|
132
148
|
'white' // background colour? body colour ??
|
|
133
149
|
],
|
|
134
150
|
'fill-opacity': [
|
|
135
151
|
'case',
|
|
136
|
-
['boolean', ['feature-state', 'hidden'], false], 0.
|
|
152
|
+
['boolean', ['feature-state', 'hidden'], false], 0.1,
|
|
137
153
|
['boolean', ['feature-state', 'selected'], false], 0.7,
|
|
138
154
|
['has', 'opacity'], ['get', 'opacity'],
|
|
139
155
|
['has', 'colour'], 1.0,
|
|
@@ -180,11 +196,13 @@ export class FeatureBorderLayer extends VectorStyleLayer
|
|
|
180
196
|
const activeRasterLayer = 'activeRasterLayer' in options && options.activeRasterLayer;
|
|
181
197
|
const lineColour = [ 'case' ];
|
|
182
198
|
lineColour.push(['boolean', ['feature-state', 'selected'], false]);
|
|
183
|
-
lineColour.push(
|
|
199
|
+
lineColour.push(FEATURE_SELECTED_BORDER);
|
|
184
200
|
if (coloured && outlined) {
|
|
185
201
|
lineColour.push(['boolean', ['feature-state', 'active'], false]);
|
|
186
|
-
lineColour.push(
|
|
202
|
+
lineColour.push(COLOUR_ACTIVE);
|
|
187
203
|
}
|
|
204
|
+
lineColour.push(['boolean', ['feature-state', 'annotated'], false]);
|
|
205
|
+
lineColour.push(COLOUR_ANNOTATED);
|
|
188
206
|
lineColour.push(['has', 'colour']);
|
|
189
207
|
lineColour.push(['get', 'colour']);
|
|
190
208
|
lineColour.push('#444');
|
|
@@ -199,6 +217,8 @@ export class FeatureBorderLayer extends VectorStyleLayer
|
|
|
199
217
|
}
|
|
200
218
|
lineOpacity.push(['boolean', ['feature-state', 'selected'], false]);
|
|
201
219
|
lineOpacity.push(0.9);
|
|
220
|
+
lineOpacity.push(['boolean', ['feature-state', 'annotated'], false]);
|
|
221
|
+
lineOpacity.push(0.9);
|
|
202
222
|
if (activeRasterLayer) {
|
|
203
223
|
lineOpacity.push((outlined && !dimmed) ? 0.3 : 0.1);
|
|
204
224
|
} else {
|
|
@@ -215,6 +235,8 @@ export class FeatureBorderLayer extends VectorStyleLayer
|
|
|
215
235
|
lineWidth.push(['boolean', ['feature-state', 'active'], false]);
|
|
216
236
|
lineWidth.push(1.5);
|
|
217
237
|
}
|
|
238
|
+
lineWidth.push(['boolean', ['feature-state', 'annotated'], false]);
|
|
239
|
+
lineWidth.push(3.5);
|
|
218
240
|
lineWidth.push(['has', 'colour']);
|
|
219
241
|
lineWidth.push(0.7);
|
|
220
242
|
lineWidth.push((coloured && outlined) ? 0.5 : 0.1);
|
|
@@ -276,7 +298,7 @@ export class FeatureLineLayer extends VectorStyleLayer
|
|
|
276
298
|
const paintStyle = {
|
|
277
299
|
'line-color': [
|
|
278
300
|
'case',
|
|
279
|
-
['boolean', ['feature-state', 'selected'], false],
|
|
301
|
+
['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
|
|
280
302
|
['boolean', ['feature-state', 'active'], false], coloured ? '#888' : '#CCC',
|
|
281
303
|
['has', 'colour'], ['get', 'colour'],
|
|
282
304
|
['==', ['get', 'type'], 'network'], '#AFA202',
|
|
@@ -338,6 +360,104 @@ export class FeatureDashLineLayer extends FeatureLineLayer
|
|
|
338
360
|
|
|
339
361
|
//==============================================================================
|
|
340
362
|
|
|
363
|
+
function sckanFilter(options)
|
|
364
|
+
{
|
|
365
|
+
const sckanState = !'sckan' in options ? 'all'
|
|
366
|
+
: options.sckan.toLowerCase();
|
|
367
|
+
const sckanFilter =
|
|
368
|
+
sckanState == 'none' ? [
|
|
369
|
+
['!has', 'sckan']
|
|
370
|
+
] :
|
|
371
|
+
sckanState == 'valid' ? [[
|
|
372
|
+
'any',
|
|
373
|
+
['!has', 'sckan'],
|
|
374
|
+
[
|
|
375
|
+
'all',
|
|
376
|
+
['has', 'sckan'],
|
|
377
|
+
['==', 'sckan', true]
|
|
378
|
+
]
|
|
379
|
+
]] :
|
|
380
|
+
sckanState == 'invalid' ? [[
|
|
381
|
+
'any',
|
|
382
|
+
['!has', 'sckan'],
|
|
383
|
+
[
|
|
384
|
+
'all',
|
|
385
|
+
['has', 'sckan'],
|
|
386
|
+
['!=', 'sckan', true]
|
|
387
|
+
]
|
|
388
|
+
]] :
|
|
389
|
+
[ ];
|
|
390
|
+
return sckanFilter;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//==============================================================================
|
|
394
|
+
|
|
395
|
+
export class AnnotatedPathLayer extends VectorStyleLayer
|
|
396
|
+
{
|
|
397
|
+
constructor(id, sourceLayer)
|
|
398
|
+
{
|
|
399
|
+
super(id, 'annotated-path', sourceLayer);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
makeFilter(options={})
|
|
403
|
+
{
|
|
404
|
+
return [
|
|
405
|
+
'all',
|
|
406
|
+
['==', '$type', 'LineString'],
|
|
407
|
+
...sckanFilter(options)
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
paintStyle(options={}, changes=false)
|
|
412
|
+
{
|
|
413
|
+
const dimmed = 'dimmed' in options && options.dimmed;
|
|
414
|
+
const paintStyle = {
|
|
415
|
+
'line-color': COLOUR_ANNOTATED,
|
|
416
|
+
'line-dasharray': [5, 0.5, 3, 0.5],
|
|
417
|
+
'line-opacity': [
|
|
418
|
+
'case',
|
|
419
|
+
['boolean', ['feature-state', 'hidden'], false], 0.05,
|
|
420
|
+
['boolean', ['feature-state', 'annotated'], false],
|
|
421
|
+
(dimmed ? 0.1 : 0.8),
|
|
422
|
+
0.6
|
|
423
|
+
],
|
|
424
|
+
'line-width': [
|
|
425
|
+
'let',
|
|
426
|
+
'width',
|
|
427
|
+
['case',
|
|
428
|
+
['boolean', ['feature-state', 'annotated'], false],
|
|
429
|
+
['*', 1.2, ['case', ['has', 'stroke-width'], ['get', 'stroke-width'], 1.0]],
|
|
430
|
+
0.0
|
|
431
|
+
],
|
|
432
|
+
['interpolate',
|
|
433
|
+
['exponential', 2],
|
|
434
|
+
['zoom'],
|
|
435
|
+
2, ["*", ['var', 'width'], ["^", 2, -0.5]],
|
|
436
|
+
7, ["*", ['var', 'width'], ["^", 2, 2.5]],
|
|
437
|
+
9, ["*", ['var', 'width'], ["^", 2, 4.0]]
|
|
438
|
+
]
|
|
439
|
+
]
|
|
440
|
+
};
|
|
441
|
+
return super.changedPaintStyle(paintStyle, changes);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
style(options)
|
|
445
|
+
{
|
|
446
|
+
const dimmed = 'dimmed' in options && options.dimmed;
|
|
447
|
+
return {
|
|
448
|
+
...super.style(),
|
|
449
|
+
'type': 'line',
|
|
450
|
+
'filter': this.makeFilter(options),
|
|
451
|
+
'paint': this.paintStyle(options),
|
|
452
|
+
'layout': {
|
|
453
|
+
'line-cap': 'square'
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
//==============================================================================
|
|
460
|
+
|
|
341
461
|
export class PathLineLayer extends VectorStyleLayer
|
|
342
462
|
{
|
|
343
463
|
constructor(id, sourceLayer, options={})
|
|
@@ -350,31 +470,7 @@ export class PathLineLayer extends VectorStyleLayer
|
|
|
350
470
|
|
|
351
471
|
makeFilter(options={})
|
|
352
472
|
{
|
|
353
|
-
const
|
|
354
|
-
: options.sckan.toLowerCase();
|
|
355
|
-
const sckan_filter =
|
|
356
|
-
sckanState == 'none' ? [
|
|
357
|
-
['!has', 'sckan']
|
|
358
|
-
] :
|
|
359
|
-
sckanState == 'valid' ? [[
|
|
360
|
-
'any',
|
|
361
|
-
['!has', 'sckan'],
|
|
362
|
-
[
|
|
363
|
-
'all',
|
|
364
|
-
['has', 'sckan'],
|
|
365
|
-
['==', 'sckan', true]
|
|
366
|
-
]
|
|
367
|
-
]] :
|
|
368
|
-
sckanState == 'invalid' ? [[
|
|
369
|
-
'any',
|
|
370
|
-
['!has', 'sckan'],
|
|
371
|
-
[
|
|
372
|
-
'all',
|
|
373
|
-
['has', 'sckan'],
|
|
374
|
-
['!=', 'sckan', true]
|
|
375
|
-
]
|
|
376
|
-
]] :
|
|
377
|
-
[ ];
|
|
473
|
+
const sckan_filter = sckanFilter(options);
|
|
378
474
|
|
|
379
475
|
return this.__dashed ? [
|
|
380
476
|
'all',
|
|
@@ -402,7 +498,7 @@ export class PathLineLayer extends VectorStyleLayer
|
|
|
402
498
|
const paintStyle = {
|
|
403
499
|
'line-color': [
|
|
404
500
|
'case',
|
|
405
|
-
['boolean', ['feature-state', 'selected'], false],
|
|
501
|
+
['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
|
|
406
502
|
['boolean', ['feature-state', 'hidden'], false], '#CCC',
|
|
407
503
|
['==', ['get', 'type'], 'bezier'], 'red',
|
|
408
504
|
['==', ['get', 'kind'], 'unknown'], '#888',
|
|
@@ -487,9 +583,9 @@ class CentrelineLayer extends VectorStyleLayer
|
|
|
487
583
|
const paintStyle = {
|
|
488
584
|
'line-color': (this.__type == 'edge') ? '#000' : [
|
|
489
585
|
'case',
|
|
490
|
-
['boolean', ['feature-state', 'selected'], false],
|
|
491
|
-
['boolean', ['feature-state', 'active'], false],
|
|
492
|
-
|
|
586
|
+
['boolean', ['feature-state', 'selected'], false], COLOUR_SELECTED,
|
|
587
|
+
['boolean', ['feature-state', 'active'], false], CENTRELINE_ACTIVE,
|
|
588
|
+
CENTRELINE_COLOUR
|
|
493
589
|
],
|
|
494
590
|
'line-opacity': [
|
|
495
591
|
'case',
|
|
@@ -650,8 +746,8 @@ export class FeatureNerveLayer extends VectorStyleLayer
|
|
|
650
746
|
'line-color': [
|
|
651
747
|
'case',
|
|
652
748
|
['boolean', ['feature-state', 'hidden'], false], '#CCC',
|
|
653
|
-
['boolean', ['feature-state', 'active'], false],
|
|
654
|
-
['boolean', ['feature-state', 'selected'], false],
|
|
749
|
+
['boolean', ['feature-state', 'active'], false], NERVE_ACTIVE,
|
|
750
|
+
['boolean', ['feature-state', 'selected'], false], NERVE_SELECTED,
|
|
655
751
|
'#888'
|
|
656
752
|
],
|
|
657
753
|
'line-opacity': [
|
|
@@ -702,7 +798,7 @@ export class NervePolygonBorder extends VectorStyleLayer
|
|
|
702
798
|
'paint': {
|
|
703
799
|
'line-color': [
|
|
704
800
|
'case',
|
|
705
|
-
['boolean', ['feature-state', 'active'], false],
|
|
801
|
+
['boolean', ['feature-state', 'active'], false], COLOUR_ACTIVE,
|
|
706
802
|
['boolean', ['feature-state', 'selected'], false], 'red',
|
|
707
803
|
'#444'
|
|
708
804
|
],
|
package/src/systems.js
CHANGED
|
@@ -16,61 +16,78 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
16
16
|
See the License for the specific language governing permissions and
|
|
17
17
|
limitations under the License.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import { Control } from './controls';
|
|
23
|
-
|
|
19
|
+
**/
|
|
24
20
|
//==============================================================================
|
|
25
21
|
|
|
26
22
|
export class SystemsManager
|
|
27
23
|
{
|
|
28
|
-
constructor()
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
//==============================================================================
|
|
35
|
-
|
|
36
|
-
export class SystemsControl extends Control
|
|
37
|
-
{
|
|
38
|
-
constructor(flatmap, systems)
|
|
24
|
+
constructor(flatmap, ui, enabled=true)
|
|
39
25
|
{
|
|
40
|
-
|
|
41
|
-
this.__systems =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
this.__ui = ui;
|
|
27
|
+
this.__systems = new Map();
|
|
28
|
+
this.__enabledChildren = new Map();
|
|
29
|
+
for (const [id, ann] of flatmap.annotations) {
|
|
30
|
+
if (ann['fc-class'] === 'fc-class:System') {
|
|
31
|
+
if (this.__systems.has(ann.name)) {
|
|
32
|
+
this.__systems.get(ann.name).featureIds.push(ann.featureId)
|
|
33
|
+
} else {
|
|
34
|
+
this.__systems.set(ann.name, {
|
|
35
|
+
id: ann.name.replaceAll(' ', '_'),
|
|
36
|
+
colour: ann.colour,
|
|
37
|
+
featureIds: [ ann.featureId ],
|
|
38
|
+
enabled: enabled
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
for (const childId of ann['children']) {
|
|
42
|
+
if (enabled) {
|
|
43
|
+
const enabledCount = (this.__enabledChildren.has(childId))
|
|
44
|
+
? this.__enabledChildren.get(childId)
|
|
45
|
+
: 0;
|
|
46
|
+
this.__enabledChildren.set(childId, enabledCount + 1);
|
|
47
|
+
} else {
|
|
48
|
+
this.__enabledChildren.set(childId, 0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
50
52
|
}
|
|
51
|
-
return html;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
get systems()
|
|
56
|
+
//===========
|
|
56
57
|
{
|
|
58
|
+
const systems = [];
|
|
57
59
|
for (const [name, system] of this.__systems.entries()) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
systems.push({
|
|
61
|
+
name: name,
|
|
62
|
+
id: system.id,
|
|
63
|
+
colour: system.colour,
|
|
64
|
+
enabled: system.enabled
|
|
65
|
+
});
|
|
63
66
|
}
|
|
67
|
+
return systems;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
enable(systemName, enable=true)
|
|
71
|
+
//=============================
|
|
68
72
|
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
const system = this.__systems.get(systemName);
|
|
74
|
+
if (system !== undefined && enable !== system.enabled) {
|
|
75
|
+
for (const featureId of system.featureIds) {
|
|
76
|
+
const feature = this.__ui.mapFeature(featureId);
|
|
77
|
+
if (feature !== undefined) {
|
|
78
|
+
this.__ui.enableFeature(feature, enable);
|
|
79
|
+
for (const childFeatureId of feature.children) {
|
|
80
|
+
const enabledCount = this.__enabledChildren.get(childFeatureId);
|
|
81
|
+
if (enable && enabledCount === 0 || !enable && enabledCount == 1) {
|
|
82
|
+
this.__ui.enableFeatureWithChildren(childFeatureId, enable);
|
|
83
|
+
}
|
|
84
|
+
this.__enabledChildren.set(childFeatureId, enabledCount + (enable ? 1 : -1));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
72
87
|
}
|
|
88
|
+
system.enabled = enable;
|
|
73
89
|
}
|
|
74
90
|
}
|
|
75
|
-
|
|
76
91
|
}
|
|
92
|
+
|
|
93
|
+
//==============================================================================
|
|
File without changes
|