Device Location

This tutorial explains how to work with device location and it covers the following topics:

  1. Detecting the current location
  2. Location properties
  3. Measuring distance between locations
  4. Location accuracy
  5. Geofences
  6. Serializing locations

Detecting the Current Location

Use mobicontrol.location.locate to find the current device location. Note that this function is asynchronous and accepts a callback parameter. That callback will be invoked once the location is available:

mobicontrol.location.locate(onLocationEvent);

function onLocationEvent(result) {
    if (result.isSuccessful) {
        var location = result.location;
        mobicontrol.log.info(
            "Latitude: " + location.latitude + ", " +
            "Longitude: " + location.longitude
        );
    }
}

Location Properties

mobicontrol.location.Location class is a wrapper over Android location and has the same properties: latitude, longitude, timestamp, accuracy, and other information such as bearing, altitude and velocity.

All locations returned by mobicontrol.location.locate are guaranteed to have a valid latitude, longitude, timestamp, and horizontal accuracy. All other properties are optional (set to null if not provided).

Location provider is a system component - such as GPS, Wi-Fi, or cellular networks - that supplies a device's geographical location. Fused location provider is a special provider, which intelligently combines the above mentioned sources for optimal accuracy and power usage. Location provider can be fetched using one of the following properties: hasGpsProvider, hasNetworkProvider and hasFusedProvider.

time property reflects the timestamp of the location. There is no guarantee that different locations have times set from the same clock. Locations provided by GPS provider are guaranteed to have their time originate from the clock in use by the satellite constellation. Locations provided by other providers may use any clock to set their time (which may be incorrect).
Moreover, mobicontrol.location.locate might return an old location if not provided with recent one, so you might want to check time property when working with unreliable location providers.

speed and bearing properties are unique in the sense that they provide information measured over time (device velocity and direction of device movement accordingly).

Measuring Distance between Locations

mobicontrol.location.Location.distanceTo can be used to measure the distance between two locations. The following example shows how to navigate to the closest hospital:

var createLocation = mobicontrol.location.createLocation;

mobicontrol.location.locate(onLocationEvent);
var hospitals = [
    { location: createLocation(43.491331368, -79.868663192), name: "Milton District Hospital" },
    { location: createLocation(43.4539, -79.674), name: "Oakville Trafalgar Memorial Hospital" },
    { location: createLocation(43.559, -79.703), name: "Credit Valley Hospital" },
    { location: createLocation(43.63999744, -79.933996264), name: "Georgetown Hospital" },
    { location: createLocation(43.658977, -79.388505), name: "Toronto General Hospital" }
];

function onLocationEvent(result) {
    if (result.isSuccessful) {
        var currentLocation = result.location;
        hospitals.forEach(hospital => hospital.distance = currentLocation.distanceTo(hospital.location));
        var closestHospital = hospitals.reduce((min, currentHospital) => {
            return (currentHospital.distance < min.distance) ? currentHospital: min;
        });
        navigate(closestHospital);
    }
}

function navigate(place) {
    mobicontrol.log.info("Navigating to: " + place.name);
    var intent = mobicontrol.android.createIntent()
        .withAction("android.intent.action.VIEW")
        .withData("google.navigation:q=" + place.location.latitude + "," + place.location.longitude);
    mobicontrol.android.startActivity(intent);
}

Note that in this example we use mobicontrol.location.createLocation function to construct specific locations for the hospitals.

Location Accuracy

When performing geometrical operations, like measuring the distance between locations, it is important to understand that locations provided by mobicontrol.location.locate are not precise. There is a 68% chance the true location of the device is within the accuracy circle - a circle with a center at the location's coordinates and a radius equal to horizontalAccuracy property of location.

The programmer needs to keep that in mind and use horizontalAccuracy in certain scenarios. For example, the following code snippet shows how to find whether the device's real location is highly likely inside or highly likely outside 5 m range of the meeting point:

var meetingPoint = mobicontrol.location.createLocation(40.7575, -73.9858);

mobicontrol.location.locate(onLocationEvent);

function onLocationEvent(result) {
    if (result.isSuccessful) {
        if (isInsideRange(result.location, 5)) {
            mobicontrol.log.info("The device is very likely inside 5 m range of the meeting point.");
        } else if (isOutsideRange(result.location, 5)) {
            mobicontrol.log.info("The device is very likely outside 5 m range of the meeting point.");
        }
    }
}

function isInsideRange(location, maxAllowedDistance) {
    return location.distanceTo(meetingPoint) + location.horizontalAccuracy < maxAllowedDistance;
}

function isOutsideRange(location, minAllowedDistance) {
    return location.distanceTo(meetingPoint) - location.horizontalAccuracy > minAllowedDistance;
}

Apart from the horizontal accuracy, location can provide verticalAccuracy, speedAccuracy and bearingAccuracy.

Geofences

A geofence is a virtual "perimeter" or "fence" around a given geographic area. It is represented by the Geofence class, which can be passed into Location.isInside or Location.isOutside in order to find out whether specific location lies within that area:

var createLocation = mobicontrol.location.createLocation;
var colosseum = mobicontrol.location.createGeofence([
    createLocation(41.890961, 12.491094),
    createLocation(41.890961, 12.493604),
    createLocation(41.889459, 12.493604),
    createLocation(41.889459, 12.491094)
]);

mobicontrol.location.locate(onLocationEvent);

function onLocationEvent(result) {
    if (result.isSuccessful) {
        if (result.location.isInside(colosseum)) {
            mobicontrol.log.info("Device is likely inside Colosseum");
        } else if (result.location.isOutside(colosseum)) {
            mobicontrol.log.info("Device is likely outside Colosseum");
        } else {
            mobicontrol.log.info("Device is in grey area");
        }
    }
}

Note that isInside and isOutside take into the account the location accuracy, this is why location might be "in the gray area", that is neither isInside nor isOutside return true.

Serializing Locations

Locations can be serialized (see the Serialization tutorial) and stored for later reference. For example, this can be used to track location over time.

The following script, if scheduled to execute every hour, warns the user that they haven't rested in the past 8 hours:

var jsonFile = new mobicontrol.io.File(mobicontrol.storage.internal.dataDirectory + '/restless.json');

mobicontrol.location.locate(onLocationEvent);

function onLocationEvent(result) {
    if (result.isSuccessful) {
        var location = result.location;
        var restless = readRestlessData();
        if (restless.lastLocation != null) {
            if (location.distanceTo(restless.lastLocation) > 1000) { // Consider changes over 1km "restless"
                restless.totalTime += 1;
                if (restless.totalTime > 8) {
                    mobicontrol.message.createWarnDialog("You didn't take a rest for the last " + restless.totalTime + " hours!").show();
                }
            } else {
                restless.totalTime = 0;
            }
        }
        restless.lastLocation = location;
        writeRestlessData(restless);
    }
}

function readRestlessData() {
    if (!jsonFile.exists) {
        return {
            lastLocation: null,
            totalTime: 0
        }
    }
    return JSON.parse(jsonFile.readText(), mobicontrol.json.revive);
}

function writeRestlessData(data) {
    jsonFile.writeText(JSON.stringify(data));
}