mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 11:57:08 +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:
@@ -96,15 +96,6 @@
|
||||
<label for="lyr-crosshairs" title="Show or hide the crosshairs position marker">Crosshairs marker</label>
|
||||
</template>
|
||||
</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,
|
||||
as 3D columns with lengths proportional to the QC values. Not implemented
|
||||
@@ -365,14 +356,13 @@ export default {
|
||||
return {
|
||||
layerSelection: [],
|
||||
layersAvailable: {},
|
||||
sequenceDataTypes: [ "P", "F" ],
|
||||
sequenceDataElements: [],
|
||||
sequenceDataTStamp: null,
|
||||
loadingProgress: null,
|
||||
viewState: {},
|
||||
viewStateDefaults: {
|
||||
//maxZoom: 18,
|
||||
//maxPitch: 85
|
||||
maxPitch: 89
|
||||
},
|
||||
|
||||
crosshairsPosition: [],
|
||||
@@ -383,27 +373,6 @@ export default {
|
||||
filterVisible: false,
|
||||
|
||||
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 );
|
||||
},
|
||||
|
||||
sequenceBinaryData () {
|
||||
return this.sequenceDataElements?.sort( (a, b) => a-b )?.map( ({data}) => {
|
||||
sequenceBinaryData() {
|
||||
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);
|
||||
|
||||
// Total point count
|
||||
const totalCount = bundle.chunks().reduce( (acc, cur) => acc += cur.jCount, 0 );
|
||||
|
||||
// There is an implicit assumption that all chunks in a bundle
|
||||
// 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');
|
||||
// Validate first sequence to get array sizes
|
||||
let firstBundle;
|
||||
try {
|
||||
firstBundle = DougalBinaryBundle.clone(sequences[0].data);
|
||||
if (!firstBundle.chunks || typeof firstBundle.chunks !== 'function') {
|
||||
throw new Error('Invalid DougalBinaryBundle: chunks method missing');
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
sequenceDataPreplots () {
|
||||
return {
|
||||
tstamp: this.sequenceDataTStamp,
|
||||
sequences: this.sequenceDataElements.filter(i => i.type == "P")
|
||||
};
|
||||
},
|
||||
if (offset !== totalCount) {
|
||||
console.warn(`Offset mismatch: ${offset} ≠ ${totalCount}`);
|
||||
}
|
||||
|
||||
sequenceDataRaw () {
|
||||
return {
|
||||
tstamp: this.sequenceDataTStamp,
|
||||
sequences: this.sequenceDataElements.filter(i => i.type == "R")
|
||||
};
|
||||
console.log(`Concatenated ${totalCount} points, ${values.length} value arrays`);
|
||||
return { positions, values, udv };
|
||||
},
|
||||
|
||||
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']),
|
||||
...mapState({projectSchema: state => state.project.projectSchema})
|
||||
},
|
||||
@@ -552,31 +488,12 @@ export default {
|
||||
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 () {
|
||||
//console.log("seq data changed");
|
||||
this.sequenceDataTStamp = Date.now();
|
||||
//this.render();
|
||||
},
|
||||
|
||||
sequenceDataPreplots () {
|
||||
this.render();
|
||||
},
|
||||
|
||||
sequenceDataPostplots () {
|
||||
this.render();
|
||||
},
|
||||
|
||||
$route (to, from) {
|
||||
if (to.name == "map") {
|
||||
this.decodeURLHash();
|
||||
@@ -595,6 +512,11 @@ export default {
|
||||
for (const name in this.layersAvailable) {
|
||||
//console.log("Visible", name, this.layerSelection.includes(name));
|
||||
const fn = this.layersAvailable[name];
|
||||
|
||||
if (typeof fn != 'function') {
|
||||
throw new Error(`Layer ${name}: expected a function, got ${typeof fn}`);
|
||||
}
|
||||
|
||||
layers.push(fn({
|
||||
visible: this.layerSelection.includes(name)
|
||||
}));
|
||||
@@ -680,10 +602,10 @@ export default {
|
||||
|
||||
let data = layer.props.data;
|
||||
|
||||
// Handle DougalSequenceLayer (CompositeLayer)
|
||||
// Handle DougalSequenceLayer
|
||||
if (layer.constructor.layerName === 'DougalSequenceLayer') {
|
||||
// Iterate over sequence data (array of {positions, values, udv})
|
||||
data.forEach(({ positions }) => {
|
||||
const { positions } = data;
|
||||
if (positions && positions.length) {
|
||||
for (let i = 0; i < positions.length; i += 2) {
|
||||
const lon = positions[i];
|
||||
const lat = positions[i + 1];
|
||||
@@ -693,8 +615,8 @@ export default {
|
||||
λ1 = Math.max(λ1, lon);
|
||||
φ1 = Math.max(φ1, lat);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (layer.constructor.layerName === 'ScatterplotLayer') {
|
||||
// Direct point data (e.g., navp)
|
||||
if (Array.isArray(data)) {
|
||||
@@ -708,7 +630,7 @@ export default {
|
||||
}
|
||||
});
|
||||
} else if (data && data.attributes && data.attributes.getPosition) {
|
||||
// Binary data (e.g., DougalSequenceLayer sublayers)
|
||||
// Binary data
|
||||
const positions = data.attributes.getPosition.value;
|
||||
for (let i = 0; i < data.length * 2; i += 2) {
|
||||
const lon = positions[i];
|
||||
@@ -723,7 +645,7 @@ export default {
|
||||
}
|
||||
} else if (layer.constructor.layerName === 'GeoJsonLayer') {
|
||||
// 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 => {
|
||||
let features = item;
|
||||
if (item?.type === 'FeatureCollection') {
|
||||
@@ -882,85 +804,6 @@ export default {
|
||||
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) {
|
||||
//console.log("SHOULD BE INITIALISING LAYERS HERE", gl);
|
||||
this.decodeURL();
|
||||
@@ -1096,25 +939,6 @@ export default {
|
||||
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.sea = this.openSeaMapLayer;
|
||||
@@ -1137,7 +961,9 @@ export default {
|
||||
|
||||
this.layersAvailable.seqrp = this.rawSequencesPointsLayer;
|
||||
|
||||
//this.layersAvailable.seqrp = this.rawSequencesPointsGunDataLayer;
|
||||
//this.layersAvailable.seqfp = this.rawSequencesPointsGunDataLayer;
|
||||
|
||||
this.layersAvailable.seqrh = this.rawSequencesIJErrorLayer;
|
||||
|
||||
this.layersAvailable.crosshairs = (options = {}) => {
|
||||
return new IconLayer({
|
||||
|
||||
Reference in New Issue
Block a user