Skip to content

Commit

Permalink
Merge pull request #376 from wearepal/imd-component
Browse files Browse the repository at this point in the history
IMD 2019 map view layer and model view component
  • Loading branch information
paulthatjazz authored Jun 4, 2024
2 parents 695d0ae + 442d751 commit a6219c0
Show file tree
Hide file tree
Showing 9 changed files with 604 additions and 52 deletions.
2 changes: 1 addition & 1 deletion app/javascript/projects/analysis_panel_tools/subsection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface ChartData {
inputHistogramBins: number
}

function findColor(value: number, colorArray: any[]): Color {
export function findColor(value: number, colorArray: any[]): Color {
var index = Math.floor(value * (colorArray.length / 2))
index = Math.min(index, colorArray.length / 2 - 1)
var alpha = colorArray[index * 2]
Expand Down
21 changes: 21 additions & 0 deletions app/javascript/projects/layer_palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { KewOption, Layer } from './state'
import { iconForLayerType } from "./util"
import { CompiledDatasetRecord } from './saved_dataset'
import { designations } from './modelling/designations'
import { IMDProperties } from './reify_layer/imd'

interface AddLayerButtonProps {
prototype: Layer
Expand Down Expand Up @@ -304,6 +305,26 @@ export const LayerPalette = ({ addLayer, hide, dbModels, getTeamDatasets, teamNa
)
}
</Section>
<Section title="Indices of Multiple Deprivation">
{
Array<{ year: number }>(
{ year: 2019 }
).map(({ year }) =>
<AddLayerButton
addLayer={addLayer}
prototype={{
type: "IMDLayer",
name: `Indices of Multiple Deprivation ${year}`,
visible: true,
opacity: 1,
fill: "jet",
property: IMDProperties[0],
year
}}
/>
)
}
</Section>
<Section title="UKCEH Land Cover Maps">
<AddLayerButton
addLayer={addLayer}
Expand Down
5 changes: 1 addition & 4 deletions app/javascript/projects/modelling/bounding_box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,8 @@ const maskMap = new Map<string, BooleanTileGrid>()
export async function maskFromExtentAndShape(extent: Extent, zoom: number, shapeLayer: string, shapeId: string, maskMode: boolean = false): Promise<BooleanTileGrid> {
const id = `${shapeLayer}${shapeId}`
if(maskMap.has(id)) return maskMap.get(id) as BooleanTileGrid

else{

const cachedMask = await loadMask(id)
console.log(cachedMask)
if(cachedMask !== null) {
maskMap.set(id, cachedMask)
return cachedMask
Expand Down Expand Up @@ -144,7 +141,7 @@ export async function maskFromExtentAndShape(extent: Extent, zoom: number, shape
++y
) {

// DEBUG, shows progress percentage in console
// TODO: Add progress bar
if (i % seg === 0) {
console.log(Math.floor((i / len) * 100) + "%")
}
Expand Down
279 changes: 279 additions & 0 deletions app/javascript/projects/modelling/components/imd_component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
import { Extent } from "ol/extent"
import { BaseComponent } from "./base_component"
import { Node, Output, Socket } from "rete"
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
import { numericDataSocket } from "../socket_types"
import { bboxFromExtent, maskFromExtentAndShape } from "../bounding_box"
import { GeoJSON } from "ol/format";
import { Feature } from "ol"
import { Geometry } from "ol/geom"
import { NumericTileGrid } from "../tile_grid"
import { createXYZ } from "ol/tilegrid"

interface OutputFormat {
name: string
typeName: string
socket: Socket
}

const outputFormats: OutputFormat[] = [
{
name: "IMD Rank",
typeName: "IMD_Rank",
socket: numericDataSocket
},
{
name: "IMD Decile",
typeName: "IMD_Decile",
socket: numericDataSocket
},
{
name: "Income Rank",
typeName: "IncRank",
socket: numericDataSocket
},
{
name: "Income Decile",
typeName: "IncDec",
socket: numericDataSocket
},
{
name: "Income Score",
typeName: "IncScore",
socket: numericDataSocket
},
{
name: "Employment Rank",
typeName: "EmpRank",
socket: numericDataSocket
},
{
name: "Employment Decile",
typeName: "EmpDec",
socket: numericDataSocket
},
{
name: "Employment Score",
typeName: "EmpScore",
socket: numericDataSocket
},
{
name: "Education, Skills and Training Rank",
typeName: "EduRank",
socket: numericDataSocket
},
{
name: "Education, Skills and Training Decile",
typeName: "EduDec",
socket: numericDataSocket
},
{
name: "Education, Skills and Training Score",
typeName: "EduScore",
socket: numericDataSocket
},
{
name: "Heath Deprevation and Disability Rank",
typeName: "HDDRank",
socket: numericDataSocket
},
{
name: "Heath Deprevation and Disability Decile",
typeName: "HDDDec",
socket: numericDataSocket
},
{
name: "Heath Deprevation and Disability Score",
typeName: "HDDScore",
socket: numericDataSocket
},
{
name: "Crime Rank",
typeName: "CriRank",
socket: numericDataSocket
},
{
name: "Crime Decile",
typeName: "CriDec",
socket: numericDataSocket
},
{
name: "Crime Score",
typeName: "CriScore",
socket: numericDataSocket
},
{
name: "Barriers to Housing and Services Rank",
typeName: "BHSRank",
socket: numericDataSocket
},
{
name: "Barriers to Housing and Services Decile",
typeName: "BHSDec",
socket: numericDataSocket
},
{
name: "Barriers to Housing and Services Score",
typeName: "BHSScore",
socket: numericDataSocket
},
{
name: "Living Environment Rank",
typeName: "EnvRank",
socket: numericDataSocket
},
{
name: "Living Environment Decile",
typeName: "EnvDec",
socket: numericDataSocket
},
{
name: "Living Environment Score",
typeName: "EnvScore",
socket: numericDataSocket
},
{
name: "Population (Total)",
typeName: "TotPop",
socket: numericDataSocket
},
{
name: "Population (16-59)",
typeName: "Pop16_59",
socket: numericDataSocket
},
{
name: "Population (60+)",
typeName: "Pop60+",
socket: numericDataSocket
},
{
name: "Population (Working)",
typeName: "WorkPop",
socket: numericDataSocket
}
]

async function fetchIMDData(projectExtent: Extent) : Promise<Feature<Geometry>[]> {

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

if (!response.ok) throw new Error()
const features = new GeoJSON().readFeatures(await response.json())
return features

}

async function buildIMDTileGrid(label: string, projectExtent: Extent, zoom: number, maskMode: boolean, maskLayer: string, maskCQL: string, cache: Map<string, NumericTileGrid>) : Promise<NumericTileGrid> {

if (cache.has(label)){
return cache.get(label)!
}

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

const mask = await maskFromExtentAndShape(projectExtent, zoom, maskLayer, maskCQL, maskMode)

const features = await fetchIMDData(projectExtent)

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

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

const category = feature.get(label)

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)) {
result.set(x, y, mask.get(x, y) ? category as number : NaN)
}
}
}
}

cache.set(label, result)

return result
}

export class IMDComponent extends BaseComponent {
projectExtent: Extent
projectZoom: number
maskMode: boolean
maskLayer: string
maskCQL: string
cachedData: Feature<Geometry>[]
cachedGrids: Map<string, NumericTileGrid>

constructor(projectExtent: Extent, projectZoom: number, maskMode: boolean, maskLayer: string, maskCQL: string) {
super("Indices of Multiple Deprivation")
this.category = "Inputs"
this.projectExtent = projectExtent
this.projectZoom = projectZoom
this.maskMode = maskMode
this.maskLayer = maskLayer
this.maskCQL = maskCQL
this.cachedGrids = new Map()
}

async builder(node: Node) {
node.meta.toolTip = "Indices of Multiple Deprivation 2019 data for England. Decilces are most deprived (1) to least deprived (10)"
node.meta.toolTipLink = "https://data.cdrc.ac.uk/dataset/index-multiple-deprivation-imd"
for (const format of outputFormats) {
const output = new Output(format.typeName, format.name, format.socket)
node.addOutput(output)
}
}

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

if (this.cachedData === undefined){
this.cachedData = await fetchIMDData(this.projectExtent)
}

const p = outputFormats.filter(
f => node.outputs[f.typeName].connections.length > 0
).map(async i =>
{
const grid = await buildIMDTileGrid(i.typeName, this.projectExtent, this.projectZoom, this.maskMode, this.maskLayer, this.maskCQL, this.cachedGrids)
grid.name = i.typeName
outputs[i.typeName] = grid
}
)
await Promise.all(p)
}

}
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 @@ -33,12 +33,14 @@ import { MlTreeHedgeComponent } from "./ml_tree_hedge_component"
import { ATIComponent } from "./ati_component"
import { DesignationsComponent } from "./designations_component"
import { ORValComponent } from "./orval_component"
import { IMDComponent } from "./imd_component"

export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: SaveModel, getDatasets: getDatasets, extent: Extent, zoom: number, mask: boolean, maskLayer: string, maskCQL: string): BaseComponent[] {
return [
// Inputs
new UkcehLandCoverComponent(extent, zoom, mask, maskLayer, maskCQL),
new LehLandCoverComponent(extent, zoom, mask, maskLayer, maskCQL),
new IMDComponent(extent, zoom, mask, maskLayer, maskCQL),
new MlTreeHedgeComponent(extent, zoom, mask, maskLayer, maskCQL),
new BiodiversityComponent(extent, zoom, mask, maskLayer, maskCQL),
new NevoLayerComponent(extent, zoom, mask, maskLayer, maskCQL),
Expand Down
Loading

0 comments on commit a6219c0

Please sign in to comment.