2D-Plan-Renderer auf WebGL2 (GPU) + akkumulierter Funktionsstand
Neuer GPU-Renderer fuer den Grundriss (src/plan/glPlan/): Earcut-Tessellierung (konkav-faehig), gehrte Linienzuege (Miter), echte Papier-mm-Strichbreiten im Massstab (repliziert den SVG-printStrokeVb-Pfad), Hybrid mit scharfem SVG-Text- Overlay. GPU ist der Standardpfad; der SVG-Renderer bleibt automatischer Fallback, falls WebGL2/Shader nicht verfuegbar sind. Imperativer Pan (rAF + CSS-transform) fuer fluessige Interaktion ohne React-Re-Render je Frame. Enthaelt zudem den bisher nicht committeten Arbeitsstand des Browser-BIM (Oeffnungen, Treppen, Raeume, Decken, DXF-Export, Materialbibliothek, Kontext- Import, Tauri-Compute-Boundary-PoC).
This commit is contained in:
+182
@@ -0,0 +1,182 @@
|
||||
// LV95 ↔ WGS84 Koordinaten-Transformation + Ursprungs-Verschiebung.
|
||||
//
|
||||
// Schweizer Landeskoordinaten (LV95 / EPSG:2056) liegen im Millionenbereich
|
||||
// (Ost ~2'600'000 m, Nord ~1'200'000 m). Für die numerische Genauigkeit im
|
||||
// CAD (float32 in three.js!) verschieben wir importierte Geometrie in die Nähe
|
||||
// des Modell-Ursprungs (0,0). Der einmal berechnete Offset wird mitgeführt, so
|
||||
// dass ALLE Objekte eines Imports dieselbe Verschiebung teilen und korrekt
|
||||
// zueinander liegen.
|
||||
//
|
||||
// Die Umrechnung LV95↔WGS84 verwendet die von swisstopo publizierten
|
||||
// Näherungsformeln („Bezugsrahmenwechsel und Kartenprojektionen", genauigkeit
|
||||
// im Meter-Bereich, für Kontext-Geometrie völlig ausreichend). Referenz-
|
||||
// Rundlauf (siehe Unit-Check im Report) < 1 m.
|
||||
//
|
||||
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md). Einheiten: Meter
|
||||
// bzw. Dezimalgrad.
|
||||
|
||||
/** Geographische Koordinate (WGS84), Dezimalgrad. */
|
||||
export interface LatLon {
|
||||
lat: number;
|
||||
lon: number;
|
||||
}
|
||||
|
||||
/** Schweizer Landeskoordinate LV95 (EPSG:2056), Meter. */
|
||||
export interface LV95 {
|
||||
/** Ostwert (E), ~2'600'000. */
|
||||
e: number;
|
||||
/** Nordwert (N), ~1'200'000. */
|
||||
n: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* WGS84 → LV95 (approximativ, swisstopo-Näherungsformeln).
|
||||
* Eingabe Dezimalgrad, Ausgabe Meter (E/N).
|
||||
*/
|
||||
export function wgs84ToLv95(lat: number, lon: number): LV95 {
|
||||
// Breite/Länge in Sexagesimalsekunden.
|
||||
const phi = lat * 3600;
|
||||
const lam = lon * 3600;
|
||||
// Hilfsgrößen relativ zum Bezugspunkt Bern (46°57'08.66" N, 7°26'22.50" E).
|
||||
const p = (phi - 169028.66) / 10000;
|
||||
const l = (lam - 26782.5) / 10000;
|
||||
const p2 = p * p;
|
||||
const l2 = l * l;
|
||||
|
||||
// Ostwert (Y in LV03 + 2'000'000 für LV95).
|
||||
const y =
|
||||
2600072.37 +
|
||||
211455.93 * l -
|
||||
10938.51 * l * p -
|
||||
0.36 * l * p2 -
|
||||
44.54 * l * l2;
|
||||
|
||||
// Nordwert (X in LV03 + 1'000'000 für LV95).
|
||||
const x =
|
||||
1200147.07 +
|
||||
308807.95 * p +
|
||||
3745.25 * l2 +
|
||||
76.63 * p2 -
|
||||
194.56 * l2 * p +
|
||||
119.79 * p * p2;
|
||||
|
||||
return { e: y, n: x };
|
||||
}
|
||||
|
||||
/**
|
||||
* LV95 → WGS84 (approximativ, swisstopo-Näherungsformeln).
|
||||
* Eingabe Meter (E/N), Ausgabe Dezimalgrad.
|
||||
*/
|
||||
export function lv95ToWgs84(e: number, n: number): LatLon {
|
||||
// Relativ zum Projektions-Ursprung, in 1'000'000-Einheiten.
|
||||
const y = (e - 2600000) / 1000000;
|
||||
const x = (n - 1200000) / 1000000;
|
||||
const y2 = y * y;
|
||||
const x2 = x * x;
|
||||
|
||||
// Breite/Länge in 10'000"-Einheiten (Näherungspolynome).
|
||||
const lam =
|
||||
2.6779094 +
|
||||
4.728982 * y +
|
||||
0.791484 * y * x +
|
||||
0.1306 * y * x2 -
|
||||
0.0436 * y * y2;
|
||||
|
||||
const phi =
|
||||
16.9023892 +
|
||||
3.238272 * x -
|
||||
0.270978 * y2 -
|
||||
0.002528 * x2 -
|
||||
0.0447 * y2 * x -
|
||||
0.014 * x * x2;
|
||||
|
||||
// Zurück in Dezimalgrad (Faktor 100/36).
|
||||
return {
|
||||
lat: (phi * 100) / 36,
|
||||
lon: (lam * 100) / 36,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine Ursprungs-Verschiebung: der LV95-Punkt, der im Modell auf (0,0)
|
||||
* abgebildet wird. Alle importierten Objekte eines Imports teilen dieselbe
|
||||
* Origin, damit sie zueinander lagerichtig bleiben.
|
||||
*/
|
||||
export interface GeoOrigin {
|
||||
e: number;
|
||||
n: number;
|
||||
}
|
||||
|
||||
/** Erzeugt eine Origin aus einem LV95-Zentrum. */
|
||||
export function makeOrigin(center: LV95): GeoOrigin {
|
||||
return { e: center.e, n: center.n };
|
||||
}
|
||||
|
||||
/**
|
||||
* LV95 → lokale Modell-Meter (Ost→X, Nord→Y) relativ zur Origin. LV95 ist bereits
|
||||
* metrisch und (nahezu) nordorientiert, daher genügt eine reine Translation.
|
||||
*/
|
||||
export function lv95ToLocal(pt: LV95, origin: GeoOrigin): { x: number; y: number } {
|
||||
return { x: pt.e - origin.e, y: pt.n - origin.n };
|
||||
}
|
||||
|
||||
/** Lokale Modell-Meter → LV95 (Umkehrung von {@link lv95ToLocal}). */
|
||||
export function localToLv95(
|
||||
x: number,
|
||||
y: number,
|
||||
origin: GeoOrigin,
|
||||
): LV95 {
|
||||
return { e: x + origin.e, n: y + origin.n };
|
||||
}
|
||||
|
||||
/**
|
||||
* WGS84 → lokale Modell-Meter (über LV95 und die Origin). Praktisch für
|
||||
* OSM-Punkte (lon/lat), die zuerst nach LV95 projiziert und dann verschoben
|
||||
* werden — so teilen OSM- und swisstopo-Import exakt dasselbe Bezugssystem.
|
||||
*/
|
||||
export function wgs84ToLocal(
|
||||
lat: number,
|
||||
lon: number,
|
||||
origin: GeoOrigin,
|
||||
): { x: number; y: number } {
|
||||
return lv95ToLocal(wgs84ToLv95(lat, lon), origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut eine LV95-Bounding-Box (quadratisch) um ein Zentrum mit gegebenem Radius
|
||||
* (Meter). Reihenfolge [eMin, nMin, eMax, nMax].
|
||||
*/
|
||||
export function bboxAround(
|
||||
center: LV95,
|
||||
radius: number,
|
||||
): [number, number, number, number] {
|
||||
return [
|
||||
center.e - radius,
|
||||
center.n - radius,
|
||||
center.e + radius,
|
||||
center.n + radius,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt eine LV95-Bounding-Box in eine WGS84-Box (für Overpass:
|
||||
* [south, west, north, east]). Nutzt die vier Ecken und nimmt die Extremwerte,
|
||||
* damit die leichte Nord-Rotation der LV95-Projektion abgedeckt ist.
|
||||
*/
|
||||
export function lv95BboxToWgs84(
|
||||
bbox: [number, number, number, number],
|
||||
): { south: number; west: number; north: number; east: number } {
|
||||
const [eMin, nMin, eMax, nMax] = bbox;
|
||||
const corners: LatLon[] = [
|
||||
lv95ToWgs84(eMin, nMin),
|
||||
lv95ToWgs84(eMax, nMin),
|
||||
lv95ToWgs84(eMin, nMax),
|
||||
lv95ToWgs84(eMax, nMax),
|
||||
];
|
||||
return {
|
||||
south: Math.min(...corners.map((c) => c.lat)),
|
||||
west: Math.min(...corners.map((c) => c.lon)),
|
||||
north: Math.max(...corners.map((c) => c.lat)),
|
||||
east: Math.max(...corners.map((c) => c.lon)),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user