147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
import type { CategoryId, TravelMode } from "./osm-tags.js";
|
|
|
|
// ─── Cities ──────────────────────────────────────────────────────────────────
|
|
|
|
export type CityStatus = "pending" | "processing" | "ready" | "error" | "empty";
|
|
|
|
export interface City {
|
|
slug: string;
|
|
name: string;
|
|
countryCode: string;
|
|
geofabrikUrl: string;
|
|
bbox: [number, number, number, number]; // [minLng, minLat, maxLng, maxLat]
|
|
boundary?: object | null;
|
|
status: CityStatus;
|
|
lastIngested: string | null;
|
|
}
|
|
|
|
// ─── POIs ────────────────────────────────────────────────────────────────────
|
|
|
|
export interface Poi {
|
|
osmId: string;
|
|
osmType: "N" | "W" | "R";
|
|
category: CategoryId;
|
|
subcategory: string;
|
|
name: string | null;
|
|
lng: number;
|
|
lat: number;
|
|
}
|
|
|
|
// ─── Grid / Heatmap ──────────────────────────────────────────────────────────
|
|
|
|
export interface GridCell {
|
|
gridX: number;
|
|
gridY: number;
|
|
lng: number;
|
|
lat: number;
|
|
score: number;
|
|
categoryScores: Partial<Record<CategoryId, number>>;
|
|
}
|
|
|
|
export interface HeatmapPayload {
|
|
citySlug: string;
|
|
travelMode: TravelMode;
|
|
thresholdMin: number;
|
|
weights: Record<CategoryId, number>;
|
|
gridSpacingM: number;
|
|
cells: GridCell[];
|
|
generatedAt: string;
|
|
}
|
|
|
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
|
|
export interface CategoryStats {
|
|
category: CategoryId;
|
|
poiCount: number;
|
|
p10: number;
|
|
p25: number;
|
|
p50: number;
|
|
p75: number;
|
|
p90: number;
|
|
coveragePct: number;
|
|
}
|
|
|
|
export interface CityStats {
|
|
citySlug: string;
|
|
totalPois: number;
|
|
gridPointCount: number;
|
|
categoryStats: CategoryStats[];
|
|
}
|
|
|
|
// ─── Isochrones ──────────────────────────────────────────────────────────────
|
|
|
|
export interface IsochroneRequest {
|
|
lng: number;
|
|
lat: number;
|
|
travelMode: TravelMode;
|
|
contourMinutes: number[];
|
|
}
|
|
|
|
// ─── Job / Pipeline ──────────────────────────────────────────────────────────
|
|
|
|
export interface JobProgress {
|
|
stage: string;
|
|
pct: number;
|
|
message: string;
|
|
bytesDownloaded?: number;
|
|
totalBytes?: number;
|
|
}
|
|
|
|
export type JobState =
|
|
| "waiting"
|
|
| "active"
|
|
| "completed"
|
|
| "failed"
|
|
| "delayed"
|
|
| "waiting-children";
|
|
|
|
export interface JobSummary {
|
|
id: string;
|
|
type: string;
|
|
citySlug: string;
|
|
state: JobState;
|
|
progress: JobProgress | null;
|
|
failedReason: string | null;
|
|
createdAt: number;
|
|
finishedAt: number | null;
|
|
duration: number | null;
|
|
}
|
|
|
|
// ─── SSE Events ──────────────────────────────────────────────────────────────
|
|
|
|
export type RoutingDetail = Record<string, { done: number; total: number }>;
|
|
|
|
export type SSEEvent =
|
|
| { type: "progress"; stage: string; pct: number; message: string; bytesDownloaded?: number; totalBytes?: number; routingDetail?: RoutingDetail }
|
|
| { type: "completed"; jobId: string }
|
|
| { type: "failed"; jobId: string; error: string }
|
|
| { type: "heartbeat" };
|
|
|
|
// ─── Geofabrik ───────────────────────────────────────────────────────────────
|
|
|
|
export interface GeofabrikFeature {
|
|
type: "Feature";
|
|
properties: {
|
|
id: string;
|
|
parent?: string;
|
|
name: string;
|
|
urls: {
|
|
pbf: string;
|
|
"pbf.md5": string;
|
|
};
|
|
poly?: string;
|
|
};
|
|
geometry: GeoJSON.Polygon | GeoJSON.MultiPolygon | null;
|
|
}
|
|
|
|
export interface GeofabrikIndex {
|
|
type: "FeatureCollection";
|
|
features: GeofabrikFeature[];
|
|
}
|
|
|
|
// ─── API helpers ─────────────────────────────────────────────────────────────
|
|
|
|
export interface ApiError {
|
|
error: string;
|
|
code: string;
|
|
}
|