Skip to content

Commit d97e66c

Browse files
authored
Merge pull request #170 from wearepal/census-component
Census component - beta
2 parents 70f6b1b + b5d990b commit d97e66c

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { Node, Output } from "rete"
2+
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
3+
import { BaseComponent } from "./base_component"
4+
import { numericDataSocket } from "../socket_types"
5+
import { currentBbox , currentExtent} from "../bounding_box"
6+
import GeoJSON from "ol/format/GeoJSON"
7+
import { createXYZ } from "ol/tilegrid"
8+
import { NumericTileGrid } from "../tile_grid"
9+
import { SelectControl } from "../controls/select"
10+
11+
const censusOptions = [
12+
{
13+
"code": "output_modified_HIGHEST_QUAL_0",
14+
"name": "No Qualifications",
15+
"id": 0
16+
},
17+
{
18+
"code": "output_modified_HIGHEST_QUAL_1",
19+
"name": "Level 1 and entry level",
20+
"id": 1
21+
},
22+
{
23+
"code": "output_modified_HIGHEST_QUAL_2",
24+
"name": "Level 2",
25+
"id": 2
26+
},
27+
{
28+
"code": "output_modified_HIGHEST_QUAL_3",
29+
"name": "Apprenticeship",
30+
"id": 3
31+
},
32+
{
33+
"code": "output_modified_HIGHEST_QUAL_4",
34+
"name": "Level 3 (A Level)",
35+
"id": 4
36+
},
37+
{
38+
"code": "output_modified_HIGHEST_QUAL_5",
39+
"name": "Level 4 (BSc/BA or higher)",
40+
"id": 5
41+
},
42+
{
43+
"code": "output_modified_HIGHEST_QUAL_6",
44+
"name": "Other",
45+
"id": 6
46+
},
47+
{
48+
"code": "output_modified_HIGHEST_QUAL_-8",
49+
"name": "Does not apply",
50+
"id": 7
51+
}
52+
]
53+
54+
async function fetchCensusShapefilesFromExtent(bbox: string){
55+
56+
const response = await fetch(
57+
"https://landscapes.wearepal.ai/geoserver/wfs?" +
58+
new URLSearchParams(
59+
{
60+
outputFormat: 'application/json',
61+
request: 'GetFeature',
62+
typeName: 'census:oa_2021_ew_bgc_v3_1294693978171420992__oa_2021_ew_bgc_v2',
63+
srsName: 'EPSG:3857',
64+
bbox
65+
}
66+
)
67+
)
68+
if (!response.ok) throw new Error()
69+
70+
return await response.json()
71+
}
72+
73+
export class CensusComponent extends BaseComponent {
74+
DataCache: Map<any, any> // CACHING WIP
75+
76+
constructor() {
77+
super("UK Census - Highest Education")
78+
this.category = "Inputs"
79+
}
80+
81+
async builder(node: Node) {
82+
node.addOutput(new Output('out', 'Census Data', numericDataSocket))
83+
84+
node.meta.toolTip = "Experimental Feature - Census data for highest education per Output Area. Returns percentage value."
85+
86+
node.addControl(
87+
new SelectControl(
88+
this.editor,
89+
'censusOptionId',
90+
() => censusOptions,
91+
() => [],
92+
'Education Level'
93+
)
94+
)
95+
}
96+
97+
98+
async worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]) {
99+
100+
101+
const editorNode = this.editor?.nodes.find(n => n.id === node.id)
102+
if (editorNode === undefined) { return }
103+
104+
const zoom = 20
105+
106+
const shapeFiles = await fetchCensusShapefilesFromExtent(currentBbox)
107+
const features = new GeoJSON().readFeatures(shapeFiles)
108+
109+
const tileGrid = createXYZ()
110+
const outputTileRange = tileGrid.getTileRangeForExtentAndZ(currentExtent, zoom)
111+
112+
const result = editorNode.meta.output = outputs['out'] =new NumericTileGrid(
113+
zoom,
114+
outputTileRange.minX,
115+
outputTileRange.minY,
116+
outputTileRange.getWidth(),
117+
outputTileRange.getHeight()
118+
)
119+
120+
let index = node.data.censusOptionId as number
121+
if (index === undefined) { index = 0 }
122+
123+
for (let feature of features) {
124+
125+
let n =
126+
+feature.get("output_modified_HIGHEST_QUAL_0") +
127+
+feature.get("output_modified_HIGHEST_QUAL_1") +
128+
+feature.get("output_modified_HIGHEST_QUAL_2") +
129+
+feature.get("output_modified_HIGHEST_QUAL_3") +
130+
+feature.get("output_modified_HIGHEST_QUAL_4") +
131+
+feature.get("output_modified_HIGHEST_QUAL_5") +
132+
+feature.get("output_modified_HIGHEST_QUAL_6") +
133+
+feature.get("output_modified_HIGHEST_QUAL_-8")
134+
135+
const geom = feature.getGeometry()
136+
if (geom === undefined) { continue }
137+
138+
const featureTileRange = tileGrid.getTileRangeForExtentAndZ(
139+
geom.getExtent(),
140+
zoom
141+
)
142+
for (
143+
let x = Math.max(featureTileRange.minX, outputTileRange.minX);
144+
x <= Math.min(featureTileRange.maxX, outputTileRange.maxX);
145+
++x
146+
) {
147+
for (
148+
let y = Math.max(featureTileRange.minY, outputTileRange.minY);
149+
y <= Math.min(featureTileRange.maxY, outputTileRange.maxY);
150+
++y
151+
) {
152+
const center = tileGrid.getTileCoordCenter([zoom, x, y])
153+
if (geom.intersectsCoordinate(center)) {
154+
result.set(x, y, feature.get(censusOptions[index].code) / n * 100)
155+
}
156+
}
157+
}
158+
}
159+
160+
161+
editorNode.update()
162+
163+
}
164+
165+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { NumericDatasetToNumberComponent } from "./numeric_dataset_to_numeric_co
1919
import { SaveModelOutputComponent, SaveModel } from "./save_model_component"
2020
import { PrecompiledModelComponent, getDatasets } from "./dataset_component"
2121
import { UkcehLandCoverComponent } from "./ukceh_land_cover_component"
22+
import { CensusComponent } from "./census_component"
2223

2324
export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: SaveModel, getDatasets: getDatasets): BaseComponent[] {
2425
return [
@@ -29,6 +30,7 @@ export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: S
2930
new NumericConstantComponent(),
3031
new DigitalModelComponent(),
3132
new PrecompiledModelComponent(getDatasets),
33+
new CensusComponent(),
3234

3335
// Outputs
3436
new MapLayerComponent(saveMapLayer),

0 commit comments

Comments
 (0)