Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Location

How the LOCATION measure collects location fixes, continuously or once per trigger, and the data it produces.

Copy MarkdownOpen inOpen in GitHubView as MarkdownOpen in Scira AIOpen in ChatGPTOpen in ClaudeOpen in Cursor

dk.cachet.carp.location (ContextSamplingPackage.LOCATION) collects location fixes from the phone's location service through the location plugin. It is an event-based measure that runs on the LocationService, on Android and iOS. It needs location access all the time, with precise location, on both platforms.

Collect location continuously

Start LOCATION with an ImmediateTrigger and target the task at the location service:

protocol.addTaskControl(
  ImmediateTrigger(),
  BackgroundTask(measures: [Measure(type: ContextSamplingPackage.LOCATION)]),
  locationService,
);

The measure then reports every location that the service delivers, until the study stops. How often that is depends on the LocationService. See Distance and interval.

Collect one location per trigger

To collect a single location each time a trigger fires, override the sampling configuration with once: true and use a trigger that repeats:

protocol.addTaskControl(
  PeriodicTrigger(period: const Duration(minutes: 15)),
  BackgroundTask(
    measures: [
      Measure(type: ContextSamplingPackage.LOCATION)
        ..overrideSamplingConfiguration = LocationSamplingConfiguration(
          once: true,
        ),
    ],
  ),
  locationService,
);

On each trigger, the probe waits up to 6 seconds for a new location. Otherwise it uses the last location that the app received, which may be old. If the app has received no location at all, for example right after it starts, the probe reports an error instead of a location.

The probe pauses itself 5 seconds after each run, and the next trigger resumes it.

Configuration

LocationSamplingConfiguration has one property:

PropertyTypeDefaultDescription
onceboolfalseWhether to collect one location per trigger instead of a continuous stream.

The measure has no default sampling configuration, so it collects continuously unless you override it. Accuracy, distance and interval are settings of the LocationService, not of the measure, and they apply to GEOFENCE and MOBILITY too.

Accuracy levels

The accuracy of the LocationService is a GeolocationAccuracy. The location plugin turns each value into these platform settings:

ValueAndroid priorityiOS accuracy
powerSavePRIORITY_PASSIVEkCLLocationAccuracyKilometer
lowPRIORITY_LOW_POWERkCLLocationAccuracyHundredMeters
balancedPRIORITY_BALANCED_POWER_ACCURACYkCLLocationAccuracyNearestTenMeters
highPRIORITY_HIGH_ACCURACYkCLLocationAccuracyBest
navigationPRIORITY_HIGH_ACCURACYkCLLocationAccuracyBestForNavigation
reducedPRIORITY_LOW_POWERkCLLocationAccuracyReduced

The default is balanced. Higher accuracy uses GPS more and drains the battery faster. With powerSave, Android delivers only the locations that other apps ask for, so the measure may get none.

Distance and interval

The distance and interval of the LocationService set how often a location arrives:

AndroidiOS

The fused location provider gets interval as the update interval and half of it as the fastest interval. It gets distance as the smallest movement between two updates. A phone that moves less than distance gets no new location.

distance becomes the distance filter of Core Location, and 0 turns the filter off. The plugin applies interval itself: it drops each location that arrives sooner than interval after the last one it delivered. It also drops locations that are more than 15 seconds old.

With the defaults, a location arrives about once a minute, but only after the phone has moved 10 meters.

Read locations in the app

To use the locations in the app, filter the measurements stream for Location data:

client.measurements
    .where((measurement) => measurement.data is Location)
    .map((measurement) => measurement.data as Location)
    .listen(
      (location) => print('${location.latitude}, ${location.longitude}'),
    );

To get the position once outside a task, call LocationManager().getLocation(). See LocationManager.

Platform differences

AndroidiOS
  • altitude is the altitude above mean sea level from the GPS data when it is available, and otherwise the altitude above the WGS 84 ellipsoid.
  • heading is the bearing, which Android reports as 0 when a fix has no bearing.
  • elapsedRealtimeUncertaintyNanos needs Android 10 (API level 29) or later, and satellites is set only when the plugin knows the satellite count.
  • isMock is true for locations from a mock location app.
  • altitude is the altitude above mean sea level from Core Location.
  • heading is the course, that is, the direction of travel. Core Location reports a negative value when it cannot determine the course.
  • speed and speedAccuracy are null when Core Location reports them as invalid.
  • headingAccuracy, elapsedRealtimeNanos, elapsedRealtimeUncertaintyNanos and satellites are never set.
  • isMock is true for locations that software simulates.

Privacy

Location data can reveal where a participant lives and works. The package defines no privacy transformer for it, so the default privacy schema leaves locations unchanged. See Data transformation and privacy.

Open mHealth format

CARP Mobile Sensing (CAMS) can convert each location to an Open mHealth geoposition data point. To get this format, set dataFormat: NameSpace.OMH on the data endpoint:

final protocol = SmartphoneStudyProtocol(
  ownerId: 'owner@example.com',
  name: 'Daily mobility',
  dataEndPoint: SQLiteDataEndPoint(dataFormat: NameSpace.OMH),
);

The geoposition holds the latitude and longitude, with GPS as the positioning system; the other fields are dropped. The app's measurements stream carries the data points too, so the code in Read locations in the app gets no Location data.

Data

Each measurement holds a Location with the type dk.cachet.carp.location:

FieldTypeDescription
latitudedoubleLatitude in degrees, WGS 84.
longitudedoubleLongitude in degrees, WGS 84.
altitudedouble?Altitude in meters. See Platform differences.
accuracydouble?Horizontal accuracy: the radius of uncertainty in meters.
verticalAccuracydouble?Vertical accuracy in meters.
speeddouble?Speed in m/s.
speedAccuracydouble?Accuracy of speed in m/s.
headingdouble?Direction of travel in degrees.
headingAccuracydouble?Accuracy of heading in degrees. Android only.
timeDateTime?Time of the fix, in the phone's local time without a UTC offset.
isMockbool?Whether the location comes from a mock location app or a simulation.
elapsedRealtimeNanosdouble?Time of the fix in nanoseconds since the phone started. Android only.
elapsedRealtimeUncertaintyNanosdouble?Uncertainty of elapsedRealtimeNanos in nanoseconds. Android only.
satellitesint?Number of satellites used for the fix. Android only.
providerString?Not set by the package, so always null.

Fields without a value are left out of the JSON. A location from an iPhone looks like this:

{
  "__type": "dk.cachet.carp.location",
  "latitude": 55.786025,
  "longitude": 12.524159,
  "altitude": 41.8,
  "accuracy": 8.9,
  "verticalAccuracy": 6.1,
  "speed": 1.4,
  "speedAccuracy": 0.7,
  "heading": 212.5,
  "time": "2026-10-26T10:15:29.412",
  "isMock": false
}

For the measurement envelope around the data, see Measurements and data.

Quick links

Page · DocumentationCARP DocumentationPage · DocumentationIntroductionPage · DocumentationQuickstartPage · DocumentationTerminologyPage · DocumentationArchitecture