A Python tool for generating Military Grid Reference System (MGRS) grid squares as GeoJSON — optimized for MapTiler vector tiling and military/defence map overlays.
- Multi-precision grids — GZD, 100km, 10km, 1km, 100m, 10m, 1m
- Adaptive parallelism — Single-thread for small areas,
ProcessPoolExecutorfor large - Zone exceptions — Norway (band V) and Svalbard (band X) handled correctly
- Deduplication — Automatic removal of duplicate cells at zone boundaries
- Three output layers — Polygons, label points (
--labels), grid lines (--lines) - Enriched properties — MGRS components, centroid coords, precision, UTM data
- GeoJSON validation — Output validated before writing, warnings on malformed features
- Antimeridian support — Bounding boxes crossing ±180° longitude handled correctly
- Area lookup —
--area bavariaauto-resolves bounding box for countries, regions, and states - Coordinate rounding — 7 decimal places (~1cm) for smaller file sizes
pip install -r requirements.txt
pip install -e .
# For development (mypy, pytest):
pip install -e ".[dev]"| Package | Purpose |
|---|---|
mgrs |
MGRS coordinate conversion |
shapely |
Geometry operations |
pyproj |
CRS transformations |
python -m src.cli.main <command> [options]# Generate all GZD zones
python -m src.cli.main generate -l GZD -o gzd.geojson
# Generate 10km grid for a bounding box
python -m src.cli.main generate -l 10km -b 12.0 48.5 19.0 51.1 -o cz_10km.geojson
# Generate with companion layers
python -m src.cli.main generate -l 1km -b 14.0 50.0 15.0 51.0 \
-o prague.geojson --labels --lines
# Outputs: prague.geojson, prague_labels.geojson, prague_lines.geojson
# Auto-resolve bounding box by place name (country, region, state, etc.)
python -m src.cli.main generate -l 10km --area bavaria -o bavaria_10km.geojson
python -m src.cli.main generate -l GZD --area japan -o japan_gzd.geojson
python -m src.cli.main generate -l 10km --area texas -o texas_10km.geojson
# Filter specific UTM zones
python -m src.cli.main generate -l GZD --zone 33 34 -o zones.geojson
# Antimeridian crossing (wraps around 180°)
python -m src.cli.main generate -l GZD -b 170 50 -170 60 -o antimeridian.geojson
# Verbose mode
python -m src.cli.main generate -l GZD -o gzd.geojson -v| Flag | Description |
|---|---|
-l, --level |
Grid precision: GZD, 100km, 10km, 1km, 100m, 10m, 1m |
-b, --bbox |
Bounding box: MIN_LON MIN_LAT MAX_LON MAX_LAT |
--area |
Place name to auto-resolve bbox — country, region, state (e.g. bavaria, texas, CZ) |
-o, --output |
Output file path (default: stdout) |
--labels |
Write separate label-points GeoJSON |
--lines |
Write separate grid-lines GeoJSON |
--zone |
Filter by UTM zone number(s) |
-v, --verbose |
Enable debug logging |
python -m src.cli.main point 14.42 50.08 # Get MGRS zone for a coordinate
python -m src.cli.main bbox bavaria # Look up bounding box for an area
python -m src.cli.main list # List all 1,197 grid zonesfrom src.geocoding import lookup_area_bbox
# Look up bounding box for any area (requires MAPTILER_API_KEY env var)
bbox = lookup_area_bbox("bavaria") # (8.9764, 47.2701, 13.8396, 50.5647)
bbox = lookup_area_bbox("japan") # (122.9338, 24.0456, 153.9869, 45.5227)
bbox = lookup_area_bbox("CZ") # (12.0905, 48.5519, 18.8593, 51.0557)from shapely.geometry import box
from src.grid.generator import (
GridGenerator,
generate_gzd_features,
generate_label_points,
generate_grid_lines,
build_feature_collection,
)
# Generate GZD features
features = generate_gzd_features() # All 1,197 zones
filtered = generate_gzd_features(bbox=box(14, 50, 15, 51)) # Filtered
# Generate grid cells (auto-deduplicated at zone boundaries)
gen = GridGenerator(level_meters=1000)
features = gen.generate_grid(box(14.0, 50.0, 15.0, 51.0))
# Companion layers
labels = generate_label_points(features) # Point features for label placement
lines = generate_grid_lines(features) # LineString features for grid edges
# FeatureCollection with metadata
fc = build_feature_collection(features, level="1KM", bbox=(14.0, 50.0, 15.0, 51.0))from src.geometry.zones import get_utm_zone, get_mgrs_grid_zone, get_zone_geometry
zone_num, hemisphere = get_utm_zone(50.08, 14.42) # (33, 'N')
gzd = get_mgrs_grid_zone(50.08, 14.42) # "33U"
geom = get_zone_geometry(33, 'U') # Shapely Polygonfrom src.transformation.transformer import UTMTransformer
transformer = UTMTransformer(zone_num=33, latitude=50.0)
easting, northing = transformer.transform_point_to_utm(14.42, 50.08)
lon, lat = transformer.transform_point_to_wgs84(easting, northing)| Level | Cell Size | MGRS Digits | NATO Use Case |
|---|---|---|---|
| GZD | ~6° × 8° | — | Theater-level planning, logistics corridors |
| 100km | 100 × 100 km | 0 + 0 | Operational-level planning, area of operations (AO) |
| 10km | 10 × 10 km | 1 + 1 | Tactical planning, brigade/battalion sectors |
| 1km | 1 × 1 km | 2 + 2 | Mission planning — fire support, patrol routes, objectives |
| 100m | 100 × 100 m | 3 + 3 | Precision targeting, CAS (close air support) |
| 10m | 10 × 10 m | 4 + 4 | Point targets, IED markers, POI |
| 1m | 1 × 1 m | 5 + 5 | Maximum precision targeting |
{
"type": "Feature",
"id": "33UVR",
"properties": {
"mgrs": "33UVR",
"gzd": "33U",
"sqid": "VR",
"zone_number": 33,
"band": "U",
"easting": "",
"northing": "",
"utm_easting": 400000,
"utm_northing": 5500000,
"precision_meters": 100000,
"center_lon": 14.5006602,
"center_lat": 50.2748925
}
}{
"type": "FeatureCollection",
"bbox": [14.0, 50.0, 15.0, 51.0],
"properties": {
"generated_at": "2026-02-08T10:00:00+00:00",
"generator": "mgrs-grid",
"level": "1KM",
"feature_count": 1234
}
}Zone 31V narrowed to 0–3°E, Zone 32V widened to 3–12°E.
Band X is 12° tall (not 8°). Zones 32X, 34X, 36X don't exist — their space is absorbed by odd-numbered zones (31X, 33X, 35X, 37X). Total GZD count: 1,197.
src/
├── _compat.py # Centralized path setup
├── cli/main.py # CLI with subcommands
├── config/settings.py # Grid level enums and lookups
├── geometry/zones.py # UTM zone calculations, band logic
├── grid/
│ ├── generator.py # Grid cells, label points, grid lines
│ └── tiling.py # Zone iteration utilities
└── transformation/
└── transformer.py # pyproj UTM ↔ WGS84 transforms
tests/ # pytest suite (56 tests)
| Module | Key Exports |
|---|---|
geometry.zones |
get_utm_zone, get_zone_geometry, get_all_zone_boundaries, LAT_BANDS |
grid.generator |
GridGenerator, generate_gzd_features, generate_label_points, generate_grid_lines, build_feature_collection |
grid.tiling |
iter_zones, get_all_zones, count_zones |
transformation.transformer |
UTMTransformer, get_utm_crs |
config.settings |
GridLevel, PRECISION_MAP, LEVEL_NAME_MAP |
Grid generation uses adaptive parallelism:
- ≤4 zones: Single-threaded (avoids ~200ms process spawn overhead)
- >4 zones:
ProcessPoolExecutorwith workers capped at zone count
python -m pytest tests/ -v # 56 tests
python -m mypy src/ # Type checking (0 errors)MIT — see LICENSE for details.