Skip to content

Commit

Permalink
Merge pull request #442 from wearepal/kew-data-2
Browse files Browse the repository at this point in the history
Kew sample data updates & Interpolation component
  • Loading branch information
paulthatjazz authored Nov 5, 2024
2 parents 0f66f8e + 2304a1a commit d72dc9a
Show file tree
Hide file tree
Showing 11 changed files with 504 additions and 18 deletions.
48 changes: 48 additions & 0 deletions app/javascript/modelling/worker/interpolation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getMedianCellSize } from "../../projects/modelling/components/cell_area_component"
import { BooleanTileGrid, NumericTileGrid } from "../../projects/modelling/tile_grid"
import { kdTree } from 'kd-tree-javascript'


export function interpolateGrid(input : NumericTileGrid, mask : BooleanTileGrid, type: "NearestNeighbour" | "Bilinear", maxDist: number) : NumericTileGrid {

const result = new NumericTileGrid(input.zoom, input.x, input.y, input.width, input.height)

const points: {x: number, y: number, val: number}[] = []

input.iterate((x, y) => {
const val = input.get(x, y)
if(isNaN(val)) return
points.push({x, y, val: input.get(x, y)})
})

if (points.length === 0) return result

const tree = new kdTree(
points,
(a, b) => Math.sqrt(
Math.pow(a.x - b.x, 2) +
Math.pow(a.y - b.y, 2)
),
['x', 'y', 'val']
)

const tileSize = getMedianCellSize(input).length

result.iterate((x, y) => {
switch (type) {
case "NearestNeighbour":
const nearest = tree.nearest({x, y}, 1)[0]
const dist = nearest[1] * tileSize
if (maxDist === 0 || dist < maxDist) result.set(x, y, nearest[0].val)
break
case "Bilinear":
// WIP
break
default:
break
}
})


return result
}
4 changes: 3 additions & 1 deletion app/javascript/packs/modelling_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { expose } from 'threads'
import { generateDistanceMap } from '../modelling/worker/generateDistanceMap'
import { performOperation } from '../modelling/worker/performOperation'
import { rasteriseOverlay } from '../modelling/worker/rasteriseOverlay'
import { interpolateGrid } from '../modelling/worker/interpolation'

expose({
generateDistanceMap,
performOperation,
rasteriseOverlay
rasteriseOverlay,
interpolateGrid
})
4 changes: 3 additions & 1 deletion app/javascript/projects/layer_palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { designations } from './modelling/designations'
import { IMDProperties } from './reify_layer/imd'
import { ProjectPermissions } from './project_editor'
import { KewPointOptions } from './reify_layer/kew'
import { seasonYearOptions } from './modelling/components/kew_samples_component'

interface AddLayerButtonProps {
prototype: Layer
Expand Down Expand Up @@ -113,10 +114,11 @@ export const LayerPalette = ({ addLayer, hide, dbModels, getTeamDatasets, teamNa
name: "Wakehurst Soil",
identifier: "kew:wakehurst_soil_rp3857",
fill: "hsv",
metric: KewPointOptions[0],
metric: KewPointOptions.find(option => option.value === "ph")!,
metricOpts: KewPointOptions,
visible: true,
opacity: 1,
seasonYear: seasonYearOptions[0]
}}
/>
}
Expand Down
42 changes: 42 additions & 0 deletions app/javascript/projects/map_view.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.ol-popup {
position: absolute;
background-color: white;
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
min-width: 580px;
display: none;
}
.ol-popup:after, .ol-popup:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
}
.ol-popup-closer:after {
content: "✖";
}
9 changes: 8 additions & 1 deletion app/javascript/projects/map_view.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { Feature, Map, View } from 'ol'
import { Feature, Map, Overlay, View } from 'ol'
import { Control, ScaleLine, defaults as defaultControls } from 'ol/control'
import { Extent, createEmpty as createEmptyExtent, extend, isEmpty } from 'ol/extent'
import olBaseLayer from 'ol/layer/Base'
Expand All @@ -14,6 +14,7 @@ import { Fill, Stroke, Style } from 'ol/style'
import { fromExtent } from 'ol/geom/Polygon'
import { DragBox, Select } from 'ol/interaction'
import { platformModifierKeyOnly } from 'ol/events/condition'
import './map_view.css'

