feat(map): refine sector inspector styling
This commit is contained in:
@@ -316,6 +316,11 @@ export type GridLodProfile = {
|
||||
volumeMinimumHeightMeters: number;
|
||||
volumeMaximumHeightMeters: number;
|
||||
volumeBandHeightMeters: number;
|
||||
selectionFillColor: string;
|
||||
selectionFillOpacityPercent: number;
|
||||
selectionOutlineColor: string;
|
||||
selectionOutlineWidthPx: number;
|
||||
selectionOutlineOpacityPercent: number;
|
||||
};
|
||||
|
||||
export type GridVolumeSelection = {
|
||||
@@ -1006,6 +1011,11 @@ type LocalGridAddressing = {
|
||||
volumeMinimumHeightMeters: number;
|
||||
volumeMaximumHeightMeters: number;
|
||||
volumeBandHeightMeters: number;
|
||||
selectionFillColor: string;
|
||||
selectionFillOpacityPercent: number;
|
||||
selectionOutlineColor: string;
|
||||
selectionOutlineWidthPx: number;
|
||||
selectionOutlineOpacityPercent: number;
|
||||
};
|
||||
type GraticuleGridAddressing = {
|
||||
mode: "graticule";
|
||||
@@ -1017,6 +1027,11 @@ type GraticuleGridAddressing = {
|
||||
lineColor: string;
|
||||
lineOpacity: number;
|
||||
majorLineWidthMultiplier: number;
|
||||
selectionFillColor: string;
|
||||
selectionFillOpacityPercent: number;
|
||||
selectionOutlineColor: string;
|
||||
selectionOutlineWidthPx: number;
|
||||
selectionOutlineOpacityPercent: number;
|
||||
};
|
||||
type GridAddressing = LocalGridAddressing | GraticuleGridAddressing;
|
||||
type GridResources = {
|
||||
@@ -1525,7 +1540,56 @@ function materializeGridLayer(plan: Exclude<GridLayerPlan, HiddenGridPlan>, seri
|
||||
return plan.mode === "3d" ? materializeLocalGrid(plan, serial) : materializeGraticule(plan, serial);
|
||||
}
|
||||
|
||||
const GRID_SELECTION_COLOR = Color.fromCssColorString("#35CFFF");
|
||||
const DEFAULT_GRID_SELECTION_COLOR = "#35cfff";
|
||||
|
||||
type GridSelectionStyle = {
|
||||
fillColor: Color;
|
||||
outlineColor: Color;
|
||||
outlineWidthPixels: number;
|
||||
};
|
||||
|
||||
function safeGridSelectionColor(value: unknown) {
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
try {
|
||||
const parsed = Color.fromCssColorString(value.trim());
|
||||
if (parsed) return parsed;
|
||||
} catch {
|
||||
// Persisted profiles may predate strict colour validation. Rendering a
|
||||
// stable selection is preferable to failing the whole Cesium layer.
|
||||
}
|
||||
}
|
||||
return Color.fromCssColorString(DEFAULT_GRID_SELECTION_COLOR);
|
||||
}
|
||||
|
||||
function safeGridSelectionPercent(value: unknown, fallback: number) {
|
||||
const candidate = typeof value === "number" ? value : Number.NaN;
|
||||
return clamp(Number.isFinite(candidate) ? candidate : fallback, 0, 100) / 100;
|
||||
}
|
||||
|
||||
function resolveGridSelectionStyle(addressing: GridAddressing): GridSelectionStyle {
|
||||
const fallbackOutlineWidth = addressing.mode === "3d"
|
||||
? clamp(2.5 * addressing.majorLineWidthMultiplier, 3, 12)
|
||||
: clamp(addressing.lineWidthPixels * addressing.majorLineWidthMultiplier * 1.5, 3, 12);
|
||||
const requestedOutlineWidth = typeof addressing.selectionOutlineWidthPx === "number"
|
||||
? addressing.selectionOutlineWidthPx
|
||||
: Number.NaN;
|
||||
const fallbackFillOpacityPercent = addressing.mode === "graticule"
|
||||
? 12
|
||||
: addressing.volumeEnabled ? 10 : 16;
|
||||
return {
|
||||
fillColor: safeGridSelectionColor(addressing.selectionFillColor).withAlpha(
|
||||
safeGridSelectionPercent(addressing.selectionFillOpacityPercent, fallbackFillOpacityPercent),
|
||||
),
|
||||
outlineColor: safeGridSelectionColor(addressing.selectionOutlineColor).withAlpha(
|
||||
safeGridSelectionPercent(addressing.selectionOutlineOpacityPercent, 95),
|
||||
),
|
||||
outlineWidthPixels: clamp(
|
||||
Number.isFinite(requestedOutlineWidth) ? requestedOutlineWidth : fallbackOutlineWidth,
|
||||
1,
|
||||
12,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function sampledRange(start: number, end: number, maximumStep: number) {
|
||||
const span = end - start;
|
||||
@@ -1562,6 +1626,7 @@ function materializeLocalSelection(
|
||||
|
||||
const dataSource = new CustomDataSource(`nodedc-map-grid-selection-local:${serial}`);
|
||||
const crosses = new PolylineCollection();
|
||||
const selectionStyle = resolveGridSelectionStyle(addressing);
|
||||
const volumeAvailable = addressing.volumeEnabled
|
||||
&& Number.isFinite(addressing.volumeBandHeightMeters)
|
||||
&& addressing.volumeBandHeightMeters > 0
|
||||
@@ -1582,9 +1647,9 @@ function materializeLocalSelection(
|
||||
const polyline = crosses.add({
|
||||
id: { kind: "nodedc-grid-selection", sectorId: selection.id, edgeId: id },
|
||||
positions,
|
||||
width: clamp(2.5 * addressing.majorLineWidthMultiplier, 3, 12),
|
||||
width: selectionStyle.outlineWidthPixels,
|
||||
});
|
||||
if (polyline.material?.uniforms) polyline.material.uniforms.color = GRID_SELECTION_COLOR.withAlpha(0.95);
|
||||
if (polyline.material?.uniforms) polyline.material.uniforms.color = selectionStyle.outlineColor;
|
||||
};
|
||||
addLine("floor", floorPerimeter);
|
||||
if (ceiling > floor + 1e-6) addLine("ceiling", ceilingPerimeter);
|
||||
@@ -1608,7 +1673,7 @@ function materializeLocalSelection(
|
||||
polygon: {
|
||||
hierarchy: new PolygonHierarchy(ceilingSurface),
|
||||
perPositionHeight: true,
|
||||
material: GRID_SELECTION_COLOR.withAlpha(volumeAvailable ? 0.1 : 0.16),
|
||||
material: selectionStyle.fillColor,
|
||||
outline: false,
|
||||
},
|
||||
});
|
||||
@@ -1621,6 +1686,7 @@ function materializeGraticuleSelection(
|
||||
serial: number,
|
||||
): GridResources {
|
||||
const dataSource = new CustomDataSource(`nodedc-map-grid-selection-graticule:${serial}`);
|
||||
const selectionStyle = resolveGridSelectionStyle(addressing);
|
||||
const height = Math.max(0, addressing.heightMeters);
|
||||
const horizontalStep = Math.min(10, Math.max(0.5, addressing.stepDegrees));
|
||||
const verticalStep = Math.min(10, Math.max(0.5, addressing.stepDegrees));
|
||||
@@ -1643,8 +1709,8 @@ function materializeGraticuleSelection(
|
||||
id: `${selection.id}/selection-surface`,
|
||||
polyline: {
|
||||
positions: perimeter,
|
||||
width: clamp(addressing.lineWidthPixels * addressing.majorLineWidthMultiplier * 1.5, 3, 12),
|
||||
material: GRID_SELECTION_COLOR.withAlpha(0.95),
|
||||
width: selectionStyle.outlineWidthPixels,
|
||||
material: selectionStyle.outlineColor,
|
||||
clampToGround,
|
||||
arcType: ArcType.RHUMB,
|
||||
granularity: graticuleGranularity(horizontalStep, clampToGround),
|
||||
@@ -1654,7 +1720,7 @@ function materializeGraticuleSelection(
|
||||
height: clampToGround ? undefined : height,
|
||||
heightReference: clampToGround ? HeightReference.CLAMP_TO_GROUND : HeightReference.NONE,
|
||||
arcType: ArcType.RHUMB,
|
||||
material: GRID_SELECTION_COLOR.withAlpha(0.12),
|
||||
material: selectionStyle.fillColor,
|
||||
outline: false,
|
||||
},
|
||||
});
|
||||
@@ -1712,12 +1778,38 @@ class GridLayerController {
|
||||
: JSON.stringify([addressing.mode, addressing.lod, addressing.stepDegrees, addressing.majorStepDegrees]);
|
||||
}
|
||||
|
||||
private syncSelectionStyle(plan: Exclude<GridLayerPlan, HiddenGridPlan>) {
|
||||
let changed = false;
|
||||
const targetLod = plan.lod.index + 1;
|
||||
for (const layer of [this.current, this.pending]) {
|
||||
const addressing = layer?.addressing;
|
||||
if (!addressing || addressing.mode !== plan.mode || addressing.lod !== targetLod) continue;
|
||||
if (
|
||||
addressing.selectionFillColor === plan.lod.selectionFillColor
|
||||
&& addressing.selectionFillOpacityPercent === plan.lod.selectionFillOpacityPercent
|
||||
&& addressing.selectionOutlineColor === plan.lod.selectionOutlineColor
|
||||
&& addressing.selectionOutlineWidthPx === plan.lod.selectionOutlineWidthPx
|
||||
&& addressing.selectionOutlineOpacityPercent === plan.lod.selectionOutlineOpacityPercent
|
||||
) continue;
|
||||
addressing.selectionFillColor = plan.lod.selectionFillColor;
|
||||
addressing.selectionFillOpacityPercent = plan.lod.selectionFillOpacityPercent;
|
||||
addressing.selectionOutlineColor = plan.lod.selectionOutlineColor;
|
||||
addressing.selectionOutlineWidthPx = plan.lod.selectionOutlineWidthPx;
|
||||
addressing.selectionOutlineOpacityPercent = plan.lod.selectionOutlineOpacityPercent;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) this.refreshSelection(this.pending?.addressing ?? this.current?.addressing);
|
||||
}
|
||||
|
||||
rebuild(presentation: MapPresentation) {
|
||||
// Planning is intentionally cheap. Geometry is materialized only after
|
||||
// key comparison; repeated camera/settings notifications cannot create
|
||||
// and discard thousands of Cesium objects for an unchanged layer.
|
||||
const plan = planGridLayer(this.viewer, presentation, this.lodIndex);
|
||||
if (plan.key === this.key) return;
|
||||
if (plan.key === this.key) {
|
||||
if (plan.mode !== "hidden") this.syncSelectionStyle(plan);
|
||||
return;
|
||||
}
|
||||
this.key = plan.key;
|
||||
this.lodIndex = plan.lodIndex;
|
||||
const epoch = ++this.epoch;
|
||||
@@ -1751,6 +1843,11 @@ class GridLayerController {
|
||||
volumeMinimumHeightMeters: plan.lod.volumeMinimumHeightMeters,
|
||||
volumeMaximumHeightMeters: plan.lod.volumeMaximumHeightMeters,
|
||||
volumeBandHeightMeters: plan.lod.volumeBandHeightMeters,
|
||||
selectionFillColor: plan.lod.selectionFillColor,
|
||||
selectionFillOpacityPercent: plan.lod.selectionFillOpacityPercent,
|
||||
selectionOutlineColor: plan.lod.selectionOutlineColor,
|
||||
selectionOutlineWidthPx: plan.lod.selectionOutlineWidthPx,
|
||||
selectionOutlineOpacityPercent: plan.lod.selectionOutlineOpacityPercent,
|
||||
} : {
|
||||
mode: "graticule",
|
||||
lod: plan.lod.index + 1,
|
||||
@@ -1761,6 +1858,11 @@ class GridLayerController {
|
||||
lineColor: plan.lod.graticuleColor,
|
||||
lineOpacity: plan.lod.graticuleOpacity,
|
||||
majorLineWidthMultiplier: plan.lod.majorLineWidthMultiplier,
|
||||
selectionFillColor: plan.lod.selectionFillColor,
|
||||
selectionFillOpacityPercent: plan.lod.selectionFillOpacityPercent,
|
||||
selectionOutlineColor: plan.lod.selectionOutlineColor,
|
||||
selectionOutlineWidthPx: plan.lod.selectionOutlineWidthPx,
|
||||
selectionOutlineOpacityPercent: plan.lod.selectionOutlineOpacityPercent,
|
||||
};
|
||||
const activeAddressing = this.pending?.addressing ?? this.current?.addressing;
|
||||
if (activeAddressing && this.addressingKey(activeAddressing) !== this.addressingKey(addressing)) {
|
||||
@@ -1943,6 +2045,11 @@ class GridLayerController {
|
||||
addressing.lineColor,
|
||||
addressing.lineOpacity,
|
||||
addressing.majorLineWidthMultiplier,
|
||||
addressing.selectionFillColor,
|
||||
addressing.selectionFillOpacityPercent,
|
||||
addressing.selectionOutlineColor,
|
||||
addressing.selectionOutlineWidthPx,
|
||||
addressing.selectionOutlineOpacityPercent,
|
||||
addressing.mode === "3d" ? [
|
||||
addressing.volumeEnabled,
|
||||
addressing.volumeMinimumHeightMeters,
|
||||
|
||||
Reference in New Issue
Block a user