Skip to content

Commit 4514119

Browse files
authored
Merge pull request #313 from statisticsnorway/nwa-speed
undo nwa changes because of floating point precision...
2 parents cdadf60 + adfd92b commit 4514119

File tree

7 files changed

+46
-66
lines changed

7 files changed

+46
-66
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "ssb-sgis"
3-
version = "1.3.8"
3+
version = "1.3.9"
44
description = "GIS functions used at Statistics Norway."
55
authors = ["Morten Letnes <morten.letnes@ssb.no>"]
66
license = "MIT"

src/sgis/geopandas_tools/general.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,8 +915,8 @@ def _prepare_make_edge_cols_simple(
915915
lines: GeoDataFrame,
916916
) -> tuple[GeoDataFrame, GeoDataFrame]:
917917
"""Faster version of _prepare_make_edge_cols."""
918+
lines = lines[(lines.geometry.notna()) & (~lines.geometry.is_empty)]
918919
endpoints = lines.geometry.boundary.explode(ignore_index=False)
919-
920920
if len(lines) and len(endpoints) / len(lines) != 2:
921921
raise ValueError(
922922
"The lines should have only two endpoints each. "

src/sgis/networkanalysis/_points.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,17 @@ def _get_edges_and_weights(
8484

8585
search_factor_multiplier = 1 + rules.search_factor / 100
8686
distances = distances.loc[
87-
lambda df: (df.distance <= rules.search_tolerance)
87+
lambda df: (df["distance"] <= rules.search_tolerance)
8888
& (
89-
df.distance
90-
<= df.distance_min * search_factor_multiplier + rules.search_factor
89+
df["distance"]
90+
<= df["distance_min"] * search_factor_multiplier + rules.search_factor
9191
)
9292
]
9393

9494
edges = self._make_edges(distances, from_col=from_col, to_col=to_col)
9595

9696
weighs = self._convert_distance_to_weight(
97-
distances=list(distances.distance), rules=rules
97+
distances=list(distances["distance"]), rules=rules
9898
)
9999

100100
return edges, weighs

src/sgis/networkanalysis/_service_area.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ def _service_area(
9797
partly_within["remaining_distance"] = break_ - partly_within[weight]
9898

9999
split_lines = _split_lines(partly_within, directed)
100-
101100
split_lines = make_edge_wkt_cols(split_lines)
102101

103102
# select the cutted lines shorter than the cut distance that intersects

src/sgis/networkanalysis/network.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,16 @@ def _nodes_are_up_to_date(self) -> bool:
139139
"""
140140
if not hasattr(self, "_nodes"):
141141
return False
142-
new_or_missing = (~self.gdf.source.isin(self._nodes.node_id)) | (
143-
~self.gdf.target.isin(self._nodes.node_id)
142+
new_or_missing = (~self.gdf["source"].isin(self._nodes["node_id"])) | (
143+
~self.gdf["target"].isin(self._nodes["node_id"])
144144
)
145145

146146
if any(new_or_missing):
147147
return False
148148

149149
removed = ~(
150-
(self._nodes.node_id.isin(self.gdf.source))
151-
| (self._nodes.node_id.isin(self.gdf.target))
150+
(self._nodes["node_id"].isin(self.gdf["source"]))
151+
| (self._nodes["node_id"].isin(self.gdf["target"]))
152152
)
153153

154154
if any(removed):
@@ -208,9 +208,12 @@ def _warn_if_undirected(self) -> None:
208208
warnings.warn(mess, stacklevel=2)
209209

210210
@property
211-
def percent_bidirectional(self) -> float:
211+
def percent_bidirectional(self) -> float | None:
212212
"""The percentage of lines that appear in both directions."""
213-
return self._percent_bidirectional
213+
try:
214+
return self._percent_bidirectional
215+
except AttributeError:
216+
return None
214217

215218
def copy(self) -> "Network":
216219
"""Returns a shallow copy of the class instance."""
@@ -224,7 +227,7 @@ def __repr__(self) -> str:
224227
"""The print representation."""
225228
cl = self.__class__.__name__
226229
km = int(sum(self.gdf.length) / 1000)
227-
return f"{cl}({km} km, percent_bidirectional={self._percent_bidirectional})"
230+
return f"{cl}({km} km, percent_bidirectional={self.percent_bidirectional})"
228231

229232
def __len__(self) -> int:
230233
"""Number og rows in the GeoDataFrame."""

src/sgis/networkanalysis/networkanalysis.py

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
from shapely import force_2d
2121

2222
from ..geopandas_tools.general import _push_geom_col
23-
from ..geopandas_tools.sfilter import sfilter_inverse
2423
from ._get_route import _get_k_routes
2524
from ._get_route import _get_route
2625
from ._get_route import _get_route_frequencies
@@ -31,8 +30,6 @@
3130
from .cutting_lines import split_lines_by_nearest_point
3231
from .network import Network
3332
from .networkanalysisrules import NetworkAnalysisRules
34-
from .nodes import _map_node_ids_from_wkt
35-
from .nodes import make_node_ids
3633

3734

3835
class NetworkAnalysis:
@@ -413,8 +410,8 @@ def od_cost_matrix(
413410
if lines:
414411
results = _push_geom_col(results)
415412

416-
if self.rules.split_lines:
417-
self._unsplit_network()
413+
# if self.rules.split_lines:
414+
# self._unsplit_network()
418415

419416
if self._log:
420417
minutes_elapsed = round((perf_counter() - time_) / 60, 1)
@@ -650,8 +647,8 @@ def multiindex_mapper(x: tuple[int, int]) -> tuple[int, int]:
650647

651648
results.geometry = force_2d(results.geometry)
652649

653-
if self.rules.split_lines:
654-
self._unsplit_network()
650+
# if self.rules.split_lines:
651+
# self._unsplit_network()
655652

656653
if self._log:
657654
minutes_elapsed = round((perf_counter() - time_) / 60, 1)
@@ -760,8 +757,8 @@ def get_route(
760757
results["origin"] = results["origin"].map(self.origins.idx_dict)
761758
results["destination"] = results["destination"].map(self.destinations.idx_dict)
762759

763-
if self.rules.split_lines:
764-
self._unsplit_network()
760+
# if self.rules.split_lines:
761+
# self._unsplit_network()
765762

766763
if self._log:
767764
minutes_elapsed = round((perf_counter() - time_) / 60, 1)
@@ -928,8 +925,8 @@ def get_k_routes(
928925
if isinstance(results, GeoDataFrame):
929926
results = _push_geom_col(results)
930927

931-
if self.rules.split_lines:
932-
self._unsplit_network()
928+
# if self.rules.split_lines:
929+
# self._unsplit_network()
933930

934931
if self._log:
935932
minutes_elapsed = round((perf_counter() - time_) / 60, 1)
@@ -1054,8 +1051,8 @@ def service_area(
10541051

10551052
results = _push_geom_col(results)
10561053

1057-
if self.rules.split_lines:
1058-
self._unsplit_network()
1054+
# if self.rules.split_lines:
1055+
# self._unsplit_network()
10591056

10601057
if self._log:
10611058
minutes_elapsed = round((perf_counter() - time_) / 60, 1)
@@ -1183,8 +1180,8 @@ def precice_service_area(
11831180

11841181
results = _push_geom_col(results)
11851182

1186-
if self.rules.split_lines:
1187-
self._unsplit_network()
1183+
# if self.rules.split_lines:
1184+
# self._unsplit_network()
11881185

11891186
if self._log:
11901187
minutes_elapsed = round((perf_counter() - time_) / 60, 1)
@@ -1371,6 +1368,8 @@ def _prepare_network_analysis(
13711368
"rowwise=True"
13721369
)
13731370

1371+
self._unsplit_network()
1372+
13741373
self.network.gdf = self.rules._validate_weight(self.network.gdf)
13751374

13761375
self.origins = Origins(origins)
@@ -1383,6 +1382,7 @@ def _prepare_network_analysis(
13831382
self.destinations._make_temp_idx(
13841383
start=max(self.origins.gdf["temp_idx"].astype(int)) + 1
13851384
)
1385+
13861386
else:
13871387
self.destinations = None
13881388

@@ -1413,12 +1413,13 @@ def _get_edges_and_weights(
14131413
"""
14141414
if self.rules.split_lines:
14151415
self._split_lines()
1416+
self.network._make_node_ids()
14161417
self.origins._make_temp_idx(
1417-
start=max(self.network.nodes.node_id.astype(int)) + 1
1418+
start=max(self.network.nodes["node_id"].astype(int)) + 1
14181419
)
14191420
if self.destinations is not None:
14201421
self.destinations._make_temp_idx(
1421-
start=max(self.origins.gdf.temp_idx.astype(int)) + 1
1422+
start=max(self.origins.gdf["temp_idx"].astype(int)) + 1
14221423
)
14231424

14241425
edges: list[tuple[str, str]] = self.network.get_edges()
@@ -1427,7 +1428,6 @@ def _get_edges_and_weights(
14271428

14281429
self.network.gdf["src_tgt_wt"] = self.network._create_edge_ids(edges, weights)
14291430

1430-
# add edges between origins+destinations to the network nodes
14311431
edges_start, weights_start = self.origins._get_edges_and_weights(
14321432
nodes=self.network.nodes,
14331433
rules=self.rules,
@@ -1484,7 +1484,7 @@ def _split_lines(self) -> None:
14841484

14851485
points = points.drop_duplicates(points.geometry.name)
14861486

1487-
self.network.gdf["_meters2"] = self.network.gdf.length
1487+
self.network.gdf["meters_"] = self.network.gdf.length
14881488

14891489
# create an id from before the split, used to revert the split later
14901490
self.network.gdf["temp_idx__"] = range(len(self.network.gdf))
@@ -1497,39 +1497,16 @@ def _split_lines(self) -> None:
14971497
)
14981498

14991499
# save the unsplit lines for later
1500-
splitted = lines.loc[lines["splitted"] == 1]
1500+
splitted = lines.loc[lines["splitted"] == 1, "temp_idx__"]
15011501
self.network._not_splitted = self.network.gdf.loc[
1502-
lambda x: x["temp_idx__"].isin(splitted["temp_idx__"])
1502+
self.network.gdf["temp_idx__"].isin(splitted)
15031503
]
15041504

1505-
new_lines, new_nodes = make_node_ids(splitted)
1506-
new_nodes = sfilter_inverse(new_nodes, self.network.nodes.buffer(1e-5))
1507-
new_nodes["node_id"] = (
1508-
new_nodes["node_id"].astype(int) + len(self.network.nodes) + 1
1509-
).astype(str)
1510-
self.network._new_node_ids = list(new_nodes["node_id"])
1511-
15121505
# adjust weight to new length
1513-
new_lines[self.rules.weight] = new_lines[self.rules.weight] * (
1514-
new_lines.length / new_lines["_meters2"]
1515-
)
1516-
self.network._nodes = pd.concat(
1517-
[self.network._nodes, new_nodes],
1518-
ignore_index=True,
1519-
)
1520-
1521-
lines = pd.concat(
1522-
[
1523-
self.network.gdf.loc[
1524-
lambda x: ~x["temp_idx__"].isin(splitted["temp_idx__"])
1525-
],
1526-
new_lines,
1527-
],
1528-
ignore_index=True,
1506+
lines[self.rules.weight] = lines[self.rules.weight] * (
1507+
lines.length / lines["meters_"]
15291508
)
15301509

1531-
lines = _map_node_ids_from_wkt(lines, self.network._nodes)
1532-
15331510
self.network.gdf = lines
15341511

15351512
def _unsplit_network(self):
@@ -1540,10 +1517,7 @@ def _unsplit_network(self):
15401517
self.network.gdf = pd.concat(
15411518
[lines, self.network._not_splitted], ignore_index=True
15421519
).drop("temp_idx__", axis=1)
1543-
self.network._nodes = self.network._nodes[
1544-
lambda x: ~x["node_id"].isin(self.network._new_node_ids)
1545-
]
1546-
del self.network._not_splitted, self.network._new_node_ids
1520+
del self.network._not_splitted
15471521

15481522
@staticmethod
15491523
def _make_graph(
@@ -1587,7 +1561,7 @@ def _graph_is_up_to_date(self) -> bool:
15871561
for points in ["origins", "destinations"]:
15881562
if self[points] is None:
15891563
continue
1590-
if not hasattr(self, points) or self[points] is None:
1564+
if points not in self.wkts:
15911565
return False
15921566
if self._points_have_changed(self[points].gdf, what=points):
15931567
return False
@@ -1603,7 +1577,7 @@ def _points_have_changed(self, points: GeoDataFrame, what: str) -> bool:
16031577
if not np.array_equal(self.wkts[what], points.geometry.to_wkt().values):
16041578
return True
16051579

1606-
if not all(x in self.graph.vs["name"] for x in list(points.temp_idx.values)):
1580+
if not all(x in self.graph.vs["name"] for x in list(points["temp_idx"].values)):
16071581
return True
16081582

16091583
return False
@@ -1617,6 +1591,8 @@ def _update_wkts(self) -> None:
16171591
"""
16181592
self.wkts = {}
16191593

1594+
self.wkts["network"] = self.network.gdf.geometry.to_wkt().values
1595+
16201596
if not hasattr(self, "origins"):
16211597
return
16221598

src/sgis/networkanalysis/nodes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,6 @@ def _map_node_ids_from_wkt(lines, nodes, wkt: bool = True) -> GeoDataFrame:
9595
}
9696
lines["source"] = lines[geomcol1].map(id_dict)
9797
lines["target"] = lines[geomcol2].map(id_dict)
98+
assert lines["source"].notna().all(), lines.loc[lines["source"].isna(), geomcol1]
99+
assert lines["target"].notna().all(), lines.loc[lines["target"].isna(), geomcol2]
98100
return lines

0 commit comments

Comments
 (0)