diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b21b5e9..c4172794 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -186,7 +186,7 @@ unittest: - pip3 install -r ./full-requirements.txt - pip3 install -U -r ./full-requirements.txt - cd src - - python3 -m unittest discover -t . + - python3.12 -m unittest discover -t . test-pep8: stage: test diff --git a/README.md b/README.md index e7a36070..787cfee9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Pamhyr is a free and open source graphical user interface for 1D hydro-sedimentary modelling of rivers. -![logo](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/raw/master/src/View/ui/ressources/Pamhyr2_logo.png) +![logo](https://gitlab.com/pamhyr/pamhyr2/-/raw/master/src/View/ui/ressources/Pamhyr2_logo.png) ## Features @@ -21,9 +21,9 @@ modelling of rivers. + Create and edit study alternative scenario tree Let see the -[documentation](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home) -([:fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-fr), -[:gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-en)) +[documentation](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home) +([:fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-fr), +[:gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-en)) for more details. ## Install @@ -31,11 +31,11 @@ for more details. ### GNU/Linux See documentation: -- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20GNULinux) -- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20GNULinux) +- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20GNULinux) +- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20GNULinux) ### Windows See documentation: -- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20Windows) -- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20Windows) +- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20Windows) +- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20Windows) diff --git a/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr b/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr index 02a1a1eb..d1b6f381 100644 Binary files a/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr and b/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr differ diff --git a/requirements.txt b/requirements.txt index 1856f36f..ebe3523f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ platformdirs>=4.2.0 pyshp>=2.3.1 rasterio==1.3.11 #fortranformat==2.0.3 + diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py index 7a14bad5..eacd04b4 100644 --- a/src/Meshing/Internal.py +++ b/src/Meshing/Internal.py @@ -66,7 +66,6 @@ class InternalMeshing(AMeshingTool): return new_profiles def st_to_m(self, profiles, guide_list): - guide_list = ["un"] + guide_list + ["np"] max_values = [0] * (len(guide_list) - 1) max_values_index = [0] * (len(guide_list) - 1) @@ -85,11 +84,13 @@ class InternalMeshing(AMeshingTool): profiles[isect+1], guide_list[i], guide_list[i+1]) + for isect in reversed(range(0, max_values_index[i])): self.compl_sect(profiles[isect+1], profiles[isect], guide_list[i], guide_list[i+1]) + return profiles def interpolate_transversal_step(self, @@ -129,6 +130,7 @@ class InternalMeshing(AMeshingTool): p.name = f'interpol{p.rk}' new_profiles2.append(p) new_profiles.append(new_profiles2) + return new_profiles def compl_sect(self, sect1, sect2, tag1, tag2): @@ -243,8 +245,9 @@ class InternalMeshing(AMeshingTool): sect2.point(start2+len2).name = '' if tag1 != "un": sect2.point(start2).name = tag1 - if tag1 != "np": + if tag2 != "np": sect2.point(start2+len2).name = tag2 + sect2.modified() def update_rk(self, reach, begin_rk, end_rk, diff --git a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py index f4648d93..d5b7bcd4 100644 --- a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py +++ b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py @@ -248,15 +248,13 @@ class BoundaryConditionAdisTS(SQLSubModel): major, minor, release = version.strip().split(".") created = False - if major == "0" and minor == "0": - if int(release) < 11: + if major == "0" and int(minor) < 2: + if cls.is_table_exists(execute, "boundary_condition_adists"): + cls._db_update_to_0_2_0(execute, data) + else: cls._db_create(execute) created = True - if major == "0" and int(minor) < 2: - if not created: - cls._db_update_to_0_2_0(execute, data) - if not created: return cls._update_submodel(execute, version, data) diff --git a/src/Model/D90AdisTS/D90AdisTS.py b/src/Model/D90AdisTS/D90AdisTS.py index f0efa4a3..bfc4c103 100644 --- a/src/Model/D90AdisTS/D90AdisTS.py +++ b/src/Model/D90AdisTS/D90AdisTS.py @@ -73,15 +73,13 @@ class D90AdisTS(SQLSubModel): major, minor, release = version.strip().split(".") created = False - if major == "0" and minor == "0": - if int(release) < 11: + if major == "0" and int(minor) < 2: + if cls.is_table_exists(execute, "d90_adists"): + cls._db_update_to_0_2_0(execute, data) + else: cls._db_create(execute) created = True - if major == "0" and int(minor) < 2: - if not created: - cls._db_update_to_0_2_0(execute, data) - if not created: return cls._update_submodel(execute, version, data) diff --git a/src/Model/D90AdisTS/D90AdisTSSpec.py b/src/Model/D90AdisTS/D90AdisTSSpec.py index 1c07ae23..eb1611a0 100644 --- a/src/Model/D90AdisTS/D90AdisTSSpec.py +++ b/src/Model/D90AdisTS/D90AdisTSSpec.py @@ -70,16 +70,12 @@ class D90AdisTSSpec(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - created = False - - if major == "0" and minor == "0": - if int(release) < 11: - cls._db_create(execute) - created = True if major == "0" and int(minor) < 2: - if not created: + if cls.is_table_exists(execute, "d90_spec"): cls._db_update_to_0_2_0(execute, data) + else: + cls._db_create(execute) return True @@ -87,6 +83,7 @@ class D90AdisTSSpec(SQLSubModel): def _db_update_to_0_2_0(cls, execute, data): table = "d90_spec" table_new = "d90_adists_spec" + reachs = data['id2pid']['river_reach'] cls.update_db_add_pamhyr_id(execute, table, data) Scenario.update_db_add_scenario(execute, table) @@ -106,6 +103,7 @@ class D90AdisTSSpec(SQLSubModel): execute(f"ALTER TABLE {table_new}_tmp RENAME TO {table_new}") cls._db_update_to_0_2_0_set_d90_pid(execute, data) + cls._db_update_to_0_2_0_set_reach_pid(execute, table_new, reachs) @classmethod def _db_update_to_0_2_0_set_d90_pid(cls, execute, data): diff --git a/src/Model/DIFAdisTS/DIFAdisTS.py b/src/Model/DIFAdisTS/DIFAdisTS.py index 0f0a0d40..0af3f1e8 100644 --- a/src/Model/DIFAdisTS/DIFAdisTS.py +++ b/src/Model/DIFAdisTS/DIFAdisTS.py @@ -79,15 +79,13 @@ class DIFAdisTS(SQLSubModel): major, minor, release = version.strip().split(".") created = False - if major == "0" and minor == "0": - if int(release) < 11: + if major == "0" and int(minor) < 2: + if cls.is_table_exists(execute, "dif_adists"): + cls._db_update_to_0_2_0(execute, data) + else: cls._db_create(execute) created = True - if major == "0" and int(minor) < 2: - if not created: - cls._db_update_to_0_2_0(execute, data) - if not created: return cls._update_submodel(execute, version, data) diff --git a/src/Model/DIFAdisTS/DIFAdisTSSpec.py b/src/Model/DIFAdisTS/DIFAdisTSSpec.py index 167a85f8..6baec9b3 100644 --- a/src/Model/DIFAdisTS/DIFAdisTSSpec.py +++ b/src/Model/DIFAdisTS/DIFAdisTSSpec.py @@ -34,15 +34,13 @@ class DIFAdisTSSpec(SQLSubModel): def __init__(self, id: int = -1, method: str = "", status=None, owner_scenario=-1): - super(DIFAdisTSSpec, self).__init__() + super(DIFAdisTSSpec, self).__init__( + id=id, status=status, + owner_scenario=owner_scenario + ) self._status = status - if id == -1: - self._id = DIFAdisTSSpec._id_cnt - else: - self._id = id - self._method = method self._reach = None self._start_rk = None @@ -52,8 +50,6 @@ class DIFAdisTSSpec(SQLSubModel): self._c = None self._enabled = True - DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self._id) - @classmethod def _db_create(cls, execute, ext=""): execute(f""" @@ -81,16 +77,12 @@ class DIFAdisTSSpec(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - created = False - - if major == "0" and minor == "0": - if int(release) < 11: - cls._db_create(execute) - created = True if major == "0" and int(minor) < 2: - if not created: + if cls.is_table_exists(execute, "dif_spec"): cls._db_update_to_0_2_0(execute, data) + else: + cls._db_create(execute) return True diff --git a/src/Model/Friction/Friction.py b/src/Model/Friction/Friction.py index 80466c8b..28722255 100644 --- a/src/Model/Friction/Friction.py +++ b/src/Model/Friction/Friction.py @@ -365,3 +365,9 @@ class Friction(SQLSubModel): # self.end_strickler.medium]) return minor, medium + + def update_rks(self, begin_rk, end_rk): + self._begin_rk = begin_rk + self._end_rk = end_rk + + self.modified() diff --git a/src/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py index 4c28a07a..af18bf0b 100644 --- a/src/Model/Geometry/PointXYZ.py +++ b/src/Model/Geometry/PointXYZ.py @@ -66,6 +66,11 @@ class PointXYZ(Point): ) """) + execute( + "CREATE INDEX idx_point " + + "ON geometry_pointXYZ(profile, scenario);" + ) + return cls._create_submodel(execute) @classmethod @@ -90,6 +95,13 @@ class PointXYZ(Point): "ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE" ) + if major == "0" and minor == "2": + if int(release) < 5: + execute( + "CREATE INDEX idx_point " + + "ON geometry_pointXYZ(profile, scenario);" + ) + return cls._update_submodel(execute, version, data) @classmethod diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py index d928fdd1..1b513447 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -1098,6 +1098,7 @@ class ProfileXYZ(Profile, SQLSubModel): rk=self.rk, reach=self.reach, status=self._status) + for i, k in enumerate(self.points): p.insert_point(i, k.copy()) diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py index 668ac5bc..05b48d33 100644 --- a/src/Model/Geometry/Reach.py +++ b/src/Model/Geometry/Reach.py @@ -967,3 +967,9 @@ class Reach(SQLSubModel): break return global_index + + def update_rks(self, rks): + for rk, profile in zip(rks, self.profiles): + profile.rk = rk + + self.modified() diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py index 72c1b7b9..845d9bc3 100644 --- a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py +++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py @@ -79,16 +79,12 @@ class InitialConditionsAdisTS(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - created = False - - if major == "0" and minor == "0": - if int(release) < 11: - cls._db_create(execute) - created = True if major == "0" and int(minor) < 2: - if not created: + if cls.is_table_exists(execute, "initial_conditions_adists"): cls._db_update_to_0_2_0(execute, data) + else: + cls._db_create(execute) return True diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py index b00e16e0..85628c40 100644 --- a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py +++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py @@ -77,16 +77,12 @@ class ICAdisTSSpec(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - created = False - - if major == "0" and minor == "0": - if int(release) < 11: - cls._db_create(execute) - created = True if major == "0" and int(minor) < 2: - if not created: + if cls.is_table_exists(execute, "initial_conditions_adists_spec"): cls._db_update_to_0_2_0(execute, data) + else: + cls._db_create(execute) return True diff --git a/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py b/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py index 2b6be0d5..15b82df0 100644 --- a/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py +++ b/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py @@ -69,14 +69,12 @@ class Data(SQLSubModel): major, minor, release = version.strip().split(".") created = False - if major == "0" and minor == "0": - if int(release) < 11: - cls._db_create(execute) - created = True - if major == "0" and int(minor) < 2: - if not created: + if cls.is_table_exists( + execute, "lateral_contribution_data_adists"): cls._db_update_to_0_2_0(execute, data) + else: + cls._db_create(execute) return True @@ -243,15 +241,13 @@ class LateralContributionAdisTS(SQLSubModel): major, minor, release = version.strip().split(".") created = False - if major == "0" and minor == "0": - if int(release) < 11: + if major == "0" and int(minor) < 2: + if cls.is_table_exists(execute, "lateral_contribution_adists"): + cls._db_update_to_0_2_0(execute, data) + else: cls._db_create(execute) created = True - if major == "0" and int(minor) < 2: - if not created: - cls._db_update_to_0_2_0(execute, data) - if not created: return cls._update_submodel(execute, version, data) diff --git a/src/Model/OutputRKAdists/OutputRKAdists.py b/src/Model/OutputRKAdists/OutputRKAdists.py index 18a79360..b64af320 100644 --- a/src/Model/OutputRKAdists/OutputRKAdists.py +++ b/src/Model/OutputRKAdists/OutputRKAdists.py @@ -95,16 +95,12 @@ class OutputRKAdists(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - created = False - - if major == "0" and minor == "0": - if int(release) < 11: - cls._db_create(execute) - created = True if major == "0" and int(minor) < 2: - if not created: + if cls.is_table_exists(execute, "OutputRKAdists"): cls._db_update_to_0_2_0(execute, data) + else: + cls._db_create(execute) return True diff --git a/src/Model/Pollutants/Pollutants.py b/src/Model/Pollutants/Pollutants.py index 89aac30d..5169dab9 100644 --- a/src/Model/Pollutants/Pollutants.py +++ b/src/Model/Pollutants/Pollutants.py @@ -138,16 +138,12 @@ class PollutantCharacteristics(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - created = False - - if major == "0" and minor == "0": - if int(release) < 11: - cls._db_create(execute) - created = True if major == "0" and int(minor) < 2: - if not created: + if cls.is_table_exists(execute, "Pollutants_characteristics"): cls._db_update_to_0_2_0(execute, data) + else: + cls._db_create(execute) return cls._update_submodel(execute, version, data) @@ -371,11 +367,14 @@ class Pollutants(SQLSubModel): major, minor, release = version.strip().split(".") created = False - if major == "0" and minor == "0": - if int(release) < 11: + if major == "0" and int(minor) < 2: + if cls.is_table_exists(execute, "Pollutants"): + cls._db_update_to_0_2_0(execute, data) + else: cls._db_create(execute) created = True +<<<<<<< HEAD if major == "0" and int(minor) < 2: if not created: cls._db_update_to_0_2_0(execute, data) @@ -388,6 +387,8 @@ class Pollutants(SQLSubModel): # cls._db_update_to_0_2_5(execute, data) # # created = True +======= +>>>>>>> origin/scenarios if not created: return cls._update_submodel(execute, version, data) diff --git a/src/Model/Reservoir/Reservoir.py b/src/Model/Reservoir/Reservoir.py index e120f9ec..6137122c 100644 --- a/src/Model/Reservoir/Reservoir.py +++ b/src/Model/Reservoir/Reservoir.py @@ -312,7 +312,7 @@ class Reservoir(SQLSubModel): new_reservoir._node = next( filter( lambda n: n.id == node_id, data["nodes"] - ) + ), None ) data["reservoir"] = new_reservoir diff --git a/src/Model/Results/Results.py b/src/Model/Results/Results.py index aaddd54f..2e92767e 100644 --- a/src/Model/Results/Results.py +++ b/src/Model/Results/Results.py @@ -349,10 +349,16 @@ class Results(SQLSubModel): f"AND result = {pid}" ) + def _is_valid(self): + return "timestamps" in self._meta_data + def _db_save(self, execute, data=None): if self._status.scenario.id != self._owner_scenario: return True + if not self._is_valid(): + return False + pid = self._pamhyr_id if self._solver is None: solver_name = self.get("solver_name") diff --git a/src/Model/Results/ResultsAdisTS.py b/src/Model/Results/ResultsAdisTS.py index 97c29509..d5672e57 100644 --- a/src/Model/Results/ResultsAdisTS.py +++ b/src/Model/Results/ResultsAdisTS.py @@ -21,12 +21,13 @@ import os from copy import deepcopy from datetime import datetime +from Model.Tools.PamhyrDB import SQLSubModel from Model.Results.River.RiverAdisTS import River logger = logging.getLogger() -class Results(object): +class Results(SQLSubModel): def __init__(self, study=None, solver=None, repertory="", name="0", type_pol=None): self._study = study @@ -90,3 +91,6 @@ class Results(object): self._repertory, qlog=None, ) + + def _db_save(self, execute, data=None): + return False diff --git a/src/Model/Study.py b/src/Model/Study.py index 92028c67..05a04a9c 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -520,6 +520,9 @@ class Study(SQLModel): [self.scenarios, self._river] ) logger.debug(cnt) + # HACK: 8 is the number of SQL request do and dont counted in + # submodel save_count (let's count sql execute into the + # '_save' method) return cnt + 8 def close(self): diff --git a/src/Model/Tools/PamhyrDB.py b/src/Model/Tools/PamhyrDB.py index fba538c4..1db1e36d 100644 --- a/src/Model/Tools/PamhyrDB.py +++ b/src/Model/Tools/PamhyrDB.py @@ -301,6 +301,15 @@ class SQLSubModel(PamhyrID): """ raise NotImplementedMethodeError(cls, cls._db_update) + @classmethod + def is_table_exists(cls, execute, table): + return any( + execute( + "SELECT name FROM sqlite_master " + + f"WHERE type='table' AND name='{table}'" + ) + ) + @classmethod def _db_update_to_0_2_0_set_node_pid(cls, execute, table, nodes): els = execute( diff --git a/src/Solver/AdisTS.py b/src/Solver/AdisTS.py index b8310dfb..76f063d3 100644 --- a/src/Solver/AdisTS.py +++ b/src/Solver/AdisTS.py @@ -164,13 +164,194 @@ class AdisTS(CommandLineSolver): return lst def input_param(self): - name = self._study.name + name = self._study.name.replace(" ", "_") return f"{name}.REP" def log_file(self): - name = self._study.name + name = self._study.name.replace(" ", "_") return f"{name}.TRA" + def _export_ST(self, study, repertory, qlog, name="0"): + files = [] + + if qlog is not None: + qlog.put("Export ST file") + + os.makedirs(os.path.join(repertory, "net"), exist_ok=True) + + # Write header + edges = study.river.enable_edges() + for edge in edges: + name = f"Reach_{edge.id + 1:>3}".replace(" ", "0") + + with adists_file_open( + os.path.join(repertory, "net", f"{name}.ST"), + "w+" + ) as f: + files.append(str(os.path.join("net", f"{name}.ST"))) + + cnt_num = 1 + for profile in edge.reach.profiles: + self._export_ST_profile_header( + f, files, profile, cnt_num + ) + cnt_num += 1 + + # Points + for point in profile.points: + self._export_ST_point_line( + f, files, point + ) + + # Profile last line + f.write(f" 999.9990 999.9990 999.9990\n") + + def _export_ST_profile_header(self, wfile, files, + profile, cnt): + num = f"{cnt:>6}" + c1 = f"{profile.code1:>6}" + c2 = f"{profile.code2:>6}" + t = f"{len(profile.points):>6}" + rk = f"{profile.rk:>12f}"[0:12] + pname = profile.name + if profile.name == "": + # Generate name from profile id prefixed with + # 'p' (and replace space char with '0' char) + pname = f"p{profile.id:>3}".replace(" ", "0") + name = f"{pname:<19}" + + # Generate sediment additional data if available + sediment = "" + if profile.sl is not None: + if not any(filter(lambda f: ".GRA" in f, files)): + files.append(self._gra_file) + + # Number of layers + nl = len(profile.sl) + sediment = f" {nl:>3}" + + # Layers data + for layer in profile.sl.layers: + sediment += ( + f" {layer.height:>10} {layer.d50:>10} " + + f"{layer.sigma:>10} " + + f"{layer.critical_constraint:>10}" + ) + + # Profile header line + wfile.write(f"{num}{c1}{c2}{t} {rk} {pname} {sediment}\n") + + def _export_ST_point_line(self, wfile, files, point): + x = f"{point.x:<12.4f}"[0:12] + y = f"{point.y:<12.4f}"[0:12] + z = f"{point.z:<12.4f}"[0:12] + n = f"{point.name:<3}" + + # Generate sediment additional data if available + sediment = "" + prev = point.z + if point.sl is not None: + # Number of layers + nl = len(point.sl) + sediment = f"{nl:>3}" + + # Layers data + for layer in point.sl.layers: + prev = round(prev - layer.height, 5) + sediment += ( + f" {prev:>10} {layer.d50:>10} " + + f"{layer.sigma:>10} " + + f"{layer.critical_constraint:>10}" + ) + + # Point line + wfile.write(f"{x} {y} {z} {n} {sediment}\n") + + def _export_NET(self, study, repertory, qlog=None, name="0"): + + if qlog is not None: + qlog.put("Export NET file") + + with adists_file_open( + os.path.join(repertory, f"{name}.NET"), "w+") as f: + edges = study.river.enable_edges() + + for e in edges: + name = f"Reach_{e.id + 1:>3}".replace(" ", "0") + id = name + + n1 = f"{e.node1.id:3}".replace(" ", "x") + n2 = f"{e.node2.id:3}".replace(" ", "x") + file = os.path.join("net", name + ".ST") + + f.write(f"{id} {n1} {n2} {file}\n") + + def _export_fake_INI(self, study, repertory, qlog=None, name="0"): + if qlog is not None: + qlog.put("Export fake INI file") + + with adists_file_open( + os.path.join( + repertory, "Mage_fin.ini" + ), "w+" + ) as f: + edges = study.river.enable_edges() + for i, edge in enumerate(edges): + lst = list(filter( + lambda f: f.is_full_defined(), + edge.frictions.frictions + )) + rk_min = 9999999.9 + rk_max = -9999999.9 + coeff_min = -1.0 + coeff_max = -1.0 + for s in lst: # TODO optimise ? + if s.begin_rk > rk_max: + rk_max = s.begin_rk + coeff_max = s.begin_strickler + if s.begin_rk < rk_min: + rk_min = s.begin_rk + coeff_min = s.begin_strickler + if s.end_rk > rk_max: + rk_max = s.end_rk + coeff_max = s.end_strickler + if s.end_rk < rk_min: + rk_min = s.end_rk + coeff_min = s.end_strickler + + print("min max", rk_min, rk_max) + + def get_stricklers_from_rk(rk, lst): + print("rk", rk) + + coeff = None + if rk > rk_max: + coeff = coeff_max + elif rk < rk_min: + coeff = coeff_min + else: + for s in lst: + if (rk >= s.begin_rk and rk <= s.end_rk or + rk <= s.begin_rk and rk >= s.end_rk): + coeff = s.begin_strickler # TODO: inerpolate + break + + # TODO interpolation if rk is not in frictons + + if coeff is None: + logger.error( + "Study frictions are not fully defined" + ) + return None + + return coeff.minor, coeff.medium + + for j, profile in enumerate(edge.reach.profiles): + coef_min, coef_moy = get_stricklers_from_rk(profile.rk, + lst) + f.write( + f" {i+1:3}{j+1:4}{' '*116}{coef_min:9}{coef_moy:9}\n") + def _export_REP_additional_lines(self, study, rep_file): lines = filter( lambda line: line.is_enabled(), @@ -192,7 +373,7 @@ class AdisTS(CommandLineSolver): ), "w+" ) as f: path = os.path.join("..", mage_rep, name) - f.write(f"NET {path}.NET\n") + f.write(f"NET {name}.NET\n") f.write(f"REP {path}.REP\n") for file in files: @@ -201,13 +382,25 @@ class AdisTS(CommandLineSolver): self._export_REP_additional_lines(study, f) - path_mage_net = os.path.join(os.path.abspath( - os.path.join(repertory, os.pardir) - ), os.path.join(mage_rep, "net")) - path_adists_net = os.path.join(repertory, "net") + self._export_ST(study, repertory, qlog, name=name) + self._export_NET(study, repertory, qlog, name=name) - if os.path.exists(path_mage_net): - shutil.copytree(path_mage_net, path_adists_net, dirs_exist_ok=True) + # fake mage_fin.ini: + + path_mage = os.path.join(os.path.abspath( + os.path.join(repertory, os.pardir)), mage_rep) + self._export_fake_INI(study, path_mage, + qlog, name=name) + + # path_mage_net = os.path.join(os.path.abspath( + # os.path.join(repertory, os.pardir) + # ), os.path.join(mage_rep, "net")) + # path_adists_net = os.path.join(repertory, "net") + + # if os.path.exists(path_mage_net): + # shutil.copytree(path_mage_net, + # path_adists_net, + # dirs_exist_ok=True) @timer def export(self, study, repertory, qlog=None): diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py index 772fc7b7..614a3d05 100644 --- a/src/Solver/Mage.py +++ b/src/Solver/Mage.py @@ -386,26 +386,26 @@ class Mage(CommandLineSolver): return files def _export_LC(self, study, lateral, f, qlog, name="0"): - if lateral.edge is None: + if lateral.reach is None: return if lateral.is_deleted(): return edges = study.river.enable_edges() - if lateral.edge not in edges: + if lateral.reach not in edges: return eid, _ = next( filter( - lambda e: e[1] == lateral.edge, + lambda e: e[1] == lateral.reach, enumerate(edges) ) ) name = f"{eid+1:>3}" - # name = f"Reach_{lateral.edge.id + 1:>3}".replace(" ", "0") - f.write(f"* {lateral.edge.name} ({name}) {lateral.lctype}\n") + # name = f"Reach_{lateral.reach.id + 1:>3}".replace(" ", "0") + f.write(f"* {lateral.reach.name} ({name}) {lateral.lctype}\n") f.write( f"${name} " + f"{lateral.begin_rk:>10.4f} {lateral.end_rk:>10.4f}\n" @@ -546,6 +546,15 @@ class Mage(CommandLineSolver): return files + def skip_hs(self, hs): + return ( + hs.input_reach is None + or not hs.input_reach.is_enable() + or not hs.enabled + or hs.input_section is None + or hs.is_deleted() + ) + def _export_SIN(self, study, repertory, qlog, name="0"): files = [] @@ -571,19 +580,7 @@ class Mage(CommandLineSolver): files.append(f"{name}.SIN") for hs in hydraulic_structures: - if hs.input_reach is None: - continue - - if not hs.input_reach.is_enable(): - continue - - if not hs.enabled: - continue - - if hs.input_section is None: - continue - - if hs.is_deleted(): + if self.skip_hs(hs): continue f.write( @@ -1186,7 +1183,7 @@ class Mage8(Mage): for p in r.profiles: p.geometry.compute_tabulation() - logger.info(f"compute velocily...") + logger.info(f"compute velocity...") for r in reachs: for t in ts_list: diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index e92ca2cd..4da1ad1f 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -58,12 +58,12 @@ class Rubar3(CommandLineSolver): ("rubarbe_iovis", "n"), ("rubarbe_rep", "n"), ("rubarbe_tinit", "000:00:00:00"), - ("rubarbe_tmax", "999:99:99:00"), + ("rubarbe_tmax", "000:01:00:00"), # 1 day ("rubarbe_tiopdt", "000:00:00:00"), ("rubarbe_dt", "5.0"), ("rubarbe_ts", "999:99:99:00"), - ("rubarbe_dtsauv", "00:00:00:05"), - ("rubarbe_psave", "00:00:00:05"), + ("rubarbe_dtsauv", "00:00:05:00"), + ("rubarbe_psave", "00:00:05:00"), ("rubarbe_fdeb1", "1"), ("rubarbe_fdeb2", "10"), ("rubarbe_fdeb3", "100"), @@ -610,6 +610,28 @@ class Rubar3(CommandLineSolver): ) ts = set() + timestamp = next(filter( + lambda p: p.name == 'rubarbe_tinit', + study.river.get_params(self._type).parameters + )).value + if timestamp.count(':') == 3: + timestamp = old_pamhyr_date_to_timestamp(timestamp) + ts.add(timestamp) + + # add initial condition + for r, edge in enumerate(study.river.enable_edges()): + reach = edge.reach + ics = study.river.initial_conditions.get(edge) + q = ics.get_discharge() + z = ics.get_elevation() + k = 0 + for i, j in zip(z, q): + v = reach.profiles[k].speed(i, j) + set_and_compute_limites(reachs[r][0], k, i, j, v) + k += 1 + + # start read + end = False while True: line = f.readline() @@ -649,6 +671,97 @@ class Rubar3(CommandLineSolver): h, s, q, z = read_data_line(f) set_and_compute_limites(reach, ind, z+h, q, s) + @timer + def write_bin(self, study, fname, results, qlog=None, name="0"): + logger.info(f"write_bin: Start writing '{fname}' ...") + + with open(fname, "wb") as f: + def newline(j): return np.asarray([j], dtype=np.int32).tofile(f) + def endline(j): return np.asarray([j], dtype=np.int32).tofile(f) + + def write_int(i): + newline(len(i)*4) + np.array(i, dtype=np.int32).tofile(f) + endline(len(i)*4) + + def write_float(i): + newline(len(i)*4) + np.array(i, dtype=np.float32).tofile(f) + endline(len(i)*4) + + def write_float64(i): + newline(len(i)*8) + np.array(i, dtype=np.float64).tofile(f) + endline(len(i)*8) + + def write_float64(i): + newline(len(i)*8) + np.array(i, dtype=np.float64).tofile(f) + endline(len(i)*8) + + def write_data(npts, t, a, val): + newline(npts*4 + 13) + np.array(npts, dtype=np.int32).tofile(f) + np.array(t, dtype=np.float64).tofile(f) + np.array(bytearray(a.encode()), dtype=np.byte).tofile(f) + np.array(val, dtype=np.float32).tofile(f) + endline(npts*4 + 13) + + ts_list = sorted(results.get("timestamps")) + profiles = [] + for r in results.river.reachs: + profiles += r.profiles + + # Meta data (1st line) + nb_reach = len(results.river) + nb_profile = len(profiles) + write_int([nb_reach, nb_profile, "82"]) + + # Reach information (2nd line) + is1 = [] + is2 = [] + ltmp = [] + i = 1 + for r in results.river.reachs: + j = i+len(r)-1 + ltmp += [i, j] + is1.append(i) + is2.append(j) + i += i+len(r) + write_int(ltmp) + + # X (3rd line) + rk = [] + for r in results.river.reachs: + rk += r.geometry.get_rk() + write_float(rk) + + # Z and Y (4th line) + # ltmp = [] + # for r in results.river.reachs: + # for p in r.prifiles: + # ltpm.append(p.rk) + write_float(3*nb_profile*[0.0]) + + # Data + + for timestamp in ts_list: + q = list( + map( + lambda p: p.get_ts_key(timestamp, "Q"), + profiles + ) + ) + write_data(nb_profile, timestamp, "Q", q) + z = list( + map( + lambda p: p.get_ts_key(timestamp, "Z"), + profiles + ) + ) + write_data(nb_profile, timestamp, "Z", z) + logger.info(f"write_bin: ... end with {len(ts_list)} timestamps") + @timer def results(self, study, repertory, qlog=None, name=None): results = Results( diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index d428ea30..d57de56f 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -16,7 +16,7 @@ # -*- coding: utf-8 -*- -from tools import trace, timer +from tools import trace, timer, logger_exception from PyQt5.QtWidgets import ( QAction, QListView, QVBoxLayout, @@ -134,7 +134,10 @@ class GeoTIFFListWindow(PamhyrWindow): def _update_rectangle(self): for rect in self._plot_rect: - rect.remove() + try: + rect.remove() + except Exception as e: + logger_exception(e) self._plot_rect = [] diff --git a/src/View/Geometry/UndoCommand.py b/src/View/Geometry/UndoCommand.py index 5414f358..62c5acf6 100644 --- a/src/View/Geometry/UndoCommand.py +++ b/src/View/Geometry/UndoCommand.py @@ -247,14 +247,13 @@ class UpdateRKCommand(QUndoCommand): self._new_end_rk = None def undo(self): - for rk, profile in zip(self._rks, self._reach.profiles): - profile.rk = rk + + self._reach.update_rks(self._rks) for begin_rk, end_rk, friction in zip(self._begin_rk, self._end_rk, self._frictions): - friction.begin_rk = begin_rk - friction.end_rk = end_rk + friction.update_rks(begin_rk, end_rk) def redo(self): if self._new_rks is None: @@ -264,13 +263,12 @@ class UpdateRKCommand(QUndoCommand): **self._data ) - for rk, profile in zip(self._new_rks, self._reach.profiles): - profile.rk = rk + self._reach.update_rks(self._new_rks) + for begin_rk, end_rk, friction in zip(self._new_begin_rk, self._new_end_rk, self._frictions): - friction.begin_rk = begin_rk - friction.end_rk = end_rk + friction.update_rks(begin_rk, end_rk) class MeshingCommand(QUndoCommand): diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index ce4bc954..2744c190 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -302,7 +302,6 @@ class GeometryWindow(PamhyrWindow): self.tableView.model().blockSignals(False) def edit_meshing(self): - rows = list( set( (i.row() for i in self.tableView.selectedIndexes()) @@ -326,6 +325,25 @@ class GeometryWindow(PamhyrWindow): logger_exception(e) return + ind = [] + for i in range(self._reach.number_profiles): + if self._reach.profile(i).rk in selected_rk: + ind.append(i) + self.tableView.setFocus() + selection = self.tableView.selectionModel() + index = QItemSelection() + if len(ind) > 0: + for i in ind: + index.append(QItemSelectionRange( + self.tableView.model().index(i, 0)) + ) + selection.select( + index, + QItemSelectionModel.Rows | + QItemSelectionModel.ClearAndSelect | + QItemSelectionModel.Select + ) + def _edit_meshing(self, data): try: mesher = InternalMeshing() @@ -371,9 +389,6 @@ class GeometryWindow(PamhyrWindow): except Exception as e: logger_exception(e) raise ExternFileMissingError( - module="mage", - filename="MailleurTT", - path=MeshingWithMageMailleurTT._path(), src_except=e ) diff --git a/src/View/LateralContribution/translate.py b/src/View/LateralContribution/translate.py index ed04fbd3..24fe45d2 100644 --- a/src/View/LateralContribution/translate.py +++ b/src/View/LateralContribution/translate.py @@ -53,7 +53,8 @@ class LCTranslate(MainTranslate): self._dict["y"] = _translate("Geometry", "Y (m)") self._dict["z"] = _translate("Geometry", "Z (m)") self._dict["file_lat"] = _translate( - "LateralContribution", "Shapefile (*.LAT *.lat)") + "LateralContribution", + "Mage lateral contributions file (*.LAT *.lat)") self._dict["file_all"] = _translate( "LateralContribution", "All files (*)") diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index f651b3d9..70a70f5c 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -299,6 +299,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_menu_rep_additional_lines": self.open_rep_lines, "action_menu_edit_geotiff": self.open_geotiff, "action_menu_close": self.close_model, + "action_menu_quit": self.close, "action_menu_results_last": self.open_last_results, "action_menu_open_results_from_file": self.open_results_from_file, "action_menu_compare_scenarios_results": @@ -2006,6 +2007,13 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if res.river.reach(r).has_bedload(): res.river.reach(r).bufferize(ts, "zfd") + for res in (result3, result4, result5): + for r in range(int(res.get("nb_reach"))): + for key in ["Z", "Q", "V"]: + res.river.reach(r).bufferize(ts, key) + if res.river.reach(r).has_bedload(): + res.river.reach(r).bufferize(ts, "zfd") + return [result4, result5, result3] def open_results_adists(self): diff --git a/src/View/Results/CustomExport/CustomExport.py b/src/View/Results/CustomExport/CustomExport.py index fe430c7c..93efae93 100644 --- a/src/View/Results/CustomExport/CustomExport.py +++ b/src/View/Results/CustomExport/CustomExport.py @@ -19,7 +19,7 @@ from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtWidgets import ( - QRadioButton, QCheckBox, QVBoxLayout, QLabel, + QRadioButton, QCheckBox, QVBoxLayout, QLabel, QFrame ) from View.Results.translate import ResultsTranslate @@ -43,6 +43,7 @@ class CustomExportDialog(PamhyrDialog): self.setup_radio_buttons_x() self.setup_radio_buttons_res() + self.setup_label() self.setup_envelop_box() self.setup_check_boxes() @@ -87,6 +88,22 @@ class CustomExportDialog(PamhyrDialog): layout.addStretch() + def setup_label(self): + self._label = self.find(QLabel, "label_4") + self._label.setFrameStyle(QFrame.StyledPanel) + self._label.setStyleSheet('background-color: white') + self.set_label() + for r in self._radio: + r[1].clicked.connect(self.set_label) + + def set_label(self): + if self._radio[0][1].isChecked(): + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_time()) + else: + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_profile()) + def setup_envelop_box(self): layout = self.find(QVBoxLayout, "verticalLayout_x") self._envelop = QCheckBox( diff --git a/src/View/Results/CustomExport/CustomExportAdis.py b/src/View/Results/CustomExport/CustomExportAdis.py index d2364ac6..ec3fd68a 100644 --- a/src/View/Results/CustomExport/CustomExportAdis.py +++ b/src/View/Results/CustomExport/CustomExportAdis.py @@ -19,7 +19,7 @@ from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtWidgets import ( - QRadioButton, QCheckBox, QVBoxLayout, + QRadioButton, QCheckBox, QVBoxLayout, QLabel, QFrame ) from View.Results.translate import ResultsTranslate @@ -50,6 +50,7 @@ class CustomExportAdisDialog(PamhyrDialog): self.setup_radio_buttons_x() self.setup_radio_buttons_pol() + self.setup_label() self.setup_check_boxes() self.value = None @@ -84,6 +85,22 @@ class CustomExportAdisDialog(PamhyrDialog): self._radio2[0][1].setChecked(True) layout.addStretch() + def setup_label(self): + self._label = self.find(QLabel, "label_4") + self._label.setFrameStyle(QFrame.StyledPanel) + self._label.setStyleSheet('background-color: white') + self.set_label() + for r in self._radio: + r[1].clicked.connect(self.set_label) + + def set_label(self): + if self._radio[0][1].isChecked(): + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_time()) + else: + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_profile()) + def setup_check_boxes(self): self._check = [] layout = self.find(QVBoxLayout, "verticalLayout_y") diff --git a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py index aa3daadc..c9b8fa17 100644 --- a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py +++ b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py @@ -19,7 +19,7 @@ from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtWidgets import ( - QRadioButton, QCheckBox, QVBoxLayout, + QRadioButton, QCheckBox, QVBoxLayout, QLabel, QFrame ) from View.Results.CustomPlot.Translate import CustomPlotTranslate @@ -38,10 +38,12 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): parent=parent ) + self._parent = parent self._available_values_x = self._trad.get_dict("values_x") self._available_values_y = self._trad.get_dict("values_y") self.setup_radio_buttons() + self.setup_label() self.setup_envelop_box() self.setup_check_boxs() @@ -62,6 +64,22 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): self._radio[0][1].setChecked(True) layout.addStretch() + def setup_label(self): + self._label = self.find(QLabel, "label_3") + self._label.setFrameStyle(QFrame.StyledPanel) + self._label.setStyleSheet('background-color: white') + self.set_label() + for r in self._radio: + r[1].clicked.connect(self.set_label) + + def set_label(self): + if self._radio[0][1].isChecked(): + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_time()) + else: + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_profile()) + def setup_envelop_box(self): self._envelop = [] layout = self.find(QVBoxLayout, "verticalLayout_x") diff --git a/src/View/Results/CustomPlot/Plot.py b/src/View/Results/CustomPlot/Plot.py index 2f3e4741..ddbeb72b 100644 --- a/src/View/Results/CustomPlot/Plot.py +++ b/src/View/Results/CustomPlot/Plot.py @@ -204,7 +204,8 @@ class CustomPlot(PamhyrPlot): e = list( map( - lambda p: max(self.get_ts_zmin(p, res_id)), + lambda p: max(self.get_ts_zmin( + p, self._current_res_id)), range(len(reach)) ) ) @@ -217,7 +218,8 @@ class CustomPlot(PamhyrPlot): e = list( map( - lambda p: min(self.get_ts_zmin(p, res_id)), + lambda p: min(self.get_ts_zmin( + p, self._current_res_id)), range(len(reach)) ) ) diff --git a/src/View/Results/PlotH.py b/src/View/Results/PlotH.py index 77ff7193..e40c132b 100644 --- a/src/View/Results/PlotH.py +++ b/src/View/Results/PlotH.py @@ -21,7 +21,7 @@ import logging from functools import reduce from datetime import datetime -from tools import timer, trace +from tools import timer, trace, logger_exception from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( @@ -68,6 +68,7 @@ class PlotH(PamhyrPlot): self._line_max = [] self._line = [] + self._add_data_lines = [] @property def results(self): @@ -117,15 +118,17 @@ class PlotH(PamhyrPlot): self.set_ticks_time_formater() + self.draw_additional_data(0) + self.enable_legend() self.canvas.draw_idle() - self.update_idle + self.update_idle() + self.draw_current() self._init = True def draw_data(self, res_id): - results = self.results[res_id] reach = results.river.reach(self._current_reach_id) for i, p in enumerate(self._current_profile_id): @@ -152,6 +155,28 @@ class PlotH(PamhyrPlot): ) self._line.append(line) + def draw_additional_data(self, res_id): + results = self.results[res_id] + + self._add_data_lines = [] + + for data in results.get("additional_data"): + data = data._data + x, y = data['x'], data['y'] + legend = data['legend'] + unit = data['unit'] + + if data['type_x'] == 'time' and data['type_y'] == 'discharge': + line, = self.canvas.axes.plot( + x, y, marker="+", + label=legend + ' ' + unit + ) + + self._add_data_lines.append(line) + + self.enable_legend() + self.canvas.draw_idle() + def draw_current(self): self._current, = self.canvas.axes.plot( [self._current_timestamp, self._current_timestamp], @@ -220,6 +245,10 @@ class PlotH(PamhyrPlot): self.update_current() self.update_idle() + def update_additional_data(self): + self.draw_additional_data(0) + self.update_idle() + def update_all(self): self._current_reach_id = self._parent._get_current_reach() self._current_profile_id = self._parent._get_current_profiles_list() diff --git a/src/View/Results/PlotRKC.py b/src/View/Results/PlotRKC.py index 2c09035c..744dc7e3 100644 --- a/src/View/Results/PlotRKC.py +++ b/src/View/Results/PlotRKC.py @@ -210,6 +210,29 @@ class PlotRKC(PamhyrPlot): linestyle='dotted', ) + def draw_additional_data(self, res_id): + results = self.results[res_id] + + self._add_data_lines = [] + + for data in results.get("additional_data"): + data = data._data + + tx, ty = data['type_x'], data['type_y'] + x, y = data['x'], data['y'] + legend = data['legend'] + unit = data['unit'] + + if tx == 'water_elevation' and ty == 'time': + line, = self.canvas.axes.plot( + x, y, marker="+", + label=legend + ' ' + unit + ) + + self._add_data_lines.append(line) + + self.canvas.draw_idle() + def draw_current(self, reach): rk = reach.geometry.get_rk() z_min = reach.geometry.get_z_min() @@ -332,6 +355,10 @@ class PlotRKC(PamhyrPlot): alpha=0.7, interpolate=True ) + def update_additional_data(self): + self.draw_additional_data(0) + self.update_idle() + def update_current(self): results = self.results[self._current_res_id] reach = results.river.reach(self._current_reach_id) diff --git a/src/View/Results/PlotXY.py b/src/View/Results/PlotXY.py index b4618079..6696ff55 100644 --- a/src/View/Results/PlotXY.py +++ b/src/View/Results/PlotXY.py @@ -20,7 +20,7 @@ import logging from functools import reduce -from tools import timer, trace +from tools import timer, trace, logger_exception from View.Tools.PamhyrPlot import PamhyrPlot import numpy as np from matplotlib import collections @@ -68,6 +68,9 @@ class PlotXY(PamhyrPlot): self._plot_img = {} + self._geotiff_img = [] + self._geotiff_bounds = [] + self._parent = parent self._timestamps = parent._timestamps self._current_timestamp = max(self._timestamps) @@ -203,7 +206,6 @@ class PlotXY(PamhyrPlot): self.canvas.axes.add_collection(self.line_xy_collection) def draw_other_profiles(self, reaches): - for reach in reaches: for xy in zip(reach.geometry.get_x(), reach.geometry.get_y()): @@ -325,6 +327,24 @@ class PlotXY(PamhyrPlot): alpha=0.7 ) + def add_geotiff(self, img, bounds): + self._geotiff_img.append(img) + self._geotiff_bounds.append(bounds) + + def draw_add_geotiff(self): + xlim = self.canvas.axes.get_xlim() + ylim = self.canvas.axes.get_ylim() + + for i, img in enumerate(self._geotiff_img): + bounds = self._geotiff_bounds[i] + + self.canvas.axes.imshow(img.transpose((1, 2, 0)), + extent=bounds) + + self.idle() + self.canvas.axes.set_xlim(xlim) + self.canvas.axes.set_ylim(ylim) + def draw_geotiff(self): if not _rasterio_loaded: return @@ -332,7 +352,10 @@ class PlotXY(PamhyrPlot): lst = self._data[0]._study.river._geotiff.lst for img in self._plot_img: - self._plot_img[img].remove() + try: + self._plot_img[img].remove() + except Exception as e: + logger_exception(e) self._plot_img = {} @@ -358,6 +381,7 @@ class PlotXY(PamhyrPlot): if not geotiff.is_enabled(): self._plot_img[geotiff].set(alpha=0.5) + self.draw_add_geotiff() self.idle() def set_reach(self, reach_id): @@ -453,8 +477,12 @@ class PlotXY(PamhyrPlot): overflow.append(pt_right) for plot in self.overflow: - plot[0].remove() + try: + plot[0].remove() + except Exception as e: + logger_exception(e) del plot[0] + self.overflow = [] for p in overflow: diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 9324f356..c67df06e 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -314,16 +314,12 @@ class ResultsWindow(PamhyrWindow): super(ResultsWindow, self).closeEvent(event) def _compute_status_label(self): - # Timestamp - ts = self._timestamps[self._slider_time.value()] - t0 = datetime.fromtimestamp(0) - fts = str( - datetime.fromtimestamp(ts) - t0 - ) - fts.replace("days", _translate("Results", "days"))\ - .replace("day", _translate("Results", "day")) + return (self.text_bief() + " | " + + self.text_profile() + " | " + + self.text_time()) + def text_bief(self): # Reach table = self.find(QTableView, f"tableView_reach") indexes = table.selectedIndexes() @@ -331,7 +327,16 @@ class ResultsWindow(PamhyrWindow): reach = self._study.river.enable_edges()[0] else: reach = self._study.river.enable_edges()[indexes[0].row()] + return f"{self._trad['reach']}: {reach.name}" + def text_profile(self): + # Reach + table = self.find(QTableView, f"tableView_reach") + indexes = table.selectedIndexes() + if len(indexes) == 0: + reach = self._study.river.enable_edges()[0] + else: + reach = self._study.river.enable_edges()[indexes[0].row()] # Profile table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() @@ -341,10 +346,22 @@ class ResultsWindow(PamhyrWindow): profile = reach.reach.profile(indexes[0].row()) pname = profile.name if profile.name != "" else profile.rk + return f"{self._trad['cross_section']}: {pname}" - return (f"{self._trad['reach']}: {reach.name} | " + - f"{self._trad['cross_section']}: {pname} | " + - f"{self._trad['unit_time_s']} : {fts} ({ts} sec)") + def text_time(self): + # Timestamp + ts = self._timestamps[self._slider_time.value()] + + t0 = datetime.fromtimestamp(0) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts.replace("days", _translate("Results", "days"))\ + .replace("day", _translate("Results", "day")) + return f"{self._trad['time']} : {fts} ({ts} sec)" def setup_statusbar(self): txt = self._compute_status_label() @@ -779,20 +796,29 @@ class ResultsWindow(PamhyrWindow): first_line.append(f"Profile: {pname}") val_dict = self._export_time(profile_id, y, solver_id) - with open(filename, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=',', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - dict_x = self._trad.get_dict("values_x") - header = [] - writer.writerow(first_line) - for text in val_dict.keys(): - header.append(text) - writer.writerow(header) - for row in range(len(val_dict[dict_x[x]])): - line = [] - for var in val_dict.keys(): - line.append(val_dict[var][row]) - writer.writerow(line) + try: + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + dict_x = self._trad.get_dict("values_x") + header = [] + writer.writerow(first_line) + for text in val_dict.keys(): + header.append(text) + writer.writerow(header) + for row in range(len(val_dict[dict_x[x]])): + line = [] + for var in val_dict.keys(): + line.append(val_dict[var][row]) + writer.writerow(line) + self._timer.stop() + except Exception as e: + self.message_box( + window_title=self._trad["Warning"], + text=self._trad["mb_write_error"], + informative_text=self._trad["mb_close_file"] + ) + logger_exception(e) def export_all(self, reach, directory, timestamps): name = reach.name @@ -805,17 +831,25 @@ class ResultsWindow(PamhyrWindow): f"reach_{name}.csv" ) - with open(file_name, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=',', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - ts = timestamps[0] - writer.writerow(self._table["raw_data"]._headers) - for row in range(self._table["raw_data"].rowCount()): - line = [] - for column in range(self._table["raw_data"].columnCount()): - index = self._table["raw_data"].index(row, column) - line.append(self._table["raw_data"].data(index)) - writer.writerow(line) + try: + with open(file_name, 'w', newline='') as csvfile: + writer = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + ts = timestamps[0] + writer.writerow(self._table["raw_data"]._headers) + for row in range(self._table["raw_data"].rowCount()): + line = [] + for column in range(self._table["raw_data"].columnCount()): + index = self._table["raw_data"].index(row, column) + line.append(self._table["raw_data"].data(index)) + writer.writerow(line) + except Exception as e: + self.message_box( + window_title=self._trad["Warning"], + text=self._trad["mb_write_errore"], + informative_text=self._trad["mb_close_file"] + ) + logger_exception(e) def export_current(self): self.file_dialog( @@ -1250,24 +1284,22 @@ class ResultsWindow(PamhyrWindow): # b[1] bottom # b[2] right # b[3] top - xlim = self.canvas.axes.get_xlim() - ylim = self.canvas.axes.get_ylim() - if b[2] > b[0] and b[1] < b[3]: - self.canvas.axes.imshow(img.transpose((1, 2, 0)), - extent=[b[0], b[2], b[1], b[3]]) - else: + bounds = [b[0], b[2], b[1], b[3]] + + if not (b[2] > b[0] and b[1] < b[3]): + xlim = self.canvas.axes.get_xlim() + ylim = self.canvas.axes.get_ylim() + dlg = CoordinatesDialog( xlim, ylim, trad=self._trad, parent=self ) if dlg.exec(): - self.canvas.axes.imshow(img.transpose((1, 2, 0)), - extent=dlg.values) - self.plot_xy.idle() - self.canvas.axes.set_xlim(xlim) - self.canvas.axes.set_ylim(ylim) - return + bounds = dlg.values + + self.plot_xy.add_geotiff(img, bounds) + self.plot_xy.draw_add_geotiff() def import_data(self): file_types = [ @@ -1289,6 +1321,8 @@ class ResultsWindow(PamhyrWindow): x, y = self.read_csv_file_data(filename) data = self.read_csv_file_format(x, y) + if data is None: + return results = self._results[self._current_results[0]] data_lst = results.get("additional_data") @@ -1362,32 +1396,9 @@ class ResultsWindow(PamhyrWindow): def update_plot_additional_data(self): results = self._results[self._current_results[0]] + self.plot_rkc.update_additional_data() + self.plot_h.update_additional_data() + for data in results.get("additional_data"): - data = data._data - x, y = data['x'], data['y'] - legend = data['legend'] - unit = data['unit'] - - if ( - data['type_x'] == 'water_elevation' and - data['type_y'] == 'time' - ): - line = self.canvas_2.axes.plot( - x, y, marker="+", - label=legend + ' ' + unit - ) - self.plot_rkc.canvas.draw_idle() - self.plot_rkc.update_idle() - - if data['type_x'] == 'time' and data['type_y'] == 'discharge': - line = self.canvas_4.axes.plot( - x, y, marker="+", - label=legend + ' ' + unit - ) - self.plot_h._line.append(line) - self.plot_h.enable_legend() - self.plot_h.canvas.draw_idle() - self.plot_h.update_idle() - for p in self._additional_plot: self._additional_plot[p].add_imported_plot(data) diff --git a/src/View/Results/WindowAdisTS.py b/src/View/Results/WindowAdisTS.py index 727d17ea..74b4cd8d 100644 --- a/src/View/Results/WindowAdisTS.py +++ b/src/View/Results/WindowAdisTS.py @@ -345,24 +345,29 @@ class ResultsWindowAdisTS(PamhyrWindow): super(ResultsWindowAdisTS, self).closeEvent(event) def _compute_status_label(self): - # Timestamp - ts = self._timestamps[self._slider_time.value()] - - t0 = datetime.fromtimestamp(0) - fts = str( - datetime.fromtimestamp(ts) - t0 - ) - fts.replace("days", _translate("Results", "days"))\ - .replace("day", _translate("Results", "day")) + return (self.text_bief() + " | " + + self.text_profile() + " | " + + self.text_pollutant() + " | " + + self.text_time()) + def text_bief(self): # Reach table = self.find(QTableView, f"tableView_reach") indexes = table.selectedIndexes() if len(indexes) == 0: - reach = self._study.river.edges()[0] + reach = self._study.river.enable_edges()[0] else: - reach = self._study.river.edges()[indexes[0].row()] + reach = self._study.river.enable_edges()[indexes[0].row()] + return f"{self._trad['reach']}: {reach.name}" + def text_profile(self): + # Reach + table = self.find(QTableView, f"tableView_reach") + indexes = table.selectedIndexes() + if len(indexes) == 0: + reach = self._study.river.enable_edges()[0] + else: + reach = self._study.river.enable_edges()[indexes[0].row()] # Profile table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() @@ -372,7 +377,24 @@ class ResultsWindowAdisTS(PamhyrWindow): profile = reach.reach.profile(indexes[0].row()) pname = profile.name if profile.name != "" else profile.rk + return f"{self._trad['cross_section']}: {pname}" + def text_time(self): + # Timestamp + ts = self._timestamps[self._slider_time.value()] + + t0 = datetime.fromtimestamp(0) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts.replace("days", _translate("Results", "days"))\ + .replace("day", _translate("Results", "day")) + return f"{self._trad['time']} : {fts} ({ts} sec)" + + def text_pollutant(self): # Pollutant table = self.find(QTableView, f"tableView_pollutants") indexes = table.selectedIndexes() @@ -381,10 +403,7 @@ class ResultsWindowAdisTS(PamhyrWindow): self._results.pollutants_list[i.row()+1] for i in indexes ] - return (f"{self._trad['reach']}: {reach.name} | " + - f"{self._trad['cross_section']}: {pname} | " + - f"Pollutant: {', '.join(self.pollutant_label)} | " + - f"{self._trad['unit_time_s']} : {fts} ({ts} sec)") + return (f"Pollutant: {', '.join(self.pollutant_label)}") def setup_statusbar(self): txt = self._compute_status_label() diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index 95ddd9a4..df4c26dd 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -56,6 +56,13 @@ class ResultsTranslate(MainTranslate): self._dict["ImageCoordinates"] = _translate( "Results", "Image coordinates" ) + self._dict["mb_write_error"] = _translate( + "Results", "An error occured when writing to file" + ) + self._dict["mb_close_file"] = _translate( + "Results", + "If the file is in use, close it and try again" + ) self._sub_dict["table_headers_reach"] = { "name": _translate("Results", "Reach name"), diff --git a/src/View/RunSolver/WindowAdisTS.py b/src/View/RunSolver/WindowAdisTS.py index 17c5a85c..47c7c803 100644 --- a/src/View/RunSolver/WindowAdisTS.py +++ b/src/View/RunSolver/WindowAdisTS.py @@ -94,7 +94,8 @@ class SelectSolverWindowAdisTS(PamhyrDialog): ) solvers_mage = list(filter( - lambda x: "mage" in x._type.lower(), self._config.solvers + lambda x: "mage" or "rubar" in x._type.lower(), + self._config.solvers )) solvers_mage_names = list(map(lambda x: x._name, solvers_mage)) @@ -184,6 +185,9 @@ class SolverLogWindowAdisTS(PamhyrWindow): parent=parent ) + self._workdir = "" + self._workdir_mage = "" + self.setup_action() self.setup_alarm() self.setup_connections() @@ -226,17 +230,47 @@ class SolverLogWindowAdisTS(PamhyrWindow): self._alarm.timeout.connect(self.update) def setup_workdir(self): - self._workdir = "" + scenario = self._study.status.scenario + srep = scenario.workdir() + if self._study.filename == "": - self._workdir = tempfile.TemporaryDirectory() + self._workdir = os.path.join( + tempfile.TemporaryDirectory(), + srep + ) else: self._workdir = os.path.join( os.path.dirname(self._study.filename), "_PAMHYR_", self._study.name.replace(" ", "_"), self._solver.name.replace(" ", "_"), + srep ) - os.makedirs(self._workdir, exist_ok=True) + + logger.info(f"Create workdir: '{self._workdir}'") + os.makedirs(self._workdir, exist_ok=True) + + self.setup_mage_workdir() + + def setup_mage_workdir(self): + scenario = self._study.status.scenario + srep = scenario.workdir() + + if self._study.filename == "": + self._workdir_mage = os.path.join( + tempfile.TemporaryDirectory(), + srep + ) + else: + self._workdir_mage = os.path.join( + os.path.dirname(self._study.filename), + "_PAMHYR_", + self._study.name.replace(" ", "_"), + self._mage_rep.replace(" ", "_"), + srep + ) + + logger.info(f"Create workdir: '{self._workdir_mage}'") def setup_process(self): self._alarm.start(100) @@ -252,7 +286,8 @@ class SolverLogWindowAdisTS(PamhyrWindow): def export(self): self._log(f" *** Export study {self._solver.name}", color="blue") ok = self._solver.export( - self._study, self._workdir, self._mage_rep, qlog=self._output + self._study, self._workdir, self._workdir_mage, + qlog=self._output ) self.update() @@ -366,7 +401,8 @@ class SolverLogWindowAdisTS(PamhyrWindow): self._log(f" *** Export study {self._solver.name}", color="blue") ok = self._solver.export( - self._study, self._workdir, self._mage_rep, qlog=self._output + self._study, self._workdir, self._workdir_mage, + qlog=self._output ) if not ok: @@ -442,12 +478,7 @@ class SolverLogWindowAdisTS(PamhyrWindow): mage_solver = next(filter( lambda x: x._name == self._mage_rep, self._config.solvers )) - workdir_mage = os.path.join( - os.path.dirname(self._study.filename), - "_PAMHYR_", - self._study.name.replace(" ", "_"), - mage_solver.name.replace(" ", "_"), - ) + workdir_mage = self._workdir_mage def reading_fn(): self._results_mage = mage_solver.results( diff --git a/src/View/SolverParameters/translate.py b/src/View/SolverParameters/translate.py index 2aad4a5f..26c00934 100644 --- a/src/View/SolverParameters/translate.py +++ b/src/View/SolverParameters/translate.py @@ -53,10 +53,10 @@ class ParamTranslate(MainTranslate): self._sub_dict["names"] = { "all_init_time": _translate("SolverParameters", - "Initial time (jj:hh:mm:ss)"), + "Initial time (jjj:hh:mm:ss)"), "all_final_time": _translate("SolverParameters", - "Final time (jj:hh:mm:ss)"), + "Final time (jjj:hh:mm:ss)"), "all_timestep": _translate("SolverParameters", "Timestep (second)"), diff --git a/src/View/WaitingDialog.py b/src/View/WaitingDialog.py index b9ee9947..0cba3072 100644 --- a/src/View/WaitingDialog.py +++ b/src/View/WaitingDialog.py @@ -91,6 +91,7 @@ class WaitingDialog(PamhyrDialog): parent=parent ) self._to_close = False + self.setFixedSize(self.width(), self.height()) self._payload_fn = payload_fn diff --git a/src/View/ui/CustomExportAdisDialog.ui b/src/View/ui/CustomExportAdisDialog.ui index b1c0f419..c2b4a81f 100644 --- a/src/View/ui/CustomExportAdisDialog.ui +++ b/src/View/ui/CustomExportAdisDialog.ui @@ -7,23 +7,13 @@ 0 0 194 - 70 + 126 Dialog - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - @@ -64,6 +54,23 @@ + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + TextLabel + + + diff --git a/src/View/ui/CustomPlotValuesSelectionDialog.ui b/src/View/ui/CustomPlotValuesSelectionDialog.ui index f7daec37..0414519c 100644 --- a/src/View/ui/CustomPlotValuesSelectionDialog.ui +++ b/src/View/ui/CustomPlotValuesSelectionDialog.ui @@ -7,14 +7,24 @@ 0 0 414 - 70 + 132 Dialog - + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + Qt::Horizontal @@ -43,13 +53,10 @@ - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + TextLabel diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui index 36d77a44..68dbe7f6 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui @@ -22,7 +22,7 @@ - Upstream height (m) + Upstream elevation (m) @@ -70,7 +70,7 @@ - Downstream height (m) + Downstream elevation (m) diff --git a/src/config.py b/src/config.py index eb3f3f9b..d94b39ca 100644 --- a/src/config.py +++ b/src/config.py @@ -101,6 +101,7 @@ class Config(SQL): # Add default solver posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", "mage8", f"mage{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -111,7 +112,7 @@ class Config(SQL): '', '', '', '', - '@install_dir/mage/mage{ext} @args @input', + '{path} @args @input', '' ) """) @@ -135,16 +136,19 @@ class Config(SQL): if int(release) < 5: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", "mage8", f"mage{ext}") self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/mage8/mage{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-mage'" ) if int(release) < 6: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", + "adists", f"adists{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -155,19 +159,21 @@ class Config(SQL): '', '', '', '', - '@install_dir/adists/adists{ext} @args @input', + '{path} @args @input', '' ) """) self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/adists/adists{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-AdisTS'" ) if int(release) < 7: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", + "rubar", f"rubarbe{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -178,13 +184,13 @@ class Config(SQL): '', '', '', '', - '@install_dir/rubar/rubar3{ext} @args @input', + '{path} @args @input', '' ) """) self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/rubar/rubar3{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-Rubar3'" ) @@ -350,14 +356,24 @@ class Config(SQL): ctor = solver_type_list["mage8"] new = ctor("default-mage") new._description = "Default Pamhyr2 mage 8 version" - new._cmd_solver = f""""@install_dir/mage8/mage{ext}" @args @input""" + path = os.path.join("@install_dir", "mage8", "mage") + new._cmd_solver = f""""{path}{ext}" @args @input""" self._solvers.append(new) # AdisTS ctor = solver_type_list["adistswc"] new = ctor("default-AdisTS") new._description = "Default Pamhyr2 AdisTS version" - new._cmd_solver = f""""@install_dir/adists/adists{ext}" @args @input""" + path = os.path.join("@install_dir", "adists", "adists") + new._cmd_solver = f""""{path}{ext}" @args @input""" + self._solvers.append(new) + + # Rubar3 + ctor = solver_type_list["rubar3"] + new = ctor("default-Rubar3") + new._description = "Default Pamhyr2 Rubar 3 version" + path = os.path.join("@install_dir", "rubar", f"rubarbe{ext}") + new._cmd_solver = f""""{path}" @args @input""" self._solvers.append(new) # Rubar3 diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 1cfd508f..bce458b1 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -3785,13 +3785,13 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie. - Initial time (jj:hh:mm:ss) - Temps initial (jj:hh:mm:ss) + Initial time (jjj:hh:mm:ss) + Temps initial (jjj:hh:mm:ss) - Final time (jj:hh:mm:ss) - Temps final (jj:hh:mm:ss) + Final time (jjj:hh:mm:ss) + Temps final (jjj:hh:mm:ss)