@mytec: iter2.5 vectorization start

This commit is contained in:
2026-02-01 13:13:39 +02:00
parent 4026233b21
commit acc90fe538
8 changed files with 747 additions and 71 deletions

View File

@@ -8,38 +8,35 @@ interface ElevationLayerProps {
opacity: number;
}
// Terrain color gradient: low = green, mid = yellow/tan, high = brown/white
const COLOR_STOPS = [
{ elev: 0, r: 20, g: 100, b: 40 }, // dark green
{ elev: 100, r: 50, g: 160, b: 60 }, // green
{ elev: 200, r: 130, g: 200, b: 80 }, // yellow-green
{ elev: 350, r: 210, g: 190, b: 100 }, // tan
{ elev: 500, r: 180, g: 140, b: 80 }, // brown
{ elev: 800, r: 160, g: 120, b: 90 }, // dark brown
{ elev: 1200, r: 200, g: 190, b: 180 }, // light grey
{ elev: 2000, r: 240, g: 240, b: 240 }, // near white
// Color gradient for normalized elevation (0 = lowest local, 1 = highest local)
// Blue (valleys) → Green → Yellow → Orange → Brown (peaks)
const GRADIENT_STOPS: [number, number, number][] = [
[33, 102, 172], // 0.0 — deep blue (lowest)
[103, 169, 207], // 0.2 — light blue
[145, 207, 96], // 0.4 — green
[254, 224, 139], // 0.6 — yellow
[252, 141, 89], // 0.8 — orange
[215, 48, 39], // 1.0 — brown/red (highest)
];
function getColorForElevation(elev: number): [number, number, number] {
if (elev <= COLOR_STOPS[0].elev) {
return [COLOR_STOPS[0].r, COLOR_STOPS[0].g, COLOR_STOPS[0].b];
function getColorForNormalizedElevation(normalized: number): [number, number, number] {
const n = Math.max(0, Math.min(1, normalized));
// Map 0-1 to gradient index (0-5)
const scaled = n * (GRADIENT_STOPS.length - 1);
const idx = Math.floor(scaled);
const t = scaled - idx;
if (idx >= GRADIENT_STOPS.length - 1) {
return GRADIENT_STOPS[GRADIENT_STOPS.length - 1];
}
for (let i = 1; i < COLOR_STOPS.length; i++) {
if (elev <= COLOR_STOPS[i].elev) {
const low = COLOR_STOPS[i - 1];
const high = COLOR_STOPS[i];
const t = (elev - low.elev) / (high.elev - low.elev);
return [
Math.round(low.r + t * (high.r - low.r)),
Math.round(low.g + t * (high.g - low.g)),
Math.round(low.b + t * (high.b - low.b)),
];
}
}
const last = COLOR_STOPS[COLOR_STOPS.length - 1];
return [last.r, last.g, last.b];
const low = GRADIENT_STOPS[idx];
const high = GRADIENT_STOPS[idx + 1];
return [
Math.round(low[0] + t * (high[0] - low[0])),
Math.round(low[1] + t * (high[1] - low[1])),
Math.round(low[2] + t * (high[2] - low[2])),
];
}
export default function ElevationLayer({ visible, opacity }: ElevationLayerProps) {
@@ -100,10 +97,16 @@ export default function ElevationLayer({ visible, opacity }: ElevationLayerProps
const imageData = ctx.createImageData(data.cols, data.rows);
const pixels = imageData.data;
// Use LOCAL min/max for color normalization (not absolute thresholds)
const minElev = data.min_elevation;
const maxElev = data.max_elevation;
const elevRange = maxElev - minElev || 1; // avoid division by zero
for (let row = 0; row < data.rows; row++) {
for (let col = 0; col < data.cols; col++) {
const elev = data.grid[row][col];
const [r, g, b] = getColorForElevation(elev);
const normalized = (elev - minElev) / elevRange;
const [r, g, b] = getColorForNormalizedElevation(normalized);
const idx = (row * data.cols + col) * 4;
pixels[idx] = r;
pixels[idx + 1] = g;