Skip to content

Commit d93977a

Browse files
authored
Merge pull request #467 from wearepal/units-beta
Units in model view
2 parents 7927a8c + 731028b commit d93977a

File tree

13 files changed

+163
-26
lines changed

13 files changed

+163
-26
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,6 @@ yarn-debug.log*
3939
/coverage
4040

4141
# Ignore local vs code properties
42-
.vscode
42+
.vscode
43+
44+
.DS_Store

app/assets/stylesheets/model-editor.scss

+10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
background: steelblue;
3737
}
3838

39+
.socket.property {
40+
background: purple;
41+
}
42+
3943
.socket--number,
4044
.socket.number {
4145
background: #46b44c;
@@ -83,6 +87,12 @@
8387
}
8488
}
8589

90+
.connection.socket-output-property {
91+
.main-path {
92+
stroke: purple;
93+
}
94+
}
95+
8696
.connection.socket-output-number,
8797
.connection.socket-output-number {
8898
.main-path {

app/javascript/projects/analysis_panel.tsx

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
import { Extent } from 'ol/extent'
22
import * as React from 'react'
33
import { DatasetLayer, Layer, ModelOutputLayer } from './state'
4-
import { BooleanTileGrid, CategoricalTileGrid, NumericTileGrid } from './modelling/tile_grid'
4+
import { BooleanTileGrid, CategoricalTileGrid, NumericTileGrid, TileGridProps } from './modelling/tile_grid'
55
import { ChartData, extentToChartDataCached } from './analysis_panel_tools/subsection'
66
import { GenerateChart } from './analysis_panel_tools/charts'
77
import './analysis_panel.css'
88
import { getArea } from 'ol/sphere'
99
import { fromExtent } from 'ol/geom/Polygon'
1010
import { TeamExtentData } from './project_editor'
11+
import { getMedianCellSize } from './modelling/components/cell_area_component'
1112

1213
export type ChartType = "pie" | "hist" | "bar" | "kde"
1314

1415
interface ChartProps {
1516
chartType: ChartType | undefined
1617
chartData: ChartData | undefined
17-
18+
props: TileGridProps | undefined
19+
cellArea: number
1820
}
1921

20-
const Chart = ({ chartType, chartData }: ChartProps) => {
22+
const Chart = ({ chartType, chartData, props, cellArea }: ChartProps) => {
2123

2224
if (!chartType || !chartData) return <></>
2325

2426
return <GenerateChart
2527
chartData={chartData}
2628
chartType={chartType}
29+
props={props}
30+
cellArea={cellArea}
2731
/>
2832
}
2933

@@ -90,9 +94,10 @@ const ChartSelection = ({ SourceType, ChartTypeSelected, SetChartType }: ChartSe
9094
interface ChartLegendProps {
9195
chartData: ChartData | undefined
9296
sourceType: string
97+
props: TileGridProps | undefined
9398
}
9499

95-
const ChartLegend = ({ chartData, sourceType }: ChartLegendProps) => {
100+
const ChartLegend = ({ chartData, sourceType, props }: ChartLegendProps) => {
96101
if (!chartData) {
97102
return null
98103
}
@@ -122,7 +127,7 @@ const ChartLegend = ({ chartData, sourceType }: ChartLegendProps) => {
122127
<input
123128
disabled
124129
type="text"
125-
value={NumStats[key]}
130+
value={(key === "sum" && props && props.unit) ? `${NumStats[key]} ${props.unit}` : NumStats[key]}
126131
/>
127132
</div>
128133
))
@@ -264,6 +269,8 @@ export const AnalysisPanel = ({ selectedArea, setSelectedArea, setShowAP, select
264269
<Chart
265270
chartType={chartType}
266271
chartData={chartData}
272+
props={data instanceof NumericTileGrid ? data.properties : undefined}
273+
cellArea={data ? getMedianCellSize(data).area : 0}
267274
/>
268275
</div>
269276
<div style={{ textAlign: 'center' }}>
@@ -283,6 +290,7 @@ export const AnalysisPanel = ({ selectedArea, setSelectedArea, setShowAP, select
283290
<ChartLegend
284291
chartData={chartData}
285292
sourceType={dataSourceType}
293+
props={data instanceof NumericTileGrid ? data.properties : undefined}
286294
/>
287295
</div>
288296
</>

app/javascript/projects/analysis_panel_tools/charts/histogram.tsx

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import * as React from "react"
22
import * as d3 from 'd3'
33
import { ChartData } from "../subsection"
4+
import { TileGridProps } from "../../modelling/tile_grid"
45

56
interface HistogramProps {
67
chartData: ChartData
8+
props: TileGridProps | undefined
9+
cellArea: number
710
}
8-
export const GenerateHistogram = ({ chartData }: HistogramProps) => {
11+
12+
export const GenerateHistogram = ({ chartData, props, cellArea }: HistogramProps) => {
913

1014

1115
const axesRef = React.useRef(null)
@@ -62,7 +66,7 @@ export const GenerateHistogram = ({ chartData }: HistogramProps) => {
6266

6367

6468
return (
65-
<svg id="hist" width={width} height={height}>
69+
<svg id="hist" width={width} height={height} style={{marginBottom: 15, overflow: "auto"}}>
6670
<g
6771
width={boundsWidth}
6872
height={boundsHeight}
@@ -76,6 +80,25 @@ export const GenerateHistogram = ({ chartData }: HistogramProps) => {
7680
ref={axesRef}
7781
transform={`translate(${[MARGIN.left, MARGIN.top].join(",")})`}
7882
/>
83+
<text
84+
x={width / 2}
85+
y={height - 5}
86+
textAnchor="middle"
87+
fontSize="14px"
88+
fill="black"
89+
>
90+
{props?.area && props.unit ? `${props.unit}/${props.area}` : `value`}
91+
</text>
92+
<text
93+
x={-height / 2}
94+
y={15}
95+
transform="rotate(-90)"
96+
textAnchor="middle"
97+
fontSize="14px"
98+
fill="black"
99+
>
100+
km²
101+
</text>
79102
</svg>
80103
);
81104
}

app/javascript/projects/analysis_panel_tools/charts/index.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { ChartType } from "../../analysis_panel"
44
import { GeneratePieChart } from "./pie"
55
import { GenerateHistogram } from "./histogram"
66
import { GenerateBarChart } from "./bar"
7+
import { TileGridProps } from "../../modelling/tile_grid"
78

89
interface ChartProps {
910
chartData: ChartData
1011
chartType: ChartType
12+
props: TileGridProps | undefined
13+
cellArea: number
1114
}
12-
export const GenerateChart = ({ chartData, chartType }: ChartProps) => {
15+
export const GenerateChart = ({ chartData, chartType, props, cellArea }: ChartProps) => {
1316
switch (chartType) {
1417
case "pie":
1518
return <GeneratePieChart
@@ -19,6 +22,8 @@ export const GenerateChart = ({ chartData, chartType }: ChartProps) => {
1922
case "hist":
2023
return <GenerateHistogram
2124
chartData={chartData}
25+
props={props}
26+
cellArea={cellArea}
2227
/>
2328
break;
2429
case "bar":

app/javascript/projects/analysis_panel_tools/subsection.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getArea } from "ol/sphere"
55
import { fromExtent } from "ol/geom/Polygon"
66
import { getColorStops } from "../reify_layer/model_output"
77
import { re, sum } from "mathjs"
8+
import { getMedianCellSize } from "../modelling/components/cell_area_component"
89

910
type Color = [number, number, number, number]
1011

@@ -35,6 +36,14 @@ export function findColor(value: number, colorArray: any[]): Color {
3536
return alpha
3637
}
3738

39+
function unitsAdjustmentFactor(unit: string | undefined, grid: NumericTileGrid): number {
40+
const area = getMedianCellSize(grid).area
41+
if (unit === "m²") return area / 1
42+
if (unit === "ha") return area / 10000
43+
if (unit === "km²") return area / (1000 ** 2)
44+
return 1
45+
}
46+
3847
function medianFromMap(arr: [number, number][], total: number): number | undefined {
3948
let idx = total / 2;
4049

@@ -162,17 +171,17 @@ export function extentToChartData(colors: Color[] | undefined, model: BooleanTil
162171
let counts = new Map<any, number>()
163172
let color = new Map<any, [number, number, number, number]>()
164173
let numeric_stats: NumericStats | undefined
174+
const cellSize = getMedianCellSize(model).area / 1000000
165175

166176
for (let x = outputTileRange.minX; x <= outputTileRange.maxX; x++) {
167177
for (let y = outputTileRange.minY; y <= outputTileRange.maxY; y++) {
168178

169179
if (model instanceof CategoricalTileGrid) {
170180

171-
const area = getArea(fromExtent(tileGrid.getTileCoordExtent([model.zoom, x, y]))) / 1000000
172181

173182
const value = model.labels.get(model.get(x, y)) ? model.labels.get(model.get(x, y)) : "No Data"
174183
const count = counts.get(value) || 0
175-
counts.set(value, count + area)
184+
counts.set(value, count + cellSize)
176185

177186

178187
if (colors) {
@@ -182,12 +191,11 @@ export function extentToChartData(colors: Color[] | undefined, model: BooleanTil
182191

183192
} else {
184193

185-
const area = model instanceof NumericTileGrid ? 1 : getArea(fromExtent(tileGrid.getTileCoordExtent([model.zoom, x, y]))) / 1000000
186194
const value = model.get(x, y)
187195

188196
const count = counts.get(value) || 0
189197

190-
counts.set(value, count + area)
198+
counts.set(value, count + cellSize)
191199

192200
if (colors && model instanceof BooleanTileGrid) {
193201
const col_value = colors[value ? 1 : 0]
@@ -219,7 +227,7 @@ export function extentToChartData(colors: Color[] | undefined, model: BooleanTil
219227
const range = max - min
220228
const step = range / bins
221229

222-
const _sum = sum(mapEntries.map((x) => x[1] * x[0]))
230+
const _sum = sum(mapEntries.map((x) => x[1] * x[0])) * unitsAdjustmentFactor(model.properties.area, model)
223231
const total_entries = mapEntries.reduce((acc, cur) => acc + cur[1], 0)
224232

225233
const _mean = _sum / total_entries

app/javascript/projects/modelling/components/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import { SegmentComponent } from "./segment_component"
4141
import { KewSamplesComponent } from "./kew_samples_component"
4242
import { InterpolationComponent } from "./interpolation_component"
4343
import { NatmapSoilComponent } from "./natmap_soil_component"
44+
import { UnitComponent } from "./units_component"
45+
import { areas, units } from "../tile_grid"
4446

4547
export interface ProjectProperties {
4648
extent: Extent
@@ -87,6 +89,10 @@ export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: S
8789
new MapLayerComponent(saveMapLayer),
8890
new SaveModelOutputComponent(saveModel),
8991

92+
// Properties
93+
new UnitComponent('Unit', projectProps, units.map((unit, idx) => ({ name: unit, id: idx }))),
94+
new UnitComponent('Area', projectProps, areas.map((unit, idx) => ({ name: unit, id: idx }))),
95+
9096
// Conversions
9197
new NumberToNumericDatasetComponent(),
9298
new NumericDatasetToNumberComponent(),

app/javascript/projects/modelling/components/map_layer_component.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Input, Node } from "rete"
22
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
3-
import { dataSocket } from "../socket_types"
3+
import { dataSocket, propertySocket } from "../socket_types"
44
import { BooleanTileGrid, CategoricalTileGrid, NumericTileGrid } from "../tile_grid"
55
import { BaseComponent } from "./base_component"
66

@@ -18,6 +18,7 @@ export class MapLayerComponent extends BaseComponent {
1818
async builder(node: Node) {
1919
node.meta.toolTip = "Output a model to the map view."
2020
node.addInput(new Input("in", "Output", dataSocket))
21+
node.addInput(new Input("props", "Properties (optional)", propertySocket, true))
2122
}
2223

2324
async worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]) {
@@ -32,7 +33,18 @@ export class MapLayerComponent extends BaseComponent {
3233

3334
const name = editorNode.data.name as string
3435

35-
if (inputs["in"][0]) this.callback(node.id, name ? (name !== "" ? name.trim() : undefined) : undefined, inputs["in"][0] as BooleanTileGrid | NumericTileGrid | CategoricalTileGrid)
36+
let out = inputs["in"][0] as BooleanTileGrid | NumericTileGrid | CategoricalTileGrid
37+
const props = inputs["props"]
38+
39+
out = (out instanceof NumericTileGrid && props.length > 0) ? out.clone() : out
40+
41+
props.forEach((prop: any) => {
42+
if (out instanceof NumericTileGrid){
43+
out.properties[(prop.type as string).toLowerCase()] = prop.unit
44+
}
45+
})
46+
47+
if (out) this.callback(node.id, name ? (name !== "" ? name.trim() : undefined) : undefined, out)
3648
else editorNode.meta.errorMessage = 'No input'
3749

3850
}

app/javascript/projects/modelling/components/segment_component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ export class SegmentComponent extends BaseComponent {
129129
}
130130

131131
if (!('cls_conf' in node.data)) {
132-
node.data.cls_conf = "90"
132+
node.data.cls_conf = "0"
133133
}
134134

135135
if (!('n_repeats' in node.data)) {
136-
node.data.n_repeats = "5"
136+
node.data.n_repeats = "1"
137137
}
138138

139139
if (!('prompt' in node.data)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Node, Output } from "rete";
2+
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data";
3+
import { BaseComponent } from "./base_component";
4+
import { ProjectProperties } from ".";
5+
import { propertySocket } from "../socket_types";
6+
import { SelectControl, SelectControlOptions } from "../controls/select";
7+
8+
export class UnitComponent extends BaseComponent {
9+
unitArray: SelectControlOptions[]
10+
unitType: string
11+
unit: string
12+
13+
constructor(unit: string, projectProps : ProjectProperties, unitArray: SelectControlOptions[]) {
14+
super(`${unit} Property`)
15+
this.category = "Properties"
16+
this.unitArray = unitArray
17+
this.unit = unit
18+
}
19+
20+
async builder(node: Node) {
21+
node.addControl(new SelectControl(
22+
this.editor,
23+
this.unit,
24+
() => this.unitArray,
25+
() => []
26+
))
27+
node.addOutput(new Output(this.unit, this.unit, propertySocket))
28+
}
29+
30+
worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]): void {
31+
const idx = (node.data[this.unit] ?? 0 )as number
32+
outputs[this.unit] = {
33+
type: this.unit,
34+
unit: this.unitArray[idx].name
35+
}
36+
}
37+
}

app/javascript/projects/modelling/socket_types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const numericDataSocket = new Socket('Numeric dataset')
77

88
export const categoricalDataSocket = new Socket('Categorical dataset')
99

10+
export const propertySocket = new Socket('Property')
11+
1012
export const dataSocket = new Socket('Dataset')
1113
booleanDataSocket.combineWith(dataSocket)
1214
numericDataSocket.combineWith(dataSocket)

0 commit comments

Comments
 (0)