feat(workbench): integrate operational workflows and map state
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import maplibregl, { type Map as MapLibreMap } from "maplibre-gl";
|
||||
import {
|
||||
CITY_LAYERS,
|
||||
CITY_LIGHT,
|
||||
CITY_ROOT,
|
||||
cityManifestSchema,
|
||||
citySources,
|
||||
@@ -37,9 +38,10 @@ class CitySceneControl {
|
||||
private addLayers() {
|
||||
const map = this.map;
|
||||
if (!map) return;
|
||||
map.setLight(CITY_LIGHT);
|
||||
Object.entries(citySources()).forEach(([id, source]) => map.addSource(id, source));
|
||||
const firstBusinessLayer = findCitySceneInsertionLayerId(map.getStyle().layers);
|
||||
CITY_LAYERS.forEach((layer) => map.addLayer(layer, firstBusinessLayer));
|
||||
const firstHitLayer = findCitySceneInsertionLayerId(map.getStyle().layers);
|
||||
CITY_LAYERS.forEach((layer) => map.addLayer(layer, firstHitLayer));
|
||||
this.attribution = new maplibregl.AttributionControl({
|
||||
compact: true,
|
||||
customAttribution:
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { LayerSpecification } from "maplibre-gl";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
CITY_LAYERS,
|
||||
CITY_LIGHT,
|
||||
cityManifestSchema,
|
||||
citySources,
|
||||
findCitySceneInsertionLayerId
|
||||
@@ -18,7 +19,7 @@ const manifest = {
|
||||
attribution: "OSM"
|
||||
};
|
||||
describe("city scene geographic contract", () => {
|
||||
it("inserts buildings above every basemap layer and below business layers", () => {
|
||||
it("inserts buildings after business layers and before transparent hit layers", () => {
|
||||
const layers = [
|
||||
{ id: "background", type: "background" },
|
||||
{ id: "mapbox-light", type: "raster", source: "mapbox-light" },
|
||||
@@ -28,16 +29,26 @@ describe("city scene geographic contract", () => {
|
||||
type: "raster",
|
||||
source: "tianditu-vector-annotation"
|
||||
},
|
||||
{ id: "pipes", type: "line", source: "pipes", "source-layer": "pipes" }
|
||||
{ id: "pipes-flow", type: "line", source: "pipes", "source-layer": "pipes" },
|
||||
{ id: "pipes-hover", type: "line", source: "pipes", "source-layer": "pipes" },
|
||||
{ id: "pipes-hit", type: "line", source: "pipes", "source-layer": "pipes" }
|
||||
] satisfies LayerSpecification[];
|
||||
|
||||
expect(findCitySceneInsertionLayerId(layers)).toBe("pipes");
|
||||
expect(findCitySceneInsertionLayerId(layers)).toBe("pipes-hit");
|
||||
});
|
||||
|
||||
it("adds only the building background and leaves business layers to their existing controls", () => {
|
||||
expect(Object.keys(citySources())).toEqual(["fengxian-city-buildings"]);
|
||||
expect(CITY_LAYERS.map((layer) => layer.id)).toEqual(["fengxian-city-buildings"]);
|
||||
});
|
||||
it("uses a stable directional light for the engineering white model", () => {
|
||||
expect(CITY_LIGHT).toEqual({
|
||||
anchor: "map",
|
||||
position: [1.5, 225, 35],
|
||||
color: "#EAF2F4",
|
||||
intensity: 0.42
|
||||
});
|
||||
});
|
||||
it("accepts WGS84 scene bounds", () => {
|
||||
expect(cityManifestSchema.safeParse(manifest).success).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import type { LayerSpecification, SourceSpecification } from "maplibre-gl";
|
||||
import type { LayerSpecification, LightSpecification, SourceSpecification } from "maplibre-gl";
|
||||
import { z } from "zod";
|
||||
import { BASE_LAYER_IDS } from "./map-control-config";
|
||||
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[]) {
|
||||
const basemapLayerIds = new Set<string>(BASE_LAYER_IDS);
|
||||
return layers.find((layer) => layer.id !== "background" && !basemapLayerIds.has(layer.id))?.id;
|
||||
// Layers painted after a 3D extrusion bypass depth testing, so ground overlays stay below it.
|
||||
const hitLayerIds = new Set<string>(INTERACTIVE_HIT_LAYER_IDS);
|
||||
return layers.find((layer) => hitLayerIds.has(layer.id))?.id;
|
||||
}
|
||||
|
||||
export const cityManifestSchema = z
|
||||
@@ -44,7 +52,7 @@ export function citySources(): Record<string, SourceSpecification> {
|
||||
|
||||
export const CITY_LAYERS: LayerSpecification[] = [
|
||||
{
|
||||
id: CITY_PREFIX + "buildings",
|
||||
id: CITY_BUILDING_LAYER_ID,
|
||||
source: CITY_PREFIX + "buildings",
|
||||
"source-layer": "buildings",
|
||||
type: "fill-extrusion",
|
||||
|
||||
@@ -26,6 +26,7 @@ export async function exportMapViewImage(map: MapLibreMap, { scale, targetLongEd
|
||||
const exportHeight = Math.round(sourceHeight * exportScale);
|
||||
const container = createExportContainer(exportWidth, exportHeight);
|
||||
const style = cloneMapStyle(map.getStyle());
|
||||
style.light = map.getLight();
|
||||
const bounds = map.getBounds();
|
||||
|
||||
document.body.appendChild(container);
|
||||
@@ -37,6 +38,7 @@ export async function exportMapViewImage(map: MapLibreMap, { scale, targetLongEd
|
||||
zoom: map.getZoom(),
|
||||
pitch: map.getPitch(),
|
||||
bearing: map.getBearing(),
|
||||
antialias: true,
|
||||
preserveDrawingBuffer: true,
|
||||
interactive: false,
|
||||
attributionControl: false,
|
||||
|
||||
Reference in New Issue
Block a user