diff --git a/geoprob_pipe/changelog.py b/geoprob_pipe/changelog.py index e4ae840e..e8b8c14b 100644 --- a/geoprob_pipe/changelog.py +++ b/geoprob_pipe/changelog.py @@ -1,5 +1,7 @@ CHANGELOG = { + "2.2.11": + "Bug fix. When there were NaN-calculation results (PoF), it resulted in a crash in the KansElement.", "2.2.10": "Bug fix en added validation. Traject probability of failure kon net boven 1.0 uitkomen door de toegepaste " "rekentechniek. Daarnaast om het gebruik van de applicatie te verbeteren extra validatie toegepast, zoals dat " diff --git a/geoprob_pipe/results/assemblage/objects.py b/geoprob_pipe/results/assemblage/objects.py index b26b5c12..467488eb 100644 --- a/geoprob_pipe/results/assemblage/objects.py +++ b/geoprob_pipe/results/assemblage/objects.py @@ -1,11 +1,10 @@ from __future__ import annotations from dataclasses import dataclass -import math from typing import Optional, List, Tuple, cast from geoprob_pipe.results.assemblage.functions import bepaal_N_vak import scipy.stats as stats # importeer de scipy.stats module -from geoprob_pipe.results.assemblage.functions import ( - combine_series, window_collect, scaled_collect) +from geoprob_pipe.results.assemblage.functions import combine_series, window_collect, scaled_collect +import numpy as np @dataclass @@ -23,27 +22,24 @@ class te initialiseren. def __post_init__(self): - if self.pf is not None: - if not (0.0 <= self.pf <= 1.0): - raise ValueError(f"pof moet tussen 0.0 en 1.0 liggen. Huidige waarde is {self.pf}") + pf_is_nan = self.pf is None or np.isnan(self.pf) + beta_is_nan = self.beta is None or np.isnan(self.beta) - # if self.beta is not None: - # if not math.isfinite(self.beta): - # raise ValueError("beta moet een eindige waarde zijn.") - # if (-38.0 <= self.beta <= 38.0) is False: - # raise ValueError("beta moet tussen -38.0 en 38.0 liggen.") + if not pf_is_nan: + if not (0.0 <= self.pf <= 1.0): + raise ValueError( + f"pof moet tussen 0.0 en 1.0 liggen. Huidige waarde is {self.pf}. Beta waarde is {self.beta}.") - if self.pf is None and self.beta is not None: + if pf_is_nan and not beta_is_nan: self.pf = float(stats.norm.cdf(-1.0 * self.beta)) - if self.beta is None and self.pf is not None: + if beta_is_nan and not pf_is_nan: self.beta = -1.0 * float(stats.norm.ppf(self.pf)) @dataclass class UittredepuntElement: - """DataClass om alle relevante data van een uittredepunt te verzamelen. - """ + """ DataClass om alle relevante data van een uittredepunt te verzamelen. """ m_value: float a: float converged: bool @@ -54,29 +50,26 @@ class UittredepuntElement: def __post_init__(self): - if self.pf is not None: - if not (0.0 <= self.pf <= 1.0): - raise ValueError(f"pof moet tussen 0.0 en 1.0 liggen. Huidige waarde is {self.pf}") + pf_is_nan = self.pf is None or np.isnan(self.pf) + beta_is_nan = self.beta is None or np.isnan(self.beta) - # if self.beta is not None: - # if not math.isfinite(self.beta): - # raise ValueError("beta moet een eindige waarde zijn.") - # if (-38.0 <= self.beta <= 38.0) is False: - # raise ValueError("beta moet tussen -38.0 en 38.0 liggen.") + # Validate pof value + if not pf_is_nan: + if not (0.0 <= self.pf <= 1.0): + raise ValueError( + f"De pof moet tussen 0.0 en 1.0 liggen. Huidige waarde is {self.pf}. Beta waarde is {self.beta}.") - if self.pf is None and self.beta is not None: + if pf_is_nan and not beta_is_nan: self.pf = float(stats.norm.cdf(-1.0 * self.beta)) - if self.beta is None and self.pf is not None: + if beta_is_nan and not pf_is_nan: self.beta = -1.0 * float(stats.norm.ppf(self.pf)) @dataclass class VakElement: - """ DataClass om alle relevante data van een vak te verzamelen. - Vanuit deze class kunnen de verschillende methoden om tot een faalkans van - het vak worden bepaald. - """ + """ DataClass om alle relevante data van een vak te verzamelen. Vanuit deze class kunnen de verschillende methoden + om tot een faalkans van het vak worden bepaald. """ id: int m_van: float # Locatie van het begin van het element [meters] m_tot: float # Locatie van het einde van het element [meters] @@ -139,21 +132,14 @@ def conv_max_dsn(self) -> bool: # vak: moving window met variable lengtes def pf_window(self, window_size: float): pf_sum, pf_max, window_elements = window_collect( - window_size=window_size, point_list=self.dsn_list, - m_van=self.m_van, m_tot=self.m_tot, vak_id=self.id - ) - """Helper om de `window_collect` functie op te zetten voor alle - attributen. - """ + window_size=window_size, point_list=self.dsn_list, m_van=self.m_van, m_tot=self.m_tot, vak_id=self.id) + """ Helper om de `window_collect` functie op te zetten voor alle attributen. """ return KansElement(pf=pf_sum), KansElement(pf=pf_max), window_elements @property - def pf_scaled(self) -> Tuple[KansElement, KansElement, - List[WindowElement]]: + def pf_scaled(self) -> Tuple[KansElement, KansElement, List[WindowElement]]: pf_sum, pf_max, window_elements = scaled_collect( - self.delta_length, self.dsn_list, self.m_van, - self.m_tot, vak_id=self.id - ) + self.delta_length, self.dsn_list, self.m_van, self.m_tot, vak_id=self.id) return KansElement(pf=pf_sum), KansElement(pf=pf_max), window_elements @@ -205,24 +191,19 @@ def m_uittredepunt(self) -> float: if isinstance(self._m_uittredepunt, float): return self._m_uittredepunt else: - raise AttributeError( - "No attribute m_uittredepunt specified in object." - ) + raise AttributeError("No attribute m_uittredepunt specified in object.") @property def n_vak(self) -> float: if isinstance(self._n_vak, float): return self._n_vak else: - raise AttributeError( - "No attribute N_vak specified in object." - ) + raise AttributeError("No attribute N_vak specified in object.") @dataclass class TrajectElement: - """DataClass om de data van het traject te verzamelen. - """ + """ DataClass om de data van het traject te verzamelen. """ list_vakken: list[VakElement] list_dsn: list[UittredepuntElement] delta_length: float @@ -256,18 +237,12 @@ def pf_max_vak(self) -> Tuple[KansElement, KansElement]: # traject: moving window met variable lengtes def pf_window(self, window_size: float) -> Tuple[KansElement, KansElement, List[WindowElement]]: pf_sum, pf_max, window_elements = window_collect( - window_size=window_size, point_list=self.list_dsn, - m_van=self.m_van, m_tot=self.m_tot, vak_id=None - ) - """Helper om de `window_collect` functie op te zetten voor alle - attributen. - """ + window_size=window_size, point_list=self.list_dsn, m_van=self.m_van, m_tot=self.m_tot, vak_id=None) + """ Helper om de `window_collect` functie op te zetten voor alle attributen. """ return KansElement(pf=pf_sum), KansElement(pf=pf_max), window_elements @property - def pf_scaled(self) -> Tuple[KansElement, KansElement, - List[WindowElement]]: + def pf_scaled(self) -> Tuple[KansElement, KansElement, List[WindowElement]]: pf_sum, pf_max, window_elements = scaled_collect( - dL=self.delta_length, point_list=self.list_dsn, - m_van=self.m_van, m_tot=self.m_tot, vak_id=None) + dL=self.delta_length, point_list=self.list_dsn, m_van=self.m_van, m_tot=self.m_tot, vak_id=None) return KansElement(pf=pf_sum), KansElement(pf=pf_max), window_elements diff --git a/geoprob_pipe/visualizations/maps/betamap.py b/geoprob_pipe/visualizations/maps/betamap.py index f5bd9291..f77b9828 100644 --- a/geoprob_pipe/visualizations/maps/betamap.py +++ b/geoprob_pipe/visualizations/maps/betamap.py @@ -2,19 +2,34 @@ from typing import TYPE_CHECKING, Dict, List, Tuple import plotly.graph_objects as go import os -import geopandas as gpd +from pandas import DataFrame +from geopandas import GeoDataFrame, read_file from shapely.geometry import LineString, MultiLineString, GeometryCollection - if TYPE_CHECKING: from geoprob_pipe import GeoProbPipe -def _add_line(geoprob_pipe: GeoProbPipe, fig: go.Figure, - layer: str, color: str): +def _calculate_zoom(lat_range, lon_range): + max_range = max(lat_range, lon_range) + if max_range < 0.01: + return 15 + elif max_range < 0.05: + return 13 + elif max_range < 0.1: + return 12 + elif max_range < 0.5: + return 10 + elif max_range < 1.0: + return 9 + else: + return 8 + + +def _add_line(geoprob_pipe: GeoProbPipe, fig: go.Figure, layer: str, color: str): """ Helperfunctie om de lijnen uit de geopackage te vinden en toe te voegen aan de map. Layer is de naam van de laag in de geopackage waar de lijn is opgeslagen. Color is de kleur van deze lijn in de map. """ - gdf_traject = gpd.read_file( + gdf_traject = read_file( geoprob_pipe.input_data.app_settings.geopackage_filepath, layer=layer) gdf_traject = gdf_traject.to_crs("EPSG:4326") @@ -115,52 +130,39 @@ def __init__(self, geoprob_pipe: GeoProbPipe, export: bool = False): def _import_results(self): # results import - self.inp_point = self.geoprob_pipe.input_data.uittredepunten.gdf - self.res_sc = self.geoprob_pipe.results.df_beta_scenarios_final - mask = self.inp_point["uittredepunt_id"].isin(self.res_sc["uittredepunt_id"]) - self.inp_point = self.inp_point[mask] + self.gdf_uittredepunten: GeoDataFrame = self.geoprob_pipe.input_data.uittredepunten.gdf + self.df_beta_scenarios_final: DataFrame = self.geoprob_pipe.results.df_beta_scenarios_final + mask = self.gdf_uittredepunten["uittredepunt_id"].isin(self.df_beta_scenarios_final["uittredepunt_id"]) + self.gdf_uittredepunten = self.gdf_uittredepunten[mask] # Setup of beta category limits - self.cg: Dict[str, List] = self.geoprob_pipe.input_data.traject_normering.riskeer_categorie_grenzen - self.labels: List[str] = list(self.cg.keys()) + self.categorie_grenzen: Dict[str, List] = self.geoprob_pipe.input_data.traject_normering.riskeer_categorie_grenzen + self.labels_categorie_grenzen: List[str] = list(self.categorie_grenzen.keys()) def _setup_gdf(self): self.hoverdata = ["uittredepunt_id", "converged", "beta"] - self.df = self.res_sc.merge(self.inp_point, on="uittredepunt_id", how="inner") - idx = self.df.groupby(["uittredepunt_id"])["beta"].idxmin() - self.df = self.df.loc[idx] + # Prep dataframe + df = self.df_beta_scenarios_final.merge(self.gdf_uittredepunten, on="uittredepunt_id", how="inner") + gdf = GeoDataFrame(df, geometry="geometry", crs=self.gdf_uittredepunten.crs) - self.gdf = gpd.GeoDataFrame( - self.df, - geometry=gpd.points_from_xy(self.inp_point.geometry.x, self.inp_point.geometry.y), - crs="EPSG:28992") - # Transformeer naar WGS84 (latitude / longitude) - self.gdf_latlon = self.gdf.to_crs("EPSG:4326") + # Filter uit NAN values TODO: Present nan values in a way? + gdf = gdf[~gdf["beta"].isna()] + + # Continue logic + idx = gdf.groupby(["uittredepunt_id"])["beta"].idxmin() # TODO: Unclear what this line adds/does. + gdf = gdf.loc[idx] + self.gdf_latlon = gdf.to_crs("EPSG:4326") def _determine_zoom(self): self.center_lat = self.gdf_latlon.geometry.y.mean() self.center_lon = self.gdf_latlon.geometry.x.mean() + self.min_lat = self.gdf_latlon.geometry.y.min() self.max_lat = self.gdf_latlon.geometry.y.max() self.min_lon = self.gdf_latlon.geometry.x.min() self.max_lon = self.gdf_latlon.geometry.x.max() - def _calculate_zoom(lat_range, lon_range): - max_range = max(lat_range, lon_range) - if max_range < 0.01: - return 15 - elif max_range < 0.05: - return 13 - elif max_range < 0.1: - return 12 - elif max_range < 0.5: - return 10 - elif max_range < 1.0: - return 9 - else: - return 8 - self.lat_range = self.max_lat - self.min_lat self.lon_range = self.max_lon - self.min_lon self.zoom = _calculate_zoom(self.lat_range, self.lon_range) @@ -180,30 +182,30 @@ def _create_figure(self): marker=dict( size=8, color=self.gdf_latlon['beta'], - colorscale=_generate_colorscale(cg=self.cg, labels=self.labels), - cmin=self.cg[self.labels[6]][0], - cmax=self.cg[self.labels[0]][1], + colorscale=_generate_colorscale(cg=self.categorie_grenzen, labels=self.labels_categorie_grenzen), + cmin=self.categorie_grenzen[self.labels_categorie_grenzen[6]][0], + cmax=self.categorie_grenzen[self.labels_categorie_grenzen[0]][1], colorbar=dict( title="Bèta, WBI cat.", tickvals=[ - self.cg[self.labels[6]][0], - self.cg[self.labels[6]][1], - self.cg[self.labels[5]][1], - self.cg[self.labels[4]][1], - self.cg[self.labels[3]][1], - self.cg[self.labels[2]][1], - self.cg[self.labels[1]][1], - self.cg[self.labels[0]][1] + self.categorie_grenzen[self.labels_categorie_grenzen[6]][0], + self.categorie_grenzen[self.labels_categorie_grenzen[6]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[5]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[4]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[3]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[2]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[1]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[0]][1] ], ticktext=[f"{v:.2f}" for v in [ - self.cg[self.labels[6]][0], - self.cg[self.labels[6]][1], - self.cg[self.labels[5]][1], - self.cg[self.labels[4]][1], - self.cg[self.labels[3]][1], - self.cg[self.labels[2]][1], - self.cg[self.labels[1]][1], - self.cg[self.labels[0]][1]]], + self.categorie_grenzen[self.labels_categorie_grenzen[6]][0], + self.categorie_grenzen[self.labels_categorie_grenzen[6]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[5]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[4]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[3]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[2]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[1]][1], + self.categorie_grenzen[self.labels_categorie_grenzen[0]][1]]], ) ), hoverinfo='text', diff --git a/pyproject.toml b/pyproject.toml index a6af3c9b..74a611ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "geoprob_pipe" -version = "2.2.10" +version = "2.2.11" description = "Execute probabilistic piping calculations using the Deltares probabilic library." keywords = [ "levee",