Geospatial Anchors

Build location-based AR experiences using Geospatial Anchors.

Geospatial Anchors

Overview

The Geospatial Anchors API enables location-based augmented reality experiences by placing virtual content at real-world geographic coordinates (latitude, longitude, altitude) that persists across sessions and can be experienced by multiple users.

🚀

New in v2.53.0 — ReactVision is now the default provider

Starting with v2.53.0, the provider prop defaults to "reactvision". ReactVision's Studio Platform provides cross-platform geospatial anchor support. You will need an rvApiKey and rvProjectId from ReactVision Studio (see ReactVision Studio Setup for details). If you need Google's ARCore Geospatial API instead, set provider="arcore" explicitly on your <ViroARSceneNavigator>.

The Geospatial Anchors API provides:

  • Earth Tracking: Track the device's position relative to the Earth using GPS and Visual Positioning System (VPS)
  • Geospatial Anchors: Place AR content at specific geographic coordinates
  • Three Anchor Types: WGS84 (absolute altitude), Terrain (relative to ground), and Rooftop (relative to buildings)
  • VPS Availability: Check if enhanced visual positioning is available at a location
  • Cross-Platform: Works on both iOS and Android

Requirements

Regardless of which provider you choose, the following requirements apply:

  • iOS: iOS 12.0+ with ARKit support, ARCore SDK for iOS
  • Android: Android 7.0+ (API 24) with ARCore support
  • Location Services: Device location permissions must be granted
  • Network: Internet connection required for geospatial features

See the Provider Setup guide for a full comparison of the ReactVision and ARCore providers, and setup instructions for each.


Setup

For full setup instructions for both the ReactVision and ARCore providers, see the Provider Setup guide.

Note: Geospatial features require location permissions in addition to camera permissions. The Provider Setup guide covers this — make sure to follow the "Geospatial features only" steps.


Basic Usage

import React, { useRef, useEffect, useState } from "react";
import {
  ViroARSceneNavigator,
  ViroARScene,
  ViroBox,
  ViroNode,
  ViroGeospatialPose,
} from "@reactvision/react-viro";

const GeospatialScene = (props: { arSceneNavigator: any }) => {
  const { arSceneNavigator } = props;
  const [pose, setPose] = useState<ViroGeospatialPose | null>(null);
  const [anchorPosition, setAnchorPosition] = useState<number[] | null>(null);

  useEffect(() => {
    // Enable geospatial mode when scene loads
    const interval = setInterval(async () => {
      const result = await arSceneNavigator.getCameraGeospatialPose();
      if (result.success && result.pose) {
        setPose(result.pose);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  const placeAnchorAtCurrentLocation = async () => {
    if (!pose) return;

    // Create a terrain anchor 1 meter above ground at current location
    const result = await arSceneNavigator.createTerrainAnchor(
      pose.latitude,
      pose.longitude,
      1.0, // 1 meter above terrain
      [0, 0, 0, 1] // Identity quaternion (facing north)
    );

    if (result.success && result.anchor) {
      setAnchorPosition(result.anchor.position);
      console.log("Anchor created:", result.anchor.anchorId);
    }
  };

  return (
    <ViroARScene>
      {anchorPosition && (
        <ViroNode position={anchorPosition}>
          <ViroBox
            position={[0, 0, 0]}
            scale={[0.3, 0.3, 0.3]}
            materials={["redMaterial"]}
          />
        </ViroNode>
      )}
    </ViroARScene>
  );
};

const App = () => {
  return (
    <ViroARSceneNavigator
      initialScene={{ scene: GeospatialScene }}
      // provider defaults to "reactvision" — set provider="arcore" to use ARCore instead
    />
  );
};

export default App;

API Reference

provider Prop

Set the provider prop on <ViroARSceneNavigator> to select the Geospatial Anchors provider.

TypeRequiredDefault
"reactvision" | "arcore" | "none"No"reactvision"
// ReactVision provider (default — no additional setup required)
<ViroARSceneNavigator
  // provider="reactvision" // this is the default
  // ... other props
/>

// Google ARCore provider (requires Google Cloud setup)
<ViroARSceneNavigator
  provider="arcore"
  // ... other props
/>

Methods

All methods are available through the arSceneNavigator object passed to your scene.

isGeospatialModeSupported()

Check if geospatial mode is supported on the current device.

Returns: Promise<ViroGeospatialSupportResult>

const checkSupport = async () => {
  const result = await arSceneNavigator.isGeospatialModeSupported();
  if (result.supported) {
    console.log("Geospatial is supported!");
  } else {
    console.log("Not supported:", result.error);
  }
};

setGeospatialModeEnabled(enabled: boolean)

Enable or disable geospatial tracking.

// Enable geospatial tracking
arSceneNavigator.setGeospatialModeEnabled(true);

// Disable when done
arSceneNavigator.setGeospatialModeEnabled(false);

getEarthTrackingState()

Get the current Earth tracking state.

Returns: Promise<ViroEarthTrackingStateResult>

const checkTracking = async () => {
  const result = await arSceneNavigator.getEarthTrackingState();
  console.log("Tracking state:", result.state);
  // "Enabled" | "Paused" | "Stopped"
};

getCameraGeospatialPose()

Get the camera's current geospatial pose including location and accuracy metrics.

Returns: Promise<ViroGeospatialPoseResult>

const getPose = async () => {
  const result = await arSceneNavigator.getCameraGeospatialPose();

  if (result.success && result.pose) {
    const { pose } = result;
    console.log(`Location: ${pose.latitude}, ${pose.longitude}`);
    console.log(`Altitude: ${pose.altitude}m`);
    console.log(`Heading: ${pose.heading}°`);
    console.log(`Horizontal accuracy: ${pose.horizontalAccuracy}m`);
    console.log(`Vertical accuracy: ${pose.verticalAccuracy}m`);
  }
};

checkVPSAvailability(latitude, longitude)

Check if Visual Positioning System (VPS) is available at a location.

Parameters:

  • latitude : number – Latitude in degrees
  • longitude : number – Longitude in degrees

Returns: Promise<ViroVPSAvailabilityResult>

const checkVPS = async (lat: number, lng: number) => {
  const result = await arSceneNavigator.checkVPSAvailability(lat, lng);

  switch (result.availability) {
    case "Available":
      console.log("VPS available — high accuracy positioning!");
      break;
    case "Unavailable":
      console.log("VPS not available — GPS-only positioning");
      break;
    case "Unknown":
      console.log("Could not determine VPS availability");
      break;
  }
};

createGeospatialAnchor(latitude, longitude, altitude, quaternion?)

Create a local-only WGS84 geospatial anchor at absolute coordinates. This anchor exists only in the current AR session — it is not persisted to any backend. The returned anchorId is a random UUID generated on the device.

Use this to place AR content at a GPS position for yourself. If you need the anchor to persist so that other users can see it, use hostGeospatialAnchor instead (ReactVision provider only).

Parameters:

  • latitude : number – Latitude in degrees
  • longitude : number – Longitude in degrees
  • altitude : number – Altitude in meters above WGS84 ellipsoid
  • quaternion : [x, y, z, w] – Orientation in EUS frame (optional, defaults to facing north)

Returns: Promise<ViroCreateGeospatialAnchorResult>

const createAnchor = async () => {
  const result = await arSceneNavigator.createGeospatialAnchor(
    37.7749, // latitude
    -122.4194, // longitude
    10.0, // altitude
    [0, 0, 0, 1] // quaternion (facing north)
  );

  if (result.success && result.anchor) {
    console.log("Created anchor:", result.anchor.anchorId); // random UUID, local only
    console.log("World position:", result.anchor.position);
  }
};

createTerrainAnchor(latitude, longitude, altitudeAboveTerrain, quaternion?)

Create an anchor positioned relative to the terrain surface. The altitude is automatically resolved based on terrain data.

Parameters:

  • latitude : number – Latitude in degrees
  • longitude : number – Longitude in degrees
  • altitudeAboveTerrain : number – Height above terrain in meters
  • quaternion : [x, y, z, w] – Orientation in EUS frame (optional)

Returns: Promise<ViroCreateGeospatialAnchorResult>

const createTerrainAnchor = async () => {
  // Place content 2 meters above the ground
  const result = await arSceneNavigator.createTerrainAnchor(
    37.7749,
    -122.4194,
    2.0 // 2 meters above terrain
  );

  if (result.success) {
    // Position your AR content at result.anchor.position
  }
};

createRooftopAnchor(latitude, longitude, altitudeAboveRooftop, quaternion?)

Create an anchor positioned relative to a building rooftop. Useful for placing content on top of buildings.

Parameters:

  • latitude : number – Latitude in degrees
  • longitude : number – Longitude in degrees
  • altitudeAboveRooftop : number – Height above rooftop in meters
  • quaternion : [x, y, z, w] – Orientation in EUS frame (optional)

Returns: Promise<ViroCreateGeospatialAnchorResult>

const createRooftopAnchor = async () => {
  // Place content 1 meter above the building rooftop
  const result = await arSceneNavigator.createRooftopAnchor(
    37.7749,
    -122.4194,
    1.0
  );

  if (result.success) {
    console.log("Rooftop anchor created at:", result.anchor.position);
  }
};

removeGeospatialAnchor(anchorId: string)

Remove a previously created geospatial anchor.

Parameters:

  • anchorId : string – The ID of the anchor to remove
const removeAnchor = (anchorId: string) => {
  arSceneNavigator.removeGeospatialAnchor(anchorId);
};

ReactVision-Only APIs

The following methods are only available when using the ReactVision provider (the default). They are powered by the proprietary ReactVisionClient SDK.

Note: createGeospatialAnchor, createTerrainAnchor, and createRooftopAnchor are now supported with provider="reactvision". No VPS, no ARCore Geospatial API, and no ARCore pods are required.

rvListGeospatialAnchors(limit, offset)

Paginated list of all geospatial anchors in your project.

ParameterTypeDescription
limitnumberMaximum number of anchors to return
offsetnumberNumber of anchors to skip (for pagination)
const result = await arSceneNavigator.rvListGeospatialAnchors(20, 0);
result.anchors.forEach(anchor => {
  console.log(anchor.anchorId, anchor.name);
});

rvGetGeospatialAnchor(anchorId)

Fetch a single geospatial anchor record by ID.

ParameterTypeDescription
anchorIdstringThe geospatial anchor ID
const result = await arSceneNavigator.rvGetGeospatialAnchor("anchor-abc-123");
if (result.success) {
  console.log(result.anchor);
}

rvFindNearbyGeospatialAnchors(lat, lng, radius, limit)

GPS proximity search — query for geospatial anchors near a geographic coordinate. This enables discovering nearby AR content without knowing specific anchor IDs.

ParameterTypeDescription
latnumberLatitude in degrees
lngnumberLongitude in degrees
radiusnumberSearch radius in meters
limitnumberMaximum number of results to return
const nearby = await arSceneNavigator.rvFindNearbyGeospatialAnchors(
  37.7749,   // latitude
  -122.4194, // longitude
  500,       // radius in meters
  20         // max results
);

nearby.anchors.forEach(anchor => {
  console.log(anchor.anchorId, anchor.name);
});

rvUpdateGeospatialAnchor(id, sceneAssetId, sceneId, name)

Update an existing geospatial anchor's metadata.

ParameterTypeDescription
idstringThe geospatial anchor ID
sceneAssetIdstringThe asset ID to link to this anchor (optional)
sceneIdstringThe scene ID to associate (optional)
namestringA human-readable name for the anchor (optional)
await arSceneNavigator.rvUpdateGeospatialAnchor(
  "anchor-abc-123",
  "asset-xyz-789",  // link an uploaded asset
  "scene-001",
  "Coffee Shop Entrance"
);

You can link assets uploaded via rvUploadAsset — see the Asset Uploads documentation for details.

rvDeleteGeospatialAnchor(anchorId)

Permanently delete a geospatial anchor and its associated data.

ParameterTypeDescription
anchorIdstringThe geospatial anchor ID
await arSceneNavigator.rvDeleteGeospatialAnchor("anchor-abc-123");

hostGeospatialAnchor(lat, lng, alt, altitudeMode)

Persist a geospatial anchor to the ReactVision backend so that other users can resolve it. Unlike createGeospatialAnchor (which is local-only), this method sends a POST request to the ReactVision platform and returns a platform UUID that can be shared with other users or devices.

ParameterTypeDescription
latnumberLatitude in degrees
lngnumberLongitude in degrees
altnumberAltitude in meters above WGS84 ellipsoid
altitudeModestringHow to interpret the altitude value. One of "WGS84", "Terrain", or "Rooftop".

Returns: Promise<{ success: boolean; anchorId?: string; error?: string }>

The returned anchorId is the platform UUID assigned by the ReactVision backend. Store this ID and share it so other users can resolve the anchor.

const hostAnchor = async () => {
  const result = await arSceneNavigator.hostGeospatialAnchor(
    37.7749,    // latitude
    -122.4194,  // longitude
    10.0,       // altitude
    "Terrain"   // altitude mode
  );

  if (result.success) {
    console.log("Hosted anchor ID:", result.anchorId); // platform UUID
    // Store or share this ID so others can resolve the anchor
  } else {
    console.error("Host failed:", result.error);
  }
};

resolveGeospatialAnchor(platformUuid)

Resolve a previously hosted geospatial anchor from the ReactVision backend. Sends a GET request to fetch the anchor's stored GPS position, then creates a local AR anchor at that location. Use this to display anchors that were hosted by other users or on other devices.

ParameterTypeDescription
platformUuidstringThe platform UUID returned by hostGeospatialAnchor

Returns: Promise<ViroCreateGeospatialAnchorResult>

The resolved anchor's anchorId will match the platformUuid you passed in, making it easy to track which hosted anchor corresponds to which local AR anchor.

const resolveAnchor = async (platformUuid: string) => {
  const result = await arSceneNavigator.resolveGeospatialAnchor(platformUuid);

  if (result.success && result.anchor) {
    console.log("Resolved anchor at:", result.anchor.position);
    // Place AR content at result.anchor.position
  } else {
    console.error("Resolve failed:", result.error);
  }
};

Typical workflow — host on one device, resolve on another:

// Device A: host an anchor
const hostResult = await arSceneNavigator.hostGeospatialAnchor(
  37.7749, -122.4194, 2.0, "Terrain"
);
// Share hostResult.anchorId with Device B (e.g. via your app's backend)

// Device B: resolve the anchor
const resolveResult = await arSceneNavigator.resolveGeospatialAnchor(
  sharedAnchorId // the platform UUID from Device A
);
if (resolveResult.success) {
  // AR content now appears at the same real-world location
}

Geospatial Utilities

Two new helper functions are exported from @reactvision/react-viro:

gpsToArWorld(devicePose, lat, lng, alt)

Converts a GPS coordinate to an AR world-space [x, y, z] offset from the device's current geospatial pose. Useful for positioning AR content at GPS coordinates without creating anchors.

import { gpsToArWorld } from "@reactvision/react-viro";

const pose = await arSceneNavigator.getCameraGeospatialPose();
if (pose.success && pose.pose) {
  const worldPos = gpsToArWorld(pose.pose, 37.7750, -122.4195, 10.0);
  console.log("AR world offset:", worldPos); // [x, y, z]
}

latLngToMercator(lat, lng)

Converts a GPS coordinate to a metric 2D position using Mercator projection. Building block for gpsToArWorld and custom geo math.

import { latLngToMercator } from "@reactvision/react-viro";

const [x, y] = latLngToMercator(37.7749, -122.4194);

Anchor Types

WGS84 Anchors

WGS84 anchors use absolute coordinates on the WGS84 ellipsoid. You specify the exact altitude in meters above the ellipsoid (not above sea level or ground).

Use when:

  • You know the exact altitude from survey data
  • Placing content at known fixed heights
  • Working with data from external sources with WGS84 altitudes

Terrain Anchors

Terrain anchors are positioned relative to the ground surface. The system automatically resolves the terrain height, so you only specify how far above the ground your content should appear.

Use when:

  • You don't know the exact altitude
  • Placing content that should appear at ground level or a fixed height above it
  • The ground elevation may vary

Rooftop Anchors

Rooftop anchors are positioned relative to building rooftops. The system resolves the building height automatically.

Use when:

  • Placing content on top of buildings
  • Creating experiences tied to building structures
  • You need content at rooftop level without knowing exact building heights

Best Practices

1. Check Support Before Using

useEffect(() => {
  const init = async () => {
    const support = await arSceneNavigator.isGeospatialModeSupported();
    if (!support.supported) {
      Alert.alert(
        "Not Supported",
        "Geospatial AR is not available on this device"
      );
      return;
    }
    arSceneNavigator.setGeospatialModeEnabled(true);
  };
  init();
}, []);

2. Wait for Good Tracking Quality

const waitForGoodTracking = async (): Promise<ViroGeospatialPose | null> => {
  const MAX_ATTEMPTS = 30;
  const REQUIRED_ACCURACY = 5; // meters

  for (let i = 0; i < MAX_ATTEMPTS; i++) {
    const result = await arSceneNavigator.getCameraGeospatialPose();

    if (result.success && result.pose) {
      const { horizontalAccuracy, verticalAccuracy } = result.pose;

      if (
        horizontalAccuracy < REQUIRED_ACCURACY &&
        verticalAccuracy < REQUIRED_ACCURACY
      ) {
        return result.pose;
      }
    }

    await new Promise((resolve) => setTimeout(resolve, 500));
  }

  return null;
};

3. Use Terrain Anchors for Unknown Altitudes

When you don't know the exact altitude, prefer terrain anchors over WGS84 anchors. They automatically resolve to the correct ground height.

4. Clean Up Anchors

Always remove anchors when they're no longer needed:

const cleanup = () => {
  anchors.forEach((id) => arSceneNavigator.removeGeospatialAnchor(id));
  setAnchors([]);
};

useEffect(() => {
  return () => cleanup();
}, []);

Types

// Provider type (canonical — replaces deprecated ViroGeospatialAnchorProvider)
type ViroProvider = "none" | "arcore" | "reactvision";

// Deprecated alias — still compiles with a warning
type ViroGeospatialAnchorProvider = "none" | "arcore" | "reactvision";

// Earth tracking state
type ViroEarthTrackingState = "Enabled" | "Paused" | "Stopped";

// VPS availability
type ViroVPSAvailability = "Available" | "Unavailable" | "Unknown";

// Quaternion type
type ViroQuaternion = [number, number, number, number]; // [x, y, z, w]

// Geospatial pose
type ViroGeospatialPose = {
  latitude: number;
  longitude: number;
  altitude: number;
  heading: number;
  quaternion: ViroQuaternion; // Orientation in EUS frame
  horizontalAccuracy: number; // Meters (95% confidence)
  verticalAccuracy: number; // Meters (95% confidence)
  headingAccuracy: number; // Degrees (95% confidence)
  orientationYawAccuracy: number; // Degrees (95% confidence)
};

// Geospatial anchor
type ViroGeospatialAnchor = {
  anchorId: string;
  type: ViroGeospatialAnchorType;
  latitude: number;
  longitude: number;
  altitude: number;
  heading: number;
  position: [number, number, number]; // World coordinates [x, y, z]
};

// Result types
type ViroGeospatialSupportResult = {
  supported: boolean;
  error?: string;
};

type ViroEarthTrackingStateResult = {
  state: ViroEarthTrackingState;
  error?: string;
};

type ViroGeospatialPoseResult = {
  success: boolean;
  pose?: ViroGeospatialPose;
  error?: string;
};

type ViroVPSAvailabilityResult = {
  availability: ViroVPSAvailability;
  error?: string;
};

type ViroCreateGeospatialAnchorResult = {
  success: boolean;
  anchor?: ViroGeospatialAnchor;
  error?: string;
};

Troubleshooting

"Geospatial not supported"

  • Ensure device has ARCore/ARKit support
  • Check that location permissions are granted
  • Verify internet connectivity
  • Confirm API key is correctly configured

Poor accuracy / anchors drifting

  • Move the device slowly to allow tracking to stabilize
  • Ensure good lighting conditions
  • Try to capture more visual features (textured surfaces)
  • Check if VPS is available at the location
  • Wait for accuracy values to improve before placing anchors

"API key not found" errors

See the Provider Setup — Troubleshooting section for API key configuration issues.

Earth tracking state stuck on "Stopped"

  • Ensure setGeospatialModeEnabled(true) was called
  • Verify location services are enabled on the device
  • Grant location permissions to the app

Terrain/Rooftop anchors failing

  • These anchor types require additional data from Google's servers
  • Ensure internet connectivity
  • The location may not have terrain/rooftop data available
  • Try using WGS84 anchors as a fallback

Android-specific issues

  • Ensure ARCore is installed and up to date
  • Check that your app targets API level 24 or higher
  • Verify the ARCore dependency version matches your setup

iOS-specific issues

  • Ensure iOS 12.0 or later
  • Verify ARCore SDK is properly linked via CocoaPods
  • Check that the app has camera and location permissions in Info.plist