Open Source Google Maps Street View Panorama Downloader

2 hours ago 2

Python

Full API Documentation

This project provides a Google Street View Panorama Downloader as both an API and a command-line tool. It is optimized to download millions of panoramas asynchronously. Using Python Aiohttp, and Asyncio.

  • Fully asynchronous panorama & tile downloading
  • Detects black tiles and older panoramas with black margins
  • Handles differences between pre-2016 and post-2016 images
  • Configurable zoom levels and concurrency
  • Scales to large datasets (millions of panoramas)

Download panoramas from a dataset with a single command:

python run.py --dataset ./dataset.json --limit 100 --conn-limit 100 --output ./downloads

Example dataset (dataset.json):

[ "-0cRHfYuByN1eUvfjcF-Xg", "zkBlgLSISS2RgEQE0OQ_kg", "TJAWObg1DzqkNWiGpEm8YQ", "1SnVdDntjJavWTlyoxQ5YQ" ]

This will download panoramas into:

downloads/ ├── panos_z2/ │ ├── -0cRHfYuByN1eUvfjcF-Xg.jpg │ ├── zkBlgLSISS2RgEQE0OQ_kg.jpg │ └── ...

Here are some example panoramas at different zoom levels:

Zoom 0 Zoom 0 example

Zoom 1 Zoom 1 example

Zoom 2 Zoom 2 example

Check out the example_panos folder for more samples.

  1. Clone the repository:
git clone https://github.com/yousephzidan/gsvp-dl.git cd gsvp-dl
  1. Create a virtual environment and install dependencies:
python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r req.txt

The entry point is run.py. Available arguments:

python run.py --zoom 2 \ --dataset ./dataset.json \ --max-pano 100 \ --workers 5 \ --limit 100 \ --conn-limit 100 \ --output ./downloads

Arguments:

  • --zoom (int) – Zoom level (0–5) (default: 2)
  • --dataset (str, required) – Path to dataset JSON file (default: ./dataset.json)
  • --max-pano (int) – Max concurrent pano downloads (default: 100)
  • --workers (int) – Max process pool workers (default: 5)
  • --limit (int) – Limit panoids for testing (default: None)
  • --output (str) – Output directory (default: current working dir)
  • --conn-limit (int) – Maximum total TCP connections per host (default: 100)

You can also import it:

import asyncio import aiohttp from gsvpd import fetch_panos from gsvpd import timer from rich import print async def main(): dataset: list[str] = ["list of pano ids"] sem_pano = asyncio.Semaphore(100) connector = aiohttp.TCPConnector(limit_per_host=100) zoom_level: int = 2 workers: int = 5 return await fetch_panos(sem_pano, connector, workers, zoom_level, dataset) if __name__ == "__main__": try: with timer() as t: total_panos, successful_panos, output_dir = asyncio.run(main()) print(f"\n[gray]{'-' * 85}[/]") print(f"\n[orange1]| Processed [green]{successful_panos}/{total_panos}[/] panos in [green]{t.time_elapsed}[/][/]") print(f"[orange1]| Saved at [green]{output_dir}[/][/]\n") except Exception as error: print(f"[red][MAIN]Error: {error}[/]") except KeyboardInterrupt: print("[red]Keyboard Interrupted[/]")

The downloader handles panoramas differently depending on zoom level, image age, and tile grid.

  • Old panoramas (≤2016): lower resolution
  • New panoramas (>2016): higher resolution

Old Panoramas Specs | 2016 and below

Zoom Level Width Height X tiles Y tiles Notes
0 416 208 1 1 Single tile, 55-59% black space at the bottom
1 832 416 2 1 X/Y tile grid same for old/new. Extra bottom black space (≤2016)
2 1664 832 4 2 X/Y tile grid same for old/new. Extra bottom black space (≤2016)
3 3328 1664 7 4 Older images (2016 or earlier); less resolution, fewer tiles on X-Y axis
4 6656 3328 13 7 Older images (2016 or earlier); less resolution, fewer tiles on X-Y axis
5 13312 6656 26 13 Older images (2016 or earlier); less resolution, fewer tiles on X-Y axis

This is how we can tell old panoramas (≤2016) from new ones (>2016) at different zoom levels:

  • Zoom 0: Both old and new panoramas have black space at the bottom.

    • Old panoramas: ~55–59% black space
    • New panoramas: ~40–55% black space
  • Zoom 1 & 2:

    • Old panoramas (≤2016) have extra black space at the bottom tiles.
    • New panoramas (>2016) do not have this extra black space.

New Panoramas Specs | After 2016k

Zoom Level Width Height X tiles Y tiles Notes
0 512 256 1 1 Single tile, 40-55% black space at the bottom
1 1024 512 2 1 X/Y tile grid same for old/new panos. No extra bottom black space
2 2048 1024 4 2 X/Y tile grid same for old/new panos. No extra bottom black space
3 4096 2048 8 4 Modern images (>2016)
4 8192 4096 16 8 Modern images (>2016)
5 16384 8192 32 16 Modern images (>2016)

For zoom levels 3, 4, and 5, the difference between old and new panoramas is captured using a dictionary that maps the number of X/Y tiles to the correct image width and height.

  • API endpoints are not official and may change.
  • Black tile detection is heuristic and may not be 100% accurate.
  • Downloading millions of panoramas may hit Google rate limits.

This project is intended for educational and research purposes only.

By using this project, you agree that:

  • You are solely responsible for how you use it.
  • The maintainers are not liable for any misuse, account bans, legal issues, or damages.

Contributions are welcome!

  • Fork the repo and create a new branch
  • Make your changes with clear commit messages
  • Submit a pull request

For larger changes, please open an issue first to discuss.

This project is licensed under the MIT License.

Read Entire Article