Files
gods-eye-view/src/layers/streetLevel/providers/mapillary/sequences.js
T
Said TurkseverandClaude Opus 5.5 cb01014e36 feat(street-level): KartaView as a third, keyless imagery provider
Coverage lines from KartaView's z13-14 JSON tiles (OSM ways with photos,
chained end to start), the photo nearest a clicked line, cones along its
sequence, and a viewer for flat photos and 360° panoramas (a small WebGL
equirectangular renderer, no new dependency) with ‹ › along the sequence.

Mapillary's camera-driven line coverage and sequence cones move to the
provider-neutral core (lineCoverage.js, sequenceCones.js) with the tile
fetch, zoom ladder, colours and pick prefixes injected; Mapillary keeps
thin wrappers and its tests pass unchanged.

KartaView quirks handled: api.kartaview.org (the documented
api.openstreetcam.org serves empty tiles), photo URLs that name storage1
for photos stored elsewhere, and a photo API that answers 429 to more than
about three requests at once (two in flight, retried with backoff).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 18:33:14 +01:00

44 lines
1.5 KiB
JavaScript

import { createSequenceCones, thinImages } from '../../sequenceCones.js';
import { COLORS, PICK_PREFIX } from './policy.js';
export { thinImages } from '../../sequenceCones.js';
/** Normalize a graph image record into the shape the cones use. */
export function normalizeSequenceImage(record) {
const coordinates = record?.geometry?.coordinates;
if (!Array.isArray(coordinates) || coordinates.length < 2) return null;
return {
id: String(record.id),
lon: Number(coordinates[0]),
lat: Number(coordinates[1]),
compassAngle: Number(record.compass_angle) || 0,
capturedAt: Number(record.captured_at) || 0,
isPano: record.is_pano === true,
altitude: Number.isFinite(record.computed_altitude)
? record.computed_altitude
: null,
};
}
/** Image cones for the selected Mapillary sequence, its line highlighted. */
export function createSequences({ state, source, parts }) {
return createSequenceCones({
state,
async loadImages(sequenceId, { signal }) {
const records = await source.getSequenceImages(sequenceId, { signal });
return thinImages(
records
.map(normalizeSequenceImage)
.filter(Boolean)
.sort((a, b) => a.capturedAt - b.capturedAt),
);
},
colors: COLORS,
pickPrefix: PICK_PREFIX.image,
spriteId: 'street-level:mapillary-cones',
renderKey: 'mapillary-sequence',
onHighlight: (sequenceId, selected) =>
parts.coverage.recolorSequence(sequenceId, selected),
});
}