@osm-editor-kit/street-imagery 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ import type { Feature, Polygon } from 'geojson';
2
+ /** View cone radius in meters; ~50 m at z14, scales with zoom like click search radius. */
3
+ export declare const coneRadiusMeters: (zoom: number) => number;
4
+ export declare const viewConeGeoJson: (lngLat: [number, number], bearingDeg: number, fovDeg: number, radiusMeters: number) => Feature<Polygon>;
@@ -0,0 +1,24 @@
1
+ import type { Feature, FeatureCollection, Polygon } from 'geojson';
2
+ import type { NormalizedPhoto } from '../providers/model';
3
+ /** Narrow heading wedge for flat (directional) photos — iD-style viewfield. */
4
+ export declare const FLAT_VIEWFIELD_FOV_DEG = 55;
5
+ /** Full disk for 360° / equirectangular photos. */
6
+ export declare const PANO_VIEWFIELD_FOV_DEG = 360;
7
+ /** Lightweight triangle wedge (apex + left/right rays) for dense coverage layers. */
8
+ export declare const flatViewfieldTriangle: (lngLat: [number, number], bearingDeg: number, fovDeg: number, radiusMeters: number) => Feature<Polygon>;
9
+ export type ViewfieldPhotoProps = {
10
+ providerId: string;
11
+ photoId: string;
12
+ sequenceId: string | null;
13
+ capturedAt: number | null;
14
+ isPano: boolean | null;
15
+ heading: number | null;
16
+ };
17
+ /**
18
+ * Build viewfield polygons for every photo that can show direction:
19
+ * - panorama → full 360° disk
20
+ * - flat with heading → narrow triangle wedge
21
+ * Photos without heading that are not panos are skipped.
22
+ */
23
+ export declare const photosToViewfieldsFeatureCollection: (photos: NormalizedPhoto[], zoom: number) => FeatureCollection<Polygon, ViewfieldPhotoProps>;
24
+ export declare const emptyPolygonCollection: () => FeatureCollection<Polygon>;
@@ -0,0 +1,18 @@
1
+ import type { NormalizedPhoto, ProviderAdapter } from '../model';
2
+ export type KartaviewItem = {
3
+ id?: string | number;
4
+ sequence_id?: string | number;
5
+ sequence_index?: string | number;
6
+ name?: string;
7
+ lth_name?: string;
8
+ heading?: string | number;
9
+ shot_date?: string;
10
+ date_added?: string;
11
+ lat?: string | number;
12
+ lng?: string | number;
13
+ };
14
+ export declare const kartaviewImageUrl: (imagePath: string) => string;
15
+ export declare const maxPageAtZoom: (z: number) => number;
16
+ export declare const parseKartaviewDate: (value: unknown) => number | null;
17
+ export declare const normalizeKartaviewItem: (item: KartaviewItem) => Omit<NormalizedPhoto, 'providerId'> | null;
18
+ export declare const kartaviewAdapter: ProviderAdapter;
@@ -0,0 +1,17 @@
1
+ import type { ProviderAdapter } from '../model';
2
+ /**
3
+ * Encode Apple Maps Look Around `_mvs` view state.
4
+ * Share links from Maps include this protobuf; without it, `/look-around?coordinate=`
5
+ * opens the place/map card instead of Look Around.
6
+ *
7
+ * Layout: length-delimited message with little-endian fixed64 fields
8
+ * 1=latitude, 2=longitude, 3=altitude (meters).
9
+ */
10
+ export declare const encodeLookAroundMvs: (lat: number, lng: number, altitudeMeters?: number) => string;
11
+ /** Apple Maps Look Around deep link (no API key). Includes `_mvs` so Maps opens Look Around. */
12
+ export declare const lookAroundDeepLink: (lat: number, lng: number) => string;
13
+ /**
14
+ * Link-out-only provider: no official bulk coverage listing API.
15
+ * Map clicks open Apple Maps Look Around at the clicked coordinate.
16
+ */
17
+ export declare const lookaroundAdapter: ProviderAdapter;
@@ -0,0 +1,7 @@
1
+ import type { Feature } from 'geojson';
2
+ import type { NormalizedPhoto, NormalizedSequence, ProviderAdapter } from '../model';
3
+ export declare const parseMapilioCaptureTime: (value: unknown) => number | null;
4
+ export declare const parseMapilioResolution: (resolution: unknown) => boolean | null;
5
+ export declare const normalizeMapilioPointFeature: (feature: Feature) => Omit<NormalizedPhoto, 'providerId'> | null;
6
+ export declare const normalizeMapilioRoadFeature: (feature: Feature) => Omit<NormalizedSequence, 'providerId'> | null;
7
+ export declare const mapilioAdapter: ProviderAdapter;
@@ -0,0 +1,4 @@
1
+ import type { Feature } from 'geojson';
2
+ import type { NormalizedMapFeature, ProviderAdapter } from '../model';
3
+ export declare const normalizeMapillaryMapFeature: (feature: Feature) => Omit<NormalizedMapFeature, 'providerId'> | null;
4
+ export declare const mapillaryMapFeaturesAdapter: ProviderAdapter;
@@ -0,0 +1,4 @@
1
+ import type { Feature } from 'geojson';
2
+ import type { NormalizedMapFeature, ProviderAdapter } from '../model';
3
+ export declare const normalizeMapillaryTrafficSignFeature: (feature: Feature) => Omit<NormalizedMapFeature, 'providerId'> | null;
4
+ export declare const mapillarySignsAdapter: ProviderAdapter;
@@ -0,0 +1,5 @@
1
+ import type { Feature } from 'geojson';
2
+ import type { NormalizedPhoto, NormalizedSequence, ProviderAdapter } from '../model';
3
+ export declare const normalizeMapillaryImageFeature: (feature: Feature) => Omit<NormalizedPhoto, 'providerId'> | null;
4
+ export declare const normalizeMapillarySequenceFeature: (feature: Feature) => Omit<NormalizedSequence, 'providerId'> | null;
5
+ export declare const mapillaryAdapter: ProviderAdapter;
@@ -0,0 +1,6 @@
1
+ import type { Feature } from 'geojson';
2
+ import type { NormalizedPhoto, NormalizedSequence, ProviderAdapter } from '../model';
3
+ export declare const parsePanoramaxTimestamp: (value: unknown) => number | null;
4
+ export declare const normalizePanoramaxPictureFeature: (feature: Feature) => Omit<NormalizedPhoto, 'providerId'> | null;
5
+ export declare const normalizePanoramaxSequenceFeature: (feature: Feature) => Omit<NormalizedSequence, 'providerId'> | null;
6
+ export declare const panoramaxAdapter: ProviderAdapter;
@@ -0,0 +1,16 @@
1
+ import type { NormalizedPhoto, ProviderAdapter } from '../model';
2
+ export type StreetsideBubble = {
3
+ id?: string;
4
+ imageUrl?: string;
5
+ imageUrlSubdomains?: string[];
6
+ lon?: number;
7
+ longitude?: number;
8
+ lat?: number;
9
+ latitude?: number;
10
+ he?: number;
11
+ heading?: number;
12
+ vintageEnd?: string;
13
+ };
14
+ export declare const parseStreetsideDate: (value: unknown) => number | null;
15
+ export declare const normalizeStreetsideBubble: (bubble: StreetsideBubble) => Omit<NormalizedPhoto, 'providerId'> | null;
16
+ export declare const streetsideAdapter: ProviderAdapter;
@@ -0,0 +1,16 @@
1
+ import type { NormalizedPhoto, ProviderAdapter } from '../model';
2
+ export type StreetViewMetadataResponse = {
3
+ status: string;
4
+ copyright?: string;
5
+ date?: string;
6
+ location?: {
7
+ lat?: number;
8
+ lng?: number;
9
+ };
10
+ pano_id?: string;
11
+ };
12
+ export declare const getGoogleMapsApiKey: () => string | undefined;
13
+ export declare const parseStreetViewDate: (value: unknown) => number | null;
14
+ export declare const normalizeStreetViewMetadata: (data: StreetViewMetadataResponse, clickLng: number, clickLat: number) => Omit<NormalizedPhoto, 'providerId'> | null;
15
+ export declare const fetchStreetViewMetadata: (lat: number, lng: number, signal: AbortSignal) => Promise<NormalizedPhoto | null>;
16
+ export declare const streetViewAdapter: ProviderAdapter;
@@ -0,0 +1,14 @@
1
+ import type { Feature } from 'geojson';
2
+ import type { Bbox, NormalizedPhoto, ProviderAdapter } from '../model';
3
+ export type VegbilderLayerInfo = {
4
+ name: string;
5
+ is360: boolean;
6
+ year: number;
7
+ };
8
+ export declare const parseVegbilderLayerNames: (capabilitiesXml: string) => VegbilderLayerInfo[];
9
+ export declare const selectVegbilderLayers: (layers: VegbilderLayerInfo[], minYear?: number, maxLayers?: number) => string[];
10
+ export declare const parseVegbilderTimestamp: (value: unknown) => number | null;
11
+ export declare const buildVegbilderSequenceId: (props: Record<string, unknown>) => string | null;
12
+ export declare const normalizeVegbilderFeature: (feature: Feature, layerYear?: number) => Omit<NormalizedPhoto, 'providerId'> | null;
13
+ export declare const intersectsNorway: (bbox: Bbox) => boolean;
14
+ export declare const vegbilderAdapter: ProviderAdapter;
@@ -0,0 +1,5 @@
1
+ import type { Feature } from 'geojson';
2
+ import type { TileCoord } from './model';
3
+ export type MvtLayers = Record<string, Feature[]>;
4
+ export declare const pointLngLat: (feature: Feature) => [number, number] | null;
5
+ export declare const fetchMvt: (url: string, tile: TileCoord, signal: AbortSignal) => Promise<MvtLayers>;
@@ -0,0 +1,6 @@
1
+ import { pointLngLat } from './fetchMvt';
2
+ import type { Bbox, TileCoord } from './model';
3
+ export { pointLngLat };
4
+ export declare const MAPILLARY_TILE_ZOOM = 14;
5
+ export declare const mapillaryTileUrl: (path: string, tile: TileCoord) => string;
6
+ export declare const fetchMapillaryMvtTiles: (cachePrefix: string, path: string, bbox: Bbox, signal: AbortSignal) => Promise<import("./fetchMvt").MvtLayers[]>;
@@ -0,0 +1,67 @@
1
+ import type { LineString, MultiLineString } from 'geojson';
2
+ export declare const PROVIDER_IDS: readonly ['mapillary', 'panoramax', 'kartaview', 'mapilio', 'streetside', 'vegbilder', 'streetview', 'lookaround', 'mapillary-signs', 'mapillary-map-features'];
3
+ export type ProviderId = (typeof PROVIDER_IDS)[number];
4
+ /** Bounding box as [west, south, east, north] in WGS84 degrees. */
5
+ export type Bbox = [west: number, south: number, east: number, north: number];
6
+ export type TileCoord = {
7
+ z: number;
8
+ x: number;
9
+ y: number;
10
+ };
11
+ export type ProviderKind = 'photo' | 'mapFeature';
12
+ export type NormalizedPhoto = {
13
+ providerId: ProviderId;
14
+ photoId: string;
15
+ sequenceId: string | null;
16
+ /** KartaView sequence_index for deep links; undefined for other providers. */
17
+ sequenceIndex?: number;
18
+ capturedAt: number | null;
19
+ isPano: boolean | null;
20
+ heading: number | null;
21
+ lngLat: [number, number];
22
+ /** Direct thumbnail URL when the list API already provides one. */
23
+ thumbUrl?: string;
24
+ /** Full-resolution image URL when the list API already provides one. */
25
+ fullUrl?: string;
26
+ /** Vegbilder atlas year for external viewer links. */
27
+ viewerYear?: number;
28
+ /** Bing Streetside cubemap tile template from the metadata API. */
29
+ streetside?: {
30
+ urlTemplate: string;
31
+ subdomains: string[];
32
+ };
33
+ };
34
+ export type NormalizedMapFeature = {
35
+ providerId: ProviderId;
36
+ featureId: string;
37
+ value: string;
38
+ firstSeenAt: number | null;
39
+ lastSeenAt: number | null;
40
+ lngLat: [number, number];
41
+ };
42
+ export type NormalizedSequence = {
43
+ providerId: ProviderId;
44
+ sequenceId: string;
45
+ geometry: LineString | MultiLineString;
46
+ /** Sequence capture time (ms), when the tile/API provides it (e.g. Mapillary). */
47
+ capturedAt: number | null;
48
+ /** Whether the sequence is panoramic, when known. */
49
+ isPano: boolean | null;
50
+ };
51
+ export type ProviderAdapter = {
52
+ id: ProviderId;
53
+ kind: ProviderKind;
54
+ label: string;
55
+ color: string;
56
+ /** Minimum map zoom before photo markers are fetched. */
57
+ minZoom: number;
58
+ /** Minimum map zoom before sequence lines are fetched (defaults to minZoom). */
59
+ sequencesMinZoom?: number;
60
+ /** When false, provider is omitted from the default enabled set. */
61
+ defaultEnabled?: boolean;
62
+ /** When set, map layers cannot load in the browser (e.g. upstream CORS). */
63
+ browserUnavailableReason?: string;
64
+ fetchPhotos?: (bbox: Bbox, zoom: number, signal: AbortSignal) => Promise<NormalizedPhoto[]>;
65
+ fetchSequences?: (bbox: Bbox, zoom: number, signal: AbortSignal) => Promise<NormalizedSequence[]>;
66
+ fetchMapFeatures?: (bbox: Bbox, zoom: number, signal: AbortSignal) => Promise<NormalizedMapFeature[]>;
67
+ };
@@ -0,0 +1,29 @@
1
+ import type { ProviderAdapter, ProviderId, ProviderKind } from './model';
2
+ export type ProviderMeta = {
3
+ id: ProviderId;
4
+ kind: ProviderKind;
5
+ label: string;
6
+ color: string;
7
+ minZoom: number;
8
+ sequencesMinZoom: number;
9
+ homepageUrl?: string;
10
+ browserUnavailableReason?: string;
11
+ };
12
+ export declare const PROVIDER_ADAPTERS: ProviderAdapter[];
13
+ export declare const adapterById: Record<ProviderId, ProviderAdapter>;
14
+ export declare const PROVIDERS: ProviderMeta[];
15
+ export declare const DEFAULT_PROVIDER_IDS: ProviderId[];
16
+ export declare const isClickOnlyPhotoProvider: (providerId: ProviderId) => boolean;
17
+ export declare const isBrowserAvailableProvider: (providerId: ProviderId) => boolean;
18
+ export declare const providerById: Record<ProviderId, ProviderMeta>;
19
+ export declare const photoLayerId: (providerId: ProviderId) => string;
20
+ export declare const featureLayerId: (providerId: ProviderId) => string;
21
+ export declare const sequenceLayerId: (providerId: ProviderId) => string;
22
+ export declare const viewfieldLayerId: (providerId: ProviderId) => string;
23
+ export declare const viewfieldLineLayerId: (providerId: ProviderId) => string;
24
+ export declare const photoSourceId: (providerId: ProviderId) => string;
25
+ export declare const featureSourceId: (providerId: ProviderId) => string;
26
+ export declare const sequenceSourceId: (providerId: ProviderId) => string;
27
+ export declare const viewfieldSourceId: (providerId: ProviderId) => string;
28
+ export declare const isPhotoProviderId: (providerId: ProviderId) => boolean;
29
+ export declare const isMapFeatureProviderId: (providerId: ProviderId) => boolean;
@@ -0,0 +1,9 @@
1
+ export declare const getTileCacheKey: (providerId: string, tile: {
2
+ z: number;
3
+ x: number;
4
+ y: number;
5
+ }) => string;
6
+ export declare const fetchTileCached: <T>(key: string, fetcher: (signal: AbortSignal) => Promise<T>, signal: AbortSignal) => Promise<T>;
7
+ /** Await all tile fetches, tolerating partial failures. Throws only when every tile failed. */
8
+ export declare const collectSettledTiles: <T>(promises: Promise<T>[]) => Promise<Awaited<T>[]>;
9
+ export declare const clearTileCache: () => void;
@@ -0,0 +1,16 @@
1
+ import type { Bbox, TileCoord } from './model';
2
+ export declare const lonToTileX: (lon: number, z: number) => number;
3
+ export declare const latToTileY: (lat: number, z: number) => number;
4
+ export declare const tileToLon: (x: number, z: number) => number;
5
+ export declare const tileToLat: (y: number, z: number) => number;
6
+ export declare const tileBbox: (tile: TileCoord) => Bbox;
7
+ export declare const clampZoom: (zoom: number, min: number, max: number) => number;
8
+ type TilesForBboxOptions = {
9
+ skipNullIsland?: boolean;
10
+ };
11
+ /** Null Island guard — skip tiles whose center lies within ~1° of (0, 0). */
12
+ export declare const isNullIslandTile: (tile: TileCoord) => boolean;
13
+ export declare const tilesForBbox: (bbox: Bbox, z: number, options?: TilesForBboxOptions) => TileCoord[];
14
+ export declare const bboxIntersects: (a: Bbox, b: Bbox) => boolean;
15
+ export declare const tilePixelSizeAtZoom: (zoom: number) => number;
16
+ export {};
@@ -0,0 +1,4 @@
1
+ /** Great-circle distance in meters between two WGS84 points. */
2
+ export declare const haversineDistanceMeters: (lng1: number, lat1: number, lng2: number, lat2: number) => number;
3
+ /** Click search radius in meters; ~50 m at z14, scales with zoom. */
4
+ export declare const clickRadiusMeters: (zoom: number) => number;
@@ -0,0 +1,5 @@
1
+ import type { NormalizedMapFeature, NormalizedPhoto, ProviderId } from '../providers/model';
2
+ export declare const providerExternalLink: (photo: NormalizedPhoto) => string;
3
+ export declare const mapFeatureExternalLink: (feature: NormalizedMapFeature) => string;
4
+ /** Deep link to a provider's viewer at a map location (no specific photo selected). */
5
+ export declare const providerLocationLink: (providerId: ProviderId, lat: number, lng: number, zoom?: number) => string;
@@ -0,0 +1,20 @@
1
+ import type { NormalizedPhoto, ProviderId } from '../providers/model';
2
+ export type PhotoSequenceGroup = {
3
+ providerId: ProviderId;
4
+ sequenceId: string;
5
+ groupKey: string;
6
+ photos: NormalizedPhoto[];
7
+ distanceMeters: number;
8
+ };
9
+ export declare const sequenceGroupKey: (providerId: ProviderId, sequenceId: string) => string;
10
+ export declare const photoGroupSequenceId: (photo: NormalizedPhoto) => string;
11
+ export declare const distanceToPhoto: (photo: NormalizedPhoto, lng: number, lat: number) => number;
12
+ export declare const findNearestPhoto: (photos: NormalizedPhoto[], lng: number, lat: number) => NormalizedPhoto | null;
13
+ /** Remove photos within minDistanceMeters of an earlier photo in the same sequence. */
14
+ export declare const dedupeNearbyPhotos: (photos: NormalizedPhoto[], minDistanceMeters?: number) => NormalizedPhoto[];
15
+ export declare const groupClickedPhotos: (photos: NormalizedPhoto[], lng: number, lat: number) => PhotoSequenceGroup[];
16
+ export declare const findGroupBySelection: (groups: PhotoSequenceGroup[], providerId: ProviderId, sequenceId: string) => PhotoSequenceGroup | null;
17
+ export declare const findPhotoIndexInGroup: (group: PhotoSequenceGroup, photoId: string) => number;
18
+ export declare const STREETSIDE_NEARBY_LIMIT = 15;
19
+ /** Nearby Streetside bubbles across all clicked groups, sorted by distance to the active photo. */
20
+ export declare const collectNearbyStreetsidePhotos: (groups: PhotoSequenceGroup[], activePhoto: NormalizedPhoto, limit?: number) => NormalizedPhoto[];
@@ -0,0 +1,17 @@
1
+ import type { NormalizedPhoto } from '../providers/model';
2
+ type MapilioSequenceItem = {
3
+ id?: number | string;
4
+ filename?: string;
5
+ uploaded_hash?: string;
6
+ };
7
+ export declare const fetchMapilioSequenceItem: (sequenceId: string, photoId: string) => Promise<MapilioSequenceItem | null>;
8
+ export declare const fetchMapillaryThumbnail: (photoId: string) => Promise<string | null>;
9
+ export declare const fetchPanoramaxThumbnail: (sequenceId: string, photoId: string) => Promise<string | null>;
10
+ export declare const fetchMapilioThumbnail: (sequenceId: string, photoId: string) => Promise<string | null>;
11
+ export declare const fetchMapilioFullUrl: (sequenceId: string, photoId: string) => Promise<string | null>;
12
+ export declare const resolvePhotoFullUrl: (photo: NormalizedPhoto) => Promise<string | null>;
13
+ export declare const resolvePhotoPanoramaUrl: (photo: NormalizedPhoto) => Promise<string | null>;
14
+ export declare const resolvePhotoThumbnailUrl: (photo: NormalizedPhoto) => Promise<string | null>;
15
+ export declare const photoFullUrlQueryKey: (photo: NormalizedPhoto) => readonly ['photo-full-url', "kartaview" | "lookaround" | "mapilio" | "mapillary" | "mapillary-map-features" | "mapillary-signs" | "panoramax" | "streetside" | "streetview" | "vegbilder", string | null, string];
16
+ export declare const photoThumbnailQueryKey: (photo: NormalizedPhoto) => readonly ['photo-thumbnail', "kartaview" | "lookaround" | "mapilio" | "mapillary" | "mapillary-map-features" | "mapillary-signs" | "panoramax" | "streetside" | "streetview" | "vegbilder", string | null, string];
17
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Streetside preview strategy: when the API returns an imageUrl template we substitute
3
+ * `{subdomain}` → `t0`, `{faceId}` → `01`, and `{tileId}` → empty string to request a
4
+ * single cubemap face tile. This is a best-effort static preview — cubemap stitching is
5
+ * not implemented in v1. When the template is missing or substitution fails, the viewer
6
+ * shows metadata + "Open in Bing Maps" instead of an image.
7
+ */
8
+ export declare const buildStreetsidePreviewUrl: (imageUrlTemplate: string | undefined) => string | null;
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@osm-editor-kit/street-imagery",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Street-level imagery providers, GeoJSON, viewfields, tiles, and viewer helpers (Mapillary, Panoramax, and more).",
5
+ "license": "MIT",
6
+ "author": "Tobias Jordans",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/osmberlin/street-space-editor.git",
10
+ "directory": "packages/street-imagery"
11
+ },
12
+ "type": "module",
13
+ "sideEffects": false,
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "tag": "alpha"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup && rm -f .tsbuildinfo && tsc -p tsconfig.build.json",
30
+ "type-check": "tsc --noEmit",
31
+ "test-run": "bun test src",
32
+ "prepublishOnly": "bun run build && bun ../../scripts/package-exports-for-publish.ts",
33
+ "postpublish": "bun ../../scripts/package-exports-restore.ts",
34
+ "prepack": "bun ../../scripts/package-exports-for-publish.ts",
35
+ "postpack": "bun ../../scripts/package-exports-restore.ts"
36
+ },
37
+ "dependencies": {
38
+ "@mapbox/vector-tile": "^3.0.0",
39
+ "pbf": "^5.1.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/geojson": "^7946.0.16",
43
+ "bun-types": "^1.2.20",
44
+ "maplibre-gl": "^5.7.1",
45
+ "tsup": "^8.5.1",
46
+ "typescript": "^7.0.0",
47
+ "vite": "^8.0.0"
48
+ },
49
+ "publishExports": {
50
+ ".": {
51
+ "types": "./dist/index.d.ts",
52
+ "import": "./dist/index.js"
53
+ },
54
+ "./package.json": "./package.json"
55
+ }
56
+ }