Did GPS functionality break in Studio?

Hi folks,

Today and the last couple of days I’ve not been able to get real GPS data in Niantic Studio from navigator.geolocation in either the desktop browser or mobile Safari on iOS.

It is returning 37.795528, -122.393422 (Niantic SF HQ?) in Niantic Studio simulator and 37.795528, -122.393425 (also the Ferry Building) on device. I know I was getting real GPS coordinates the other day. I’ve checked it on macOS / Brave (with shields off), Win11 on Chrome.

I’ve also tried variations like the Scavenger Hunt project, running Simulator, with Location set to Current Location and set to one of the VPS locations in that project, and Doty just stays in the Ferry Building.

I tested apps like https://gps-coordinates.org/ and they report correct GPS position on desktop and mobile browsers.

It could be lack of sleep, but am I missing something? : )

Thanks!

Jason

Hi Jason!

I’ll look into this, however I don’t suspect we broke any map functionality recently. Are you seeing the issue on different devices as well?

Hi George, yes, I tested it on two different iOS devices.

Here’s some simple component code I made to test, perhaps this would reveal a mistake in my approach?

// Ultra Simple GPS Location Component Template
// Uses both watchPosition and getCurrentPosition with immediate printing

import * as ecs from '@8thwall/ecs'

/**
 * A minimal component that gets GPS coordinates using both watchPosition and
 * getCurrentPosition methods, printing values immediately when received.
 *
 * This implementation:
 * 1. Uses watchPosition() for real-time updates when the device moves
 * 2. Uses getCurrentPosition() at regular intervals for consistent updates
 * 3. Prints values immediately when received (no local caching)
 * 4. Clearly labels which method provided each update
 */

const TemplateComponentGpsLocationSimple = ecs.registerComponent({
  name: 'template-component-gps-location-simple',

  schema: {
    isEnabled: ecs.boolean,
    printIntervalMs: ecs.i32,  // How often to call getCurrentPosition
  },

  schemaDefaults: {
    isEnabled: true,
    printIntervalMs: 1000,  // Default to checking every 1 second
  },

  data: {
    watchId: ecs.i32,
    lastPrintTime: ecs.f64,
    watchCount: ecs.i32,
    getCurrentCount: ecs.i32,
  },

  add: (world, component) => {
    const {eid, dataAttribute} = component

    console.log('[gps-simple] Component added to entity', eid)

    // Initialize component data
    dataAttribute.set(eid, {
      watchId: -1,
      lastPrintTime: 0,
      watchCount: 0,
      getCurrentCount: 0,
    })

    // Check if geolocation is supported
    if (!navigator.geolocation) {
      console.error('[gps-simple] Geolocation is not supported by this browser/device')
      return
    }

    // Set up watch for real-time GPS updates
    const watchId = navigator.geolocation.watchPosition(
      (position) => {
        // Check if entity still exists
        if (!dataAttribute.has(eid)) {
          return
        }

        // Increment watch count
        dataAttribute.mutate(eid, (cursor) => {
          cursor.watchCount++
        })

        // Get updated data
        const data = dataAttribute.get(eid)

        // Extract position data
        const lat = position.coords.latitude
        const lng = position.coords.longitude
        const accuracy = position.coords.accuracy || 0

        // Get current time for timestamp
        const now = new Date()
        const timestamp = now.toLocaleTimeString()

        // Print GPS coordinates with timestamp and method
        console.log(`[WATCH GPS #${data.watchCount} @ ${timestamp}] Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)} (Accuracy: ${accuracy.toFixed(1)}m)`)
      },
      (error) => {
        console.error(`[gps-simple] Watch position error: ${error.code} - ${error.message}`)
      },
      {
        // Request the most accurate position available (uses more battery)
        enableHighAccuracy: true,
        // Don't use cached positions (0 = always get fresh position)
        maximumAge: 0,
        // Wait up to 15 seconds for a position
        timeout: 15000,
      }
    )

    // Store watch ID for cleanup
    dataAttribute.mutate(eid, (cursor) => {
      cursor.watchId = watchId
    })

    console.log(`[gps-simple] GPS watch started with ID: ${watchId}`)
  },

  tick: (world, component) => {
    const {eid, dataAttribute, schemaAttribute} = component
    const schema = schemaAttribute.get(eid)
    const data = dataAttribute.get(eid)

    if (!schema.isEnabled) return

    const currentTime = world.time.elapsed

    // Only call getCurrentPosition at the specified interval
    if (currentTime - data.lastPrintTime >= schema.printIntervalMs) {
      // Update last print time
      dataAttribute.mutate(eid, (cursor) => {
        cursor.lastPrintTime = currentTime
      })

      // Get current position
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
          (position) => {
            // Check if entity still exists
            if (!dataAttribute.has(eid)) {
              return
            }

            // Increment getCurrentPosition count
            dataAttribute.mutate(eid, (cursor) => {
              cursor.getCurrentCount++
            })

            // Get updated data
            const updatedData = dataAttribute.get(eid)

            // Extract position data
            const lat = position.coords.latitude
            const lng = position.coords.longitude
            const accuracy = position.coords.accuracy || 0

            // Get current time for timestamp
            const now = new Date()
            const timestamp = now.toLocaleTimeString()

            // Print GPS coordinates with timestamp and method
            console.log(`[GET_CURRENT GPS #${updatedData.getCurrentCount} @ ${timestamp}] Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)} (Accuracy: ${accuracy.toFixed(1)}m)`)
          },
          (error) => {
            console.error(`[gps-simple] GetCurrentPosition error: ${error.code} - ${error.message}`)
          },
          {
            // Request the most accurate position available (uses more battery)
            enableHighAccuracy: true,
            // Don't use cached positions (0 = always get fresh position)
            maximumAge: 0,
            // Wait up to 15 seconds for a position
            timeout: 15000,
          }
        )
      }
    }
  },

  remove: (world, component) => {
    // Check if the component is still attached before accessing data
    if (!component.dataAttribute || !component.dataAttribute.has(component.eid)) {
      return
    }

    const {eid, dataAttribute} = component
    const data = dataAttribute.get(eid)

    // Clear geolocation watch
    if (data?.watchId !== -1) {
      navigator.geolocation.clearWatch(data.watchId)
      console.log(`[gps-simple] GPS watch stopped (ID: ${data.watchId})`)
    }

    console.log('[gps-simple] Component removed')
  },
})

