mobility_features 1.3.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 65

Mobility Features #

Author: Thomas Nilsson (tnni@dtu.dk)

Usage #

Step 0: Get the package #

Add the package to your pubspec.yaml file and import the package

import 'package:mobility_features/mobility_features.dart';

Step 1: Init the MobilityFactory instance #

MobilityFactory mobilityFactory = MobilityFactory.instance;

Optionally, the following configurations can be made, which will influence the algorithms for producing features.

In the example below, the default values are shown:

mobilityFactory.stopDuration = Duration(minutes: 3);
mobilityFactory.placeRadius = 50;
mobilityFactory.stopRadius = 25;
mobilityFactory.usePriorContexts = false;

Step 2: Collect location data #

Location data collection is not directly supported by this package, for this you have to use a location plugin such as https://pub.dev/packages/geolocator.

From here, you can to convert from whichever Data Transfer Object is used by the location plugin to a LocationSample.

Below is shown an example using the geolocator plugin, where a Position stream is converted into a LocationSample stream by using a map-function.

void setUpLocationStream() {
  // Set up a Position stream, and make it into a broadcast stream
  Stream<Position> positionStream =
    Geolocator().getPositionStream().asBroadcastStream(); 

  // Convert the Position stream into a LocationSample stream
  Stream<LocationSample> locationSampleStream = positionStream.map((e) =>
    LocationSample(GeoPosition(e.latitude, e.longitude), e.timestamp));

  // Make the Mobility Factory start listening to location updates
  mobilityFactory.startListening(locationSampleStream);
}

Step 3: Compute features #

The features can be computed using the MobilityFactory class which uses stored data to compute the features.

There most basic computation is done as follows:

MobilityContext mc = await mobilityFactory.computeFeatures();

All features are implemented as getters for the MobilityContext object.

context.places;
context.stops;
context.moves;

context.numberOfPlaces;
context.homeStay;
context.entropy;
context.normalizedEntropy;
context.distanceTravelled;
context.routineIndex;

Note: it is not possible to instantiate a MobilityContext object directly. It must be intantiated through the mobilityFactory.computeFeatures() method.

Step 3.1 : Compute features with prior contexts

Should you wish to compute the Routine Index feature (see Theoretical Background) as well, then prior contexts are needed.

Concretely, you will have to track for at least 2 days, to compute this feature and set the usePriorContexts field to true.

mobilityFactory.usePriorContexts = ttrue;

The computation is carried out in the same way as in Step 3.

Step 3.2: Compute features for a specific date

By default, the MobilityContext object uses the current date as reference to filter and group data, however, should you wish to compute the features for a specific date, then it is possible to do so using the date parameter.

DateTime myDate = DateTime(01, 01, 2020);
MobilityContext context = await mobilityFactory.computeFeatures(date: myDate);

Feature-specific instructions #

When a feature cannot be evaluated, it will result in a value of -1.0.

The Home Stay feature requires at least some data to be collected between 00:00 and 06:00, otherwise the feature cannot be evaluated.

The Routine Index feature requires at least two days of sufficient data to be computed.

The Entropy and Normalized Entropy features require at least 2 places to be evaluated. If only a single place was found, the feature can technically still be evaluated and will result in an Entropy of 0, as per the definition of Entropy.

Theorical Background #

For mental health research, location data, together with a time component, both collected from the user’s smartphone, can be reduced to certain behavioral features pertaining to the user’s mobility. These features can be used to diagnose patients suffering from mental disorders such as depression.

Previously, mobility recognition has been done in an off-device fashion where features are extracted after a study was completed. We propose performing mobility feature extracting in real-time on the device itself, as new data comes in a continuous fashion. This trades compute power, i.e. phone battery for bandwidth and storage since the reduced features take up much less space than the raw GPS data, and transforms the very intrusive GPS data to abstract features, which avoids unnecessary logging of sensitive data.

Location Features #

The mobility features which will be used are derived from GPS location data are:

Stop A collection of GPS points which together represent a visit at a known \texit{Place} (see below) for an extended period of time. A \textit{Stop} is defined by a location that represents the centroid of a collection of data points, from which a \textit{Stop} is created. In addition a \textit{Stop} also has an \textit{arrival}- and a \textit{departure} time-stamp, representing when the user arrived at the place and when the user left the place. From the arrival- and departure timestamps of the \textit{Stop} the duration can be computed.

Place A group of stops that were clustered by the DBSCAN algorithm \cite{density-based-1996}. From the cluster of stops, the centroid of the stops can be found, i.e. the center location. In addition, it can be computed how long a user has visited a given place by summing over the duration of all the stops at that place.

Move The travel between two Stops, which the user will pass though a path of GPS points. The distance of a Move can be computed as the sum of using the haversine distance of this path. Given the distance travelled as well as departure and arrival timestamp from the Stops, the average speed at which the user traveled can be derived.

Derived Features #

Home Stay The portion (percentage) of the total time elapsed since midnight which was spent at home. Elapsed time is calculated from the departure time of the last known stop.

Location Variance The statistical variance in the latitude- and longitudinal coordinates.

Number of Places The number of places visited today.

Entropy The entropy with respect to time spent at places.

Normalized Entropy The normalized entropy with respect to time spent at places.

Distance Travelled The total distance travelled today (in meters), i.e. not limited to walking or running.

Routine Index The percentage of today that overlapped with the previous, maximally, 28 days.

[1.3.0] - Streaming based API #

  • Refactored API to support streaming
  • An example app is now included

[1.2.0] - Restructuring #

  • MobilitySerializer is now private.

[1.1.5] - Major refactoring #

  • Renamed and refactored classes such as Location and SingleLocationPoint to GeoPosition and LocationSample respectively.

[1.1.0] - Private classes #

  • Made a series of classes private such that they cannot be instantiated from outside the package

[1.0.0] - Formatting #

  • Fixed a series of formatting issues which caused the package to score lower on pub.dev
  • Upgraded the release number to 1.x.x to increase the package score on pub.dev

[0.1.5] - Private constructor. #

  • The Mobility Context constructor is now private
  • A Mobility Context should always be instantiated via the ContextGenerator class.

[0.1.0] - First release. #

  • The first official release with working unit tests
  • Includes a minimalistic API which allows the application programmer to generate features with very few lines of code.

example/lib/main.dart

import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:mobility_features/mobility_features.dart';

void main() => runApp(MyApp());

String formatDate(DateTime date) {
  return '${date.year}/${date.month}/${date.day}';
}

