Skip to content

Commit

Permalink
Add name as column and use as visualisation in QGIS. (#658)
Browse files Browse the repository at this point in the history
Fixes #567

- [x] Some warnings still need updating

<img width="676" alt="Screenshot 2023-10-09 at 13 46 40"
src="https://github.com/Deltares/Ribasim/assets/8655030/e49edacd-6ebe-45db-b662-974e7d2823a3">
  • Loading branch information
evetion authored Oct 10, 2023
1 parent 9429bfb commit 3639313
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 19 deletions.
30 changes: 16 additions & 14 deletions core/src/create.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function parse_static_and_time(
vals_out = []

node_ids = get_ids(db, nodetype)
node_names = get_names(db, nodetype)
n_nodes = length(node_ids)

# Initialize the vectors for the output
Expand Down Expand Up @@ -91,7 +92,7 @@ function parse_static_and_time(
t_end = seconds_since(config.endtime, config.starttime)
trivial_timespan = [nextfloat(-Inf), prevfloat(Inf)]

for (node_idx, node_id) in enumerate(node_ids)
for (node_idx, (node_id, node_name)) in enumerate(zip(node_ids, node_names))
if node_id in static_node_ids
# The interval of rows of the static table that have the current node_id
rows = searchsorted(static.node_id, node_id)
Expand Down Expand Up @@ -153,7 +154,7 @@ function parse_static_and_time(
)
if !is_valid
errors = true
@error "A $parameter_name time series for $nodetype node #$node_id has repeated times, this can not be interpolated."
@error "A $parameter_name time series for $nodetype node $(repr(node_name)) (#$node_id) has repeated times, this can not be interpolated."
end
else
# Activity of transient nodes is assumed to be true
Expand All @@ -167,7 +168,7 @@ function parse_static_and_time(
getfield(out, parameter_name)[node_idx] = val
end
else
@error "$nodetype node #$node_id data not in any table."
@error "$nodetype node $(repr(node_name)) (#$node_id) data not in any table."
errors = true
end
end
Expand All @@ -179,10 +180,11 @@ function static_and_time_node_ids(
static::StructVector,
time::StructVector,
node_type::String,
)::Tuple{Set{Int}, Set{Int}, Vector{Int}, Bool}
)::Tuple{Set{Int}, Set{Int}, Vector{Int}, Vector{String}, Bool}
static_node_ids = Set(static.node_id)
time_node_ids = Set(time.node_id)
node_ids = get_ids(db, node_type)
node_names = get_names(db, node_type)
doubles = intersect(static_node_ids, time_node_ids)
errors = false
if !isempty(doubles)
Expand All @@ -193,7 +195,7 @@ function static_and_time_node_ids(
errors = true
@error "$node_type node IDs don't match."
end
return static_node_ids, time_node_ids, node_ids, !errors
return static_node_ids, time_node_ids, node_ids, node_names, !errors
end

function Connectivity(db::DB, config::Config, chunk_size::Int)::Connectivity
Expand Down Expand Up @@ -249,7 +251,7 @@ function TabulatedRatingCurve(db::DB, config::Config)::TabulatedRatingCurve
static = load_structvector(db, config, TabulatedRatingCurveStaticV1)
time = load_structvector(db, config, TabulatedRatingCurveTimeV1)

static_node_ids, time_node_ids, node_ids, valid =
static_node_ids, time_node_ids, node_ids, node_names, valid =
static_and_time_node_ids(db, static, time, "TabulatedRatingCurve")

if !valid
Expand All @@ -263,7 +265,7 @@ function TabulatedRatingCurve(db::DB, config::Config)::TabulatedRatingCurve
active = BitVector()
errors = false

for node_id in node_ids
for (node_id, node_name) in zip(node_ids, node_names)
if node_id in static_node_ids
# Loop over all static rating curves (groups) with this node_id.
# If it has a control_state add it to control_mapping.
Expand Down Expand Up @@ -294,11 +296,11 @@ function TabulatedRatingCurve(db::DB, config::Config)::TabulatedRatingCurve
push!(interpolations, interpolation)
push!(active, true)
else
@error "TabulatedRatingCurve node #$node_id data not in any table."
@error "TabulatedRatingCurve node $(repr(node_name)) (#$node_id) data not in any table."
errors = true
end
if !is_valid
@error "A Q(h) relationship for TabulatedRatingCurve #$node_id from the $source table has repeated levels, this can not be interpolated."
@error "A Q(h) relationship for TabulatedRatingCurve $(repr(node_name)) (#$node_id) from the $source table has repeated levels, this can not be interpolated."
errors = true
end
end
Expand Down Expand Up @@ -349,7 +351,7 @@ function LevelBoundary(db::DB, config::Config)::LevelBoundary
static = load_structvector(db, config, LevelBoundaryStaticV1)
time = load_structvector(db, config, LevelBoundaryTimeV1)

static_node_ids, time_node_ids, node_ids, valid =
static_node_ids, time_node_ids, node_ids, node_names, valid =
static_and_time_node_ids(db, static, time, "LevelBoundary")

if !valid
Expand Down Expand Up @@ -377,7 +379,7 @@ function FlowBoundary(db::DB, config::Config)::FlowBoundary
static = load_structvector(db, config, FlowBoundaryStaticV1)
time = load_structvector(db, config, FlowBoundaryTimeV1)

static_node_ids, time_node_ids, node_ids, valid =
static_node_ids, time_node_ids, node_ids, node_names, valid =
static_and_time_node_ids(db, static, time, "FlowBoundary")

if !valid
Expand All @@ -397,7 +399,7 @@ function FlowBoundary(db::DB, config::Config)::FlowBoundary
for itp in parsed_parameters.flow_rate
if any(itp.u .< 0.0)
@error(
"Currently negative flow rates are not supported, found some for dynamic flow boundary #$node_id."
"Currently negative flow rates are not supported, found some in dynamic flow boundary."
)
valid = false
end
Expand Down Expand Up @@ -564,7 +566,7 @@ function PidControl(db::DB, config::Config, chunk_size::Int)::PidControl
static = load_structvector(db, config, PidControlStaticV1)
time = load_structvector(db, config, PidControlTimeV1)

static_node_ids, time_node_ids, node_ids, valid =
static_node_ids, time_node_ids, node_ids, node_names, valid =
static_and_time_node_ids(db, static, time, "PidControl")

if !valid
Expand Down Expand Up @@ -626,7 +628,7 @@ function User(db::DB, config::Config)::User
static = load_structvector(db, config, UserStaticV1)
time = load_structvector(db, config, UserTimeV1)

static_node_ids, time_node_ids, node_ids, valid =
static_node_ids, time_node_ids, node_ids, node_names, valid =
static_and_time_node_ids(db, static, time, "User")

if !valid
Expand Down
9 changes: 9 additions & 0 deletions core/src/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ function get_ids(db::DB, nodetype)::Vector{Int}
return only(execute(columntable, db, sql))
end

function get_names(db::DB)::Vector{String}
return only(execute(columntable, db, "SELECT name FROM Node ORDER BY fid"))
end

function get_names(db::DB, nodetype)::Vector{String}
sql = "SELECT name FROM Node where type = $(esc_id(nodetype)) ORDER BY fid"
return only(execute(columntable, db, sql))
end

function exists(db::DB, tablename::String)
query = execute(
db,
Expand Down
2 changes: 2 additions & 0 deletions core/src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,13 @@ n_neighbor_bounds_control(nodetype) =
# TODO NodeV1 and EdgeV1 are not yet used
@version NodeV1 begin
fid::Int
name::String = isnothing(s) ? "" : String(s)
type::String = in(Symbol(type), nodetypes) ? type : error("Unknown node type $type")
end

@version EdgeV1 begin
fid::Int
name::String = isnothing(s) ? "" : String(s)
from_node_id::Int
to_node_id::Int
edge_type::String
Expand Down
4 changes: 2 additions & 2 deletions core/test/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ end
@test length(logger.logs) == 2
@test logger.logs[1].level == Error
@test logger.logs[1].message ==
"A Q(h) relationship for TabulatedRatingCurve #1 from the static table has repeated levels, this can not be interpolated."
"A Q(h) relationship for TabulatedRatingCurve \"\" (#1) from the static table has repeated levels, this can not be interpolated."
@test logger.logs[2].level == Error
@test logger.logs[2].message ==
"A Q(h) relationship for TabulatedRatingCurve #2 from the time table has repeated levels, this can not be interpolated."
"A Q(h) relationship for TabulatedRatingCurve \"\" (#2) from the time table has repeated levels, this can not be interpolated."
end

@testset "Neighbor count validation" begin
Expand Down
5 changes: 5 additions & 0 deletions docs/schema/Edge.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"format": "default",
"type": "integer"
},
"name": {
"format": "default",
"type": "string"
},
"from_node_id": {
"format": "default",
"type": "integer"
Expand All @@ -30,6 +34,7 @@
},
"required": [
"fid",
"name",
"from_node_id",
"to_node_id",
"edge_type"
Expand Down
5 changes: 5 additions & 0 deletions docs/schema/Node.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"format": "default",
"type": "integer"
},
"name": {
"format": "default",
"type": "string"
},
"type": {
"format": "default",
"type": "string"
Expand All @@ -22,6 +26,7 @@
},
"required": [
"fid",
"name",
"type"
]
}
4 changes: 4 additions & 0 deletions python/ribasim/ribasim/geometry/edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@


class StaticSchema(pa.SchemaModel):
name: Series[str] = pa.Field(default="")
from_node_id: Series[int] = pa.Field(coerce=True)
to_node_id: Series[int] = pa.Field(coerce=True)
geometry: GeoSeries[Any]

class Config:
add_missing_columns = True


class Edge(TableModel):
"""
Expand Down
4 changes: 4 additions & 0 deletions python/ribasim/ribasim/geometry/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@


class StaticSchema(pa.SchemaModel):
name: Series[str] = pa.Field(default="")
type: Series[str]
geometry: GeoSeries[Any]

class Config:
add_missing_columns = True


class Node(TableModel):
"""
Expand Down
2 changes: 2 additions & 0 deletions python/ribasim/ribasim/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class DiscreteControlLogic(BaseModel):

class Edge(BaseModel):
fid: int
name: str
from_node_id: int
to_node_id: int
edge_type: str
Expand Down Expand Up @@ -123,6 +124,7 @@ class ManningResistanceStatic(BaseModel):

class Node(BaseModel):
fid: int
name: str
type: str
remarks: str = Field("", description="a hack for pandera")

Expand Down
7 changes: 7 additions & 0 deletions python/ribasim/ribasim/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import random
import string
from typing import Any, Sequence, Tuple

import numpy as np
Expand Down Expand Up @@ -76,3 +78,8 @@ def connectivity_from_geometry(
from_id = node_index[edge_node_id[:, 0]].to_numpy()
to_id = node_index[edge_node_id[:, 1]].to_numpy()
return from_id, to_id


def random_string(length=3):
letters = string.ascii_lowercase
return "".join(random.choice(letters) for i in range(length))
6 changes: 5 additions & 1 deletion python/ribasim_testmodels/ribasim_testmodels/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ def basic_model() -> ribasim.Model:
# Make sure the feature id starts at 1: explicitly give an index.
node = ribasim.Node(
static=gpd.GeoDataFrame(
data={"type": node_type},
data={
"type": node_type,
"name": [ribasim.utils.random_string() for _ in range(len(node_id))],
},
index=pd.Index(node_id, name="fid"),
geometry=node_xy,
crs="EPSG:28992",
Expand All @@ -177,6 +180,7 @@ def basic_model() -> ribasim.Model:
edge = ribasim.Edge(
static=gpd.GeoDataFrame(
data={
"name": [ribasim.utils.random_string() for _ in range(len(from_id))],
"from_node_id": from_id,
"to_node_id": to_id,
"edge_type": len(from_id) * ["flow"],
Expand Down
19 changes: 17 additions & 2 deletions qgis/core/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from ribasim_qgis.core import geopackage

from qgis.core import (
Qgis,
QgsCategorizedSymbolRenderer,
QgsEditorWidgetSetup,
QgsField,
Expand Down Expand Up @@ -128,7 +129,10 @@ def set_editor_widget(self) -> None:
class Node(Input):
input_type = "Node"
geometry_type = "Point"
attributes = (QgsField("type", QVariant.String),)
attributes = (
QgsField("name", QVariant.String),
QgsField("type", QVariant.String),
)

@classmethod
def is_spatial(cls):
Expand Down Expand Up @@ -206,7 +210,8 @@ def renderer(self) -> QgsCategorizedSymbolRenderer:
@property
def labels(self) -> Any:
pal_layer = QgsPalLayerSettings()
pal_layer.fieldName = "fid"
pal_layer.fieldName = """concat("name", ' (#', "fid", ')')"""
pal_layer.isExpression = True
pal_layer.enabled = True
pal_layer.dist = 2.0
labels = QgsVectorLayerSimpleLabeling(pal_layer)
Expand All @@ -217,6 +222,7 @@ class Edge(Input):
input_type = "Edge"
geometry_type = "Linestring"
attributes = [
QgsField("name", QVariant.String),
QgsField("from_node_id", QVariant.Int),
QgsField("to_node_id", QVariant.Int),
QgsField("edge_type", QVariant.String),
Expand Down Expand Up @@ -281,6 +287,15 @@ def renderer(self) -> QgsCategorizedSymbolRenderer:
)
return renderer

@property
def labels(self) -> Any:
pal_layer = QgsPalLayerSettings()
pal_layer.fieldName = "name"
pal_layer.enabled = True
pal_layer.placement = Qgis.LabelPlacement.Line
labels = QgsVectorLayerSimpleLabeling(pal_layer)
return labels


class BasinProfile(Input):
input_type = "Basin / profile"
Expand Down

0 comments on commit 3639313

Please sign in to comment.