Published
Edited
Feb 7, 2022
2 forks
50 stars
Insert cell
Insert cell
Insert cell
myCanvas = {
const controls = new OrbitControls(camera, renderer.domElement);
controls.minPolarAngle = 0;
controls.maxPolarAngle = Math.PI / 2;
controls.object.position.x = 100;
controls.object.position.y = 120;
controls.object.position.z = 45;
controls.autoRotate = false;

// https://threejs.org/docs/#examples/en/controls/OrbitControls
let lastID;
const loop = () => {
renderer.render(scene, camera);
controls.update();
lastID = requestAnimationFrame(loop);
};
invalidation.then(() => cancelAnimationFrame(lastID));
loop();

invalidation.then(() => {
controls.dispose();
renderer.dispose();
});

return renderer.domElement;
}
Insert cell
axesHelper = new THREE.AxesHelper(50) // The X axis is red. The Y axis is green. The Z axis is blue.
Insert cell
zip_files = FileAttachment("lds-nz-contours-topo-150k-SHP (1).zip").zip()
Insert cell
fileNames = zip_files.filenames
Insert cell
te_data = shapefile.read(
await zip_files.file("nz-contours-topo-150k.shp").stream(), // *.shp files contain the shape information (feature coordinates)
await zip_files.file("nz-contours-topo-150k.dbf").stream() // *.dbf gives the rivers an ID
)
Insert cell
te_data.features[178] // Example of "MultiLineString"
Insert cell
te_data.features[10] // Example of "LineString"
Insert cell
elevation_range = d3.extent(te_data.features.map((d) => {
return d.properties.elevation;
}))
Insert cell
Insert cell
xyzCordsMulti = {
const C = [];
let cTemp = [];
let lineType = "";
let elev = 0;
for (const d of te_data.features) {
lineType = d.geometry.type;
elev = d.properties.elevation / 100;
if (lineType == "MultiLineString") {
let cord = d.geometry.coordinates.map((c) => {
let subCord = [];
for (let cxy of c) {
subCord.push([(cxy[0] - xoff) / 100, (cxy[1] - yoff) / 100, elev]);
}
return subCord;
});
cTemp = _.flatten(cord);
} else {
cTemp = d.geometry.coordinates.map((c) => {
return [(c[0] - xoff) / 100, (c[1] - yoff) / 100, elev];
});
} // end if multi linestring
C.push({ lineType, cord: cTemp });
}
return C;
}
Insert cell
xoff = te_data.bbox[0] + (te_data.bbox[2] - te_data.bbox[0]) / 2 + 1750 // center of the contours map
Insert cell
yoff = te_data.bbox[1] + (te_data.bbox[3] - te_data.bbox[1]) / 2 + 30
Insert cell
Insert cell
obj_source = {
const group = new THREE.Group();

// https://github.com/mrdoob/three.js/blob/master/examples/webgl_buffergeometry_lines_indexed.html
const geometries = [];
let positions = [],
indices = [],
next = 0;

// We can only address up to 256*256 (0xFFFF) indices.
// "Committing" means we stor the collected positions and indices
// in a new BufferGeometry. Then we empty out the positions and
// indices array so that we can start to count from 0 again.
const commit = () => {
if (!indices.length || !positions.length) return;
const geometry = new THREE.BufferGeometry();

geometry.setIndex(indices);
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
geometries.push(geometry);
indices = [];
positions = [];
next = 0;
};

for (let { cord: points } of xyzCordsMulti) {
// We got some empty coordinates.
points = points.filter(([x, y]) => !isNaN(x) && !isNaN(y));
//console.assert(points.length > 1, "Contains at least 2 points");
if (points.length < 2) continue;

// If the next array of coordinates would exceed the maximum
// index length, start a new BufferGeometry.
if (positions.length + points.length * 3 > 0xffff) commit();

for (let i = 0; i < points.length; i++) {
const [x, y, z] = points[i];

if (i > 0) indices.push(next - 1, next);
positions.push(x, Math.max(0, z || 0), -y);

next++;
}
}
commit();

for (const geometry of geometries) {
group.add(
new THREE.LineSegments(
geometry,
new THREE.LineBasicMaterial({
color: quantize(geometry.attributes.position.array[1] * 100),
linewidth: 1,
opacity: 1.0
})
)
);
}

return group;
}
Insert cell
material = new THREE.LineBasicMaterial({
color: 0xdeadf0, // this colour will be scaled based on it's elevation.
linewidth: 1,
opacity: 1.0
})
Insert cell
quantize = d3
.scaleQuantize()
.domain(elevation_range) // pass only the extreme values to a scaleQuantize’s domain
.range(colours)
Insert cell
Insert cell
colours = batlowK
Insert cell
cx = (1691758.2024483867 - xoff) / 100 // close to center of map / mount peek
Insert cell
cy = (5649815.166192245 - yoff) / 100
Insert cell
from = [0, 800, 80]
Insert cell
lookAt = new THREE.Vector3(0, 0, 20)
Insert cell
renderer = {
let rend = new THREE.WebGLRenderer({ antialias: true });

rend.setSize(w, height);
rend.setPixelRatio(devicePixelRatio);
return rend;
}
Insert cell
camera = {
const camera = new THREE.PerspectiveCamera(
75, // fov
w / height, // aspect
0.1, // near
15000 // far
);
camera.position.set(...from);
camera.lookAt(lookAt);

return camera;
}
Insert cell
scene = {
const scene = new THREE.Scene();
scene.background = new THREE.Color("white"); //0x050532); // dark background
// scene.add(axesHelper);
scene.add(obj_source); // put object into the scene

// scene.add(bit);
scene.add(ambient_light); // , point_light

return scene;
}
Insert cell
ambient_light = {
return new THREE.AmbientLight(0xffffff, 0.5);
}
Insert cell
point_light ={
let pl = new THREE.PointLight(0x909090, 0.5);
pl.position.set( 500, 500, 500 );
return pl
}
Insert cell
w = width
Insert cell
height = w * 0.75
Insert cell
// Tried to make a shape, ignore that the whole x,y,z is turned 45 degrees, it's still a mess...
bit = {
let geom = new THREE.Shape();

// xyzCordsMulti.map((a, z) => { // commented out to save it from running
// if (z > 1000) return;
// a.cord.map((c, i) => {
// if (i == 0) {
// geom.moveTo(c[0], c[1]);
// } else {
// geom.lineTo(c[0], c[1]);
// }
// });
// });

const extrudeSettings = {
depth: 1,
bevelEnabled: true,
bevelSegments: 2,
steps: 2,
bevelSize: 1,
bevelThickness: 1
};

const geometry = new THREE.ExtrudeGeometry(geom, extrudeSettings);

const mesh = new THREE.Mesh(
geometry,
new THREE.MeshPhongMaterial({
color: "red",
opacity: 0.5
})
);

return mesh;
}
Insert cell
# Todo

