factor: Switch to Web Mercator coordinates

This commit is contained in:
2026-01-21 00:25:46 +01:00
parent 87312408a2
commit 08cfcdee19
3 changed files with 124 additions and 54 deletions

View File

@@ -188,6 +188,14 @@ export default {
});
if (renderer) renderer.render(scene, camera);
};
// Convert lat/lon to Web Mercator meters (EPSG:3857)
const lonLatToWebMercator = (lon, lat) => {
const x = lon * 20037508.34 / 180;
let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
y = y * 20037508.34 / 180;
return { x, y };
};
const initThreeJS = () => {
scene = new THREE.Scene();
@@ -230,10 +238,14 @@ export default {
const ne = bounds.getNorthEast();
const sw = bounds.getSouthWest();
camera.left = sw.lng;
camera.right = ne.lng;
camera.top = ne.lat;
camera.bottom = sw.lat;
// Convert lat/lon bounds to Web Mercator meters
const neMerc = lonLatToWebMercator(ne.lng, ne.lat);
const swMerc = lonLatToWebMercator(sw.lng, sw.lat);
camera.left = swMerc.x;
camera.right = neMerc.x;
camera.top = neMerc.y;
camera.bottom = swMerc.y;
camera.updateProjectionMatrix();
renderer.render(scene, camera);

View File

@@ -165,7 +165,7 @@ const settings = reactive({
altitude: 60,
intensity: 1.2,
heightScale: 3,
terrainColor: "#9a9996",
terrainColor: 0x9A9996,
...props.initialSettings
});
@@ -249,12 +249,31 @@ const handleResize = () => {
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
// Always maintain square frustum regardless of canvas aspect ratio
const viewSize = 6;
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize;
camera.bottom = -viewSize;
// Maintain tile aspect ratio in frustum
if (geometryCache && geometryCache.tileAspect) {
const viewSize = 6;
const tileAspect = geometryCache.tileAspect;
if (tileAspect > 1) {
camera.left = -viewSize * tileAspect;
camera.right = viewSize * tileAspect;
camera.top = viewSize;
camera.bottom = -viewSize;
} else {
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize / tileAspect;
camera.bottom = -viewSize / tileAspect;
}
} else {
// Fallback: square frustum if no tile loaded yet
const viewSize = 6;
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize;
camera.bottom = -viewSize;
}
camera.updateProjectionMatrix();
};
@@ -285,17 +304,20 @@ const loadTileData = (tileData) => {
const spanZ = tileData.bounds.maxZ - tileData.bounds.minZ;
const maxSpan = Math.max(spanX, spanY);
// Normalize XY to fit in a 10-unit box
// Calculate actual aspect ratio of the tile
const tileAspect = spanX / spanY;
// Normalize XY to fit in view, maintaining actual aspect ratio
const normalizeScale = 10 / maxSpan;
// CRITICAL: Use App2's adaptive Z scaling
// This makes Z proportional to actual elevation variation
// Z scaling: make Z variation visible but proportional
const zScale = normalizeScale * (maxSpan * 0.1) / spanZ;
console.log('Tile scaling:', {
spanX: spanX.toFixed(2),
spanY: spanY.toFixed(2),
spanZ: spanZ.toFixed(2),
tileAspect: tileAspect.toFixed(3),
normalizeScale: normalizeScale.toFixed(4),
zScale: zScale.toFixed(4)
});
@@ -314,7 +336,7 @@ const loadTileData = (tileData) => {
geometry.setIndex(new THREE.BufferAttribute(tileData.indices, 1));
geometry.computeVertexNormals();
// Cache base Z values for height exaggeration (matching App2's approach)
// Cache base Z values for height exaggeration
const baseZ = new Float32Array(tileData.positions.length);
for (let i = 0; i < tileData.positions.length; i += 3) {
baseZ[i] = 0;
@@ -326,7 +348,8 @@ const loadTileData = (tileData) => {
geometry,
baseZ,
spanZ,
zScale
zScale,
tileAspect
};
// Create material and mesh
@@ -338,15 +361,25 @@ const loadTileData = (tileData) => {
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Configure camera frustum for orthographic view - ALWAYS SQUARE
const viewSize = 6; // 10-unit terrain + padding
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize;
camera.bottom = -viewSize;
// Configure camera frustum to match tile aspect ratio
const viewSize = 6; // Base size for the view
// Adjust frustum based on tile aspect ratio
if (tileAspect > 1) {
// Wider than tall
camera.left = -viewSize * tileAspect;
camera.right = viewSize * tileAspect;
camera.top = viewSize;
camera.bottom = -viewSize;
} else {
// Taller than wide
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize / tileAspect;
camera.bottom = -viewSize / tileAspect;
}
// Adjust near/far to accommodate height exaggeration
// Terrain is centered at Z=0, extends ±(spanZ * zScale / 2) in base form
const maxZExtent = (spanZ * zScale / 2) * 20; // Max exaggeration
camera.near = 0.1;
camera.far = 100 + maxZExtent * 2;
@@ -523,11 +556,11 @@ const renderTileWithSettings = async (tileData, renderSettings, resolution = 102
const originalHeight = renderer.domElement.height;
const originalPixelRatio = renderer.getPixelRatio();
// Set render size (square)
// Set render size (square for export)
renderer.setSize(resolution, resolution);
renderer.setPixelRatio(1);
// Update camera for square aspect (always square)
// Update camera for square render output
const viewSize = 6;
camera.left = -viewSize;
camera.right = viewSize;
@@ -545,11 +578,28 @@ const renderTileWithSettings = async (tileData, renderSettings, resolution = 102
renderer.setSize(originalWidth / originalPixelRatio, originalHeight / originalPixelRatio);
renderer.setPixelRatio(originalPixelRatio);
// Restore camera (always square frustum)
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize;
camera.bottom = -viewSize;
// Restore camera with tile aspect ratio
if (geometryCache && geometryCache.tileAspect) {
const tileAspect = geometryCache.tileAspect;
if (tileAspect > 1) {
camera.left = -viewSize * tileAspect;
camera.right = viewSize * tileAspect;
camera.top = viewSize;
camera.bottom = -viewSize;
} else {
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize / tileAspect;
camera.bottom = -viewSize / tileAspect;
}
} else {
// Fallback: square frustum
camera.left = -viewSize;
camera.right = viewSize;
camera.top = viewSize;
camera.bottom = -viewSize;
}
camera.updateProjectionMatrix();
const endTime = performance.now();