Skip to content

Commit 4a65c73

Browse files
committed
refactor: update GeneralizeFences docs, _excute and tests
1 parent 128aefb commit 4a65c73

File tree

3 files changed

+106
-61
lines changed

3 files changed

+106
-61
lines changed

src/geogenalg/application/generalize_fences.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,40 @@
2020

2121
@dataclass(frozen=True)
2222
class GeneralizeFences(BaseAlgorithm):
23-
"""Generalize the LineString layer representing fences."""
23+
"""Generalize lines representing fences.
2424
25-
closing_fence_area_threshold: float
25+
Reference data should contain a Point GeoDataFrame with the key
26+
"masts".
27+
28+
Output contains the generalized line fences.
29+
30+
The algorithm does the following steps:
31+
- Merges line segments
32+
- Adds helper lines to close small gaps between lines
33+
- Removes short lines within large enough enclosed areas
34+
- Removes surrounding lines of small enough enclosed areas
35+
- Removes close enough lines surrounding a mast
36+
- Removes all short lines
37+
- Simplifies all lines
38+
"""
39+
40+
closing_fence_area_threshold: float = 2000.0
2641
"""Minimum area for a fence-enclosed region."""
27-
closing_fence_area_with_mast_threshold: float
42+
closing_fence_area_with_mast_threshold: float = 8000.0
2843
"""Minimum area for a fence-enclosed region containing a mast."""
29-
fence_length_threshold: float
44+
fence_length_threshold: float = 80.0
3045
"""Minimum length for a fence line."""
31-
fence_length_threshold_in_closed_area: float
46+
fence_length_threshold_in_closed_area: float = 300.0
3247
"""Minimum length for a fence line within a closed area."""
33-
simplification_tolerance: float
48+
simplification_tolerance: float = 4.0
3449
"""Tolerance used for geometry simplification."""
35-
gap_threshold: float
50+
gap_threshold: float = 25.0
3651
"""Maximum gap between two fence lines to be connected with a helper line."""
37-
attribute_for_line_merge: str
52+
attribute_for_line_merge: str = "kohdeluokka"
3853
"""Name of the attribute to determine which line features can be merged."""
3954

