@abi-software/flatmap-viewer 2.5.0-a.1 → 2.5.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/package.json +3 -2
- package/src/controls/annotation.js +351 -0
- package/src/controls/controls.js +68 -0
- package/src/controls/minimap.js +3 -8
- package/src/controls/paths3d.js +90 -0
- package/src/flatmap-viewer.js +68 -35
- package/src/interactions.js +188 -138
- package/src/{layers.js → layers/index.js} +2 -2
- package/src/layers/paths3d.js +335 -0
- package/src/{styling.js → layers/styling.js} +2 -2
- package/src/main.js +1 -0
- package/src/pathways.js +62 -5
|
@@ -0,0 +1,335 @@
|
|
|
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
|
+
import {ArcLayer} from '@deck.gl/layers'
|
|
22
|
+
import {MapboxOverlay as DeckOverlay} from '@deck.gl/mapbox'
|
|
23
|
+
import {Model, Geometry} from '@luma.gl/core'
|
|
24
|
+
import GL from '@luma.gl/constants'
|
|
25
|
+
|
|
26
|
+
//==============================================================================
|
|
27
|
+
|
|
28
|
+
import {pathColourArray} from '../pathways'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
//==============================================================================
|
|
32
|
+
|
|
33
|
+
const transparencyCheck = '|| length(vColor) == 0.0'
|
|
34
|
+
|
|
35
|
+
class ArcMapLayer extends ArcLayer
|
|
36
|
+
{
|
|
37
|
+
static layerName = 'ArcMapLayer'
|
|
38
|
+
|
|
39
|
+
#dirty = false
|
|
40
|
+
#pathData
|
|
41
|
+
|
|
42
|
+
constructor(...args)
|
|
43
|
+
{
|
|
44
|
+
super(...args)
|
|
45
|
+
this.#pathData = new Map([...this.props.data].map(ann => [+ann.featureId, ann]))
|
|
46
|
+
this.#pathData.forEach(ann => delete ann['hidden'])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get featureIds()
|
|
50
|
+
//==============
|
|
51
|
+
{
|
|
52
|
+
return [...this.#pathData.keys()]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getShaders()
|
|
56
|
+
//==========
|
|
57
|
+
{
|
|
58
|
+
const shaders = super.getShaders()
|
|
59
|
+
shaders.fs = `#version 300 es\n${shaders.fs}`
|
|
60
|
+
.replace('isValid == 0.0', `isValid == 0.0 ${transparencyCheck}`)
|
|
61
|
+
shaders.vs = `#version 300 es\n${shaders.vs}`
|
|
62
|
+
return shaders
|
|
63
|
+
}
|
|
64
|
+
setDataProperty(featureId, key, enabled)
|
|
65
|
+
//======================================
|
|
66
|
+
{
|
|
67
|
+
const properties = this.#pathData.get(+featureId)
|
|
68
|
+
if (properties) {
|
|
69
|
+
if (!(key in properties) || properties[key] !== enabled) {
|
|
70
|
+
properties[key] = enabled
|
|
71
|
+
this.#dirty = true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
redraw(force=false)
|
|
77
|
+
//=================
|
|
78
|
+
{
|
|
79
|
+
if (force || this.#dirty) {
|
|
80
|
+
this.internalState.changeFlags.dataChanged = true
|
|
81
|
+
this.setNeedsUpdate()
|
|
82
|
+
this.#dirty = false
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//==============================================================================
|
|
88
|
+
|
|
89
|
+
const makeDashedTriangles = ` float alpha = floor(fract(float(gl_VertexID)/12.0)+0.5);
|
|
90
|
+
if (vColor.a != 0.0) vColor.a *= alpha;
|
|
91
|
+
`
|
|
92
|
+
|
|
93
|
+
class ArcDashedLayer extends ArcMapLayer
|
|
94
|
+
{
|
|
95
|
+
static layerName = 'ArcDashedLayer'
|
|
96
|
+
|
|
97
|
+
constructor(...args)
|
|
98
|
+
{
|
|
99
|
+
super(...args)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getShaders()
|
|
103
|
+
//==========
|
|
104
|
+
{
|
|
105
|
+
const shaders = super.getShaders()
|
|
106
|
+
shaders.vs = shaders.vs.replace('DECKGL_FILTER_COLOR(', `${makeDashedTriangles}\n DECKGL_FILTER_COLOR(`)
|
|
107
|
+
return shaders
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_getModel(gl)
|
|
111
|
+
//===========
|
|
112
|
+
{
|
|
113
|
+
const {numSegments} = this.props
|
|
114
|
+
let positions = []
|
|
115
|
+
for (let i = 0; i < numSegments; i++) {
|
|
116
|
+
positions = positions.concat([i, 1, 0, i, -1, 0, i+1, 1, 0,
|
|
117
|
+
i, -1, 0, i+1, 1, 0, i+1, -1, 0])
|
|
118
|
+
}
|
|
119
|
+
const model = new Model(gl, {
|
|
120
|
+
...this.getShaders(),
|
|
121
|
+
id: this.props.id,
|
|
122
|
+
geometry: new Geometry({
|
|
123
|
+
drawMode: GL.TRIANGLES,
|
|
124
|
+
attributes: {
|
|
125
|
+
positions: new Float32Array(positions)
|
|
126
|
+
}
|
|
127
|
+
}),
|
|
128
|
+
isInstanced: true,
|
|
129
|
+
})
|
|
130
|
+
model.setUniforms({numSegments: numSegments})
|
|
131
|
+
return model
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//==============================================================================
|
|
136
|
+
|
|
137
|
+
export class Paths3DLayer
|
|
138
|
+
{
|
|
139
|
+
#arcLayers = new Map()
|
|
140
|
+
#deckOverlay = null
|
|
141
|
+
#dimmed = false
|
|
142
|
+
#enabled = false
|
|
143
|
+
#featureToLayer = new Map()
|
|
144
|
+
#knownTypes = []
|
|
145
|
+
#map
|
|
146
|
+
#pathData
|
|
147
|
+
#pathManager
|
|
148
|
+
#pathStyles
|
|
149
|
+
#ui
|
|
150
|
+
|
|
151
|
+
constructor(flatmap, ui)
|
|
152
|
+
{
|
|
153
|
+
this.#ui = ui
|
|
154
|
+
this.#map = flatmap.map
|
|
155
|
+
this.#pathManager = ui.pathManager
|
|
156
|
+
this.#pathManager.addWatcher(this.#pathStateChanged.bind(this))
|
|
157
|
+
this.#pathData = new Map([...flatmap.annotations.values()]
|
|
158
|
+
.filter(ann => ann['tile-layer'] === 'pathways'
|
|
159
|
+
&& ann['geometry'] === 'LineString'
|
|
160
|
+
&& 'type' in ann && ann['type'].startsWith('line')
|
|
161
|
+
&& 'kind' in ann
|
|
162
|
+
&& 'pathStartPosition' in ann
|
|
163
|
+
&& 'pathEndPosition' in ann)
|
|
164
|
+
.map(ann => [ann.featureId, ann]))
|
|
165
|
+
this.#pathStyles = new Map(this.#pathManager.pathStyles().map(s => [s.type, s]))
|
|
166
|
+
this.#knownTypes = [...this.#pathStyles.keys()].filter(t => t !== 'other')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
enable(enable=true)
|
|
170
|
+
//=================
|
|
171
|
+
{
|
|
172
|
+
if (enable && !this.#enabled) {
|
|
173
|
+
this.#setupDeckOverlay()
|
|
174
|
+
this.#map.addControl(this.#deckOverlay)
|
|
175
|
+
} else if (!enable && this.#enabled) {
|
|
176
|
+
if (this.#deckOverlay) {
|
|
177
|
+
this.#map.removeControl(this.#deckOverlay)
|
|
178
|
+
this.#deckOverlay.finalize()
|
|
179
|
+
this.#deckOverlay = null
|
|
180
|
+
}
|
|
181
|
+
this.#featureToLayer = new Map()
|
|
182
|
+
}
|
|
183
|
+
this.#enabled = enable
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
queryFeaturesAtPoint(point)
|
|
187
|
+
//=========================
|
|
188
|
+
{
|
|
189
|
+
if (this.#deckOverlay) {
|
|
190
|
+
return this.#deckOverlay
|
|
191
|
+
.pickMultipleObjects(point)
|
|
192
|
+
.map(o => this.#makeMapFeature(o.object))
|
|
193
|
+
}
|
|
194
|
+
return []
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
redraw(force=false)
|
|
198
|
+
//=================
|
|
199
|
+
{
|
|
200
|
+
for (const layer of this.#arcLayers.values()) {
|
|
201
|
+
layer.redraw(force)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
removeFeatureState(featureId, key)
|
|
206
|
+
//================================
|
|
207
|
+
{
|
|
208
|
+
const layer = this.#featureToLayer.get(+featureId)
|
|
209
|
+
if (layer) {
|
|
210
|
+
layer.setDataProperty(featureId, key, false)
|
|
211
|
+
layer.redraw()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setFeatureState(featureId, state)
|
|
216
|
+
//===============================
|
|
217
|
+
{
|
|
218
|
+
const layer = this.#featureToLayer.get(+featureId)
|
|
219
|
+
if (layer) {
|
|
220
|
+
for (const [key, value] of Object.entries(state)) {
|
|
221
|
+
layer.setDataProperty(featureId, key, value)
|
|
222
|
+
}
|
|
223
|
+
layer.redraw()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setPaint(options)
|
|
228
|
+
//===============
|
|
229
|
+
{
|
|
230
|
+
const dimmed = options.dimmed || false
|
|
231
|
+
if (this.#dimmed !== dimmed) {
|
|
232
|
+
this.#dimmed = dimmed
|
|
233
|
+
this.redraw(true)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#addArcLayer(pathType)
|
|
238
|
+
//====================
|
|
239
|
+
{
|
|
240
|
+
const layer = this.#pathStyles.get(pathType).dashed
|
|
241
|
+
? new ArcDashedLayer(this.#layerOptions(pathType))
|
|
242
|
+
: new ArcMapLayer(this.#layerOptions(pathType))
|
|
243
|
+
layer.featureIds.forEach(id => this.#featureToLayer.set(+id, layer))
|
|
244
|
+
this.#arcLayers.set(pathType, layer)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#removeArcLayer(pathType)
|
|
248
|
+
//=======================
|
|
249
|
+
{
|
|
250
|
+
const layer = this.#arcLayers.get(pathType)
|
|
251
|
+
if (layer) {
|
|
252
|
+
layer.featureIds.forEach(id => this.#featureToLayer.delete(+id))
|
|
253
|
+
this.#arcLayers.delete(pathType)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#pathColour(properties)
|
|
258
|
+
//=====================
|
|
259
|
+
{
|
|
260
|
+
if (properties.hidden) {
|
|
261
|
+
return [0, 0, 0, 0]
|
|
262
|
+
}
|
|
263
|
+
return pathColourArray(properties.kind,
|
|
264
|
+
properties.active || properties.selected ? 255
|
|
265
|
+
: this.#dimmed ? 20 : 160)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#pathStateChanged(changes={})
|
|
269
|
+
//===========================
|
|
270
|
+
{
|
|
271
|
+
if (this.#deckOverlay) {
|
|
272
|
+
if ('pathType' in changes) {
|
|
273
|
+
const pathType = changes.pathType
|
|
274
|
+
const enabled = this.#pathManager.pathTypeEnabled(pathType)
|
|
275
|
+
if (enabled && !this.#arcLayers.has(pathType)) {
|
|
276
|
+
this.#addArcLayer(pathType)
|
|
277
|
+
} else if (!enabled && this.#arcLayers.has(pathType)) {
|
|
278
|
+
this.#removeArcLayer(pathType)
|
|
279
|
+
}
|
|
280
|
+
this.#deckOverlay.setProps({
|
|
281
|
+
layers: [...this.#arcLayers.values()]
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
#layerOptions(pathType)
|
|
289
|
+
//=====================
|
|
290
|
+
{
|
|
291
|
+
const pathData = [...this.#pathData.values()]
|
|
292
|
+
.filter(ann => (this.#knownTypes.includes(ann.kind) && (ann.kind === pathType)
|
|
293
|
+
|| !this.#knownTypes.includes(ann.kind) && (pathType === 'other')))
|
|
294
|
+
return {
|
|
295
|
+
id: `arc-${pathType}`,
|
|
296
|
+
data: pathData,
|
|
297
|
+
pickable: true,
|
|
298
|
+
autoHighlight: true,
|
|
299
|
+
numSegments: 400,
|
|
300
|
+
// Styles
|
|
301
|
+
getSourcePosition: f => f.pathStartPosition,
|
|
302
|
+
getTargetPosition: f => f.pathEndPosition,
|
|
303
|
+
getSourceColor: this.#pathColour.bind(this),
|
|
304
|
+
getTargetColor: this.#pathColour.bind(this),
|
|
305
|
+
highlightColor: o => this.#pathColour(o.object),
|
|
306
|
+
opacity: 1.0,
|
|
307
|
+
getWidth: 3,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#makeMapFeature(pickedObject)
|
|
312
|
+
//===========================
|
|
313
|
+
{
|
|
314
|
+
// Mock up a map vector feature
|
|
315
|
+
return {
|
|
316
|
+
id: pickedObject.featureId,
|
|
317
|
+
source: 'vector-tiles',
|
|
318
|
+
sourceLayer: `${pickedObject.layer}_${pickedObject['tile-layer']}`,
|
|
319
|
+
properties: pickedObject,
|
|
320
|
+
arc3dLayer: true
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
#setupDeckOverlay()
|
|
325
|
+
//=================
|
|
326
|
+
{
|
|
327
|
+
[...this.#pathStyles.values()].filter(style => this.#pathManager.pathTypeEnabled(style.type))
|
|
328
|
+
.forEach(style => this.#addArcLayer(style.type))
|
|
329
|
+
this.#deckOverlay = new DeckOverlay({
|
|
330
|
+
layers: [...this.#arcLayers.values()],
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
//==============================================================================
|
|
@@ -26,8 +26,8 @@ export const VECTOR_TILES_SOURCE = 'vector-tiles';
|
|
|
26
26
|
|
|
27
27
|
//==============================================================================
|
|
28
28
|
|
|
29
|
-
import {UNCLASSIFIED_TAXON_ID} from '
|
|
30
|
-
import {PATH_STYLE_RULES} from '
|
|
29
|
+
import {UNCLASSIFIED_TAXON_ID} from '../flatmap-viewer';
|
|
30
|
+
import {PATH_STYLE_RULES} from '../pathways';
|
|
31
31
|
|
|
32
32
|
//==============================================================================
|
|
33
33
|
|
package/src/main.js
CHANGED
package/src/pathways.js
CHANGED
|
@@ -20,6 +20,8 @@ limitations under the License.
|
|
|
20
20
|
|
|
21
21
|
'use strict';
|
|
22
22
|
|
|
23
|
+
import {colord} from 'colord'
|
|
24
|
+
|
|
23
25
|
//==============================================================================
|
|
24
26
|
|
|
25
27
|
import { reverseMap } from './utils';
|
|
@@ -49,15 +51,18 @@ const PATH_TYPES = [
|
|
|
49
51
|
{ type: "error", label: "Paths with errors or warnings", colour: "#FF0", enabled: false}
|
|
50
52
|
];
|
|
51
53
|
|
|
54
|
+
const PathTypeMap = new Map(PATH_TYPES.map(t => [t.type, t]))
|
|
55
|
+
|
|
52
56
|
export const PATH_STYLE_RULES =
|
|
53
57
|
PATH_TYPES.flatMap(pathType => [['==', ['get', 'kind'], pathType.type], pathType.colour]);
|
|
54
58
|
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
export function pathColour(pathType)
|
|
59
|
+
export function pathColourArray(pathType, alpha=255)
|
|
60
|
+
//==================================================
|
|
59
61
|
{
|
|
60
|
-
|
|
62
|
+
const rgb = colord(PathTypeMap.has(pathType)
|
|
63
|
+
? PathTypeMap.get(pathType).colour
|
|
64
|
+
: '#FF0').toRgb()
|
|
65
|
+
return [rgb.r, rgb.g, rgb.b, alpha]
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
//==============================================================================
|
|
@@ -162,6 +167,21 @@ export class PathManager
|
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
|
|
170
|
+
pathStyles()
|
|
171
|
+
//==========
|
|
172
|
+
{
|
|
173
|
+
const styles = []
|
|
174
|
+
for (const mapType of this.pathTypes()) {
|
|
175
|
+
const defn = PathTypeMap.get(mapType.type)
|
|
176
|
+
styles.push({
|
|
177
|
+
type: defn.type,
|
|
178
|
+
colour: defn.colour,
|
|
179
|
+
dashed: defn.dashed || false
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
return styles
|
|
183
|
+
}
|
|
184
|
+
|
|
165
185
|
pathTypes()
|
|
166
186
|
//=========
|
|
167
187
|
{
|
|
@@ -299,6 +319,7 @@ export class PathManager
|
|
|
299
319
|
enablePathsBySystem(system, enable, force=false)
|
|
300
320
|
//==============================================
|
|
301
321
|
{
|
|
322
|
+
let changed = false;
|
|
302
323
|
for (const pathId of system.pathIds) {
|
|
303
324
|
const path = this.__paths[pathId];
|
|
304
325
|
if (this.__pathtypeEnabled[path.pathType]
|
|
@@ -311,6 +332,7 @@ export class PathManager
|
|
|
311
332
|
for (const featureId of featureIds) {
|
|
312
333
|
this.__ui.enableFeature(featureId, enable, force);
|
|
313
334
|
}
|
|
335
|
+
changed = true
|
|
314
336
|
}
|
|
315
337
|
path.systemCount += (enable ? 1 : -1);
|
|
316
338
|
if (path.systemCount < 0) {
|
|
@@ -318,6 +340,9 @@ export class PathManager
|
|
|
318
340
|
}
|
|
319
341
|
// TODO? Show connectors and parent components of these paths??
|
|
320
342
|
}
|
|
343
|
+
if (changed) {
|
|
344
|
+
this.#notifyWatchers()
|
|
345
|
+
}
|
|
321
346
|
}
|
|
322
347
|
|
|
323
348
|
enablePathsByType(pathType, enable, force=false)
|
|
@@ -330,9 +355,16 @@ export class PathManager
|
|
|
330
355
|
this.__ui.enableFeature(featureId, enable, force);
|
|
331
356
|
}
|
|
332
357
|
this.__pathtypeEnabled[pathType] = enable;
|
|
358
|
+
this.#notifyWatchers({pathType})
|
|
333
359
|
}
|
|
334
360
|
}
|
|
335
361
|
|
|
362
|
+
pathTypeEnabled(pathType)
|
|
363
|
+
//=======================
|
|
364
|
+
{
|
|
365
|
+
return this.__pathtypeEnabled[pathType] || false
|
|
366
|
+
}
|
|
367
|
+
|
|
336
368
|
nodePathModels(nodeId)
|
|
337
369
|
//====================
|
|
338
370
|
{
|
|
@@ -360,6 +392,31 @@ export class PathManager
|
|
|
360
392
|
}
|
|
361
393
|
return nodeIds;
|
|
362
394
|
}
|
|
395
|
+
|
|
396
|
+
#lastWatcherId = 0
|
|
397
|
+
#watcherCallbacks = new Map()
|
|
398
|
+
|
|
399
|
+
addWatcher(callback)
|
|
400
|
+
//==================
|
|
401
|
+
{
|
|
402
|
+
this.#lastWatcherId += 1
|
|
403
|
+
this.#watcherCallbacks.set(this.#lastWatcherId, callback)
|
|
404
|
+
return this.#lastWatcherId
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
removeWatcher(watcherId)
|
|
408
|
+
//======================
|
|
409
|
+
{
|
|
410
|
+
this.#watcherCallbacks.delete(watcherId)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#notifyWatchers(changes={})
|
|
414
|
+
//=========================
|
|
415
|
+
{
|
|
416
|
+
for (const callback of this.#watcherCallbacks.values()) {
|
|
417
|
+
callback(changes)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
363
420
|
}
|
|
364
421
|
|
|
365
422
|
//==============================================================================
|