ssb-sgis 0.2.7__tar.gz → 0.2.8__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.2.7 → ssb_sgis-0.2.8}/PKG-INFO +1 -1
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/pyproject.toml +1 -1
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/__init__.py +3 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/buffer_dissolve_explode.py +26 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/general.py +9 -2
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/polygon_operations.py +141 -140
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/to_geodataframe.py +75 -3
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/helpers.py +3 -3
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/explore.py +35 -2
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/LICENSE +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/README.md +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/dapla.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/exceptions.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/__init__.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/geometry_types.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/neighbors.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/overlay.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/geopandas_tools/point_operations.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/__init__.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/examine.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/httpserver.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/legend.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/map.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/maps.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/maps/thematicmap.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/__init__.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/_get_route.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/_od_cost_matrix.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/_points.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/_service_area.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/closing_network_holes.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/cutting_lines.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/directednetwork.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/finding_isolated_networks.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/network.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/networkanalysis.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/networkanalysisrules.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/networkanalysis/nodes.py +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/py.typed +0 -0
- {ssb_sgis-0.2.7 → ssb_sgis-0.2.8}/src/sgis/read_parquet.py +0 -0
|
@@ -3,7 +3,9 @@ from .geopandas_tools.buffer_dissolve_explode import (
|
|
|
3
3
|
buff,
|
|
4
4
|
buffdiss,
|
|
5
5
|
buffdissexp,
|
|
6
|
+
buffdissexp_by_cluster,
|
|
6
7
|
dissexp,
|
|
8
|
+
dissexp_by_cluster,
|
|
7
9
|
)
|
|
8
10
|
from .geopandas_tools.general import (
|
|
9
11
|
bounds_to_points,
|
|
@@ -23,6 +25,7 @@ from .geopandas_tools.general import (
|
|
|
23
25
|
from .geopandas_tools.geometry_types import (
|
|
24
26
|
get_geom_type,
|
|
25
27
|
is_single_geom_type,
|
|
28
|
+
make_all_singlepart,
|
|
26
29
|
to_single_geom_type,
|
|
27
30
|
)
|
|
28
31
|
from .geopandas_tools.neighbors import (
|
|
@@ -17,6 +17,7 @@ for the following:
|
|
|
17
17
|
from geopandas import GeoDataFrame, GeoSeries
|
|
18
18
|
|
|
19
19
|
from .geometry_types import make_all_singlepart
|
|
20
|
+
from .polygon_operations import get_polygon_clusters
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _decide_ignore_index(kwargs: dict) -> tuple[dict, bool]:
|
|
@@ -190,6 +191,31 @@ def dissexp(
|
|
|
190
191
|
)
|
|
191
192
|
|
|
192
193
|
|
|
194
|
+
def dissexp_by_cluster(gdf: GeoDataFrame) -> GeoDataFrame:
|
|
195
|
+
return (
|
|
196
|
+
gdf.explode(ignore_index=True)
|
|
197
|
+
.pipe(get_polygon_clusters, cluster_col="cluster")
|
|
198
|
+
.pipe(dissexp, by="cluster")
|
|
199
|
+
.reset_index(drop=True)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def buffdissexp_by_cluster(
|
|
204
|
+
gdf: GeoDataFrame,
|
|
205
|
+
distance: int | float,
|
|
206
|
+
*,
|
|
207
|
+
resolution: int = 50,
|
|
208
|
+
copy: bool = True,
|
|
209
|
+
) -> GeoDataFrame:
|
|
210
|
+
return (
|
|
211
|
+
buff(gdf, distance, resolution=resolution, copy=copy)
|
|
212
|
+
.explode(ignore_index=True)
|
|
213
|
+
.pipe(get_polygon_clusters, cluster_col="cluster")
|
|
214
|
+
.pipe(dissexp, by="cluster")
|
|
215
|
+
.reset_index(drop=True)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
193
219
|
def buff(
|
|
194
220
|
gdf: GeoDataFrame | GeoSeries,
|
|
195
221
|
distance: int | float,
|
|
@@ -2,6 +2,7 @@ import warnings
|
|
|
2
2
|
|
|
3
3
|
import geopandas as gpd
|
|
4
4
|
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
5
6
|
from geopandas import GeoDataFrame, GeoSeries
|
|
6
7
|
from geopandas.array import GeometryDtype
|
|
7
8
|
from shapely import (
|
|
@@ -519,11 +520,17 @@ def to_lines(*gdfs: GeoDataFrame, copy: bool = True) -> GeoDataFrame:
|
|
|
519
520
|
if len(lines) == 1:
|
|
520
521
|
return lines[0]
|
|
521
522
|
|
|
522
|
-
|
|
523
|
+
if len(lines[0]) and len(lines[1]):
|
|
524
|
+
unioned = lines[0].overlay(lines[1], how="union", keep_geom_type=True)
|
|
525
|
+
else:
|
|
526
|
+
unioned = pd.concat([lines[0], lines[1]], ignore_index=True)
|
|
523
527
|
|
|
524
528
|
if len(lines) > 2:
|
|
525
529
|
for line_gdf in lines[2:]:
|
|
526
|
-
|
|
530
|
+
if len(line_gdf):
|
|
531
|
+
unioned = unioned.overlay(line_gdf, how="union", keep_geom_type=True)
|
|
532
|
+
else:
|
|
533
|
+
unioned = pd.concat([unioned, line_gdf], ignore_index=True)
|
|
527
534
|
|
|
528
535
|
return make_all_singlepart(unioned, ignore_index=True)
|
|
529
536
|
|
|
@@ -23,6 +23,147 @@ from .neighbors import get_neighbor_indices
|
|
|
23
23
|
from .overlay import clean_overlay
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def get_polygon_clusters(
|
|
27
|
+
*gdfs: GeoDataFrame | GeoSeries,
|
|
28
|
+
cluster_col: str = "cluster",
|
|
29
|
+
allow_multipart: bool = False,
|
|
30
|
+
) -> GeoDataFrame | tuple[GeoDataFrame]:
|
|
31
|
+
"""Find which polygons overlap without dissolving.
|
|
32
|
+
|
|
33
|
+
Devides polygons into clusters in a fast and precice manner by using spatial join
|
|
34
|
+
and networkx to find the connected components, i.e. overlapping geometries.
|
|
35
|
+
If multiple GeoDataFrames are given, the clusters will be based on all
|
|
36
|
+
combined.
|
|
37
|
+
|
|
38
|
+
This can be used instead of dissolve+explode, or before dissolving by the cluster
|
|
39
|
+
column. This has been tested to be a lot faster if there are many
|
|
40
|
+
non-overlapping polygons, but somewhat slower than dissolve+explode if most
|
|
41
|
+
polygons overlap.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
gdfs: One or more GeoDataFrames of polygons.
|
|
45
|
+
cluster_col: Name of the resulting cluster column.
|
|
46
|
+
allow_multipart: Whether to allow mutipart geometries in the gdfs.
|
|
47
|
+
Defaults to False to avoid confusing results.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
One or more GeoDataFrames (same amount as was given) with a new cluster column.
|
|
51
|
+
|
|
52
|
+
Examples
|
|
53
|
+
--------
|
|
54
|
+
|
|
55
|
+
Create geometries with three clusters of overlapping polygons.
|
|
56
|
+
|
|
57
|
+
>>> import sgis as sg
|
|
58
|
+
>>> gdf = sg.to_gdf([(0, 0), (1, 1), (0, 1), (4, 4), (4, 3), (7, 7)])
|
|
59
|
+
>>> buffered = sg.buff(gdf, 1)
|
|
60
|
+
>>> gdf
|
|
61
|
+
geometry
|
|
62
|
+
0 POLYGON ((1.00000 0.00000, 0.99951 -0.03141, 0...
|
|
63
|
+
1 POLYGON ((2.00000 1.00000, 1.99951 0.96859, 1....
|
|
64
|
+
2 POLYGON ((1.00000 1.00000, 0.99951 0.96859, 0....
|
|
65
|
+
3 POLYGON ((5.00000 4.00000, 4.99951 3.96859, 4....
|
|
66
|
+
4 POLYGON ((5.00000 3.00000, 4.99951 2.96859, 4....
|
|
67
|
+
5 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
68
|
+
|
|
69
|
+
Add a cluster column to the GeoDataFrame:
|
|
70
|
+
|
|
71
|
+
>>> gdf = sg.get_polygon_clusters(gdf, cluster_col="cluster")
|
|
72
|
+
>>> gdf
|
|
73
|
+
cluster geometry
|
|
74
|
+
0 0 POLYGON ((1.00000 0.00000, 0.99951 -0.03141, 0...
|
|
75
|
+
1 0 POLYGON ((2.00000 1.00000, 1.99951 0.96859, 1....
|
|
76
|
+
2 0 POLYGON ((1.00000 1.00000, 0.99951 0.96859, 0....
|
|
77
|
+
3 1 POLYGON ((5.00000 4.00000, 4.99951 3.96859, 4....
|
|
78
|
+
4 1 POLYGON ((5.00000 3.00000, 4.99951 2.96859, 4....
|
|
79
|
+
5 2 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
80
|
+
|
|
81
|
+
If multiple GeoDataFrames are given, all are returned with common
|
|
82
|
+
cluster values.
|
|
83
|
+
|
|
84
|
+
>>> gdf2 = sg.to_gdf([(0, 0), (7, 7)])
|
|
85
|
+
>>> gdf, gdf2 = sg.get_polygon_clusters(gdf, gdf2, cluster_col="cluster")
|
|
86
|
+
>>> gdf2
|
|
87
|
+
cluster geometry
|
|
88
|
+
0 0 POINT (0.00000 0.00000)
|
|
89
|
+
1 2 POINT (7.00000 7.00000)
|
|
90
|
+
>>> gdf
|
|
91
|
+
cluster geometry
|
|
92
|
+
0 0 POLYGON ((1.00000 0.00000, 0.99951 -0.03141, 0...
|
|
93
|
+
1 0 POLYGON ((2.00000 1.00000, 1.99951 0.96859, 1....
|
|
94
|
+
2 0 POLYGON ((1.00000 1.00000, 0.99951 0.96859, 0....
|
|
95
|
+
3 1 POLYGON ((5.00000 4.00000, 4.99951 3.96859, 4....
|
|
96
|
+
4 1 POLYGON ((5.00000 3.00000, 4.99951 2.96859, 4....
|
|
97
|
+
5 2 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
98
|
+
|
|
99
|
+
Dissolving 'by' the cluster column will make the dissolve much
|
|
100
|
+
faster if there are a lot of non-overlapping polygons.
|
|
101
|
+
|
|
102
|
+
>>> dissolved = gdf.dissolve(by="cluster", as_index=False)
|
|
103
|
+
>>> dissolved
|
|
104
|
+
cluster geometry
|
|
105
|
+
0 0 POLYGON ((0.99951 -0.03141, 0.99803 -0.06279, ...
|
|
106
|
+
1 1 POLYGON ((4.99951 2.96859, 4.99803 2.93721, 4....
|
|
107
|
+
2 2 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
108
|
+
"""
|
|
109
|
+
if isinstance(gdfs[-1], str):
|
|
110
|
+
*gdfs, cluster_col = gdfs
|
|
111
|
+
|
|
112
|
+
concated = pd.DataFrame()
|
|
113
|
+
orig_indices = ()
|
|
114
|
+
for i, gdf in enumerate(gdfs):
|
|
115
|
+
if isinstance(gdf, GeoSeries):
|
|
116
|
+
gdf = gdf.to_frame()
|
|
117
|
+
|
|
118
|
+
if not isinstance(gdf, GeoDataFrame):
|
|
119
|
+
raise TypeError("'gdfs' should be GeoDataFrames or GeoSeries.")
|
|
120
|
+
|
|
121
|
+
if not allow_multipart and len(gdf) != len(gdf.explode(index_parts=False)):
|
|
122
|
+
raise ValueError(
|
|
123
|
+
"All geometries should be exploded to singlepart "
|
|
124
|
+
"in order to get correct polygon clusters. "
|
|
125
|
+
"To allow multipart geometries, set allow_multipart=True"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
orig_indices = orig_indices + (gdf.index,)
|
|
129
|
+
|
|
130
|
+
gdf["i__"] = i
|
|
131
|
+
|
|
132
|
+
concated = pd.concat([concated, gdf], ignore_index=True)
|
|
133
|
+
|
|
134
|
+
neighbors = get_neighbor_indices(concated, concated)
|
|
135
|
+
|
|
136
|
+
edges = [(source, target) for source, target in neighbors.items()]
|
|
137
|
+
|
|
138
|
+
graph = nx.Graph()
|
|
139
|
+
graph.add_edges_from(edges)
|
|
140
|
+
|
|
141
|
+
component_mapper = {
|
|
142
|
+
j: i
|
|
143
|
+
for i, component in enumerate(nx.connected_components(graph))
|
|
144
|
+
for j in component
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
concated[cluster_col] = component_mapper
|
|
148
|
+
|
|
149
|
+
concated = _push_geom_col(concated)
|
|
150
|
+
|
|
151
|
+
n_gdfs = concated["i__"].unique()
|
|
152
|
+
|
|
153
|
+
if len(n_gdfs) == 1:
|
|
154
|
+
concated.index = orig_indices[0]
|
|
155
|
+
return concated.drop(["i__"], axis=1)
|
|
156
|
+
|
|
157
|
+
unconcated = ()
|
|
158
|
+
for i in n_gdfs:
|
|
159
|
+
gdf = concated[concated["i__"] == i]
|
|
160
|
+
gdf.index = orig_indices[i]
|
|
161
|
+
gdf = gdf.drop(["i__"], axis=1)
|
|
162
|
+
unconcated = unconcated + (gdf,)
|
|
163
|
+
|
|
164
|
+
return unconcated
|
|
165
|
+
|
|
166
|
+
|
|
26
167
|
def eliminate_by_longest(
|
|
27
168
|
gdf: GeoDataFrame,
|
|
28
169
|
to_eliminate: GeoDataFrame,
|
|
@@ -215,146 +356,6 @@ def _eliminate_by_area(
|
|
|
215
356
|
return eliminated
|
|
216
357
|
|
|
217
358
|
|
|
218
|
-
def get_polygon_clusters(
|
|
219
|
-
*gdfs: GeoDataFrame | GeoSeries,
|
|
220
|
-
cluster_col: str = "cluster",
|
|
221
|
-
allow_multipart: bool = False,
|
|
222
|
-
) -> GeoDataFrame | tuple[GeoDataFrame]:
|
|
223
|
-
"""Find which polygons overlap without dissolving.
|
|
224
|
-
|
|
225
|
-
Devides polygons into clusters in a fast and precice manner by using spatial join
|
|
226
|
-
and networkx to find the connected components, i.e. overlapping geometries.
|
|
227
|
-
If multiple GeoDataFrames are given, the clusters will be based on all
|
|
228
|
-
combined.
|
|
229
|
-
|
|
230
|
-
This can be used instead of dissolve+explode, or before dissolving by the cluster
|
|
231
|
-
column. This has been tested to be a lot faster if there are many
|
|
232
|
-
non-overlapping polygons, but somewhat slower than dissolve+explode if most
|
|
233
|
-
polygons overlap.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
gdfs: One or more GeoDataFrames of polygons.
|
|
237
|
-
cluster_col: Name of the resulting cluster column.
|
|
238
|
-
allow_multipart: Whether to allow mutipart geometries in the gdfs.
|
|
239
|
-
Defaults to False to avoid confusing results.
|
|
240
|
-
|
|
241
|
-
Returns:
|
|
242
|
-
One or more GeoDataFrames (same amount as was given) with a new cluster column.
|
|
243
|
-
|
|
244
|
-
Examples
|
|
245
|
-
--------
|
|
246
|
-
|
|
247
|
-
Create geometries with three clusters of overlapping polygons.
|
|
248
|
-
|
|
249
|
-
>>> import sgis as sg
|
|
250
|
-
>>> gdf = sg.to_gdf([(0, 0), (1, 1), (0, 1), (4, 4), (4, 3), (7, 7)])
|
|
251
|
-
>>> buffered = sg.buff(gdf, 1)
|
|
252
|
-
>>> gdf
|
|
253
|
-
geometry
|
|
254
|
-
0 POLYGON ((1.00000 0.00000, 0.99951 -0.03141, 0...
|
|
255
|
-
1 POLYGON ((2.00000 1.00000, 1.99951 0.96859, 1....
|
|
256
|
-
2 POLYGON ((1.00000 1.00000, 0.99951 0.96859, 0....
|
|
257
|
-
3 POLYGON ((5.00000 4.00000, 4.99951 3.96859, 4....
|
|
258
|
-
4 POLYGON ((5.00000 3.00000, 4.99951 2.96859, 4....
|
|
259
|
-
5 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
260
|
-
|
|
261
|
-
Add a cluster column to the GeoDataFrame:
|
|
262
|
-
|
|
263
|
-
>>> gdf = sg.get_polygon_clusters(gdf, cluster_col="cluster")
|
|
264
|
-
>>> gdf
|
|
265
|
-
cluster geometry
|
|
266
|
-
0 0 POLYGON ((1.00000 0.00000, 0.99951 -0.03141, 0...
|
|
267
|
-
1 0 POLYGON ((2.00000 1.00000, 1.99951 0.96859, 1....
|
|
268
|
-
2 0 POLYGON ((1.00000 1.00000, 0.99951 0.96859, 0....
|
|
269
|
-
3 1 POLYGON ((5.00000 4.00000, 4.99951 3.96859, 4....
|
|
270
|
-
4 1 POLYGON ((5.00000 3.00000, 4.99951 2.96859, 4....
|
|
271
|
-
5 2 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
272
|
-
|
|
273
|
-
If multiple GeoDataFrames are given, all are returned with common
|
|
274
|
-
cluster values.
|
|
275
|
-
|
|
276
|
-
>>> gdf2 = sg.to_gdf([(0, 0), (7, 7)])
|
|
277
|
-
>>> gdf, gdf2 = sg.get_polygon_clusters(gdf, gdf2, cluster_col="cluster")
|
|
278
|
-
>>> gdf2
|
|
279
|
-
cluster geometry
|
|
280
|
-
0 0 POINT (0.00000 0.00000)
|
|
281
|
-
1 2 POINT (7.00000 7.00000)
|
|
282
|
-
>>> gdf
|
|
283
|
-
cluster geometry
|
|
284
|
-
0 0 POLYGON ((1.00000 0.00000, 0.99951 -0.03141, 0...
|
|
285
|
-
1 0 POLYGON ((2.00000 1.00000, 1.99951 0.96859, 1....
|
|
286
|
-
2 0 POLYGON ((1.00000 1.00000, 0.99951 0.96859, 0....
|
|
287
|
-
3 1 POLYGON ((5.00000 4.00000, 4.99951 3.96859, 4....
|
|
288
|
-
4 1 POLYGON ((5.00000 3.00000, 4.99951 2.96859, 4....
|
|
289
|
-
5 2 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
290
|
-
|
|
291
|
-
Dissolving 'by' the cluster column will make the dissolve much
|
|
292
|
-
faster if there are a lot of non-overlapping polygons.
|
|
293
|
-
|
|
294
|
-
>>> dissolved = gdf.dissolve(by="cluster", as_index=False)
|
|
295
|
-
>>> dissolved
|
|
296
|
-
cluster geometry
|
|
297
|
-
0 0 POLYGON ((0.99951 -0.03141, 0.99803 -0.06279, ...
|
|
298
|
-
1 1 POLYGON ((4.99951 2.96859, 4.99803 2.93721, 4....
|
|
299
|
-
2 2 POLYGON ((8.00000 7.00000, 7.99951 6.96859, 7....
|
|
300
|
-
"""
|
|
301
|
-
if isinstance(gdfs[-1], str):
|
|
302
|
-
*gdfs, cluster_col = gdfs
|
|
303
|
-
|
|
304
|
-
concated = pd.DataFrame()
|
|
305
|
-
orig_indices = ()
|
|
306
|
-
for i, gdf in enumerate(gdfs):
|
|
307
|
-
if isinstance(gdf, GeoSeries):
|
|
308
|
-
gdf = gdf.to_frame()
|
|
309
|
-
|
|
310
|
-
if not isinstance(gdf, GeoDataFrame):
|
|
311
|
-
raise TypeError("'gdfs' should be one or more GeoDataFrames or GeoSeries.")
|
|
312
|
-
|
|
313
|
-
if not allow_multipart and len(gdf) != len(gdf.explode(index_parts=False)):
|
|
314
|
-
raise ValueError(
|
|
315
|
-
"All geometries should be exploded to singlepart "
|
|
316
|
-
"in order to get correct polygon clusters. "
|
|
317
|
-
"To allow multipart geometries, set allow_multipart=True"
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
orig_indices = orig_indices + (gdf.index,)
|
|
321
|
-
gdf["i__"] = i
|
|
322
|
-
|
|
323
|
-
concated = pd.concat([concated, gdf], ignore_index=True)
|
|
324
|
-
|
|
325
|
-
neighbors = get_neighbor_indices(concated, concated)
|
|
326
|
-
|
|
327
|
-
edges = [(source, target) for source, target in neighbors.items()]
|
|
328
|
-
|
|
329
|
-
graph = nx.Graph()
|
|
330
|
-
graph.add_edges_from(edges)
|
|
331
|
-
|
|
332
|
-
component_mapper = {
|
|
333
|
-
j: i
|
|
334
|
-
for i, component in enumerate(nx.connected_components(graph))
|
|
335
|
-
for j in component
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
concated[cluster_col] = component_mapper
|
|
339
|
-
|
|
340
|
-
concated = _push_geom_col(concated)
|
|
341
|
-
|
|
342
|
-
n_gdfs = concated["i__"].unique()
|
|
343
|
-
|
|
344
|
-
if len(n_gdfs) == 1:
|
|
345
|
-
concated.index = orig_indices[0]
|
|
346
|
-
return concated.drop(["i__"], axis=1)
|
|
347
|
-
|
|
348
|
-
unconcated = ()
|
|
349
|
-
for i in n_gdfs:
|
|
350
|
-
gdf = concated[concated["i__"] == i]
|
|
351
|
-
gdf.index = orig_indices[i]
|
|
352
|
-
gdf = gdf.drop(["i__"], axis=1)
|
|
353
|
-
unconcated = unconcated + (gdf,)
|
|
354
|
-
|
|
355
|
-
return unconcated
|
|
356
|
-
|
|
357
|
-
|
|
358
359
|
def get_overlapping_polygons(
|
|
359
360
|
gdf: GeoDataFrame | GeoSeries, ignore_index: bool = False
|
|
360
361
|
) -> GeoDataFrame | GeoSeries:
|
|
@@ -3,6 +3,7 @@ from collections.abc import Iterator, Sized
|
|
|
3
3
|
|
|
4
4
|
import geopandas as gpd
|
|
5
5
|
import pandas as pd
|
|
6
|
+
import shapely
|
|
6
7
|
from geopandas import GeoDataFrame, GeoSeries
|
|
7
8
|
from pandas.api.types import is_array_like, is_dict_like, is_list_like
|
|
8
9
|
from shapely import Geometry, box, wkb, wkt
|
|
@@ -128,7 +129,7 @@ def to_gdf(
|
|
|
128
129
|
raise TypeError("'to_gdf' doesn't accept GeoDataFrames as input type.")
|
|
129
130
|
|
|
130
131
|
if isinstance(geom, GeoSeries):
|
|
131
|
-
geom_col =
|
|
132
|
+
geom_col = geometry if geometry else "geometry"
|
|
132
133
|
return _geoseries_to_gdf(geom, geom_col, crs, **kwargs)
|
|
133
134
|
|
|
134
135
|
geom_col = _find_geometry_column(geom, geometry)
|
|
@@ -144,8 +145,26 @@ def to_gdf(
|
|
|
144
145
|
return GeoDataFrame({geom_col: geom}, geometry=geom_col, crs=crs, **kwargs)
|
|
145
146
|
|
|
146
147
|
if not is_dict_like(geom):
|
|
147
|
-
geom
|
|
148
|
-
|
|
148
|
+
if not hasattr(geom, "__iter__") and hasattr(geom, "__dict__"):
|
|
149
|
+
if all(attr in geom.__dict__ for attr in ["minx", "miny", "maxx", "maxy"]):
|
|
150
|
+
geom = GeoSeries(
|
|
151
|
+
shapely.box(*(geom.minx, geom.miny, geom.maxx, geom.maxy)),
|
|
152
|
+
index=index,
|
|
153
|
+
)
|
|
154
|
+
return GeoDataFrame(
|
|
155
|
+
{geom_col: geom}, geometry=geom_col, crs=crs, **kwargs
|
|
156
|
+
)
|
|
157
|
+
if hasattr(geom, "__iter__") and all(isinstance(g, dict) for g in geom):
|
|
158
|
+
crs = crs if crs else _get_crs(geom)
|
|
159
|
+
geom = pd.concat(GeoSeries(_from_json(g)) for g in geom)
|
|
160
|
+
if index is not None:
|
|
161
|
+
geom.index = index
|
|
162
|
+
else:
|
|
163
|
+
geom = geom.reset_index(drop=True)
|
|
164
|
+
return GeoDataFrame({geom_col: geom}, geometry=geom_col, crs=crs, **kwargs)
|
|
165
|
+
else:
|
|
166
|
+
geom = GeoSeries(_make_shapely_geoms(geom), index=index)
|
|
167
|
+
return GeoDataFrame({geom_col: geom}, geometry=geom_col, crs=crs, **kwargs)
|
|
149
168
|
|
|
150
169
|
# now we have dict, Series or DataFrame
|
|
151
170
|
|
|
@@ -177,10 +196,61 @@ def to_gdf(
|
|
|
177
196
|
if geometry and geom_col not in geom or isinstance(geom, pd.DataFrame):
|
|
178
197
|
raise ValueError("Cannot find geometry column(s)", geometry)
|
|
179
198
|
|
|
199
|
+
# geojson, __geo_interface__
|
|
200
|
+
if (
|
|
201
|
+
isinstance(geom, dict)
|
|
202
|
+
and sum(key in geom for key in ["type", "coordinates", "features"]) >= 2
|
|
203
|
+
):
|
|
204
|
+
if "geometry" in geom:
|
|
205
|
+
geometry = "geometry"
|
|
206
|
+
|
|
207
|
+
crs = crs if crs else _get_crs(geom)
|
|
208
|
+
print(crs)
|
|
209
|
+
geom = GeoSeries(_from_json(geom), index=index)
|
|
210
|
+
return GeoDataFrame({geom_col: geom}, geometry=geom_col, crs=crs, **kwargs)
|
|
211
|
+
|
|
180
212
|
geoseries = _series_like_to_geoseries(geom, index=index)
|
|
181
213
|
return GeoDataFrame(geometry=geoseries, crs=crs, **kwargs)
|
|
182
214
|
|
|
183
215
|
|
|
216
|
+
def _get_crs(geom):
|
|
217
|
+
if not is_dict_like(geom) and is_dict_like(geom[0]):
|
|
218
|
+
crss = list({_get_crs(g) for g in geom})
|
|
219
|
+
if len(crss) == 1:
|
|
220
|
+
return crss[0]
|
|
221
|
+
return None
|
|
222
|
+
if "properties" in geom:
|
|
223
|
+
return _get_crs(geom["properties"])
|
|
224
|
+
if "crs" in geom:
|
|
225
|
+
geom = geom["crs"]
|
|
226
|
+
while is_dict_like(geom):
|
|
227
|
+
if "properties" in geom:
|
|
228
|
+
geom = geom["properties"]
|
|
229
|
+
elif "name" in geom:
|
|
230
|
+
geom = geom["name"]
|
|
231
|
+
else:
|
|
232
|
+
return None
|
|
233
|
+
return geom
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _from_json(geom: dict):
|
|
238
|
+
if not isinstance(geom, dict) and isinstance(geom[0], dict):
|
|
239
|
+
return [_from_json(g) for g in geom]
|
|
240
|
+
if "geometry" in geom:
|
|
241
|
+
return _from_json(geom["geometry"])
|
|
242
|
+
if "features" in geom:
|
|
243
|
+
return _from_json(geom["features"])
|
|
244
|
+
coords = geom["coordinates"]
|
|
245
|
+
constructor = eval("shapely.geometry." + geom.get("type", Point))
|
|
246
|
+
try:
|
|
247
|
+
return constructor(coords)
|
|
248
|
+
except TypeError:
|
|
249
|
+
while len(coords) == 1:
|
|
250
|
+
coords = coords[0]
|
|
251
|
+
return constructor(coords)
|
|
252
|
+
|
|
253
|
+
|
|
184
254
|
def _series_like_to_geoseries(geom, index):
|
|
185
255
|
if index is None:
|
|
186
256
|
index = geom.keys()
|
|
@@ -252,6 +322,8 @@ def _is_one_geometry(geom) -> bool:
|
|
|
252
322
|
def _make_shapely_geoms(geom):
|
|
253
323
|
if _is_one_geometry(geom):
|
|
254
324
|
return _make_one_shapely_geom(geom)
|
|
325
|
+
if isinstance(geom, dict) and "coordinates" in geom:
|
|
326
|
+
return _from_json(geom)
|
|
255
327
|
return (_make_one_shapely_geom(g) for g in geom)
|
|
256
328
|
|
|
257
329
|
|
|
@@ -58,9 +58,9 @@ def get_name(var: object, n: int = 5) -> str | None:
|
|
|
58
58
|
frame = inspect.currentframe().f_back.f_back
|
|
59
59
|
|
|
60
60
|
for _ in range(n):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
names = [
|
|
62
|
+
var_name for var_name, var_val in frame.f_locals.items() if var_val is var
|
|
63
|
+
]
|
|
64
64
|
if names and len(names) == 1:
|
|
65
65
|
return names[0]
|
|
66
66
|
|
|
@@ -4,6 +4,7 @@ This module holds the Explore class, which is the basis for the explore, samplem
|
|
|
4
4
|
clipmap functions from the 'maps' module.
|
|
5
5
|
"""
|
|
6
6
|
import warnings
|
|
7
|
+
from numbers import Number
|
|
7
8
|
from statistics import mean
|
|
8
9
|
|
|
9
10
|
import branca as bc
|
|
@@ -15,10 +16,11 @@ from folium import plugins
|
|
|
15
16
|
from geopandas import GeoDataFrame
|
|
16
17
|
from IPython.display import display
|
|
17
18
|
from jinja2 import Template
|
|
19
|
+
from shapely import Geometry
|
|
18
20
|
from shapely.geometry import LineString
|
|
19
21
|
|
|
20
22
|
from ..geopandas_tools.general import clean_geoms, make_all_singlepart
|
|
21
|
-
from ..geopandas_tools.geometry_types import get_geom_type
|
|
23
|
+
from ..geopandas_tools.geometry_types import get_geom_type, to_single_geom_type
|
|
22
24
|
from ..geopandas_tools.to_geodataframe import to_gdf
|
|
23
25
|
from ..helpers import unit_is_degrees
|
|
24
26
|
from .httpserver import run_html_server
|
|
@@ -110,6 +112,17 @@ class Explore(Map):
|
|
|
110
112
|
|
|
111
113
|
super().__init__(*gdfs, column=column, **kwargs)
|
|
112
114
|
|
|
115
|
+
# remove columns not renerable by leaflet (list columns etc.)
|
|
116
|
+
new_gdfs = []
|
|
117
|
+
for gdf in self.gdfs:
|
|
118
|
+
cols_to_keep = [
|
|
119
|
+
col
|
|
120
|
+
for col in gdf.columns
|
|
121
|
+
if isinstance(gdf[col].iloc[0], (Number, str, Geometry))
|
|
122
|
+
]
|
|
123
|
+
new_gdfs.append(gdf[cols_to_keep])
|
|
124
|
+
self._gdfs = new_gdfs
|
|
125
|
+
|
|
113
126
|
self.popup = popup
|
|
114
127
|
self.max_zoom = max_zoom
|
|
115
128
|
self.smooth_factor = smooth_factor
|
|
@@ -266,6 +279,7 @@ class Explore(Map):
|
|
|
266
279
|
|
|
267
280
|
new_gdfs = []
|
|
268
281
|
for gdf in self._gdfs:
|
|
282
|
+
print(gdf)
|
|
269
283
|
if get_geom_type(gdf) == "mixed" and not unit_is_degrees(gdf):
|
|
270
284
|
gdf[gdf._geometry_column_name] = gdf.buffer(0.01)
|
|
271
285
|
gdf = make_all_singlepart(gdf)
|
|
@@ -615,7 +629,26 @@ class Explore(Map):
|
|
|
615
629
|
"supported as marker values"
|
|
616
630
|
)
|
|
617
631
|
|
|
618
|
-
gdf = clean_geoms(gdf)
|
|
632
|
+
gdf = clean_geoms(gdf).pipe(make_all_singlepart)
|
|
633
|
+
if get_geom_type(gdf) == "mixed":
|
|
634
|
+
if gdf.geom_type.str.lower().str.contains("polygon").any():
|
|
635
|
+
warnings.warn(
|
|
636
|
+
"GeoJsonTooltip is not configured to render for GeoJson "
|
|
637
|
+
"GeometryCollection geometries. Keeping only polygons."
|
|
638
|
+
)
|
|
639
|
+
gdf = to_single_geom_type(gdf, geom_type="polygon")
|
|
640
|
+
elif gdf.geom_type.str.lower().str.contains("line").any():
|
|
641
|
+
warnings.warn(
|
|
642
|
+
"GeoJsonTooltip is not configured to render for GeoJson "
|
|
643
|
+
"GeometryCollection geometries. Keeping only lines."
|
|
644
|
+
)
|
|
645
|
+
gdf = to_single_geom_type(gdf, geom_type="line")
|
|
646
|
+
else:
|
|
647
|
+
warnings.warn(
|
|
648
|
+
"GeoJsonTooltip is not configured to render for GeoJson "
|
|
649
|
+
"GeometryCollection geometries. Keeping only points."
|
|
650
|
+
)
|
|
651
|
+
gdf = to_single_geom_type(gdf, geom_type="point")
|
|
619
652
|
|
|
620
653
|
# prepare tooltip and popup
|
|
621
654
|
if isinstance(gdf, GeoDataFrame):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|