mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
feat(street-level): Panoramax as a fourth, keyless imagery provider
Panoramax, the open federated street-level commons, registers beside Mapillary, Google Street View and KartaView. Its meta-catalogue (api.panoramax.xyz) is keyless and CORS-open, so it runs in the browser: - coverage from its vector tiles: sequence lines at z11–14 below 30 km (longest first under the per-tile cap), and above that the grid layer's density cells as binned points, not counted as sequences; - a click on a line opens that sequence's picture nearest the click (STAC search, any direction), without moving the camera, then any sequence's if it has none within 50 m; the 360° and since filters go to the lines and to the API; - cones from the z15 tiles' pictures layer around the open picture (~110 bytes a picture, against ~7.5 kB for a STAC item), re-centred when a picture opens outside them; ‹ › follow the picture's prev/next links; - the caption names the author (not an account named by an email address) and the picture's licence (StreetPose `license`), and links to the picture in the Panoramax viewer; the globe credits Panoramax. KartaView's viewer becomes the shared photoViewer.js (flat <img>, 360° on panorama.js, ‹ ›, keyboard, pinch, context loss), driven by a per-provider PhotoSources table; the request limiter moves to limiter.js. Panoramax shows the 2,048 px SD derivative and fetches the HD original only when the viewer is wide enough, once, shrunk while it decodes (never above 8,192 px). Orange #ff6f00 (Panoramax's own), off by default, share option 0.m.0_0.r.panoramax-1. The MCP panel allows the meta-catalogue and the biggest instances. Tests: policy, decode, source, viewer, provider, and a Panoramax fixture in the hermetic browser gate. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c99eb93174
commit
00d2b73b6a
37 files changed
+4235
-711
No files matched your search
@@ -0,0 +1,129 @@
|
||||
// Panoramax stand-ins for tests and the hermetic QA gate: vector tiles in
|
||||
// the meta-catalogue's shape, and STAC items.
|
||||
import { PbfWriter } from 'pbf';
|
||||
import {
|
||||
layerWriter,
|
||||
lineGeometry,
|
||||
lonLatToTileLocal,
|
||||
zigzag,
|
||||
} from '../layers/streetLevel/providers/mapillary/coverageFixture.mjs';
|
||||
|
||||
/**
|
||||
* Encode a Panoramax `/api/map/{z}/{x}/{y}.mvt` tile from lon/lat features.
|
||||
* @param {{x: number, y: number, z: number}} tile
|
||||
* @param {{sequences?: Array<{id: string, date?: string, type?: string, parts: Array<Array<[number, number]>>}>, grid?: Array<{lon: number, lat: number, pano?: number, flat?: number}>, pictures?: Array<{id: string, lon: number, lat: number, sequenceId: string, heading?: number, ts?: string, type?: string}>, extent?: number}} [content]
|
||||
*/
|
||||
export function encodePanoramaxTile(
|
||||
tile,
|
||||
{ sequences = [], grid = [], pictures = [], extent = 4096 } = {},
|
||||
) {
|
||||
const pbf = new PbfWriter();
|
||||
if (sequences.length) {
|
||||
const layer = layerWriter('sequences', extent);
|
||||
for (const sequence of sequences)
|
||||
layer.add(
|
||||
2,
|
||||
lineGeometry(
|
||||
sequence.parts.map((part) =>
|
||||
part.map((point) => lonLatToTileLocal(point, tile, extent)),
|
||||
),
|
||||
),
|
||||
{
|
||||
id: sequence.id,
|
||||
type: sequence.type ?? 'flat',
|
||||
date: sequence.date ?? '2025-06-01',
|
||||
model: 'Test camera',
|
||||
},
|
||||
);
|
||||
pbf.writeMessage(3, (_, out) => layer.write(out), null);
|
||||
}
|
||||
const point = (lon, lat) => {
|
||||
const [px, py] = lonLatToTileLocal([lon, lat], tile, extent);
|
||||
return [9, zigzag(px), zigzag(py)];
|
||||
};
|
||||
if (grid.length) {
|
||||
const layer = layerWriter('grid', extent);
|
||||
for (const cell of grid) {
|
||||
const pano = cell.pano ?? 0;
|
||||
const flat = cell.flat ?? 1;
|
||||
layer.add(1, point(cell.lon, cell.lat), {
|
||||
nb_pictures: pano + flat,
|
||||
nb_360_pictures: pano,
|
||||
nb_flat_pictures: flat,
|
||||
});
|
||||
}
|
||||
pbf.writeMessage(3, (_, out) => layer.write(out), null);
|
||||
}
|
||||
if (pictures.length) {
|
||||
const layer = layerWriter('pictures', extent);
|
||||
for (const picture of pictures)
|
||||
layer.add(1, point(picture.lon, picture.lat), {
|
||||
id: picture.id,
|
||||
ts: picture.ts ?? '2025-06-01 10:00:00+00',
|
||||
heading: picture.heading ?? 90,
|
||||
sequences: JSON.stringify([picture.sequenceId]),
|
||||
first_sequence: picture.sequenceId,
|
||||
type: picture.type ?? 'flat',
|
||||
});
|
||||
pbf.writeMessage(3, (_, out) => layer.write(out), null);
|
||||
}
|
||||
return new Uint8Array(pbf.finish());
|
||||
}
|
||||
|
||||
/**
|
||||
* A STAC item as the meta-catalogue returns it.
|
||||
* @param {{id: string, lon: number, lat: number, sequenceId?: string, heading?: number, pano?: boolean, datetime?: string, producer?: string, license?: string, width?: number, prevId?: string|null, nextId?: string|null, host?: string}} options
|
||||
*/
|
||||
export function stacItem({
|
||||
id,
|
||||
lon,
|
||||
lat,
|
||||
sequenceId = 'seq-1',
|
||||
heading = 90,
|
||||
pano = false,
|
||||
datetime = '2025-06-01T10:00:00+00:00',
|
||||
producer = 'tester',
|
||||
license = 'CC-BY-SA-4.0',
|
||||
width = pano ? 5760 : 4032,
|
||||
prevId = null,
|
||||
nextId = null,
|
||||
host = 'https://panoramax.example.org',
|
||||
}) {
|
||||
const api = 'https://api.panoramax.xyz/api';
|
||||
const links = [
|
||||
{ rel: 'self', href: `${api}/collections/${sequenceId}/items/${id}` },
|
||||
];
|
||||
if (prevId)
|
||||
links.push({
|
||||
rel: 'prev',
|
||||
href: `${api}/collections/${sequenceId}/items/${prevId}`,
|
||||
});
|
||||
if (nextId)
|
||||
links.push({
|
||||
rel: 'next',
|
||||
href: `${api}/collections/${sequenceId}/items/${nextId}`,
|
||||
});
|
||||
return {
|
||||
type: 'Feature',
|
||||
id,
|
||||
collection: sequenceId,
|
||||
geometry: { type: 'Point', coordinates: [lon, lat] },
|
||||
providers: [{ name: producer, roles: ['producer'] }],
|
||||
properties: {
|
||||
datetime,
|
||||
license,
|
||||
'view:azimuth': heading,
|
||||
'geovisio:producer': producer,
|
||||
'pers:interior_orientation': {
|
||||
...(pano ? { field_of_view: 360 } : {}),
|
||||
sensor_array_dimensions: [width, pano ? width / 2 : 3024],
|
||||
},
|
||||
},
|
||||
assets: {
|
||||
hd: { href: `${host}/pictures/${id}/hd.jpg` },
|
||||
sd: { href: `${host}/pictures/${id}/sd.jpg` },
|
||||
thumb: { href: `${host}/pictures/${id}/thumb.jpg` },
|
||||
},
|
||||
links,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user