function getLayerExtent(layer: olBaseLayer) {
if (layer instanceof VectorLayer) {
Expand Down Expand Up @@ -221,6 +222,12 @@ export const MapView = ({ layers, dbModels, initialZoom, setZoom, initialCenter,

return <div className="flex-grow-1 position-relative">
<div className="bg-dark" style={{ width: "100%", height: "100%" }} ref={mapRef as any} />

<div id="popup" className="ol-popup">
<a href="#" id="popup-closer" className="ol-popup-closer"></a>
<div id="popup-content"></div>
</div>

{
!allLayersVisible &&
<div className="bg-dark text-light rounded-sm px-3 py-2 position-absolute" style={{ bottom: "1em", left: "50%", transform: "translateX(-50%)" }}>
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/projects/modelling/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { HedgerowComponent } from "./hedgerow_component"
import { ProjectPermissions } from "../../project_editor"
import { SoilComponent } from "./soil_component"
import { SegmentComponent } from "./segment_component"
import { KewSamplesComponent } from "./kew_samples_component"
import { InterpolationComponent } from "./interpolation_component"

export interface ProjectProperties {
extent: Extent
Expand All @@ -55,6 +57,7 @@ export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: S

// Team permissions restrict some components. Add them here.
if (permissions.DefraHedgerows) restrictedComponents.push(new HedgerowComponent(projectProps))
if (permissions.KewSamples) restrictedComponents.push(new KewSamplesComponent(projectProps))

// Freely available components here.
const components : BaseComponent[] = [
Expand Down Expand Up @@ -91,6 +94,7 @@ export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: S
new AreaComponent(),
new DistanceMapComponent(projectProps),
new ScaleFactorComponent(),
new InterpolationComponent(projectProps),

// Charts
new BarChartComponent(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { BaseComponent } from "./base_component"
import { Input, Node, Output, Socket } from "rete"
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
import { ProjectProperties } from "."
import { numberSocket, numericDataSocket } from "../socket_types"
import { workerPool } from '../../../modelling/workerPool'
import { maskFromExtentAndShape } from "../bounding_box"
import { NumericConstant } from "../numeric_constant"
import { NumericTileGrid } from "../tile_grid"

export class InterpolationComponent extends BaseComponent {
projectProps : ProjectProperties
maxdist : number
cache : Map<number, Map<NumericTileGrid, NumericTileGrid>>

constructor(projectProps : ProjectProperties) {
super("Interpolation")
this.category = "Calculations"
this.projectProps = projectProps
this.maxdist = 50
this.cache = new Map()
}

async builder(node: Node) {
node.addInput(new Input('input', 'Input', numericDataSocket))
node.addInput(new Input('maxdist', `maxdist (default: ${this.maxdist})`, numberSocket))
node.addOutput(new Output('output', 'Output', numericDataSocket))
}

async worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]) {
let editorNode = this.editor?.nodes.find(n => n.id === node.id)
if (editorNode === undefined) { return }


const mask = await maskFromExtentAndShape(
this.projectProps.extent,
this.projectProps.zoom,
this.projectProps.maskLayer,
this.projectProps.maskCQL,
this.projectProps.mask
)

const maxDist = inputs['maxdist'].length > 0 ? (inputs['maxdist'][0] as NumericConstant).value : this.maxdist
const input = inputs['input'][0] as NumericTileGrid

// TODO: Caching doesn't work
if(this.cache.has(maxDist)){

if(this.cache.get(maxDist)?.has(input)){

outputs['output'] = this.cache.get(maxDist)?.get(input)
return
}
}


const out = await workerPool.queue(async worker =>
worker.interpolateGrid(inputs['input'][0], mask, "NearestNeighbour", maxDist)
)

const map = this.cache.get(maxDist) || new Map()
map.set(input, out)
this.cache.set(maxDist, map)

outputs['output'] = out
}

}
Loading

0 comments on commit d72dc9a

Please sign in to comment.