Skip to content

How placement works

James Merrix edited this page Nov 9, 2026 · 2 revisions

The problem

ARCore tracks the phone very precisely relative to where the session started. Its world frame is gravity aligned, so "up" is known, but ARCore doesn't know which way north is or where on earth the session started.

GPS and the compass know both, but they're noisy. A typical phone GPS fix is off by 5 to 20 metres, and the compass by 10 to 30 degrees, more near steel or magnets.

Combining them

Whenever a GPS reading arrives, the tracker records it alongside the camera's ARCore position at that moment. It does the same with each compass reading and the direction the camera faced. Each pair gives one estimate of two unknowns:

  1. the direction in ARCore's world that points to true north, and
  2. the geographic location of ARCore's world origin.

WorldAlignment averages many of these estimates, weighting each by the accuracy the sensor reported. It then discards estimates that disagree badly with the rest, such as a GPS jump or a reading taken next to a car, and averages again. It keeps the most recent 60 position samples and 300 heading samples from the last two minutes, so slow drift in ARCore's tracking doesn't build up.

Because ARCore's own tracking is far more precise than GPS, a dozen GPS fixes taken while walking around are better than one, and the estimate improves the longer the session runs.

To place a coordinate, the tracker works out where the camera is in the real world, finds the bearing and distance to the target, and converts that into a direction in ARCore's world using the north estimate.

flowchart LR
    gps["GPS fix"] --> pairs
    compass["Compass heading,<br/>corrected to true north"] --> pairs
    geo["Geospatial pose<br/>(replaces GPS and compass<br/>when available)"] -.-> pairs
    camera["ARCore camera pose<br/>at the same moment"] --> pairs
    pairs["Paired samples"] --> alignment["WorldAlignment<br/>weighted average,<br/>outliers dropped"]
    alignment --> north["Which way is north<br/>in ARCore's world"]
    alignment --> origin["Where ARCore's origin<br/>is on earth"]
    north --> place["GeoTracker.place()"]
    origin --> place
    target["Target GeoCoordinate"] --> place
    place --> world["World position<br/>for an anchor"]
Loading

Distant targets

A target 1 km away is drawn 25 metres away on the same bearing, with its height scaled by the same ratio

Renderers clip anything beyond their far plane; Sceneform's is 30 metres. Targets further than the maximum render distance (25 metres by default) are drawn at that distance in the correct direction. Their height above or below the camera is scaled by the same ratio, so a hilltop 2 km away still appears slightly above the horizon.

Compass details

The compass reports direction relative to magnetic north. The tracker converts it to true north using Android's GeomagneticField model for your location and date. In the UK the difference is under a degree; on the US west coast it is around 13 degrees.

Compass readings are only used when:

  • the camera is within about 60 degrees of horizontal, because headings become meaningless when pointing at the floor or sky,
  • the phone isn't turning faster than 45 degrees a second, because the compass and camera are sampled at slightly different times, and
  • the sensor doesn't report itself as unreliable.

Geospatial API

With the Geospatial API, ARCore reports the camera's location and heading directly. It combines GPS with Google's Visual Positioning System, which matches the camera image against Street View, so it can be accurate to a metre or two and a degree or two. When Geospatial poses are available, the tracker uses them in place of GPS and compass, through the same averaging. See Geospatial API.

What to expect

Accuracy depends on the phone, how much sky it can see and what's nearby, and 2.0 hasn't yet been measured across a range of devices. As a rule of thumb, placement starts out as good as a single GPS fix and compass reading, and improves as samples accumulate, most noticeably if the user walks around for the first minute. Google publishes figures for the Geospatial API, which is usually far better than GPS and compass.

Heading error matters more for distant targets: 10 degrees is 17 metres sideways at 100 metres.

GeoTracker.getHorizontalAccuracyMetres() and getHeadingAccuracyDegrees() report the current estimates. They assume each reading's error is independent of the others, which GPS errors often aren't, so treat them as optimistic.

Clone this wiki locally