Guide

Why your shapefile shows up in the wrong place (and how to fix it)

You open a shapefile on a web map and it is in the sea off West Africa, in the Bay of Bengal, or near the North Pole. Nothing is wrong with the shapes: the numbers are being read in the wrong coordinate system. The good news is that each mistake leaves a signature, and how far off the data lands tells you which one you are looking at.

Start with how far off it is

A shapefile stores plain numbers: an x and a y for every vertex. Whether 781,000 means metres east of a false origin or degrees of longitude is not in those numbers at all. It is in a separate file, the .prj, and when software reads the numbers in a different system from the one they were written in, the shapes land somewhere else, perfectly intact. The distance to where they land is the clue:

  • Nothing appears, or the map zooms out to the whole world. Projected metres are being read as degrees. An easting of 781,000 is not a longitude, so a web map either rejects it or draws it far off the edge of the world.
  • A tiny blob at 0° N, 0° E, in the Gulf of Guinea. The opposite: degrees read as metres. A longitude of 77.59 read as Web Mercator metres is 77 metres east of the origin, so a whole country shrinks into a dot on the spot known as Null Island.
  • The right shape, hundreds of kilometres to one side. The wrong UTM zone, or the wrong national grid.
  • Mirrored into the other hemisphere, or near a pole. The wrong hemisphere, or latitude and longitude swapped.
  • In the right place but 50 to 300 metres out. A datum problem. The projection is right; the model of the earth underneath it is not.

Shapes that are the right shape but in the wrong place never need editing. They need their coordinate system stated correctly, and then, if necessary, reprojecting.

The .prj file, and what happens without it

The .prj is a one-line text file beside the .shp holding a coordinate system in Well-Known Text. From QGIS or ArcGIS it usually looks like this:

PROJCS["WGS_1984_UTM_Zone_43N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",
  SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],
  UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],
  PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],
  PARAMETER["Central_Meridian",75.0],PARAMETER["Scale_Factor",0.9996],
  PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]

Read from the inside out, it says: the WGS 84 datum, a Transverse Mercator projection centred on 75° E, a false easting of 500,000 m, and units in metres. That is WGS 84 / UTM zone 43N, EPSG:32643, though the file never says so. Esri-style names such as GCS_WGS_1984 rarely match the EPSG names, so good software identifies the system from the datum and the parameters, not from the name.

The .prj is also the part most often lost. Someone emails the .shp and .dbf but not the rest, a download script only keeps the extensions it knows, or a tool that exports shapefiles never writes one. Without it, every program has to guess. QGIS asks or assumes the project's system; many web tools assume WGS 84, which works only if the data happened to be in WGS 84. The Shapefile to GeoJSON converter assumes WGS 84 only when every coordinate is a valid longitude and latitude, says so, and otherwise stops and asks for the code.

Finding the right EPSG code

When the .prj is missing, the numbers themselves narrow the system down quickly. Look at one vertex from the attribute table or the .shp's bounding box:

  • x between −180 and 180, y between −90 and 90, with decimals: almost certainly geographic degrees, most likely EPSG:4326.
  • A six-digit x between about 166,000 and 834,000 and a seven-digit y: UTM. The northing gives the distance from the equator; the zone has to come from knowing roughly where the data is.
  • x and y in the millions, up to about 20,037,508 either way: Web Mercator, EPSG:3857, usually exported from a web map.
  • Values that fit a national grid: British National Grid (EPSG:27700) has eastings under 700,000 and northings under 1,300,000; Dutch RD New (EPSG:28992) keeps the whole country under 300,000 by 630,000; a German Gauss-Krüger easting starts with the zone number, such as 3,500,000.

For UTM the zone follows from longitude: zone = floor((longitude + 180) / 6) + 1. Bengaluru at 77.59° E is in zone 43 (72° E to 78° E), so parcels there in WGS 84 UTM are EPSG:32643; Kolkata at 88.36° E is zone 45, EPSG:32645. The EPSG Lookup shows each code's area of use, and the Coordinate Converter turns a known point into UTM, so you can compare it with a vertex from the file.

Getting the zone wrong is one of the commonest errors, and it is a large one. UTM coordinates are measured from each zone's central meridian, so the same numbers read in zone 44 instead of 43 move everything 6° east. For that Bengaluru parcel, 6° of longitude at 13° N is about 650 km: the parcel lands in the Bay of Bengal, about 350 km out to sea east of Chennai. Getting the hemisphere wrong is worse: 32743 instead of 32643 reads a northing of 1,434,000 as 8,566,000 m south of the equator, which is about 77° S, in Antarctica.

Latitude and longitude the wrong way round

People say "latitude, longitude". Shapefiles, GeoJSON, WKT and PostGIS all store x then y: longitude first. The trouble comes from everything in between: a CSV with a lat column first, a GPS app that exports "12.9716, 77.5946", or a WFS request in EPSG:4326, whose official axis order is latitude first.

Swapped Bengaluru coordinates are a good example of how far off this goes. Read as longitude 12.97 and latitude 77.59, the point is in Svalbard, in the Arctic Ocean north of Norway. If data that should be in South India appears near the North Pole, or data from South America appears in the Indian Ocean, suspect swapped axes before anything else. The fix is to swap the columns at the source rather than hunt for a coordinate system that "works".

When it is off by metres: datum shifts

Some data lands in the right town, on the right street, and still sits beside where it should be, usually by somewhere between tens of metres and a few hundred. That is a datum problem. A datum is the model of the earth and where it is anchored, and the same latitude and longitude on two datums are two different places.

Older national mapping is full of local datums: NAD27 in North America, OSGB36 in Great Britain, ED50 in much of Europe, and the Everest-based Kalianpur datums on older Survey of India mapping. Between OSGB36 and WGS 84 the difference in Great Britain is around 100 metres; between NAD27 and NAD83 it reaches tens to more than a hundred metres across the United States. If the .prj names one datum and the data was captured on another, or the software ignores the datum shift, the offset looks exactly like this.

Modern datums such as ETRS89, NAD83 and GDA2020 differ from WGS 84 by less than about two metres, which matters for surveying but not for a web map. The ST_Transform tool states how each datum change was made: exact, treated as coincident, or a published Helmert shift good to a few metres. It refuses NAD27 outright, because doing that properly needs grid files a browser does not have.

Fixing it for good: assign or reproject

There are two different operations here, and mixing them up is how data ends up in the wrong place twice.

Assigning a coordinate system

Use this when the numbers are right but the label is missing or wrong. Nothing moves; the file just starts telling the truth. In QGIS, use Processing, then Vector general, then Assign projection. With GDAL, pass the source system explicitly. In PostGIS, it is ST_SetSRID:

-- The numbers are UTM 43N metres; say so without changing them.
UPDATE parcels SET geom = ST_SetSRID(geom, 32643);

Reprojecting

Use this when the label is right and you need the numbers in another system, for example WGS 84 for GeoJSON. Every vertex changes. In QGIS it is Export, then Save Features As, with a different CRS; in PostGIS, ST_Transform; with GDAL, one command does both steps:

ogr2ogr -f GeoJSON -s_srs EPSG:32643 -t_srs EPSG:4326 parcels.geojson parcels.shp

The -s_srs assigns the source system (only needed when the .prj is missing or wrong) and -t_srs reprojects to the target. The Shapefile to GeoJSON converter does the same in the browser: its Source coordinate system field is the assign step, and the conversion to EPSG:4326 is the reproject step. Once you know the right code, write a proper .prj so the next person does not have to guess: export the layer again from QGIS with the correct system, or save the WKT from the EPSG Lookup into a .prj file with the same name as the .shp.

A checklist

  1. How far off is it? Whole world, Null Island, hundreds of kilometres, a pole, or tens of metres?
  2. Is there a .prj, with the same name as the .shp? What system does it describe?
  3. Do the numbers look like degrees, UTM, Web Mercator or a national grid?
  4. For UTM, is the zone right for the longitude, and is it the northern or southern hemisphere?
  5. Could latitude and longitude have been swapped anywhere along the way?
  6. If it is tens to hundreds of metres out, which datum was the data captured on?
  7. Fix the label with an assign step, then reproject, never the other way round.

Work through them in that order. The first three catch nearly every shapefile that appears in the ocean, and each has a fix that takes a minute once you know which one it is.