4055
@override
41-
def execute(
56+
def _execute(
4257
self,
4358
data: GeoDataFrame,
4459
reference_data: dict[str, GeoDataFrame],
@@ -61,7 +76,8 @@ def execute(
6176
GeoDataFrame with key "masts" in `reference_data` contains
6277
non-point geometries.
6378
KeyError: If `reference_data` does not contain data with key
64-
"masts".
79+
"masts" or input data does not have specified
80+
`attribute_for_line_merge`.
6581
6682
"""
6783
if not check_gdf_geometry_type(data, ["LineString"]):
@@ -76,6 +92,12 @@ def execute(
7692
if not check_gdf_geometry_type(reference_data["masts"], ["Point"]):
7793
msg = "Masts data should be a Point GeoDataFrame."
7894
raise GeometryTypeError(msg)
95+
if self.attribute_for_line_merge not in data.columns:
96+
msg = (
97+
"Specified `attribute_for_line_merge` "
98+
+ f"({self.attribute_for_line_merge}) not found in input GeoDataFrame."
99+
)
100+
raise KeyError(msg)
79101

80102
result_gdf = data.copy()
81103

src/geogenalg/main.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,6 @@ def get_class_attribute_docstrings(cls: type[Any]) -> dict[str, str]:
195195
),
196196
]
197197

198-
GeoPackageOptionMandatory = Annotated[
199-
GeoPackageURI,
200-
typer.Option(
201-
parser=geopackage_uri,
202-
help=GEOPACKAGE_URI_HELP,
203-
),
204-
]
205-
206198
ReferenceGeoPackageList = Annotated[
207199
list[NamedGeoPackageURI],
208200
typer.Option(

test/application/test_generalize_fences.py

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,26 @@
1515

1616
from geogenalg.application.generalize_fences import GeneralizeFences
1717
from geogenalg.core.exceptions import GeometryTypeError
18+
from geogenalg.utility.dataframe_processing import read_gdf_from_file_and_set_index
1819

20+
UNIQUE_ID_COLUMN = "mtk_id"
1921

20-
def _instantiate_algorithm() -> GeneralizeFences:
21-
return GeneralizeFences(
22+
23+
def test_generalize_fences_50k(testdata_path: Path) -> None:
24+
"""Test generalizing fences with parameters to the 1: 50 000 scale."""
25+
input_path = testdata_path / "fences_rovaniemi.gpkg"
26+
input_data = read_gdf_from_file_and_set_index(
27+
input_path,
28+
UNIQUE_ID_COLUMN,
29+
layer="mtk_fences",
30+
)
31+
32+
masts_data = read_file(input_path, layer="mtk_masts")
33+
34+
temp_dir = TemporaryDirectory()
35+
output_path = temp_dir.name + "/generalized_fences.gpkg"
36+
37+
algorithm = GeneralizeFences(
2238
closing_fence_area_threshold=2000,
2339
closing_fence_area_with_mast_threshold=8000,
2440
fence_length_threshold=80,
@@ -28,74 +44,89 @@ def _instantiate_algorithm() -> GeneralizeFences:
2844
attribute_for_line_merge="kohdeluokka",
2945
)
3046

47+
control = read_gdf_from_file_and_set_index(
48+
input_path,
49+
UNIQUE_ID_COLUMN,
50+
layer="generalized_fences",
51+
)
52+
algorithm.execute(input_data, reference_data={"masts": masts_data}).to_file(
53+
output_path, layer="result"
54+
)
3155

32-
def test_generalize_fences_50k(
33-
testdata_path: Path,
34-
) -> None:
35-
"""
36-
Test generalizing fences with parameters to the 1: 50 000 scale
37-
"""
38-
source_path = testdata_path / "fences_rovaniemi.gpkg"
39-
40-
temp_dir = TemporaryDirectory()
41-
output_path = temp_dir.name + "/generalized_fences.gpkg"
42-
43-
fences_gdf = read_file(source_path, layer="mtk_fences")
44-
masts_gdf = read_file(source_path, layer="mtk_masts")
45-
46-
algorithm = _instantiate_algorithm()
47-
48-
result = algorithm.execute(fences_gdf, reference_data={"masts": masts_gdf})
49-
50-
assert result is not None
51-
52-
result.to_file(output_path, layer="fences_50k")
53-
54-
control_fences: GeoDataFrame = read_file(source_path, layer="generalized_fences")
55-
56-
result_fences = read_file(output_path, layer="fences_50k")
57-
58-
control_fences = control_fences.sort_values("geometry").reset_index(drop=True)
59-
result_fences = result_fences.sort_values("geometry").reset_index(drop=True)
56+
result = read_gdf_from_file_and_set_index(output_path, "index", layer="result")
6057

61-
assert_geodataframe_equal(control_fences, result_fences, check_index_type=False)
58+
control = control.sort_values("geometry").reset_index(drop=True)
59+
result = result.sort_values("geometry").reset_index(drop=True)
60+
assert_geodataframe_equal(control, result)
6261

6362

6463
def test_generalize_fences_50k_invalid_geometry_type() -> None:
65-
fences_gdf = GeoDataFrame({"id": [1]}, geometry=[Point(0, 0)])
64+
input_data = GeoDataFrame({"id": [1]}, geometry=[Point(0, 0)])
6665

67-
algorithm = _instantiate_algorithm()
66+
algorithm = GeneralizeFences(
67+
closing_fence_area_threshold=2000,
68+
closing_fence_area_with_mast_threshold=8000,
69+
fence_length_threshold=80,
70+
fence_length_threshold_in_closed_area=300,
71+
simplification_tolerance=4,
72+
gap_threshold=25,
73+
attribute_for_line_merge="kohdeluokka",
74+
)
6875

6976
with pytest.raises(
7077
GeometryTypeError,
7178
match=r"GeneralizeFences works only with LineString geometries.",
7279
):
73-
algorithm.execute(data=fences_gdf, reference_data={})
80+
algorithm.execute(data=input_data, reference_data={})
7481

7582

7683
def test_generalize_fences_50k_missing_masts_data(testdata_path: Path) -> None:
77-
source_path = testdata_path / "fences_rovaniemi.gpkg"
78-
fences_gdf = read_file(source_path, layer="mtk_fences")
84+
input_path = testdata_path / "fences_rovaniemi.gpkg"
85+
input_data = read_gdf_from_file_and_set_index(
86+
input_path,
87+
UNIQUE_ID_COLUMN,
88+
layer="mtk_fences",
89+
)
7990

80-
algorithm = _instantiate_algorithm()
91+
algorithm = GeneralizeFences(
92+
closing_fence_area_threshold=2000,
93+
closing_fence_area_with_mast_threshold=8000,
94+
fence_length_threshold=80,
95+
fence_length_threshold_in_closed_area=300,
96+
simplification_tolerance=4,
97+
gap_threshold=25,
98+
attribute_for_line_merge="kohdeluokka",
99+
)
81100

82101
with pytest.raises(
83102
KeyError,
84103
match=r"GeneralizeFences requires mast Point GeoDataFrame in reference_data with key 'masts'.",
85104
):
86-
algorithm.execute(data=fences_gdf, reference_data={})
105+
algorithm.execute(data=input_data, reference_data={})
87106

88107

89108
def test_generalize_fences_50k_invalid_geometry_type_masts(testdata_path: Path) -> None:
90-
source_path = testdata_path / "fences_rovaniemi.gpkg"
91-
fences_gdf = read_file(source_path, layer="mtk_fences")
92-
masts_gdf = GeoDataFrame(
109+
input_path = testdata_path / "fences_rovaniemi.gpkg"
110+
input_data = read_gdf_from_file_and_set_index(
111+
input_path,
112+
UNIQUE_ID_COLUMN,
113+
layer="mtk_fences",
114+
)
115+
masts_data = GeoDataFrame(
93116
{"id": [1]}, geometry=[LineString((Point(0, 0), Point(1, 0)))]
94117
)
95118

96-
algorithm = _instantiate_algorithm()
119+
algorithm = GeneralizeFences(
120+
closing_fence_area_threshold=2000,
121+
closing_fence_area_with_mast_threshold=8000,
122+
fence_length_threshold=80,
123+
fence_length_threshold_in_closed_area=300,
124+
simplification_tolerance=4,
125+
gap_threshold=25,
126+
attribute_for_line_merge="kohdeluokka",
127+
)
97128

98129
with pytest.raises(
99130
GeometryTypeError, match=r"Masts data should be a Point GeoDataFrame."
100131
):
101-
algorithm.execute(data=fences_gdf, reference_data={"masts": masts_gdf})
132+
algorithm.execute(data=input_data, reference_data={"masts": masts_data})

0 commit comments

Comments
 (0)