0
# Coordinate Transformation
1
2
Functions for reprojecting and transforming map tiles and images between different coordinate reference systems. These capabilities enable integration with diverse geospatial workflows and coordinate systems.
3
4
## Capabilities
5
6
### Tile Warping
7
8
Reproject Web Mercator basemap tiles into any coordinate reference system with configurable resampling methods for optimal visual quality.
9
10
```python { .api }
11
def warp_tiles(img, extent, t_crs='EPSG:4326', resampling=Resampling.bilinear):
12
"""
13
Reproject Web Mercator basemap into any CRS on-the-fly.
14
15
Parameters:
16
- img: ndarray - Image as 3D array (height, width, bands) of RGB values
17
- extent: tuple - Bounding box (minX, maxX, minY, maxY) in Web Mercator (EPSG:3857)
18
- t_crs: str or CRS - Target coordinate reference system (default: WGS84 lon/lat)
19
- resampling: Resampling - Resampling method for warping (default: bilinear)
20
21
Returns:
22
tuple: (img: ndarray, extent: tuple)
23
- img: Warped image as 3D array (height, width, bands)
24
- extent: Bounding box (minX, maxX, minY, maxY) in target CRS
25
"""
26
```
27
28
**Usage Examples:**
29
30
```python
31
import contextily as ctx
32
from rasterio.enums import Resampling
33
import matplotlib.pyplot as plt
34
35
# Download Web Mercator tiles
36
west, south, east, north = -74.1, 40.7, -73.9, 40.8 # NYC bounds (lon/lat)
37
img, extent_3857 = ctx.bounds2img(west, south, east, north, zoom=12, ll=True)
38
39
# Transform to WGS84 (lon/lat) coordinates
40
img_4326, extent_4326 = ctx.warp_tiles(img, extent_3857, t_crs='EPSG:4326')
41
42
# Transform to UTM Zone 18N (appropriate for NYC)
43
img_utm, extent_utm = ctx.warp_tiles(img, extent_3857, t_crs='EPSG:32618')
44
45
# Use different resampling method for better quality
46
img_cubic, extent_cubic = ctx.warp_tiles(img, extent_3857,
47
t_crs='EPSG:4326',
48
resampling=Resampling.cubic)
49
50
# Plot comparison
51
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
52
53
ax1.imshow(img, extent=extent_3857)
54
ax1.set_title('Web Mercator (EPSG:3857)')
55
ax1.set_xlabel('Easting (m)')
56
ax1.set_ylabel('Northing (m)')
57
58
ax2.imshow(img_4326, extent=extent_4326)
59
ax2.set_title('WGS84 (EPSG:4326)')
60
ax2.set_xlabel('Longitude')
61
ax2.set_ylabel('Latitude')
62
63
plt.show()
64
```
65
66
### Image Transform Warping
67
68
Reproject images with known transforms and coordinate systems, providing full control over the transformation process for advanced geospatial workflows.
69
70
```python { .api }
71
def warp_img_transform(img, transform, s_crs, t_crs, resampling=Resampling.bilinear):
72
"""
73
Reproject image with given transform and source/target CRS.
74
75
Parameters:
76
- img: ndarray - Image as 3D array (bands, height, width) in rasterio format
77
- transform: affine.Affine - Transform of input image from rasterio/affine
78
- s_crs: str or CRS - Source coordinate reference system
79
- t_crs: str or CRS - Target coordinate reference system
80
- resampling: Resampling - Resampling method for warping
81
82
Returns:
83
tuple: (w_img: ndarray, w_transform: affine.Affine)
84
- w_img: Warped image as 3D array (bands, height, width)
85
- w_transform: Transform of warped image
86
"""
87
```
88
89
**Usage Examples:**
90
91
```python
92
import contextily as ctx
93
import rasterio
94
from rasterio.transform import from_bounds
95
from rasterio.enums import Resampling
96
import numpy as np
97
98
# Load existing georeferenced raster
99
with rasterio.open('input_raster.tif') as src:
100
img = src.read() # Shape: (bands, height, width)
101
transform = src.transform
102
source_crs = src.crs
103
104
# Reproject to different CRS
105
img_reproj, transform_reproj = ctx.warp_img_transform(
106
img, transform, source_crs, 'EPSG:4326'
107
)
108
109
# Create transform from known bounds (Web Mercator example)
110
west_m, south_m, east_m, north_m = -8238000, 4969000, -8236000, 4971000
111
height, width = 1000, 1000
112
transform_wm = from_bounds(west_m, south_m, east_m, north_m, width, height)
113
114
# Create synthetic data for demonstration
115
synthetic_img = np.random.randint(0, 255, (3, height, width), dtype=np.uint8)
116
117
# Transform from Web Mercator to UTM
118
img_utm, transform_utm = ctx.warp_img_transform(
119
synthetic_img, transform_wm, 'EPSG:3857', 'EPSG:32618',
120
resampling=Resampling.nearest
121
)
122
123
# Save reprojected result
124
with rasterio.open('reprojected.tif', 'w',
125
driver='GTiff',
126
height=img_utm.shape[1],
127
width=img_utm.shape[2],
128
count=img_utm.shape[0],
129
dtype=img_utm.dtype,
130
crs='EPSG:32618',
131
transform=transform_utm) as dst:
132
dst.write(img_utm)
133
```
134
135
## Resampling Methods
136
137
### Available Resampling Algorithms
138
139
```python
140
from rasterio.enums import Resampling
141
142
# Common resampling methods
143
Resampling.nearest # Nearest neighbor - preserves pixel values, blocky appearance
144
Resampling.bilinear # Bilinear interpolation - smooth, good for continuous data
145
Resampling.cubic # Cubic convolution - smoother, slower
146
Resampling.lanczos # Lanczos windowed sinc - high quality, slower
147
Resampling.average # Average of contributing pixels
148
Resampling.mode # Most common value - good for categorical data
149
```
150
151
### Choosing Resampling Methods
152
153
```python
154
import contextily as ctx
155
from rasterio.enums import Resampling
156
157
# Download base image
158
img, extent = ctx.bounds2img(-74.1, 40.7, -73.9, 40.8, zoom=12, ll=True)
159
160
# Nearest neighbor - best for categorical/discrete data
161
img_nearest, _ = ctx.warp_tiles(img, extent, resampling=Resampling.nearest)
162
163
# Bilinear - good general purpose, default
164
img_bilinear, _ = ctx.warp_tiles(img, extent, resampling=Resampling.bilinear)
165
166
# Cubic - smoother for imagery, slower
167
img_cubic, _ = ctx.warp_tiles(img, extent, resampling=Resampling.cubic)
168
169
# Lanczos - highest quality for imagery
170
img_lanczos, _ = ctx.warp_tiles(img, extent, resampling=Resampling.lanczos)
171
```
172
173
## Common Coordinate Systems
174
175
### Frequently Used CRS
176
177
```python
178
# Geographic coordinate systems (lat/lon)
179
'EPSG:4326' # WGS84 - World wide standard
180
'EPSG:4269' # NAD83 - North America
181
'EPSG:4258' # ETRS89 - Europe
182
183
# Projected coordinate systems
184
'EPSG:3857' # Web Mercator - Web mapping standard
185
'EPSG:32618' # UTM Zone 18N - US East Coast
186
'EPSG:32633' # UTM Zone 33N - Central Europe
187
'EPSG:3395' # World Mercator
188
'EPSG:3031' # Antarctic Polar Stereographic
189
'EPSG:3413' # WGS 84 / NSIDC Sea Ice Polar Stereographic North
190
```
191
192
### CRS Format Options
193
194
```python
195
# String formats supported
196
ctx.warp_tiles(img, extent, t_crs='EPSG:4326') # EPSG code
197
ctx.warp_tiles(img, extent, t_crs='WGS84') # Common name
198
ctx.warp_tiles(img, extent, t_crs='+proj=longlat +datum=WGS84') # PROJ string
199
200
# Using rasterio CRS objects
201
from rasterio.crs import CRS
202
target_crs = CRS.from_epsg(4326)
203
ctx.warp_tiles(img, extent, t_crs=target_crs)
204
```
205
206
## Integration Examples
207
208
### Working with GeoPandas
209
210
```python
211
import contextily as ctx
212
import geopandas as gpd
213
import matplotlib.pyplot as plt
214
215
# Load vector data
216
gdf = gpd.read_file('data.shp')
217
print(f"Original CRS: {gdf.crs}")
218
219
# Get basemap in same CRS as data
220
if gdf.crs != 'EPSG:3857':
221
# Get extent in data's CRS
222
bounds = gdf.total_bounds # [minx, miny, maxx, maxy]
223
224
# Download tiles in Web Mercator
225
img, extent_3857 = ctx.bounds2img(bounds[0], bounds[1], bounds[2], bounds[3],
226
zoom=12, ll=(gdf.crs == 'EPSG:4326'))
227
228
# Transform tiles to match data CRS
229
img_reproj, extent_reproj = ctx.warp_tiles(img, extent_3857, t_crs=gdf.crs)
230
231
# Plot with matching coordinates
232
ax = gdf.plot(figsize=(12, 8), alpha=0.7)
233
ax.imshow(img_reproj, extent=extent_reproj, alpha=0.8)
234
plt.show()
235
```
236
237
### Working with Rasterio
238
239
```python
240
import contextily as ctx
241
import rasterio
242
from rasterio.warp import transform_bounds
243
244
# Load existing raster data
245
with rasterio.open('analysis_area.tif') as src:
246
data_crs = src.crs
247
data_bounds = src.bounds
248
249
# Transform bounds to WGS84 for tile download
250
ll_bounds = transform_bounds(data_crs, 'EPSG:4326', *data_bounds)
251
252
# Download basemap tiles
253
img, extent_3857 = ctx.bounds2img(ll_bounds[0], ll_bounds[1],
254
ll_bounds[2], ll_bounds[3],
255
zoom=14, ll=True)
256
257
# Transform tiles to match raster CRS
258
img_reproj, extent_reproj = ctx.warp_tiles(img, extent_3857, t_crs=data_crs)
259
260
# Now img_reproj and your raster data are in same CRS
261
raster_data = src.read()
262
```
263
264
### Custom Projection Workflows
265
266
```python
267
import contextily as ctx
268
from pyproj import CRS, Transformer
269
270
# Define custom projection (example: Albers Equal Area for continental US)
271
custom_proj = """
272
+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96
273
+x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs
274
"""
275
276
# Download tiles for continental US
277
img, extent = ctx.bounds2img(-125, 24, -66, 49, zoom=5, ll=True)
278
279
# Transform to custom projection
280
img_custom, extent_custom = ctx.warp_tiles(img, extent, t_crs=custom_proj)
281
282
# Use with projection-aware plotting
283
import matplotlib.pyplot as plt
284
from matplotlib.patches import Rectangle
285
286
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 8))
287
288
# Web Mercator view
289
ax1.imshow(img, extent=extent)
290
ax1.set_title('Web Mercator')
291
292
# Custom projection view
293
ax2.imshow(img_custom, extent=extent_custom)
294
ax2.set_title('Albers Equal Area')
295
296
plt.show()
297
```
298
299
## Performance Considerations
300
301
### Memory Management
302
303
```python
304
# Large image warping can be memory intensive
305
# Process in chunks for very large datasets
306
def warp_large_image(img, extent, t_crs, chunk_size=1000):
307
"""Warp large images in smaller chunks."""
308
h, w, bands = img.shape
309
warped_chunks = []
310
311
for i in range(0, h, chunk_size):
312
for j in range(0, w, chunk_size):
313
# Extract chunk
314
chunk = img[i:i+chunk_size, j:j+chunk_size, :]
315
316
# Calculate chunk extent
317
# ... extent calculation logic ...
318
319
# Warp chunk
320
chunk_warped, _ = ctx.warp_tiles(chunk, chunk_extent, t_crs=t_crs)
321
warped_chunks.append(chunk_warped)
322
323
# Reassemble chunks
324
# ... reassembly logic ...
325
return warped_image, warped_extent
326
```
327
328
### Quality vs Speed Tradeoffs
329
330
```python
331
from rasterio.enums import Resampling
332
333
# Fast transformation (lower quality)
334
img_fast, extent_fast = ctx.warp_tiles(img, extent,
335
resampling=Resampling.nearest)
336
337
# High quality transformation (slower)
338
img_quality, extent_quality = ctx.warp_tiles(img, extent,
339
resampling=Resampling.lanczos)
340
341
# Balanced approach (good quality, reasonable speed)
342
img_balanced, extent_balanced = ctx.warp_tiles(img, extent,
343
resampling=Resampling.cubic)
344
```
345
346
## Error Handling
347
348
### Common Transformation Issues
349
350
```python
351
import contextily as ctx
352
from rasterio.errors import CRSError
353
354
try:
355
img_warped, extent_warped = ctx.warp_tiles(img, extent, t_crs='INVALID:1234')
356
except CRSError as e:
357
print(f"Invalid CRS: {e}")
358
# Use fallback CRS
359
img_warped, extent_warped = ctx.warp_tiles(img, extent, t_crs='EPSG:4326')
360
361
# Handle extreme transformations
362
try:
363
# Very large extent might cause issues
364
img_warped, extent_warped = ctx.warp_tiles(img, extent, t_crs='EPSG:3413')
365
except Exception as e:
366
print(f"Transformation failed: {e}")
367
# Consider clipping to smaller extent or different resampling
368
```
369
370
### Validation and Debugging
371
372
```python
373
import contextily as ctx
374
import rasterio
375
from rasterio.warp import transform_bounds
376
377
def validate_transformation(img, extent, source_crs, target_crs):
378
"""Validate transformation parameters before processing."""
379
380
# Check if transformation is reasonable
381
try:
382
# Test transform with small extent
383
test_bounds = transform_bounds(source_crs, target_crs, *extent)
384
print(f"Source extent: {extent}")
385
print(f"Target extent: {test_bounds}")
386
387
# Check for extreme distortion
388
source_area = (extent[1] - extent[0]) * (extent[3] - extent[2])
389
target_area = (test_bounds[1] - test_bounds[0]) * (test_bounds[3] - test_bounds[2])
390
area_ratio = target_area / source_area
391
392
if area_ratio > 100 or area_ratio < 0.01:
393
print(f"Warning: Large area distortion (ratio: {area_ratio:.2f})")
394
395
except Exception as e:
396
print(f"Transformation validation failed: {e}")
397
return False
398
399
return True
400
401
# Use validation before warping
402
if validate_transformation(img, extent, 'EPSG:3857', 'EPSG:4326'):
403
img_warped, extent_warped = ctx.warp_tiles(img, extent, t_crs='EPSG:4326')
404
```