@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.
@@ -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 './flatmap-viewer';
30
- import {PATH_STYLE_RULES} from './pathways';
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
@@ -62,6 +62,7 @@ export async function standaloneViewer(map_endpoint=null, options={})
62
62
  showId: true,
63
63
  showPosition: false,
64
64
  standalone: true,
65
+ annotator: true,
65
66
  }, options);
66
67
 
67
68
  function loadMap(id, taxon, sex)
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 const PATH_COLOURS =
56
- Object.fromEntries(PATH_TYPES.flatMap(pathType => [[pathType.type, pathType.colour]]));
57
-
58
- export function pathColour(pathType)
59
+ export function pathColourArray(pathType, alpha=255)
60
+ //==================================================
59
61
  {
60
- return PATH_COLOURS[pathType] || '#FF0';
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
  //==============================================================================