Shapefile in the Wrong Place (or in the Ocean): the Missing .prj
Short answer: the file has no .prj, so nothing knows what your numbers mean. The .shp stores raw doubles; the .prj next to it is the only thing that says whether they are degrees or UTM meters. Without it, GDAL doesn’t print any coordinate system, QGIS asks for a CRS, and the browser viewer assumes WGS 84 and warns about it — which puts a geographic file in the right place and a UTM file off the edge of the world, with the same message in both cases. Below: how to tell which of the two you have just from the numbers, and the two ogr2ogr commands that fix it, which are not interchangeable.
Everything was tested on September 16, 2026 with GDAL 3.13.3 and the shapefile-viewer in version static-page-tools@442f8d8, with its September 2026 message for files without .prj, not yet released at the time this was written. The dataset is the IBGE 2022 mesh of Brazil’s 27 states in SIRGAS 2000 geographic coordinates, converted once to SIRGAS 2000 / UTM zone 23S, so the same 27 polygons exist in degrees and in meters. The Shapefile guide covers this sibling file under “If the .prj is missing”; this post reproduces and corrects that.
Three tools, three behaviors
GDAL does not guess: ogrinfo -so on the UTM layer with the .prj removed still reports Feature Count: 27 and the extent in meters, and then prints Layer SRS WKT: with (unknown) on the next line. QGIS, with its default CRS settings, asks for a CRS when you add the layer. The viewer reads the numbers as WGS 84 degrees and adds a warning on the layer line. The warning is new — it wasn’t released when this was written and is published before this post — but the guess is not: a zip without a .prj has always been read as degrees; what changed is that there is now a line that warns.
Four variations in the viewer
Each variation was dropped into the viewer and exported with Export GeoJSON; the first vertex of Acre in that export is the oracle of position, because it is the geometry the viewer drew projected back to degrees.
- Zipped UTM with its .prj: the line reads 27 polygons, the map shows Brazil, and the first pair in the export is [-68.7928173712301, -10.999569659337354]. GDAL’s -t_srs EPSG:4326 for the same vertex gives [-68.792817347, -10.999569268]. The viewer’s vertex is 0.0436 m from GDAL’s vertex — both treat SIRGAS 2000 → WGS 84 as a null transformation (PROJ evaluates this at 1 m: EPSG:15894,+proj=noop), and the 4 cm are entirely from the viewer’s own 0.2 m drawing grid: the vertex exported in Web Mercator is an exact multiple of 0.2 m on both axes.
- The same zip without its .prj: the line reads 27 polygons · CRS unknown — assuming WGS 84, the base map remains visible, and nothing is drawn where Brazil should be. The first pair in the export is [-2171693.959168141, -53.911070387188786]: the exported longitude is your easting, digit for digit — the .shp stores -2171693.959168141, 8673426.088929612 for that vertex — and the latitude is an artifact of Mercator math, which is periodic in northing and returned a value that looks like a real latitude in Patagonia. An easting of −2,171,693 read as degrees wraps around the planet about 6.032 times to the west. The message is the only clue, because the map looks normal.
- The same three files loose, not zipped: identical to the zip — the same line, the same transparent map, a byte-for-byte identical export. That parity is also new: before the September 2026 change, loose files without .prj were drawn as raw meters in Web Mercator over a gray screen with the base map hidden, which placed a degrees file in a 45 m by 39 m box near the origin, unrecognizable and with no message.
- The geographic file without its .prj: the line carries the same warning, and the shape is in Brazil. The first pair in its export is [-68.7928173712301, -10.999569659337354], bit for bit the same pair as the original IBGE zip. It works, and the viewer warns that it guessed: the guess is correct because degrees is what the file contains, and the message exists so a lucky guess is not mistaken for a known CRS.
How to tell the CRS of a file without .prj
Look at the numbers before the map. Values within ±180 and ±90 are degrees, and the viewer’s guess is correct. Six- or seven-digit values are projected meters, and the guess is wrong: a UTM easting within its zone is between 166,000 and 834,000, a southern hemisphere northing reaches 10,000,000 because of the false northing, and a file that crosses multiple zones, like this one, has eastings that go negative or exceed a million (in this layer, from −2,839,536 to 2,201,621). Meters in Web Mercator reach about 20,037,508. Exporting and looking is a one-step diagnosis: if the viewer says it assumed WGS 84 and the exported longitude is a six- or seven-digit number, that is your easting, and the file was projected. Then find the zone — the EPSG code guide lists the codes for Brazil and Latin America.
Assign or reproject: two commands, two verbs
Assigning says what the numbers already are; reprojecting changes the numbers. In the file whose ogrinfo -so printed (unknown) under Layer SRS WKT:, ogr2ogr -a_srs EPSG:31983 fixed.shp in.shp writes a .prj and does not move anything: ogrinfo -so on the result prints PROJCRS["SIRGAS 2000 / UTM zone 23S" ending in ID["EPSG",31983], the extent remains the same, and the first vertex is still -2171693.95916814 8673426.08892961. Dropped again in the viewer, the file with the assigned CRS falls into Brazil with the line back to 27 polygons and no warning. ogr2ogr -s_srs EPSG:31983 -t_srs EPSG:4326 out.shp in.shp does the other job: -s_srs is what the missing .prj would have said, -t_srs is where you want to go, and ogrinfo -so on the result shows GEOGCRS["WGS 84" with the extent in degrees and the first vertex at -68.792817347 -10.999569268 — the double stored in the source file, recovered. Assign a file that is already in degrees and you lied to it; reproject with the wrong -s_srs and every vertex moves to a wrong place that now has a .prj as authority. Check the code first: gdalsrsinfo -o wkt1 EPSG:31983 | grep -m1 -o '^\(GEOGCS\|PROJCS\)\["[^"]*"' prints PROJCS["SIRGAS 2000 / UTM zone 23S"; the neighbor 31984 prints zone 24S.
If the viewer says it assumed WGS 84 and the shape is not where it should be, fix the .prj before exporting — and if you already exported, the longitude column is your easting, unchanged.
The same failure in GeoJSON
A GeoJSON with a member named crs: "EPSG:31983" fails the same way in the browser: the member is not understood, the transformation is ignored, and the first vertex of Acre is exported as [-19.5086591263066, 61.20664023081724], in the North Atlantic — the GeoJSON guide explains under “The CRS member has been removed” why the format no longer has that member.
Test files you can use
The dataset is the IBGE 2022 state mesh, 13,717,460 bytes, SHA-256 282ec7f0f0beeeead45e6609f4ffffce161bda04cb8ee0afcad2316d1c841bcb; the directory listing does not print any license line, so credit IBGE and check the portal terms before republishing.
https://geoftp.ibge.gov.br/organizacao_do_territorio/malhas_territoriais/malhas_municipais/municipio_2022/Brasil/BR/BR_UF_2022.zip
To generate the broken variant, project the file with ogr2ogr -f "ESRI Shapefile" -t_srs EPSG:31983 -lco ENCODING=UTF-8 utm/BR_UF_2022_utm.shp BR_UF_2022.shp, then delete utm/BR_UF_2022_utm.prj and drop the other three files into the viewer, zipped or loose. The -lco ENCODING=UTF-8 is there for a reason outside this post: without it ogr2ogr writes an attribute table in Latin-1 without .cpg, and accented state names break in the browser — a .cpg problem, not a .prj problem.
For teams
A .prj is lost in a chat attachment, in a copied folder, in an export that never wrote one — and the map receiving the file can’t ask the sender. Geodocs keeps the coordinate system with the data on a shared map, so the team draws and collects in a single CRS and the question only appears on export, when you already know the answer.