✅ Fix map orientation. <sub>(still seems broken as Y is where I imagine Z should be and z where y)</sub>

🥈📉 Add colours based on elevation, there are ${number_of_elevation_zones} zones over a range of ${elevation_range[0]} to ${elevation_range[1]}
needs more work as I am getting the elevation from the objects geometry which has been scaled.
It looks like the colours are looping instead of using the full range.

Instead of lines, use shape so each elevation shape is a solid. Much bigger job that I imagined.

Test one will be to make a new shape object from the xyzCordsMulti using just the x y positions.

Could I use [SVGRenderer](https://threejs.org/docs/#examples/en/renderers/SVGRenderer) to save each layer as an SVG and then use [ShapePath](https://threejs.org/docs/#api/en/extras/core/ShapePath) to turn them back into objects?

Insert cell
Insert cell
Insert cell
Insert cell
import { batlowK } from "@hellonearthis/fabio-crameri-scientific-colour-maps";
Insert cell
// More recent versions change the way examples like OrbitControls are packaged.
VERSION = "0.127.0"
Insert cell
THREE = import(`https://unpkg.com/three@${VERSION}/build/three.module.js`)
Insert cell
OrbitControls = importExample("controls/OrbitControls", undefined, VERSION)
Insert cell
Insert cell
shapefile = require("shapefile")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more