How to Use Python to Download High-Resolution RGB Satellite Tiles from Esri World Imagery ?

Introduction

To request a satellite tile image (from Esri World Imagery) centered at a given latitude, longitude, and zoom level, you can use Python.

Step-by-step Python Example

Requirements:

1
pip install mercantile requests pillow

Python Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import mercantile
import requests
from PIL import Image
from io import BytesIO

# Define your center coordinates and zoom level
lat = 37.7749       # Example: San Francisco
lon = -122.4194
zoom = 12           # Zoom level (0–19)

# Step 1: Convert lat/lon to XYZ tile
tile = mercantile.tile(lon, lat, zoom)
x, y, z = tile.x, tile.y, tile.z

# Step 2: Construct Esri tile URL
url = f"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"

# Step 3: Request and display/save the tile image
response = requests.get(url)
if response.status_code == 200:
    img = Image.open(BytesIO(response.content))
    img.show()  # To display
    img.save(f"esri_tile_z{z}_x{x}_y{y}.png")  # To save
    print(f"Saved tile at zoom {z}, x={x}, y={y}")
else:
    print("Failed to download tile:", response.status_code)

How to Use Python to Download High-Resolution RGB Satellite Tiles from Esri World Imagery ?
How to Use Python to Download High-Resolution RGB Satellite Tiles from Esri World Imagery ?

Tile Size and Coverage

Each tile is:

  • 256×256 pixels
  • At zoom level:
    1. z=0: world = 1 tile
    2. z=1: 2×2 tiles
    3. ...
    4. z=19: \~38cm resolution, millions of tiles

Notes:

  • Esri allows usage under fair use and non-commercial limits. For large-scale use, check their terms of service.
  • For deep learning, you may want to normalize lat/lon → multiple tiles → stitched image → patch → label.

Improving image resolution

The default Esri tile server (https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}) provides pre-rendered 256×256 pixel tiles, and the resolution depends on the zoom level.

If the image appears blurry or low-resolution, you can improve it by:

Requesting Higher Zoom Levels**

Each increase in zoom level approximately doubles the spatial resolution. For example:

Zoom Approx. Resolution
12 \~38 meters/pixel
16 \~2.4 meters/pixel
19 \~0.3 meters/pixel

So, try setting zoom = 18 or zoom = 19. At those zoom levels, Esri often provides very high-resolution imagery (urban areas especially).

Update line:

1
zoom = 19

But note:

  • The image will still be only 256x256 pixels, just representing a smaller real-world area with more detail.
  • To get a larger image with high detail, you need to download and stitch multiple tiles.

Stitch Multiple Tiles (e.g., 3x3 or 5x5 Tile Patch)**

Here’s how to get a high-res image centered at a point by stitching multiple high-zoom tiles:

Install:

1
pip install mercantile requests pillow

Code to Stitch Tiles:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import mercantile
import requests
from PIL import Image
from io import BytesIO

def get_tile_image(z, x, y):
    url = f"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
    response = requests.get(url)
    if response.status_code == 200:
        return Image.open(BytesIO(response.content))
    else:
        return None

def stitch_tiles(lat, lon, zoom=18, tiles_across=3):
    center_tile = mercantile.tile(lon, lat, zoom)
    half = tiles_across // 2

    stitched = Image.new("RGB", (256 * tiles_across, 256 * tiles_across))

    for dx in range(-half, half + 1):
        for dy in range(-half, half + 1):
            x = center_tile.x + dx
            y = center_tile.y + dy
            img = get_tile_image(zoom, x, y)
            if img:
                stitched.paste(img, ((dx + half) * 256, (dy + half) * 256))
            else:
                print(f"Tile {x},{y} failed")

    return stitched

# Example usage:
lat, lon = 37.7749, -122.4194
zoom = 18
tiles_across = 3  # for a 768x768 image

stitched_image = stitch_tiles(lat, lon, zoom, tiles_across)
stitched_image.show()
stitched_image.save("stitched_highres_patch.png")

Result

  • You'll get a 768×768 pixel image (or larger, depending on tiles_across).
  • Each pixel will have high spatial resolution, especially at zoom=18 or zoom=19.
  • This is ideal for training neural networks with good geographic detail.

How to Use Python to Download High-Resolution RGB Satellite Tiles from Esri World Imagery ?
How to Use Python to Download High-Resolution RGB Satellite Tiles from Esri World Imagery ?

Note: You can reduce the size of the stitched image in your existing code by:

  1. Converting it to JPEG (or WebP) to save space
  2. Optionally resizing it before saving

Option 1: Save as JPEG (smaller than PNG)

Just replace your last line:

1
stitched_image.save("stitched_highres_patch.png")

With this:

1
stitched_image.save("stitched_highres_patch.jpg", format="JPEG", quality=85, optimize=True)

This will compress the image without visibly affecting quality, and reduce storage from \~1–6 MB PNG → \~200–800 KB JPEG.

Option 2: Resize + Save as JPEG (smaller still)

If you don't need full 768×768 resolution, resize before saving:

1
2
resized = stitched_image.resize((384, 384), Image.LANCZOS)  # or 512x512, or whatever you prefer
resized.save("stitched_highres_patch.jpg", format="JPEG", quality=85, optimize=True)

This reduces both dimensions and file size, perfect for ML training data.

Option 3: Save as WebP (best compression)

Requires modern readers, but better compression than JPEG:

1
stitched_image.save("stitched_highres_patch.webp", format="WEBP", quality=80, method=6)

Summary:

Method File Type Typical Size Notes
PNG (your original) Lossless 1–6 MB Big, high quality
JPEG Lossy 200–800 KB Small, good for ML
Resized JPEG Lossy 50–300 KB Smaller + faster
WebP Lossy or lossless 30–70% of JPEG Best compression

Want Even Better Quality?

If Esri’s tiles still aren't good enough:

  • Use Google Static Maps API (limited free use)
  • Try Maxar (commercial, via Esri Premium)
  • Consider NAIP (1m) via Microsoft Planetary Computer or Google Earth Engine

Acquisition date

When you download a tile from Esri’s World Imagery tile server, the exact acquisition date of the satellite image is not embedded in the tile or URL. These are pre-rendered map tiles stitched together from a global mosaic, and the imagery dates vary by location and zoom level.

What You Can’t Do Directly

You cannot get the acquisition date from:

1
`https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}`

The image metadata (it’s just a static PNG with no useful EXIF or metadata)

Use Google Earth Engine or Sentinel/Planet Imagery Instead

If you want precise control over:

  • Image acquisition date
  • Cloud cover
  • Spatial resolution
  • Spectral bands

Then use:

  • Google Earth Engine (GEE): Query Landsat, Sentinel, MODIS by date
  • SentinelHub or Microsoft Planetary Computer: Same idea, all STAC-based imagery

Summary

Source Acquisition Date Available? How to Access
Esri World Imagery Tiles ❌ Not directly Use metadata service + lat/lon
Esri World Imagery Metadata ✅ Yes REST query or ArcGIS tool
Google Earth Engine ✅ Yes Filter imagery by date + cloud cover
SentinelHub / Planetary Comp. ✅ Yes Full access to date, bands, quality

References

Links Site
esri website esri
Layer: Low Resolution 15m Imagery server.arcgisonline.com
World Imagery arcgis.com
Image

of