enum AppState { NO_FEATURES, CALCULATING_FEATURES, FEATURES_READY }

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Mobility Features Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String location = '';
  StreamSubscription subscription;
  MobilityFactory mobilityFactory = MobilityFactory.instance;
  AppState _state = AppState.NO_FEATURES;
  MobilityContext _mobilityContext;

  @override
  void initState() {
    setUpLocationStream();
    mobilityFactory.stopDuration = Duration(seconds: 1);
    mobilityFactory.placeRadius = 50;
    mobilityFactory.stopRadius = 25;
    mobilityFactory.usePriorContexts = true;
  }

  void setUpLocationStream() {
    Stream<Position> positionStream =
        Geolocator().getPositionStream().asBroadcastStream();

    Stream<LocationSample> locationSampleStream = positionStream.map((e) =>
        LocationSample(GeoPosition(e.latitude, e.longitude), e.timestamp));

    mobilityFactory.startListening(locationSampleStream);
    subscription = positionStream.listen(onData);
  }

  void onData(Position p) {
    print(p);
    setState(() {
      location = p.toString();
    });
  }

  Widget entry(String key, String value, IconData icon) {
    return Container(
        padding: const EdgeInsets.all(2),
        margin: EdgeInsets.all(3),
        child: ListTile(
          leading: Icon(icon),
          title: Text(key),
          trailing: Text(value),
        ));
  }

  Widget get featuresOverview {
    return ListView(
      children: <Widget>[
        entry(
            "Routine Index",
            _mobilityContext.routineIndex < 0
                ? "?"
                : "${(_mobilityContext.routineIndex * 100).toStringAsFixed(1)}%",
            Icons.repeat),
        entry(
            "Home Stay",
            _mobilityContext.homeStay < 0
                ? "?"
                : "${(_mobilityContext.homeStay * 100).toStringAsFixed(1)}%",
            Icons.home),
        entry(
            "Distance Travelled",
            "${(_mobilityContext.distanceTravelled / 1000).toStringAsFixed(2)} km",
            Icons.directions_walk),
        entry("Significant Places", "${_mobilityContext.numberOfPlaces}",
            Icons.place),
        entry(
            "Normalized Entropy",
            "${_mobilityContext.normalizedEntropy.toStringAsFixed(2)}",
            Icons.equalizer),
        entry(
            "Location Variance",
            "${(111.133 * _mobilityContext.locationVariance).toStringAsFixed(5)} km",
            Icons.crop_rotate),
      ],
    );
  }

  List<Widget> get contentNoFeatures {
    return [
      Container(
          margin: EdgeInsets.all(25),
          child: Text(
            'Click on the refresh button to generate features',
            style: TextStyle(fontSize: 20),
          ))
    ];
  }

  List<Widget> get contentFeaturesReady {
    return [
      Container(
          margin: EdgeInsets.all(25),
          child: Column(children: [
            Text(
              'Statistics for today,',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '${formatDate(_mobilityContext.date)}',
              style: TextStyle(fontSize: 20, color: Colors.blue),
            ),
          ])),
      Expanded(child: featuresOverview)
    ];
  }

  List<Widget> get contentCalculatingFeatures {
    return [
      Container(
          margin: EdgeInsets.all(25),
          child: Column(children: [
            Text(
              'Calculating features...',
              style: TextStyle(fontSize: 20),
            ),
            Container(
                margin: EdgeInsets.only(top: 50),
                child:
                    Center(child: CircularProgressIndicator(strokeWidth: 10)))
          ]))
    ];
  }

  List<Widget> get content {
    if (_state == AppState.FEATURES_READY)
      return contentFeaturesReady;
    else if (_state == AppState.CALCULATING_FEATURES)
      return contentCalculatingFeatures;
    else
      return contentNoFeatures;
  }

  void _updateFeatures() async {
    if (_state == AppState.CALCULATING_FEATURES) {
      print('Already calculating features!');
      return;
    }

    setState(() {
      _state = AppState.CALCULATING_FEATURES;
    });

    print('Calculating features...');

    DateTime start = DateTime.now();
    MobilityContext mc = await mobilityFactory.computeFeatures();
    DateTime end = DateTime.now();
    Duration dur = Duration(milliseconds: end.millisecondsSinceEpoch - start.millisecondsSinceEpoch);
    print('Computed features in $dur');
    setState(() {
      _mobilityContext = mc;
      _state = AppState.FEATURES_READY;
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Column(children: content),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateFeatures,
        tooltip: 'Calculate features',
        child: Icon(Icons.refresh),
      ), //  This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  mobility_features: ^1.3.1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:mobility_features/mobility_features.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
42
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
70
Overall:
Weighted score of the above. [more]
65
Learn more about scoring.

We analyzed this package on Jul 10, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.14
  • Flutter: 1.17.5

Analysis suggestions

Package does not support Flutter platform android

Because:

  • package:mobility_features/mobility_features.dart that imports:
  • package:path_provider/path_provider.dart that imports:
  • package:path_provider_linux/path_provider_linux.dart that declares support for platforms: linux

Package does not support Flutter platform ios

Because:

  • package:mobility_features/mobility_features.dart that imports:
  • package:path_provider/path_provider.dart that imports:
  • package:path_provider_linux/path_provider_linux.dart that declares support for platforms: linux

Package does not support Flutter platform macos

Because:

  • package:mobility_features/mobility_features.dart that imports:
  • package:path_provider/path_provider.dart that imports:
  • package:path_provider_linux/path_provider_linux.dart that declares support for platforms: linux

Package does not support Flutter platform web

Because:

  • package:mobility_features/mobility_features.dart that imports:
  • package:path_provider/path_provider.dart that declares support for platforms: android, ios, linux, macos

Package does not support Flutter platform windows

Because:

  • package:mobility_features/mobility_features.dart that imports:
  • package:path_provider/path_provider.dart that declares support for platforms: android, ios, linux, macos

Package not compatible with SDK dart

Because:

  • mobility_features that is a package requiring null.

Health suggestions

Format lib/mobility_context.dart.

Run flutter format to format lib/mobility_context.dart.

Format lib/mobility_domain.dart.

Run flutter format to format lib/mobility_domain.dart.

Format lib/mobility_features.dart.

Run flutter format to format lib/mobility_features.dart.

Format lib/mobility_file_util.dart.

Run flutter format to format lib/mobility_file_util.dart.

Format lib/mobility_intermediate.dart.

Run flutter format to format lib/mobility_intermediate.dart.

Maintenance issues and suggestions

Support latest dependencies. (-10 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency (stats).

The package description is too short. (-20 points)

Add more detail to the description field of pubspec.yaml. Use 60 to 180 characters to describe the package, what it does, and its target use case.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.7.0 <3.0.0
flutter 0.0.0
path_provider ^1.6.10 1.6.11
simple_cluster ^0.2.0 0.2.1
stats ^0.2.0+3 0.2.0+3 1.0.0
Transitive dependencies
collection 1.14.12 1.14.13
file 5.2.1
intl 0.16.1
json_annotation 3.0.1
meta 1.1.8 1.2.2
path 1.7.0
path_provider_linux 0.0.1+2
path_provider_macos 0.0.4+3
path_provider_platform_interface 1.0.2
platform 2.2.1
plugin_platform_interface 1.0.2
process 3.0.13
sky_engine 0.0.99
typed_data 1.1.6 1.2.0
vector_math 2.0.8 2.1.0-nullsafety
xdg_directories 0.1.0
Dev dependencies
flutter_test