Download satellite map tiles from the command line or as a Python library
Project description
geodot
geodot downloads satellite map tiles and can be used as a CLI or as a library from Python, JavaScript, and Rust.
Tiles are saved as:
{out}/tiles/{z}/{x}/{y}.jpg
{out}/manifest.json
{out}/index.html
Install
cargo install geodot
npm install -g @geodot/cli
npm install @geodot/lib
pip install geodot
Run without installing globally:
npx -y @geodot/cli -x 37.6504907 -y 55.7303 -z 18 -c 1 -r 1
uvx geodot -x 37.6504907 -y 55.7303 -z 18 -c 1 -r 1
cargo install geodot && geodot -x 37.6504907 -y 55.7303 -z 18 -c 1 -r 1
During local development:
python -m pip install -e '.[test]'
npm test
cargo test --manifest-path rust/Cargo.toml
Lint and format checks:
python -m pip install -e '.[test,dev]'
ruff format --check python
ruff check python
npm install
npm run format:check
npm run lint
cargo fmt --manifest-path rust/Cargo.toml -- --check
cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
CLI
geodot -x 37.6504907 -y 55.7303 -z 18 -c 3 -r 3 -o data -j 16
geodot -x 37.6504907 -y 55.7303 --x2 37.652 --y2 55.7297 -z 18 -o data
geodot -p "37.6504,55.7304;37.6520,55.7304;37.6520,55.7297;37.6504,55.7297" -z 18 -o data
geodot --geojson area.geojson -z 18 -o data
geodot --geojson https://example.com/area.geojson -z 18 -o data
| Flag | Default | Description |
|---|---|---|
-x, --lon |
37.6504907 |
Top-left longitude |
-y, --lat |
55.7303 |
Top-left latitude |
--x2, --bottom-right-lon |
none | Bottom-right longitude |
--y2, --bottom-right-lat |
none | Bottom-right latitude |
-p, --polygon |
none | Closed area as lon,lat;lon,lat;lon,lat |
-g, --geojson |
none | GeoJSON Polygon, Feature, or FeatureCollection file path or URL |
-z, --zoom |
18 |
Zoom level |
-c, --cols |
3 |
Tile columns to the right of the top-left tile |
-r, --rows |
3 |
Tile rows downward from the top-left tile |
-o, --out |
data |
Output directory |
-j, --jobs |
16 |
Concurrent downloads |
--no-manifest |
off | Do not write manifest.json |
--no-demo |
off | Do not write index.html |
Output
For -o data, a 3 by 3 download at zoom 18 writes files like this:
data/
├── manifest.json
├── index.html
└── tiles/
└── 18/
└── 158488/
└── 81979.jpg
JPEG bytes are written directly from the tile server without re-compression.
Run geodot demo to inspect the downloaded tiles as a MapLibre raster overlay on a satellite base map at their tile coordinates and zoom. The demo serves {out}/index.html and reads tiles from {out}/tiles/{z}/{x}/{y}.jpg; it does not depend on manifest.json. Use the corner opacity control to compare the overlay against the base map. Zooming is disabled because the output folder only contains the downloaded zoom level.
Do not open index.html with file://; browsers block local tile loading from file origins. Serve the output folder instead:
geodot demo
Then open http://127.0.0.1:8000/. Use geodot demo -o other-dir for a different output folder. Pass --no-manifest when you only want tiles and the demo, or --no-demo when you only want tiles and manifest.json.
manifest.json contains:
{
"center": { "x": 158488, "y": 81979, "z": 18 },
"tiles": [
{
"tile": { "x": 158488, "y": 81979, "z": 18 },
"bounds": {
"lat_min": 55.730012,
"lon_min": 37.650146,
"lat_max": 55.730793,
"lon_max": 37.651520
},
"path": "data/tiles/18/158488/81979.jpg",
"bytes": 12345
}
],
"failed": []
}
Selection Modes
geodot supports three tile selection modes:
- Grid mode:
-x/-yselects the top-left tile, thencols/rowsexpands right and down. - Rectangle mode:
-x/-yis the top-left geographic coordinate and--x2/--y2is the bottom-right geographic coordinate. - Polygon mode:
-p/--polygonspecifies a closed area as semicolon-separatedlon,latpairs. The closing edge is implicit.
Polygon downloads include tiles whose center or corners fall inside the polygon, plus tiles containing polygon vertices.
GeoJSON input uses the first Polygon geometry found in a Polygon, MultiPolygon, Feature, or FeatureCollection and uses its exterior ring as the download polygon.
Python API
from geodot import Coordinate, DownloadOptions, download, latlon_to_tile, tile_bounds, tile_grid, tile_grid_between, tile_grid_for_polygon
tile = latlon_to_tile(55.7303, 37.6504907, 18)
bounds = tile_bounds(tile)
tiles = tile_grid(55.7303, 37.6504907, zoom=18, cols=3, rows=3)
rectangle = tile_grid_between(55.7303, 37.6504907, 55.7297, 37.652, zoom=18)
polygon = tile_grid_for_polygon([
Coordinate(lon=37.6504, lat=55.7304),
Coordinate(lon=37.6520, lat=55.7304),
Coordinate(lon=37.6520, lat=55.7297),
Coordinate(lon=37.6504, lat=55.7297),
], zoom=18)
report = download(DownloadOptions(
lat=55.7303,
lon=37.6504907,
bottom_right_lat=55.7297,
bottom_right_lon=37.652,
zoom=18,
cols=3,
rows=3,
out="data",
jobs=16,
geojson=None,
no_manifest=False,
no_demo=False,
))
JavaScript API
import { download, latlonToTile, tileBounds, tileGrid, tileGridBetween, tileGridForPolygon } from '@geodot/lib';
const tile = latlonToTile(55.7303, 37.6504907, 18);
const bounds = tileBounds(tile);
const tiles = tileGrid(55.7303, 37.6504907, 18, 3, 3);
const rectangle = tileGridBetween(55.7303, 37.6504907, 55.7297, 37.652, 18);
const polygon = tileGridForPolygon([
{ lon: 37.6504, lat: 55.7304 },
{ lon: 37.6520, lat: 55.7304 },
{ lon: 37.6520, lat: 55.7297 },
{ lon: 37.6504, lat: 55.7297 },
], 18);
const report = await download({
lat: 55.7303,
lon: 37.6504907,
bottomRightLat: 55.7297,
bottomRightLon: 37.652,
zoom: 18,
cols: 3,
rows: 3,
out: 'data',
jobs: 16,
geojson: undefined,
noManifest: false,
noDemo: false,
});
Rust API
use geodot::{download, latlon_to_tile, tile_bounds, tile_grid, tile_grid_between, tile_grid_for_polygon, Coordinate, DownloadOptions};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let tile = latlon_to_tile(55.7303, 37.6504907, 18);
let bounds = tile_bounds(tile);
let tiles = tile_grid(55.7303, 37.6504907, 18, 3, 3);
let rectangle = tile_grid_between(55.7303, 37.6504907, 55.7297, 37.652, 18);
let polygon = tile_grid_for_polygon(&[
Coordinate { lon: 37.6504, lat: 55.7304 },
Coordinate { lon: 37.6520, lat: 55.7304 },
Coordinate { lon: 37.6520, lat: 55.7297 },
Coordinate { lon: 37.6504, lat: 55.7297 },
], 18);
let report = download(DownloadOptions {
lat: 55.7303,
lon: 37.6504907,
bottom_right_lat: Some(55.7297),
bottom_right_lon: Some(37.652),
polygon: Vec::new(),
zoom: 18,
cols: 3,
rows: 3,
out: "data".into(),
jobs: 16,
geojson: None,
tile_url_template: None,
no_manifest: false,
no_demo: false,
})
.await?;
Ok(())
}
Tile Math
Given tile { z: 18, x: 158488, y: 81979 }:
n = 2^z
lon_min = x / n * 360 - 180
lon_max = (x + 1) / n * 360 - 180
lat_max = atan(sinh(pi * (1 - 2y/n))) * 180/pi
lat_min = atan(sinh(pi * (1 - 2(y+1)/n))) * 180/pi
Tile bounds are returned as [lat_min, lon_min, lat_max, lon_max] fields.
Approximate resolution:
| Zoom | m/px | Tile covers |
|---|---|---|
| 18 | 0.34 | 86 x 86 m |
| 16 | 1.36 | 347 x 347 m |
| 14 | 5.45 | 1.4 x 1.4 km |
| 10 | 86 | 22 x 22 km |
| 8 | 345 | 88 x 88 km |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file geodot-0.1.4.tar.gz.
File metadata
- Download URL: geodot-0.1.4.tar.gz
- Upload date:
- Size: 56.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74230cc97ce98bab8ad84a0bb8bb4b49c0e6ec1f8add3093e6a0d984896c94ce
|
|
| MD5 |
f658fe068fca2c2be65f95381ca1acee
|
|
| BLAKE2b-256 |
115be90b56d0fdb0636b12fe48e7ca73a26de193c17c19d8cea6747fa8a29d6c
|
Provenance
The following attestation bundles were made for geodot-0.1.4.tar.gz:
Publisher:
publish.yml on ArgusPano/geodot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
geodot-0.1.4.tar.gz -
Subject digest:
74230cc97ce98bab8ad84a0bb8bb4b49c0e6ec1f8add3093e6a0d984896c94ce - Sigstore transparency entry: 1854631340
- Sigstore integration time:
-
Permalink:
ArgusPano/geodot@02fd2f67d542b4c2a2ab6622055039d5a48d7de7 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/ArgusPano
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@02fd2f67d542b4c2a2ab6622055039d5a48d7de7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file geodot-0.1.4-py3-none-any.whl.
File metadata
- Download URL: geodot-0.1.4-py3-none-any.whl
- Upload date:
- Size: 11.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed183925d824af2f10eeade912e717201f44865602f5ee11628273a5c601ac64
|
|
| MD5 |
84b1b448887cfb85011634d302581519
|
|
| BLAKE2b-256 |
6861cc9c014594b4742af2e81f8224331bef3cbe0bf7de4139d66c62d03a5e7c
|
Provenance
The following attestation bundles were made for geodot-0.1.4-py3-none-any.whl:
Publisher:
publish.yml on ArgusPano/geodot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
geodot-0.1.4-py3-none-any.whl -
Subject digest:
ed183925d824af2f10eeade912e717201f44865602f5ee11628273a5c601ac64 - Sigstore transparency entry: 1854631349
- Sigstore integration time:
-
Permalink:
ArgusPano/geodot@02fd2f67d542b4c2a2ab6622055039d5a48d7de7 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/ArgusPano
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@02fd2f67d542b4c2a2ab6622055039d5a48d7de7 -
Trigger Event:
release
-
Statement type: