@abi-software/flatmap-viewer 2.2.13-b.2 → 2.2.13
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/editor.js +198 -0
- package/src/interactions.js +4 -16
- package/src/newcontrols.js +617 -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.2.13
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.2.13``
|
|
42
42
|
|
|
43
43
|
Documentation
|
|
44
44
|
-------------
|
package/package.json
CHANGED
package/src/editor.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Bezier } from "bezier-js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BezierCurve
|
|
5
|
+
{
|
|
6
|
+
constructor(id, ...points) {
|
|
7
|
+
this.__id = id;
|
|
8
|
+
this.__bezier = new Bezier(...points);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
asGeoJSON(samples=100)
|
|
12
|
+
{
|
|
13
|
+
const coords = [];
|
|
14
|
+
for (let ts = 0; ts <= samples; ts++) {
|
|
15
|
+
const pt = this.__bezier.get(float(ts)/float(samples));
|
|
16
|
+
coords.push([pt.x, pt.y]);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
'type': 'Feature',
|
|
20
|
+
'geometry': {
|
|
21
|
+
'type': 'LineString',
|
|
22
|
+
'coordinates': coords
|
|
23
|
+
},
|
|
24
|
+
'properties': {
|
|
25
|
+
'bezier': this.__id
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
asPoints()
|
|
31
|
+
{
|
|
32
|
+
const geojson = [];
|
|
33
|
+
for (const n of [0, 1, 2, 3]) {
|
|
34
|
+
const pt = this.__bezier.points[n];
|
|
35
|
+
geojson.push({
|
|
36
|
+
'type': 'Feature',
|
|
37
|
+
'geometry': {
|
|
38
|
+
'type': 'Point',
|
|
39
|
+
'coordinates': [pt.x, pt.y]
|
|
40
|
+
},
|
|
41
|
+
'properties': {
|
|
42
|
+
'bezier': this.__id,
|
|
43
|
+
'point': n
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return geojson;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
export class NetworkEditor
|
|
53
|
+
{
|
|
54
|
+
constructor(flatmap)
|
|
55
|
+
{
|
|
56
|
+
this.__map = flatmap.map;
|
|
57
|
+
this.__canvas = this.__map.getCanvasContainer();
|
|
58
|
+
|
|
59
|
+
this.__geojson = { // lines and points as separate sources???
|
|
60
|
+
'type': 'FeatureCollection',
|
|
61
|
+
'features': [
|
|
62
|
+
{
|
|
63
|
+
'type': 'Feature',
|
|
64
|
+
'geometry': {
|
|
65
|
+
'type': 'Point',
|
|
66
|
+
'coordinates': [0, 0]
|
|
67
|
+
},
|
|
68
|
+
'properties': {
|
|
69
|
+
'id': 0
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Add a single point to the map
|
|
76
|
+
this.__map.addSource('curves', {
|
|
77
|
+
'type': 'geojson',
|
|
78
|
+
'data': this.__geojson
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.__map.addLayer({
|
|
82
|
+
'id': 'lines',
|
|
83
|
+
'type': 'line',
|
|
84
|
+
'source': 'curves',
|
|
85
|
+
'paint': {
|
|
86
|
+
'line-color': '#3887be'
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
this.__map.addLayer({
|
|
90
|
+
'id': 'points',
|
|
91
|
+
'type': 'circle',
|
|
92
|
+
'source': 'curves',
|
|
93
|
+
'paint': {
|
|
94
|
+
'circle-radius': 10,
|
|
95
|
+
'circle-color': '#3887be'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.__currentPoint = null;
|
|
100
|
+
|
|
101
|
+
this.__map.on('mouseenter', this.mouseEnter.bind(this));
|
|
102
|
+
this.__map.on('mouseleave', this.mouseLeave.bind(this));
|
|
103
|
+
this.__map.on('mousedown', this.mouseDown.bind(this));
|
|
104
|
+
this.__map.on('touchstart', this.touchStart.bind(this));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
addPoint(coords)
|
|
108
|
+
{
|
|
109
|
+
const nextId = this.__geojson.features.length;
|
|
110
|
+
this.__geojson.features.push({
|
|
111
|
+
'type': 'Feature',
|
|
112
|
+
'geometry': {
|
|
113
|
+
'type': 'Point',
|
|
114
|
+
'coordinates': coords
|
|
115
|
+
},
|
|
116
|
+
'properties': {
|
|
117
|
+
'id': nextId
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
this.__map.getSource('curves').setData(this.__geojson);
|
|
121
|
+
return nextId;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mouseEnter(e) {
|
|
125
|
+
console.log('Mouse enter...');
|
|
126
|
+
|
|
127
|
+
this.__map.setPaintProperty('lines', 'line-color', '#3bb2d0');
|
|
128
|
+
this.__map.setPaintProperty('points', 'circle-color', '#3bb2d0');
|
|
129
|
+
this.__canvas.style.cursor = 'move';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
mouseLeave(e) {
|
|
134
|
+
this.__map.setPaintProperty('lines', 'line-color', '#3887be');
|
|
135
|
+
this.__map.setPaintProperty('points', 'circle-color', '#3887be');
|
|
136
|
+
this.__canvas.style.cursor = '';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
mouseDown(e) {
|
|
141
|
+
console.log('Mouse down...')
|
|
142
|
+
// Prevent the default map drag behavior.
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
const features = this.__map.queryRenderedFeatures(e.point, {'layers': ['lines', 'points']});
|
|
147
|
+
if (features.length === 0) {
|
|
148
|
+
const coords = e.lngLat;
|
|
149
|
+
this.__currentPoint = this.addPoint([coords.lng, coords.lat]);
|
|
150
|
+
} else {
|
|
151
|
+
const currentPoint = features[0].properties.id;
|
|
152
|
+
if (this.__currentPoint === null) {
|
|
153
|
+
this.__currentPoint = currentPoint;
|
|
154
|
+
} else if (this.__currentPoint === currentPoint) {
|
|
155
|
+
this.__currentPoint = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.__canvas.style.cursor = 'grab';
|
|
160
|
+
|
|
161
|
+
this.__map.on('mousemove', this.onMove.bind(this));
|
|
162
|
+
this.__map.once('mouseup', this.onUp.bind(this));
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
touchStart(e) {
|
|
167
|
+
if (e.points.length !== 1) return;
|
|
168
|
+
|
|
169
|
+
// Prevent the default map drag behavior.
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
|
|
172
|
+
this.__map.on('touchmove', this.onMove.bind(this));
|
|
173
|
+
this.__map.once('touchend', this.onUp.bind(this));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
onMove(e) {
|
|
177
|
+
console.log('Mouse move...')
|
|
178
|
+
|
|
179
|
+
// Set a UI indicator for dragging.
|
|
180
|
+
this.__canvas.style.cursor = 'grabbing'; // ????
|
|
181
|
+
|
|
182
|
+
if (this.__currentPoint !== null) {
|
|
183
|
+
const coords = e.lngLat;
|
|
184
|
+
this.__geojson.features[this.__currentPoint].geometry.coordinates = [coords.lng, coords.lat];
|
|
185
|
+
this.__map.getSource('curves').setData(this.__geojson);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onUp(e) {
|
|
190
|
+
console.log('End draw at', e.lngLat)
|
|
191
|
+
|
|
192
|
+
this.__canvas.style.cursor = '';
|
|
193
|
+
|
|
194
|
+
// Unbind mouse/touch events
|
|
195
|
+
this.__map.off('mousemove', this.onMove.bind(this));
|
|
196
|
+
this.__map.off('touchmove', this.onMove.bind(this));
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/interactions.js
CHANGED
|
@@ -338,9 +338,9 @@ export class UserInteractions
|
|
|
338
338
|
return {
|
|
339
339
|
id: featureId,
|
|
340
340
|
source: VECTOR_TILES_SOURCE,
|
|
341
|
-
sourceLayer: this._flatmap.options.separateLayers
|
|
341
|
+
sourceLayer: (this._flatmap.options.separateLayers
|
|
342
342
|
? `${ann['layer']}_${ann['tile-layer']}`
|
|
343
|
-
: ann['tile-layer'],
|
|
343
|
+
: ann['tile-layer']).replaceAll('/', '_'),
|
|
344
344
|
children: ann.children || []
|
|
345
345
|
};
|
|
346
346
|
}
|
|
@@ -405,19 +405,6 @@ export class UserInteractions
|
|
|
405
405
|
this._layerManager.setPaint({...this.__colourOptions, dimmed: false});
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
activeFeaturesAtEvent_(event)
|
|
409
|
-
//===========================
|
|
410
|
-
{
|
|
411
|
-
// Get the features covering the event's point that are in the active layers
|
|
412
|
-
|
|
413
|
-
return this._map.queryRenderedFeatures(event.point).filter(f => {
|
|
414
|
-
return (this.__enabledFeature(f)
|
|
415
|
-
&& this.activeLayerNames.indexOf(f.sourceLayer) >= 0)
|
|
416
|
-
&& ('featureId' in f.properties);
|
|
417
|
-
}
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
408
|
__activateFeature(feature)
|
|
422
409
|
//=======================
|
|
423
410
|
{
|
|
@@ -940,7 +927,8 @@ export class UserInteractions
|
|
|
940
927
|
//================
|
|
941
928
|
{
|
|
942
929
|
this.clearActiveMarker_();
|
|
943
|
-
const clickedFeatures = this._map.queryRenderedFeatures(event.point)
|
|
930
|
+
const clickedFeatures = this._map.queryRenderedFeatures(event.point)
|
|
931
|
+
.filter(feature => this.__enabledFeature(feature));
|
|
944
932
|
if (clickedFeatures.length == 0){
|
|
945
933
|
this.__unselectFeatures();
|
|
946
934
|
return;
|
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
|
|
3
|
+
Flatmap viewer and annotation tool
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 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
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
//==============================================================================
|
|
24
|
+
|
|
25
|
+
// Needed for Webpack ??
|
|
26
|
+
//import zoomInButton from '../static/images/zoom-in-button.png'
|
|
27
|
+
|
|
28
|
+
//==============================================================================
|
|
29
|
+
|
|
30
|
+
// Make sure colour string is in `#rrggbb` form.
|
|
31
|
+
// Based on https://stackoverflow.com/a/47355187
|
|
32
|
+
|
|
33
|
+
function standardise_color(str){
|
|
34
|
+
const canvas = document.createElement("canvas");
|
|
35
|
+
const ctx = canvas.getContext("2d");
|
|
36
|
+
ctx.fillStyle = str;
|
|
37
|
+
const colour = ctx.fillStyle;
|
|
38
|
+
canvas.remove()
|
|
39
|
+
return colour;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//==============================================================================
|
|
43
|
+
|
|
44
|
+
export class NavigationControl
|
|
45
|
+
{
|
|
46
|
+
constructor(flatmap)
|
|
47
|
+
{
|
|
48
|
+
this._flatmap = flatmap;
|
|
49
|
+
this._map = undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getDefaultPosition()
|
|
53
|
+
//==================
|
|
54
|
+
{
|
|
55
|
+
return 'top-right';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onAdd(map)
|
|
59
|
+
//========
|
|
60
|
+
{
|
|
61
|
+
this._map = map;
|
|
62
|
+
this._container = document.createElement('div');
|
|
63
|
+
this._container.className = 'maplibregl-ctrl navigation-group';
|
|
64
|
+
this._container.innerHTML = `<button id="flatmap-zoom-in" class="navigation-zoom-in" type="button" title="Zoom in" aria-label="Zoom in"></button>
|
|
65
|
+
<button id="flatmap-zoom-out" class="navigation-zoom-out" type="button" title="Zoom out" aria-label="Zoom out"></button>
|
|
66
|
+
<button id="flatmap-reset" class="navigation-reset" type="button" title="Reset" aria-label="Reset"></button>`;
|
|
67
|
+
this._container.onclick = this.onClick_.bind(this);
|
|
68
|
+
return this._container;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onRemove()
|
|
72
|
+
//========
|
|
73
|
+
{
|
|
74
|
+
this._container.parentNode.removeChild(this._container);
|
|
75
|
+
this._map = undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onClick_(e)
|
|
79
|
+
//=========
|
|
80
|
+
{
|
|
81
|
+
if (e.target.id === 'flatmap-zoom-in') {
|
|
82
|
+
this._flatmap.zoomIn();
|
|
83
|
+
} else if (e.target.id === 'flatmap-zoom-out') {
|
|
84
|
+
this._flatmap.zoomOut();
|
|
85
|
+
} else if (e.target.id === 'flatmap-reset') {
|
|
86
|
+
this._flatmap.resetMap();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//==============================================================================
|
|
92
|
+
|
|
93
|
+
export class PathControl
|
|
94
|
+
{
|
|
95
|
+
constructor(flatmap, pathTypes)
|
|
96
|
+
{
|
|
97
|
+
this._flatmap = flatmap;
|
|
98
|
+
this._map = undefined;
|
|
99
|
+
this.__pathTypes = pathTypes;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getDefaultPosition()
|
|
103
|
+
//==================
|
|
104
|
+
{
|
|
105
|
+
return 'top-right';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onAdd(map)
|
|
109
|
+
//========
|
|
110
|
+
{
|
|
111
|
+
this._map = map;
|
|
112
|
+
this._container = document.createElement('div');
|
|
113
|
+
this._container.className = 'maplibregl-ctrl';
|
|
114
|
+
this._container.id = 'flatmap-nerve-key';
|
|
115
|
+
|
|
116
|
+
this._legend = document.createElement('div');
|
|
117
|
+
this._legend.id = 'nerve-key-text';
|
|
118
|
+
this._legend.className = 'flatmap-nerve-grid';
|
|
119
|
+
|
|
120
|
+
const innerHTML = [];
|
|
121
|
+
innerHTML.push(`<label for="path-all-paths">ALL PATHS:</label><div class="nerve-line"></div><input id="path-all-paths" type="checkbox" checked/>`);
|
|
122
|
+
for (const path of this.__pathTypes) {
|
|
123
|
+
// need to set style background instead of class...
|
|
124
|
+
// background: #2A62F6;
|
|
125
|
+
// background: repeating-linear-gradient(to right,#EA3423 0,#EA3423 6px,transparent 6px,transparent 9px)
|
|
126
|
+
innerHTML.push(`<label for="path-${path.type}">${path.label}</label><div class="nerve-line nerve-${path.type}"></div><input id="path-${path.type}" type="checkbox" checked/>`);
|
|
127
|
+
}
|
|
128
|
+
this._legend.innerHTML = innerHTML.join('\n');
|
|
129
|
+
this.__checkedCount = this.__pathTypes.length;
|
|
130
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
131
|
+
|
|
132
|
+
this._button = document.createElement('button');
|
|
133
|
+
this._button.id = 'nerve-key-button';
|
|
134
|
+
this._button.className = 'control-button text-button';
|
|
135
|
+
this._button.setAttribute('type', 'button');
|
|
136
|
+
this._button.setAttribute('aria-label', 'Nerve paths legend');
|
|
137
|
+
this._button.setAttribute('control-visible', 'false');
|
|
138
|
+
this._button.textContent = 'PATHS';
|
|
139
|
+
this._button.title = 'Show/hide neuron paths';
|
|
140
|
+
this._container.appendChild(this._button);
|
|
141
|
+
|
|
142
|
+
this._container.addEventListener('click', this.onClick_.bind(this));
|
|
143
|
+
return this._container;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
onRemove()
|
|
147
|
+
//========
|
|
148
|
+
{
|
|
149
|
+
this._container.parentNode.removeChild(this._container);
|
|
150
|
+
this._map = undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onClick_(event)
|
|
154
|
+
//=============
|
|
155
|
+
{
|
|
156
|
+
if (event.target.id === 'nerve-key-button') {
|
|
157
|
+
if (this._button.getAttribute('control-visible') === 'false') {
|
|
158
|
+
this._container.appendChild(this._legend);
|
|
159
|
+
this._button.setAttribute('control-visible', 'true');
|
|
160
|
+
this._legend.focus();
|
|
161
|
+
} else {
|
|
162
|
+
this._legend = this._container.removeChild(this._legend);
|
|
163
|
+
this._button.setAttribute('control-visible', 'false');
|
|
164
|
+
}
|
|
165
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
166
|
+
if (event.target.id === 'path-all-paths') {
|
|
167
|
+
if (event.target.indeterminate) {
|
|
168
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
169
|
+
event.target.indeterminate = false;
|
|
170
|
+
}
|
|
171
|
+
if (event.target.checked) {
|
|
172
|
+
this.__checkedCount = this.__pathTypes.length;
|
|
173
|
+
} else {
|
|
174
|
+
this.__checkedCount = 0;
|
|
175
|
+
}
|
|
176
|
+
for (const path of this.__pathTypes) {
|
|
177
|
+
const pathCheckbox = document.getElementById(`path-${path.type}`);
|
|
178
|
+
if (pathCheckbox) {
|
|
179
|
+
pathCheckbox.checked = event.target.checked;
|
|
180
|
+
this._flatmap.enablePath(path.type, event.target.checked);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else if (event.target.id.startsWith('path-')) {
|
|
184
|
+
const pathType = event.target.id.substring(5);
|
|
185
|
+
this._flatmap.enablePath(pathType, event.target.checked);
|
|
186
|
+
if (event.target.checked) {
|
|
187
|
+
this.__checkedCount += 1;
|
|
188
|
+
} else {
|
|
189
|
+
this.__checkedCount -= 1;
|
|
190
|
+
}
|
|
191
|
+
const allPathsCheckbox = document.getElementById('path-all-paths');
|
|
192
|
+
if (this.__checkedCount === 0) {
|
|
193
|
+
allPathsCheckbox.checked = false;
|
|
194
|
+
allPathsCheckbox.indeterminate = false;
|
|
195
|
+
} else if (this.__checkedCount === this.__pathTypes.length) {
|
|
196
|
+
allPathsCheckbox.checked = true;
|
|
197
|
+
allPathsCheckbox.indeterminate = false;
|
|
198
|
+
} else {
|
|
199
|
+
allPathsCheckbox.indeterminate = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
event.stopPropagation();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
//==============================================================================
|
|
208
|
+
|
|
209
|
+
export class LayerControl
|
|
210
|
+
{
|
|
211
|
+
constructor(flatmap, layerManager)
|
|
212
|
+
{
|
|
213
|
+
this.__flatmap = flatmap;
|
|
214
|
+
this.__manager = layerManager;
|
|
215
|
+
this.__map = undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getDefaultPosition()
|
|
219
|
+
//==================
|
|
220
|
+
{
|
|
221
|
+
return 'top-right';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
onAdd(map)
|
|
225
|
+
//========
|
|
226
|
+
{
|
|
227
|
+
this.__map = map;
|
|
228
|
+
this.__container = document.createElement('div');
|
|
229
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
230
|
+
this.__container.id = 'flatmap-layer-control';
|
|
231
|
+
|
|
232
|
+
this.__layers = document.createElement('div');
|
|
233
|
+
this.__layers.id = 'layer-control-text';
|
|
234
|
+
this.__layers.className = 'flatmap-layer-grid';
|
|
235
|
+
|
|
236
|
+
const innerHTML = [];
|
|
237
|
+
innerHTML.push(`<label for="layer-all-layers">ALL LAYERS:</label><input id="layer-all-layers" type="checkbox" checked/>`);
|
|
238
|
+
for (const layer of this.__manager.layers) {
|
|
239
|
+
innerHTML.push(`<label for="layer-${layer.id}">${layer.description}</label><input id="layer-${layer.id}" type="checkbox" checked/>`);
|
|
240
|
+
}
|
|
241
|
+
this.__layers.innerHTML = innerHTML.join('\n');
|
|
242
|
+
|
|
243
|
+
this.__layersCount = this.__manager.layers.length;
|
|
244
|
+
this.__checkedCount = this.__layersCount;
|
|
245
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
246
|
+
|
|
247
|
+
this.__button = document.createElement('button');
|
|
248
|
+
this.__button.id = 'map-layers-button';
|
|
249
|
+
this.__button.className = 'control-button text-button';
|
|
250
|
+
this.__button.setAttribute('type', 'button');
|
|
251
|
+
this.__button.setAttribute('aria-label', 'Show/hide map layers');
|
|
252
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
253
|
+
this.__button.textContent = 'LAYERS';
|
|
254
|
+
this.__button.title = 'Show/hide map layers';
|
|
255
|
+
this.__container.appendChild(this.__button);
|
|
256
|
+
|
|
257
|
+
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
258
|
+
return this.__container;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
onRemove()
|
|
262
|
+
//========
|
|
263
|
+
{
|
|
264
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
265
|
+
this.__map = undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
onClick_(event)
|
|
269
|
+
//=============
|
|
270
|
+
{
|
|
271
|
+
if (event.target.id === 'map-layers-button') {
|
|
272
|
+
if (this.__button.getAttribute('control-visible') === 'false') {
|
|
273
|
+
this.__container.appendChild(this.__layers);
|
|
274
|
+
this.__button.setAttribute('control-visible', 'true');
|
|
275
|
+
this.__layers.focus();
|
|
276
|
+
} else {
|
|
277
|
+
this.__layers = this.__container.removeChild(this.__layers);
|
|
278
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
279
|
+
}
|
|
280
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
281
|
+
if (event.target.id === 'layer-all-layers') {
|
|
282
|
+
if (event.target.indeterminate) {
|
|
283
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
284
|
+
event.target.indeterminate = false;
|
|
285
|
+
}
|
|
286
|
+
if (event.target.checked) {
|
|
287
|
+
this.__checkedCount = this.__layersCount;
|
|
288
|
+
} else {
|
|
289
|
+
this.__checkedCount = 0;
|
|
290
|
+
}
|
|
291
|
+
for (const layer of this.__manager.layers) {
|
|
292
|
+
const layerCheckbox = document.getElementById(`layer-${layer.id}`);
|
|
293
|
+
if (layerCheckbox) {
|
|
294
|
+
layerCheckbox.checked = event.target.checked;
|
|
295
|
+
this.__manager.activate(layer.id, event.target.checked);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (event.target.id.startsWith('layer-')) {
|
|
299
|
+
const layerId = event.target.id.substring(6);
|
|
300
|
+
this.__manager.activate(layerId, event.target.checked);
|
|
301
|
+
if (event.target.checked) {
|
|
302
|
+
this.__checkedCount += 1;
|
|
303
|
+
} else {
|
|
304
|
+
this.__checkedCount -= 1;
|
|
305
|
+
}
|
|
306
|
+
const allLayersCheckbox = document.getElementById('layer-all-layers');
|
|
307
|
+
if (this.__checkedCount === 0) {
|
|
308
|
+
allLayersCheckbox.checked = false;
|
|
309
|
+
allLayersCheckbox.indeterminate = false;
|
|
310
|
+
} else if (this.__checkedCount === this.__layersCount) {
|
|
311
|
+
allLayersCheckbox.checked = true;
|
|
312
|
+
allLayersCheckbox.indeterminate = false;
|
|
313
|
+
} else {
|
|
314
|
+
allLayersCheckbox.indeterminate = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
event.stopPropagation();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
//==============================================================================
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
const SCKAN_STATES = [
|
|
326
|
+
{
|
|
327
|
+
'id': 'VALID',
|
|
328
|
+
'description': 'Path consistent with SCKAN'
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
'id': 'INVALID',
|
|
332
|
+
'description': 'Path inconsistent with SCKAN'
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
export class SCKANControl
|
|
338
|
+
{
|
|
339
|
+
constructor(flatmap)
|
|
340
|
+
{
|
|
341
|
+
this.__flatmap = flatmap;
|
|
342
|
+
this.__map = undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
getDefaultPosition()
|
|
346
|
+
//==================
|
|
347
|
+
{
|
|
348
|
+
return 'top-right';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
onAdd(map)
|
|
352
|
+
//========
|
|
353
|
+
{
|
|
354
|
+
this.__map = map;
|
|
355
|
+
this.__container = document.createElement('div');
|
|
356
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
357
|
+
this.__container.id = 'flatmap-layer-control';
|
|
358
|
+
|
|
359
|
+
this.__sckan = document.createElement('div');
|
|
360
|
+
this.__sckan.id = 'sckan-control-text';
|
|
361
|
+
this.__sckan.className = 'flatmap-layer-grid';
|
|
362
|
+
|
|
363
|
+
const innerHTML = [];
|
|
364
|
+
innerHTML.push(`<label for="sckan-all-paths">ALL PATHS:</label><input id="sckan-all-paths" type="checkbox" checked/>`);
|
|
365
|
+
for (const state of SCKAN_STATES) {
|
|
366
|
+
innerHTML.push(`<label for="sckan-${state.id}">${state.description}</label><input id="sckan-${state.id}" type="checkbox" checked/>`);
|
|
367
|
+
}
|
|
368
|
+
this.__sckan.innerHTML = innerHTML.join('\n');
|
|
369
|
+
|
|
370
|
+
this.__sckanCount = SCKAN_STATES.length;
|
|
371
|
+
this.__checkedCount = this.__sckanCount;
|
|
372
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
373
|
+
|
|
374
|
+
this.__button = document.createElement('button');
|
|
375
|
+
this.__button.id = 'map-sckan-button';
|
|
376
|
+
this.__button.className = 'control-button text-button';
|
|
377
|
+
this.__button.setAttribute('type', 'button');
|
|
378
|
+
this.__button.setAttribute('aria-label', 'Show/hide valid SCKAN paths');
|
|
379
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
380
|
+
this.__button.textContent = 'SCKAN';
|
|
381
|
+
this.__button.title = 'Show/hide valid SCKAN paths';
|
|
382
|
+
this.__container.appendChild(this.__button);
|
|
383
|
+
|
|
384
|
+
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
385
|
+
return this.__container;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
onRemove()
|
|
389
|
+
//========
|
|
390
|
+
{
|
|
391
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
392
|
+
this.__map = undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
onClick_(event)
|
|
396
|
+
//=============
|
|
397
|
+
{
|
|
398
|
+
if (event.target.id === 'map-sckan-button') {
|
|
399
|
+
if (this.__button.getAttribute('control-visible') === 'false') {
|
|
400
|
+
this.__container.appendChild(this.__sckan);
|
|
401
|
+
this.__button.setAttribute('control-visible', 'true');
|
|
402
|
+
this.__sckan.focus();
|
|
403
|
+
} else {
|
|
404
|
+
this.__sckan = this.__container.removeChild(this.__sckan);
|
|
405
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
406
|
+
}
|
|
407
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
408
|
+
if (event.target.id === 'sckan-all-paths') {
|
|
409
|
+
if (event.target.indeterminate) {
|
|
410
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
411
|
+
event.target.indeterminate = false;
|
|
412
|
+
}
|
|
413
|
+
if (event.target.checked) {
|
|
414
|
+
this.__checkedCount = this.__sckanCount;
|
|
415
|
+
} else {
|
|
416
|
+
this.__checkedCount = 0;
|
|
417
|
+
}
|
|
418
|
+
for (const state of SCKAN_STATES) {
|
|
419
|
+
const sckanCheckbox = document.getElementById(`sckan-${state.id}`);
|
|
420
|
+
if (sckanCheckbox) {
|
|
421
|
+
sckanCheckbox.checked = event.target.checked;
|
|
422
|
+
this.__flatmap.showSckanPaths(state.id, event.target.checked);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} else if (event.target.id.startsWith('sckan-')) {
|
|
426
|
+
const sckanId = event.target.id.substring(6);
|
|
427
|
+
this.__flatmap.showSckanPaths(sckanId, event.target.checked);
|
|
428
|
+
if (event.target.checked) {
|
|
429
|
+
this.__checkedCount += 1;
|
|
430
|
+
} else {
|
|
431
|
+
this.__checkedCount -= 1;
|
|
432
|
+
}
|
|
433
|
+
const allLayersCheckbox = document.getElementById('sckan-all-paths');
|
|
434
|
+
if (this.__checkedCount === 0) {
|
|
435
|
+
allLayersCheckbox.checked = false;
|
|
436
|
+
allLayersCheckbox.indeterminate = false;
|
|
437
|
+
} else if (this.__checkedCount === this.__sckanCount) {
|
|
438
|
+
allLayersCheckbox.checked = true;
|
|
439
|
+
allLayersCheckbox.indeterminate = false;
|
|
440
|
+
} else {
|
|
441
|
+
allLayersCheckbox.indeterminate = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
event.stopPropagation();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class StateControl
|
|
453
|
+
{
|
|
454
|
+
constructor(id, flatmap, states, controlFunction)
|
|
455
|
+
{
|
|
456
|
+
this.__id = id;
|
|
457
|
+
this.__flatmap = flatmap;
|
|
458
|
+
this.__states = states;
|
|
459
|
+
this.__controlFunction = controlFunction;
|
|
460
|
+
this.__map = undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
getDefaultPosition()
|
|
464
|
+
//==================
|
|
465
|
+
{
|
|
466
|
+
return 'top-right';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
onAdd(map)
|
|
470
|
+
//========
|
|
471
|
+
{
|
|
472
|
+
this.__map = map;
|
|
473
|
+
this.__container = document.createElement('div');
|
|
474
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
475
|
+
this.__container.id = 'flatmap-state-control';
|
|
476
|
+
|
|
477
|
+
this.__control = document.createElement('div');
|
|
478
|
+
this.__control.id = `${this.__id}-control-text`;
|
|
479
|
+
this.__control.className = 'flatmap-state-grid';
|
|
480
|
+
|
|
481
|
+
const innerHTML = [];
|
|
482
|
+
innerHTML.push(`<label for="sckan-all-paths">ALL PATHS:</label><input id="sckan-all-paths" type="checkbox" checked/>`);
|
|
483
|
+
for (const state of SCKAN_STATES) {
|
|
484
|
+
innerHTML.push(`<label for="sckan-${state.id}">${state.description}</label><input id="sckan-${state.id}" type="checkbox" checked/>`);
|
|
485
|
+
}
|
|
486
|
+
this.__sckan.innerHTML = innerHTML.join('\n');
|
|
487
|
+
|
|
488
|
+
this.__sckanCount = SCKAN_STATES.length;
|
|
489
|
+
this.__checkedCount = this.__sckanCount;
|
|
490
|
+
this.__halfCount = Math.trunc(this.__checkedCount/2);
|
|
491
|
+
|
|
492
|
+
this.__button = document.createElement('button');
|
|
493
|
+
this.__button.id = 'map-sckan-button';
|
|
494
|
+
this.__button.className = 'control-button text-button';
|
|
495
|
+
this.__button.setAttribute('type', 'button');
|
|
496
|
+
this.__button.setAttribute('aria-label', 'Show/hide valid SCKAN paths');
|
|
497
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
498
|
+
this.__button.textContent = 'SCKAN';
|
|
499
|
+
this.__button.title = 'Show/hide valid SCKAN paths';
|
|
500
|
+
this.__container.appendChild(this.__button);
|
|
501
|
+
|
|
502
|
+
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
503
|
+
return this.__container;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
onRemove()
|
|
507
|
+
//========
|
|
508
|
+
{
|
|
509
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
510
|
+
this.__map = undefined;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
onClick_(event)
|
|
514
|
+
//=============
|
|
515
|
+
{
|
|
516
|
+
if (event.target.id === 'map-sckan-button') {
|
|
517
|
+
if (this.__button.getAttribute('control-visible') === 'false') {
|
|
518
|
+
this.__container.appendChild(this.__sckan);
|
|
519
|
+
this.__button.setAttribute('control-visible', 'true');
|
|
520
|
+
this.__sckan.focus();
|
|
521
|
+
} else {
|
|
522
|
+
this.__sckan = this.__container.removeChild(this.__sckan);
|
|
523
|
+
this.__button.setAttribute('control-visible', 'false');
|
|
524
|
+
}
|
|
525
|
+
} else if (event.target.tagName === 'INPUT') {
|
|
526
|
+
if (event.target.id === 'sckan-all-paths') {
|
|
527
|
+
if (event.target.indeterminate) {
|
|
528
|
+
event.target.checked = (this.__checkedCount >= this.__halfCount);
|
|
529
|
+
event.target.indeterminate = false;
|
|
530
|
+
}
|
|
531
|
+
if (event.target.checked) {
|
|
532
|
+
this.__checkedCount = this.__sckanCount;
|
|
533
|
+
} else {
|
|
534
|
+
this.__checkedCount = 0;
|
|
535
|
+
}
|
|
536
|
+
for (const state of SCKAN_STATES) {
|
|
537
|
+
const sckanCheckbox = document.getElementById(`sckan-${state.id}`);
|
|
538
|
+
if (sckanCheckbox) {
|
|
539
|
+
sckanCheckbox.checked = event.target.checked;
|
|
540
|
+
this.__flatmap.showSckanPaths(state.id, event.target.checked);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} else if (event.target.id.startsWith('sckan-')) {
|
|
544
|
+
const sckanId = event.target.id.substring(6);
|
|
545
|
+
this.__flatmap.showSckanPaths(sckanId, event.target.checked);
|
|
546
|
+
if (event.target.checked) {
|
|
547
|
+
this.__checkedCount += 1;
|
|
548
|
+
} else {
|
|
549
|
+
this.__checkedCount -= 1;
|
|
550
|
+
}
|
|
551
|
+
const allLayersCheckbox = document.getElementById('sckan-all-paths');
|
|
552
|
+
if (this.__checkedCount === 0) {
|
|
553
|
+
allLayersCheckbox.checked = false;
|
|
554
|
+
allLayersCheckbox.indeterminate = false;
|
|
555
|
+
} else if (this.__checkedCount === this.__sckanCount) {
|
|
556
|
+
allLayersCheckbox.checked = true;
|
|
557
|
+
allLayersCheckbox.indeterminate = false;
|
|
558
|
+
} else {
|
|
559
|
+
allLayersCheckbox.indeterminate = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
event.stopPropagation();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
//==============================================================================
|
|
568
|
+
|
|
569
|
+
export class BackgroundControl
|
|
570
|
+
{
|
|
571
|
+
constructor(flatmap)
|
|
572
|
+
{
|
|
573
|
+
this.__flatmap = flatmap;
|
|
574
|
+
this.__map = undefined;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
getDefaultPosition()
|
|
578
|
+
//==================
|
|
579
|
+
{
|
|
580
|
+
return 'bottom-right';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
onAdd(map)
|
|
584
|
+
//========
|
|
585
|
+
{
|
|
586
|
+
this.__map = map;
|
|
587
|
+
this.__container = document.createElement('div');
|
|
588
|
+
this.__container.className = 'maplibregl-ctrl';
|
|
589
|
+
this.__colourDiv = document.createElement('div');
|
|
590
|
+
this.__colourDiv.setAttribute('aria-label', 'Change background colour');
|
|
591
|
+
this.__colourDiv.title = 'Change background colour';
|
|
592
|
+
const background = standardise_color(this.__flatmap.getBackgroundColour());
|
|
593
|
+
this.__colourDiv.innerHTML = `<input type="color" id="colourPicker" value="${background}">`;
|
|
594
|
+
this.__container.appendChild(this.__colourDiv);
|
|
595
|
+
this.__colourDiv.addEventListener('input', this.__updateColour.bind(this), false);
|
|
596
|
+
this.__colourDiv.addEventListener('change', this.__updateColour.bind(this), false);
|
|
597
|
+
return this.__container;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
onRemove()
|
|
601
|
+
//========
|
|
602
|
+
{
|
|
603
|
+
this.__container.parentNode.removeChild(this.__container);
|
|
604
|
+
this.__map = undefined;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
__updateColour(event)
|
|
608
|
+
//===================
|
|
609
|
+
{
|
|
610
|
+
const colour = event.target.value;
|
|
611
|
+
this.__flatmap.setBackgroundColour(colour);
|
|
612
|
+
this.__flatmap.controlEvent('change', 'background', colour)
|
|
613
|
+
event.stopPropagation();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
//==============================================================================
|