Add OSM topography
This commit is contained in:
parent
c20b682d33
commit
c414b9d2d6
10 changed files with 96 additions and 18 deletions
|
@ -7,6 +7,7 @@ import {
|
||||||
SceneViewContextType,
|
SceneViewContextType,
|
||||||
} from "../providers/scene-view-provider";
|
} from "../providers/scene-view-provider";
|
||||||
import { Mesh, MeshStandardMaterial } from "three";
|
import { Mesh, MeshStandardMaterial } from "three";
|
||||||
|
import { scheduler } from "timers/promises";
|
||||||
|
|
||||||
function Toggle({
|
function Toggle({
|
||||||
title,
|
title,
|
||||||
|
@ -123,16 +124,23 @@ export function Form() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleChangeTopography() {
|
||||||
|
if (!sceneView) return;
|
||||||
|
|
||||||
|
sceneView.toggleTopography();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col gap-2 overflow-y-auto">
|
<div className="w-full flex flex-col gap-2 overflow-y-auto">
|
||||||
<div className="w-full flex flex-col gap-3 p-4 border border-gray-200 rounded shadow">
|
<div className="w-full flex flex-col gap-3 p-4 border border-gray-200 rounded shadow">
|
||||||
<Toggle title="Slicing Box" onChange={handleChange} />
|
<Toggle title="Slicing Box" onChange={handleChange} />
|
||||||
|
<Toggle title="Coordinate Grid" onChange={handleChangeCG} />
|
||||||
|
<Toggle title="Wireframe" onChange={handleChangeWireframe} />
|
||||||
<Toggle
|
<Toggle
|
||||||
title="Coordinate Grid"
|
title="Topography (OSM)"
|
||||||
onChange={handleChangeCG}
|
onChange={handleChangeTopography}
|
||||||
defaultChecked
|
defaultChecked
|
||||||
/>
|
/>
|
||||||
<Toggle title="Wireframe" onChange={handleChangeWireframe} />
|
|
||||||
<Accordion title="Layers">
|
<Accordion title="Layers">
|
||||||
{
|
{
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext, useEffect, useRef } from "react";
|
||||||
import { SceneView } from "../three/SceneView";
|
|
||||||
import {
|
import {
|
||||||
SceneViewContext,
|
SceneViewContext,
|
||||||
SceneViewContextType,
|
SceneViewContextType,
|
||||||
} from "../providers/scene-view-provider";
|
} from "../providers/scene-view-provider";
|
||||||
|
|
||||||
|
async function lazyLoad() {
|
||||||
|
const { SceneView } = await import("../three/SceneView");
|
||||||
|
return SceneView;
|
||||||
|
}
|
||||||
|
|
||||||
export function Map() {
|
export function Map() {
|
||||||
const divRef = useRef<HTMLDivElement>(null);
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
const { setSceneView } = useContext(SceneViewContext) as SceneViewContextType;
|
const { setSceneView } = useContext(SceneViewContext) as SceneViewContextType;
|
||||||
|
@ -17,6 +21,7 @@ export function Map() {
|
||||||
|
|
||||||
async function loadScene() {
|
async function loadScene() {
|
||||||
if (divRef.current) {
|
if (divRef.current) {
|
||||||
|
const SceneView = await lazyLoad();
|
||||||
const _sceneView = await SceneView.create(divRef.current, "20");
|
const _sceneView = await SceneView.create(divRef.current, "20");
|
||||||
if (_sceneView) {
|
if (_sceneView) {
|
||||||
setSceneView(_sceneView);
|
setSceneView(_sceneView);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Group, Mesh, MeshStandardMaterial, Scene } from "three";
|
import { Group, Mesh, MeshStandardMaterial, Scene } from "three";
|
||||||
import { buildMeshes } from "./utils/build-meshes";
|
import { buildMeshes } from "./utils/build-meshes";
|
||||||
import { Extent, buildScene } from "./utils/build-scene";
|
import { Extent, buildScene } from "./utils/build-scene";
|
||||||
import { getMetadata } from "./utils/utils";
|
import { getCenter3D, getMetadata, transform } from "./utils/utils";
|
||||||
import { MODEL_ID, SERVICE_URL } from "./config";
|
import { MODEL_ID, SERVICE_URL } from "./config";
|
||||||
import {
|
import {
|
||||||
Orientation,
|
Orientation,
|
||||||
|
@ -9,6 +9,7 @@ import {
|
||||||
} from "./utils/build-clipping-planes";
|
} from "./utils/build-clipping-planes";
|
||||||
import { buildCoordinateGrid } from "./utils/build-coordinate-grid";
|
import { buildCoordinateGrid } from "./utils/build-coordinate-grid";
|
||||||
import { DragControls } from "three/examples/jsm/Addons.js";
|
import { DragControls } from "three/examples/jsm/Addons.js";
|
||||||
|
import { MapView, OpenStreetMapsProvider } from "geo-three";
|
||||||
|
|
||||||
export class SceneView {
|
export class SceneView {
|
||||||
private _scene: Scene;
|
private _scene: Scene;
|
||||||
|
@ -88,6 +89,13 @@ export class SceneView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleTopography() {
|
||||||
|
const topo = this._scene.getObjectByName("topography");
|
||||||
|
if (topo) {
|
||||||
|
topo.visible = !topo.visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init(container: HTMLElement, modelId = MODEL_ID) {
|
async function init(container: HTMLElement, modelId = MODEL_ID) {
|
||||||
|
@ -95,13 +103,16 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
|
||||||
const mappedFeatures = modelData.mappedfeatures;
|
const mappedFeatures = modelData.mappedfeatures;
|
||||||
const modelarea = modelData.modelarea;
|
const modelarea = modelData.modelarea;
|
||||||
|
|
||||||
|
// Transfrom extent to EPSG 3857
|
||||||
|
const pmin = transform([modelarea.x.min, modelarea.y.min, modelarea.z.min]);
|
||||||
|
const pmax = transform([modelarea.x.max, modelarea.y.max, modelarea.z.max]);
|
||||||
const extent: Extent = {
|
const extent: Extent = {
|
||||||
xmin: modelarea.x.min,
|
xmin: pmin[0],
|
||||||
xmax: modelarea.x.max,
|
xmax: pmax[0],
|
||||||
ymin: modelarea.y.min,
|
ymin: pmin[1],
|
||||||
ymax: modelarea.y.max,
|
ymax: pmax[1],
|
||||||
zmin: modelarea.z.min,
|
zmin: pmin[2],
|
||||||
zmax: modelarea.z.max,
|
zmax: pmax[2],
|
||||||
};
|
};
|
||||||
|
|
||||||
const { renderer, scene, camera, controls } = buildScene(container, extent);
|
const { renderer, scene, camera, controls } = buildScene(container, extent);
|
||||||
|
@ -133,10 +144,21 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
|
||||||
const annotationsGroup = new Group();
|
const annotationsGroup = new Group();
|
||||||
annotationsGroup.name = "coordinate-grid";
|
annotationsGroup.name = "coordinate-grid";
|
||||||
annotationsGroup.add(...annotations, gridHelper);
|
annotationsGroup.add(...annotations, gridHelper);
|
||||||
|
annotationsGroup.visible = false;
|
||||||
scene.add(annotationsGroup);
|
scene.add(annotationsGroup);
|
||||||
|
|
||||||
//const axesHelper = new AxesHelper(5);
|
//const axesHelper = new AxesHelper(5);
|
||||||
//scene.add(axesHelper);
|
//scene.add(axesHelper);
|
||||||
|
|
||||||
|
// Create a map tiles provider object
|
||||||
|
const provider = new OpenStreetMapsProvider();
|
||||||
|
|
||||||
|
// Create the map view of OSM topography
|
||||||
|
const map = new MapView(MapView.PLANAR, provider);
|
||||||
|
map.rotateX(Math.PI / 2);
|
||||||
|
// map.position.setComponent(2, -100);
|
||||||
|
map.name = "topography";
|
||||||
|
scene.add(map);
|
||||||
|
|
||||||
return { scene, model, dragControls };
|
return { scene, model, dragControls };
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,13 @@ function createLabel(
|
||||||
transparent: true,
|
transparent: true,
|
||||||
});
|
});
|
||||||
const sprite = new Sprite(spriteMaterial);
|
const sprite = new Sprite(spriteMaterial);
|
||||||
sprite.position.set(position.x, position.y, position.z);
|
|
||||||
|
// Set position according to axis orientation
|
||||||
|
if (orientation === Orientation.Horizontal) {
|
||||||
|
sprite.position.set(position.x + 1000, position.y - 1500, position.z + 500);
|
||||||
|
} else {
|
||||||
|
sprite.position.set(position.x, position.y - 500, position.z + 500);
|
||||||
|
}
|
||||||
sprite.scale.set(5000, 2500, 1); // Scale the sprite to make the text readable
|
sprite.scale.set(5000, 2500, 1); // Scale the sprite to make the text readable
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +107,8 @@ function generateTextCanvas(text: string, orientation: Orientation) {
|
||||||
context.fillStyle = "black";
|
context.fillStyle = "black";
|
||||||
|
|
||||||
if (orientation === Orientation.Horizontal) {
|
if (orientation === Orientation.Horizontal) {
|
||||||
context.fillText(text, 300, 160);
|
//context.fillText(text, 300, 160);
|
||||||
|
context.fillText(text, 100, 90);
|
||||||
} else {
|
} else {
|
||||||
context.fillText(text, 100, 90);
|
context.fillText(text, 100, 90);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
MeshStandardMaterial,
|
MeshStandardMaterial,
|
||||||
} from "three";
|
} from "three";
|
||||||
|
|
||||||
import { fetchVertices, fetchTriangleIndices } from "./utils";
|
import { fetchVertices, fetchTriangleIndices, transform } from "./utils";
|
||||||
import { TRIANGLE_INDICES_URL, VERTICES_URL } from "../config";
|
import { TRIANGLE_INDICES_URL, VERTICES_URL } from "../config";
|
||||||
|
|
||||||
interface MappedFeature {
|
interface MappedFeature {
|
||||||
|
@ -38,7 +38,14 @@ async function buildMesh(layerData: MappedFeature) {
|
||||||
const geometry = new BufferGeometry();
|
const geometry = new BufferGeometry();
|
||||||
const vertices = await fetchVertices(VERTICES_URL, geomId);
|
const vertices = await fetchVertices(VERTICES_URL, geomId);
|
||||||
|
|
||||||
const positions = new BufferAttribute(vertices, 3);
|
// Transform coordinates to EPSG 3857
|
||||||
|
const vertices3857 = new Float32Array(vertices.length);
|
||||||
|
for (let i = 0; i < vertices.length; i += 3) {
|
||||||
|
const vertex = Array.from(vertices.slice(i, i + 3));
|
||||||
|
vertices3857.set(transform(vertex), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const positions = new BufferAttribute(vertices3857, 3);
|
||||||
geometry.setAttribute("position", positions);
|
geometry.setAttribute("position", positions);
|
||||||
|
|
||||||
const indexArray = await fetchTriangleIndices(TRIANGLE_INDICES_URL, geomId);
|
const indexArray = await fetchTriangleIndices(TRIANGLE_INDICES_URL, geomId);
|
||||||
|
|
|
@ -64,7 +64,7 @@ export function buildScene(container: HTMLElement, extent: Extent) {
|
||||||
maxSize * 25
|
maxSize * 25
|
||||||
);
|
);
|
||||||
|
|
||||||
camera.position.set(center.x, center.y - 125000, extent.zmax + 100000);
|
camera.position.set(center.x, center.y - 200000, extent.zmax + 100000);
|
||||||
camera.up.set(0, 0, 1);
|
camera.up.set(0, 0, 1);
|
||||||
camera.lookAt(center);
|
camera.lookAt(center);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Vector3 } from "three";
|
import { Vector3 } from "three";
|
||||||
import { Extent } from "./build-scene";
|
import { Extent } from "./build-scene";
|
||||||
import { unpackEdges, unpackVertices } from "./parsers";
|
import { unpackEdges, unpackVertices } from "./decoders";
|
||||||
|
import proj4 from "proj4";
|
||||||
|
|
||||||
export function getMaxSize(extent: Extent) {
|
export function getMaxSize(extent: Extent) {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
|
@ -54,3 +55,13 @@ export async function fetchVertices(pointUrl: string, geomId: string) {
|
||||||
const buffer = await request(url);
|
const buffer = await request(url);
|
||||||
return unpackVertices(buffer);
|
return unpackVertices(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transformation from EPSG 3034 to EPSG 3857
|
||||||
|
const SOURCE = "EPSG:3034";
|
||||||
|
const PROJ_STRING =
|
||||||
|
"+proj=lcc +lat_0=52 +lon_0=10 +lat_1=35 +lat_2=65 +x_0=4000000 +y_0=2800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs";
|
||||||
|
const DEST = "EPSG:3857";
|
||||||
|
proj4.defs(SOURCE, PROJ_STRING);
|
||||||
|
export function transform(p: number[]) {
|
||||||
|
return proj4(SOURCE, DEST, p);
|
||||||
|
}
|
||||||
|
|
16
package-lock.json
generated
16
package-lock.json
generated
|
@ -9,6 +9,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"earcut": "^3.0.1",
|
"earcut": "^3.0.1",
|
||||||
|
"geo-three": "^0.1.15",
|
||||||
"next": "15.2.1",
|
"next": "15.2.1",
|
||||||
"proj4": "^2.15.0",
|
"proj4": "^2.15.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
"@tailwindcss/postcss": "^4.0.11",
|
"@tailwindcss/postcss": "^4.0.11",
|
||||||
"@types/earcut": "^3.0.0",
|
"@types/earcut": "^3.0.0",
|
||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
|
"@types/proj4": "^2.5.6",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@types/three": "^0.174.0",
|
"@types/three": "^0.174.0",
|
||||||
|
@ -1047,6 +1049,12 @@
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/proj4": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/proj4/-/proj4-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-zfMrPy9fx+8DchqM0kIUGeu2tTVB5ApO1KGAYcSGFS8GoqRIkyL41xq2yCx/iV3sOLzo7v4hEgViSLTiPI1L0w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.0.10",
|
"version": "19.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
||||||
|
@ -2714,6 +2722,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/geo-three": {
|
||||||
|
"version": "0.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/geo-three/-/geo-three-0.1.15.tgz",
|
||||||
|
"integrity": "sha512-mU5K5QfnRwOawTjXg1zmAMTu+c/uC6cZFNGYqf41eCRXfX4bkDHFaPhXa08z1WjYzg5kq1BSjpFR9weW2vNJZg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": ">0.120.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"earcut": "^3.0.1",
|
"earcut": "^3.0.1",
|
||||||
|
"geo-three": "^0.1.15",
|
||||||
"next": "15.2.1",
|
"next": "15.2.1",
|
||||||
"proj4": "^2.15.0",
|
"proj4": "^2.15.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
"@tailwindcss/postcss": "^4.0.11",
|
"@tailwindcss/postcss": "^4.0.11",
|
||||||
"@types/earcut": "^3.0.0",
|
"@types/earcut": "^3.0.0",
|
||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
|
"@types/proj4": "^2.5.6",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@types/three": "^0.174.0",
|
"@types/three": "^0.174.0",
|
||||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue