import type { LayerSpecification, LightSpecification, SourceSpecification } from "maplibre-gl"; import { z } from "zod"; import { INTERACTIVE_HIT_LAYER_IDS } from "./layers"; import { MAP_STYLE_TOKENS as colors } from "./map-colors"; export const CITY_ROOT = "/city-model"; export const CITY_PREFIX = "fengxian-city-"; export const CITY_BUILDING_LAYER_ID = CITY_PREFIX + "buildings"; export const CITY_LIGHT = { anchor: "map", position: [1.5, 225, 35], color: "#EAF2F4", intensity: 0.42 } satisfies LightSpecification; export function findCitySceneInsertionLayerId(layers: readonly LayerSpecification[]) { // Layers painted after a 3D extrusion bypass depth testing, so ground overlays stay below it. const hitLayerIds = new Set(INTERACTIVE_HIT_LAYER_IDS); return layers.find((layer) => hitLayerIds.has(layer.id))?.id; } export const cityManifestSchema = z .object({ schema_version: z.literal(1), name: z.string(), generated_at: z.string().datetime({ offset: true }).optional(), bbox: z.tuple([ z.number().min(-180).max(180), z.number().min(-90).max(90), z.number().min(-180).max(180), z.number().min(-90).max(90) ]), building_count: z.number().int().nonnegative(), pipe_count: z.number().int().nonnegative(), asset_count: z.number().int().nonnegative(), notice: z.string(), attribution: z.string(), tiles: z .object({ buildings: z.object({ sha256: z .string() .regex(/^[a-f0-9]{64}$/) .optional() }) }) .optional() }) .refine(({ bbox: [w, s, e, n] }) => w < e && s < n, "Invalid scene bounds"); export function cityBuildingArchiveUrl(origin = window.location.origin) { return new URL(`${CITY_ROOT}/buildings.pmtiles`, origin).href; } export function citySources(): Record { return { [CITY_PREFIX + "buildings"]: { type: "vector", url: `pmtiles://${cityBuildingArchiveUrl()}`, minzoom: 12, maxzoom: 16, attribution: '© OpenStreetMap contributors · Overture Maps', promoteId: "id" } }; } export const CITY_LAYERS: LayerSpecification[] = [ { id: CITY_BUILDING_LAYER_ID, source: CITY_PREFIX + "buildings", "source-layer": "buildings", type: "fill-extrusion", minzoom: 12, paint: { "fill-extrusion-color": colors.canvas.secondary, "fill-extrusion-height": ["to-number", ["get", "height_m"], 6], "fill-extrusion-base": 0, "fill-extrusion-opacity": 1, "fill-extrusion-vertical-gradient": true } } ];