Skip to content

python-geospatial/crs-fixer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

crs-fixer

Detect wrong or missing CRS on vector data, suggest the right EPSG code with a reason, and batch-reproject a whole folder.

CI Python License: MIT

A missing or wrong coordinate reference system is the single most common way a geospatial dataset goes silently wrong: the geometries load fine, plot on top of nothing, and every distance comes out nonsensical. crs-fixer inspects the raw coordinate extent of a dataset, tells you the CRS it was most likely recorded in (with a confidence score and a plain-language reason), and reprojects one file or an entire folder to the CRS you actually want.

Maintained by python-geospatial.com, a knowledge base for the modern Python geospatial stack.

What it does

  • Guesses the CRS from coordinate bounds: geographic degrees (EPSG:4326), Web Mercator (EPSG:3857), or a projected metric grid — and flags the classic swapped (lat, lon) axis-order mistake.
  • Suggests a UTM zone from an approximate longitude/latitude, using the correct 6-degree zoning maths.
  • Assigns a CRS without transforming when the numbers are right but the metadata is missing — and refuses to clobber an existing CRS by accident.
  • Batch-reprojects a folder, mirroring names into a new directory and never touching the source files, with a per-file status report.

Install

Not published to PyPI — install straight from the repository:

pip install "git+https://github.com/python-geospatial/crs-fixer.git"

or clone and install in editable mode:

git clone https://github.com/python-geospatial/crs-fixer.git
cd crs-fixer
pip install -e ".[dev]"

Usage

Detect the CRS of a file

crs-fixer detect parcels.geojson
Best guess     EPSG:4326  WGS 84 (geographic, lon/lat degrees)
Confidence     ████████████████░░░░ 80%
Rationale      All coordinates fall within +-180 longitude and +-90 latitude,
               so the data is almost certainly geographic degrees (WGS 84). If
               the data is European, EPSG:4258 (ETRS89) is a common
               near-identical alternative.
Alternatives   EPSG:4258

Machine-readable output for scripts:

crs-fixer detect parcels.geojson --json
{
  "epsg": 4326,
  "name": "WGS 84 (geographic, lon/lat degrees)",
  "confidence": 0.8,
  "rationale": "All coordinates fall within +-180 longitude and +-90 latitude ...",
  "alternatives": [4258]
}

Look up a UTM zone for a coordinate

crs-fixer utm 10.5 59.9
lon=10.5, lat=59.9 -> EPSG:32632 (WGS 84 / UTM zone 32N)

Reproject a single file

crs-fixer reproject parcels.geojson parcels_utm.geojson --to EPSG:32632

If the source has no CRS but you know what the coordinates represent, declare it first with --assume:

crs-fixer reproject raw_grid.shp grid_utm.shp --to EPSG:32632 --assume EPSG:4326

Batch-reproject a folder

crs-fixer batch ./raw ./reprojected --to EPSG:25832 --assume EPSG:4326 --pattern "*.shp"
                     Batch reproject -> EPSG:25832
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ File             ┃ Status   ┃ Source CRS ┃ Detail                     ┃
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ parcels.shp      │ ok       │ EPSG:4326  │ Reprojected.               │
│ floodplain.shp   │ assigned │ EPSG:4326  │ Assigned assumed CRS, ...  │
│ sensors.shp      │ error    │ -          │ Reprojection or write ...  │
└──────────────────┴──────────┴────────────┴───────────────────────────┘

2/3 reprojected, 1 need attention.

The command exits non-zero if any file failed or was skipped, so it drops cleanly into CI or a Makefile.

Library API

import geopandas as gpd
from crs_fixer import guess_crs, suggest_utm, assign_crs, reproject

parcels = gpd.read_file("parcels.geojson")

guess = guess_crs(parcels)
print(guess.epsg, guess.confidence, guess.rationale)

# Numbers are right, metadata missing -> label without transforming:
labelled = assign_crs(parcels, guess.epsg)

# Move the coordinates into a metric UTM zone for analysis:
utm_guess = suggest_utm(labelled, approx_lonlat=(10.5, 59.9))
parcels_utm = reproject(labelled, utm_guess.epsg)

Features

  • Sound, documented heuristics — no magic, every guess carries a rationale.
  • Correct UTM zone maths (zone = int((lon + 180) / 6) + 1; 326xx north, 327xx south).
  • Axis-order gotcha detection for swapped (lat, lon) data.
  • Batch mode never mutates source files and reports every file individually.
  • always_xy=True discipline and EPSG-vs-PROJ-string guidance baked into the docstrings.

How it works

guess_crs reads only the total bounds of the geometries and reasons about them: coordinates inside [-180, 180] x [-90, 90] are geographic degrees; coordinates inside the Web Mercator world extent (±20,037,508 m) are almost certainly EPSG:3857; anything larger is a projected national/UTM grid whose zone can only be pinned down with an external location hint. Assigning a CRS uses GeoPandas' set_crs (metadata only), while reprojection uses to_crs (pyproj under the hood). When you build a pyproj Transformer yourself, always pass always_xy=True, and prefer authoritative EPSG codes over PROJ strings so datum shifts are not silently dropped.

Learn more

Development

git clone https://github.com/python-geospatial/crs-fixer.git
cd crs-fixer
pip install -e ".[dev]"
ruff check .
pytest

License

MIT — see LICENSE.

About

Detect wrong or missing CRS on vector data, suggest the right EPSG code with a reason, and batch-reproject a folder. Maintained by python-geospatial.com.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages