Wednesday, November 26, 2014

Tracking movement path with magnetometer in Android

This time I wanted to learn Android development. I've tried to choose some interesting idea for test Android project. So i came to final project mission - drawing user movement path using magnetometer as a compass. You can use this app as a helper to orientation in unknown place or for example it can be used to chart scheme of apartments in some building. I've used this app to draw my flat scheme :-) You can download sources of this application from Google project hosting of my project. I've called this app PathRecorder :-) Structure of code:

  • FullscreenActivity.java


  • Basic usage of activity is: Creation/setup of view which will used for drawing; setup of UI interaction (On Touch listener,etc.); magnetometer sensor setup; initialization of various classes used for project (such as Storage of app data, MenuManager for showing user menu, etc.); starting some threads for program state monitoring.


  • MenuItem.java


  • Used for saving information about current active menu item in UI, such as - menu text and program state which will be fired when user will switch to this menu item. (All communication between application components goes through ProgramState object, it's like a state machine :-) )


  • MenuManager.java


  • Container for MenuItem class instances. Also holds logic for controlling menu system such as - switching current active menu item, loading menu bitmap resources, checking mouse click event and performing menu actions (changing active menu item, executing current item, etc.).


  • Movement.java


  • Holds class MovementStatistics, which is used for computing some information about current movement path, such as - time traveled in some compass direction, times user rotated clockwise or counter-clockwise, etc. Movement class itself stores LinkedList of user moves, appends nodes to this list as user travels in some direction, communicates to magnetometer sensor fetching current user direction relative to North, converts movement data into representation suitable for drawing movement path in a view (returns Path draw object for canvas).


  • Orientation.java


  • Implements SensorEventListener interface for getting data from magnetometer sensor. Performs all interaction with magnetometer sensor, such as - performing magnetometer calibration (detects accuracy of magnetometer, min/max values of magnetic field, checks for errors in calibration, for example there should be zero or minimum device tilting when in calibration mode, because tilting changes magnetometer output rapidly), calculates degrees to North pole according magnetometer field strength data and passes this information for compass drawing to view.


  • ProgramState.java


  • State machine of application - integrates all application components, so that all components could interact between each other and user interface. Also some threads monitors program state and changes it by calling "check..." method in ProgramState class (for example,- calibration monitor thread informs ProgramState when calibration is finished and UI can show menu for a user...).


  • SketchView.java


  • Exports drawing to bitmap. Performs drawing of visual elements in view such as,- compass, status bar, menu, user movement statistics, user movement path.


  • Storage.java


  • Exports user path drawing to JPG file. Loads/Saves magnetometer calibration data.


  • Vector2D.java


  • Wrapper for vector algebra (for example,- calculation of dot product of vectors, angle between vectors, vector normalization, vector addition, vector rotation, etc...).

    Most important classes are Orientation.java and Movement.java.
    --------------------------------------------
    Orientation.java
    --------------------------------------------
    Encapsulates control of magnetometer sensor. Now I would like to talk a bit about how direction to North is calculated. Most important fact is that absolute maximum value of magnetic field strength along X axis points to west, and absolute maximum value of magnetic field strength along Y axis points to north. I.e. X axis in magnetometer measures magnetic field strength along west-east axis and Y magnetometer axis measures magnetic field strength along north-south axis. So by having these facts we can calculate direction to North. Consider this picture:

    So by having magnetometer data we can calculate (a,b) angles in a following way:


    The only problem is that calculated angle a is in range 0,180 degrees, and we need full range of -180,+180 degrees. So we need to decide sign of a angle, which can be determined from angle b. By doing that we get full direction to North angle formula:

    So we just need to find min,max values of magnetic field. This is achieved by magnetometer calibration in two phases. Phase 1 - simply measures fluctuation of magnetic field when device is stationary and extracts error of magnetic field magnitude. And second phase of calibration - collects magnetic field strength data when user is turning around itself (i.e. around Z axis). After 1 full rotation min,max values are extracted from this data.
    -----------------------------------------
    Movement.java
    -----------------------------------------
    This class collects user movement information in a form - How much time user traveled in some direction relative to North, by fetching magnetometer data using Orientation.java class. Later view which draws user movement path queries draw data from Movement.java class by calling method "getMovementDataForDraw()", which looks like this:
     public Object[] getMovementDataForDraw(float[] startPoint) {
         
      if (moves == null || moves.size() < 2)
       return null;
      
      if (this.isMovementStatisticsNeeded)
       this.movementStats.calculateMovementStatistics(moves);
    
      LinkedList points = convertMovesToPoints(startPoint);
      
      float[] limits = getMinMaxCoordsOfPoints(points);
      
      centerPathToScreen(points, startPoint, limits);
      
      LinkedList pointsFiltered = filterOutLinesWithSmallAngleChange(points);
      
      Path path = convertPointsToPath(pointsFiltered);
      
      Object[] ret = new Object[3];
      ret[0] = path;
      ret[1] = pointsFiltered.getLast();
      ret[2] = this.movementStats;
      
      return ret;
     }
    
    I think that this peace of code is self-descriptive, so I will not make many comments on this :-)
    And finally - some picture describing How this movement path looks like when application runs on Android-enabled device. Using this app I walked near the walls of my flat, so by doing this in the end we get my flat scheme, which looks like this:

    Some explanations,- starting from lower-right corner and going clockwise in picture you will get such rooms: kitchen, bedroom, WC, bathroom, sitting-room.

    Have fun developing for Android and using sensors !

    16 comments:

    1. Hello Agnius, I appreciate if you will let me know on which device and version you run the code. From some reason the sensor fail to register when I am run the code on my nexus 5 device. (I am getting the "No magnetometer available !" exception) Do you have any suggestions? Thank you.

      ReplyDelete
    2. I've developed and tested solution on Samsung GT-S6500D model with Android version 2.3.6.
      I'm not sure if your device has magnetometer sensor implemented by manufacturer. To find out this you need to read your device specification or to contact device manufacturer and ask about this. Hope that helps.

      ReplyDelete
      Replies
      1. Many thanks Agnius.
        I'll check it on other devices. Great project anyway :-)

        Delete
      2. Thank you so much man , but have you tried this with iOS?

        Delete
      3. You are welcome. This is just an Android compatible device solution. I don't have plans to port it to iOs devices.

        Delete
      4. I have been trying to calibrate my android device with this solution , but i am stuck at phase too , and with the message "that you are moving too fast/" I have tried figure 8 movement as well , and rotation across z axis

        Delete
      5. I see. Phase two depends on phase one. Both are sensitive on device tilting. I can make some recommendations on calibration. First try to pass both calibration phases by holding phone in hands. (Calibration can be done holding phone in stable position on some surface, such as table. But in that case compass will be inaccurate, because movement will be by holding phone in hands and that means different device tilting which in turn means that such calibration doesn't makes sense). So calibrate with device in hands. Also when you perform first phase calibration - try to be steady, do not shake device or don't move hands or yourself, so that device could read same tilting. For phase two - try to turn around yourself slowly without changing device tilting a lot. (However some tilting will happen, because when turning humans will put off/put on feet(s) from/to ground. Which in turn will change body position/device tilting. But this is acceptable. Just try to keep hand with device in same position in relation to body.) When you will follow these instructions - magnetometer calibration should be successful.

        Delete
    3. Phase 1 calibration gets completed , but i have tried turning very slow in second phase , with my handy very steady holding the phone , but nothing happens and the screen keeps saying "you are moving too fast ". :/

      ReplyDelete
    4. I don't know - it can be that your situation is very specific. Maybe try at first calibration with phone on table. In that case it will be easier to keep same tilting angle. In second phase simply rotate phone on table slowly until it reaches starting angle (you need to rotate phone for full 360 degrees).
      When you'll succeed - approach with calibration with phone in hands.

      ReplyDelete
    5. Hello Agnius Vasiliauskas,

      Project link is no longer working could you provide updated one?

      Thank you,

      ReplyDelete
    6. Hi,
      Yep, in near future Google-code repository will be shut-downed - so i've moved my projects into GitHub. Check these at link:
      https://github.com/0x69/coding-experiments

      ReplyDelete
    7. Hi Agnius Vasiliauskas,

      I am new to android development. Could you please tell me which IDE is used for this. I tried with eclipse it didnt work.

      Regards,
      Hari

      ReplyDelete
    8. Hi,

      I've used Android Studio. It's almost the same Eclipse - just with some integrated tools for Android OS development.

      ReplyDelete
    9. This comment has been removed by the author.

      ReplyDelete
    10. This comment has been removed by the author.

      ReplyDelete

    Comment will be posted after comment moderation.
    Thank you for your appreciation.