Skip to content

Commit

Permalink
Merge pull request #371 from wearepal/orval-model-view
Browse files Browse the repository at this point in the history
ORVal component on model view
  • Loading branch information
paulthatjazz authored Apr 22, 2024
2 parents 8be7c4a + af089d0 commit 528066f
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 2 deletions.
2 changes: 2 additions & 0 deletions app/javascript/projects/modelling/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { LehLandCoverComponent } from "./leh_land_cover_component"
import { MlTreeHedgeComponent } from "./ml_tree_hedge_component"
import { ATIComponent } from "./ati_component"
import { DesignationsComponent } from "./designations_component"
import { ORValComponent } from "./orval_component"

export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: SaveModel, getDatasets: getDatasets, extent: Extent, zoom: number): BaseComponent[] {
return [
Expand All @@ -41,6 +42,7 @@ export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: S
new MlTreeHedgeComponent(extent, zoom),
new BiodiversityComponent(extent, zoom),
new NevoLayerComponent(extent, zoom),
new ORValComponent(extent, zoom),
new OSMLandUseComponent(extent, zoom),
new NumericConstantComponent(),
new DigitalModelComponent(extent, zoom),
Expand Down
326 changes: 326 additions & 0 deletions app/javascript/projects/modelling/components/orval_component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
import { Extent } from "ol/extent"
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
import { BaseComponent } from "./base_component"
import { Node, Output, Socket } from "rete"
import { booleanDataSocket } from "../socket_types"
import { createXYZ } from "ol/tilegrid";
import { retrieveModelData } from "../model_retrieval"
import { BooleanTileGrid } from "../tile_grid"
import { TypedArray } from "d3"
import { bboxFromExtent } from "../bounding_box"
import { GeoJSON } from "ol/format"
import { Feature } from "ol"
import { Geometry } from "ol/geom"


const FeaturesCache : Feature<Geometry>[] | null = null

interface OutputData {
name: string
prettyName: string
socket: Socket
layer: string
fn: (extent: Extent, zoom: number, type: string, layer: string) => Promise<BooleanTileGrid>
}

const OutputDatas : OutputData[] = [
{
name: "Paths",
prettyName: "Paths",
socket: booleanDataSocket,
layer: "ORVAL:paths_england",
fn: retrievePathData
},
{
name: "Path Access",
prettyName: "Path Access",
socket: booleanDataSocket,
layer: "ORVAL:paths_england_accesspts",
fn: retrievePathData
},
{
name: "Beaches",
prettyName: "Beaches",
socket: booleanDataSocket,
layer: "ORVAL:beaches_england",
fn: retrievePathData
},
{
name: "Parks - any",
prettyName: "Parks - any",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrievePathData
},
{
name: "allotment",
prettyName: "Parks - Allotment",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "anemity_park",
prettyName: "Parks - Amenity Park",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "anemity_woods",
prettyName: "Parks - Amenity Woods",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "cemetery",
prettyName: "Parks - Cemetery",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "churchyard",
prettyName: "Parks - Churchyard",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "club",
prettyName: "Parks - Club",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "common",
prettyName: "Parks - Common",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "country_park",
prettyName: "Parks - Country Park",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "doorstep_green",
prettyName: "Parks - Doorstep Green",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "FC_woods",
prettyName: "Parks - FC Woods",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "garden",
prettyName: "Parks - Garden",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "golf",
prettyName: "Parks - Golf",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "grave_yard",
prettyName: "Parks - Grave Yard",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "millenium_green",
prettyName: "Parks - Millenium Green",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "nature",
prettyName: "Parks - Nature",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "park",
prettyName: "Parks - Park",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "recreation_ground",
prettyName: "Parks - Recreation Ground",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "village_green",
prettyName: "Parks - Village Green",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
},
{
name: "wood",
prettyName: "Parks - Wood",
socket: booleanDataSocket,
layer: "ORVAL:parks_england",
fn: retrieveCatData
}
]

async function retrieveCatData(extent: Extent, zoom: number, type: string, layer: string) {

const tileGrid = createXYZ()
const outputTileRange = tileGrid.getTileRangeForExtentAndZ(extent, zoom)

let features = FeaturesCache

if (features === null) {

const response = await fetch(
"https://landscapes.wearepal.ai/geoserver/wfs?" +
new URLSearchParams(
{
outputFormat: 'application/json',
request: 'GetFeature',
typeName: layer,
srsName: 'EPSG:3857',
bbox : bboxFromExtent(extent),
}
)
)

if (!response.ok) throw new Error()

features = new GeoJSON().readFeatures(await response.json())

}

const result = new BooleanTileGrid(
zoom,
outputTileRange.minX,
outputTileRange.minY,
outputTileRange.getWidth(),
outputTileRange.getHeight()
)

for (let feature of features) {
const geom = feature.getGeometry()
if (geom === undefined) { continue }

const featureTileRange = tileGrid.getTileRangeForExtentAndZ(
geom.getExtent(),
zoom
)

for (
let x = Math.max(featureTileRange.minX, outputTileRange.minX);
x <= Math.min(featureTileRange.maxX, outputTileRange.maxX);
++x
) {
for (
let y = Math.max(featureTileRange.minY, outputTileRange.minY);
y <= Math.min(featureTileRange.maxY, outputTileRange.maxY);
++y
) {
const center = tileGrid.getTileCoordCenter([zoom, x, y])
if (geom.intersectsCoordinate(center) && feature.get("TYPE") === type) {
result.set(x, y, true)
}
}
}

}

return result
}

async function retrievePathData(extent: Extent, zoom: number, type: string, layer: string) {
const tileGrid = createXYZ()
const outputTileRange = tileGrid.getTileRangeForExtentAndZ(extent, zoom)

const geotiff = await retrieveModelData(extent, layer, outputTileRange)

const rasters = await geotiff.readRasters({ bbox: extent, width: outputTileRange.getWidth(), height: outputTileRange.getHeight() })
const image = await geotiff.getImage()

const result = new BooleanTileGrid(
zoom,
outputTileRange.minX,
outputTileRange.minY,
outputTileRange.getWidth(),
outputTileRange.getHeight()
)

for (let i = 0; i < (rasters[0] as TypedArray).length; i++) {

let x = (outputTileRange.minX + i % image.getWidth())
let y = (outputTileRange.minY + Math.floor(i / image.getWidth()))

result.set(x, y, rasters[3][i])

}

return result
}

export class ORValComponent extends BaseComponent {
projectExtent: Extent
projectZoom: number
outputCache: Map<string, BooleanTileGrid>

constructor(projectExtent: Extent, projectZoom: number) {
super("ORVal")
this.category = "Inputs"
this.projectExtent = projectExtent
this.projectZoom = projectZoom
this.outputCache = new Map()
}

async builder(node: Node) {
node.meta.toolTip = "Data from ORVal (Outdoor Recreation Valuation Tool)"
node.meta.toolTipLink = "https://www.leep.exeter.ac.uk/orval/"
OutputDatas.forEach(outputData => node.addOutput(new Output(outputData.name, outputData.prettyName, outputData.socket)))
}

async worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]) {

const editorNode = this.editor?.nodes.find(n => n.id === node.id)
if (editorNode === undefined) { return }

const promises = OutputDatas.filter(d => node.outputs[d.name].connections.length > 0)
.map(d => {
return this.outputCache.has(d.name) ? this.outputCache.get(d.name) : d.fn(this.projectExtent, this.projectZoom, d.name, d.layer)
.then(data => {
data.name = d.name
this.outputCache.set(d.name, data)
outputs[d.name] = data
})
})

await Promise.all(promises)

editorNode.update()

}

}
4 changes: 2 additions & 2 deletions app/javascript/projects/modelling/model_retrieval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as GeoTIFF from 'geotiff/dist-browser/geotiff'
import { Extent } from 'ol/extent'
import { bboxFromExtent } from './bounding_box'

export async function retrieveModelData(extent: Extent, source: string, tileRange: any) {
export async function retrieveModelData(extent: Extent, source: string, tileRange: any, style?: string) {

// Uses WMS server: Returns data between 0 and 255

Expand All @@ -20,7 +20,7 @@ export async function retrieveModelData(extent: Extent, source: string, tileRang
version: '1.3.0',
request: 'GetMap',
layers: source,
styles: '',
styles: style || '',
format: 'image/geotiff',
transparent: 'true',
width,
Expand Down

0 comments on commit 528066f

Please sign in to comment.