Supun Vindula
The journal
GISGDA2020Coordinate SystemsWeb MappingPROJArcGIS

The 1.8 Metre Problem: Why GDA2020 Data Is Off on Your Web Map

GDA2020 data offset by about 1.8 m on a Web Mercator map? Why the null WGS 84 transformation causes it, and how to check ArcGIS Pro, QGIS, GeoServer and proj4js.

sv.
Supun Imbulana5 Nov 2026 · 14 min read

GDA94, GDA2020, WGS 84, Web Mercator, and the transformation nobody told you your stack was choosing.

If your GDA2020 data sits about 1.5 to 1.8 metres north-east of where it should on a web map, the data is almost certainly fine. The offset comes from how your software gets from GDA2020 to WGS 84 before it projects to Web Mercator (EPSG:3857). By default most tools use a "null" transformation that treats GDA2020 and WGS 84 as identical, and they treat GDA94 and WGS 84 as identical too. Both can't be true.

That one sentence is the whole bug. The rest of this article covers where the numbers come from, how to see which transformation each tool in your stack is picking, and what to do about it in ArcGIS Pro, QGIS, GeoServer and browser libraries like Leaflet, OpenLayers and MapLibre.

Why GDA2020 data doesn't line up on a web map

Web maps draw in Web Mercator, which is defined on WGS 84. Your data is in GDA2020 (EPSG:7844, or a projected grid like MGA zone 55, EPSG:7855). Somewhere between your data and the screen, something has to convert GDA2020 coordinates into WGS 84 coordinates.

Here's what the EPSG registry offers for that hop:

EPSG codeNameWhat it doesStated accuracy
8450GDA2020 to WGS 84 (2)Null: copies the coordinates across unchanged3 m
1150GDA94 to WGS 84 (1)Null: copies the coordinates across unchanged3 m
9690WGS 84 to GDA2020 (3)1150 + the 7-parameter GDA94 to GDA2020 (8048), through GDA943 m
9691WGS 84 to GDA2020 (4)1150 + the conformal and distortion grid (8447), through GDA943 m

Look at the first two rows together. When GDA2020 was defined, the same shortcut used for GDA94 two decades earlier was applied again. The ICSM GDA2020 Implementation Working Group's advisory on WGS 84 and web mapping (June 2020) puts it plainly: a similar argument was used to create another null transformation, this time between GDA2020 and WGS 84.

So GDA94 coordinates go into Web Mercator unchanged. GDA2020 coordinates go into Web Mercator unchanged. But the same physical survey mark has different GDA94 and GDA2020 coordinates, about 1.8 m apart. Put a GDA94 layer and a GDA2020 layer on the same web map and the two copies of that mark land 1.8 m apart. Nothing errored. Every tool did exactly what the registry told it to.

The EPSG remarks on 8450 are honest about it: the transformation "ignores the low accuracy of the WGS 84 ensemble and the inconsistent application of tectonic plate motion to WGS 84 data". That's the part most GDA2020 explainers skip.

WGS 84 here isn't really a datum

When PROJ resolves EPSG:4326 today, it doesn't return a datum. It returns a datum ensemble called "World Geodetic System 1984 ensemble", made up of every WGS 84 realisation from the original Transit version onwards. An ensemble is EPSG's way of saying "these are all WGS 84, and we can't tell you which one you've got". Its accuracy is stated in metres, not centimetres.

Web Mercator inherits that. A Web Mercator tile has no idea whether the data behind it was captured in 1994, 2020 or last week. That's why the null transformations exist, and why their accuracy is 3 m: they're a convention, not a measurement.

