Work on bore profiling
This commit is contained in:
parent
d2987d07c4
commit
46db218492
8 changed files with 940 additions and 48 deletions
|
@ -73,8 +73,8 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
|
|||
);
|
||||
|
||||
public constructor(
|
||||
parentNode: MapHeightNode | undefined | null,
|
||||
mapView: MapView | undefined | null,
|
||||
parentNode: MapHeightNode | undefined,
|
||||
mapView: MapView,
|
||||
location: number = QuadTreePosition.root,
|
||||
level: number = 0,
|
||||
x: number = 0,
|
||||
|
@ -87,14 +87,6 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
|
|||
})
|
||||
);
|
||||
|
||||
if (parentNode === null) {
|
||||
parentNode = undefined;
|
||||
}
|
||||
|
||||
if (mapView === null) {
|
||||
mapView = undefined;
|
||||
}
|
||||
|
||||
super(
|
||||
parentNode,
|
||||
mapView,
|
||||
|
@ -173,10 +165,9 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
|
|||
) {
|
||||
console.warn("Geo-Three: Loading tile outside of provider range: ", this);
|
||||
|
||||
// @ts-ignore
|
||||
this.material.map = MapHeightNodeShader.defaultTexture;
|
||||
// @ts-ignore
|
||||
this.material.needsUpdate = true;
|
||||
(this.material as MeshPhongMaterial).map =
|
||||
CustomMapHeightNodeShader.defaultTexture;
|
||||
(this.material as MeshPhongMaterial).needsUpdate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -191,16 +182,16 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
|
|||
return;
|
||||
}
|
||||
|
||||
const texture = new Texture(image as any);
|
||||
const texture = new Texture(image as HTMLImageElement);
|
||||
texture.generateMipmaps = false;
|
||||
texture.format = RGBAFormat;
|
||||
texture.magFilter = NearestFilter;
|
||||
texture.minFilter = NearestFilter;
|
||||
texture.needsUpdate = true;
|
||||
|
||||
// @ts-ignore
|
||||
this.material.userData.heightMap.value = texture;
|
||||
(this.material as Material).userData.heightMap.value = texture;
|
||||
} catch (e) {
|
||||
console.warn("Could not fetch tile: ", e);
|
||||
if (this.disposed) {
|
||||
return;
|
||||
}
|
||||
|
@ -208,13 +199,11 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
|
|||
console.warn("Geo-Three: Failed to load height data: ", this);
|
||||
|
||||
// Water level texture (assume that missing texture will be water level)
|
||||
// @ts-ignore
|
||||
this.material.userData.heightMap.value =
|
||||
(this.material as Material).userData.heightMap.value =
|
||||
CustomMapHeightNodeShader.defaultHeightTexture;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this.material.needsUpdate = true;
|
||||
(this.material as Material).needsUpdate = true;
|
||||
|
||||
this.heightLoaded = true;
|
||||
}
|
||||
|
|
|
@ -1,38 +1,72 @@
|
|||
import { Group, Material, Mesh, MeshStandardMaterial, Scene } from "three";
|
||||
import {
|
||||
Camera,
|
||||
Group,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
MeshStandardMaterial,
|
||||
Plane,
|
||||
Raycaster,
|
||||
Scene,
|
||||
SphereGeometry,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from "three";
|
||||
import { buildMeshes } from "./utils/build-meshes";
|
||||
import { Extent, buildScene } from "./utils/build-scene";
|
||||
import { getCenter3D, getMetadata, transform } from "./utils/utils";
|
||||
import { MODEL_ID, SERVICE_URL } from "./config";
|
||||
import { getMetadata, transform } from "./utils/utils";
|
||||
import { MAPTILER_API_KEY, MODEL_ID, SERVICE_URL } from "./config";
|
||||
import {
|
||||
Orientation,
|
||||
buildClippingplanes,
|
||||
} from "./utils/build-clipping-planes";
|
||||
import { buildCoordinateGrid } from "./utils/build-coordinate-grid";
|
||||
import { DragControls } from "three/examples/jsm/Addons.js";
|
||||
import {
|
||||
DebugProvider,
|
||||
HeightDebugProvider,
|
||||
MapHeightNode,
|
||||
MapTilerProvider,
|
||||
MapView,
|
||||
OpenStreetMapsProvider,
|
||||
} from "geo-three";
|
||||
import { MapTilerProvider, MapView, OpenStreetMapsProvider } from "geo-three";
|
||||
import { CustomMapHeightNodeShader } from "./CustomMapHeightNodeShader";
|
||||
import { createSVG } from "./utils/create-borehole-svg";
|
||||
|
||||
export class SceneView {
|
||||
export type CustomEvent = CustomEventInit<{
|
||||
element: SVGSVGElement | null;
|
||||
}>;
|
||||
|
||||
export class SceneView extends EventTarget {
|
||||
private _scene: Scene;
|
||||
private _dragControls: DragControls;
|
||||
private _model: Group;
|
||||
private _camera: Camera;
|
||||
private _container: HTMLElement;
|
||||
private _raycaster: Raycaster;
|
||||
private _extent: Extent;
|
||||
private _startX: number = 0;
|
||||
private _startY: number = 0;
|
||||
private _isDragging: boolean = false;
|
||||
private static _DRAG_THRESHOLD = 5;
|
||||
|
||||
constructor(scene: Scene, model: Group, controls: DragControls) {
|
||||
constructor(
|
||||
scene: Scene,
|
||||
model: Group,
|
||||
controls: DragControls,
|
||||
camera: Camera,
|
||||
container: HTMLElement,
|
||||
extent: Extent
|
||||
) {
|
||||
super();
|
||||
this._scene = scene;
|
||||
this._dragControls = controls;
|
||||
this._model = model;
|
||||
this._camera = camera;
|
||||
this._container = container;
|
||||
this._raycaster = new Raycaster();
|
||||
this._extent = extent;
|
||||
}
|
||||
|
||||
static async create(container: HTMLElement, modelId: string) {
|
||||
const { scene, model, dragControls } = await init(container, modelId);
|
||||
return new SceneView(scene, model, dragControls);
|
||||
const { scene, model, dragControls, camera, extent } = await init(
|
||||
container,
|
||||
modelId
|
||||
);
|
||||
|
||||
return new SceneView(scene, model, dragControls, camera, container, extent);
|
||||
}
|
||||
|
||||
get scene() {
|
||||
|
@ -118,9 +152,108 @@ export class SceneView {
|
|||
topo.visible = !topo.visible;
|
||||
}
|
||||
}
|
||||
|
||||
private _onPointerClick(event: MouseEvent) {
|
||||
// Convert screen position to NDC (-1 to +1 range)
|
||||
const pointer = new Vector2();
|
||||
const clientRectangle = this._container.getBoundingClientRect();
|
||||
pointer.x = (event.clientX / clientRectangle.width) * 2 - 1;
|
||||
pointer.y = -(event.clientY / clientRectangle.height) * 2 + 1;
|
||||
|
||||
// Raycast from the camera
|
||||
this._raycaster.setFromCamera(pointer, this._camera);
|
||||
|
||||
// Intersect with plane
|
||||
const plane = new Plane(new Vector3(0, 0, 1), 0);
|
||||
const worldPoint = new Vector3();
|
||||
this._raycaster.ray.intersectPlane(plane, worldPoint);
|
||||
|
||||
// Cast a vertical ray from above
|
||||
this._castVerticalRay(worldPoint);
|
||||
}
|
||||
|
||||
private _castVerticalRay(targetPosition: Vector3) {
|
||||
const z = this._extent.zmax + 10000;
|
||||
const startPoint = new Vector3(targetPosition.x, targetPosition.y, z);
|
||||
|
||||
const direction = new Vector3(0, 0, -1);
|
||||
this._raycaster.set(startPoint, direction);
|
||||
|
||||
// Check intersections with objects in the scene
|
||||
const meshes = this._model.children.filter((c) => c.name !== "Topography");
|
||||
const intersects = this._raycaster.intersectObjects(meshes, true);
|
||||
|
||||
this._addPoint(targetPosition);
|
||||
if (intersects.length > 0) {
|
||||
const data = [];
|
||||
for (let i = 0; i < intersects.length; i += 2) {
|
||||
const depthStart = intersects[i].point.z;
|
||||
const depthEnd = intersects[i + 1].point.z;
|
||||
let name = intersects[i].object.name;
|
||||
let color = `#${(
|
||||
(intersects[i].object as Mesh).material as MeshStandardMaterial
|
||||
).color.getHexString()}`;
|
||||
|
||||
data.push({ depthStart, depthEnd, name, color });
|
||||
}
|
||||
|
||||
const element = createSVG(data, 400, 800, this._extent);
|
||||
const event = new CustomEvent("svg-created", {
|
||||
detail: { element },
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private _pointerDownListener = (event: PointerEvent) => {
|
||||
this._isDragging = false;
|
||||
this._startX = event.clientX;
|
||||
this._startY = event.clientY;
|
||||
};
|
||||
|
||||
private _pointerMoveListener = (event: PointerEvent) => {
|
||||
if (
|
||||
Math.abs(event.clientX - this._startX) > SceneView._DRAG_THRESHOLD ||
|
||||
Math.abs(event.clientY - this._startY) > SceneView._DRAG_THRESHOLD
|
||||
) {
|
||||
this._isDragging = true;
|
||||
}
|
||||
};
|
||||
|
||||
private _pointerUpListener = (event: PointerEvent) => {
|
||||
if (!this._isDragging) {
|
||||
this._onPointerClick(event);
|
||||
}
|
||||
};
|
||||
|
||||
enableRaycaster() {
|
||||
this._container.addEventListener("pointerdown", this._pointerDownListener);
|
||||
this._container.addEventListener("pointermove", this._pointerMoveListener);
|
||||
this._container.addEventListener("pointerup", this._pointerUpListener);
|
||||
}
|
||||
|
||||
disableRaycaster() {
|
||||
this._container.removeEventListener(
|
||||
"pointerdown",
|
||||
this._pointerDownListener
|
||||
);
|
||||
this._container.removeEventListener(
|
||||
"pointermove",
|
||||
this._pointerMoveListener
|
||||
);
|
||||
this._container.removeEventListener("pointerup", this._pointerUpListener);
|
||||
}
|
||||
|
||||
private _addPoint(point: Vector3) {
|
||||
const geometry = new SphereGeometry(2500, 16, 16); // Small sphere
|
||||
const material = new MeshBasicMaterial({ color: 0xff0000 }); // Red color
|
||||
const sphere = new Mesh(geometry, material);
|
||||
|
||||
sphere.position.set(point.x, point.y, point.z);
|
||||
this._scene.add(sphere);
|
||||
}
|
||||
}
|
||||
|
||||
const MAPTILER_API_KEY = "1JkD1W8u5UM5Tjd8r3Wl ";
|
||||
async function init(container: HTMLElement, modelId = MODEL_ID) {
|
||||
const modelData = await getMetadata(SERVICE_URL + modelId);
|
||||
const mappedFeatures = modelData.mappedfeatures;
|
||||
|
@ -188,5 +321,5 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
|
|||
map.name = "topography";
|
||||
scene.add(map);
|
||||
|
||||
return { scene, model, dragControls };
|
||||
return { scene, model, dragControls, camera, extent };
|
||||
}
|
||||
|
|
|
@ -5,3 +5,5 @@ export const SERVICE_URL =
|
|||
export const VERTICES_URL = "https://geusegdi01.geus.dk/geom3d/data/nodes/";
|
||||
export const TRIANGLE_INDICES_URL =
|
||||
"https://geusegdi01.geus.dk/geom3d/data/triangles/";
|
||||
|
||||
export const MAPTILER_API_KEY = "1JkD1W8u5UM5Tjd8r3Wl ";
|
||||
|
|
|
@ -6,8 +6,6 @@ import {
|
|||
DirectionalLight,
|
||||
Group,
|
||||
Object3D,
|
||||
Vector3,
|
||||
HemisphereLight,
|
||||
} from "three";
|
||||
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
|
@ -99,27 +97,28 @@ function animate() {
|
|||
function buildDefaultLights(scene: Scene, extent: Extent) {
|
||||
const center = getCenter3D(extent);
|
||||
|
||||
// Directional light position
|
||||
const lightPosition = {
|
||||
x: center.x,
|
||||
y: center.y - 200000,
|
||||
y: center.y - 15000,
|
||||
z: extent.zmax + 100000,
|
||||
};
|
||||
|
||||
const lights = [];
|
||||
|
||||
// Ambient light
|
||||
const ambient = new AmbientLight(0xffffff, 2);
|
||||
const ambient = new AmbientLight(0xffffff, 1);
|
||||
lights.push(ambient);
|
||||
|
||||
// Directional lights
|
||||
const directionalLight = new DirectionalLight(0xffffff, 2);
|
||||
const directionalLight = new DirectionalLight(0xffffff, 1.5);
|
||||
directionalLight.position.set(
|
||||
lightPosition.x,
|
||||
lightPosition.y,
|
||||
lightPosition.z
|
||||
);
|
||||
|
||||
// Create a target for the ligth
|
||||
// Create a target for the directional light
|
||||
const target = new Object3D();
|
||||
target.position.set(center.x, center.y, center.z);
|
||||
scene.add(target);
|
||||
|
|
60
app/three/utils/create-borehole-svg.ts
Normal file
60
app/three/utils/create-borehole-svg.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import * as d3 from "d3";
|
||||
import { Extent } from "./build-scene";
|
||||
|
||||
// SVG dimensions
|
||||
const margin = { top: 20, right: 250, bottom: 20, left: 20 };
|
||||
|
||||
interface Data {
|
||||
depthStart: number;
|
||||
depthEnd: number;
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export function createSVG(
|
||||
data: Data[],
|
||||
width: number = 400,
|
||||
height: number = 800,
|
||||
extent: Extent
|
||||
) {
|
||||
console.log(data);
|
||||
const svg = d3
|
||||
.create("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("viewBox", [0, 0, width, height])
|
||||
.attr("style", "max-width: 100%; height: auto;");
|
||||
|
||||
// Scales: Invert Y-axis so depth increases downward
|
||||
const zmax = d3.max(data, (d) => d.depthStart) ?? extent.zmax;
|
||||
const zmin = d3.max(data, (d) => d.depthEnd) ?? extent.zmin;
|
||||
const zScale = d3
|
||||
.scaleLinear()
|
||||
.domain([zmax, zmin])
|
||||
.range([margin.top, height - margin.bottom]);
|
||||
|
||||
// Draw bars (formations)
|
||||
svg
|
||||
.append("g")
|
||||
.selectAll()
|
||||
.data(data)
|
||||
.join("rect")
|
||||
.attr("x", margin.left)
|
||||
.attr("y", (d) => zScale(d.depthStart))
|
||||
.attr("height", (d) => zScale(d.depthEnd) - zScale(d.depthStart))
|
||||
.attr("width", width - margin.left - margin.right)
|
||||
.attr("fill", (d) => d.color);
|
||||
|
||||
// Add labels (formation names)
|
||||
svg
|
||||
.selectAll(".label")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("class", "label")
|
||||
.attr("x", width - margin.right + 5) // Place text slightly outside the bar
|
||||
.attr("y", (d) => (zScale(d.depthStart) + zScale(d.depthEnd)) / 2) // Center in the bar
|
||||
.text((d) => d.name);
|
||||
|
||||
return svg.node();
|
||||
}
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue