Repository navigation
Getting started
arcore-location tells you where a real-world coordinate is in ARCore's world. You create the anchor and draw the content, with ARCore directly or any renderer: SceneView, Filament, or your own OpenGL or Vulkan code.
For a complete working app, see example-core in the repository. It uses ARCore directly, draws the camera image with OpenGL and puts Android views over the camera at each target's screen position.
Add JitPack to your repositories (maven("https://jitpack.io")), then the library:
implementation("com.github.appoly.ARCore-Location:arcore-location:2.0.0")Before starting AR, request the permissions it needs with an Activity Result launcher:
ActivityResultLauncher<String[]> launcher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
result -> { if (ArLocationPermissions.hasRequiredPermissions(this)) startAr(); });
launcher.launch(ArLocationPermissions.REQUIRED_PERMISSIONS);Create a tracker and tie it to your activity's lifecycle:
GeoTracker tracker = new GeoTracker(context);
getLifecycle().addObserver(tracker); // starts and stops location and compass updatesTo use the Geospatial API as well, enable it in your session config; see Geospatial API.
Call update once per frame, after session.update(), then place each target:
tracker.update(session, frame);
if (frame.getCamera().getTrackingState() != TrackingState.TRACKING || !tracker.isReady()) {
return;
}
Pose cameraPose = frame.getCamera().getPose();
for (MyMarker marker : markers) {
GeoPlacement placement = tracker.place(marker.coordinate, cameraPose, 25f, marker.height);
if (placement == null) {
continue;
}
if (marker.anchor == null || movedMoreThan(marker.anchor.getPose(), placement, 0.3f)) {
Anchor replacement = session.createAnchor(Pose.makeTranslation(
placement.getWorldX(), placement.getWorldY(), placement.getWorldZ()));
if (marker.anchor != null) {
marker.anchor.detach();
}
marker.anchor = replacement;
// Move your content to the new anchor here.
}
}static boolean movedMoreThan(Pose pose, GeoPlacement placement, float metres) {
float dx = pose.tx() - placement.getWorldX();
float dy = pose.ty() - placement.getWorldY();
float dz = pose.tz() - placement.getWorldZ();
return dx * dx + dy * dy + dz * dz > metres * metres;
}Don't create a new anchor every frame. ARCore keeps a limited number of anchors, and each one costs tracking work. Re-anchor when the ideal position has moved by more than a small tolerance. The Sceneform module uses 0.3 metres plus 1% of the drawn distance.
Positions move for two reasons. The tracker's estimate of north and of the world's location improves as samples arrive, which mostly causes small shifts in the first minute. And targets beyond the maximum render distance are drawn a fixed distance from the camera, so they move as the user walks.
GeoTracker.getStatus() tells you what the user should do:
| Status | Meaning |
|---|---|
WAITING_FOR_LOCATION |
No GPS fix yet. Outdoors, this takes a few seconds. |
WAITING_FOR_HEADING |
Location is known but the compass hasn't been sampled. Ask the user to point the camera at the horizon. |
LOCATION_UNAVAILABLE |
Location updates couldn't start, usually missing permission. getLocationUnavailableReason() says why. |
HEADING_UNAVAILABLE |
The phone has no usable compass. |
LOCATION_STALE |
Placement works, but no GPS fix has been accepted for 30 seconds. |
TRACKING |
Everything is working. getSource() says whether it is using the Geospatial API. |
Add a GeoTracker.Listener to be told when the status changes.
GeoTracker(LocationSource, HeadingSource) accepts your own sources. Use this to read from an external GPS receiver, to replay a recorded walk in tests, or to supply a heading from another system. Callbacks must arrive on the main thread.