export {TemplateComponentGpsLocationSimple}

You’re probably better off using the lat and long from a map component in your project and checking the lat and long variables in tick. I’m pretty sure our system intercepts the navigator , from the window, to provide simulator functionality etc.

Thanks, George. I’m looking to get the GPS coordinates in AR view, where it wouldn’t be appropriate to have the Map displaying. I guess I could have the Map in scene, and hide all of its visible components?

Okay, I dropped a Map object in the AR view and attached a component to log location, still getting Ferry Building coordinates. Could this be an issue with Pro license vs. Demo license?

1. [MAP GPS #7 @ 9:29:54 AM] Lat: 37.795528, Lng: -122.393422[map-gps-logger.ts:116:18](https://www.8thwall.com/aeonia/gps-test/studio?file=map-gps-logger.ts#L116C18)9:29:54 AM
2. [map-gps-logger] Additional Map data: {useGPS: undefined, radius: 500}

from this code:

// Map GPS Logger Component
// Logs geolocation coordinates from a Map component to the console

import * as ecs from '@8thwall/ecs'

/**
 * A component that attaches to an entity with a Map component and logs
 * the geolocation coordinates to the console at regular intervals.
 *
 * Features:
 * - Works with entities that have a Map component
 * - Logs coordinates at configurable intervals
 * - Provides formatted console output with timestamp and counter
 * - Minimal overhead with no direct geolocation API usage
 *
 * Usage:
 * 1. Attach this component to an entity that already has a Map component
 * 2. Configure the logging interval as needed
 * 3. Check console for coordinate logs
 *
 * Note: The Map component should have useGPS set to true for this component
 * to log real-time GPS coordinates.
 */

const MapGpsLogger = ecs.registerComponent({
  name: 'map-gps-logger',

  schema: {
    isEnabled: ecs.boolean,
    logIntervalMs: ecs.i32,  // How often to log coordinates (in milliseconds)
    enableDebugLogging: ecs.boolean,  // Enable detailed debug logging
  },

  schemaDefaults: {
    isEnabled: true,
    logIntervalMs: 1000,  // Default to logging every 1 second
    enableDebugLogging: true,  // Enable debug logging by default
  },

  data: {
    lastLogTime: ecs.f64,
    logCount: ecs.i32,
  },

  add: (world, component) => {
    const {eid, dataAttribute, schemaAttribute} = component
    const schema = schemaAttribute.get(eid)

    console.log('[map-gps-logger] Component added to entity', eid)

    // Initialize component data
    dataAttribute.set(eid, {
      lastLogTime: 0,
      logCount: 0,
    })

    // Check if entity has a Map component
    if (!ecs.Map.has(world, eid)) {
      console.error('[map-gps-logger] Entity does not have a Map component. This component requires a Map component to function.')
      return
    }

    if (schema.enableDebugLogging) {
      console.log('[map-gps-logger] Entity has Map component, logger initialized successfully')
      console.log(`[map-gps-logger] Will log coordinates every ${schema.logIntervalMs}ms`)
    }
  },

  tick: (world, component) => {
    const {eid, dataAttribute, schemaAttribute} = component
    const schema = schemaAttribute.get(eid)
    const data = dataAttribute.get(eid)

    if (!schema.isEnabled) return

    // Check if entity still has a Map component
    if (!ecs.Map.has(world, eid)) {
      if (schema.enableDebugLogging) {
        console.warn('[map-gps-logger] Entity no longer has a Map component')
      }
      return
    }

    const currentTime = world.time.elapsed

    // Only log at the specified interval
    if (currentTime - data.lastLogTime >= schema.logIntervalMs) {
      // Update last log time
      dataAttribute.mutate(eid, (cursor) => {
        cursor.lastLogTime = currentTime
        cursor.logCount++
      })

      // Get updated data for log count
      const updatedData = dataAttribute.get(eid)

      // Get coordinates from Map component
      // @ts-ignore - Map component is a built-in component
      const mapData = ecs.Map.get(world, eid)

      if (!mapData) {
        if (schema.enableDebugLogging) {
          console.warn('[map-gps-logger] Could not get Map component data')
        }
        return
      }

      const lat = mapData.latitude
      const lng = mapData.longitude

      // Get current time for timestamp
      const now = new Date()
      const timestamp = now.toLocaleTimeString()

      // Log coordinates to console
      console.log(`[MAP GPS #${updatedData.logCount} @ ${timestamp}] Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)}`)

      // Log additional debug info if enabled
      if (schema.enableDebugLogging) {
        console.log('[map-gps-logger] Additional Map data:', {
          useGPS: mapData.useGPS,
          radius: mapData.radius,
          // Add any other relevant Map properties
        })
      }
    }
  },

  remove: (world, component) => {
    const {eid} = component
    console.log('[map-gps-logger] Component removed from entity', eid)
  },
})

export {MapGpsLogger}

Hmm, that could work however if you’re not even using a map there shouldn’t be anything overriding the navigator. I’ll look into this when I have some free time.

EDIT: Definitely not a matter of pro vs free. :slight_smile: Can you land your changes and share your project with the support workspace so I can take a look?

Ok, shared, thanks! I have included the map approach and the navigator approach for comparison.

Hello, I got the same problem with the wrong GPS data, 37.795528, -122.393422, in the simulator. I switched the location option to ‘current location’ and I got the right data in the simulator, but it was still wrong when I used my iPhone


Is there any update of the issue?

Hello! This is a bug that our team has recently fixed. If the phone GPS isn’t being updated its likely the GPS permissions are being blocked on either the browser or device level.

1 Like

Hello,

Thank you for your support!
I’m still facing a GPS permission issue in a project cloned from the VPS Procedural Template, testing the webar via the QR code generated on the 8th Wall website, but it seems still not working, below is how I test my project

Steps Taken:

  1. Cloned VPS Procedural Template: I created a new project using the VPS Procedural Template and tested GPS functionality, but the issue persists.

  2. Tested with a Simple Vite Page: To isolate the issue, I built a basic webpage using Vite to test the GPS function. When clicking the “Update Location” button, the geolocation updates as expected. See the screen recording below:

  3. Screenshots: Here’s a screenshot of the issue in the 8th Wall project:

Code in 8th Wall Studio Project:

Below is the relevant code from my project, including the geolocation logic:

import * as ecs from '@8thwall/ecs'
const {THREE} = window as any

interface Position {
  coords: {
    latitude: number
    longitude: number
    accuracy: number
    altitude?: number | null
    altitudeAccuracy?: number | null
    heading?: number | null
    speed?: number | null
  }
  timestamp: number
}

const getLocation = () =>
  new Promise((resolve, reject) => {
    if (!navigator.geolocation) {
      return reject(new Error('Geolocation not supported'))
    }
    navigator.geolocation.getCurrentPosition(
      resolve,
      reject,
      {
        enableHighAccuracy: true,
        timeout: 5000,
        maximumAge: 0,
      }
    )
  })

ecs.registerComponent({
  name: 'colorful-vps-mesh-shader',
  schema: {
    wireframeColor: ecs.string,
    animationSpeed: ecs.f32,
    opacity: ecs.f32,
    waveScale: ecs.f32,
  },
  schemaDefaults: {
    wireframeColor: '#ffffff',
    animationSpeed: 0.002,
    opacity: 0.5,
    waveScale: 0.1,
  },
  data: {
    time: ecs.f32,
  },
  add: (world, component) => {
    const {scene} = world.three
    let meshFound = false

    const setLocation = async () => {
      try {
        const pos = (await getLocation()) as Position
        const {latitude, longitude, accuracy} = pos.coords
        console.log(`lat: ${latitude}, lon: ${longitude}, acc: ${accuracy}`)
      } catch (e) {
        console.error('Geolocation error:', e)
      }
    }

    // Initial location fetch
    setLocation()
    // Update location every 5 seconds
    setInterval(setLocation, 5000)

    // Mesh and VPS event handlers (omitted for brevity, full code available if needed)
    // ... [rest of the mesh and shader logic]
  },
  tick: (world, component) => {
    // Animation logic (omitted for brevity)
  },
  remove: (world, component) => {
    // Cleanup logic (omitted for brevity)
  },
})

Questions:

  1. Is there an issue with my geolocation implementation or project configuration?
  2. Is there a working template or example project for GPS functionality that I can reference?

Thank you!

Are you overriding the GPS coordinates with any of the following options in the Simulator?

1 Like

Thank you for your response!

I’ve figured out the issue: the GPS functionality works on the iPhone when the project is published. It doesn’t work in simulation mode, but after publishing the project, it works perfectly!

Thank you!

1 Like