Short answer
A layer that plots in the wrong place, or refuses to overlay anything, almost always has a coordinate reference system (CRS) problem, and there are exactly two cures with very different consequences. If the CRS tag is missing or wrong but the coordinate numbers are correct, you assign the right CRS and nothing about the geometry changes. If the coordinate numbers are in a different system than you need, you reproject, which recomputes every coordinate. The single most damaging mistake in GIS troubleshooting is reaching for one when you needed the other. So before you touch anything, diagnose which case you are in.
Step 1: Read what the file claims, and what the numbers say
The CRS declaration and the actual coordinates are two separate facts. A .shp missing its .prj sidecar has no declaration at all; a GeoTIFF can carry a perfectly valid but completely wrong CRS tag. Inspect both.
For vector:
ogrinfo -al -so data.shp
Look at the Layer SRS WKT (the declaration) and the Extent (the coordinate ranges). For raster:
gdalinfo data.tif
Look at Coordinate System is and the Corner Coordinates.
Now interpret the ranges. This is the heart of the diagnosis:
- X in [-180, 180], Y in [-90, 90]: geographic coordinates, degrees. Almost always EPSG:4326 (WGS84). If a NoData or origin sits at exactly 0,0 in the Gulf of Guinea, that is the classic "null island" sign of a missing CRS being treated as 4326.
- X six to seven digits (e.g. 500000-ish), Y up to ~10,000,000: a UTM zone or similar transverse Mercator. The 500000 false easting on X is a strong UTM signature.
- X and Y both six digits, distinctive national ranges: a national grid (British National Grid eastings/northings sit roughly 0-700000 / 0-1300000, EPSG:27700).
- Values in feet (very large numbers, hundreds of thousands to millions, with a state-plane feel): a US State Plane zone in US survey feet.
Cross-reference with what you know about the data's location. A dataset you know is in France with X around 650000 and Y around 6860000 is RGF93 / Lambert-93 (EPSG:2154), not UTM. Use epsg.io to confirm a candidate code's area of use and false easting/northing against your numbers.
Step 2: Decide assign vs reproject
Build a small truth table from the diagnosis:
| Situation | Action |
|---|---|
| No CRS declared, numbers are in degrees | Assign EPSG:4326 |
| No CRS declared, numbers are UTM meters | Assign the correct UTM EPSG |
| Wrong CRS declared, but numbers match a different (correct) CRS | Assign the correct CRS (overwrite the wrong tag) |
| Correct CRS declared, you need the data in another system | Reproject |
| Wrong CRS declared and numbers were already transformed under it | Investigate provenance before acting |
The intuition: assigning fixes a label, reprojecting fixes coordinates. If your data lands in the ocean off west Africa, the label is wrong — assign. If your data lands correctly but you need meters for measurement, the label is right but the system is unsuitable — reproject.
Step 3a: Assigning a CRS (no coordinates change)
Raster, in place, rewriting only the tag:
gdal_edit.py -a_srs EPSG:25831 dem.tif
Vector, writing a new file with the assigned CRS (note -a_srs, not -t_srs):
ogr2ogr -a_srs EPSG:25831 fixed.gpkg broken.shp
For a shapefile missing only its .prj, you can also generate the sidecar from the WKT, but ogr2ogr -a_srs into a GeoPackage is cleaner and avoids the fragile multi-file shapefile.
In QGIS: Layer Properties > Source > set the layer CRS does not rewrite the file; it overrides the declaration for that session. To persist the fix, use the Processing algorithm "Assign projection" (qgis:definecurrentprojection) for vectors or "gdal:assignprojection" for rasters, both of which change the metadata without transforming.
In PostGIS, assigning is ST_SetSRID, which only stamps the SRID and never moves coordinates:
UPDATE survey SET geom = ST_SetSRID(geom, 25831) WHERE ST_SRID(geom) = 0;
Step 3b: Reprojecting (coordinates recomputed)
Only after the source CRS is correctly known. Raster:
gdalwarp -s_srs EPSG:25831 -t_srs EPSG:4326 -r bilinear dem_utm.tif dem_4326.tif
Vector:
ogr2ogr -s_srs EPSG:25831 -t_srs EPSG:4326 out_4326.gpkg in_utm.gpkg
PostGIS:
SELECT ST_Transform(geom, 4326) FROM survey;
The distinction in PostGIS is the cleanest illustration of the whole article: ST_SetSRID assigns, ST_Transform reprojects. ST_Transform on a geometry with SRID 0 will error, because it has no source to transform from — which is exactly why you assign first.
Datum transformations: the silent error inside reprojection
Reprojecting between datums (NAD27↔NAD83, OSGB36↔WGS84, local↔WGS84) is not just a formula change; it requires a datum transformation that can shift positions by tens to hundreds of meters. GDAL/PROJ will pick a default operation, but the default is not always the most accurate one available for your region. When sub-meter accuracy matters, check the available pipelines:
projinfo -s EPSG:27700 -t EPSG:4326 --summary
and select the operation that uses the proper grid shift (for example an OSTN/OSGM grid for Britain). A "successful" reprojection with the wrong datum operation is one of the hardest errors to catch because everything lines up at small scale and drifts only when you zoom in.
Common pitfalls and why they happen
- Forcing data to move by re-assigning. Someone sees the layer in the wrong place, opens the CRS dropdown, and keeps changing it hoping the data jumps to the right spot. Assigning never moves data; they are corrupting the label. The fix they wanted was reproject — but only after the true source CRS is known.
- Reprojecting from a guessed source.
gdalwarp -s_srswith the wrong-s_srsproduces confidently wrong coordinates. Diagnose first. - Trusting the
.prjover the numbers. A.prjcan be stale or copied from another dataset. When the declaration and the coordinate ranges disagree, the ranges win. - Null island. A layer clustered at 0,0 usually means a CRS-less file was assumed to be 4326, or an empty geometry was written. Check before assigning.
QA and validation
- Overlay against a trusted reference. Load a known-good basemap or boundary in the same area; the repaired layer should sit on it.
- Check a control coordinate. Pick a recognizable point (a road junction, a survey monument) and confirm its coordinates in the repaired CRS match an independent source.
- Round-trip test for reprojections. Reproject out and back to the original CRS; coordinates should return to within sub-millimeter. A large residual flags a bad datum operation.
- Confirm the tag persisted. Re-run
gdalinfo/ogrinfoafter the fix. A QGIS session override that was never written to disk will silently revert. - Record what you did. Note the original declaration, the diagnosed true CRS, whether you assigned or reprojected, and the transformation pipeline used.
Bathyl perspective
CRS repair is forensic work: the file tells one story, the coordinates tell another, and your job is to reconcile them before changing anything. We separate the diagnosis (what is the data actually in?) from the remedy (assign or reproject), record both, and validate against an independent control. That discipline turns a frightening "everything is in the ocean" moment into a two-minute fix with a clear audit trail.
Related reading
- How to Audit CRS Problems in a GIS Project
- CRS for Web Maps vs Desktop Analysis
- CRS for Field GPS Data
- Why Your GIS Layers Do Not Line Up
- GIS and spatial analysis