0
# Spatial Relationships
1
2
GeoPandas provides comprehensive spatial relationship operations including spatial joins, overlays, and geometric predicates. These operations enable complex spatial analysis by testing relationships between geometries and combining datasets based on spatial criteria.
3
4
## Capabilities
5
6
### Spatial Predicates
7
8
Binary spatial predicates that test relationships between geometries.
9
10
```python { .api }
11
class GeoSeries:
12
def contains(self, other, align=True):
13
"""
14
Test whether each geometry contains the other geometry.
15
16
Parameters:
17
- other: GeoSeries or single geometry
18
- align: Whether to align indices before operation
19
20
Returns:
21
- pandas.Series: Boolean values indicating containment
22
"""
23
...
24
25
def within(self, other, align=True):
26
"""
27
Test whether each geometry is within the other geometry.
28
29
Parameters:
30
- other: GeoSeries or single geometry
31
- align: Whether to align indices before operation
32
33
Returns:
34
- pandas.Series: Boolean values indicating within relationship
35
"""
36
...
37
38
def intersects(self, other, align=True):
39
"""
40
Test whether each geometry intersects the other geometry.
41
42
Parameters:
43
- other: GeoSeries or single geometry
44
- align: Whether to align indices before operation
45
46
Returns:
47
- pandas.Series: Boolean values indicating intersection
48
"""
49
...
50
51
def touches(self, other, align=True):
52
"""
53
Test whether each geometry touches the other geometry.
54
55
Parameters:
56
- other: GeoSeries or single geometry
57
- align: Whether to align indices before operation
58
59
Returns:
60
- pandas.Series: Boolean values indicating touch relationship
61
"""
62
...
63
64
def crosses(self, other, align=True):
65
"""
66
Test whether each geometry crosses the other geometry.
67
68
Parameters:
69
- other: GeoSeries or single geometry
70
- align: Whether to align indices before operation
71
72
Returns:
73
- pandas.Series: Boolean values indicating crossing
74
"""
75
...
76
77
def disjoint(self, other, align=True):
78
"""
79
Test whether each geometry is disjoint from the other geometry.
80
81
Parameters:
82
- other: GeoSeries or single geometry
83
- align: Whether to align indices before operation
84
85
Returns:
86
- pandas.Series: Boolean values indicating disjoint relationship
87
"""
88
...
89
90
def overlaps(self, other, align=True):
91
"""
92
Test whether each geometry overlaps the other geometry.
93
94
Parameters:
95
- other: GeoSeries or single geometry
96
- align: Whether to align indices before operation
97
98
Returns:
99
- pandas.Series: Boolean values indicating overlap
100
"""
101
...
102
103
def covers(self, other, align=True):
104
"""
105
Test whether each geometry covers the other geometry.
106
107
Parameters:
108
- other: GeoSeries or single geometry
109
- align: Whether to align indices before operation
110
111
Returns:
112
- pandas.Series: Boolean values indicating coverage
113
"""
114
...
115
116
def covered_by(self, other, align=True):
117
"""
118
Test whether each geometry is covered by the other geometry.
119
120
Parameters:
121
- other: GeoSeries or single geometry
122
- align: Whether to align indices before operation
123
124
Returns:
125
- pandas.Series: Boolean values indicating covered_by relationship
126
"""
127
...
128
129
def contains_properly(self, other, align=True):
130
"""
131
Test whether each geometry properly contains the other geometry.
132
133
Parameters:
134
- other: GeoSeries or single geometry
135
- align: Whether to align indices before operation
136
137
Returns:
138
- pandas.Series: Boolean values indicating proper containment
139
"""
140
...
141
142
def geom_equals(self, other, align=True):
143
"""
144
Test whether each geometry is geometrically equal to the other.
145
146
Parameters:
147
- other: GeoSeries or single geometry
148
- align: Whether to align indices before operation
149
150
Returns:
151
- pandas.Series: Boolean values indicating geometric equality
152
"""
153
...
154
155
def geom_equals_exact(self, other, tolerance, align=True):
156
"""
157
Test whether each geometry is exactly equal to the other within tolerance.
158
159
Parameters:
160
- other: GeoSeries or single geometry
161
- tolerance: Coordinate tolerance for comparison
162
- align: Whether to align indices before operation
163
164
Returns:
165
- pandas.Series: Boolean values indicating exact equality
166
"""
167
...
168
169
def dwithin(self, other, distance, align=True):
170
"""
171
Test whether each geometry is within specified distance of the other.
172
173
Parameters:
174
- other: GeoSeries or single geometry
175
- distance: Maximum distance threshold
176
- align: Whether to align indices before operation
177
178
Returns:
179
- pandas.Series: Boolean values indicating within-distance relationship
180
"""
181
...
182
183
def covered_by(self, other, align=True):
184
"""
185
Test whether each geometry is covered by the other geometry.
186
187
Parameters:
188
- other: GeoSeries or single geometry
189
- align: Whether to align indices before operation
190
191
Returns:
192
- pandas.Series: Boolean values indicating covered relationship
193
"""
194
...
195
```
196
197
### Distance Operations
198
199
Functions for calculating distances between geometries.
200
201
```python { .api }
202
class GeoSeries:
203
def distance(self, other, align=True):
204
"""
205
Calculate distance to other geometry.
206
207
Parameters:
208
- other: GeoSeries or single geometry
209
- align: Whether to align indices before operation
210
211
Returns:
212
- pandas.Series: Distance values (units depend on CRS)
213
"""
214
...
215
```
216
217
### Set Operations
218
219
Geometric set operations that combine geometries.
220
221
```python { .api }
222
class GeoSeries:
223
def intersection(self, other, align=True):
224
"""
225
Calculate intersection with other geometry.
226
227
Parameters:
228
- other: GeoSeries or single geometry
229
- align: Whether to align indices before operation
230
231
Returns:
232
- GeoSeries: Intersection geometries
233
"""
234
...
235
236
def union(self, other, align=True):
237
"""
238
Calculate union with other geometry.
239
240
Parameters:
241
- other: GeoSeries or single geometry
242
- align: Whether to align indices before operation
243
244
Returns:
245
- GeoSeries: Union geometries
246
"""
247
...
248
249
def difference(self, other, align=True):
250
"""
251
Calculate difference from other geometry.
252
253
Parameters:
254
- other: GeoSeries or single geometry
255
- align: Whether to align indices before operation
256
257
Returns:
258
- GeoSeries: Difference geometries
259
"""
260
...
261
262
def symmetric_difference(self, other, align=True):
263
"""
264
Calculate symmetric difference with other geometry.
265
266
Parameters:
267
- other: GeoSeries or single geometry
268
- align: Whether to align indices before operation
269
270
Returns:
271
- GeoSeries: Symmetric difference geometries
272
"""
273
...
274
275
def unary_union(self):
276
"""
277
Calculate union of all geometries in the series.
278
279
Returns:
280
- shapely.geometry: Single unified geometry
281
"""
282
...
283
```
284
285
### Spatial Joins
286
287
Functions for joining datasets based on spatial relationships.
288
289
```python { .api }
290
def sjoin(left_df, right_df, how='inner', predicate='intersects', lsuffix='left', rsuffix='right', **kwargs):
291
"""
292
Spatial join of two GeoDataFrames.
293
294
Parameters:
295
- left_df: Left GeoDataFrame
296
- right_df: Right GeoDataFrame
297
- how: Type of join ('left', 'right', 'outer', 'inner')
298
- predicate: Spatial predicate ('intersects', 'within', 'contains', 'overlaps', 'crosses', 'touches')
299
- lsuffix: Suffix for left DataFrame column conflicts
300
- rsuffix: Suffix for right DataFrame column conflicts
301
- **kwargs: Additional parameters
302
303
Returns:
304
- GeoDataFrame: Spatially joined data
305
"""
306
...
307
308
def sjoin_nearest(left_df, right_df, how='inner', max_distance=None, lsuffix='left', rsuffix='right', distance_col=None, **kwargs):
309
"""
310
Spatial join to nearest geometries.
311
312
Parameters:
313
- left_df: Left GeoDataFrame
314
- right_df: Right GeoDataFrame
315
- how: Type of join ('left', 'right', 'outer', 'inner')
316
- max_distance: Maximum distance for nearest neighbor search
317
- lsuffix: Suffix for left DataFrame column conflicts
318
- rsuffix: Suffix for right DataFrame column conflicts
319
- distance_col: Name for distance column in result
320
- **kwargs: Additional parameters
321
322
Returns:
323
- GeoDataFrame: Spatially joined data with nearest neighbors
324
"""
325
...
326
```
327
328
### Overlay Operations
329
330
Functions for geometric overlay operations between datasets.
331
332
```python { .api }
333
def overlay(df1, df2, how='intersection', keep_geom_type=None, make_valid=True):
334
"""
335
Perform geometric overlay operation between two GeoDataFrames.
336
337
Parameters:
338
- df1: First GeoDataFrame
339
- df2: Second GeoDataFrame
340
- how: Overlay operation ('intersection', 'union', 'identity', 'symmetric_difference', 'difference')
341
- keep_geom_type: Whether to keep only original geometry types
342
- make_valid: Whether to make invalid geometries valid before operation
343
344
Returns:
345
- GeoDataFrame: Result of overlay operation
346
"""
347
...
348
```
349
350
### Clipping Operations
351
352
Functions for clipping geometries by masks.
353
354
```python { .api }
355
def clip(gdf, mask, keep_geom_type=False, sort=False):
356
"""
357
Clip geometries to the boundary of another geometry.
358
359
Parameters:
360
- gdf: GeoDataFrame to clip
361
- mask: Geometry or GeoDataFrame to use as clipping mask
362
- keep_geom_type: Whether to keep only original geometry types
363
- sort: Whether to sort the result by index
364
365
Returns:
366
- GeoDataFrame: Clipped geometries
367
"""
368
...
369
```
370
371
### Geocoding Operations
372
373
Functions for converting between addresses and coordinates.
374
375
```python { .api }
376
def geocode(strings, provider=None, **kwargs):
377
"""
378
Geocode addresses to coordinates.
379
380
Parameters:
381
- strings: List or Series of address strings
382
- provider: Geocoding provider name
383
- **kwargs: Provider-specific parameters
384
385
Returns:
386
- GeoDataFrame: Geocoded addresses with geometry points
387
"""
388
...
389
390
def reverse_geocode(points, provider=None, **kwargs):
391
"""
392
Reverse geocode coordinates to addresses.
393
394
Parameters:
395
- points: GeoSeries of Point geometries or array-like coordinates
396
- provider: Geocoding provider name
397
- **kwargs: Provider-specific parameters
398
399
Returns:
400
- GeoDataFrame: Reverse geocoded coordinates with address information
401
"""
402
...
403
```
404
405
### Geometry Utilities
406
407
Utility functions for working with collections of geometries.
408
409
```python { .api }
410
def collect(x, multi=False):
411
"""
412
Collect geometries into a GeometryCollection or Multi-geometry.
413
414
Parameters:
415
- x: Array-like of geometries
416
- multi: Whether to create Multi-geometry instead of GeometryCollection
417
418
Returns:
419
- shapely.geometry: GeometryCollection or Multi-geometry
420
"""
421
...
422
```
423
424
## Usage Examples
425
426
### Spatial Predicates
427
428
```python
429
import geopandas as gpd
430
from shapely.geometry import Point, Polygon
431
432
# Create sample data
433
points = gpd.GeoDataFrame({
434
'id': [1, 2, 3, 4],
435
'geometry': [Point(1, 1), Point(3, 3), Point(5, 1), Point(2, 4)]
436
})
437
438
polygons = gpd.GeoDataFrame({
439
'name': ['A', 'B'],
440
'geometry': [
441
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]), # Square A
442
Polygon([(3, 2), (5, 2), (5, 4), (3, 4)]) # Square B
443
]
444
})
445
446
# Test spatial relationships
447
polygon_a = polygons.geometry.iloc[0]
448
449
# Points within polygon A
450
within_a = points.geometry.within(polygon_a)
451
print(f"Points within A: {points[within_a]['id'].tolist()}")
452
453
# Points intersecting polygon A
454
intersect_a = points.geometry.intersects(polygon_a)
455
print(f"Points intersecting A: {points[intersect_a]['id'].tolist()}")
456
457
# Test between all combinations
458
for i, poly in polygons.iterrows():
459
contained = points.geometry.within(poly['geometry'])
460
print(f"Points in {poly['name']}: {points[contained]['id'].tolist()}")
461
```
462
463
### Distance Calculations
464
465
```python
466
# Calculate distances between points and polygons
467
distances_to_a = points.geometry.distance(polygons.geometry.iloc[0])
468
print(f"Distances to polygon A: {distances_to_a}")
469
470
# Find nearest polygon for each point
471
distances_to_all = []
472
for _, poly in polygons.iterrows():
473
dist = points.geometry.distance(poly['geometry'])
474
distances_to_all.append(dist)
475
476
# Create distance matrix
477
import pandas as pd
478
distance_matrix = pd.DataFrame(distances_to_all,
479
index=polygons['name'],
480
columns=points['id']).T
481
print("Distance matrix:")
482
print(distance_matrix)
483
484
# Find nearest polygon for each point
485
nearest_polygon = distance_matrix.idxmin(axis=1)
486
print(f"Nearest polygon for each point: {nearest_polygon}")
487
```
488
489
### Spatial Joins
490
491
```python
492
# Join points to polygons they fall within
493
joined = gpd.sjoin(points, polygons, how='left', predicate='within')
494
print("Points joined to containing polygons:")
495
print(joined[['id', 'name']])
496
497
# Join points to all intersecting polygons
498
joined_intersects = gpd.sjoin(points, polygons, how='left', predicate='intersects')
499
print("Points joined to intersecting polygons:")
500
print(joined_intersects[['id', 'name']])
501
502
# Join to nearest polygon regardless of containment
503
joined_nearest = gpd.sjoin_nearest(points, polygons, how='left')
504
print("Points joined to nearest polygons:")
505
print(joined_nearest[['id', 'name']])
506
507
# Join with distance threshold
508
joined_near = gpd.sjoin_nearest(points, polygons, how='left',
509
max_distance=1.0, distance_col='distance')
510
print("Points within 1.0 unit of polygons:")
511
print(joined_near[['id', 'name', 'distance']].dropna())
512
```
513
514
### Set Operations
515
516
```python
517
# Create overlapping polygons
518
poly1 = gpd.GeoDataFrame({
519
'id': [1],
520
'geometry': [Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])]
521
})
522
523
poly2 = gpd.GeoDataFrame({
524
'id': [2],
525
'geometry': [Polygon([(2, 2), (5, 2), (5, 5), (2, 5)])]
526
})
527
528
# Calculate geometric intersections
529
intersection = poly1.geometry.intersection(poly2.geometry.iloc[0])
530
union = poly1.geometry.union(poly2.geometry.iloc[0])
531
difference = poly1.geometry.difference(poly2.geometry.iloc[0])
532
sym_diff = poly1.geometry.symmetric_difference(poly2.geometry.iloc[0])
533
534
print(f"Intersection area: {intersection.iloc[0].area}")
535
print(f"Union area: {union.iloc[0].area}")
536
print(f"Difference area: {difference.iloc[0].area}")
537
print(f"Symmetric difference area: {sym_diff.iloc[0].area}")
538
```
539
540
### Overlay Operations
541
542
```python
543
# Perform overlay operations
544
intersection_overlay = gpd.overlay(poly1, poly2, how='intersection')
545
union_overlay = gpd.overlay(poly1, poly2, how='union')
546
difference_overlay = gpd.overlay(poly1, poly2, how='difference')
547
548
print("Intersection overlay:")
549
print(intersection_overlay[['id_1', 'id_2', 'geometry']])
550
551
print("Union overlay:")
552
print(union_overlay[['id_1', 'id_2', 'geometry']])
553
554
# Identity overlay (keeps all of first dataset)
555
identity_overlay = gpd.overlay(poly1, poly2, how='identity')
556
print("Identity overlay:")
557
print(identity_overlay)
558
```
559
560
### Clipping Operations
561
562
```python
563
# Create data to clip
564
lines = gpd.GeoDataFrame({
565
'id': [1, 2, 3],
566
'geometry': [
567
LineString([(0, 1), (4, 1)]), # Crosses polygon
568
LineString([(1, 0), (1, 4)]), # Crosses polygon
569
LineString([(6, 0), (6, 4)]) # Outside polygon
570
]
571
})
572
573
# Clip lines to polygon boundary
574
clipping_polygon = Polygon([(0.5, 0.5), (3.5, 0.5), (3.5, 3.5), (0.5, 3.5)])
575
clipped_lines = gpd.clip(lines, clipping_polygon)
576
577
print("Original lines:", len(lines))
578
print("Clipped lines:", len(clipped_lines))
579
print("Clipped geometries:")
580
for i, geom in enumerate(clipped_lines.geometry):
581
print(f" Line {i+1}: {geom}")
582
```
583
584
### Complex Spatial Analysis
585
586
```python
587
# Load real world data
588
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
589
cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))
590
591
# Find cities within each country
592
cities_in_countries = gpd.sjoin(cities, world, how='left', predicate='within')
593
594
# Count cities per country
595
cities_per_country = (cities_in_countries
596
.groupby('name_right')
597
.size()
598
.sort_values(ascending=False))
599
print("Top 10 countries by number of cities:")
600
print(cities_per_country.head(10))
601
602
# Find countries that border the ocean (simplified)
603
# Create buffer around all land
604
land_union = world.geometry.unary_union
605
ocean_buffer = land_union.buffer(0.1) # Small buffer
606
607
# Countries touching the buffer are coastal
608
coastal_countries = world[world.geometry.touches(ocean_buffer)]
609
print(f"Number of coastal countries: {len(coastal_countries)}")
610
611
# Calculate distance from each city to nearest country border
612
country_boundaries = world.geometry.boundary
613
cities['dist_to_border'] = cities.geometry.apply(
614
lambda city: min(city.distance(boundary) for boundary in country_boundaries)
615
)
616
print("Cities farthest from any border:")
617
print(cities.nlargest(5, 'dist_to_border')[['name', 'dist_to_border']])
618
```