Skip to content
EN

Data Models

The main object shapes the API returns. Timestamps are epoch milliseconds unless noted.

interface Marker {
id: string;
latitude: number;
longitude: number;
title: string;
description?: string; // Markdown
color: string; // hex, e.g. "#3B82F6"
markerIconId?: string; // reference into the room icon registry
markerIconSize?: number; // per-marker size override (px)
tags?: string[];
createdBy: string;
createdAt: number;
eventDate?: number; // for the timeline
imageKey?: string; // storage key — fetch via .../markers/image?key=…
thumbnailKey?: string;
imageBlob?: string; // legacy inline image, superseded by imageKey
thumbnailBlob?: string; // legacy
embeddedMediaUrl?: string; // YouTube / Vimeo / external
mediaSize?: 'sm' | 'md' | 'lg'; // embedded-media popup width (default 'md')
imageMetadata?: ImageMetadata; // EXIF read from the attached photo — read-only
}
interface ImageMetadata {
gpsSource: boolean; // true when the marker sits where the photo was taken
cameraMake?: string;
cameraModel?: string;
dateTaken?: number; // epoch ms
altitude?: number;
orientation?: number;
}

imageMetadata is derived when a photo is attached in the app — a geotagged photo moves the marker to its GPS position and sets gpsSource. It is not settable over the API, and swapping imageKey/thumbnailKey clears it so a new photo never inherits the previous one’s provenance.

The single-marker endpoints (GET/PATCH /api/rooms/:roomId/markers/:markerId) omit createdByEmail and the inline blob fields (imageBlob, thumbnailBlob, iconBlob) from their responses; the list endpoint returns the stored shape as-is.

interface PolygonLayer {
id: string;
name: string;
description?: string;
geometryType?: string; // "Polygon" | "MultiPolygon" | "LineString" | ...
color: string;
opacity: number; // 0–1
strokeWeight: number; // px
visible: boolean;
totalFeatureCount?: number;
createdBy: string;
createdAt: number;
eventDate?: number;
bbox?: [number, number, number, number]; // [minLon, minLat, maxLon, maxLat]
}
interface RasterLayer {
id: string;
name: string;
description?: string;
fileSize: number; // original TIFF size, bytes
bounds: [number, number, number, number];
minZoom: number;
maxZoom: number;
opacity: number; // 0–1
visible: boolean;
createdAt: number;
eventDate?: number;
processingStatus?: 'pending' | 'processing' | 'complete' | 'error';
// display (see the Raster Layers reference)
colormap?: string;
vmin?: number;
vmax?: number;
band?: number; // 0 = natural color / all bands
renderMode?: 'tiles' | 'image_overlay' | 'rgb';
rgbBands?: [number, number, number]; // 1-based [R, G, B]
}
interface MarkerIcon {
id: string;
name: string;
iconBlob: string; // image data (base64)
category?: string;
size: number; // display height in px (default 27)
createdAt: number;
}
interface Room {
roomId: string;
createdBy: string; // owner's user ID
createdAt: number;
markerCount: number;
commitCount: number;
lastActivity?: number;
isPublic?: boolean;
}
interface RoomMember {
userId: string;
email: string;
name?: string;
role: 'owner' | 'editor' | 'viewer';
addedBy: string;
addedAt: number;
}