mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 08:37:07 +00:00
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.
This commit is contained in:
142
lib/www/client/source/src/lib/deck.gl/DougalEventsLayer.js
Normal file
142
lib/www/client/source/src/lib/deck.gl/DougalEventsLayer.js
Normal file
@@ -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;
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
// Ref.: https://deck.gl/docs/developer-guide/custom-layers/composite-layers
|
// Ref.: https://deck.gl/docs/developer-guide/custom-layers/layer-lifecycle
|
||||||
import { CompositeLayer } from '@deck.gl/core';
|
import { ScatterplotLayer } from '@deck.gl/layers';
|
||||||
import { GeoJsonLayer, LineLayer, BitmapLayer, ScatterplotLayer, IconLayer } from '@deck.gl/layers';
|
|
||||||
import {HeatmapLayer} from '@deck.gl/aggregation-layers';
|
|
||||||
|
|
||||||
class DougalSequenceLayer extends CompositeLayer {
|
class DougalSequenceLayer extends ScatterplotLayer {
|
||||||
static layerName = "DougalSequenceLayer";
|
static layerName = "DougalSequenceLayer";
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
...ScatterplotLayer.defaultProps,
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
radiusUnits: "pixels",
|
radiusUnits: "pixels",
|
||||||
radiusScale: 1,
|
radiusScale: 1,
|
||||||
@@ -18,161 +17,59 @@ class DougalSequenceLayer extends CompositeLayer {
|
|||||||
radiusMaxPixels: 50,
|
radiusMaxPixels: 50,
|
||||||
lineWidthMinPixels: 1,
|
lineWidthMinPixels: 1,
|
||||||
lineWidthMaxPixels: 50,
|
lineWidthMaxPixels: 50,
|
||||||
// billboard: this.props.billboard,
|
getPosition: { type: 'accessor', value: d => d.positions },
|
||||||
// antialiasing: this.props.antialiasing,
|
|
||||||
getPosition: {type: 'accessor', value: d => d.positions},
|
|
||||||
getRadius: 5,
|
getRadius: 5,
|
||||||
//getColor: this.props.getColor,
|
|
||||||
getFillColor: [255, 0, 0, 200],
|
getFillColor: [255, 0, 0, 200],
|
||||||
getLineColor: [255, 0, 0, 200],
|
getLineColor: [255, 0, 0, 200],
|
||||||
getLineWidth: 2,
|
getLineWidth: 2,
|
||||||
pickable: true
|
pickable: true
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// A unique id (prefix) for this layer
|
|
||||||
this.uid = "sl-" + Math.random().toString().slice(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPickingInfo({ info, mode, sourceLayer }) {
|
initializeState(context) {
|
||||||
console.log("picking info", info);
|
super.initializeState(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPickingInfo({ info, mode }) {
|
||||||
const index = info.index;
|
const index = info.index;
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
const d = sourceLayer.props.data.attributes;
|
const d = this.props.data.attributes;
|
||||||
console.log(index, d);
|
|
||||||
|
|
||||||
if (d) {
|
if (d) {
|
||||||
info.object = {
|
info.object = {
|
||||||
udv: d.udv ?? 2, // FIXME must add
|
udv: d.udv ?? 2,
|
||||||
i: d.value0.value[index],
|
i: d.value0.value[index],
|
||||||
j: d.value1.value[index],
|
j: d.value1.value[index],
|
||||||
ts: Number(d.value2.value[index]),
|
ts: Number(d.value2.value[index]),
|
||||||
//λ: d.value3[k*2+0],
|
εi: d.value3.value[index] / 10,
|
||||||
//φ: d.value3[k*2+1],
|
εj: d.value4.value[index] / 10,
|
||||||
εi: d.value3.value[index] / 10,
|
delta_μ: d.value5.value[index] / 10,
|
||||||
εj: d.value4.value[index] / 10,
|
delta_σ: d.value6.value[index] / 10,
|
||||||
delta_μ: d.value5.value[index] / 10,
|
delta_R: d.value7.value[index] / 10,
|
||||||
delta_σ: d.value6.value[index] / 10,
|
press_μ: d.value8.value[index],
|
||||||
delta_R: d.value7.value[index] / 10,
|
press_σ: d.value9.value[index],
|
||||||
press_μ: d.value8.value[index],
|
press_R: d.value10.value[index],
|
||||||
press_σ: d.value9.value[index],
|
depth_μ: d.value11.value[index] / 10,
|
||||||
press_R: d.value10.value[index],
|
depth_σ: d.value12.value[index] / 10,
|
||||||
depth_μ: d.value11.value[index] / 10,
|
depth_R: d.value13.value[index] / 10,
|
||||||
depth_σ: d.value12.value[index] / 10,
|
fill_μ: d.value14.value[index],
|
||||||
depth_R: d.value13.value[index] / 10,
|
fill_σ: d.value15.value[index],
|
||||||
fill_μ: d.value14.value[index],
|
fill_R: d.value16.value[index],
|
||||||
fill_σ: d.value15.value[index],
|
delay_μ: d.value17.value[index] / 10,
|
||||||
fill_R: d.value16.value[index],
|
delay_σ: d.value18.value[index] / 10,
|
||||||
delay_μ: d.value17.value[index] / 10,
|
delay_R: d.value19.value[index] / 10,
|
||||||
delay_σ: d.value18.value[index] / 10,
|
nofire: d.value20.value[index] >> 4,
|
||||||
delay_R: d.value19.value[index] / 10,
|
|
||||||
/*
|
|
||||||
nofire: d.value20.value[index] >> 4,
|
|
||||||
autofire: d.value20.value[index] & 0xf
|
autofire: d.value20.value[index] & 0xf
|
||||||
*/
|
};
|
||||||
}
|
console.log(`Picked sequence ${info.object.i}, point ${info.object.j}, udv ${info.object.udv}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`No data found index = ${index}`);
|
console.log(`No data found index = ${index}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
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;
|
export default DougalSequenceLayer;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
import DougalSequenceLayer from './DougalSequenceLayer'
|
import DougalSequenceLayer from './DougalSequenceLayer'
|
||||||
|
import DougalEventsLayer from './DougalEventsLayer'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DougalSequenceLayer
|
DougalSequenceLayer,
|
||||||
|
DougalEventsLayer
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,15 +96,6 @@
|
|||||||
<label for="lyr-crosshairs" title="Show or hide the crosshairs position marker">Crosshairs marker</label>
|
<label for="lyr-crosshairs" title="Show or hide the crosshairs position marker">Crosshairs marker</label>
|
||||||
</template>
|
</template>
|
||||||
</form>
|
</form>
|
||||||
<!--
|
|
||||||
<h3 class="mt-3" title="At least one of raw or final will always be selected">Sequence data</h3>
|
|
||||||
<form>
|
|
||||||
<input id="sd-raw" type="checkbox" value="R" v-model="sequenceDataTypes"/>
|
|
||||||
<label for="sd-raw">Show raw</label>
|
|
||||||
<input id="sd-final" type="checkbox" value="F" v-model="sequenceDataTypes"/>
|
|
||||||
<label for="sd-final">Show final</label>
|
|
||||||
</form>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- QC data: This section is meant to show (some) QC results in a graphical way,
|
<!-- QC data: This section is meant to show (some) QC results in a graphical way,
|
||||||
as 3D columns with lengths proportional to the QC values. Not implemented
|
as 3D columns with lengths proportional to the QC values. Not implemented
|
||||||
@@ -365,14 +356,13 @@ export default {
|
|||||||
return {
|
return {
|
||||||
layerSelection: [],
|
layerSelection: [],
|
||||||
layersAvailable: {},
|
layersAvailable: {},
|
||||||
sequenceDataTypes: [ "P", "F" ],
|
|
||||||
sequenceDataElements: [],
|
sequenceDataElements: [],
|
||||||
sequenceDataTStamp: null,
|
sequenceDataTStamp: null,
|
||||||
loadingProgress: null,
|
loadingProgress: null,
|
||||||
viewState: {},
|
viewState: {},
|
||||||
viewStateDefaults: {
|
viewStateDefaults: {
|
||||||
//maxZoom: 18,
|
//maxZoom: 18,
|
||||||
//maxPitch: 85
|
maxPitch: 89
|
||||||
},
|
},
|
||||||
|
|
||||||
crosshairsPosition: [],
|
crosshairsPosition: [],
|
||||||
@@ -383,27 +373,6 @@ export default {
|
|||||||
filterVisible: false,
|
filterVisible: false,
|
||||||
|
|
||||||
error: null,
|
error: null,
|
||||||
/*
|
|
||||||
layerDefinitions: [
|
|
||||||
{
|
|
||||||
id: "osm",
|
|
||||||
name: "OpenStreetMap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "sea",
|
|
||||||
name: "OpenSeaMap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nau",
|
|
||||||
name: "Nautical charts (NO)",
|
|
||||||
title: "Scan of Norway's nautical charts"
|
|
||||||
disabled: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
]
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -413,129 +382,96 @@ export default {
|
|||||||
return this.sequenceDataElements?.map( el => el.data );
|
return this.sequenceDataElements?.map( el => el.data );
|
||||||
},
|
},
|
||||||
|
|
||||||
sequenceBinaryData () {
|
sequenceBinaryData() {
|
||||||
return this.sequenceDataElements?.sort( (a, b) => a-b )?.map( ({data}) => {
|
const sequences = this.sequenceDataElements?.sort((a, b) => a.sequence - b.sequence) || [];
|
||||||
|
if (!sequences.length) {
|
||||||
|
console.warn('No sequence data available');
|
||||||
|
return { positions: new Float32Array(0), values: [], udv: 2 };
|
||||||
|
}
|
||||||
|
|
||||||
const bundle = DougalBinaryBundle.clone(data);
|
// Validate first sequence to get array sizes
|
||||||
|
let firstBundle;
|
||||||
// Total point count
|
try {
|
||||||
const totalCount = bundle.chunks().reduce( (acc, cur) => acc += cur.jCount, 0 );
|
firstBundle = DougalBinaryBundle.clone(sequences[0].data);
|
||||||
|
if (!firstBundle.chunks || typeof firstBundle.chunks !== 'function') {
|
||||||
// There is an implicit assumption that all chunks in a bundle
|
throw new Error('Invalid DougalBinaryBundle: chunks method missing');
|
||||||
// have the same ΔelemCount, elemCount, and udv.
|
|
||||||
const ΔelemCount = bundle.chunks()[0].ΔelemCount;
|
|
||||||
const elemCount = bundle.chunks()[0].elemCount;
|
|
||||||
const udv = bundle.chunks()[0].udv;
|
|
||||||
|
|
||||||
const ΣelemCount = ΔelemCount + elemCount + 2;
|
|
||||||
|
|
||||||
const values = new Array(ΣelemCount);
|
|
||||||
values[0] = new Uint16Array(totalCount); // i values
|
|
||||||
values[1] = new Uint32Array(totalCount); // j values
|
|
||||||
|
|
||||||
const positions = new Float32Array(totalCount*2);
|
|
||||||
|
|
||||||
console.log(`totalCount = ${totalCount}, ΔelemCount = ${ΔelemCount}, elemCount = ${elemCount}, ΣelemCount = ${ΣelemCount}`);
|
|
||||||
|
|
||||||
let offset = 0;
|
|
||||||
for (const chunk of bundle.chunks()) {
|
|
||||||
let k = 0;
|
|
||||||
|
|
||||||
console.log(`offset = ${offset}, k = ${k}, jCount = ${chunk.jCount}`);
|
|
||||||
// Populate the positions, which are assumed to always be at indices 0, 1
|
|
||||||
|
|
||||||
const λarray = chunk.elem(0);
|
|
||||||
const φarray = chunk.elem(1);
|
|
||||||
|
|
||||||
// Interleave lons and lats
|
|
||||||
for (let i = 0; i < λarray.length; i++) {
|
|
||||||
positions[offset*2 + i*2] = λarray[i];
|
|
||||||
positions[offset*2 + i*2+1] = φarray[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Populate the i values
|
|
||||||
//console.log(`values[${k}]: ${values[k] ? "✔" : "✘"}`);
|
|
||||||
values[k++].set((new Uint16Array(chunk.jCount)).fill(chunk.i), offset);
|
|
||||||
|
|
||||||
// Populate the j values
|
|
||||||
//console.log(`values[${k}]: ${values[k] ? "✔" : "✘"}`);
|
|
||||||
values[k++].set(Uint32Array.from({length: chunk.jCount}, (_, i) => chunk.j0 + i * chunk.Δj), offset);
|
|
||||||
|
|
||||||
|
|
||||||
// Populate values with each of Δelem first followed by elem
|
|
||||||
|
|
||||||
for (let j = 0; j < ΔelemCount; j++) {
|
|
||||||
const buffer = chunk.Δelem(j);
|
|
||||||
//console.log(`values[${k}]: ${values[k] ? "✔" : "✘"}`);
|
|
||||||
//console.log(`values[${k}] ← ΔelemCount(${j}) ${buffer.constructor.name}`);
|
|
||||||
if (!values[k]) values[k] = new buffer.constructor(totalCount);
|
|
||||||
values[k++].set(buffer, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start at 2 as 0, 1 are assumed to be λ, φ
|
|
||||||
for (let j = 2; j < elemCount; j++) {
|
|
||||||
const buffer = chunk.elem(j);
|
|
||||||
//console.log(`values[${k}]: ${values[k] ? "✔" : "✘"}`);
|
|
||||||
//console.log(`values[${k}] ← elemCount(${j}) ${buffer.constructor.name}`);
|
|
||||||
if (!values[k]) values[k] = new buffer.constructor(totalCount);
|
|
||||||
values[k++].set(buffer, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += chunk.jCount;
|
|
||||||
|
|
||||||
if (offset > totalCount) throw new Error('Overflow condition: offset > totalCount');
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to process first sequence:', e);
|
||||||
|
return { positions: new Float32Array(0), values: [], udv: 2 };
|
||||||
|
}
|
||||||
|
|
||||||
return { positions, values, udv };
|
const totalCount = sequences.reduce((acc, { data }) => {
|
||||||
|
try {
|
||||||
|
const bundle = DougalBinaryBundle.clone(data);
|
||||||
|
return acc + bundle.chunks().reduce((sum, chunk) => sum + chunk.jCount, 0);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Skipping invalid sequence data:', e);
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
console.warn('No valid points found in sequences');
|
||||||
|
return { positions: new Float32Array(0), values: [], udv: 2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ΔelemCount = firstBundle.chunks()[0].ΔelemCount;
|
||||||
|
const elemCount = firstBundle.chunks()[0].elemCount;
|
||||||
|
const positions = new Float32Array(totalCount * 2);
|
||||||
|
const values = new Array(ΔelemCount + elemCount + 2);
|
||||||
|
for (let k = 0; k < values.length; k++) {
|
||||||
|
values[k] = new (k === 0 ? Uint16Array : k === 1 ? Uint32Array : k === 2 ? BigUint64Array : Float32Array)(totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
let udv = 2;
|
||||||
|
sequences.forEach(({ data, sequence }) => {
|
||||||
|
try {
|
||||||
|
const bundle = DougalBinaryBundle.clone(data);
|
||||||
|
const chunks = bundle.chunks();
|
||||||
|
if (!chunks.length) {
|
||||||
|
console.warn(`No chunks in sequence ${sequence}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
udv = chunks[0].udv;
|
||||||
|
|
||||||
|
let chunkOffset = offset;
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
const λarray = chunk.elem(0);
|
||||||
|
const φarray = chunk.elem(1);
|
||||||
|
for (let i = 0; i < λarray.length; i++) {
|
||||||
|
positions[chunkOffset * 2 + i * 2] = λarray[i];
|
||||||
|
positions[chunkOffset * 2 + i * 2 + 1] = φarray[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
values[0].set(new Uint16Array(chunk.jCount).fill(chunk.i), chunkOffset);
|
||||||
|
values[1].set(Uint32Array.from({ length: chunk.jCount }, (_, i) => chunk.j0 + i * chunk.Δj), chunkOffset);
|
||||||
|
|
||||||
|
for (let j = 0; j < chunk.ΔelemCount; j++) {
|
||||||
|
values[2 + j].set(chunk.Δelem(j), chunkOffset);
|
||||||
|
}
|
||||||
|
for (let j = 2; j < chunk.elemCount; j++) {
|
||||||
|
values[2 + chunk.ΔelemCount + j - 2].set(chunk.elem(j), chunkOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkOffset += chunk.jCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += chunkOffset - offset;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Error processing sequence ${sequence}:`, e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
if (offset !== totalCount) {
|
||||||
sequenceDataPreplots () {
|
console.warn(`Offset mismatch: ${offset} ≠ ${totalCount}`);
|
||||||
return {
|
}
|
||||||
tstamp: this.sequenceDataTStamp,
|
|
||||||
sequences: this.sequenceDataElements.filter(i => i.type == "P")
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
sequenceDataRaw () {
|
console.log(`Concatenated ${totalCount} points, ${values.length} value arrays`);
|
||||||
return {
|
return { positions, values, udv };
|
||||||
tstamp: this.sequenceDataTStamp,
|
|
||||||
sequences: this.sequenceDataElements.filter(i => i.type == "R")
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sequenceDataFinal () {
|
|
||||||
return {
|
|
||||||
tstamp: this.sequenceDataTStamp,
|
|
||||||
sequences: this.sequenceDataElements.filter(i => i.type == "F")
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
sequenceDataPostplots () {
|
|
||||||
const fn = i => i.type != "P" && this.sequenceDataTypes.includes(i.type);
|
|
||||||
return {
|
|
||||||
tstamp: this.sequenceDataTStamp,
|
|
||||||
sequences: this.sequenceDataElements.filter(fn)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
fullProspectRaw () {
|
|
||||||
// Get raw data, so type = "R"
|
|
||||||
const sequences = this.sequenceDataElements.filter( i => i.type == "R" );
|
|
||||||
return [...sequences.map( i => toJSON(i.data) )].flat();
|
|
||||||
},
|
|
||||||
|
|
||||||
fullProspectFinal () {
|
|
||||||
// Get raw data, so type = "R"
|
|
||||||
const sequences = this.sequenceDataElements.filter( i => i.type == "F" );
|
|
||||||
return [...sequences.map( i => toJSON(i.data) )].flat();
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
...mapGetters(['user', 'loading', 'serverEvent', 'lineName', 'serverEvent']),
|
...mapGetters(['user', 'loading', 'serverEvent', 'lineName', 'serverEvent']),
|
||||||
...mapState({projectSchema: state => state.project.projectSchema})
|
...mapState({projectSchema: state => state.project.projectSchema})
|
||||||
},
|
},
|
||||||
@@ -552,31 +488,12 @@ export default {
|
|||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
|
|
||||||
sequenceDataTypes (val, old) {
|
|
||||||
// Ensure that at least one of raw or final is always selected
|
|
||||||
if (!val.includes("R") && !val.includes("F")) {
|
|
||||||
if (old.includes("F")) {
|
|
||||||
this.sequenceDataTypes.push("R");
|
|
||||||
} else {
|
|
||||||
this.sequenceDataTypes.push("F");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
sequenceDataElements () {
|
sequenceDataElements () {
|
||||||
//console.log("seq data changed");
|
//console.log("seq data changed");
|
||||||
this.sequenceDataTStamp = Date.now();
|
this.sequenceDataTStamp = Date.now();
|
||||||
//this.render();
|
//this.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
sequenceDataPreplots () {
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
sequenceDataPostplots () {
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
$route (to, from) {
|
$route (to, from) {
|
||||||
if (to.name == "map") {
|
if (to.name == "map") {
|
||||||
this.decodeURLHash();
|
this.decodeURLHash();
|
||||||
@@ -595,6 +512,11 @@ export default {
|
|||||||
for (const name in this.layersAvailable) {
|
for (const name in this.layersAvailable) {
|
||||||
//console.log("Visible", name, this.layerSelection.includes(name));
|
//console.log("Visible", name, this.layerSelection.includes(name));
|
||||||
const fn = this.layersAvailable[name];
|
const fn = this.layersAvailable[name];
|
||||||
|
|
||||||
|
if (typeof fn != 'function') {
|
||||||
|
throw new Error(`Layer ${name}: expected a function, got ${typeof fn}`);
|
||||||
|
}
|
||||||
|
|
||||||
layers.push(fn({
|
layers.push(fn({
|
||||||
visible: this.layerSelection.includes(name)
|
visible: this.layerSelection.includes(name)
|
||||||
}));
|
}));
|
||||||
@@ -680,10 +602,10 @@ export default {
|
|||||||
|
|
||||||
let data = layer.props.data;
|
let data = layer.props.data;
|
||||||
|
|
||||||
// Handle DougalSequenceLayer (CompositeLayer)
|
// Handle DougalSequenceLayer
|
||||||
if (layer.constructor.layerName === 'DougalSequenceLayer') {
|
if (layer.constructor.layerName === 'DougalSequenceLayer') {
|
||||||
// Iterate over sequence data (array of {positions, values, udv})
|
const { positions } = data;
|
||||||
data.forEach(({ positions }) => {
|
if (positions && positions.length) {
|
||||||
for (let i = 0; i < positions.length; i += 2) {
|
for (let i = 0; i < positions.length; i += 2) {
|
||||||
const lon = positions[i];
|
const lon = positions[i];
|
||||||
const lat = positions[i + 1];
|
const lat = positions[i + 1];
|
||||||
@@ -693,8 +615,8 @@ export default {
|
|||||||
λ1 = Math.max(λ1, lon);
|
λ1 = Math.max(λ1, lon);
|
||||||
φ1 = Math.max(φ1, lat);
|
φ1 = Math.max(φ1, lat);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
}
|
||||||
} else if (layer.constructor.layerName === 'ScatterplotLayer') {
|
} else if (layer.constructor.layerName === 'ScatterplotLayer') {
|
||||||
// Direct point data (e.g., navp)
|
// Direct point data (e.g., navp)
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
@@ -708,7 +630,7 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (data && data.attributes && data.attributes.getPosition) {
|
} else if (data && data.attributes && data.attributes.getPosition) {
|
||||||
// Binary data (e.g., DougalSequenceLayer sublayers)
|
// Binary data
|
||||||
const positions = data.attributes.getPosition.value;
|
const positions = data.attributes.getPosition.value;
|
||||||
for (let i = 0; i < data.length * 2; i += 2) {
|
for (let i = 0; i < data.length * 2; i += 2) {
|
||||||
const lon = positions[i];
|
const lon = positions[i];
|
||||||
@@ -723,7 +645,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} else if (layer.constructor.layerName === 'GeoJsonLayer') {
|
} else if (layer.constructor.layerName === 'GeoJsonLayer') {
|
||||||
// Extract points from GeoJSON features
|
// Extract points from GeoJSON features
|
||||||
let featureCollections = Array.isArray(data) ? data : [data]; // Handle both array and single object
|
let featureCollections = Array.isArray(data) ? data : [data];
|
||||||
featureCollections.forEach(item => {
|
featureCollections.forEach(item => {
|
||||||
let features = item;
|
let features = item;
|
||||||
if (item?.type === 'FeatureCollection') {
|
if (item?.type === 'FeatureCollection') {
|
||||||
@@ -882,85 +804,6 @@ export default {
|
|||||||
console.log("passed for await", deck);
|
console.log("passed for await", deck);
|
||||||
},
|
},
|
||||||
|
|
||||||
dataChunks (lonIndex = 0, latIndex = 1, valueIndex = 1, udvFilter = null) {
|
|
||||||
|
|
||||||
// Collect data from all bundles across all buffers
|
|
||||||
let totalCount = 0;
|
|
||||||
const chunkData = [];
|
|
||||||
let elemCountChecked = false; // To validate indices against elemCount once
|
|
||||||
let minValue = Infinity;
|
|
||||||
let maxValue = -Infinity;
|
|
||||||
const chunkRecordMap = []; // For picking: map point indices to chunk and record index
|
|
||||||
|
|
||||||
for (const buffer of this.sequenceData) {
|
|
||||||
// Clone the buffer into our class
|
|
||||||
const bundle = DougalBinaryBundle.clone(buffer);
|
|
||||||
|
|
||||||
const chunks = bundle.chunks();
|
|
||||||
|
|
||||||
chunks.forEach(chunk => {
|
|
||||||
// Check udv filter
|
|
||||||
if (udvFilter !== null) {
|
|
||||||
let match = false;
|
|
||||||
if (Array.isArray(udvFilter)) {
|
|
||||||
match = udvFilter.includes(chunk.udv);
|
|
||||||
} else if (typeof udvFilter === 'function') {
|
|
||||||
match = udvFilter(chunk.udv);
|
|
||||||
} else {
|
|
||||||
match = chunk.udv === udvFilter;
|
|
||||||
}
|
|
||||||
if (!match) {
|
|
||||||
console.log(`Skipping chunk with udv=${chunk.udv}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log chunk type for debugging
|
|
||||||
const chunkType = chunk instanceof DougalBinaryChunkSequential ? 'sequential (0x11)' : 'interleaved (0x12)';
|
|
||||||
console.log(`Processing chunk: type=${chunkType}, udv=${chunk.udv}, jCount=${chunk.jCount}`);
|
|
||||||
|
|
||||||
// Validate indices against chunk.elemCount (once, assuming consistent structure)
|
|
||||||
if (!elemCountChecked) {
|
|
||||||
const ec = chunk.elemCount;
|
|
||||||
if (lonIndex < 0 || lonIndex >= ec || latIndex < 0 || latIndex >= ec) {
|
|
||||||
throw new Error(`Invalid lonIndex (${lonIndex}) or latIndex (${latIndex}); must be between 0 and ${ec - 1}`);
|
|
||||||
}
|
|
||||||
if (valueIndex !== null && (valueIndex < 0 || valueIndex >= ec)) {
|
|
||||||
throw new Error(`Invalid valueIndex (${valueIndex}); must be between 0 and ${ec - 1}`);
|
|
||||||
}
|
|
||||||
elemCountChecked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lons = chunk.elem(lonIndex); // Float32Array
|
|
||||||
const lats = chunk.elem(latIndex); // Float32Array
|
|
||||||
|
|
||||||
let values = null;
|
|
||||||
if (valueIndex !== null) {
|
|
||||||
values = chunk.elem(valueIndex); // TypedArray for rendering value
|
|
||||||
// Clamp negative values for radius
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
if (values[i] < 0) {
|
|
||||||
console.warn(`Negative value (${values[i]}) at valueIndex ${valueIndex}; clamping to 0 for radius`);
|
|
||||||
values[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const count = chunk.jCount;
|
|
||||||
// Map point indices to chunk and record index for picking
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
chunkRecordMap[totalCount + i] = { chunk, recordIndex: i };
|
|
||||||
}
|
|
||||||
totalCount += count;
|
|
||||||
|
|
||||||
chunkData.push({ lons, lats, values, count });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update state with chunkRecordMap for getPickingInfo
|
|
||||||
return chunkRecordMap;
|
|
||||||
},
|
|
||||||
|
|
||||||
async initLayers (gl) {
|
async initLayers (gl) {
|
||||||
//console.log("SHOULD BE INITIALISING LAYERS HERE", gl);
|
//console.log("SHOULD BE INITIALISING LAYERS HERE", gl);
|
||||||
this.decodeURL();
|
this.decodeURL();
|
||||||
@@ -1096,25 +939,6 @@ export default {
|
|||||||
console.log("TODO: Should switch to legacy map view");
|
console.log("TODO: Should switch to legacy map view");
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLOR_SCALE = [
|
|
||||||
// negative
|
|
||||||
[65, 182, 196],
|
|
||||||
[127, 205, 187],
|
|
||||||
[199, 233, 180],
|
|
||||||
[237, 248, 177],
|
|
||||||
|
|
||||||
// positive
|
|
||||||
[255, 255, 204],
|
|
||||||
[255, 237, 160],
|
|
||||||
[254, 217, 118],
|
|
||||||
[254, 178, 76],
|
|
||||||
[253, 141, 60],
|
|
||||||
[252, 78, 42],
|
|
||||||
[227, 26, 28],
|
|
||||||
[189, 0, 38],
|
|
||||||
[128, 0, 38]
|
|
||||||
];
|
|
||||||
|
|
||||||
this.layersAvailable.osm = this.osmLayer;
|
this.layersAvailable.osm = this.osmLayer;
|
||||||
|
|
||||||
this.layersAvailable.sea = this.openSeaMapLayer;
|
this.layersAvailable.sea = this.openSeaMapLayer;
|
||||||
@@ -1137,7 +961,9 @@ export default {
|
|||||||
|
|
||||||
this.layersAvailable.seqrp = this.rawSequencesPointsLayer;
|
this.layersAvailable.seqrp = this.rawSequencesPointsLayer;
|
||||||
|
|
||||||
//this.layersAvailable.seqrp = this.rawSequencesPointsGunDataLayer;
|
//this.layersAvailable.seqfp = this.rawSequencesPointsGunDataLayer;
|
||||||
|
|
||||||
|
this.layersAvailable.seqrh = this.rawSequencesIJErrorLayer;
|
||||||
|
|
||||||
this.layersAvailable.crosshairs = (options = {}) => {
|
this.layersAvailable.crosshairs = (options = {}) => {
|
||||||
return new IconLayer({
|
return new IconLayer({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// https://deck.gl/docs/developer-guide/performance#supply-attributes-directly
|
// https://deck.gl/docs/developer-guide/performance#supply-attributes-directly
|
||||||
|
|
||||||
import { Deck, WebMercatorViewport, FlyToInterpolator, CompositeLayer } from '@deck.gl/core';
|
import { Deck, WebMercatorViewport, FlyToInterpolator, CompositeLayer } from '@deck.gl/core';
|
||||||
import { GeoJsonLayer, LineLayer, BitmapLayer, ScatterplotLayer, IconLayer } from '@deck.gl/layers';
|
import { GeoJsonLayer, LineLayer, BitmapLayer, ScatterplotLayer, ColumnLayer, IconLayer } from '@deck.gl/layers';
|
||||||
import {HeatmapLayer} from '@deck.gl/aggregation-layers';
|
import {HeatmapLayer} from '@deck.gl/aggregation-layers';
|
||||||
import { TileLayer, MVTLayer } from '@deck.gl/geo-layers';
|
import { TileLayer, MVTLayer } from '@deck.gl/geo-layers';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import * as d3a from 'd3-array';
|
|||||||
|
|
||||||
import { DougalBinaryBundle, DougalBinaryChunkSequential, DougalBinaryChunkInterleaved } from '@dougal/binary';
|
import { DougalBinaryBundle, DougalBinaryChunkSequential, DougalBinaryChunkInterleaved } from '@dougal/binary';
|
||||||
import { DougalShotLayer } from '@/lib/deck.gl';
|
import { DougalShotLayer } from '@/lib/deck.gl';
|
||||||
import DougalSequenceLayer from '@/lib/deck.gl/DougalSequenceLayer';
|
import { DougalSequenceLayer, DougalEventsLayer } from '@/lib/deck.gl';
|
||||||
|
|
||||||
import { colors } from 'vuetify/lib'
|
import { colors } from 'vuetify/lib'
|
||||||
|
|
||||||
@@ -53,6 +53,29 @@ export default {
|
|||||||
|
|
||||||
data () {
|
data () {
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
COLOUR_SCALE_1: [
|
||||||
|
// negative
|
||||||
|
[65, 182, 196],
|
||||||
|
[127, 205, 187],
|
||||||
|
[199, 233, 180],
|
||||||
|
[237, 248, 177],
|
||||||
|
|
||||||
|
// positive
|
||||||
|
[255, 255, 204],
|
||||||
|
[255, 237, 160],
|
||||||
|
[254, 217, 118],
|
||||||
|
[254, 178, 76],
|
||||||
|
[253, 141, 60],
|
||||||
|
[252, 78, 42],
|
||||||
|
[227, 26, 28],
|
||||||
|
[189, 0, 38],
|
||||||
|
[128, 0, 38]
|
||||||
|
]
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -63,6 +86,13 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
normalisedColourScale(v, scale = this.COLOUR_SCALE_1, min = 0, max = 1) {
|
||||||
|
const range = max-min;
|
||||||
|
const i = Math.min(scale.length, Math.max(Math.round((v-min) / range * scale.length), 0));
|
||||||
|
//console.log(`v=${v}, scale.length=${scale.length}, min=${min}, max=${max}, i=${i}, → ${scale[i]}`);
|
||||||
|
return scale[i];
|
||||||
|
},
|
||||||
|
|
||||||
osmLayer (options = {}) {
|
osmLayer (options = {}) {
|
||||||
return new TileLayer({
|
return new TileLayer({
|
||||||
id: "osm",
|
id: "osm",
|
||||||
@@ -90,7 +120,7 @@ export default {
|
|||||||
|
|
||||||
|
|
||||||
// OSM tiles layer. Handy to make water transparent
|
// OSM tiles layer. Handy to make water transparent
|
||||||
// but super reliable yet
|
// but not super reliable yet
|
||||||
|
|
||||||
osmVectorLayer (options = {}) {
|
osmVectorLayer (options = {}) {
|
||||||
return new MVTLayer({
|
return new MVTLayer({
|
||||||
@@ -225,35 +255,48 @@ export default {
|
|||||||
|
|
||||||
eventsLogLayer (options = {}) {
|
eventsLogLayer (options = {}) {
|
||||||
|
|
||||||
const labelColour = (d, i) => {
|
const labelColour = (d, i, t, c = [127, 65, 90]) => {
|
||||||
const label = d?.properties?.labels?.[0];
|
const label = d?.properties?.labels?.[0];
|
||||||
const colour = this.labels[label]?.view?.colour ?? "#cococo";
|
const colour = this.labels[label]?.view?.colour ?? "#cococo";
|
||||||
|
|
||||||
if (colour) {
|
if (colour) {
|
||||||
if (colour[0] == "#") {
|
if (colour[0] == "#") {
|
||||||
return hexToArray(colour);
|
c = hexToArray(colour);
|
||||||
} else {
|
} else {
|
||||||
return namedColourToArray(colour);
|
c = namedColourToArray(colour);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return [127, 65, 90];
|
//return [127, 65, 90];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (t != null) {
|
||||||
|
c[3] = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
};
|
};
|
||||||
|
|
||||||
return new GeoJsonLayer({
|
return new DougalEventsLayer({
|
||||||
id: 'log',
|
id: 'log',
|
||||||
data: `/api/project/${this.$route.params.project}/event?mime=application/geo%2Bjson`,
|
data: `/api/project/${this.$route.params.project}/event?mime=application/geo%2Bjson`,
|
||||||
lineWidthMinPixels: 2,
|
lineWidthMinPixels: 2,
|
||||||
//getLineColor: [127, 65, 90],
|
getPosition: d => d.geometry.coordinates,
|
||||||
getFillColor: (d, i) => labelColour(d, i),
|
jitter: 0.00015,
|
||||||
getLineColor: (d, i) => labelColour(d, i),
|
getElevation: d => Math.min(Math.max(d.properties.remarks?.length || 10, 10), 200),
|
||||||
getPointRadius: 2,
|
getFillColor: (d, i) => labelColour(d, i, 200),
|
||||||
|
getLineColor: (d, i) => labelColour(d, i, 200),
|
||||||
|
radius: 0.001,
|
||||||
|
radiusScale: 1,
|
||||||
|
// This just won't work with radiusUnits = "pixels".
|
||||||
|
// See: https://grok.com/share/c2hhcmQtMw%3D%3D_16578be4-20fd-4000-a765-f082503d0495
|
||||||
radiusUnits: "pixels",
|
radiusUnits: "pixels",
|
||||||
pointRadiusMinPixels: 2,
|
radiusMinPixels: 1.5,
|
||||||
|
radiusMaxPixels: 2.5,
|
||||||
|
|
||||||
pickable: true,
|
pickable: true,
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
preplotSaillinesLinesLayer (options = {}) {
|
preplotSaillinesLinesLayer (options = {}) {
|
||||||
@@ -344,9 +387,51 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
rawSequencesPointsLayer (options = {}) {
|
rawSequencesPointsLayer (options = {}) {
|
||||||
|
const { positions, values, udv } = this.sequenceBinaryData;
|
||||||
|
if (!positions.length || !values.length) {
|
||||||
|
console.warn('No valid data for seqrp');
|
||||||
|
|
||||||
|
return new DougalSequenceLayer({
|
||||||
|
id: 'seqrp',
|
||||||
|
data: { length: 0, attributes: {} },
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointCount = positions.length / 2;
|
||||||
|
if (!values.every(arr => arr.length === pointCount)) {
|
||||||
|
console.warn(`Length mismatch in seqrp: positions/2 (${pointCount}) ≠ values (${values.map(arr => arr.length)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = {
|
||||||
|
getPosition: {
|
||||||
|
value: positions,
|
||||||
|
type: 'float32',
|
||||||
|
size: 2
|
||||||
|
},
|
||||||
|
udv
|
||||||
|
};
|
||||||
|
|
||||||
|
values.forEach((valArray, k) => {
|
||||||
|
let value = valArray;
|
||||||
|
if (valArray instanceof BigUint64Array) {
|
||||||
|
value = Float64Array.from(valArray, v => Number(v));
|
||||||
|
}
|
||||||
|
attributes[`value${k}`] = {
|
||||||
|
value,
|
||||||
|
type: value instanceof Float64Array ? 'float64' :
|
||||||
|
value instanceof Uint16Array ? 'uint16' :
|
||||||
|
value instanceof Uint32Array ? 'uint32' : 'float32',
|
||||||
|
size: 1
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return new DougalSequenceLayer({
|
return new DougalSequenceLayer({
|
||||||
id: 'seqrp',
|
id: 'seqrp',
|
||||||
data: this.sequenceBinaryData,
|
data: {
|
||||||
|
length: pointCount,
|
||||||
|
attributes
|
||||||
|
},
|
||||||
getRadius: 2,
|
getRadius: 2,
|
||||||
getFillColor: [0, 120, 220, 200],
|
getFillColor: [0, 120, 220, 200],
|
||||||
pickable: true,
|
pickable: true,
|
||||||
@@ -354,23 +439,54 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
rawSequencesPointsGunDataLayer (options = {}) {
|
rawSequencesIJErrorLayer(options = {}) {
|
||||||
return new DougalSequenceLayer({
|
const { positions, values } = this.sequenceBinaryData;
|
||||||
id: 'seqrp',
|
if (!positions.length || !values[3] || !values[4]) {
|
||||||
data: this.sequenceBinaryData,
|
console.warn('No valid data for rawSequencesIJErrorLayer');
|
||||||
getRadius: (d, i) => i?.data?.attributes?.value9.value[i?.index],
|
|
||||||
//getRadius: (d, i) => {console.log(d, i); return 1;},
|
return new HeatmapLayer({
|
||||||
//getRadius: 2,
|
id: 'seqrh',
|
||||||
radiusMinPixels: 1,
|
data: [],
|
||||||
//autoScale: true,
|
...options
|
||||||
//getFillColor: gunPressureColours,
|
});
|
||||||
getFillColor: [0, 120, 220, 200],
|
}
|
||||||
pickable: true,
|
|
||||||
|
const weights = Float32Array.from(values[3], (ei, i) => {
|
||||||
|
const ej = values[4][i];
|
||||||
|
return Math.sqrt(ei * ei + ej * ej) / 10; // Euclidean distance in meters
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Positions sample:', positions.slice(0, 10));
|
||||||
|
console.log('Weights sample:', weights.slice(0, 10));
|
||||||
|
console.log('Weight stats:', {
|
||||||
|
min: Math.min(...weights),
|
||||||
|
max: Math.max(...weights),
|
||||||
|
mean: weights.reduce((sum, v) => sum + v, 0) / weights.length
|
||||||
|
});
|
||||||
|
|
||||||
|
return new HeatmapLayer({
|
||||||
|
id: 'seqrh',
|
||||||
|
data: {
|
||||||
|
length: weights.length,
|
||||||
|
positions,
|
||||||
|
weights
|
||||||
|
/*
|
||||||
|
attributes: {
|
||||||
|
getPosition: { value: positions, type: 'float32', size: 2 },
|
||||||
|
getWeight: { value: weights, type: 'float32', size: 1 }
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
getPosition: (d, {index, data}) => [ data.positions[index*2], data.positions[index*2+1] ],
|
||||||
|
getWeight: (d, {index, data}) => data.weights[index],
|
||||||
|
colorDomain: [2, 20],
|
||||||
|
radiusPixels: 25,
|
||||||
|
aggregation: 'MEAN',
|
||||||
|
pickable: false,
|
||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user