mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5da607865c | ||
|
|
1f5ce72fb2 | ||
|
|
b6be245764 | ||
|
|
46d0f52b3e | ||
|
|
80f0b181d1 | ||
|
|
9161c54e18 | ||
|
|
64d9e456b7 | ||
|
|
846aaeadf0 | ||
|
|
eaf8d7364a | ||
|
|
54bfff9623 | ||
|
|
237364a4e2 | ||
|
|
c3288b44bb | ||
|
|
70128bbdd1 | ||
|
|
a88a164ead | ||
|
|
d6aeb5df21 | ||
|
|
32db5795da | ||
|
|
a81a641d41 | ||
|
|
53da35d959 | ||
|
|
0f1d1b0797 | ||
|
|
6586a7b191 | ||
|
|
b7aab9f263 | ||
|
|
ef34fecc31 | ||
|
|
e9aa980c88 | ||
|
|
ab495efc65 | ||
|
|
14e13d6624 | ||
|
|
8db099c5c7 | ||
|
|
b503e54210 | ||
|
|
68e4dfb598 | ||
|
|
63dfab6654 | ||
|
|
707577521f | ||
|
|
3458a5df9f | ||
|
|
e19eeedcc3 | ||
|
|
c95de5e606 | ||
|
|
53f7f380ae | ||
|
|
2028d84554 | ||
|
|
da28bae6c5 | ||
|
|
944f99f14b | ||
|
|
8978cc3482 | ||
|
|
f08d4b1a37 | ||
|
|
82b6cbfd72 | ||
|
|
a44dddb51b | ||
|
|
0fdb362313 | ||
|
|
50fa4f7bd8 | ||
|
|
9309aa9d2d | ||
|
|
dd31ffc5a2 | ||
|
|
3d467c9620 | ||
|
|
f95c646ef3 | ||
|
|
c0000bb332 | ||
|
|
252ac257fe | ||
|
|
afe0217655 | ||
|
|
516efb64e8 |
No files matched your search
@@ -137,10 +137,11 @@ class InternalMeshing(AMeshingTool):
|
||||
p.points[k].z = pt1[k].z + \
|
||||
dj*(pt2[k].z -
|
||||
pt1[k].z)
|
||||
p.rk = profiles[i].rk + \
|
||||
dj*(profiles[i+1].rk -
|
||||
profiles[i].rk)
|
||||
p.rk = round(profiles[i].rk +
|
||||
dj*(profiles[i+1].rk -
|
||||
profiles[i].rk), 2)
|
||||
p.name = f'interpol{p.rk}'
|
||||
p.interpolated = True
|
||||
new_profiles2.append(p)
|
||||
new_profiles.append(new_profiles2)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class Data(SQLSubModel):
|
||||
created = True
|
||||
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(int(minor) == 2 and int(release) < 7)):
|
||||
(int(minor) == 2 and int(release) <= 7)):
|
||||
if not cls.is_table_exists(execute,
|
||||
"boundary_condition_data_temperature"):
|
||||
cls._db_create(execute)
|
||||
@@ -205,7 +205,7 @@ class BoundaryConditionTemperature(SQLSubModel):
|
||||
created = False
|
||||
|
||||
if major == "0" and (int(minor) < 2 or (
|
||||
int(minor) == 2 and int(release) < 7)):
|
||||
int(minor) == 2 and int(release) <= 7)):
|
||||
if not cls.is_table_exists(execute,
|
||||
"boundary_condition_temperature"):
|
||||
cls._db_create(execute)
|
||||
|
||||
@@ -275,3 +275,44 @@ class D90AdisTS(SQLSubModel):
|
||||
x.set_as_not_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
split_rk = profile.rk
|
||||
reach_rks = reach.reach.get_rk()
|
||||
if len(reach_rks) == 0:
|
||||
return
|
||||
|
||||
parts = [
|
||||
(reach1.id, min(reach_rks), split_rk),
|
||||
(reach2.id, split_rk, max(reach_rks)),
|
||||
]
|
||||
specifications = [
|
||||
specification
|
||||
for specification in self._data
|
||||
if (not specification.is_deleted() and
|
||||
specification.reach == reach.id)
|
||||
]
|
||||
|
||||
for specification in specifications:
|
||||
begin = specification.start_rk
|
||||
end = specification.end_rk
|
||||
if begin is None or end is None:
|
||||
continue
|
||||
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
end_rk = clipped_lower if reverse else clipped_upper
|
||||
self._data.append(specification.cloned_for(
|
||||
new_reach, start_rk, end_rk
|
||||
))
|
||||
|
||||
if len(specifications) != 0:
|
||||
self.modified()
|
||||
@@ -61,6 +61,10 @@ class D90AdisTSList(PamhyrModelList):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
for d90 in self.lst:
|
||||
d90.split_reach(reach, profile, reach1, reach2)
|
||||
|
||||
@property
|
||||
def D90_AdisTS_List(self):
|
||||
return self.lst
|
||||
@@ -261,3 +261,13 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self.modified()
|
||||
|
||||
def cloned_for(self, reach, start_rk, end_rk):
|
||||
new = D90AdisTSSpec(name=self._name_section, status=self._status)
|
||||
new._reach = reach
|
||||
new._start_rk = start_rk
|
||||
new._end_rk = end_rk
|
||||
new._d90 = self._d90
|
||||
new._enabled = self._enabled
|
||||
new.modified()
|
||||
return new
|
||||
@@ -329,3 +329,44 @@ class DIFAdisTS(SQLSubModel):
|
||||
x.set_as_not_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
split_rk = profile.rk
|
||||
reach_rks = reach.reach.get_rk()
|
||||
if len(reach_rks) == 0:
|
||||
return
|
||||
|
||||
parts = [
|
||||
(reach1.id, min(reach_rks), split_rk),
|
||||
(reach2.id, split_rk, max(reach_rks)),
|
||||
]
|
||||
specifications = [
|
||||
specification
|
||||
for specification in self._data
|
||||
if (not specification.is_deleted() and
|
||||
specification.reach == reach.id)
|
||||
]
|
||||
|
||||
for specification in specifications:
|
||||
begin = specification.start_rk
|
||||
end = specification.end_rk
|
||||
if begin is None or end is None:
|
||||
continue
|
||||
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
end_rk = clipped_lower if reverse else clipped_upper
|
||||
self._data.append(specification.cloned_for(
|
||||
new_reach, start_rk, end_rk
|
||||
))
|
||||
|
||||
if len(specifications) != 0:
|
||||
self.modified()
|
||||
@@ -61,6 +61,10 @@ class DIFAdisTSList(PamhyrModelList):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
for dif in self.lst:
|
||||
dif.split_reach(reach, profile, reach1, reach2)
|
||||
|
||||
@property
|
||||
def DIF_AdisTS_List(self):
|
||||
return self.lst
|
||||
@@ -293,3 +293,15 @@ class DIFAdisTSSpec(SQLSubModel):
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
|
||||
def cloned_for(self, reach, start_rk, end_rk):
|
||||
new = DIFAdisTSSpec(method=self._method, status=self._status)
|
||||
new._reach = reach
|
||||
new._start_rk = start_rk
|
||||
new._end_rk = end_rk
|
||||
new._dif = self._dif
|
||||
new._b = self._b
|
||||
new._c = self._c
|
||||
new._enabled = self._enabled
|
||||
new.modified()
|
||||
return new
|
||||
@@ -109,16 +109,22 @@ class FrictionList(PamhyrModelList):
|
||||
((999 ** 100), -(999 ** 100))
|
||||
)
|
||||
|
||||
frictions = list(
|
||||
filter(
|
||||
lambda f: ((rk_min <= f.begin_rk <= rk_max) or
|
||||
(rk_min <= f.end_rk <= rk_max)),
|
||||
self._lst))
|
||||
for friction in self.lst:
|
||||
lower, upper = sorted((friction.begin_rk, friction.end_rk))
|
||||
clipped_lower = max(lower, rk_min)
|
||||
clipped_upper = min(upper, rk_max)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
for ind, friction in enumerate(frictions):
|
||||
n = friction.cloned_for(reach)
|
||||
if friction.begin_rk > friction.end_rk:
|
||||
n.begin_rk = clipped_upper
|
||||
n.end_rk = clipped_lower
|
||||
else:
|
||||
n.begin_rk = clipped_lower
|
||||
n.end_rk = clipped_upper
|
||||
|
||||
new._lst.insert(ind, n)
|
||||
new._lst.append(n)
|
||||
new._status.modified()
|
||||
|
||||
return new
|
||||
@@ -59,7 +59,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
nb_point: int = 0,
|
||||
code1: int = 0, code2: int = 0,
|
||||
status=None, owner_scenario=-1,
|
||||
enabled=True):
|
||||
enabled=True, interpolated=False):
|
||||
"""ProfileXYZ constructor
|
||||
|
||||
Args:
|
||||
@@ -92,6 +92,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
self._station = []
|
||||
self.station_up_to_date = False
|
||||
self._enabled = bool(enabled)
|
||||
self._interpolated = bool(interpolated)
|
||||
|
||||
self._get_water_limits_cache = {}
|
||||
self._get_water_limits_ac_cache = {}
|
||||
@@ -111,6 +112,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
code2 INTEGER NOT NULL,
|
||||
sl INTEGER,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
interpolated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
|
||||
@@ -155,13 +157,27 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
if major == "0" and int(minor) <=2:
|
||||
if major == "0" and int(minor) <= 2:
|
||||
if int(release) < 9:
|
||||
execute(
|
||||
"ALTER TABLE geometry_profileXYZ " +
|
||||
"ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT TRUE"
|
||||
)
|
||||
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(minor == "2" and int(release) < 10)):
|
||||
if minor != "0":
|
||||
execute(
|
||||
"ALTER TABLE geometry_profileXYZ " +
|
||||
"ADD COLUMN interpolated BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
execute(
|
||||
"UPDATE geometry_profileXYZ " +
|
||||
"SET interpolated = CASE " +
|
||||
"WHEN LOWER(name) LIKE 'interpol%' THEN TRUE " +
|
||||
"ELSE FALSE END"
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
@@ -247,7 +263,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, ind, deleted, name, rk, num, " +
|
||||
"code1, code2, sl, scenario, enabled " +
|
||||
"code1, code2, sl, scenario, enabled, interpolated " +
|
||||
"FROM geometry_profileXYZ " +
|
||||
f"WHERE reach = {reach.id} " +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
@@ -269,6 +285,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
sl = next(it)
|
||||
owner_scenario = next(it)
|
||||
enabled = (next(it) == 1)
|
||||
interpolated = (next(it) == 1)
|
||||
|
||||
profile = cls(
|
||||
id=pid, ind=ind, num=num,
|
||||
@@ -277,7 +294,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
reach=reach,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario,
|
||||
enabled=enabled
|
||||
enabled=enabled,
|
||||
interpolated=interpolated
|
||||
)
|
||||
if deleted:
|
||||
profile.set_as_deleted()
|
||||
@@ -330,12 +348,14 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"geometry_profileXYZ(pamhyr_id, deleted, ind, name, reach, " +
|
||||
"rk, num, code1, code2, sl, scenario, enabled) " +
|
||||
"rk, num, code1, code2, sl, scenario, enabled, interpolated) " +
|
||||
"VALUES (" +
|
||||
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"{ind}, '{self._db_format(self._name)}', " +
|
||||
f"{self.reach.pamhyr_id}, {self.rk}, {self.num}, " +
|
||||
f"{self.code1}, {self.code1}, {sl}, {self._status.scenario_id}, {self._db_format(self._enabled)}" +
|
||||
f"{self.code1}, {self.code1}, {sl}, {self._status.scenario_id}, " +
|
||||
f"{self._db_format(self._enabled)}, " +
|
||||
f"{self._db_format(self._interpolated)}" +
|
||||
")"
|
||||
)
|
||||
|
||||
@@ -403,7 +423,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
rk=self.rk,
|
||||
reach=self.reach,
|
||||
status=self._status,
|
||||
enabled=self.is_enabled
|
||||
enabled=self.is_enabled,
|
||||
interpolated=self.interpolated
|
||||
)
|
||||
if self.is_deleted():
|
||||
new_p.set_as_deleted()
|
||||
@@ -424,7 +445,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
rk=self.rk,
|
||||
reach=new_reach,
|
||||
status=self._status,
|
||||
enabled=self.is_enabled
|
||||
enabled=self.is_enabled,
|
||||
interpolated=self.interpolated
|
||||
)
|
||||
if self.is_deleted():
|
||||
new_p.set_as_deleted()
|
||||
@@ -981,7 +1003,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
Projection of the points of the profile on a plane.
|
||||
"""
|
||||
if self.nb_points < 2:
|
||||
return [0.0]
|
||||
return [0.0] * self.nb_points
|
||||
else:
|
||||
if self.station_up_to_date:
|
||||
return self._station
|
||||
@@ -1146,7 +1168,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
rk=self.rk,
|
||||
reach=self.reach,
|
||||
status=self._status,
|
||||
enabled=self.is_enabled)
|
||||
enabled=self.is_enabled,
|
||||
interpolated=self.interpolated)
|
||||
|
||||
for i, k in enumerate(self.points):
|
||||
p.insert_point(i, k.copy())
|
||||
@@ -1193,3 +1216,12 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
def enabled(self, value):
|
||||
self._enabled = bool(value)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def interpolated(self):
|
||||
return self._interpolated
|
||||
|
||||
@interpolated.setter
|
||||
def interpolated(self, value):
|
||||
self._interpolated = bool(value)
|
||||
self.modified()
|
||||
@@ -80,11 +80,13 @@ class Reach(SQLSubModel):
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data["status"], parent=data["reach"])
|
||||
profile_data = data.copy()
|
||||
profile_data["reach"] = new
|
||||
|
||||
new._profiles = sorted(
|
||||
ProfileXYZ._db_load(
|
||||
execute,
|
||||
data=data.copy()
|
||||
data=profile_data
|
||||
),
|
||||
key=lambda p: p._db_ind
|
||||
)
|
||||
@@ -375,6 +377,28 @@ class Reach(SQLSubModel):
|
||||
)
|
||||
)
|
||||
|
||||
def get_z_min_enabled_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
lambda profile: profile.z_min(),
|
||||
filter(
|
||||
lambda profile: len(profile) > 0 and profile.is_enabled,
|
||||
self.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def get_z_max_enabled_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
lambda profile: profile.z_max(),
|
||||
filter(
|
||||
lambda profile: len(profile) > 0 and profile.is_enabled,
|
||||
self.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def get_rk(self):
|
||||
"""List of profiles rk
|
||||
|
||||
@@ -383,6 +407,17 @@ class Reach(SQLSubModel):
|
||||
"""
|
||||
return [profile.rk for profile in self.profiles]
|
||||
|
||||
def get_rk_enabled_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
lambda profile: profile.rk,
|
||||
filter(
|
||||
lambda profile: len(profile) > 0 and profile.is_enabled,
|
||||
self.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def get_rk_complete_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
|
||||
@@ -47,9 +47,7 @@ class HydraulicStructure(SQLSubModel):
|
||||
|
||||
self._name = name
|
||||
self._input_section = None
|
||||
self._output_section = None
|
||||
self._input_reach = None
|
||||
self._output_reach = None
|
||||
self._enabled = True
|
||||
self._data = []
|
||||
|
||||
@@ -62,17 +60,12 @@ class HydraulicStructure(SQLSubModel):
|
||||
name TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
input_reach INTEGER,
|
||||
output_reach INTEGER,
|
||||
input_section INTEGER,
|
||||
output_section INTEGER,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(input_reach) REFERENCES river_reach(pamhyr_id),
|
||||
FOREIGN KEY(output_reach) REFERENCES river_reach(pamhyr_id),
|
||||
FOREIGN KEY(input_section)
|
||||
REFERENCES geometry_profileXYZ(pamhyr_id),
|
||||
FOREIGN KEY(output_section)
|
||||
REFERENCES geometry_profileXYZ(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
@@ -92,13 +85,12 @@ class HydraulicStructure(SQLSubModel):
|
||||
return True
|
||||
|
||||
if rl < 11:
|
||||
for v in ["input", "output"]:
|
||||
execute(
|
||||
f"""
|
||||
ALTER TABLE hydraulic_structures
|
||||
RENAME COLUMN {v}_kp TO {v}_rk
|
||||
"""
|
||||
)
|
||||
execute(
|
||||
"""
|
||||
ALTER TABLE hydraulic_structures
|
||||
RENAME COLUMN input_kp TO input_rk
|
||||
"""
|
||||
)
|
||||
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
|
||||
@@ -112,8 +104,28 @@ class HydraulicStructure(SQLSubModel):
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(minor == "2" and rl < 10)):
|
||||
cls._db_update_to_0_2_10(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_10(cls, execute):
|
||||
table = "hydraulic_structures"
|
||||
cls._db_create(execute, ext="_tmp")
|
||||
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
"(pamhyr_id, deleted, name, enabled, input_section, " +
|
||||
"input_reach, scenario) " +
|
||||
"SELECT pamhyr_id, deleted, name, enabled, input_section, " +
|
||||
f"input_reach, scenario FROM {table}"
|
||||
)
|
||||
|
||||
execute(f"DROP TABLE {table}")
|
||||
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0(cls, execute, data):
|
||||
table = "hydraulic_structures"
|
||||
@@ -131,11 +143,10 @@ class HydraulicStructure(SQLSubModel):
|
||||
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
"(pamhyr_id, name, enabled, input_section, output_section, " +
|
||||
"input_reach, output_reach, scenario) " +
|
||||
"(pamhyr_id, name, enabled, input_section, " +
|
||||
"input_reach, scenario) " +
|
||||
"SELECT pamhyr_id, name, enabled, " +
|
||||
"input_section, output_section, " +
|
||||
"input_reach, output_reach, scenario " +
|
||||
"input_section, input_reach, scenario " +
|
||||
f"FROM {table}"
|
||||
)
|
||||
|
||||
@@ -145,44 +156,38 @@ class HydraulicStructure(SQLSubModel):
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0_set_reach_pid(cls, execute, table, reachs):
|
||||
els = execute(
|
||||
f"SELECT pamhyr_id, input_reach, output_reach FROM {table}"
|
||||
f"SELECT pamhyr_id, input_reach FROM {table}"
|
||||
)
|
||||
|
||||
for row in els:
|
||||
it = iter(row)
|
||||
pid = next(it)
|
||||
in_reach_id = next(it)
|
||||
out_reach_id = next(it)
|
||||
|
||||
if in_reach_id == -1:
|
||||
return
|
||||
|
||||
if out_reach_id == -1:
|
||||
out_reach_id = in_reach_id
|
||||
|
||||
execute(
|
||||
f"UPDATE {table} " +
|
||||
f"SET input_reach = {reachs[in_reach_id]}, " +
|
||||
f"output_reach = {reachs[out_reach_id]} " +
|
||||
f"SET input_reach = {reachs[in_reach_id]} " +
|
||||
f"WHERE pamhyr_id = {pid}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_1_1(cls, execute, data,
|
||||
origin_version="0.1.0"):
|
||||
for v in ["input", "output"]:
|
||||
execute(
|
||||
"ALTER TABLE hydraulic_structures " +
|
||||
f"ADD COLUMN {v}_section INTEGER"
|
||||
)
|
||||
execute(
|
||||
"ALTER TABLE hydraulic_structures " +
|
||||
"ADD COLUMN input_section INTEGER"
|
||||
)
|
||||
|
||||
cls._db_update_to_0_1_1_assoc_section_from_rk(
|
||||
execute, "hydraulic_structures",
|
||||
reach_column=f"{v}_reach",
|
||||
rk_column=f"{v}_rk",
|
||||
section_column=f"{v}_section",
|
||||
origin_version=origin_version
|
||||
)
|
||||
cls._db_update_to_0_1_1_assoc_section_from_rk(
|
||||
execute, "hydraulic_structures",
|
||||
reach_column="input_reach",
|
||||
rk_column="input_rk",
|
||||
section_column="input_section",
|
||||
origin_version=origin_version
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
@@ -196,8 +201,7 @@ class HydraulicStructure(SQLSubModel):
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, name, enabled, " +
|
||||
"input_section, output_section, " +
|
||||
"input_reach, output_reach, scenario " +
|
||||
"input_section, input_reach, scenario " +
|
||||
"FROM hydraulic_structures " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
@@ -227,12 +231,7 @@ class HydraulicStructure(SQLSubModel):
|
||||
input_section_id = (
|
||||
-1 if input_section_id is None else input_section_id
|
||||
)
|
||||
output_section_id = next(it)
|
||||
output_section_id = (
|
||||
-1 if output_section_id is None else output_section_id
|
||||
)
|
||||
input_reach_id = next(it)
|
||||
output_reach_id = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
hs = cls(
|
||||
@@ -243,27 +242,19 @@ class HydraulicStructure(SQLSubModel):
|
||||
hs.set_as_deleted()
|
||||
|
||||
hs.enabled = enabled
|
||||
hs.input_reach, hs.output_reach = reduce(
|
||||
lambda acc, n: (
|
||||
n if n.pamhyr_id == input_reach_id else acc[0],
|
||||
n if n.pamhyr_id == output_reach_id else acc[1]
|
||||
),
|
||||
hs.input_reach = reduce(
|
||||
lambda acc, n: n if n.pamhyr_id == input_reach_id else acc,
|
||||
data["edges"],
|
||||
[None, None]
|
||||
None
|
||||
)
|
||||
sections = []
|
||||
if hs.input_reach is not None:
|
||||
sections += hs.input_reach.reach.profiles
|
||||
if hs.output_reach is not None:
|
||||
sections += hs.output_reach.reach.profiles
|
||||
|
||||
hs.input_section, hs.output_section = reduce(
|
||||
lambda acc, s: (
|
||||
s if s.pamhyr_id == input_section_id else acc[0],
|
||||
s if s.pamhyr_id == output_section_id else acc[1]
|
||||
),
|
||||
hs.input_section = reduce(
|
||||
lambda acc, s: s if s.pamhyr_id == input_section_id else acc,
|
||||
sections,
|
||||
[None, None]
|
||||
None
|
||||
)
|
||||
|
||||
loaded.add(hs_id)
|
||||
@@ -287,24 +278,15 @@ class HydraulicStructure(SQLSubModel):
|
||||
if self._input_reach is not None:
|
||||
input_reach_id = self._input_reach.pamhyr_id
|
||||
|
||||
output_reach_id = -1
|
||||
if self._output_reach is not None:
|
||||
output_reach_id = self._output_reach.pamhyr_id
|
||||
|
||||
input_section = 'NULL'
|
||||
if self.input_section is not None:
|
||||
input_section = self.input_section.pamhyr_id
|
||||
|
||||
output_section = 'NULL'
|
||||
if self.output_section is not None:
|
||||
output_section = self.output_section.pamhyr_id
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"hydraulic_structures(" +
|
||||
" pamhyr_id, deleted, name, enabled, " +
|
||||
" input_section, output_section, " +
|
||||
" input_reach, output_reach, " +
|
||||
" input_section, input_reach, " +
|
||||
" scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
@@ -312,8 +294,7 @@ class HydraulicStructure(SQLSubModel):
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{self._db_format(self.enabled)}, " +
|
||||
f"{input_section}, {output_section}, " +
|
||||
f"{input_reach_id}, {output_reach_id}, " +
|
||||
f"{input_section}, {input_reach_id}, " +
|
||||
f"{self._status.scenario_id}"
|
||||
")"
|
||||
)
|
||||
@@ -371,13 +352,6 @@ class HydraulicStructure(SQLSubModel):
|
||||
|
||||
return self._input_section
|
||||
|
||||
@property
|
||||
def output_rk(self):
|
||||
if self._output_section is None:
|
||||
return None
|
||||
|
||||
return self._output_section
|
||||
|
||||
@property
|
||||
def input_section(self):
|
||||
return self._input_section
|
||||
@@ -387,15 +361,6 @@ class HydraulicStructure(SQLSubModel):
|
||||
self._input_section = input_section
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def output_section(self):
|
||||
return self._output_section
|
||||
|
||||
@output_section.setter
|
||||
def output_section(self, output_section):
|
||||
self._output_section = output_section
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
@@ -414,27 +379,15 @@ class HydraulicStructure(SQLSubModel):
|
||||
self._input_reach = input_reach
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def output_reach(self):
|
||||
return self._output_reach
|
||||
|
||||
@output_reach.setter
|
||||
def output_reach(self, output_reach):
|
||||
self._output_reach = output_reach
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def basic_structures(self):
|
||||
return self.lst.copy()
|
||||
|
||||
def cloned_for(self, input_reach, input_section,
|
||||
output_reach, output_section):
|
||||
def cloned_for(self, input_reach, input_section):
|
||||
new = HydraulicStructure(name=self._name, status=self._status)
|
||||
new._enabled = self._enabled
|
||||
new._input_reach = input_reach
|
||||
new._input_section = input_section
|
||||
new._output_reach = output_reach
|
||||
new._output_section = output_section
|
||||
new._data = [structure.cloned() for structure in self.lst]
|
||||
new.modified()
|
||||
return new
|
||||
|
||||
@@ -87,8 +87,7 @@ class HydraulicStructureList(PamhyrModelList):
|
||||
structures = [
|
||||
structure
|
||||
for structure in self.lst
|
||||
if (structure.input_reach is reach or
|
||||
structure.output_reach is reach)
|
||||
if structure.input_reach is reach
|
||||
]
|
||||
|
||||
for structure in structures:
|
||||
@@ -96,15 +95,9 @@ class HydraulicStructureList(PamhyrModelList):
|
||||
structure.input_reach,
|
||||
structure.input_section
|
||||
)
|
||||
output_reach, output_section = split_endpoint(
|
||||
structure.output_reach,
|
||||
structure.output_section
|
||||
)
|
||||
self._lst.append(structure.cloned_for(
|
||||
input_reach,
|
||||
input_section,
|
||||
output_reach,
|
||||
output_section
|
||||
input_section
|
||||
))
|
||||
|
||||
if len(structures) != 0:
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import logging
|
||||
|
||||
from copy import copy, deepcopy
|
||||
from math import isclose
|
||||
from tools import trace, timer
|
||||
from functools import reduce
|
||||
from numpy import interp
|
||||
@@ -439,15 +440,28 @@ class InitialConditions(SQLSubModel):
|
||||
|
||||
def new_from_data(self, rk, discharge, elevation):
|
||||
n = Data(reach=self._reach, status=self._status)
|
||||
# Values read from Mage INI files are strings, whereas profile RK
|
||||
# values are stored as floats. Normalize the imported value before
|
||||
# looking up its associated profile.
|
||||
rk = float(rk)
|
||||
|
||||
section = reduce(
|
||||
lambda acc, s: (
|
||||
s if s.rk == rk else acc
|
||||
# Mage INI files store RK values with two decimal places.
|
||||
# Allow for the rounding introduced by that format.
|
||||
s if isclose(s.rk, rk, rel_tol=0.0, abs_tol=0.0051)
|
||||
else acc
|
||||
),
|
||||
self._reach.reach.profiles,
|
||||
None
|
||||
)
|
||||
|
||||
if section is None:
|
||||
raise ValueError(
|
||||
f"No profile with RK {rk} exists in reach "
|
||||
f"{self._reach.name}"
|
||||
)
|
||||
|
||||
n['section'] = section
|
||||
n['discharge'] = discharge
|
||||
n['elevation'] = elevation
|
||||
|
||||
@@ -391,7 +391,7 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower > clipped_upper:
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
|
||||
@@ -192,7 +192,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
new_spec.rate = rate
|
||||
new_spec.enabled = enabled
|
||||
|
||||
# loaded.add(pid)
|
||||
loaded.add(id)
|
||||
new.append(new_spec)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
@@ -237,6 +237,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def name(self, name):
|
||||
self._name_section = name
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
@@ -246,6 +247,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
@@ -255,6 +257,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def end_rk(self):
|
||||
@@ -264,6 +267,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def concentration(self):
|
||||
@@ -273,6 +277,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def concentration(self, concentration):
|
||||
self._concentration = concentration
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def eg(self):
|
||||
@@ -282,6 +287,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def eg(self, eg):
|
||||
self._eg = eg
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def em(self):
|
||||
@@ -291,6 +297,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def em(self, em):
|
||||
self._em = em
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def ed(self):
|
||||
@@ -300,6 +307,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def ed(self, ed):
|
||||
self._ed = ed
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def rate(self):
|
||||
@@ -309,6 +317,7 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
def rate(self, rate):
|
||||
self._rate = rate
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
|
||||
@@ -68,7 +68,7 @@ class InitialConditionsTemperature(SQLSubModel):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0":
|
||||
if int(minor) < 2 or (int(minor) == 2 and int(release) < 7):
|
||||
if int(minor) < 2 or (int(minor) == 2 and int(release) <= 7):
|
||||
table_name = "initial_conditions_temperature"
|
||||
if not cls.is_table_exists(execute, table_name):
|
||||
cls._db_create(execute)
|
||||
@@ -167,7 +167,11 @@ class InitialConditionsTemperature(SQLSubModel):
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
return len(self.data)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return list(filter(lambda item: not item.is_deleted(), self._data))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -254,7 +258,7 @@ class InitialConditionsTemperature(SQLSubModel):
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower > clipped_upper:
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
|
||||
@@ -31,10 +31,11 @@ class ICTemperatureSpec(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None, owner_scenario=None):
|
||||
super(ICTemperatureSpec, self).__init__()
|
||||
|
||||
self._status = status
|
||||
status=None, owner_scenario=-1):
|
||||
super(ICTemperatureSpec, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name_section = name
|
||||
self._reach = None
|
||||
@@ -69,7 +70,7 @@ class ICTemperatureSpec(SQLSubModel):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0":
|
||||
if int(minor) < 2 or (int(minor) == 2 and int(release) < 7):
|
||||
if int(minor) < 2 or (int(minor) == 2 and int(release) <= 7):
|
||||
table_name = "initial_conditions_temperature_spec"
|
||||
if not cls.is_table_exists(execute, table_name):
|
||||
cls._db_create(execute)
|
||||
@@ -122,7 +123,7 @@ class ICTemperatureSpec(SQLSubModel):
|
||||
new_spec.end_rk = end_rk
|
||||
new_spec.temperature = temperature
|
||||
|
||||
# loaded.add(pid)
|
||||
loaded.add(id)
|
||||
new.append(new_spec)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
@@ -163,7 +164,7 @@ class ICTemperatureSpec(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name_section = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
@@ -172,7 +173,7 @@ class ICTemperatureSpec(SQLSubModel):
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
@@ -181,7 +182,7 @@ class ICTemperatureSpec(SQLSubModel):
|
||||
@start_rk.setter
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def end_rk(self):
|
||||
@@ -190,7 +191,7 @@ class ICTemperatureSpec(SQLSubModel):
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
|
||||
@@ -117,7 +117,7 @@ class LateralContributionList(PamhyrModelListWithTab):
|
||||
for new_reach, part_lower, part_upper, sections in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower > clipped_upper:
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
begin_index = (
|
||||
|
||||
@@ -95,7 +95,7 @@ class LateralContributionsAdisTSList(PamhyrModelList):
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower > clipped_upper:
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
begin_rk = clipped_upper if reverse else clipped_lower
|
||||
|
||||
@@ -202,13 +202,8 @@ class TableData(SQLSubModel):
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
create = False
|
||||
|
||||
if major == "0" and int(minor) == 2:
|
||||
if int(release) < 7 and not create:
|
||||
cls._db_create(execute)
|
||||
create = True
|
||||
if not cls.is_table_exists(execute, "results_table_data"):
|
||||
cls._db_create(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@@ -527,7 +522,7 @@ class Results(SQLSubModel):
|
||||
i = 0
|
||||
for reach in new_results._river.reachs:
|
||||
nb = len(reach.profiles)
|
||||
reach.set_global_index(range(i, i + nb + 1))
|
||||
reach.set_global_index(range(i, i + nb))
|
||||
i += nb
|
||||
|
||||
if "Z" in table_data:
|
||||
|
||||
@@ -154,6 +154,7 @@ class Profile(SQLSubModel):
|
||||
"len_data, data, scenario " +
|
||||
"FROM results_data " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND result = {data['result_pid']} " +
|
||||
f"AND reach = {reach.pamhyr_id} " +
|
||||
f"AND section = {profile.pamhyr_id}"
|
||||
)
|
||||
|
||||
@@ -64,13 +64,15 @@ class Profile(object):
|
||||
|
||||
|
||||
class Reach(object):
|
||||
def __init__(self, reach, study):
|
||||
def __init__(self, reach, study, profiles=None):
|
||||
self._study = study
|
||||
self._reach = reach # Source reach in the study
|
||||
if profiles is None:
|
||||
profiles = reach.profiles
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study),
|
||||
reach.profiles
|
||||
profiles
|
||||
)
|
||||
)
|
||||
|
||||
@@ -89,6 +91,10 @@ class Reach(object):
|
||||
def profiles(self):
|
||||
return self._profiles.copy()
|
||||
|
||||
def get_rk(self):
|
||||
"""Return the PK values for profiles present in the results."""
|
||||
return [profile.rk for profile in self._profiles]
|
||||
|
||||
def profile(self, id):
|
||||
return self._profiles[id]
|
||||
|
||||
@@ -119,10 +125,10 @@ class River(object):
|
||||
def has_reach(self, id):
|
||||
return 0 <= id < len(self._reachs)
|
||||
|
||||
def add(self, reach_id):
|
||||
def add(self, reach_id, profiles=None):
|
||||
reachs = self._study.river.enable_edges()
|
||||
|
||||
new = Reach(reachs[reach_id].reach, self._study)
|
||||
new = Reach(reachs[reach_id].reach, self._study, profiles=profiles)
|
||||
|
||||
self._reachs.append(new)
|
||||
return new
|
||||
|
||||
@@ -907,6 +907,55 @@ Last export at: @date."""
|
||||
def weather_parameters(self):
|
||||
return self._WeatherParameters
|
||||
|
||||
def profile_dependencies(self, profile):
|
||||
"""Return model data which directly reference *profile*.
|
||||
|
||||
PK-based intervals are deliberately not included: only explicit
|
||||
references to the profile object prevent it from being disabled.
|
||||
"""
|
||||
dependencies = []
|
||||
|
||||
def add(kind, item, association):
|
||||
dependencies.append({
|
||||
"kind": kind,
|
||||
"item": item,
|
||||
"association": association,
|
||||
})
|
||||
|
||||
for structure in self._hydraulic_structures.lst:
|
||||
if structure.input_section is profile:
|
||||
add("hydraulic_structure", structure, "")
|
||||
|
||||
for tab in self._lateral_contribution._tabs_list:
|
||||
for contribution in self._lateral_contribution.get_tab(tab):
|
||||
if contribution.begin_section is profile:
|
||||
add("lateral_contribution", contribution, "begin_section")
|
||||
if contribution.end_section is profile:
|
||||
add("lateral_contribution", contribution, "end_section")
|
||||
|
||||
for parameter in self._WeatherParameters.lst:
|
||||
if parameter.begin_section is profile:
|
||||
add("weather_parameter", parameter, "begin_section")
|
||||
if parameter.end_section is profile:
|
||||
add("weather_parameter", parameter, "end_section")
|
||||
|
||||
reach_id = profile.reach.pamhyr_id
|
||||
for initial_condition in self._InitialConditionsTemperature.lst:
|
||||
for specification in initial_condition.data:
|
||||
same_reach = specification.reach == reach_id
|
||||
same_rk = (
|
||||
specification.start_rk == profile.rk or
|
||||
specification.end_rk == profile.rk
|
||||
)
|
||||
if same_reach and same_rk:
|
||||
add(
|
||||
"initial_condition_temperature",
|
||||
specification,
|
||||
"cross_section_pk",
|
||||
)
|
||||
|
||||
return dependencies
|
||||
|
||||
def get_params(self, solver):
|
||||
if solver in self._parameters:
|
||||
return self._parameters[solver]
|
||||
@@ -963,6 +1012,12 @@ Last export at: @date."""
|
||||
self._results[solv_type] = results
|
||||
|
||||
def _split_reach(self, reach, profile):
|
||||
profiles = reach.reach.profiles
|
||||
if not profiles or profile not in profiles[1:-1]:
|
||||
raise ValueError(
|
||||
"A reach cannot be split at its first or last profile"
|
||||
)
|
||||
|
||||
node1 = reach.node1
|
||||
node2 = reach.node2
|
||||
|
||||
@@ -1000,5 +1055,11 @@ Last export at: @date."""
|
||||
self._WeatherParameters.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._D90AdisTS.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._DIFAdisTS.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
|
||||
return r1, r2
|
||||
+10
-3
@@ -46,7 +46,7 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
class Study(SQLModel):
|
||||
_version = "0.2.9"
|
||||
_version = "0.2.10"
|
||||
|
||||
_sub_classes = [
|
||||
Scenario,
|
||||
@@ -290,7 +290,14 @@ class Study(SQLModel):
|
||||
if major == "0" and int(minor) < 2:
|
||||
self._add_into_info_if_not_exists('current_scenario', '0')
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
needs_foreign_keys_disabled = (
|
||||
major == "0" and (
|
||||
int(minor) < 2 or
|
||||
(minor == "2" and int(release) < 10)
|
||||
)
|
||||
)
|
||||
|
||||
if needs_foreign_keys_disabled:
|
||||
# Need to temporary disable the sqlite foreign keys
|
||||
# checking to update db dans change the table id fk to
|
||||
# table pamhyr_id fk
|
||||
@@ -300,7 +307,7 @@ class Study(SQLModel):
|
||||
|
||||
ok = self._update_submodel(version[0], data={})
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if needs_foreign_keys_disabled:
|
||||
# Reactivate foreign keys checking
|
||||
self.execute(
|
||||
"PRAGMA foreign_keys = ON;"
|
||||
|
||||
@@ -73,7 +73,7 @@ class Data(SQLSubModel):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(int(minor) == 2 and int(release) < 7)):
|
||||
(int(minor) == 2 and int(release) <= 7)):
|
||||
if not cls.is_table_exists(execute, "air_temperature_data"):
|
||||
cls._db_create(execute)
|
||||
|
||||
@@ -341,7 +341,7 @@ class WeatherParameters(SQLSubModel):
|
||||
created = False
|
||||
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(int(minor) == 2 and int(release) < 7)):
|
||||
(int(minor) == 2 and int(release) <= 7)):
|
||||
if not cls.is_table_exists(execute, "air_temperature"):
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
@@ -652,14 +652,38 @@ class WeatherParameters(SQLSubModel):
|
||||
|
||||
def add(self, index: int):
|
||||
value = Data(self._default_0, self._default_1, status=self._status)
|
||||
self._data.insert(index, value)
|
||||
self.modified()
|
||||
self.insert(index, value)
|
||||
return value
|
||||
|
||||
def insert(self, index: int, value):
|
||||
visible_data = self.data
|
||||
if index < len(visible_data):
|
||||
index = self._data.index(visible_data[index])
|
||||
else:
|
||||
index = len(self._data)
|
||||
self._data.insert(index, value)
|
||||
self.modified()
|
||||
|
||||
def set_data_value(self, value, column, data):
|
||||
value[column] = self._types[column](data)
|
||||
self.modified()
|
||||
|
||||
def set_deleted(self, values, deleted):
|
||||
for value in values:
|
||||
if deleted:
|
||||
value.set_as_deleted()
|
||||
else:
|
||||
value.set_as_not_deleted()
|
||||
self.modified()
|
||||
|
||||
def reorder(self, values):
|
||||
values = iter(values)
|
||||
self._data = [
|
||||
value if value.is_deleted() else next(values)
|
||||
for value in self._data
|
||||
]
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self._data = list(
|
||||
map(
|
||||
@@ -708,10 +732,7 @@ class WeatherParameters(SQLSubModel):
|
||||
return lst
|
||||
|
||||
def _set_i_c_v(self, index, column, value):
|
||||
v = self._data[index]
|
||||
v[column] = self._types[column](value)
|
||||
self._data[index] = v
|
||||
self.modified()
|
||||
self.set_data_value(self.get_i(index), column, value)
|
||||
|
||||
def set_i_0(self, index: int, value):
|
||||
self._set_i_c_v(index, 0, value)
|
||||
@@ -742,15 +763,13 @@ class WeatherParameters(SQLSubModel):
|
||||
return new
|
||||
|
||||
def move_up(self, index):
|
||||
if index < len(self):
|
||||
next = index - 1
|
||||
d = self._data
|
||||
d[index], d[next] = d[next], d[index]
|
||||
self.modified()
|
||||
if 0 < index < len(self):
|
||||
data = self.data
|
||||
data[index - 1], data[index] = data[index], data[index - 1]
|
||||
self.reorder(data)
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
d = self._data
|
||||
d[index], d[prev] = d[prev], d[index]
|
||||
self.modified()
|
||||
if 0 <= index < len(self) - 1:
|
||||
data = self.data
|
||||
data[index], data[index + 1] = data[index + 1], data[index]
|
||||
self.reorder(data)
|
||||
@@ -204,7 +204,7 @@ class WeatherParametersList(PamhyrModelList):
|
||||
for new_reach, part_lower, part_upper, sections in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower > clipped_upper:
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
begin_index = clipped_upper if reverse else clipped_lower
|
||||
|
||||
@@ -408,6 +408,41 @@ class AdisTS(CommandLineSolver):
|
||||
# RESULTS #
|
||||
###########
|
||||
|
||||
@staticmethod
|
||||
def _remove_temperature_results(repertory):
|
||||
"""Remove temperature outputs generated but unused by AdisTS."""
|
||||
results_directory = os.path.join(repertory, "resultats")
|
||||
if not os.path.isdir(results_directory):
|
||||
results_directory = repertory
|
||||
|
||||
if not os.path.isdir(results_directory):
|
||||
return
|
||||
|
||||
for filename in os.listdir(results_directory):
|
||||
if not (filename.startswith("TEM.") or
|
||||
filename.startswith("TEM_")):
|
||||
continue
|
||||
|
||||
filepath = os.path.join(results_directory, filename)
|
||||
if os.path.isfile(filepath):
|
||||
try:
|
||||
os.remove(filepath)
|
||||
logger.debug(
|
||||
f"Removed unused AdisTS result '{filepath}'"
|
||||
)
|
||||
except OSError as error:
|
||||
logger.warning(
|
||||
f"Unable to remove AdisTS result '{filepath}': "
|
||||
f"{error}"
|
||||
)
|
||||
|
||||
def run_output_data_fomater(self, study):
|
||||
# AdisTS generates temperature results that are not used by the solver,
|
||||
# but they are still present in the output directory.
|
||||
# Remove them to avoid confusion.
|
||||
self._remove_temperature_results(self._process.workingDirectory())
|
||||
return super(AdisTS, self).run_output_data_fomater(study)
|
||||
|
||||
def read_bin(self, study, repertory, results, qlog=None, name="0"):
|
||||
return
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from datetime import date, time, datetime, timedelta
|
||||
from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp
|
||||
old_pamhyr_date_to_timestamp, format_datetime
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -97,7 +97,9 @@ class TableModel(PamhyrTableModel):
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
value = format_datetime(
|
||||
self._start_date + timedelta(seconds=float(v))
|
||||
)
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
@@ -114,6 +116,10 @@ class TableModel(PamhyrTableModel):
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if (self._data.header[column] == "time"
|
||||
and self._opt_data == "date"):
|
||||
value = self.date_to_elapsed_seconds(value)
|
||||
|
||||
row = self.get_true_data_row(row)
|
||||
|
||||
self._undo.push(
|
||||
|
||||
@@ -173,7 +173,8 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
opt_data=self._study.time_system
|
||||
opt_data=self._study.time_system,
|
||||
start_date=self._study.date
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
|
||||
@@ -24,7 +24,7 @@ from datetime import date, time, datetime, timedelta
|
||||
from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp
|
||||
old_pamhyr_date_to_timestamp, format_datetime
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -87,7 +87,9 @@ class TableModel(PamhyrTableModel):
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
value = format_datetime(
|
||||
self._start_date + timedelta(seconds=float(v))
|
||||
)
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
@@ -104,6 +106,10 @@ class TableModel(PamhyrTableModel):
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if (self._data.header[column] == "time"
|
||||
and self._opt_data == "date"):
|
||||
value = self.date_to_elapsed_seconds(value)
|
||||
|
||||
row = self.get_true_data_row(row)
|
||||
|
||||
self._undo.push(
|
||||
|
||||
@@ -64,14 +64,22 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
name = trad[self._pamhyr_name]
|
||||
|
||||
if self._data is not None:
|
||||
pollutant_name = next(
|
||||
(
|
||||
pollutant.name
|
||||
for pollutant in study.river.Pollutants.Pollutants_List
|
||||
if pollutant.id == self._data.pollutant
|
||||
),
|
||||
None
|
||||
)
|
||||
n = self._data.node
|
||||
node_name = next(filter(
|
||||
lambda x: x.id == n, study.river._nodes
|
||||
)).name
|
||||
name += (
|
||||
f" - {study.name} " +
|
||||
f"({node_name})"
|
||||
)
|
||||
name += f" - {study.name}"
|
||||
if pollutant_name is not None:
|
||||
name += f" - {pollutant_name}"
|
||||
name += f" ({node_name})"
|
||||
|
||||
super(EditBoundaryConditionWindow, self).__init__(
|
||||
title=name,
|
||||
@@ -120,7 +128,8 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
opt_data=self._study.time_system
|
||||
opt_data=self._study.time_system,
|
||||
start_date=self._study.date
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
|
||||
@@ -24,7 +24,7 @@ from datetime import date, time, datetime, timedelta
|
||||
from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp
|
||||
old_pamhyr_date_to_timestamp, format_datetime
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -87,7 +87,9 @@ class TableModel(PamhyrTableModel):
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
value = format_datetime(
|
||||
self._start_date + timedelta(seconds=float(v))
|
||||
)
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
@@ -104,6 +106,10 @@ class TableModel(PamhyrTableModel):
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if (self._data.header[column] == "time"
|
||||
and self._opt_data == "date"):
|
||||
value = self.date_to_elapsed_seconds(value)
|
||||
|
||||
row = self.get_true_data_row(row)
|
||||
|
||||
self._undo.push(
|
||||
|
||||
@@ -117,7 +117,8 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
opt_data=self._study.time_system
|
||||
opt_data=self._study.time_system,
|
||||
start_date=self._study.date
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
|
||||
@@ -123,9 +123,16 @@ class D90TableModel(PamhyrTableModel):
|
||||
self._undo.indexChanged.connect(lambda _: self.update())
|
||||
|
||||
def _setup_lst(self):
|
||||
enabled_reaches = {
|
||||
reach.id for reach in self._river.enable_edges()
|
||||
}
|
||||
self._lst = list(
|
||||
filter(
|
||||
lambda d90: d90._deleted is False,
|
||||
lambda d90: (
|
||||
d90._deleted is False
|
||||
and (d90.reach in (None, -1)
|
||||
or d90.reach in enabled_reaches)
|
||||
),
|
||||
self._data._data
|
||||
)
|
||||
)
|
||||
|
||||
@@ -125,9 +125,16 @@ class DIFTableModel(PamhyrTableModel):
|
||||
self._undo.indexChanged.connect(lambda _: self.update())
|
||||
|
||||
def _setup_lst(self):
|
||||
enabled_reaches = {
|
||||
reach.id for reach in self._river.enable_edges()
|
||||
}
|
||||
self._lst = list(
|
||||
filter(
|
||||
lambda dif: dif._deleted is False,
|
||||
lambda dif: (
|
||||
dif._deleted is False
|
||||
and (dif.reach in (None, -1)
|
||||
or dif.reach in enabled_reaches)
|
||||
),
|
||||
self._data._data
|
||||
)
|
||||
)
|
||||
|
||||
@@ -171,7 +171,7 @@ class ProfileWindow(PamhyrWindow):
|
||||
|
||||
def profiles(self):
|
||||
"""Return profiles from the geometry model owning this profile."""
|
||||
return self._profile.reach.reach.profiles
|
||||
return self._profile.reach.profiles
|
||||
|
||||
def navigate_profile(self, offset):
|
||||
profiles = self.profiles()
|
||||
@@ -204,7 +204,6 @@ class ProfileWindow(PamhyrWindow):
|
||||
|
||||
self._tablemodel.beginResetModel()
|
||||
self._tablemodel._data = profile
|
||||
# update lst to display correct data and apply connexions to add/remove points
|
||||
self._tablemodel._setup_lst()
|
||||
self._tablemodel.endResetModel()
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from View.Translate import MainTranslate
|
||||
from View.WeatherParameters.translate import WeatherParametersTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
@@ -47,6 +48,57 @@ class GeometryTranslate(MainTranslate):
|
||||
self._dict["cross_section_enabled_count"] = _translate(
|
||||
"Geometry", "Cross-section enabled ({count} disabled)"
|
||||
)
|
||||
self._dict["profile_disable_blocked"] = _translate(
|
||||
"Geometry", "The cross-section cannot be disabled."
|
||||
)
|
||||
self._dict["profile_disable_blocked_info"] = _translate(
|
||||
"Geometry",
|
||||
"It is referenced by the following data:\n{dependencies}\n\n"
|
||||
"Change or remove these associations before disabling it."
|
||||
)
|
||||
self._dict["profile_dependency"] = _translate(
|
||||
"Geometry", "{kind} “{name}” ({association})"
|
||||
)
|
||||
self._dict["initial_condition"] = _translate(
|
||||
"Geometry", "Initial condition"
|
||||
)
|
||||
self._dict["initial_condition_temperature"] = _translate(
|
||||
"Geometry", "Initial condition temperature"
|
||||
)
|
||||
self._dict["hydraulic_structure"] = _translate(
|
||||
"Geometry", "Hydraulic structure"
|
||||
)
|
||||
self._dict["lateral_contribution"] = _translate(
|
||||
"Geometry", "Lateral contribution"
|
||||
)
|
||||
self._dict["weather_parameter"] = _translate(
|
||||
"Geometry", "Weather parameter"
|
||||
)
|
||||
self._dict["section"] = _translate("Geometry", "cross-section")
|
||||
self._dict["begin_section"] = _translate(
|
||||
"Geometry", "beginning cross-section"
|
||||
)
|
||||
self._dict["end_section"] = _translate(
|
||||
"Geometry", "end cross-section"
|
||||
)
|
||||
self._dict["cross_section_pk"] = _translate(
|
||||
"Geometry", "cross-section PK"
|
||||
)
|
||||
|
||||
weather_parameters = WeatherParametersTranslate().get_dict(
|
||||
"weather_parameters"
|
||||
)
|
||||
self._sub_dict["weather_parameter_types"] = {
|
||||
"AT": weather_parameters["air_temperature"],
|
||||
"SH": weather_parameters["specific_humidity"],
|
||||
"GR": weather_parameters["global_radiation"],
|
||||
"RWS": weather_parameters["reference_wind_speed"],
|
||||
"GT": weather_parameters["surface_temperature"],
|
||||
"GFR": weather_parameters["surface_flow_rate"],
|
||||
"ALB": weather_parameters["albedo"],
|
||||
"SC": weather_parameters["shading_coefficient"],
|
||||
"CCF": weather_parameters["cloud_cover_fraction"],
|
||||
}
|
||||
|
||||
self._dict["transverse_abscissa"] = _translate(
|
||||
"Geometry", "Transverse abscissa (m)"
|
||||
@@ -104,10 +156,10 @@ class GeometryTranslate(MainTranslate):
|
||||
"Geometry", "Export options"
|
||||
)
|
||||
self._dict["export_options_text"] = _translate(
|
||||
"Geometry", "Choose which cross-sections to export."
|
||||
"Geometry", "Exportation of the cross-sections."
|
||||
)
|
||||
self._dict["export_interpolated_profiles"] = _translate(
|
||||
"Geometry", "Export interpolated cross-sections"
|
||||
"Geometry", "Export with interpolated cross-sections"
|
||||
)
|
||||
self._dict["update_rk_error"] = _translate(
|
||||
"Geometry",
|
||||
|
||||
+69
-11
@@ -103,6 +103,7 @@ class GeometryWindow(PamhyrWindow):
|
||||
self.setup_plots()
|
||||
self.setup_statusbar()
|
||||
self.setup_connections()
|
||||
self.update_meshing_action()
|
||||
|
||||
def setup_table(self):
|
||||
if self._study.is_read_only():
|
||||
@@ -239,7 +240,16 @@ class GeometryWindow(PamhyrWindow):
|
||||
def update_redraw(self):
|
||||
self._update(redraw=True)
|
||||
|
||||
def update_meshing_action(self):
|
||||
enabled = (
|
||||
not self._study.is_read_only()
|
||||
and self._reach.number_profiles > 0
|
||||
)
|
||||
self.find(QAction, "action_meshing").setEnabled(enabled)
|
||||
|
||||
def _update(self, redraw=False, propagate=True):
|
||||
self.update_meshing_action()
|
||||
|
||||
if redraw:
|
||||
self._plot_xy.redraw(data=self._reach)
|
||||
self._plot_rkc.redraw(data=self._reach)
|
||||
@@ -311,6 +321,9 @@ class GeometryWindow(PamhyrWindow):
|
||||
self.tableView.model().blockSignals(False)
|
||||
|
||||
def edit_meshing(self):
|
||||
if self._reach.number_profiles == 0:
|
||||
return
|
||||
|
||||
rows = list(
|
||||
set(
|
||||
(i.row() for i in self.tableView.selectedIndexes())
|
||||
@@ -544,7 +557,8 @@ class GeometryWindow(PamhyrWindow):
|
||||
def _set_checkbox_state(self):
|
||||
rows = self.index_selected_rows()
|
||||
editable = not self._study.is_read_only()
|
||||
disabled_count = self._reach.number_profiles - self._reach.number_enabled_profiles
|
||||
disabled_count = (self._reach.number_profiles -
|
||||
self._reach.number_enabled_profiles)
|
||||
self._checkbox.setText(
|
||||
self._trad["cross_section_enabled_count"].format(
|
||||
count=disabled_count
|
||||
@@ -573,6 +587,49 @@ class GeometryWindow(PamhyrWindow):
|
||||
if len(rows) == 0:
|
||||
return
|
||||
|
||||
if not enabled:
|
||||
blocked = []
|
||||
for row in rows:
|
||||
profile = self._reach.profile(row)
|
||||
dependencies = self._study.river.profile_dependencies(profile)
|
||||
if len(dependencies) != 0:
|
||||
blocked.append((profile, dependencies))
|
||||
|
||||
if len(blocked) != 0:
|
||||
details = []
|
||||
for profile, dependencies in blocked:
|
||||
details.append(f"{profile.name}:")
|
||||
for dependency in dependencies:
|
||||
item = dependency["item"]
|
||||
if dependency["kind"] == "weather_parameter":
|
||||
name = self._trad.get_dict(
|
||||
"weather_parameter_types"
|
||||
).get(item.type, item.type)
|
||||
else:
|
||||
name = getattr(item, "name", "")
|
||||
if name == "":
|
||||
name = self._trad[dependency["kind"]]
|
||||
details.append(
|
||||
" - " +
|
||||
self._trad["profile_dependency"].format(
|
||||
kind=self._trad[dependency["kind"]],
|
||||
name=name,
|
||||
association=self._trad[
|
||||
dependency["association"]
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
self._set_checkbox_state()
|
||||
self.message_box(
|
||||
self._trad["profile_disable_blocked"],
|
||||
self._trad["profile_disable_blocked_info"].format(
|
||||
dependencies="\n".join(details)
|
||||
),
|
||||
window_title=self._trad["warning"],
|
||||
)
|
||||
return
|
||||
|
||||
self._checkbox.setTristate(False)
|
||||
self._table.enabled(rows, enabled)
|
||||
|
||||
@@ -761,6 +818,10 @@ class GeometryWindow(PamhyrWindow):
|
||||
# self.update_plot_rkc()
|
||||
|
||||
def export_to_file(self):
|
||||
export_interpolated = self._ask_export_options()
|
||||
if export_interpolated is None:
|
||||
return
|
||||
|
||||
settings = QSettings(
|
||||
QSettings.IniFormat,
|
||||
QSettings.UserScope, 'MyOrg'
|
||||
@@ -790,11 +851,9 @@ class GeometryWindow(PamhyrWindow):
|
||||
suffix = Path(filename).suffix
|
||||
if suffix != "":
|
||||
if suffix == ".st" or suffix == ".ST":
|
||||
export_interpolated = self._ask_export_options()
|
||||
if export_interpolated is not None:
|
||||
self._export_to_file_st(
|
||||
filename[:-3], export_interpolated
|
||||
)
|
||||
self._export_to_file_st(
|
||||
filename[:-3], export_interpolated
|
||||
)
|
||||
else:
|
||||
# Warning popup when the format is not exportable
|
||||
win = QtWidgets.QMessageBox()
|
||||
@@ -805,9 +864,7 @@ class GeometryWindow(PamhyrWindow):
|
||||
|
||||
pass
|
||||
else:
|
||||
export_interpolated = self._ask_export_options()
|
||||
if export_interpolated is not None:
|
||||
self._export_to_file_st(filename, export_interpolated)
|
||||
self._export_to_file_st(filename, export_interpolated)
|
||||
|
||||
def _ask_export_options(self):
|
||||
win = QtWidgets.QMessageBox(self)
|
||||
@@ -844,8 +901,9 @@ class GeometryWindow(PamhyrWindow):
|
||||
if is_interpolated and not export_interpolated:
|
||||
continue
|
||||
|
||||
self._export_to_file_st_profile(wfile, profile, pid)
|
||||
pid += 1
|
||||
if profile.enabled:
|
||||
self._export_to_file_st_profile(wfile, profile, pid)
|
||||
pid += 1
|
||||
|
||||
def _export_to_file_st_profile(self, wfile, profile, pid):
|
||||
num = f"{pid:>6}"
|
||||
|
||||
@@ -96,14 +96,17 @@ class ComboBoxDelegate(QItemDelegate):
|
||||
reach = self._data.hydraulic_structures\
|
||||
.get_for_enabled_reaches(index.row())\
|
||||
.input_reach
|
||||
profiles = list(
|
||||
filter(
|
||||
lambda p: p.display_name() == text,
|
||||
reach.reach.profiles
|
||||
if reach is None:
|
||||
value = None
|
||||
else:
|
||||
profiles = list(
|
||||
filter(
|
||||
lambda p: p.display_name() == text,
|
||||
reach.reach.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
value = profiles[0] if len(profiles) > 0 else None
|
||||
value = profiles[0] if len(profiles) > 0 else None
|
||||
else:
|
||||
value = text
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
QComboBox, QMessageBox
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -290,8 +290,6 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
logger.error("No results data")
|
||||
return
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
ts = max(results.get("timestamps"))
|
||||
res_reach = results.river.get_reach_by_geometry(
|
||||
self._reach.reach
|
||||
@@ -307,19 +305,15 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
)
|
||||
)
|
||||
|
||||
self._undo.push(
|
||||
ReplaceDataCommand(
|
||||
self._lst,
|
||||
list(
|
||||
map(
|
||||
lambda d: self._lst.new_from_data(*d),
|
||||
data
|
||||
)
|
||||
)
|
||||
new_data = list(
|
||||
map(
|
||||
lambda d: self._lst.new_from_data(*d),
|
||||
data
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self._undo.push(ReplaceDataCommand(self._lst, new_data))
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def read_from_ini(self, file_name):
|
||||
@@ -355,17 +349,31 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
line_split[2],
|
||||
line_split[3]])
|
||||
|
||||
self._undo.push(
|
||||
ReplaceDataCommand(
|
||||
self._lst,
|
||||
list(
|
||||
map(
|
||||
lambda d: self._lst.new_from_data(*d),
|
||||
data
|
||||
)
|
||||
new_data = []
|
||||
missing_rks = []
|
||||
for row in data:
|
||||
rk = row[0].strip()
|
||||
try:
|
||||
new_data.append(self._lst.new_from_data(*row))
|
||||
except ValueError:
|
||||
missing_rks.append(rk)
|
||||
logger.warning(
|
||||
f"No profile found for imported RK {rk} "
|
||||
f"in reach {self._reach.name}"
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self._undo.push(ReplaceDataCommand(self._lst, new_data))
|
||||
self.layoutChanged.emit()
|
||||
|
||||
if missing_rks:
|
||||
QMessageBox.warning(
|
||||
self._table_view,
|
||||
self._trad["missing_rk_title"],
|
||||
self._trad["missing_rk_text"].format(
|
||||
rks=", ".join(missing_rks)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
|
||||
@@ -42,6 +42,13 @@ class ICTranslate(MainTranslate):
|
||||
"InitialCondition", "Mage initial conditions file (*.INI *.ini)")
|
||||
self._dict["file_all"] = _translate(
|
||||
"InitialCondition", "All files (*)")
|
||||
self._dict["missing_rk_title"] = _translate(
|
||||
"InitialCondition", "Profiles not found")
|
||||
self._dict["missing_rk_text"] = _translate(
|
||||
"InitialCondition",
|
||||
"No profile was found for the following RK values: {rks}. "
|
||||
"They were not imported and must be entered manually."
|
||||
)
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
# "name": _translate("InitialCondition", "Name"),
|
||||
|
||||
@@ -205,13 +205,14 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
if self._headers[column] != "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column], value
|
||||
self._data, self._lst, row,
|
||||
self._headers[column], value
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column],
|
||||
self._data, self._lst, row, self._headers[column],
|
||||
self._river.edge(value).id
|
||||
)
|
||||
)
|
||||
|
||||
@@ -80,31 +80,31 @@ class SetCommand(QUndoCommand):
|
||||
|
||||
|
||||
class SetCommandSpec(QUndoCommand):
|
||||
def __init__(self, data, row, column, new_value):
|
||||
def __init__(self, parent, data, row, column, new_value):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._row = row
|
||||
self._parent = parent
|
||||
self._value = data[row]
|
||||
self._column = column
|
||||
|
||||
if self._column == "name":
|
||||
self._old = self._data[self._row].name
|
||||
self._old = self._value.name
|
||||
elif self._column == "reach":
|
||||
self._old = self._data[self._row].reach
|
||||
self._old = self._value.reach
|
||||
elif self._column == "start_rk":
|
||||
self._old = self._data[self._row].start_rk
|
||||
self._old = self._value.start_rk
|
||||
elif self._column == "end_rk":
|
||||
self._old = self._data[self._row].end_rk
|
||||
self._old = self._value.end_rk
|
||||
elif self._column == "concentration":
|
||||
self._old = self._data[self._row].concentration
|
||||
self._old = self._value.concentration
|
||||
elif self._column == "eg":
|
||||
self._old = self._data[self._row].eg
|
||||
self._old = self._value.eg
|
||||
elif self._column == "em":
|
||||
self._old = self._data[self._row].em
|
||||
self._old = self._value.em
|
||||
elif self._column == "ed":
|
||||
self._old = self._data[self._row].ed
|
||||
self._old = self._value.ed
|
||||
elif self._column == "rate":
|
||||
self._old = self._data[self._row].rate
|
||||
self._old = self._value.rate
|
||||
|
||||
_type = float
|
||||
if column == "name":
|
||||
@@ -115,44 +115,12 @@ class SetCommandSpec(QUndoCommand):
|
||||
self._new = _type(new_value)
|
||||
|
||||
def undo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = self._old
|
||||
elif self._column == "reach":
|
||||
self._data[self._row].reach = self._old
|
||||
elif self._column == "start_rk":
|
||||
self._data[self._row].start_rk = self._old
|
||||
elif self._column == "end_rk":
|
||||
self._data[self._row].end_rk = self._old
|
||||
elif self._column == "concentration":
|
||||
self._data[self._row].concentration = self._old
|
||||
elif self._column == "eg":
|
||||
self._data[self._row].eg = self._old
|
||||
elif self._column == "em":
|
||||
self._data[self._row].em = self._old
|
||||
elif self._column == "ed":
|
||||
self._data[self._row].ed = self._old
|
||||
elif self._column == "rate":
|
||||
self._data[self._row].rate = self._old
|
||||
setattr(self._value, self._column, self._old)
|
||||
self._parent.modified()
|
||||
|
||||
def redo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = self._new
|
||||
elif self._column == "reach":
|
||||
self._data[self._row].reach = self._new
|
||||
elif self._column == "start_rk":
|
||||
self._data[self._row].start_rk = self._new
|
||||
elif self._column == "end_rk":
|
||||
self._data[self._row].end_rk = self._new
|
||||
elif self._column == "concentration":
|
||||
self._data[self._row].concentration = self._new
|
||||
elif self._column == "eg":
|
||||
self._data[self._row].eg = self._new
|
||||
elif self._column == "em":
|
||||
self._data[self._row].em = self._new
|
||||
elif self._column == "ed":
|
||||
self._data[self._row].ed = self._new
|
||||
elif self._column == "rate":
|
||||
self._data[self._row].rate = self._new
|
||||
setattr(self._value, self._column, self._new)
|
||||
self._parent.modified()
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
|
||||
@@ -30,13 +30,13 @@ from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
QComboBox, QMessageBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.InitialConditionsTemperature.UndoCommand import (
|
||||
SetCommand, AddCommand, SetCommandSpec,
|
||||
AddCommand, SetCommandSpec,
|
||||
DelCommand,
|
||||
)
|
||||
|
||||
@@ -229,13 +229,22 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
column_name = self._headers[column]
|
||||
|
||||
try:
|
||||
if self._headers[column] in ["name", "temperature"]:
|
||||
self._undo.push(
|
||||
SetCommand(
|
||||
self._lst, row, self._headers[column], value
|
||||
)
|
||||
new_value = (
|
||||
self._river.edge(value).id
|
||||
if column_name == "reach"
|
||||
else value
|
||||
)
|
||||
if (column_name in ("reach", "rk")
|
||||
and self._overlaps_existing_rk(
|
||||
row, column_name, new_value
|
||||
)):
|
||||
QMessageBox.warning(
|
||||
self._table_view,
|
||||
self._trad["msg_rk_overlap_title"],
|
||||
self._trad["msg_rk_overlap_text"]
|
||||
)
|
||||
else:
|
||||
new_value = value
|
||||
@@ -248,7 +257,8 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column],
|
||||
self._data, self._lst, row,
|
||||
self._headers[column],
|
||||
new_value
|
||||
)
|
||||
)
|
||||
@@ -265,6 +275,25 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def _overlaps_existing_rk(self, row, column, value):
|
||||
current = self._lst[row]
|
||||
reach = value if column == "reach" else current.reach
|
||||
rk = value if column == "rk" else current.start_rk
|
||||
if reach in (None, -1) or rk is None:
|
||||
return False
|
||||
|
||||
rk = float(rk)
|
||||
return any(
|
||||
other is not current
|
||||
and not other.is_deleted()
|
||||
and other.reach == reach
|
||||
and other.start_rk is not None
|
||||
and other.end_rk is not None
|
||||
and min(other.start_rk, other.end_rk) <= rk
|
||||
<= max(other.start_rk, other.end_rk)
|
||||
for other in self._data._data
|
||||
)
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
|
||||
@@ -62,23 +62,23 @@ class SetCommand(QUndoCommand):
|
||||
|
||||
|
||||
class SetCommandSpec(QUndoCommand):
|
||||
def __init__(self, data, row, column, new_value):
|
||||
def __init__(self, parent, data, row, column, new_value):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._row = row
|
||||
self._parent = parent
|
||||
self._value = data[row]
|
||||
self._column = column
|
||||
|
||||
if self._column == "name":
|
||||
self._old = self._data[self._row].name
|
||||
self._old = self._value.name
|
||||
elif self._column == "reach":
|
||||
self._old = self._data[self._row].reach
|
||||
self._old_start_rk = self._data[self._row].start_rk
|
||||
self._old_end_rk = self._data[self._row].end_rk
|
||||
self._old = self._value.reach
|
||||
self._old_start_rk = self._value.start_rk
|
||||
self._old_end_rk = self._value.end_rk
|
||||
elif self._column == "rk":
|
||||
self._old = self._data[self._row].start_rk
|
||||
self._old = self._value.start_rk
|
||||
elif self._column == "temperature":
|
||||
self._old = self._data[self._row].temperature
|
||||
self._old = self._value.temperature
|
||||
|
||||
_type = float
|
||||
if column == "name":
|
||||
@@ -94,29 +94,31 @@ class SetCommandSpec(QUndoCommand):
|
||||
|
||||
def undo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = self._old
|
||||
self._value.name = self._old
|
||||
elif self._column == "reach":
|
||||
self._data[self._row].reach = self._old
|
||||
self._data[self._row].start_rk = self._old_start_rk
|
||||
self._data[self._row].end_rk = self._old_end_rk
|
||||
self._value.reach = self._old
|
||||
self._value.start_rk = self._old_start_rk
|
||||
self._value.end_rk = self._old_end_rk
|
||||
elif self._column == "rk":
|
||||
self._data[self._row].start_rk = self._old
|
||||
self._data[self._row].end_rk = self._old
|
||||
self._value.start_rk = self._old
|
||||
self._value.end_rk = self._old
|
||||
elif self._column == "temperature":
|
||||
self._data[self._row].temperature = self._old
|
||||
self._value.temperature = self._old
|
||||
self._parent.modified()
|
||||
|
||||
def redo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = self._new
|
||||
self._value.name = self._new
|
||||
elif self._column == "reach":
|
||||
self._data[self._row].reach = self._new
|
||||
self._data[self._row].start_rk = self._new_rk
|
||||
self._data[self._row].end_rk = self._new_rk
|
||||
self._value.reach = self._new
|
||||
self._value.start_rk = self._new_rk
|
||||
self._value.end_rk = self._new_rk
|
||||
elif self._column == "rk":
|
||||
self._data[self._row].start_rk = self._new
|
||||
self._data[self._row].end_rk = self._new
|
||||
self._value.start_rk = self._new
|
||||
self._value.end_rk = self._new
|
||||
elif self._column == "temperature":
|
||||
self._data[self._row].temperature = self._new
|
||||
self._value.temperature = self._new
|
||||
self._parent.modified()
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
|
||||
@@ -32,6 +32,16 @@ class IcTemperatureTranslate(MainTranslate):
|
||||
|
||||
self._dict["rk"] = self._dict["unit_rk"]
|
||||
|
||||
self._dict["msg_rk_overlap_title"] = _translate(
|
||||
"InitialConditionTemperature",
|
||||
"Chainage already used"
|
||||
)
|
||||
self._dict["msg_rk_overlap_text"] = _translate(
|
||||
"InitialConditionTemperature",
|
||||
"An initial temperature condition is already defined at this "
|
||||
"chainage on the selected reach."
|
||||
)
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"name": self._dict["name"],
|
||||
"temperature": self._dict["unit_temperature"],
|
||||
|
||||
@@ -23,7 +23,7 @@ from datetime import date, time, datetime, timedelta
|
||||
from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp
|
||||
old_pamhyr_date_to_timestamp, format_datetime
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -76,7 +76,9 @@ class TableModel(PamhyrTableModel):
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
value = format_datetime(
|
||||
self._start_date + timedelta(seconds=float(v))
|
||||
)
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
@@ -93,6 +95,10 @@ class TableModel(PamhyrTableModel):
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if (self._data.header[column] == "time"
|
||||
and self._opt_data == "date"):
|
||||
value = self.date_to_elapsed_seconds(value)
|
||||
|
||||
self._undo.push(
|
||||
SetDataCommand(
|
||||
self._data, row, column, value
|
||||
|
||||
@@ -109,7 +109,8 @@ class EditLateralContributionWindow(PamhyrWindow):
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad,
|
||||
opt_data=self._study.time_system
|
||||
opt_data=self._study.time_system,
|
||||
start_date=self._study.date
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
|
||||
@@ -24,7 +24,7 @@ from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp,
|
||||
timestamp_to_old_pamhyr_date_adists
|
||||
timestamp_to_old_pamhyr_date_adists, format_datetime
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -90,7 +90,9 @@ class TableModel(PamhyrTableModel):
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date_adists(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
value = format_datetime(
|
||||
self._start_date + timedelta(seconds=float(v))
|
||||
)
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
@@ -107,6 +109,10 @@ class TableModel(PamhyrTableModel):
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if (self._data.header[column] == "time"
|
||||
and self._opt_data == "date"):
|
||||
value = self.date_to_elapsed_seconds(value)
|
||||
|
||||
row = self.get_true_data_row(row)
|
||||
|
||||
self._undo.push(
|
||||
|
||||
@@ -124,7 +124,8 @@ class EditLateralContributionAdisTSWindow(PamhyrWindow):
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad,
|
||||
opt_data=self._study.time_system
|
||||
opt_data=self._study.time_system,
|
||||
start_date=self._study.date
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
|
||||
@@ -32,6 +32,7 @@ from PyQt5.QtGui import (
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QGraphicsScene, QGraphicsView,
|
||||
QGraphicsItem, QGraphicsTextItem, QMenu,
|
||||
QMessageBox,
|
||||
)
|
||||
|
||||
from Model.Network.Node import Node
|
||||
@@ -724,6 +725,16 @@ class GraphWidget(QGraphicsView):
|
||||
|
||||
def split_edge(self, item):
|
||||
edge = item.edge
|
||||
if len(edge.reach.profiles) < 3:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
self._trad["menu_split_edge"],
|
||||
_translate(
|
||||
"Network",
|
||||
"No profile is available to split this reach."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
dlg = SelectProfileDialog(
|
||||
study=self.parent._study,
|
||||
@@ -954,9 +965,10 @@ class GraphWidget(QGraphicsView):
|
||||
self.set_current_edge(edge)
|
||||
if not locked:
|
||||
if items and type(items[0]) is NodeItem:
|
||||
self._mouse_origin_x = pos.x()
|
||||
self._mouse_origin_y = pos.y()
|
||||
self._current_moved_node = items[0]
|
||||
node_pos = self._current_moved_node.pos()
|
||||
self._mouse_origin_x = node_pos.x()
|
||||
self._mouse_origin_y = node_pos.y()
|
||||
|
||||
if not locked:
|
||||
# Add nodes and edges
|
||||
@@ -1011,6 +1023,8 @@ class GraphWidget(QGraphicsView):
|
||||
self._mouse_origin_y)
|
||||
)
|
||||
)
|
||||
# reset the current moved node
|
||||
self._current_moved_node = None
|
||||
|
||||
self.update()
|
||||
super(GraphWidget, self).mouseReleaseEvent(event)
|
||||
|
||||
@@ -64,6 +64,7 @@ class SelectProfileDialog(PamhyrDialog):
|
||||
|
||||
self._reach = reach
|
||||
self._profile = None
|
||||
self._profiles = self._reach.reach.enabled_profiles
|
||||
|
||||
self.setup_combobox()
|
||||
|
||||
@@ -73,18 +74,23 @@ class SelectProfileDialog(PamhyrDialog):
|
||||
list(
|
||||
map(
|
||||
lambda p: p.display_name(),
|
||||
self._reach.reach.profiles)))
|
||||
|
||||
def accept(self):
|
||||
profile = self.get_combobox_text("comboBox")
|
||||
|
||||
self._profile = next(
|
||||
filter(
|
||||
lambda p: p.display_name() == profile,
|
||||
self._reach.reach.profiles
|
||||
self._reach.reach.profiles[1:-1]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if len(self._profiles) == 0:
|
||||
self.find(QDialogButtonBox, "buttonBox")\
|
||||
.button(QDialogButtonBox.Ok)\
|
||||
.setEnabled(False)
|
||||
|
||||
def accept(self):
|
||||
index = self.find(QComboBox, "comboBox").currentIndex()
|
||||
if not 0 <= index < len(self._profiles):
|
||||
return
|
||||
|
||||
self._profile = self._profiles[index]
|
||||
|
||||
super(SelectProfileDialog, self).accept()
|
||||
|
||||
def reject(self):
|
||||
|
||||
@@ -26,6 +26,7 @@ from PyQt5.QtCore import (
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
from PyQt5.QtGui import QColor
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
@@ -59,14 +60,23 @@ class TableModel(PamhyrTableModel):
|
||||
return len(self._lst)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
if not index.isValid():
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if role == Qt.BackgroundRole and not self._lst[row].enabled:
|
||||
return QColor(190, 190, 190)
|
||||
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
if self._headers[column] == "name":
|
||||
return self._lst[row].name
|
||||
name = self._lst[row].name
|
||||
if not self._lst[row].enabled:
|
||||
name += f" ({self._trad['disabled']})"
|
||||
return name
|
||||
|
||||
return QVariant()
|
||||
|
||||
@@ -74,12 +84,19 @@ class TableModel(PamhyrTableModel):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
if self.is_same_data(index, value):
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "name":
|
||||
suffix = f" ({self._trad['disabled']})"
|
||||
if not self._lst[row].enabled and str(value).endswith(suffix):
|
||||
value = str(value)[:-len(suffix)]
|
||||
|
||||
if str(self._lst[row].name) == str(value):
|
||||
return False
|
||||
elif self.is_same_data(index, value):
|
||||
return False
|
||||
|
||||
try:
|
||||
if self._headers[column] == "name":
|
||||
self._undo.push(
|
||||
@@ -110,14 +127,10 @@ class TableModel(PamhyrTableModel):
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
data_rows = {
|
||||
id(pol): i for i, pol in enumerate(self._data._Pollutants._lst)
|
||||
}
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._data._Pollutants,
|
||||
[data_rows[id(self._lst[row])] for row in rows
|
||||
if 0 <= row < len(self._lst)],
|
||||
[row for row in rows if 0 <= row < len(self._lst)],
|
||||
self._data.ic_adists
|
||||
)
|
||||
)
|
||||
|
||||
@@ -32,6 +32,7 @@ class PollutantsTranslate(MainTranslate):
|
||||
)
|
||||
|
||||
self._dict["x"] = _translate("Pollutants", "X (m)")
|
||||
self._dict["disabled"] = _translate("Pollutants", "disabled")
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"name": self._dict["name"],
|
||||
|
||||
+27
-21
@@ -62,6 +62,8 @@ class PlotRKC(PamhyrPlot):
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = False
|
||||
|
||||
self.profile = None
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
return self.data
|
||||
@@ -71,9 +73,19 @@ class PlotRKC(PamhyrPlot):
|
||||
self.data = results
|
||||
self._current_timestamp = max(self._timestamps)
|
||||
|
||||
@staticmethod
|
||||
def reach_geometry(reach):
|
||||
"""Return geometry values for profiles present in the results."""
|
||||
return (
|
||||
reach.geometry.get_rk_enabled_profiles(),
|
||||
reach.geometry.get_z_min_enabled_profiles(),
|
||||
reach.geometry.get_z_max_enabled_profiles()
|
||||
)
|
||||
|
||||
@timer
|
||||
def draw(self, highlight=None):
|
||||
self.init_axes()
|
||||
self.profile = None
|
||||
|
||||
if self.results is None:
|
||||
return
|
||||
@@ -104,7 +116,7 @@ class PlotRKC(PamhyrPlot):
|
||||
|
||||
def draw_bottom_with_bedload(self, reach):
|
||||
results = self.results[self._current_res_id]
|
||||
rk = reach.geometry.get_rk()
|
||||
rk, _, _ = self.reach_geometry(reach)
|
||||
|
||||
table = results.get("table")["zfd"]
|
||||
ts = results.get_timestamp_id(self._current_timestamp)
|
||||
@@ -136,8 +148,7 @@ class PlotRKC(PamhyrPlot):
|
||||
|
||||
for hs in lhs:
|
||||
x = hs.input_section.rk
|
||||
z_min = reach.geometry.get_z_min()
|
||||
z_max = reach.geometry.get_z_max()
|
||||
_, z_min, z_max = self.reach_geometry(reach)
|
||||
|
||||
self.canvas.axes.plot(
|
||||
[x, x],
|
||||
@@ -157,9 +168,7 @@ class PlotRKC(PamhyrPlot):
|
||||
)
|
||||
|
||||
def draw_bottom_geometry(self, reach):
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
z_max = reach.geometry.get_z_max()
|
||||
rk, z_min, _ = self.reach_geometry(reach)
|
||||
|
||||
self.line_rk_zmin = self.canvas.axes.plot(
|
||||
rk, z_min,
|
||||
@@ -170,10 +179,9 @@ class PlotRKC(PamhyrPlot):
|
||||
self._river_bottom = z_min
|
||||
|
||||
def draw_water_elevation(self, reach):
|
||||
if len(reach.geometry.profiles) != 0:
|
||||
if len(reach.profiles) != 0:
|
||||
result = self.results[self._current_res_id]
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
rk, _, _ = self.reach_geometry(reach)
|
||||
|
||||
table = result.get("table")["Z"]
|
||||
ts = result.get_timestamp_id(self._current_timestamp)
|
||||
@@ -201,10 +209,9 @@ class PlotRKC(PamhyrPlot):
|
||||
)
|
||||
|
||||
def draw_water_elevation_max(self, reach):
|
||||
if len(reach.geometry.profiles) != 0:
|
||||
if len(reach.profiles) != 0:
|
||||
result = self.results[self._current_res_id]
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
rk, _, _ = self.reach_geometry(reach)
|
||||
|
||||
table = result.get("table")["Z"]
|
||||
ts = result.get_timestamp_id(self._current_timestamp)
|
||||
@@ -250,9 +257,7 @@ class PlotRKC(PamhyrPlot):
|
||||
self.canvas.draw_idle()
|
||||
|
||||
def draw_current(self, reach):
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
z_max = reach.geometry.get_z_max()
|
||||
rk, z_min, z_max = self.reach_geometry(reach)
|
||||
|
||||
self.profile, = self.canvas.axes.plot(
|
||||
[
|
||||
@@ -349,8 +354,7 @@ class PlotRKC(PamhyrPlot):
|
||||
def update_water_elevation(self):
|
||||
result = self.results[self._current_res_id]
|
||||
reach = result.river.reach(self._current_reach_id)
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
rk, _, _ = self.reach_geometry(reach)
|
||||
table = result.get("table")["Z"]
|
||||
ts = result.get_timestamp_id(self._current_timestamp)
|
||||
|
||||
@@ -380,11 +384,13 @@ class PlotRKC(PamhyrPlot):
|
||||
self.update_idle()
|
||||
|
||||
def update_current(self):
|
||||
if not self._init or self.profile is None:
|
||||
self.draw()
|
||||
return
|
||||
|
||||
results = self.results[self._current_res_id]
|
||||
reach = results.river.reach(self._current_reach_id)
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
z_max = reach.geometry.get_z_max()
|
||||
rk, z_min, z_max = self.reach_geometry(reach)
|
||||
cid = self._current_profile_id
|
||||
|
||||
self.profile.set_data(
|
||||
@@ -396,7 +402,7 @@ class PlotRKC(PamhyrPlot):
|
||||
def update_bottom_with_bedload(self):
|
||||
results = self.results[self._current_res_id]
|
||||
reach = results.river.reach(self._current_reach_id)
|
||||
rk = reach.geometry.get_rk()
|
||||
rk, _, _ = self.reach_geometry(reach)
|
||||
# z = self.sl_compute_current_z(reach)
|
||||
|
||||
table = results.get("table")["zfd"]
|
||||
|
||||
@@ -106,7 +106,7 @@ class PlotAdis(PamhyrPlot):
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
profile = reach.profile(self._current_profile_id)
|
||||
|
||||
if reach.geometry.number_profiles == 0:
|
||||
if len(reach) == 0:
|
||||
self._init = False
|
||||
return
|
||||
|
||||
@@ -195,7 +195,7 @@ class PlotAdis_dx(PlotAdis):
|
||||
def draw_data(self):
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
profile = reach.profile(self._current_profile_id)
|
||||
x = reach.geometry.get_rk()
|
||||
x = reach.get_rk()
|
||||
|
||||
self._lines = []
|
||||
for i in range(len(self._current_pol_id)):
|
||||
@@ -225,7 +225,7 @@ class PlotAdis_dx(PlotAdis):
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
profile = reach.profile(self._current_profile_id)
|
||||
|
||||
x = reach.geometry.get_rk()
|
||||
x = reach.get_rk()
|
||||
|
||||
for i in range(len(self._current_pol_id)):
|
||||
pol_id = self._current_pol_id[i]
|
||||
@@ -244,7 +244,7 @@ class PlotAdis_dx(PlotAdis):
|
||||
|
||||
def draw_current(self):
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
rk = reach.geometry.get_rk()
|
||||
rk = reach.get_rk()
|
||||
|
||||
self._current, = self.canvas.axes.plot(
|
||||
[rk[self._current_profile_id], rk[self._current_profile_id]],
|
||||
@@ -260,7 +260,7 @@ class PlotAdis_dx(PlotAdis):
|
||||
self.canvas.axes.autoscale_view()
|
||||
self.show_current()
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
rk = reach.geometry.get_rk()
|
||||
rk = reach.get_rk()
|
||||
self._current.set_data(
|
||||
[rk[self._current_profile_id], rk[self._current_profile_id]],
|
||||
self.canvas.axes.get_ylim()
|
||||
@@ -269,13 +269,13 @@ class PlotAdis_dx(PlotAdis):
|
||||
def get_min_and_max(self):
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
profile = reach.profile(self._current_profile_id)
|
||||
x = reach.geometry.get_rk()
|
||||
x = reach.get_rk()
|
||||
|
||||
y = [-inf]*reach.geometry.number_profiles
|
||||
z = [inf]*reach.geometry.number_profiles
|
||||
y = [-inf]*len(reach)
|
||||
z = [inf]*len(reach)
|
||||
for pol_id in self._current_pol_id:
|
||||
if self.val_id[self._key] > 0 and self._type_pol[pol_id] == 1:
|
||||
for i in range(reach.geometry.number_profiles):
|
||||
for i in range(len(reach)):
|
||||
y[i] = max(0.0, y[i])
|
||||
z[i] = min(0.0, z[i])
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
# PlotTemperature.py -- Pamhyr
|
||||
# Copyright (C) 2026 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib.cm import ScalarMappable
|
||||
from matplotlib.collections import PolyCollection
|
||||
from matplotlib.colors import Normalize
|
||||
|
||||
from View.Results.PlotXY import PlotXY
|
||||
|
||||
|
||||
class PlotTemperature(PlotXY):
|
||||
def __init__(self, canvas=None, trad=None, toolbar=None,
|
||||
results=None, reach_id=0, profile_id=0,
|
||||
pol_id=1, parent=None):
|
||||
super(PlotTemperature, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
toolbar=toolbar,
|
||||
results=results,
|
||||
reach_id=reach_id,
|
||||
profile_id=profile_id,
|
||||
res_id=[0],
|
||||
parent=parent,
|
||||
)
|
||||
|
||||
self._current_pol_id = pol_id
|
||||
self._global_ranges = {}
|
||||
self._temperature_zones = None
|
||||
self._colorbar = None
|
||||
self._auto_relim_update = False
|
||||
self._autoscale_update = False
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
return self.data
|
||||
|
||||
@results.setter
|
||||
def results(self, results):
|
||||
self.data = results
|
||||
self._timestamps = sorted(results.get("timestamps"))
|
||||
self._current_timestamp = self._timestamps[-1]
|
||||
self._global_ranges.clear()
|
||||
|
||||
def draw(self, highlight=None):
|
||||
if self._colorbar is not None:
|
||||
self._colorbar.remove()
|
||||
self._colorbar = None
|
||||
self.init_axes()
|
||||
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
if reach.geometry.number_profiles == 0:
|
||||
self._init = False
|
||||
return
|
||||
|
||||
temperatures = self._temperatures(reach)
|
||||
norm = self._temperature_norm()
|
||||
|
||||
self.draw_profiles(reach, self.results.river.reachs)
|
||||
self.draw_guide_lines(reach)
|
||||
self._draw_temperature_zones(reach, temperatures, norm)
|
||||
self.draw_current(reach)
|
||||
|
||||
mappable = self._temperature_zones
|
||||
if mappable is None:
|
||||
mappable = ScalarMappable(norm=norm, cmap="coolwarm")
|
||||
self._colorbar = self.canvas.figure.colorbar(
|
||||
mappable, ax=self.canvas.axes
|
||||
)
|
||||
self._colorbar.set_label(self._trad["unit_temperature"])
|
||||
# self.canvas.axes.set_title(
|
||||
# f"{self._trad['temperature_map']} — {reach.name}"
|
||||
# )
|
||||
self.canvas.axes.set_aspect("auto")
|
||||
self._zoom_to_reach_bbox(reach)
|
||||
self.canvas.draw_idle()
|
||||
self.toolbar_update()
|
||||
self._init = True
|
||||
|
||||
def _draw_temperature_zones(self, reach, temperatures, norm):
|
||||
profiles = reach.profiles
|
||||
polygons = []
|
||||
for index in range(len(profiles) - 1):
|
||||
current = profiles[index].geometry
|
||||
following = profiles[index + 1].geometry
|
||||
if current.number_points == 0 or following.number_points == 0:
|
||||
continue
|
||||
polygons.append([
|
||||
(current.x()[0], current.y()[0]),
|
||||
(current.x()[-1], current.y()[-1]),
|
||||
(following.x()[-1], following.y()[-1]),
|
||||
(following.x()[0], following.y()[0]),
|
||||
])
|
||||
|
||||
if not polygons:
|
||||
self._temperature_zones = None
|
||||
return
|
||||
|
||||
self._temperature_zones = PolyCollection(
|
||||
polygons,
|
||||
cmap="coolwarm",
|
||||
norm=norm,
|
||||
edgecolors="none",
|
||||
alpha=0.8,
|
||||
zorder=1,
|
||||
)
|
||||
self._temperature_zones.set_array(
|
||||
self._segment_temperatures(temperatures)
|
||||
)
|
||||
self.canvas.axes.add_collection(self._temperature_zones)
|
||||
|
||||
def update(self):
|
||||
if not self._init or self._temperature_zones is None:
|
||||
self.draw()
|
||||
return
|
||||
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
self._temperature_zones.set_array(
|
||||
self._segment_temperatures(self._temperatures(reach))
|
||||
)
|
||||
self.canvas.draw_idle()
|
||||
|
||||
def set_reach(self, reach_id):
|
||||
self._current_reach_id = reach_id
|
||||
self._current_profile_id = 0
|
||||
self.draw()
|
||||
|
||||
def set_profile(self, profile_id):
|
||||
self._current_profile_id = profile_id
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
profile = reach.profile(profile_id)
|
||||
self.plot_selected.set_data(
|
||||
profile.geometry.x(), profile.geometry.y()
|
||||
)
|
||||
self.canvas.draw_idle()
|
||||
|
||||
def set_pollutant(self, pol_id):
|
||||
self._current_pol_id = pol_id
|
||||
self.draw()
|
||||
|
||||
def set_timestamp(self, timestamp):
|
||||
self._current_timestamp = timestamp
|
||||
self.update()
|
||||
|
||||
def _temperatures(self, reach):
|
||||
return np.asarray([
|
||||
profile.get_ts_key(self._current_timestamp, "pols")[
|
||||
self._current_pol_id
|
||||
][0]
|
||||
for profile in reach.profiles
|
||||
], dtype=float)
|
||||
|
||||
@staticmethod
|
||||
def _segment_temperatures(temperatures):
|
||||
return (temperatures[:-1] + temperatures[1:]) / 2.0
|
||||
|
||||
def _temperature_norm(self):
|
||||
minimum, maximum = self._global_temperature_range()
|
||||
if minimum == maximum:
|
||||
maximum = minimum + 1.0
|
||||
return Normalize(vmin=minimum, vmax=maximum)
|
||||
|
||||
def _zoom_to_reach_bbox(self, reach):
|
||||
x_values = [
|
||||
np.asarray(profile.geometry.x(), dtype=float)
|
||||
for profile in reach.profiles
|
||||
if len(profile.geometry.x()) != 0
|
||||
]
|
||||
y_values = [
|
||||
np.asarray(profile.geometry.y(), dtype=float)
|
||||
for profile in reach.profiles
|
||||
if len(profile.geometry.y()) != 0
|
||||
]
|
||||
if not x_values or not y_values:
|
||||
return
|
||||
x = np.concatenate(x_values)
|
||||
y = np.concatenate(y_values)
|
||||
|
||||
x_min, x_max = float(np.min(x)), float(np.max(x))
|
||||
y_min, y_max = float(np.min(y)), float(np.max(y))
|
||||
x_margin = max((x_max - x_min) * 0.05, 1.0)
|
||||
y_margin = max((y_max - y_min) * 0.05, 1.0)
|
||||
self.canvas.axes.set_xlim(x_min - x_margin, x_max + x_margin)
|
||||
self.canvas.axes.set_ylim(y_min - y_margin, y_max + y_margin)
|
||||
|
||||
def _global_temperature_range(self):
|
||||
pol_id = self._current_pol_id
|
||||
if pol_id in self._global_ranges:
|
||||
return self._global_ranges[pol_id]
|
||||
|
||||
temperatures = []
|
||||
for reach in self.results.river.reachs:
|
||||
for profile in reach.profiles:
|
||||
for timestamp in self._timestamps:
|
||||
values = profile.get_ts_key(timestamp, "pols")
|
||||
if values is not None:
|
||||
temperatures.append(values[pol_id][0])
|
||||
|
||||
temperatures = np.asarray(temperatures, dtype=float)
|
||||
temperatures = temperatures[np.isfinite(temperatures)]
|
||||
value_range = (
|
||||
(float(np.min(temperatures)), float(np.max(temperatures)))
|
||||
if temperatures.size
|
||||
else (0.0, 1.0)
|
||||
)
|
||||
self._global_ranges[pol_id] = value_range
|
||||
return value_range
|
||||
@@ -1152,7 +1152,6 @@ class ResultsWindow(PamhyrWindow):
|
||||
z = table["Z"][:, profile.global_index]
|
||||
q = table["Q"][:, profile.global_index]
|
||||
v = table["V"][:, profile.global_index]
|
||||
zfd = table["zfd"][:, profile.global_index]
|
||||
|
||||
if self._current_results == 2:
|
||||
reach1 = self._results[0].river.reach(self._reach)
|
||||
@@ -1171,8 +1170,8 @@ class ResultsWindow(PamhyrWindow):
|
||||
v2 = table["V"][:, profile2.global_index]
|
||||
|
||||
if "bed_elevation" in y:
|
||||
if reach.has_bedload():
|
||||
z_min = zfd
|
||||
if reach.has_bedload() and "zfd" in table:
|
||||
z_min = table["zfd"][:, profile.global_index]
|
||||
else:
|
||||
z_min = [profile.geometry.z_min()] * len(self._timestamps)
|
||||
|
||||
|
||||
@@ -726,7 +726,7 @@ class ResultsWindowAdisTS(PamhyrWindow):
|
||||
dict_x = self._trad.get_dict("values_x")
|
||||
dict_y = self._trad.get_dict("values_y_pol")
|
||||
my_dict = {}
|
||||
my_dict[dict_x["rk"]] = reach.geometry.get_rk()
|
||||
my_dict[dict_x["rk"]] = reach.get_rk()
|
||||
val_id = {}
|
||||
val_id["unit_C"] = 0
|
||||
val_id["unit_M"] = 2
|
||||
|
||||
@@ -47,6 +47,7 @@ from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||
|
||||
from View.Results.PlotSedAdis import PlotAdis_dx, PlotAdis_dt
|
||||
from View.Results.PlotTemperature import PlotTemperature
|
||||
|
||||
from View.Results.CustomPlot.Plot import CustomPlot
|
||||
from View.Results.CustomExport.CustomExportAdis import (
|
||||
@@ -225,6 +226,33 @@ class ResultsWindowAdisTT(PamhyrWindow):
|
||||
)
|
||||
self.plot_cdx.draw()
|
||||
|
||||
self.canvas_temperature_map = MplCanvas(width=5, height=4, dpi=100)
|
||||
self.canvas_temperature_map.setObjectName("canvas_temperature_map")
|
||||
self.toolbar_temperature_map = PamhyrPlotToolbar(
|
||||
self.canvas_temperature_map, self, items=[
|
||||
"home", "move", "zoom", "save", "iso", "back/forward"
|
||||
]
|
||||
)
|
||||
temperature_map_tab = QWidget()
|
||||
temperature_map_layout = QVBoxLayout(temperature_map_tab)
|
||||
temperature_map_layout.addWidget(self.toolbar_temperature_map)
|
||||
temperature_map_layout.addWidget(self.canvas_temperature_map)
|
||||
self.find(QTabWidget, "tabWidget_c").addTab(
|
||||
temperature_map_tab,
|
||||
self._trad["temperature_map"]
|
||||
)
|
||||
self.plot_temperature = PlotTemperature(
|
||||
canvas=self.canvas_temperature_map,
|
||||
results=self._results,
|
||||
reach_id=self._reach_id,
|
||||
profile_id=self._profile_id,
|
||||
pol_id=self._current_pol_id[0],
|
||||
trad=self._trad,
|
||||
toolbar=self.toolbar_temperature_map,
|
||||
parent=self,
|
||||
)
|
||||
self.plot_temperature.draw()
|
||||
|
||||
# The AdisTT window only displays temperature plots. The code below
|
||||
# belongs to the sediment/pollutant result window and its layouts are
|
||||
# intentionally absent from ResultsAdisTT.ui.
|
||||
@@ -507,6 +535,7 @@ class ResultsWindowAdisTT(PamhyrWindow):
|
||||
self._reach_id = reach_id
|
||||
self.plot_cdt.set_reach(reach_id)
|
||||
self.plot_cdx.set_reach(reach_id)
|
||||
self.plot_temperature.set_reach(reach_id)
|
||||
|
||||
self.update_table_selection_reach(reach_id)
|
||||
self.update_table_selection_profile(0)
|
||||
@@ -515,6 +544,7 @@ class ResultsWindowAdisTT(PamhyrWindow):
|
||||
self._profile_id = profile_id
|
||||
self.plot_cdt.set_profile(profile_id)
|
||||
self.plot_cdx.set_profile(profile_id)
|
||||
self.plot_temperature.set_profile(profile_id)
|
||||
|
||||
self.update_table_selection_profile(profile_id)
|
||||
|
||||
@@ -522,10 +552,12 @@ class ResultsWindowAdisTT(PamhyrWindow):
|
||||
self._current_pol_id = [p+1 for p in pol_id] # rm total_sediment
|
||||
self.plot_cdt.set_pollutant(self._current_pol_id)
|
||||
self.plot_cdx.set_pollutant(self._current_pol_id)
|
||||
self.plot_temperature.set_pollutant(self._current_pol_id[0])
|
||||
|
||||
if timestamp is not None:
|
||||
self.plot_cdt.set_timestamp_preserve_view(timestamp)
|
||||
self.plot_cdx.set_timestamp_preserve_view(timestamp)
|
||||
self.plot_temperature.set_timestamp(timestamp)
|
||||
|
||||
self._table["raw_data"].set_timestamp(timestamp)
|
||||
|
||||
@@ -574,6 +606,8 @@ class ResultsWindowAdisTT(PamhyrWindow):
|
||||
|
||||
self.plot_cdt.draw()
|
||||
self.plot_cdx.draw()
|
||||
self.plot_temperature.results = self._results
|
||||
self.plot_temperature.draw()
|
||||
|
||||
def _reload_slider(self):
|
||||
self._slider_time = self.find(QSlider, f"horizontalSlider_time")
|
||||
|
||||
@@ -46,6 +46,12 @@ class ResultsTranslate(MainTranslate):
|
||||
self._dict['solver'] = _translate("Results", "Solver")
|
||||
|
||||
self._dict['x'] = _translate("Results", "X (m)")
|
||||
self._dict["temperature_map"] = _translate(
|
||||
"Results", "Reach temperature map"
|
||||
)
|
||||
self._dict["temperature"] = _translate(
|
||||
"Results", "Temperature"
|
||||
)
|
||||
|
||||
self._dict['label_bottom'] = _translate("Results", "Bottom")
|
||||
self._dict['label_water'] = _translate("Results", "Water elevation")
|
||||
|
||||
@@ -78,11 +78,6 @@ class SolverParametersWindow(PamhyrWindow):
|
||||
for st in solver_type_list:
|
||||
self._undo_stack[st] = QUndoStack()
|
||||
|
||||
self._undo_sc = QShortcut(QKeySequence.Undo, self)
|
||||
self._redo_sc = QShortcut(QKeySequence.Redo, self)
|
||||
self._undo_sc.activated.connect(self._undo)
|
||||
self._redo_sc.activated.connect(self._redo)
|
||||
|
||||
def setup_table(self):
|
||||
self._table = {}
|
||||
self._tab_widget = self.find(QTabWidget, f"tabWidget")
|
||||
@@ -134,12 +129,10 @@ class SolverParametersWindow(PamhyrWindow):
|
||||
def _undo(self):
|
||||
tab = self.current_tab()
|
||||
self._table[tab].undo()
|
||||
self._set_current_reach()
|
||||
|
||||
def _redo(self):
|
||||
tab = self.current_tab()
|
||||
self._table[tab].redo()
|
||||
self._set_current_reach()
|
||||
|
||||
def _copy(self):
|
||||
logger.info("TODO: copy")
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from tools import trace, timer
|
||||
from tools import trace, timer, parse_datetime
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
@@ -34,8 +34,11 @@ from PyQt5.QtWidgets import (
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox, QStyledItemDelegate, QHeaderView,
|
||||
QMessageBox,
|
||||
)
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
@@ -59,6 +62,27 @@ class PamhyrTextDelegate(QStyledItemDelegate):
|
||||
|
||||
|
||||
class PamhyrTableModel(QAbstractTableModel):
|
||||
def date_to_elapsed_seconds(self, value):
|
||||
"""Convert an entered date to seconds since the study start date."""
|
||||
try:
|
||||
entered_date = parse_datetime(value)
|
||||
except ValueError as error:
|
||||
QMessageBox.warning(
|
||||
self._table_view,
|
||||
_translate("PamhyrTableModel", "Invalid date/time"),
|
||||
_translate(
|
||||
"PamhyrTableModel",
|
||||
"The entered date/time is invalid.\n"
|
||||
"Expected formats:\n"
|
||||
"YYYY-MM-DD [HH:MM[:SS]],\n"
|
||||
"DD-MM-YYYY [HH:MM[:SS]] or\n"
|
||||
"DD/MM/YYYY [HH:MM[:SS]]."
|
||||
)
|
||||
)
|
||||
raise error
|
||||
|
||||
return (entered_date - self._start_date).total_seconds()
|
||||
|
||||
def _setup_delegates(self):
|
||||
if self._table_view is None:
|
||||
return
|
||||
@@ -85,7 +109,8 @@ class PamhyrTableModel(QAbstractTableModel):
|
||||
undo=None,
|
||||
opt_data=None,
|
||||
options=["rows_selection"],
|
||||
parent=None):
|
||||
parent=None,
|
||||
start_date=None):
|
||||
super(PamhyrTableModel, self).__init__()
|
||||
|
||||
self._table_view = table_view
|
||||
@@ -102,6 +127,7 @@ class PamhyrTableModel(QAbstractTableModel):
|
||||
self._options = options
|
||||
self._undo = undo
|
||||
self._lst = []
|
||||
self._start_date = start_date
|
||||
|
||||
self._setup_delegates()
|
||||
self._setup_lst()
|
||||
|
||||
@@ -23,7 +23,7 @@ from datetime import date, time, datetime, timedelta
|
||||
from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp
|
||||
old_pamhyr_date_to_timestamp, format_datetime
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -77,7 +77,9 @@ class TableModel(PamhyrTableModel):
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
value = format_datetime(
|
||||
self._start_date + timedelta(seconds=float(v))
|
||||
)
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
@@ -94,6 +96,10 @@ class TableModel(PamhyrTableModel):
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if (self._data.header[column] == "time"
|
||||
and self._opt_data == "date"):
|
||||
value = self.date_to_elapsed_seconds(value)
|
||||
|
||||
self._undo.push(
|
||||
SetDataCommand(
|
||||
self._data, row, column, value
|
||||
@@ -145,35 +151,24 @@ class TableModel(PamhyrTableModel):
|
||||
if row <= 0:
|
||||
return
|
||||
|
||||
target = row + 2
|
||||
|
||||
self.beginMoveRows(parent, row - 1, row - 1, parent, target)
|
||||
|
||||
self._undo_stack.push(
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self._undo.push(
|
||||
MoveCommand(
|
||||
self._data, "up", row
|
||||
)
|
||||
)
|
||||
|
||||
self.endMoveRows()
|
||||
self.update()
|
||||
|
||||
def move_down(self, index, parent=QModelIndex()):
|
||||
row = index.row()
|
||||
if row >= len(self._data):
|
||||
def move_down(self, row, parent=QModelIndex()):
|
||||
if row < 0 or row >= len(self._data) - 1:
|
||||
return
|
||||
|
||||
target = row
|
||||
|
||||
self.beginMoveRows(parent, row + 1, row + 1, parent, target)
|
||||
|
||||
self._undo_stack.push(
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self._undo.push(
|
||||
MoveCommand(
|
||||
self._data, "down", row
|
||||
)
|
||||
)
|
||||
|
||||
self.endMoveRows()
|
||||
self.update()
|
||||
|
||||
def paste(self, row, header, data):
|
||||
|
||||
@@ -31,17 +31,17 @@ class SetDataCommand(QUndoCommand):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._index = index
|
||||
self._value = self._data.get_i(index)
|
||||
self._column = column
|
||||
self._old = self._data.get_i(self._index)[self._column]
|
||||
self._old = self._value[self._column]
|
||||
_type = self._data.get_type_column(self._column)
|
||||
self._new = _type(new_value)
|
||||
|
||||
def undo(self):
|
||||
self._data._set_i_c_v(self._index, self._column, self._old)
|
||||
self._data.set_data_value(self._value, self._column, self._old)
|
||||
|
||||
def redo(self):
|
||||
self._data._set_i_c_v(self._index, self._column, self._new)
|
||||
self._data.set_data_value(self._value, self._column, self._new)
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
@@ -53,13 +53,13 @@ class AddCommand(QUndoCommand):
|
||||
self._new = None
|
||||
|
||||
def undo(self):
|
||||
self._data.delete_i([self._index])
|
||||
self._data.set_deleted([self._new], True)
|
||||
|
||||
def redo(self):
|
||||
if self._new is None:
|
||||
self._new = self._data.add(self._index)
|
||||
else:
|
||||
self._data.insert(self._index, self._new)
|
||||
self._data.set_deleted([self._new], False)
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
@@ -75,10 +75,10 @@ class DelCommand(QUndoCommand):
|
||||
self._wp.sort()
|
||||
|
||||
def undo(self):
|
||||
self._data.set_as_not_deleted_i(self._rows)
|
||||
self._data.set_deleted([wp for row, wp in self._wp], False)
|
||||
|
||||
def redo(self):
|
||||
self._data.set_as_deleted_i(self._rows)
|
||||
self._data.set_deleted([wp for row, wp in self._wp], True)
|
||||
|
||||
|
||||
class SortCommand(QUndoCommand):
|
||||
@@ -89,27 +89,17 @@ class SortCommand(QUndoCommand):
|
||||
self._reverse = _reverse
|
||||
|
||||
self._old = self._data.data
|
||||
self._indexes = None
|
||||
self._new = sorted(
|
||||
self._old,
|
||||
key=lambda value: value[0],
|
||||
reverse=self._reverse
|
||||
)
|
||||
|
||||
def undo(self):
|
||||
ll = self._data.data
|
||||
self._data.sort(
|
||||
key=lambda x: self._indexes[ll.index(x)]
|
||||
)
|
||||
self._data.reorder(self._old)
|
||||
|
||||
def redo(self):
|
||||
self._data.sort(
|
||||
_reverse=self._reverse,
|
||||
key=lambda x: x[0]
|
||||
)
|
||||
if self._indexes is None:
|
||||
self._indexes = list(
|
||||
map(
|
||||
lambda p: self._old.index(p),
|
||||
self._data.data
|
||||
)
|
||||
)
|
||||
self._old = None
|
||||
self._data.reorder(self._new)
|
||||
|
||||
|
||||
class MoveCommand(QUndoCommand):
|
||||
@@ -117,20 +107,16 @@ class MoveCommand(QUndoCommand):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._up = up == "up"
|
||||
self._i = i
|
||||
self._old = self._data.data
|
||||
self._new = self._old.copy()
|
||||
other = i - 1 if up == "up" else i + 1
|
||||
self._new[i], self._new[other] = self._new[other], self._new[i]
|
||||
|
||||
def undo(self):
|
||||
if self._up:
|
||||
self._data.move_up(self._i)
|
||||
else:
|
||||
self._data.move_down(self._i)
|
||||
self._data.reorder(self._old)
|
||||
|
||||
def redo(self):
|
||||
if self._up:
|
||||
self._data.move_up(self._i)
|
||||
else:
|
||||
self._data.move_down(self._i)
|
||||
self._data.reorder(self._new)
|
||||
|
||||
|
||||
class PasteCommand(QUndoCommand):
|
||||
@@ -141,15 +127,18 @@ class PasteCommand(QUndoCommand):
|
||||
self._row = row
|
||||
self._wps = list(wps)
|
||||
self._wps.reverse()
|
||||
self._inserted = False
|
||||
|
||||
def undo(self):
|
||||
self._data.delete_i(
|
||||
range(self._row, self._row + len(self._wps))
|
||||
)
|
||||
self._data.set_deleted(self._wps, True)
|
||||
|
||||
def redo(self):
|
||||
for wp in self._wps:
|
||||
self._data.insert(self._row, wp)
|
||||
if not self._inserted:
|
||||
for wp in self._wps:
|
||||
self._data.insert(self._row, wp)
|
||||
self._inserted = True
|
||||
else:
|
||||
self._data.set_deleted(self._wps, False)
|
||||
|
||||
|
||||
class DuplicateCommand(QUndoCommand):
|
||||
@@ -160,10 +149,15 @@ class DuplicateCommand(QUndoCommand):
|
||||
self._rows = rows
|
||||
self._wp = deepcopy(bc)
|
||||
self._wp.reverse()
|
||||
self._inserted = False
|
||||
|
||||
def undo(self):
|
||||
self._data.delete(self._wp)
|
||||
self._data.set_deleted(self._wp, True)
|
||||
|
||||
def redo(self):
|
||||
for bc in self._wp:
|
||||
self._data.insert(self._rows[0], bc)
|
||||
if not self._inserted:
|
||||
for bc in self._wp:
|
||||
self._data.insert(self._rows[0], bc)
|
||||
self._inserted = True
|
||||
else:
|
||||
self._data.set_deleted(self._wp, False)
|
||||
@@ -121,7 +121,8 @@ class EditWeatherParametersWindow(PamhyrWindow):
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad,
|
||||
opt_data=self._study.time_system
|
||||
opt_data=self._study.time_system,
|
||||
start_date=self._study.date
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
|
||||
@@ -30,7 +30,7 @@ from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
QComboBox, QMessageBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
@@ -245,6 +245,10 @@ class WeatherParametersTableModel(PamhyrTableModel):
|
||||
p for p in _edge.reach.profiles
|
||||
if p.pamhyr_id == value
|
||||
)
|
||||
if self._overlaps_existing_interval(
|
||||
row, begin_section=_begin_rk):
|
||||
self._show_overlap_warning()
|
||||
return False
|
||||
self._undo.push(
|
||||
SetBeginCommand(
|
||||
self._data, global_row, _begin_rk
|
||||
@@ -256,18 +260,32 @@ class WeatherParametersTableModel(PamhyrTableModel):
|
||||
p for p in _edge.reach.profiles
|
||||
if p.pamhyr_id == value
|
||||
)
|
||||
if self._overlaps_existing_interval(
|
||||
row, end_section=_end_rk):
|
||||
self._show_overlap_warning()
|
||||
return False
|
||||
self._undo.push(
|
||||
SetEndCommand(
|
||||
self._data, global_row, _end_rk
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "reach":
|
||||
new_reach = self._river.edge(value)
|
||||
overlaps = self._overlaps_existing_interval(
|
||||
row, reach=new_reach
|
||||
)
|
||||
current = self._lst[row]
|
||||
clear_interval = overlaps and current.reach is None
|
||||
if overlaps and not clear_interval:
|
||||
self._show_overlap_warning()
|
||||
return False
|
||||
edge = self._river.edge(value)
|
||||
if not edge.reach.profiles:
|
||||
return False
|
||||
self._undo.push(
|
||||
SetEdgeCommand(
|
||||
self._data, global_row, edge
|
||||
self._data, global_row, new_reach,
|
||||
clear_interval=clear_interval
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -283,6 +301,48 @@ class WeatherParametersTableModel(PamhyrTableModel):
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def _overlaps_existing_interval(self, row, reach=None,
|
||||
begin_section=None, end_section=None):
|
||||
current = self._lst[row]
|
||||
reach = current.reach if reach is None else reach
|
||||
if reach is None:
|
||||
return False
|
||||
|
||||
if begin_section is None:
|
||||
begin_section = (
|
||||
reach.reach.profiles[0]
|
||||
if reach is not current.reach and reach.reach.profiles
|
||||
else current.begin_section
|
||||
)
|
||||
if end_section is None:
|
||||
end_section = (
|
||||
reach.reach.profiles[-1]
|
||||
if reach is not current.reach and reach.reach.profiles
|
||||
else current.end_section
|
||||
)
|
||||
if begin_section is None or end_section is None:
|
||||
return False
|
||||
|
||||
lower, upper = sorted((begin_section.rk, end_section.rk))
|
||||
return any(
|
||||
other is not current
|
||||
and not other.is_deleted()
|
||||
and other.type == current.type
|
||||
and other.reach is reach
|
||||
and other.begin_section is not None
|
||||
and other.end_section is not None
|
||||
and max(lower, min(other.begin_rk, other.end_rk))
|
||||
< min(upper, max(other.begin_rk, other.end_rk))
|
||||
for other in self._data.lst
|
||||
)
|
||||
|
||||
def _show_overlap_warning(self):
|
||||
QMessageBox.warning(
|
||||
self._table_view,
|
||||
self._trad["msg_rk_overlap_title"],
|
||||
self._trad["msg_rk_overlap_text"]
|
||||
)
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row)
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class SetEndCommand(QUndoCommand):
|
||||
|
||||
|
||||
class SetEdgeCommand(QUndoCommand):
|
||||
def __init__(self, wps, index, edge):
|
||||
def __init__(self, wps, index, edge, clear_interval=False):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._wps = wps
|
||||
|
||||
@@ -41,6 +41,14 @@ class WeatherParametersTranslate(MainTranslate):
|
||||
"These values are applied to sections that have not been "
|
||||
"defined using time series."
|
||||
)
|
||||
self._dict["msg_rk_overlap_title"] = _translate(
|
||||
"WeatherParameters", "Overlapping interval"
|
||||
)
|
||||
self._dict["msg_rk_overlap_text"] = _translate(
|
||||
"WeatherParameters",
|
||||
"Two weather-parameter intervals of the same type cannot "
|
||||
"overlap on the same reach."
|
||||
)
|
||||
|
||||
self._dict["rk"] = self._dict["unit_rk"]
|
||||
|
||||
|
||||
+537
-283
File diff suppressed because it is too large.
Load diff
+53
-1
@@ -22,7 +22,7 @@ import logging
|
||||
import sqlite3
|
||||
import traceback
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from colorama import Fore
|
||||
@@ -239,6 +239,58 @@ def timestamp(dt: datetime):
|
||||
return dt.timestamp()
|
||||
|
||||
|
||||
SUPPORTED_DATETIME_FORMATS = (
|
||||
"%d-%m-%Y %H:%M:%S.%f",
|
||||
"%d-%m-%Y %H:%M:%S",
|
||||
"%d-%m-%Y %H:%M",
|
||||
"%d-%m-%Y",
|
||||
"%d/%m/%Y %H:%M:%S.%f",
|
||||
"%d/%m/%Y %H:%M:%S",
|
||||
"%d/%m/%Y %H:%M",
|
||||
"%d/%m/%Y",
|
||||
"%d-%m-%y %H:%M:%S",
|
||||
"%d/%m/%y %H:%M:%S",
|
||||
)
|
||||
|
||||
|
||||
def parse_datetime(value):
|
||||
"""Parse a supported date representation into a naive UTC datetime."""
|
||||
if isinstance(value, datetime):
|
||||
parsed = value
|
||||
else:
|
||||
text = str(value).strip()
|
||||
if text.endswith(("Z", "z")):
|
||||
text = text[:-1] + "+00:00"
|
||||
|
||||
try:
|
||||
parsed = datetime.fromisoformat(text)
|
||||
except ValueError as iso_error:
|
||||
for date_format in SUPPORTED_DATETIME_FORMATS:
|
||||
try:
|
||||
parsed = datetime.strptime(text, date_format)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported date format: {value!r}. Expected an ISO "
|
||||
"date/time or one of: "
|
||||
+ ", ".join(SUPPORTED_DATETIME_FORMATS)
|
||||
) from iso_error
|
||||
|
||||
# If the parsed datetime has timezone information,
|
||||
# convert it to UTC and remove the timezone info
|
||||
if parsed.tzinfo is not None:
|
||||
parsed = parsed.astimezone(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
def format_datetime(value):
|
||||
"""Return a datetime using the canonical application format."""
|
||||
return parse_datetime(value).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def date_iso_to_timestamp(date: str):
|
||||
if type(date) is str:
|
||||
return timestamp(datetime.fromisoformat(date))
|
||||
|
||||
Reference in new issue
Block a user