Where the 1.8 metres comes from (and why it's 1.5 in Canberra)

Geoscience Australia's headline when GDA2020 launched in December 2017 was that Australia officially moved 1.8 metres north-east, because the Australian plate travels around 7 cm a year. That's the national figure, and it's the one everyone quotes.

The shift isn't uniform, though. The GDA94 to GDA2020 7-parameter transformation (EPSG:8048) includes rotations, so the size of the shift changes across the continent. Running a few capital cities through 8048 with PROJ 9.8 gives:

LocationGDA94 to GDA2020 shift (EPSG:8048)
Hobart1.49 m
Sydney1.49 m
Canberra1.51 m
Brisbane1.51 m
Perth1.80 m
Darwin1.80 m

So if you work in the south-east and you measure the offset on screen at closer to 1.5 m, you haven't got a second problem. Victoria's own transformation factsheet quotes 1.5 to 1.6 m across the state for the same reason. The direction is roughly north-north-east, which matches what you see on screen when GDA2020 features sit "up and to the right" of a GDA94-aligned basemap.

How to check which transformation your stack is using

Every tool in the chain can make its own choice, from the desktop GIS that prepared the data through to the JavaScript library that draws it. Check them in order.

  1. Find out what datum the basemap is aligned to. Ask the provider or read the service metadata. Some Australian government services publish Web Mercator that is GDA94-aligned. Western Australia's SLIP, for example, says its EPSG:3857 services are "the nominal equivalent of GDA94" and that it routes GDA2020 data through GDA94 on the way out.
  2. Check the desktop transformation (ArcGIS Pro map properties, QGIS project CRS settings).
  3. Check the server (ArcGIS Server service definition, GeoServer reprojection).
  4. Check the client (proj4js definitions, or whether the client does any reprojection at all).
  5. Test with one known point. A survey mark or a sharp feature like a kerb corner, with coordinates in both datums. If the two versions draw 1.5 to 1.8 m apart, a null transformation is in play somewhere.

What goes wrong at step 1 is that nobody knows. Basemaps built from imagery or vector data captured over many years may not be strictly aligned to either datum. In that case, pick the datum your authoritative layers use and document it.

PROJ, GDAL and anything built on them

PROJ ranks candidate operations by accuracy and area of use, then picks the best one it has the grids for. For GDA2020 to WGS 84, the null transformation (8450) and the concatenated options (9690, 9691) all claim 3 m accuracy, so the null wins the tie. You can see this with pyproj:

from pyproj import Transformer
 
gda94_to_web = Transformer.from_crs("EPSG:4283", "EPSG:3857", always_xy=True)
gda2020_to_web = Transformer.from_crs("EPSG:7844", "EPSG:3857", always_xy=True)
 
lon, lat = 149.1287, -35.2820  # same numbers, different datums
print(gda94_to_web.transform(lon, lat))
print(gda2020_to_web.transform(lon, lat))
# Both print (16600930.9466..., -4202270.1608...)

Identical output. The same input numbers in two different datums produce the same Web Mercator coordinate, which is exactly how two copies of the same physical point end up 1.8 m apart on the map.

From the command line, projinfo -s EPSG:7844 -t EPSG:3857 --spatial-test intersects lists the candidate operations in the order PROJ ranks them. DuckDB's spatial extension, GDAL's ogr2ogr, GeoPandas and QGIS all sit on PROJ, so they share this default unless you override it.

One more PROJ behaviour worth knowing. For GDA94 to GDA2020 (not involving WGS 84 at all), the 7-parameter 8048 is listed with an accuracy of 0.01 m and the NTv2 grids (8446, 8447) with 0.05 m. Ranked by stated accuracy, the Helmert wins even when the grids are installed. If your organisation's standard is the conformal and distortion grid, you have to ask for it explicitly.

ArcGIS Pro

ArcGIS Pro picks what it calls the optimal transformation "based on its accuracy and the extent of the data", and shows the result on the Transformation tab of Map Properties. Each layer's geographic coordinate system gets a row, and you can change the Transformation path or choose <Do not transform>.

Two things to watch:

  • Basemap extents skew the choice. Esri's documentation notes that a basemap's global extent can influence which transformation is chosen, and there's an option to ignore basemap extents. Turn that on for Australian work.
  • Composite paths are allowed, up to two steps. For a GDA2020 map with GDA94-aligned Web Mercator content, the Queensland Government's GDA2020 guidance uses the composite "GDA 1994 To WGS 1984 + GDA 1994 To GDA2020 NTv2 3 Conformal" in Map Properties. That's the 9690 idea, implemented in Esri's own transformation list.

Esri's knowledge base article on GDA2020 recommends converting everything to GDA2020 and publishing in GDA2020 or a GDA2020-based projected system, and warns against mixing GDA94 and GDA2020 data in web GIS. Map Properties fixes the map you're looking at. It doesn't change what a published service does for other clients, so publishing is where to be careful.

In ArcPy, arcpy.ListTransformations(from_sr, to_sr, extent) returns the valid options with the most appropriate one first. Print that list before you hard-code a transformation name into a script, because the names come from Esri's own catalogue, not EPSG.

QGIS

QGIS also defaults to "the most accurate transformation available", per the QGIS 3.44 user manual. Global defaults live in Settings › Options › Transformations, and project-level overrides live in the CRS tab of Project › Properties.

Turn on Ask for datum transformation if several are available. With it on, adding a GDA94 layer to a GDA2020 project pops up the list of candidates, and grid-based ones show greyed out until you download the grid (there's a button for that). If you tick Make default, the choice sticks for new projects, which is either exactly what you want or a trap for the next person who opens a project with different data.

The QGIS default for display doesn't matter much if you're using XYZ tiles in Web Mercator as a basemap and an MGA project CRS. QGIS reprojects the tiles into your project CRS, and unless you've set otherwise, that hop uses the same null transformation. If the tiles are GDA94-aligned, you're back to the same 1.5 to 1.8 m.

GeoServer

GeoServer picks the most accurate datum transform from the EPSG database unless you declare a custom one. Custom coordinate operations go in epsg_operations.properties in the user_projections folder of the data directory, and they take precedence over EPSG. NTv2 files such as GDA94_GDA2020_conformal_and_distortion.gsb go in the same folder; GeoServer doesn't ship them.

The useful part is the Reprojection Console under Demos. Paste a coordinate, choose source and target CRS, and it shows the operation it will use. Do this for EPSG:7855 to EPSG:3857 on every server before you publish GDA2020 layers to a Web Mercator client.

Leaflet, OpenLayers and MapLibre with proj4js

Browser libraries are where this gets quietly worse.

MapLibre GL JS draws Web Mercator and expects GeoJSON in longitude and latitude, as RFC 7946 requires. It does no datum handling. Leaflet and OpenLayers can reproject with proj4js, which only knows what the definition string tells it.

Here are the proj4 strings epsg.io serves today for GDA94 (EPSG:4283) and GDA2020 (EPSG:7844):

EPSG:4283  +proj=longlat +ellps=GRS80 +no_defs +type=crs
EPSG:7844  +proj=longlat +ellps=GRS80 +no_defs +type=crs

They're identical. Copy them into proj4.defs() and proj4js has no way to tell the two datums apart. The MGA zone strings (28355 and 7855) are identical to each other as well, both with +towgs84=0,0,0,0,0,0,0. So if you paste definitions from epsg.io into a Leaflet or OpenLayers app, you've built the null transformation in by hand.

If your basemap is GDA94-aligned and your data is GDA2020, you can give proj4js the 8048 Helmert parameters inverted. proj4js uses the position vector convention, so the rotations flip sign compared with EPSG's coordinate frame values:

import proj4 from "proj4";
 
// GDA2020 shown on a basemap that treats GDA94 as WGS 84 (EPSG:1150).
// Inverse of EPSG:8048, position vector convention.
proj4.defs(
  "GDA2020_VIA_GDA94",
  "+proj=longlat +ellps=GRS80 " +
    "+towgs84=-0.06155,0.01087,0.04019,-0.0394924,-0.0327221,-0.0328979,0.009994 " +
    "+no_defs"
);
 
const [lon, lat] = proj4("GDA2020_VIA_GDA94", "EPSG:4326", [149.1287055539, -35.2819871408]);
// Lands back on the GDA94 coordinate (149.1287, -35.2820) to well under a millimetre.

Tested with proj4js 2.22.0, that round-trips against PROJ's own 8048 result to well under a millimetre. It's the conformal-only shift, so it ignores the local distortion that the conformal and distortion grid (8447) models. For that, proj4js can load NTv2 .gsb or GeoTIFF grids through proj4.nadgrid(), at the cost of shipping the grid to the browser.

Usually the better fix is upstream: transform once on the server or in your data pipeline, and send the browser coordinates that already match the basemap. I covered the client side of Leaflet in advanced Leaflet integration in Vue, and if your layers are static, PMTiles can replace the map server entirely, which puts the transformation decision firmly in your build step.

Mixing GDA94 basemaps with GDA2020 data

You have three realistic options, and which is right depends on the basemap you can't change.

Your situationWhat to doTransformation for the GDA2020 to WGS 84 hop
Basemap is GDA94-aligned (common for older government Web Mercator services)Route GDA2020 through GDA949690 (conformal) or 9691 (conformal and distortion)
Basemap is GDA2020-aligned, or recent imagery you trust at that levelNull is correct8450
You don't know, and the basemap is for context onlyAccept up to 1.8 m, write it down8450, documented

The wrong answer is the one that happens by accident: each layer goes through its own default, the GDA94 layers and the GDA2020 layers both take a null path, and the map looks almost right.

For decision support at suburb scale, 1.8 m rarely matters. For address points that need to fall inside the right lot, or an asset register lined up against kerbs in imagery, it does. That's the same class of problem as G-NAF geocodes changing between releases: a metre or two that nobody notices until someone checks against something on the ground.

ATRF2014, GNSS, and the half-metre you'll notice next

GDA2020 is plate-fixed. The coordinates of a survey mark don't change as the continent moves. ATRF2014 (EPSG:9308 for the 3D geographic CRS) is Australia's time-dependent frame. It moves with the Earth's reference frame, and ICSM defines GDA2020 and ATRF2014 as equal at epoch 2020.0.

Here's the arithmetic that matters now. At roughly 7 cm a year, by late 2026 a point's ATRF2014 or current-epoch ITRF coordinates sit around half a metre north-east of its GDA2020 coordinates. Recent WGS 84 realisations are closely aligned to ITRF, so a GNSS receiver or phone reporting "WGS 84" today is, in principle, closer to ATRF2014 than to GDA2020.

So if you load fresh GNSS data tagged as EPSG:4326 into a GDA2020 project and your software applies the null 8450, you'll get a fresh offset that keeps growing every year. That's a different error from the GDA94 one, and it doesn't go away when you finish your GDA2020 migration.

For high-accuracy work, the right path is a time-dependent transformation such as EPSG:8448 (GDA2020 to WGS 84 G1762), which needs a coordinate epoch on the data. Most web mapping stacks can't carry an epoch per feature, which is why DataWA's GDA2020 guidance says web mapping needs a chosen static datum or epoch until the standards catch up.

Common mistakes

  • Assuming EPSG:4326 means "no transformation needed". It means "a datum ensemble with metre-level uncertainty". Treat any layer labelled 4326 as needing a decision.
  • Fixing it in the map, not the service. ArcGIS Pro Map Properties and QGIS project settings fix your view. Clients of the published service still get the default.
  • Copying proj4 strings from the web without reading them. Identical strings for GDA94 and GDA2020 is a null transformation you didn't choose.
  • Trusting "most accurate" rankings for GDA94 to GDA2020. The 7-parameter wins on paper. If you need the distortion grid, set it.
  • Not recording the transformation. Write the EPSG code of the transformation in your metadata next to the CRS. WA's DataWA guidance says the same: provide the datum and, where it applies, the transformation method.

FAQ

Is GDA2020 the same as WGS 84?

For web mapping, they're treated as the same by convention (EPSG:8450, accurate to about 3 m). Physically, they aren't. GDA2020 is fixed to the Australian plate at 2020.0, and current WGS 84 realisations follow the global frame, which drifts away from GDA2020 at roughly 7 cm a year.

Should I publish map services in GDA2020 or Web Mercator?

Publish in GDA2020 (or MGA) when your clients can consume it, and let them choose the transformation. If you must publish Web Mercator, decide whether you're GDA94-aligned or GDA2020-aligned, apply that transformation on the server, and say so in the service description.

Which EPSG code should I use for GDA2020 in MGA zone 55?

EPSG:7855. The GDA94 equivalent is EPSG:28355. The mainland MGA zones follow the same pattern: 78xx for GDA2020 and 283xx for GDA94, where xx is the zone number from 49 to 56.

Where to go from here

If you're migrating an organisation's services rather than a single map, the transformation choice is a design decision that belongs in your data standards. That's the sort of work I do in GIS consulting for government data teams, and you can see related geospatial systems work on this site.

The same coordinate system mistakes show up when you hand spatial tasks to AI tools, which I've written about in LLM agents and GIS coordinate systems. And if you're moving GDA2020 batch processing out of desktop tools, DuckDB spatial vs ArcPy covers how each one handles CRS transformations.

Got a basemap that refuses to line up even after all this? Get in touch.