Skip to content

Commit

Permalink
Merge pull request #410 from open-pv/feature/triangulate_vegetation
Browse files Browse the repository at this point in the history
Triangulate Vegetation
  • Loading branch information
khdlr authored Jan 24, 2025
2 parents 797e42e + ab83f20 commit bfc86ce
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 170 deletions.
16 changes: 2 additions & 14 deletions src/components/ThreeViewer/Meshes/VegetationMesh.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
import React, { useMemo } from 'react'
import * as THREE from 'three'

const vegetationColors = [
'#27AD6B', // Light green
'#2DBE76', // mint
'#33CC80', //dull green
]

const VegetationMesh = ({ geometries }) => {
const randomColors = useMemo(() => {
return geometries.map(
() =>
vegetationColors[Math.floor(Math.random() * vegetationColors.length)],
)
}, [geometries])

return (
<>
{geometries.map((geometry, index) => (
<mesh key={index} geometry={geometry}>
<meshLambertMaterial
vertexColors={false}
color={randomColors[index]}
color='#3C9000'
shininess={0}
side={THREE.DoubleSide}
/>
</mesh>
Expand Down
103 changes: 2 additions & 101 deletions src/components/ThreeViewer/Terrain.jsx
Original file line number Diff line number Diff line change
@@ -1,106 +1,7 @@
import * as geotiff from 'geotiff'
import React, { useEffect, useState } from 'react'
import * as THREE from 'three'
import { mercator2meters } from '../../simulation/download'
import {
coordinatesWebMercator,
coordinatesXY15,
xyzBounds,
} from '../../simulation/location'

class ElevationManager {
static instancePromise = null
static state = 'uninitialized'

tiff = null
image = null
width = -1
height = -1
boundingBox = [0, 0, 0, 0]
cache = {}
requestedRegions = {}

static async toPoint3D(x, y) {
const me = await this.getInstance()
const xyscale = mercator2meters()
const [cx, cy] = coordinatesWebMercator

const px = (x - me.tiepoint[3]) / me.pixelScale[0]
const py = (y - me.tiepoint[4]) / -me.pixelScale[1]

const fx = Math.floor(px)
const fy = Math.floor(py)
const tl = await me.requestPixel(fx, fy)
const tr = await me.requestPixel(fx + 1, fy)
const bl = await me.requestPixel(fx, fy + 1)
const br = await me.requestPixel(fx + 1, fy + 1)

// bilinear interpolation
const tx = px % 1
const ty = py % 1
const qx = 1 - tx
const qy = 1 - ty
const z =
qx * qy * tl + // Top left
tx * qy * tr + // Top right
qx * ty * bl + // Bottom left
tx * ty * br // Botrom right

// Calculate normal
let dx =
-qy * tl + // Top left
qy * tr + // Top right
-ty * bl + // Bottom left
ty * tr // Botrom right
let dy =
-qx * tl + // Top left
-tx * tr + // Top right
qx * bl + // Bottom left
tx * br // Botrom right
dx /= xyscale * me.pixelScale[0]
dy /= xyscale * -me.pixelScale[1]
const len = Math.sqrt(dx * dx + dy * dy + 1.0)

return {
point: [xyscale * (x - cx), xyscale * (y - cy), z],
normal: [dx / len, dy / len, -1.0 / len],
}
}

static async getInstance() {
if (!this.instancePromise) {
this.instancePromise = this.init()
}
return this.instancePromise
}

static async init() {
this.instance = new ElevationManager()
let me = this.instance
me.tiff = await geotiff.fromUrl(
'https://maps.heidler.info/sonny_dtm_20.tif',
)
me.image = await this.instance.tiff.getImage()
me.pixelScale = me.image.fileDirectory.ModelPixelScale
me.tiepoint = me.image.fileDirectory.ModelTiepoint
return me
}

async requestPixel(px, py) {
const tile_x = Math.floor(px / 256) * 256 // Internal COG tile window start
const tile_y = Math.floor(py / 256) * 256 // Internal COG tile window start
const tile_key = [tile_x, tile_y]
if (!this.requestedRegions[tile_key]) {
// Only request each tile once
this.requestedRegions[tile_key] = this.image.readRasters({
window: [tile_x, tile_y, tile_x + 256, tile_y + 256],
interleave: true,
})
}
const region = await this.requestedRegions[tile_key]
return region[px - tile_x + (py - tile_y) * 256]
}
}
import { coordinatesXY15, xyzBounds } from '../../simulation/location'
import { ElevationManager } from '../../simulation/elevation'

/** Load an OSM map tile and return it as a THREE Mesh
*/
Expand Down
100 changes: 100 additions & 0 deletions src/simulation/elevation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as geotiff from 'geotiff'
import { mercator2meters } from './download'
import { coordinatesWebMercator } from './location'

/**
* Loads the tiles from geotiff terrain model to cache, so that requests to the geotiff are faster.
*/
export class ElevationManager {
static instancePromise = null
static state = 'uninitialized'

tiff = null
image = null
width = -1
height = -1
boundingBox = [0, 0, 0, 0]
cache = {}
requestedRegions = {}

static async toPoint3D(x, y) {
const me = await this.getInstance()
const xyscale = mercator2meters()
const [cx, cy] = coordinatesWebMercator

const px = (x - me.tiepoint[3]) / me.pixelScale[0]
const py = (y - me.tiepoint[4]) / -me.pixelScale[1]

const fx = Math.floor(px)
const fy = Math.floor(py)
const tl = await me.requestPixel(fx, fy)
const tr = await me.requestPixel(fx + 1, fy)
const bl = await me.requestPixel(fx, fy + 1)
const br = await me.requestPixel(fx + 1, fy + 1)

// bilinear interpolation
const tx = px % 1
const ty = py % 1
const qx = 1 - tx
const qy = 1 - ty
const z =
qx * qy * tl + // Top left
tx * qy * tr + // Top right
qx * ty * bl + // Bottom left
tx * ty * br // Botrom right

// Calculate normal
let dx =
-qy * tl + // Top left
qy * tr + // Top right
-ty * bl + // Bottom left
ty * tr // Botrom right
let dy =
-qx * tl + // Top left
-tx * tr + // Top right
qx * bl + // Bottom left
tx * br // Botrom right
dx /= xyscale * me.pixelScale[0]
dy /= xyscale * -me.pixelScale[1]
const len = Math.sqrt(dx * dx + dy * dy + 1.0)

return {
point: [xyscale * (x - cx), xyscale * (y - cy), z],
normal: [dx / len, dy / len, -1.0 / len],
}
}

static async getInstance() {
if (!this.instancePromise) {
this.instancePromise = this.init()
}
return this.instancePromise
}

static async init() {
this.instance = new ElevationManager()
let me = this.instance
me.tiff = await geotiff.fromUrl(
'https://maps.heidler.info/sonny_dtm_20.tif',
)
me.image = await this.instance.tiff.getImage()
me.pixelScale = me.image.fileDirectory.ModelPixelScale
me.tiepoint = me.image.fileDirectory.ModelTiepoint
return me
}

async requestPixel(px, py) {
const tile_x = Math.floor(px / 256) * 256 // Internal COG tile window start
const tile_y = Math.floor(py / 256) * 256 // Internal COG tile window start
const tile_key = [tile_x, tile_y]
if (!this.requestedRegions[tile_key]) {
// Only request each tile once
this.requestedRegions[tile_key] = this.image.readRasters({
window: [tile_x, tile_y, tile_x + 256, tile_y + 256],
interleave: true,
})
}
const region = await this.requestedRegions[tile_key]
return region[px - tile_x + (py - tile_y) * 256]
}
}
10 changes: 1 addition & 9 deletions src/simulation/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export async function mainSimulation(location) {
console.log('Vegetation Raster processed successfully')

console.log('Processing vegetation geometries...')
const vegetationGeometries = processVegetationData(
const vegetationGeometries = await processVegetationData(
vegetationRaster,
new THREE.Vector3(0, 0, 0),
30,
Expand Down Expand Up @@ -131,14 +131,6 @@ export async function mainSimulation(location) {
console.log('Vegetation processing completed')
}

//vegetationGeometries.surrounding.forEach((geom) => {
// scene.addShadingGeometry(geom)
//})
//vegetationGeometries.surrounding.forEach((geom) => {
// scene.addShadingGeometry(geom)
//})
//scene.addVegetationRaster(rasterData)

let numSimulations = window.numSimulations || 80
function loadingBarWrapperFunction(progress, total) {
return window.setSimulationProgress((progress * 100) / total)
Expand Down
Loading

0 comments on commit bfc86ce

Please sign in to comment.