Files
gods-eye-view/server/providers/mapillary/decode.js
T
Said TurkseverandClaude Fable 5.1 fa5710bcf2 feat(mapillary): street-level layer core — coverage, sequences, viewer bridge, query engine
Camera-driven Mapillary coverage (z0–5 overview points from orbit, z11–14
ground-clamped sequence lines below 60 km), per-sequence image cones, an
embedded MapillaryJS viewer bridge with a camera-follow mode, sprite icons
for map features, and a query engine that turns a plan into a streamed
z14 tile fan-out. Registers an on-globe CC BY-SA 4.0 credit while enabled.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-23 15:44:32 +01:00

61 lines
2.2 KiB
JavaScript

import { PbfReader } from 'pbf';
import { VectorTile } from '@mapbox/vector-tile';
import { tileLocalToLonLat } from '../../../src/layers/mapillary/tileMath.js';
/** Vector-tile layer name inside each Mapillary map-feature tile. */
const FEATURE_LAYER_NAMES = Object.freeze({
points: 'point',
signs: 'traffic_sign',
});
/**
* Decode one map-feature tile into compact rows, keeping only features whose
* value passes `matcher` and whose sighting window overlaps [since, until].
* Rows are positional arrays to keep the streamed payload small:
* [lon, lat, id, value, firstSeenMs, lastSeenMs].
* @param {Buffer|Uint8Array} bytes
* @param {{x:number,y:number,z:number,layer:'points'|'signs'}} address
* @param {{matcher?: (value:string)=>boolean, since?: number|null, until?: number|null}} [filter]
* @returns {Array<[number, number, string, string, number, number]>}
*/
export function decodeFeatureRows(bytes, address, filter = {}) {
if (!bytes || !bytes.length) return [];
const layerName = FEATURE_LAYER_NAMES[address.layer];
const tile = new VectorTile(new PbfReader(bytes));
const layer = layerName ? tile.layers[layerName] : null;
if (!layer) return [];
const matcher = filter.matcher || (() => true);
const since = Number.isFinite(filter.since) ? filter.since : null;
const until = Number.isFinite(filter.until) ? filter.until : null;
const rows = [];
for (let i = 0; i < layer.length; i++) {
const feature = layer.feature(i);
const value = feature.properties?.value;
if (typeof value !== 'string' || !matcher(value)) continue;
const first = Number(feature.properties.first_seen_at) || 0;
const last = Number(feature.properties.last_seen_at) || first;
if (since !== null && last < since) continue;
if (until !== null && first > until) continue;
const geometry = feature.loadGeometry();
const point = geometry?.[0]?.[0];
if (!point) continue;
const [lon, lat] = tileLocalToLonLat(
point.x,
point.y,
layer.extent,
address.x,
address.y,
address.z,
);
rows.push([
Number(lon.toFixed(6)),
Number(lat.toFixed(6)),
String(feature.properties.id ?? feature.id ?? ''),
value,
first,
last,
]);
}
return rows;
}