Skip to content

Commit

Permalink
Merge pull request #383 from wearepal/hedgerow-component-and-team-per…
Browse files Browse the repository at this point in the history
…misions

Hedgerow component and team permissions
  • Loading branch information
paulthatjazz authored Jun 11, 2024
2 parents 353ff9b + 14c879e commit 01f7dbb
Show file tree
Hide file tree
Showing 19 changed files with 336 additions and 78 deletions.
8 changes: 7 additions & 1 deletion app/javascript/controllers/projects_controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class extends Controller {
projectSource: Object,
projectTeamId: Number,
projectTeamName: String,
projectDefraHedgerowPermission: Boolean,
backButtonPath: String,
dbModels: Object,
}
Expand All @@ -19,10 +20,10 @@ export default class extends Controller {
declare readonly projectSourceValue: Project
declare readonly projectTeamIdValue: number
declare readonly projectTeamNameValue: string
declare readonly projectDefraHedgerowPermissionValue: boolean
declare readonly backButtonPathValue: string
declare readonly dbModelsValue: DBModels


connect() {
ReactDOM.render(
<ProjectEditor
Expand All @@ -32,6 +33,11 @@ export default class extends Controller {
dbModels={this.dbModelsValue}
teamId={this.projectTeamIdValue}
teamName={this.projectTeamNameValue}
permissions={
{
DefraHedgerows: this.projectDefraHedgerowPermissionValue
}
}
/>,
this.element
)
Expand Down
6 changes: 4 additions & 2 deletions app/javascript/projects/model_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { NodeComponent } from './node_component'
import { SaveModel } from './modelling/components/save_model_component'
import { getDatasets } from './modelling/components/dataset_component'
import { Extent } from 'ol/extent'
import { ProjectPermissions } from './project_editor'

// Rete doesn't export `Transform`, so we have to re-define it ourselves
export interface Transform {
Expand Down Expand Up @@ -40,8 +41,9 @@ export interface ModelViewProps {
mask: boolean
maskLayer: string
maskCQL: string
permissions: ProjectPermissions
}
export function ModelView({ visible, initialTransform, setTransform, initialModel, setModel, createOutputLayer, deleteOutputLayer, saveMapLayer, setProcessing, autoProcessing, process, setProcess, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL }: ModelViewProps) {
export function ModelView({ visible, initialTransform, setTransform, initialModel, setModel, createOutputLayer, deleteOutputLayer, saveMapLayer, setProcessing, autoProcessing, process, setProcess, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL, permissions }: ModelViewProps) {
const ref = React.useRef<HTMLDivElement>(null)
const [editor, setEditor] = React.useState<NodeEditor>()
const [engine, setEngine] = React.useState<Engine>()
Expand All @@ -68,7 +70,7 @@ export function ModelView({ visible, initialTransform, setTransform, initialMode
})

const engine = new Engine("[email protected]")
createDefaultComponents(saveMapLayer, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL).forEach(component => {
createDefaultComponents(saveMapLayer, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL, permissions).forEach(component => {
editor.register(component)
engine.register(component)
})
Expand Down
92 changes: 92 additions & 0 deletions app/javascript/projects/modelling/components/hedgerow_component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
import { BaseComponent } from "./base_component"
import { Input, Node, Output } from 'rete'
import { ProjectProperties } from "./index"
import { booleanDataSocket } from "../socket_types"
import { maskFromExtentAndShape } from "../bounding_box"
import { retrieveModelData } from "../model_retrieval"
import { createXYZ } from "ol/tilegrid"
import { TypedArray } from "d3"
import { BooleanTileGrid } from "../tile_grid"

export class HedgerowComponent extends BaseComponent {
ProjectProperties: ProjectProperties
cachedHedgerows: BooleanTileGrid

constructor(projectProps: ProjectProperties) {
super("Hedgerows")
this.category = "Inputs"
this.ProjectProperties = projectProps
}

async builder(node: Node) {
node.addOutput(new Output('out', 'Hedgerows', booleanDataSocket))
}

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 mask = await maskFromExtentAndShape(
this.ProjectProperties.extent,
this.ProjectProperties.zoom,
this.ProjectProperties.maskLayer,
this.ProjectProperties.maskCQL,
this.ProjectProperties.mask
)

if (node.outputs['out'].connections.length > 0)
{

if(this.cachedHedgerows === undefined) {

const tileGrid = createXYZ()
const outputTileRange = tileGrid.getTileRangeForExtentAndZ(
this.ProjectProperties.extent,
this.ProjectProperties.zoom
)
const geotiff = await retrieveModelData(
this.ProjectProperties.extent,
'nateng:defra_lcm_hedges',
outputTileRange
)

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

const image = await geotiff.getImage()

const result = new BooleanTileGrid(
this.ProjectProperties.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] === 0 ? false : (mask.get(x, y) === true ? true : false))

}

this.cachedHedgerows = result
outputs['out'] = result

}else{

outputs['out'] = this.cachedHedgerows

}
}

}

}
156 changes: 83 additions & 73 deletions app/javascript/projects/modelling/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { ATIComponent } from "./ati_component"
import { DesignationsComponent } from "./designations_component"
import { ORValComponent } from "./orval_component"
import { IMDComponent } from "./imd_component"
import { HedgerowComponent } from "./hedgerow_component"
import { ProjectPermissions } from "../../project_editor"

export interface ProjectProperties {
extent: Extent
Expand All @@ -43,77 +45,85 @@ export interface ProjectProperties {
maskCQL: string
}

export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: SaveModel, getDatasets: getDatasets, extent: Extent, zoom: number, mask: boolean, maskLayer: string, maskCQL: string): BaseComponent[] {

const projectProps = { extent, zoom, mask, maskLayer, maskCQL }

return [
// TODO: Replace extent, mask, zoom, maskLayer, maskCQL with projectProps in all components

// 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),
new ORValComponent(extent, zoom, mask, maskLayer, maskCQL),
new OSMLandUseComponent(extent, zoom, mask, maskLayer, maskCQL),
new NumericConstantComponent(),
new DigitalModelComponent(extent, zoom, mask, maskLayer, maskCQL),
new PrecompiledModelComponent(getDatasets, extent, zoom, mask, maskLayer, maskCQL),
new CensusComponent(extent, zoom, mask, maskLayer, maskCQL),
new OSGreenSpacesComponent(extent, zoom, mask, maskLayer, maskCQL),
new CROMEComponent(extent, zoom, mask, maskLayer, maskCQL),
new ATIComponent(extent, zoom, mask, maskLayer, maskCQL),
new DesignationsComponent(extent, zoom, mask, maskLayer, maskCQL),

// Outputs
new MapLayerComponent(saveMapLayer),
new SaveModelOutputComponent(saveModel),

// Conversions
new NumberToNumericDatasetComponent(),
new NumericDatasetToNumberComponent(),
new CategoricalComponent(),

// Calculations
new AreaComponent(),
new DistanceMapComponent(extent, zoom, mask, maskLayer, maskCQL),
new ScaleFactorComponent(),

// Charts
new BarChartComponent(),

// Set operations
new VariadicOpComponent('Union', '⋃', booleanDataSocket, booleanDataSocket, 'Set operations'),
new VariadicOpComponent('Intersection', '⋂', booleanDataSocket, booleanDataSocket, 'Set operations'),
new BinaryOpComponent('Set difference', '−', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),
new VariadicOpComponent('Symmetric difference', 'Δ', booleanDataSocket, booleanDataSocket, 'Set operations'),
new UnaryOpComponent('Complement', '′', 'postfix', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),

// Arithmetic
new MaskNumericDataComponent(),
new ExpressionComponent(),
new BinaryOpComponent('Min', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Max', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new VariadicOpComponent('Sum', '∑', numericDataSocket, numericDataSocket, 'Arithmetic', 'Sum all inputs'),
new VariadicOpComponent('Merge', '', numericDataSocket, numericDataSocket, 'Arithmetic', 'Merge all inputs into a single dataset, NaN logic is overridden'),
new VariadicOpComponent('Product', '∏', numericDataSocket, numericDataSocket, 'Arithmetic'),
new BinaryOpComponent('Add', '+', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Subtract', '−', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Multiply', '×', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Divide', '÷', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Power', '^', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new UnaryOpComponent('Negate', '−', 'prefix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
new UnaryOpComponent('Reciprocal', '⁻¹', 'postfix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Less', '<', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Greater', '>', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
new ReplaceNaNComponent(),

// DEBUG TOOLS
new CellAreaComponent(),
new RescaleComponent(),

]
export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: SaveModel, getDatasets: getDatasets, extent: Extent, zoom: number, mask: boolean, maskLayer: string, maskCQL: string, permissions: ProjectPermissions): BaseComponent[] {

const projectProps: ProjectProperties = { extent, zoom, mask, maskLayer, maskCQL }

const restrictedComponents: BaseComponent[] = []

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

// Freely available components here.
const components : BaseComponent[] = [
// TODO: Replace extent, mask, zoom, maskLayer, maskCQL with projectProps in all components

// 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),
new ORValComponent(extent, zoom, mask, maskLayer, maskCQL),
new OSMLandUseComponent(extent, zoom, mask, maskLayer, maskCQL),
new NumericConstantComponent(),
new DigitalModelComponent(extent, zoom, mask, maskLayer, maskCQL),
new PrecompiledModelComponent(getDatasets, extent, zoom, mask, maskLayer, maskCQL),
new CensusComponent(extent, zoom, mask, maskLayer, maskCQL),
new OSGreenSpacesComponent(extent, zoom, mask, maskLayer, maskCQL),
new CROMEComponent(extent, zoom, mask, maskLayer, maskCQL),
new ATIComponent(extent, zoom, mask, maskLayer, maskCQL),
new DesignationsComponent(extent, zoom, mask, maskLayer, maskCQL),

// Outputs
new MapLayerComponent(saveMapLayer),
new SaveModelOutputComponent(saveModel),

// Conversions
new NumberToNumericDatasetComponent(),
new NumericDatasetToNumberComponent(),
new CategoricalComponent(),

// Calculations
new AreaComponent(),
new DistanceMapComponent(extent, zoom, mask, maskLayer, maskCQL),
new ScaleFactorComponent(),

// Charts
new BarChartComponent(),

// Set operations
new VariadicOpComponent('Union', '⋃', booleanDataSocket, booleanDataSocket, 'Set operations'),
new VariadicOpComponent('Intersection', '⋂', booleanDataSocket, booleanDataSocket, 'Set operations'),
new BinaryOpComponent('Set difference', '−', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),
new VariadicOpComponent('Symmetric difference', 'Δ', booleanDataSocket, booleanDataSocket, 'Set operations'),
new UnaryOpComponent('Complement', '′', 'postfix', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),

// Arithmetic
new MaskNumericDataComponent(),
new ExpressionComponent(),
new BinaryOpComponent('Min', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Max', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new VariadicOpComponent('Sum', '∑', numericDataSocket, numericDataSocket, 'Arithmetic', 'Sum all inputs'),
new VariadicOpComponent('Merge', '', numericDataSocket, numericDataSocket, 'Arithmetic', 'Merge all inputs into a single dataset, NaN logic is overridden'),
new VariadicOpComponent('Product', '∏', numericDataSocket, numericDataSocket, 'Arithmetic'),
new BinaryOpComponent('Add', '+', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Subtract', '−', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Multiply', '×', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Divide', '÷', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Power', '^', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
new UnaryOpComponent('Negate', '−', 'prefix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
new UnaryOpComponent('Reciprocal', '⁻¹', 'postfix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Less', '<', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
new BinaryOpComponent('Greater', '>', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
new ReplaceNaNComponent(),

// DEBUG TOOLS
new CellAreaComponent(),
new RescaleComponent(),

]

return components.concat(restrictedComponents)
}
8 changes: 7 additions & 1 deletion app/javascript/projects/project_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,20 @@ export enum Tab {
ModelView,
}

export interface ProjectPermissions {
DefraHedgerows: boolean
}

interface ProjectEditorProps {
projectId: number
projectSource: Project
backButtonPath: string
dbModels: DBModels
teamId: number
teamName: string
permissions: ProjectPermissions
}
export function ProjectEditor({ projectId, projectSource, backButtonPath, dbModels, teamId, teamName }: ProjectEditorProps) {
export function ProjectEditor({ projectId, projectSource, backButtonPath, dbModels, teamId, teamName, permissions }: ProjectEditorProps) {
const [state, dispatch] = React.useReducer(reduce, {
project: { ...defaultProject, ...projectSource },
hasUnsavedChanges: false,
Expand Down Expand Up @@ -249,6 +254,7 @@ export function ProjectEditor({ projectId, projectSource, backButtonPath, dbMode
mask={projectMask}
maskLayer={projectMaskSource}
maskCQL={projectMaskCQL}
permissions={permissions}
/>
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions app/models/permission.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Permission < ApplicationRecord
has_many :team_permissions, dependent: :destroy
has_many :teams, through: :team_permissions

after_create :assign_to_all_teams

private

def assign_to_all_teams
Team.all.each do |team|
TeamPermission.create(team: team, permission: self, enabled: false)
end
end
end
9 changes: 9 additions & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,13 @@ def parse_extent
def duplicate
dup
end

def defra_hedgerow_permission
p = Permission.find_by(name: 'defra_hedgerow')
return false unless p

tp = team.team_permissions.find_by(permission: p)
tp ? tp.enabled : false
end

end
Loading

0 comments on commit 01f7dbb

Please sign in to comment.