Skip to content

Commit d00cfb6

Browse files
authored
Merge pull request #116 from GeoOcean/WMS
Wms
2 parents 64aaa51 + 67bb5e5 commit d00cfb6

1 file changed

Lines changed: 143 additions & 12 deletions

File tree

bluemath_tk/core/plotting/WMS_USGSTopo.py

Lines changed: 143 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ def plot_usgs_raster_map(
169169
lon_min: float,
170170
lon_max: float,
171171
zoom: int = None,
172+
source: str = "arcgis",
172173
verbose: bool = True,
173174
mask_ocean: bool = False,
174175
add_features: bool = True,
@@ -180,15 +181,39 @@ def plot_usgs_raster_map(
180181
Parameters
181182
----------
182183
lat_min : float
183-
Minimum latitude of the region.
184+
Minimum latitude of the bounding box.
184185
lat_max : float
185-
Maximum latitude of the region.
186+
Maximum latitude of the bounding box.
186187
lon_min : float
187-
Minimum longitude of the region.
188+
Minimum longitude of the bounding box.
188189
lon_max : float
189-
Maximum longitude of the region.
190+
Maximum longitude of the bounding box.
191+
zoom : int, optional
192+
Zoom level (default is None, which auto-calculates based on bounding box).
193+
source : str, optional
194+
Source of the map tiles (default is "arcgis"). Options include:
195+
- "arcgis" (Esri World Imagery)
196+
- "google" (Google Maps Satellite)
197+
- "eox" (EOX Sentinel-2 Cloudless)
198+
- "osm" (OpenStreetMap Standard Tiles)
199+
- "amazon" (Amazon Terrarium Elevation Tiles)
200+
- "esri_world" (Esri World Imagery)
201+
- "geoportail_fr" (Geoportail France Orthophotos)
202+
- "ign_spain_pnoa" (IGN Spain PNOA Orthophotos)
203+
verbose : bool, optional
204+
If True, prints additional information about the source and zoom level.
205+
mask_ocean : bool, optional
206+
If True, masks ocean areas in the map.
207+
add_features : bool, optional
208+
If True, adds borders, coastlines, and states to the map.
190209
display_width_px : int, optional
191-
Approximate pixel width for display (default is 1024).
210+
Width of the display in pixels (default is 1024).
211+
Returns
212+
-------
213+
fig : plt.Figure
214+
The matplotlib figure containing the map.
215+
ax : plt.Axes
216+
The matplotlib axes with the map projection.
192217
"""
193218

194219
tile_size = 256
@@ -203,11 +228,117 @@ def plot_usgs_raster_map(
203228
height = y_end - y_start + 1
204229

205230
map_img = Image.new("RGB", (width * tile_size, height * tile_size))
206-
tile_url = "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"
231+
if source == "arcgis":
232+
tile_url = "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
233+
z_max = 19
234+
if verbose:
235+
print(
236+
"Using Esri World Imagery (ArcGIS):\n"
237+
"- Free for public and non-commercial use\n"
238+
"- Commercial use allowed under Esri terms of service\n"
239+
"- Attribution required: 'Tiles © Esri — Sources: Esri, Earthstar Geographics, CNES/Airbus DS, "
240+
"USDA, USGS, AeroGRID, IGN, and the GIS User Community'\n"
241+
"- Max zoom ~19"
242+
)
243+
244+
elif source == "google":
245+
tile_url = "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
246+
z_max = 21
247+
if verbose:
248+
print(
249+
"Using Google Maps Satellite:\n"
250+
"- NOT license-free\n"
251+
"- Usage outside official Google Maps SDKs/APIs is prohibited\n"
252+
"- Commercial use requires a paid license from Google\n"
253+
"- May be blocked without an API key\n"
254+
"- Max zoom ~21"
255+
)
256+
257+
elif source == "eox":
258+
tile_url = "https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2024_3857/default/g/{z}/{y}/{x}.jpg"
259+
z_max = 16
260+
if verbose:
261+
print(
262+
"Using EOX Sentinel-2 Cloudless:\n"
263+
"- Based on Copernicus Sentinel-2 data processed by EOX\n"
264+
"- Licensed under Creative Commons BY-NC-SA 4.0 (non-commercial use only)\n"
265+
"- Attribution required: 'Sentinel-2 cloudless – © EOX, based on modified Copernicus Sentinel data 2016–2024'\n"
266+
"- Versions from 2016–2017 are under CC BY 4.0 (commercial use allowed)\n"
267+
"- Max zoom ~16"
268+
)
269+
270+
elif source == "osm":
271+
tile_url = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
272+
z_max = 19
273+
if verbose:
274+
print(
275+
"Using OpenStreetMap Standard Tiles:\n"
276+
"- Free and open-source (ODbL license)\n"
277+
"- Commercial use allowed with attribution\n"
278+
"- Attribution required: '© OpenStreetMap contributors'\n"
279+
"- Max zoom ~19"
280+
)
281+
282+
elif source == "amazon":
283+
tile_url = "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"
284+
z_max = 15
285+
if verbose:
286+
print(
287+
"Using Amazon Terrarium Elevation Tiles:\n"
288+
"- Free for public use\n"
289+
"- Attribution recommended: 'Amazon Terrarium'\n"
290+
"- Max zoom ~15"
291+
)
292+
293+
elif source == "esri_world":
294+
tile_url = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
295+
z_max = 19
296+
if verbose:
297+
print(
298+
"Using Esri World Imagery:\n"
299+
"- High-resolution global imagery\n"
300+
"- Free with attribution under Esri terms\n"
301+
"- Max zoom ~19"
302+
)
303+
304+
elif source == "geoportail_fr":
305+
tile_url = ("https://data.geopf.fr/wmts?"
306+
"REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0"
307+
"&STYLE=normal&TILEMATRIXSET=PM&FORMAT=image/jpeg"
308+
"&LAYER=ORTHOIMAGERY.ORTHOPHOTOS&TILEMATRIX={z}"
309+
"&TILEROW={y}&TILECOL={x}")
310+
z_max = 19
311+
if verbose:
312+
print(
313+
"Using Geoportail France (Orthophotos):\n"
314+
"- Aerial orthophotos from the French National Institute (IGN)\n"
315+
"- Free for public use under Etalab license\n"
316+
"- Attribution: Geoportail France / IGN\n"
317+
"- Max zoom ~19"
318+
)
319+
elif source == "ign_spain_pnoa":
320+
tile_url = ("https://tms-pnoa-ma.idee.es/1.0.0/pnoa-ma/{z}/{x}/{y_inv}.jpeg")
321+
z_max = 19
322+
if verbose:
323+
print(
324+
"Using IGN Spain PNOA Orthophotos:\n"
325+
"- High-resolution aerial imagery (PNOA program)\n"
326+
"- Provided by IGN/CNIG (Government of Spain)\n"
327+
"- Free to use under Creative Commons BY 4.0 license\n"
328+
"- Attribution required: 'Ortofotografía PNOA – © IGN / CNIG (Gobierno de España) – CC BY 4.0'\n"
329+
"- Max zoom ~19"
330+
)
331+
332+
if zoom > z_max:
333+
zoom = z_max
207334

208335
for x in range(x_start, x_end + 1):
209336
for y in range(y_start, y_end + 1):
210-
url = tile_url.format(z=zoom, x=x, y=y)
337+
if source == "ign_spain_pnoa":
338+
y_inv = (2**zoom - 1) - y
339+
url = tile_url.format(z=zoom, x=x, y_inv=y_inv)
340+
else:
341+
url = tile_url.format(z=zoom, x=x, y=y)
211342
try:
212343
response = requests.get(url, timeout=10)
213344
tile = Image.open(BytesIO(response.content))
@@ -219,13 +350,12 @@ def plot_usgs_raster_map(
219350

220351
xmin, ymin, xmax, ymax = tile_bounds_meters(x_start, y_start, x_end, y_end, zoom)
221352

222-
crs_tiles = ccrs.Mercator.GOOGLE
353+
crs_tiles = ccrs.PlateCarree()
223354
fig = plt.figure(figsize=(12, 8))
224355
ax = plt.axes(projection=crs_tiles)
225-
ax.set_extent([xmin, xmax, ymin, ymax], crs=crs_tiles)
226-
356+
ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=crs_tiles)
227357
ax.imshow(
228-
map_img, origin="upper", extent=[xmin, xmax, ymin, ymax], transform=crs_tiles
358+
map_img, origin="upper", extent=[xmin, xmax, ymin, ymax], transform=ccrs.Mercator.GOOGLE
229359
)
230360
scale = get_cartopy_scale(zoom)
231361
if verbose:
@@ -237,7 +367,7 @@ def plot_usgs_raster_map(
237367
ax.add_feature(cfeature.STATES.with_scale(scale), linewidth=0.5)
238368

239369
if mask_ocean:
240-
ax.add_feature(cfeature.OCEAN.with_scale(scale), facecolor="w", zorder=3)
370+
ax.add_feature(cfeature.OCEAN.with_scale(scale), facecolor="w", zorder=1)
241371

242372
return fig, ax
243373

@@ -256,3 +386,4 @@ def plot_usgs_raster_map(
256386
mask_ocean=True,
257387
)
258388
fig.show()
389+

0 commit comments

Comments
 (0)