ssb-sgis 0.3.9__tar.gz → 0.3.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/PKG-INFO +1 -4
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/pyproject.toml +4 -4
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/__init__.py +13 -4
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/bounds.py +236 -37
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/buffer_dissolve_explode.py +41 -9
- ssb_sgis-0.3.11/src/sgis/geopandas_tools/cleaning.py +683 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/conversion.py +2 -2
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/duplicates.py +22 -18
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/general.py +87 -9
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/overlay.py +12 -4
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/polygon_operations.py +83 -8
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/helpers.py +8 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/io/dapla_functions.py +9 -6
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/explore.py +76 -1
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/maps.py +11 -8
- ssb_sgis-0.3.9/src/sgis/geopandas_tools/cleaning.py +0 -331
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/LICENSE +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/README.md +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/exceptions.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/__init__.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/centerlines.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/geocoding.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/geometry_types.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/neighbors.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/point_operations.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/polygons_as_rings.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/geopandas_tools/sfilter.py +53 -53
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/io/_is_dapla.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/io/opener.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/io/read_parquet.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/io/write_municipality_data.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/__init__.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/examine.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/httpserver.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/legend.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/map.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/thematicmap.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/maps/tilesources.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/__init__.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/_get_route.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/_od_cost_matrix.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/_points.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/_service_area.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/closing_network_holes.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/cutting_lines.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/directednetwork.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/finding_isolated_networks.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/network.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/networkanalysis.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/networkanalysisrules.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/nodes.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/networkanalysis/traveling_salesman.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/parallel/parallel.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/py.typed +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/raster/__init__.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/raster/base.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/raster/elevationraster.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/raster/raster.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/raster/sentinel.py +0 -0
- {ssb_sgis-0.3.9 → ssb_sgis-0.3.11}/src/sgis/raster/zonal.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ssb-sgis
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.11
|
|
4
4
|
Summary: GIS functions used at Statistics Norway.
|
|
5
5
|
Home-page: https://github.com/statisticsnorway/ssb-sgis
|
|
6
6
|
License: MIT
|
|
@@ -24,18 +24,15 @@ Requires-Dist: jenkspy (>=0.3.2)
|
|
|
24
24
|
Requires-Dist: mapclassify (>=2.5.0)
|
|
25
25
|
Requires-Dist: matplotlib (>=3.7.0)
|
|
26
26
|
Requires-Dist: networkx (>=3.0)
|
|
27
|
-
Requires-Dist: numba (>=0.57.1,<0.58.0)
|
|
28
27
|
Requires-Dist: numpy (>=1.24.2)
|
|
29
28
|
Requires-Dist: pandas (>=1.5.3)
|
|
30
29
|
Requires-Dist: pip (==23.2.1)
|
|
31
30
|
Requires-Dist: pyarrow (>=11.0.0)
|
|
32
31
|
Requires-Dist: rasterio (>=1.3.8,<2.0.0)
|
|
33
32
|
Requires-Dist: requests (>=2.28.2)
|
|
34
|
-
Requires-Dist: rioxarray (>=0.14.1,<0.15.0)
|
|
35
33
|
Requires-Dist: rtree (>=1.0.1,<2.0.0)
|
|
36
34
|
Requires-Dist: scikit-learn (>=1.2.1)
|
|
37
35
|
Requires-Dist: shapely (>=2.0.1)
|
|
38
|
-
Requires-Dist: xarray (==2023.8.0)
|
|
39
36
|
Requires-Dist: xyzservices (>=2023.2.0)
|
|
40
37
|
Project-URL: Changelog, https://github.com/statisticsnorway/ssb-sgis/releases
|
|
41
38
|
Project-URL: Repository, https://github.com/statisticsnorway/ssb-sgis
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "ssb-sgis"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.11"
|
|
4
4
|
description = "GIS functions used at Statistics Norway."
|
|
5
5
|
authors = ["Statistics Norway <ort@ssb.no>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -37,9 +37,6 @@ ipython = ">=8.13.2"
|
|
|
37
37
|
rtree = "^1.0.1"
|
|
38
38
|
geocoder = "^1.38.1"
|
|
39
39
|
rasterio = "^1.3.8"
|
|
40
|
-
xarray = "2023.8.0"
|
|
41
|
-
rioxarray = "^0.14.1"
|
|
42
|
-
numba = "^0.57.1"
|
|
43
40
|
pip = "23.2.1"
|
|
44
41
|
|
|
45
42
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -47,6 +44,9 @@ black = {extras = ["d", "jupyter"], version = ">=23.1.0"}
|
|
|
47
44
|
coverage = {extras = ["toml"], version = ">=7.2.1"}
|
|
48
45
|
darglint = ">=1.8.1"
|
|
49
46
|
deptry = ">=0.8.0"
|
|
47
|
+
numba = "^0.57.1"
|
|
48
|
+
xarray = "2023.8.0"
|
|
49
|
+
rioxarray = "^0.14.1"
|
|
50
50
|
flake8 = ">=6.0.0"
|
|
51
51
|
flake8-bandit = ">=4.1.1"
|
|
52
52
|
flake8-bugbear = ">=23.2.13"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .geopandas_tools.bounds import (
|
|
2
|
+
Gridlooper,
|
|
2
3
|
bounds_to_points,
|
|
3
4
|
bounds_to_polygon,
|
|
4
5
|
get_total_bounds,
|
|
@@ -18,11 +19,15 @@ from .geopandas_tools.buffer_dissolve_explode import (
|
|
|
18
19
|
dissexp_by_cluster,
|
|
19
20
|
)
|
|
20
21
|
from .geopandas_tools.centerlines import get_rough_centerlines
|
|
21
|
-
from .geopandas_tools.cleaning import
|
|
22
|
+
from .geopandas_tools.cleaning import (
|
|
23
|
+
coverage_clean,
|
|
24
|
+
remove_spikes,
|
|
25
|
+
split_spiky_polygons,
|
|
26
|
+
)
|
|
22
27
|
from .geopandas_tools.conversion import (
|
|
23
28
|
coordinate_array,
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
from_4326,
|
|
30
|
+
to_4326,
|
|
26
31
|
to_gdf,
|
|
27
32
|
to_geoseries,
|
|
28
33
|
to_shapely,
|
|
@@ -38,9 +43,12 @@ from .geopandas_tools.general import (
|
|
|
38
43
|
get_common_crs,
|
|
39
44
|
get_grouped_centroids,
|
|
40
45
|
random_points,
|
|
46
|
+
random_points_in_polygons,
|
|
41
47
|
rename_geometry_if,
|
|
42
48
|
sort_large_first,
|
|
43
49
|
sort_long_first,
|
|
50
|
+
sort_short_first,
|
|
51
|
+
sort_small_first,
|
|
44
52
|
to_lines,
|
|
45
53
|
)
|
|
46
54
|
from .geopandas_tools.geocoding import address_to_coords, address_to_gdf
|
|
@@ -63,6 +71,7 @@ from .geopandas_tools.polygon_operations import (
|
|
|
63
71
|
PolygonsAsRings,
|
|
64
72
|
close_all_holes,
|
|
65
73
|
close_small_holes,
|
|
74
|
+
close_thin_holes,
|
|
66
75
|
eliminate_by_largest,
|
|
67
76
|
eliminate_by_longest,
|
|
68
77
|
eliminate_by_smallest,
|
|
@@ -116,7 +125,7 @@ from .raster.sentinel import Sentinel2
|
|
|
116
125
|
|
|
117
126
|
|
|
118
127
|
try:
|
|
119
|
-
from .io.dapla_functions import check_files,
|
|
128
|
+
from .io.dapla_functions import check_files, read_geopandas, write_geopandas
|
|
120
129
|
from .io.write_municipality_data import write_municipality_data
|
|
121
130
|
except ImportError:
|
|
122
131
|
pass
|
|
@@ -1,28 +1,178 @@
|
|
|
1
|
+
import functools
|
|
1
2
|
import numbers
|
|
2
|
-
from typing import Any
|
|
3
3
|
from collections.abc import Callable, Collection, Mapping
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
4
6
|
|
|
5
7
|
import geopandas as gpd
|
|
6
8
|
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
7
10
|
from geopandas import GeoDataFrame, GeoSeries
|
|
8
11
|
from pandas.api.types import is_dict_like
|
|
9
12
|
from shapely import Geometry, box, extract_unique_points
|
|
10
13
|
from shapely.geometry import Polygon
|
|
11
14
|
|
|
15
|
+
from ..parallel.parallel import Parallel
|
|
12
16
|
from .conversion import to_gdf
|
|
13
17
|
from .general import clean_clip, is_bbox_like
|
|
14
18
|
|
|
15
19
|
|
|
20
|
+
@dataclass
|
|
21
|
+
class Gridlooper:
|
|
22
|
+
"""Run functions in a loop cellwise based on a grid.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
gridsize: Size of the grid cells in units of the crs (meters, degrees).
|
|
26
|
+
mask: Geometry object to create a grid around.
|
|
27
|
+
gridbuffer: Units to buffer each gridcell by. For edge cases.
|
|
28
|
+
Defaults to 0.
|
|
29
|
+
clip: If True (default) geometries are clipped by the grid cells.
|
|
30
|
+
If False, all geometries that intersect will be selected in each iteration.
|
|
31
|
+
verbose: Whether to print progress. Defaults to False.
|
|
32
|
+
keep_geom_type: Whether to keep only the input geometry types after clipping.
|
|
33
|
+
Defaults to True.
|
|
34
|
+
|
|
35
|
+
Examples
|
|
36
|
+
--------
|
|
37
|
+
|
|
38
|
+
Get some points and some polygons.
|
|
39
|
+
|
|
40
|
+
>>> import sgis as sg
|
|
41
|
+
>>> points = sg.read_parquet_url("https://media.githubusercontent.com/media/statisticsnorway/ssb-sgis/main/tests/testdata/points_oslo.parquet")
|
|
42
|
+
>>> points["idx"] = points.index
|
|
43
|
+
>>> buffered = sg.buff(points, 100)
|
|
44
|
+
>>> buffered
|
|
45
|
+
idx geometry
|
|
46
|
+
0 0 POLYGON ((263222.700 6651184.900, 263222.651 6...
|
|
47
|
+
1 1 POLYGON ((272556.100 6653369.500, 272556.051 6...
|
|
48
|
+
2 2 POLYGON ((270182.300 6653032.700, 270182.251 6...
|
|
49
|
+
3 3 POLYGON ((259904.800 6650339.700, 259904.751 6...
|
|
50
|
+
4 4 POLYGON ((272976.200 6652889.100, 272976.151 6...
|
|
51
|
+
.. ... ...
|
|
52
|
+
995 995 POLYGON ((266901.700 6647844.500, 266901.651 6...
|
|
53
|
+
996 996 POLYGON ((261374.000 6653593.400, 261373.951 6...
|
|
54
|
+
997 997 POLYGON ((263642.900 6645427.000, 263642.851 6...
|
|
55
|
+
998 998 POLYGON ((269326.700 6650628.000, 269326.651 6...
|
|
56
|
+
999 999 POLYGON ((264670.300 6644239.500, 264670.251 6...
|
|
57
|
+
|
|
58
|
+
[1000 rows x 2 columns]
|
|
59
|
+
|
|
60
|
+
Instantiate a gridlooper.
|
|
61
|
+
|
|
62
|
+
>>> looper = sg.Gridlooper(gridsize=200, mask=buffered, parallelizer=sg.Parallel(1, backend="multiprocessing"))
|
|
63
|
+
|
|
64
|
+
Run the function clean_overlay in a gridloop.
|
|
65
|
+
|
|
66
|
+
>>> resultslist = looper.run(
|
|
67
|
+
... sg.clean_overlay,
|
|
68
|
+
... points,
|
|
69
|
+
... buffered,
|
|
70
|
+
... )
|
|
71
|
+
>>> type(resultslist)
|
|
72
|
+
list
|
|
73
|
+
|
|
74
|
+
>>> results = pd.concat(resultslist, ignore_index=True)
|
|
75
|
+
>>> results
|
|
76
|
+
idx_1 idx_2 geometry
|
|
77
|
+
0 220 220 POINT (254575.200 6661631.500)
|
|
78
|
+
1 735 735 POINT (256337.400 6649931.700)
|
|
79
|
+
2 575 575 POINT (256369.200 6650413.300)
|
|
80
|
+
3 39 39 POINT (256142.300 6650526.300)
|
|
81
|
+
4 235 235 POINT (256231.300 6650720.200)
|
|
82
|
+
... ... ... ...
|
|
83
|
+
1481 711 795 POINT (272845.500 6655048.800)
|
|
84
|
+
1482 711 711 POINT (272845.500 6655048.800)
|
|
85
|
+
1483 757 757 POINT (273507.600 6652806.600)
|
|
86
|
+
1484 457 457 POINT (273524.400 6652979.900)
|
|
87
|
+
1485 284 284 POINT (273650.800 6653000.500)
|
|
88
|
+
|
|
89
|
+
[1486 rows x 3 columns]
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
gridsize: int
|
|
94
|
+
mask: GeoDataFrame | GeoSeries | Geometry
|
|
95
|
+
gridbuffer: int = 0
|
|
96
|
+
clip: bool = True
|
|
97
|
+
keep_geom_type: bool = True
|
|
98
|
+
verbose: bool = False
|
|
99
|
+
parallelizer: Parallel | None = None
|
|
100
|
+
|
|
101
|
+
def __post_init__(self):
|
|
102
|
+
if not isinstance(self.mask, GeoDataFrame):
|
|
103
|
+
self.mask = to_gdf(self.mask)
|
|
104
|
+
|
|
105
|
+
def run(self, func: Callable, *args, **kwargs):
|
|
106
|
+
intersects_mask = lambda df: df.index.isin(df.sjoin(self.mask).index)
|
|
107
|
+
grid: GeoSeries = (
|
|
108
|
+
make_grid(self.mask, gridsize=self.gridsize).loc[intersects_mask].geometry
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
n = len(grid)
|
|
112
|
+
|
|
113
|
+
buffered_grid = grid.buffer(self.gridbuffer, resolution=1, join_style=2)
|
|
114
|
+
|
|
115
|
+
if self.parallelizer is not None:
|
|
116
|
+
func_with_clip = functools.partial(
|
|
117
|
+
_clip_and_run_func,
|
|
118
|
+
func=func,
|
|
119
|
+
args=args,
|
|
120
|
+
kwargs=kwargs,
|
|
121
|
+
keep_geom_type=self.keep_geom_type,
|
|
122
|
+
clip=self.clip,
|
|
123
|
+
)
|
|
124
|
+
results = self.parallelizer.map(func_with_clip, buffered_grid)
|
|
125
|
+
if not self.gridbuffer or not self.clip:
|
|
126
|
+
return results
|
|
127
|
+
out = []
|
|
128
|
+
for cell_res, unbuffered in zip(results, grid, strict=True):
|
|
129
|
+
out.append(
|
|
130
|
+
_clip_back_to_unbuffered_grid(
|
|
131
|
+
cell_res, unbuffered, self.keep_geom_type
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
return out
|
|
135
|
+
|
|
136
|
+
results = []
|
|
137
|
+
for i, (unbuffered, buffered) in enumerate(zip(grid, buffered_grid)):
|
|
138
|
+
cell_kwargs = {
|
|
139
|
+
key: _clip_if_isinstance(
|
|
140
|
+
value, buffered, self.keep_geom_type, self.clip
|
|
141
|
+
)
|
|
142
|
+
for key, value in kwargs.items()
|
|
143
|
+
}
|
|
144
|
+
cell_args = tuple(
|
|
145
|
+
_clip_if_isinstance(value, buffered, self.keep_geom_type, self.clip)
|
|
146
|
+
for value in args
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
cell_res = func(*cell_args, **cell_kwargs)
|
|
150
|
+
|
|
151
|
+
# clip back to original
|
|
152
|
+
if self.gridbuffer and self.clip:
|
|
153
|
+
cell_res = _clip_back_to_unbuffered_grid(
|
|
154
|
+
cell_res, unbuffered, self.keep_geom_type
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
results.append(cell_res)
|
|
158
|
+
|
|
159
|
+
if self.verbose:
|
|
160
|
+
print(f"Done with {i+1} of {n} grid cells", end="\r")
|
|
161
|
+
|
|
162
|
+
return results
|
|
163
|
+
|
|
164
|
+
|
|
16
165
|
def gridloop(
|
|
17
166
|
func: Callable,
|
|
18
|
-
mask: GeoDataFrame | GeoSeries | Geometry,
|
|
19
167
|
gridsize: int,
|
|
168
|
+
mask: GeoDataFrame | GeoSeries | Geometry,
|
|
20
169
|
gridbuffer: int = 0,
|
|
21
170
|
clip: bool = True,
|
|
22
171
|
keep_geom_type: bool = True,
|
|
23
172
|
verbose: bool = False,
|
|
24
173
|
args: tuple | None = None,
|
|
25
174
|
kwargs: dict | None = None,
|
|
175
|
+
parallelizer: Parallel | None = None,
|
|
26
176
|
) -> list[Any]:
|
|
27
177
|
"""Runs a function in a loop cellwise based on a grid.
|
|
28
178
|
|
|
@@ -108,9 +258,6 @@ def gridloop(
|
|
|
108
258
|
[1486 rows x 3 columns]
|
|
109
259
|
|
|
110
260
|
"""
|
|
111
|
-
if not isinstance(mask, GeoDataFrame):
|
|
112
|
-
mask = to_gdf(mask)
|
|
113
|
-
|
|
114
261
|
if kwargs is None:
|
|
115
262
|
kwargs = {}
|
|
116
263
|
elif not isinstance(kwargs, dict):
|
|
@@ -121,49 +268,52 @@ def gridloop(
|
|
|
121
268
|
elif not isinstance(args, tuple):
|
|
122
269
|
raise TypeError("args should be a tuple")
|
|
123
270
|
|
|
271
|
+
if not isinstance(mask, GeoDataFrame):
|
|
272
|
+
mask = to_gdf(mask)
|
|
273
|
+
|
|
124
274
|
intersects_mask = lambda df: df.index.isin(df.sjoin(mask).index)
|
|
125
275
|
grid: GeoSeries = make_grid(mask, gridsize=gridsize).loc[intersects_mask].geometry
|
|
126
276
|
|
|
127
|
-
|
|
128
|
-
n = len(grid)
|
|
129
|
-
|
|
130
|
-
def clip_if_isinstance(value, cell, keep_geom_type):
|
|
131
|
-
if not isinstance(value, (gpd.GeoDataFrame, gpd.GeoSeries, Geometry)):
|
|
132
|
-
return value
|
|
133
|
-
|
|
134
|
-
if isinstance(value, (gpd.GeoDataFrame, gpd.GeoSeries)):
|
|
135
|
-
if clip:
|
|
136
|
-
return clean_clip(value, cell, keep_geom_type=keep_geom_type)
|
|
137
|
-
return value.loc[value.intersects(cell)]
|
|
277
|
+
n = len(grid)
|
|
138
278
|
|
|
139
|
-
|
|
279
|
+
buffered_grid = grid.buffer(gridbuffer, resolution=1, join_style=2)
|
|
140
280
|
|
|
141
|
-
|
|
281
|
+
if parallelizer is not None:
|
|
282
|
+
func_with_clip = functools.partial(
|
|
283
|
+
_clip_and_run_func,
|
|
284
|
+
func=func,
|
|
285
|
+
args=args,
|
|
286
|
+
kwargs=kwargs,
|
|
287
|
+
keep_geom_type=keep_geom_type,
|
|
288
|
+
clip=clip,
|
|
289
|
+
)
|
|
290
|
+
results = parallelizer.map(func_with_clip, buffered_grid)
|
|
291
|
+
if not gridbuffer or not clip:
|
|
292
|
+
return results
|
|
293
|
+
out = []
|
|
294
|
+
for cell_res, unbuffered in zip(results, grid, strict=True):
|
|
295
|
+
out.append(
|
|
296
|
+
_clip_back_to_unbuffered_grid(cell_res, unbuffered, keep_geom_type)
|
|
297
|
+
)
|
|
298
|
+
return out
|
|
142
299
|
|
|
143
300
|
results = []
|
|
144
|
-
for i, (
|
|
145
|
-
cell_kwargs = {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
arg = clip_if_isinstance(arg, buffered, keep_geom_type)
|
|
153
|
-
cell_args = cell_args + (arg,)
|
|
301
|
+
for i, (unbuffered, buffered) in enumerate(zip(grid, buffered_grid)):
|
|
302
|
+
cell_kwargs = {
|
|
303
|
+
key: _clip_if_isinstance(value, buffered, keep_geom_type, clip)
|
|
304
|
+
for key, value in kwargs.items()
|
|
305
|
+
}
|
|
306
|
+
cell_args = tuple(
|
|
307
|
+
_clip_if_isinstance(value, buffered, keep_geom_type, clip) for value in args
|
|
308
|
+
)
|
|
154
309
|
|
|
155
310
|
cell_res = func(*cell_args, **cell_kwargs)
|
|
156
311
|
|
|
157
312
|
# clip back to original
|
|
158
313
|
if gridbuffer and clip:
|
|
159
|
-
|
|
160
|
-
cell_res
|
|
161
|
-
|
|
162
|
-
try:
|
|
163
|
-
for res in cell_res:
|
|
164
|
-
res = clip_if_isinstance(res, cell, keep_geom_type)
|
|
165
|
-
except TypeError:
|
|
166
|
-
pass
|
|
314
|
+
cell_res = _clip_back_to_unbuffered_grid(
|
|
315
|
+
cell_res, unbuffered, keep_geom_type
|
|
316
|
+
)
|
|
167
317
|
|
|
168
318
|
results.append(cell_res)
|
|
169
319
|
|
|
@@ -173,6 +323,56 @@ def gridloop(
|
|
|
173
323
|
return results
|
|
174
324
|
|
|
175
325
|
|
|
326
|
+
def _clip_and_run_func(
|
|
327
|
+
grid_cell: Polygon,
|
|
328
|
+
func: Callable,
|
|
329
|
+
args: tuple,
|
|
330
|
+
kwargs: dict,
|
|
331
|
+
keep_geom_type: bool,
|
|
332
|
+
clip: bool,
|
|
333
|
+
):
|
|
334
|
+
cell_args = tuple(
|
|
335
|
+
_clip_if_isinstance(value, grid_cell, keep_geom_type, clip) for value in args
|
|
336
|
+
)
|
|
337
|
+
cell_kwargs = {
|
|
338
|
+
key: _clip_if_isinstance(value, grid_cell, keep_geom_type, clip)
|
|
339
|
+
for key, value in kwargs.items()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return func(*cell_args, **cell_kwargs)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _clip_if_isinstance(value, cell, keep_geom_type, clip: bool):
|
|
346
|
+
if not isinstance(value, (gpd.GeoDataFrame, gpd.GeoSeries, Geometry)):
|
|
347
|
+
return value
|
|
348
|
+
|
|
349
|
+
if isinstance(value, (gpd.GeoDataFrame, gpd.GeoSeries)):
|
|
350
|
+
if clip:
|
|
351
|
+
return clean_clip(value, cell, keep_geom_type=keep_geom_type)
|
|
352
|
+
return value.loc[value.intersects(cell)]
|
|
353
|
+
|
|
354
|
+
return value.intersection(cell).make_valid()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _clip_back_to_unbuffered_grid(results, mask, keep_geom_type):
|
|
358
|
+
if isinstance(results, (gpd.GeoDataFrame, gpd.GeoSeries, Geometry)):
|
|
359
|
+
return _clip_if_isinstance(results, mask, keep_geom_type, clip=True)
|
|
360
|
+
elif isinstance(results, (pd.DataFrame, pd.Series, np.ndarray)):
|
|
361
|
+
return results
|
|
362
|
+
try:
|
|
363
|
+
for key, value in results.items():
|
|
364
|
+
results[key] = _clip_if_isinstance(value, mask, keep_geom_type, clip=True)
|
|
365
|
+
except AttributeError:
|
|
366
|
+
try:
|
|
367
|
+
return [
|
|
368
|
+
_clip_if_isinstance(res, mask, keep_geom_type, clip=True)
|
|
369
|
+
for res in results
|
|
370
|
+
]
|
|
371
|
+
except TypeError:
|
|
372
|
+
pass
|
|
373
|
+
return results
|
|
374
|
+
|
|
375
|
+
|
|
176
376
|
def make_grid_from_bbox(
|
|
177
377
|
minx: int | float,
|
|
178
378
|
miny: int | float,
|
|
@@ -506,7 +706,6 @@ def get_total_bounds(
|
|
|
506
706
|
minx, miny, maxx, maxy = to_bbox(obj)
|
|
507
707
|
xs += [minx, maxx]
|
|
508
708
|
ys += [miny, maxy]
|
|
509
|
-
|
|
510
709
|
return min(xs), min(ys), max(xs), max(ys)
|
|
511
710
|
|
|
512
711
|
|
|
@@ -14,7 +14,9 @@ for the following:
|
|
|
14
14
|
- The buff function returns a GeoDataFrame, the geopandas method returns a GeoSeries.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
import numpy as np
|
|
17
18
|
from geopandas import GeoDataFrame, GeoSeries
|
|
19
|
+
from shapely import make_valid, unary_union
|
|
18
20
|
|
|
19
21
|
from .general import _push_geom_col
|
|
20
22
|
from .geometry_types import make_all_singlepart
|
|
@@ -46,6 +48,7 @@ def buffdissexp(
|
|
|
46
48
|
resolution: int = 50,
|
|
47
49
|
index_parts: bool = False,
|
|
48
50
|
copy: bool = True,
|
|
51
|
+
grid_size: float | int | None = None,
|
|
49
52
|
**dissolve_kwargs,
|
|
50
53
|
) -> GeoDataFrame:
|
|
51
54
|
"""Buffers and dissolves overlapping geometries.
|
|
@@ -75,6 +78,7 @@ def buffdissexp(
|
|
|
75
78
|
distance,
|
|
76
79
|
resolution=resolution,
|
|
77
80
|
copy=copy,
|
|
81
|
+
grid_size=grid_size,
|
|
78
82
|
**dissolve_kwargs,
|
|
79
83
|
)
|
|
80
84
|
|
|
@@ -159,11 +163,43 @@ def buffdiss(
|
|
|
159
163
|
"""
|
|
160
164
|
buffered = buff(gdf, distance, resolution=resolution, copy=copy)
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
return _dissolve(buffered, **dissolve_kwargs)
|
|
163
167
|
|
|
164
|
-
dissolved[gdf._geometry_column_name] = dissolved.make_valid()
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
def _dissolve(gdf, aggfunc="first", grid_size=None, **dissolve_kwargs):
|
|
170
|
+
geom_col = gdf._geometry_column_name
|
|
171
|
+
if grid_size is None:
|
|
172
|
+
dissolved = gdf.dissolve(aggfunc=aggfunc, **dissolve_kwargs)
|
|
173
|
+
|
|
174
|
+
dissolved[geom_col] = dissolved.make_valid()
|
|
175
|
+
return dissolved
|
|
176
|
+
|
|
177
|
+
def merge_geometries(x):
|
|
178
|
+
return make_valid(unary_union(x, grid_size=grid_size))
|
|
179
|
+
|
|
180
|
+
geom_col = gdf._geometry_column_name
|
|
181
|
+
|
|
182
|
+
by = dissolve_kwargs.pop("by", None)
|
|
183
|
+
|
|
184
|
+
if by is None and dissolve_kwargs.get("level") is None:
|
|
185
|
+
by = np.zeros(len(gdf), dtype="int64")
|
|
186
|
+
other_cols = list(gdf.columns.difference({geom_col}))
|
|
187
|
+
else:
|
|
188
|
+
if isinstance(by, str):
|
|
189
|
+
by = [by]
|
|
190
|
+
other_cols = list(gdf.columns.difference({geom_col} | set(by)))
|
|
191
|
+
|
|
192
|
+
dissolved = gdf.groupby(by, **dissolve_kwargs)[other_cols].agg(aggfunc)
|
|
193
|
+
geoms_agged = gdf.groupby(by, **dissolve_kwargs)[geom_col].agg(merge_geometries)
|
|
194
|
+
|
|
195
|
+
if not dissolve_kwargs.get("as_index"):
|
|
196
|
+
try:
|
|
197
|
+
geoms_agged = geoms_agged[geom_col]
|
|
198
|
+
except KeyError:
|
|
199
|
+
pass
|
|
200
|
+
dissolved[geom_col] = geoms_agged
|
|
201
|
+
|
|
202
|
+
return GeoDataFrame(dissolved, geometry=geom_col, crs=gdf.crs)
|
|
167
203
|
|
|
168
204
|
|
|
169
205
|
def dissexp(
|
|
@@ -172,6 +208,7 @@ def dissexp(
|
|
|
172
208
|
aggfunc="first",
|
|
173
209
|
as_index: bool = True,
|
|
174
210
|
index_parts: bool = False,
|
|
211
|
+
grid_size: float | int | None = None,
|
|
175
212
|
**dissolve_kwargs,
|
|
176
213
|
):
|
|
177
214
|
"""Dissolves overlapping geometries.
|
|
@@ -191,19 +228,14 @@ def dissexp(
|
|
|
191
228
|
Returns:
|
|
192
229
|
A GeoDataFrame where overlapping geometries are dissolved.
|
|
193
230
|
"""
|
|
194
|
-
geom_col = gdf._geometry_column_name
|
|
195
|
-
|
|
196
231
|
dissolve_kwargs = dissolve_kwargs | {
|
|
197
232
|
"by": by,
|
|
198
|
-
"aggfunc": aggfunc,
|
|
199
233
|
"as_index": as_index,
|
|
200
234
|
}
|
|
201
235
|
|
|
202
236
|
dissolve_kwargs, ignore_index = _decide_ignore_index(dissolve_kwargs)
|
|
203
237
|
|
|
204
|
-
dissolved = gdf
|
|
205
|
-
|
|
206
|
-
dissolved[geom_col] = dissolved.make_valid()
|
|
238
|
+
dissolved = _dissolve(gdf, aggfunc=aggfunc, grid_size=grid_size, **dissolve_kwargs)
|
|
207
239
|
|
|
208
240
|
return make_all_singlepart(
|
|
209
241
|
dissolved, ignore_index=ignore_index, index_parts=index_parts
|