From 2bcdee03d5e338315e59c9680504e31343ee54f7 Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Sat, 2 Aug 2025 16:00:54 +0200 Subject: [PATCH] Further refactor Map component. Map.sequencesBinaryData is now a single object instead of an array of objects. DougalSequenceLayer has been greatly simplified. It now inherits from ScatterplotLayer rather than CompositeLayer. DougalEventsLayer added. It shows either a ScatteplotLayer or a ColumnsLayer depending on zoom level. --- .../src/lib/deck.gl/DougalEventsLayer.js | 142 +++++++ .../src/lib/deck.gl/DougalSequenceLayer.js | 173 ++------ .../client/source/src/lib/deck.gl/index.js | 4 +- lib/www/client/source/src/views/Map.vue | 370 +++++------------- .../source/src/views/MapLayersMixin.vue | 170 ++++++-- 5 files changed, 421 insertions(+), 438 deletions(-) create mode 100644 lib/www/client/source/src/lib/deck.gl/DougalEventsLayer.js diff --git a/lib/www/client/source/src/lib/deck.gl/DougalEventsLayer.js b/lib/www/client/source/src/lib/deck.gl/DougalEventsLayer.js new file mode 100644 index 0000000..b128e05 --- /dev/null +++ b/lib/www/client/source/src/lib/deck.gl/DougalEventsLayer.js @@ -0,0 +1,142 @@ +// Ref.: https://deck.gl/docs/developer-guide/custom-layers/composite-layers +import { CompositeLayer } from '@deck.gl/core'; +import { GeoJsonLayer, ColumnLayer } from '@deck.gl/layers'; + +class DougalEventsLayer extends CompositeLayer { + static layerName = "DougalEventsLayer"; + + static defaultProps = { + columnsZoom: 11, // Threshold zoom level for switching layers + jitter: 0, // Add a small amount of jitter so that columns do not overlap. + // GeoJsonLayer props + getLineColor: [127, 65, 90], + getFillColor: [127, 65, 90], + getPointRadius: 2, + radiusUnits: "pixels", + pointRadiusMinPixels: 2, + lineWidthMinPixels: 2, + // ColumnLayer props + getPosition: { type: 'accessor', value: d => d.geometry.coordinates }, + getElevation: { type: 'accessor', value: d => Math.min(Math.max(d.properties.remarks?.length || 10, 10), 200) }, + diskResolution: 20, + radius: 5, + radiusUnits: "pixels", + radiusScale: 1, + elevationScale: 1, + filled: true, + stroked: false, + extruded: true, + wireframe: false, + material: true, + getFillColor: [255, 0, 0, 200], + getLineColor: [255, 0, 0, 200], + getLineWidth: 2, + pickable: true + } + + constructor(props) { + super(props); + this.uid = "el-" + Math.random().toString().slice(2); + // Initialize state with current zoom + this.state = { + zoom: this.context?.viewport?.zoom || 0 + }; + } + + shouldUpdateState({ changeFlags }) { + // Always update if viewport changed (including zoom) + if (changeFlags.viewportChanged) { + return true; + } + return super.shouldUpdateState({ changeFlags }); + } + + updateState({ props, oldProps, context, changeFlags }) { + // Check if zoom has changed + const newZoom = context.viewport?.zoom || 0; + if (newZoom !== this.state.zoom) { + this.setState({ zoom: newZoom }); + this.setNeedsRedraw(); // Trigger re-render of sublayers + console.log(`Zoom changed to ${newZoom}, triggering redraw`); + } + } + + getPickingInfo({ info, mode, sourceLayer }) { + if (info.index >= 0) { + info.object = { + type: sourceLayer.constructor.layerName, + ...info.object // Merge default picking info (GeoJSON feature or ColumnLayer object) + }; + //console.log(`Picked ${info.object.type}, index ${info.index}`); + } + return info; + } + + renderLayers() { + const { zoom } = this.state; + const sublayers = []; + + if (zoom >= this.props.columnsZoom) { + // Render ColumnLayer at high zoom + const data = Array.isArray(this.props.data) ? this.props.data : this.props.data.features || []; + + const positionFn = this.props.jitter + ? (d, info) => { + let pos; + if (typeof this.props.getPosition == 'function') { + pos = this.props.getPosition(d, info); + } else { + pos = this.props.getPosition; + } + return pos.map( i => i + (Math.random() - 0.5) * this.props.jitter ) + } + : this.props.getPosition; + + sublayers.push( + new ColumnLayer(this.getSubLayerProps({ + id: `${this.uid}-column`, + data, + visible: this.props.visible, + getPosition: positionFn, + getElevation: this.props.getElevation, + diskResolution: this.props.diskResolution, + radius: this.props.radius, + radiusUnits: this.props.radiusUnits, + radiusScale: this.props.radiusScale, + elevationScale: this.props.elevationScale, + filled: this.props.filled, + stroked: this.props.stroked, + extruded: this.props.extruded, + wireframe: this.props.wireframe, + material: this.props.material, + getFillColor: this.props.getFillColor, + getLineColor: this.props.getLineColor, + getLineWidth: this.props.getLineWidth, + pickable: this.props.pickable + })) + ); + } else { + // Render GeoJsonLayer at low zoom + sublayers.push( + new GeoJsonLayer(this.getSubLayerProps({ + id: `${this.uid}-geojson`, + data: this.props.data, + visible: this.props.visible, + getLineColor: this.props.getLineColor, + getFillColor: this.props.getFillColor, + getPointRadius: this.props.getPointRadius, + radiusUnits: this.props.radiusUnits, + pointRadiusMinPixels: this.props.pointRadiusMinPixels, + lineWidthMinPixels: this.props.lineWidthMinPixels, + pickable: this.props.pickable + })) + ); + } + + console.log(`Rendering ${sublayers.length} sublayer(s) at zoom ${zoom}`); + + return sublayers; + } +} + +export default DougalEventsLayer; diff --git a/lib/www/client/source/src/lib/deck.gl/DougalSequenceLayer.js b/lib/www/client/source/src/lib/deck.gl/DougalSequenceLayer.js index 6dd3d01..0519f2a 100644 --- a/lib/www/client/source/src/lib/deck.gl/DougalSequenceLayer.js +++ b/lib/www/client/source/src/lib/deck.gl/DougalSequenceLayer.js @@ -1,12 +1,11 @@ -// Ref.: https://deck.gl/docs/developer-guide/custom-layers/composite-layers -import { CompositeLayer } from '@deck.gl/core'; -import { GeoJsonLayer, LineLayer, BitmapLayer, ScatterplotLayer, IconLayer } from '@deck.gl/layers'; -import {HeatmapLayer} from '@deck.gl/aggregation-layers'; +// Ref.: https://deck.gl/docs/developer-guide/custom-layers/layer-lifecycle +import { ScatterplotLayer } from '@deck.gl/layers'; -class DougalSequenceLayer extends CompositeLayer { +class DougalSequenceLayer extends ScatterplotLayer { static layerName = "DougalSequenceLayer"; static defaultProps = { + ...ScatterplotLayer.defaultProps, valueIndex: 0, radiusUnits: "pixels", radiusScale: 1, @@ -18,161 +17,59 @@ class DougalSequenceLayer extends CompositeLayer { radiusMaxPixels: 50, lineWidthMinPixels: 1, lineWidthMaxPixels: 50, - // billboard: this.props.billboard, - // antialiasing: this.props.antialiasing, - getPosition: {type: 'accessor', value: d => d.positions}, + getPosition: { type: 'accessor', value: d => d.positions }, getRadius: 5, - //getColor: this.props.getColor, getFillColor: [255, 0, 0, 200], getLineColor: [255, 0, 0, 200], getLineWidth: 2, pickable: true } - constructor (props) { + constructor(props) { super(props); - - // A unique id (prefix) for this layer - this.uid = "sl-" + Math.random().toString().slice(2); } - getPickingInfo({ info, mode, sourceLayer }) { - console.log("picking info", info); + initializeState(context) { + super.initializeState(context); + } + + getPickingInfo({ info, mode }) { const index = info.index; if (index >= 0) { - const d = sourceLayer.props.data.attributes; - console.log(index, d); - + const d = this.props.data.attributes; if (d) { info.object = { - udv: d.udv ?? 2, // FIXME must add - i: d.value0.value[index], - j: d.value1.value[index], + udv: d.udv ?? 2, + i: d.value0.value[index], + j: d.value1.value[index], ts: Number(d.value2.value[index]), - //λ: d.value3[k*2+0], - //φ: d.value3[k*2+1], - εi: d.value3.value[index] / 10, - εj: d.value4.value[index] / 10, - delta_μ: d.value5.value[index] / 10, - delta_σ: d.value6.value[index] / 10, - delta_R: d.value7.value[index] / 10, - press_μ: d.value8.value[index], - press_σ: d.value9.value[index], - press_R: d.value10.value[index], - depth_μ: d.value11.value[index] / 10, - depth_σ: d.value12.value[index] / 10, - depth_R: d.value13.value[index] / 10, - fill_μ: d.value14.value[index], - fill_σ: d.value15.value[index], - fill_R: d.value16.value[index], - delay_μ: d.value17.value[index] / 10, - delay_σ: d.value18.value[index] / 10, - delay_R: d.value19.value[index] / 10, - /* - nofire: d.value20.value[index] >> 4, + εi: d.value3.value[index] / 10, + εj: d.value4.value[index] / 10, + delta_μ: d.value5.value[index] / 10, + delta_σ: d.value6.value[index] / 10, + delta_R: d.value7.value[index] / 10, + press_μ: d.value8.value[index], + press_σ: d.value9.value[index], + press_R: d.value10.value[index], + depth_μ: d.value11.value[index] / 10, + depth_σ: d.value12.value[index] / 10, + depth_R: d.value13.value[index] / 10, + fill_μ: d.value14.value[index], + fill_σ: d.value15.value[index], + fill_R: d.value16.value[index], + delay_μ: d.value17.value[index] / 10, + delay_σ: d.value18.value[index] / 10, + delay_R: d.value19.value[index] / 10, + nofire: d.value20.value[index] >> 4, autofire: d.value20.value[index] & 0xf - */ - } + }; + console.log(`Picked sequence ${info.object.i}, point ${info.object.j}, udv ${info.object.udv}`); } else { console.log(`No data found index = ${index}`); } } - return info; } - - renderLayers () { - // return [ - // // List of sublayers. One per sequence (+ one per data item?) - // ] - - - const subLayers = []; - let count=0; - for (const {positions, values} of this.props.data) { - const length = positions.length / 2; - - // CHANGE: Build binary attributes object - const attributes = { - getPosition: { - value: positions, - type: "float32", - size: 2 - } - }; - - // CHANGE: Add each TypedArray in values as a custom attribute 'value0', 'value1', etc. - // Note: If BigUint64Array or unsupported types, convert here (e.g., to Float64Array for timestamps) - // Example: if (values[k] instanceof BigUint64Array) values[k] = Float64Array.from(values[k], BigInt => Number(BigInt)); - values.forEach((valArray, k) => { - attributes[`value${k}`] = { - value: valArray, - size: 1 - }; - }); - - const subLayer = new ScatterplotLayer(this.getSubLayerProps({ - id: `${this.uid}-${count++}-scatter`, - data: { - length, - attributes - }, - radiusUnits: this.props.radiusUnits, - radiusScale: this.props.radiusScale, - lineWidthUnits: this.props.lineWidthUnits, - lineWidthScale: this.props.lineWidthScale, - stroked: this.props.stroked, - filled: this.props.filled, - radiusMinPixels: this.props.radiusMinPixels, - radiusMaxPixels: this.props.radiusMaxPixels, - lineWidthMinPixels: this.props.lineWidthMinPixels, - lineWidthMaxPixels: this.props.lineWidthMaxPixels, - billboard: this.props.billboard, - antialiasing: this.props.antialiasing, - getPosition: this.props.getPosition, // CHANGE: Default to d => d.positions; user can override with function accessing d.valueX - getRadius: this.props.getRadius, // CHANGE: Can now be function d => d.value0 (or string 'value0' for direct mapping) - //getColor: this.props.getColor, - getFillColor: this.props.getFillColor ?? this.props.getColor, // CHANGE: Can now be function d => d.value1 * ... - getLineColor: this.props.getLineColor, - getLineWidth: this.props.getLineWidth, - - updateTriggers: { - getPosition: this.props.updateTriggers.getPosition, - getRadius: this.props.updateTriggers.getRadius, - getColor: this.props.updateTriggers.getColor, - getFillColor: this.props.updateTriggers.getFillColor, - getLineColor: this.props.updateTriggers.getLineColor, - getLineWidth: this.props.updateTriggers.getLineWidth, - } - })); - - subLayers.push(subLayer); - } - - // console.log(`Rendering ${subLayers.length} sublayers`); - - return subLayers; - } - } -/* - -function GunPressureAvgHeatmap (data, options = {}) { - let id = options.id - ? options.id - : "gun-pressure-avg-heatmap"+Math.random().toString().slice(2); - - return new HeatmapLayer({ - id, - data: { - length, - attributes: { - getPosition, - getWeight - } - } - }) -}*/ - export default DougalSequenceLayer; diff --git a/lib/www/client/source/src/lib/deck.gl/index.js b/lib/www/client/source/src/lib/deck.gl/index.js index 00e6f58..b795b76 100644 --- a/lib/www/client/source/src/lib/deck.gl/index.js +++ b/lib/www/client/source/src/lib/deck.gl/index.js @@ -1,6 +1,8 @@ import DougalSequenceLayer from './DougalSequenceLayer' +import DougalEventsLayer from './DougalEventsLayer' export { - DougalSequenceLayer + DougalSequenceLayer, + DougalEventsLayer }; diff --git a/lib/www/client/source/src/views/Map.vue b/lib/www/client/source/src/views/Map.vue index 1e03b12..bec1e56 100644 --- a/lib/www/client/source/src/views/Map.vue +++ b/lib/www/client/source/src/views/Map.vue @@ -96,15 +96,6 @@ -