FileDocCategorySizeDatePackage
TrackProvider.javaAPI DocAndroid 1.5 API23214Wed May 06 22:42:00 BST 2009com.android.internal.location

TrackProvider.java

// Copyright 2007 The Android Open Source Project

package com.android.internal.location;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import android.location.Criteria;
import android.location.Location;
import android.location.LocationProviderImpl;
import android.os.Bundle;
import android.util.Config;
import android.util.Log;

/**
 * A dummy provider that returns positions interpolated from a sequence
 * of caller-supplied waypoints.  The waypoints are supplied as a
 * String containing one or more numeric quadruples of the form:
 * <br>
 * <code>
 * <time in millis> <latitude> <longitude> <altitude>
 * </code>
 *
 * <p> The waypoints must be supplied in increasing timestamp order.
 *
 * <p> The time at which the provider is constructed is considered to
 * be time 0, and further requests for positions will return a
 * position that is linearly interpolated between the waypoints whose
 * timestamps are closest to the amount of wall clock time that has
 * elapsed since time 0.
 *
 * <p> Following the time of the last waypoint, the position of that
 * waypoint will continue to be returned indefinitely.
 *
 * {@hide}
 */
public class TrackProvider extends LocationProviderImpl {
    static final String LOG_TAG = "TrackProvider";

    private static final long INTERVAL = 1000L;

    private boolean mEnabled = true;

    private double mLatitude;
    private double mLongitude;
    private boolean mHasAltitude;
    private boolean mHasBearing;
    private boolean mHasSpeed;
    private double mAltitude;
    private float mBearing;
    private float mSpeed;
    private Bundle mExtras;

    private long mBaseTime;
    private long mLastTime = -1L;
    private long mTime;

    private long mMinTime;
    private long mMaxTime;

    private List<Waypoint> mWaypoints = new ArrayList<Waypoint>();
    private int mWaypointIndex = 0;

    private boolean mRequiresNetwork = false;
    private boolean mRequiresSatellite = false;
    private boolean mRequiresCell = false;
    private boolean mHasMonetaryCost = false;
    private boolean mSupportsAltitude = true;
    private boolean mSupportsSpeed = true;
    private boolean mSupportsBearing = true;
    private boolean mRepeat = false;
    private int mPowerRequirement = Criteria.POWER_LOW;
    private int mAccuracy = Criteria.ACCURACY_COARSE;

    private float mTrackSpeed = 100.0f; // km/hr - default for kml tracks

    private Location mInitialLocation;

    private void close(Reader rdr) {
        try {
            if (rdr != null) {
                rdr.close();
            }
        } catch (IOException e) {
            Log.w(LOG_TAG, "Exception closing reader", e);
        }
    }

    public void readTrack(File trackFile) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(trackFile), 8192);
            String s;

            long lastTime = -Long.MAX_VALUE;
            while ((s = br.readLine()) != null) {
                String[] tokens = s.split("\\s+");
                if (tokens.length != 4 && tokens.length != 6) {
                    Log.e(LOG_TAG, "Got track \"" + s +
                        "\", wanted <time> <long> <lat> <alt> [<bearing> <speed>]");
                    continue;
                }
                long time;
                double longitude, latitude, altitude;
                try {
                    time = Long.parseLong(tokens[0]);
                    longitude = Double.parseDouble(tokens[1]);
                    latitude = Double.parseDouble(tokens[2]);
                    altitude = Double.parseDouble(tokens[3]);
                } catch (NumberFormatException e) {
                    Log.e(LOG_TAG, "Got track \"" + s +
                        "\", wanted <time> <long> <lat> <alt> " +
                        "[<bearing> <speed>]", e);
                    continue;
                }

                Waypoint w = new Waypoint(getName(), time, latitude, longitude, altitude);
                if (tokens.length >= 6) {
                    float bearing, speed;
                    try {
                        bearing = Float.parseFloat(tokens[4]);
                        speed = Float.parseFloat(tokens[5]);
                        w.setBearing(bearing);
                        w.setSpeed(speed);
                    } catch (NumberFormatException e) {
                        Log.e(LOG_TAG, "Ignoring bearing and speed \"" +
                            tokens[4] + "\", \"" + tokens[5] + "\"", e);
                    }
                }

                if (mInitialLocation == null) {
                    mInitialLocation = w.getLocation();
                }

                // Ignore waypoints whose time is less than or equal to 0 or
                // the time of the previous waypoint
                if (time < 0) {
                    Log.e(LOG_TAG, "Ignoring waypoint at negative time=" + time);
                    continue;
                }
                if (time <= lastTime) {
                    Log.e(LOG_TAG, "Ignoring waypoint at time=" + time +
                        " (< " + lastTime + ")");
                    continue;
                }

                mWaypoints.add(w);
                lastTime = time;
            }

            setTimes();
            return;
        } catch (IOException e) {
            Log.e(LOG_TAG, "Exception reading track file", e);
            mWaypoints.clear();
        } finally {
            close(br);
        }
    }

    public void readKml(File kmlFile) {
        FileReader kmlReader = null;
        try {
            kmlReader = new FileReader(kmlFile);
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xpp = factory.newPullParser();
            xpp.setInput(kmlReader);

            // Concatenate the text of each <coordinates> tag
            boolean inCoordinates = false;
            StringBuilder sb = new StringBuilder();
            int eventType = xpp.getEventType();
            do {
                if (eventType == XmlPullParser.START_DOCUMENT) {
                    // do nothing
                } else if (eventType == XmlPullParser.END_DOCUMENT) {
                    // do nothing
                } else if (eventType == XmlPullParser.START_TAG) {
                    String startTagName = xpp.getName();
                    if (startTagName.equals("coordinates")) {
                        inCoordinates = true;
                    }
                } else if (eventType == XmlPullParser.END_TAG) {
                    String endTagName = xpp.getName();
                    if (endTagName.equals("coordinates")) {
                        inCoordinates = false;
                    }
                } else if (eventType == XmlPullParser.TEXT) {
                    if (inCoordinates) {
                        sb.append(xpp.getText());
                        sb.append(' ');
                    }
                }
                eventType = xpp.next();
            } while (eventType != XmlPullParser.END_DOCUMENT);

            String coordinates = sb.toString();

            // Parse the "lon,lat,alt" triples and supply times
            // for each waypoint based on a constant speed
            Location loc = null;
            double KM_PER_HOUR = mTrackSpeed;
            double KM_PER_METER = 1.0 / 1000.0;
            double MILLIS_PER_HOUR = 60.0 * 60.0 * 1000.0;
            double MILLIS_PER_METER =
                (1.0 / KM_PER_HOUR) * (KM_PER_METER) * (MILLIS_PER_HOUR);
            long time = 0L;

            StringTokenizer st = new StringTokenizer(coordinates, ", ");
            while (st.hasMoreTokens()) {
                try {
                    String lon = st.nextToken();
                    String lat = st.nextToken();
                    String alt = st.nextToken();
                    if (Config.LOGD) {
                        Log.d(LOG_TAG,
                            "lon=" + lon + ", lat=" + lat + ", alt=" + alt);
                    }

                    double nLongitude = Double.parseDouble(lon);
                    double nLatitude = Double.parseDouble(lat);
                    double nAltitude = Double.parseDouble(alt);

                    Location nLoc = new Location(getName());
                    nLoc.setLatitude(nLatitude);
                    nLoc.setLongitude(nLongitude);
                    if (loc != null) {
                        double distance = loc.distanceTo(nLoc);
                        if (Config.LOGD) {
                            Log.d(LOG_TAG, "distance = " + distance);
                        }
                        time += (long) (distance * MILLIS_PER_METER);
                    }

                    Waypoint w = new Waypoint(getName(), time,
                        nLatitude, nLongitude, nAltitude);
                    if (supportsSpeed()) {
                        w.setSpeed(mTrackSpeed);
                    }
                    if (supportsBearing()) {
                        w.setBearing(0.0f);
                    }
                    mWaypoints.add(w);

                    if (mInitialLocation == null) {
                        mInitialLocation = w.getLocation();
                    }

                    loc = nLoc;
                } catch (NumberFormatException nfe) {
                    Log.e(LOG_TAG, "Got NumberFormatException reading KML data: " +
                        nfe, nfe);
                }
            }

            setTimes();
            return;
        } catch (IOException ioe) {
            mWaypoints.clear();
            Log.e(LOG_TAG, "Exception reading KML data: " + ioe, ioe);
            // fall through
        } catch (XmlPullParserException xppe) {
            mWaypoints.clear();
            Log.e(LOG_TAG, "Exception reading KML data: " + xppe, xppe);
            // fall through
        } finally {
            close(kmlReader);
        }
    }

    public void readNmea(String name, File file) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file), 8192);
            String s;

            String provider = getName();
            NmeaParser parser = new NmeaParser(name);
            while ((s = br.readLine()) != null) {
                boolean newWaypoint = parser.parseSentence(s);
                if (newWaypoint) {
                    Location loc = parser.getLocation();
                    Waypoint w = new Waypoint(loc);
                    mWaypoints.add(w);
                    // Log.i(TAG, "Got waypoint " + w);
                }
            }

            setTimes();
            return;
        } catch (IOException ioe) {
            Log.e(LOG_TAG, "Exception reading NMEA data: " + ioe);
            mWaypoints.clear();
        } finally {
            close(br);
        }
    }

    private static boolean booleanVal(String tf) {
        return (tf == null) || (tf.equalsIgnoreCase("true"));
    }

    private static int intVal(String val) {
        try {
            return (val == null) ? 0 : Integer.parseInt(val);
        } catch (NumberFormatException nfe) {
            return 0;
        }
    }

    private static float floatVal(String val) {
        try {
            return (val == null) ? 0 : Float.parseFloat(val);
        } catch (NumberFormatException nfe) {
            return 0.0f;
        }
    }

    public void readProperties(File propertiesFile) {
        BufferedReader br = null;
        if (!propertiesFile.exists()) {
            return;
        }
        try {
            if (Config.LOGD) {
                Log.d(LOG_TAG, "Loading properties file " +
                    propertiesFile.getPath());
            }
            br = new BufferedReader(new FileReader(propertiesFile), 8192);

            String s;
            while ((s = br.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(s);
                String command = null;
                String value = null;
                if (!st.hasMoreTokens()) {
                    continue;
                }
                command = st.nextToken();
                if (st.hasMoreTokens()) {
                    value = st.nextToken();
                }

                if (command.equalsIgnoreCase("requiresNetwork")) {
                    setRequiresNetwork(booleanVal(value));
                } else if (command.equalsIgnoreCase("requiresSatellite")) {
                    setRequiresSatellite(booleanVal(value));
                } else if (command.equalsIgnoreCase("requiresCell")) {
                    setRequiresCell(booleanVal(value));
                } else if (command.equalsIgnoreCase("hasMonetaryCost")) {
                    setHasMonetaryCost(booleanVal(value));
                } else if (command.equalsIgnoreCase("supportsAltitude")) {
                    setSupportsAltitude(booleanVal(value));
                } else if (command.equalsIgnoreCase("supportsBearing")) {
                    setSupportsBearing(booleanVal(value));
                } else if (command.equalsIgnoreCase("repeat")) {
                    setRepeat(booleanVal(value));
                } else if (command.equalsIgnoreCase("supportsSpeed")) {
                    setSupportsSpeed(booleanVal(value));
                } else if (command.equalsIgnoreCase("powerRequirement")) {
                    setPowerRequirement(intVal(value));
                } else if (command.equalsIgnoreCase("accuracy")) {
                    setAccuracy(intVal(value));
                } else if (command.equalsIgnoreCase("trackspeed")) {
                    setTrackSpeed(floatVal(value));
                } else {
                    Log.e(LOG_TAG, "Unknown command \"" + command + "\"");
                }
            }
        } catch (IOException ioe) {
            Log.e(LOG_TAG, "IOException reading properties file " +
                propertiesFile.getPath(), ioe);
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                Log.w(LOG_TAG, "IOException closing properties file " +
                    propertiesFile.getPath(), e);
            }
        }
    }

    public TrackProvider(String name) {
        super(name);
        setTimes();
    }

    public TrackProvider(String name, File file) {
        this(name);

        String filename = file.getName();
        if (filename.endsWith("kml")) {
            readKml(file);
        } else if (filename.endsWith("nmea")) {
            readNmea(getName(), file);
        } else if (filename.endsWith("track")) {
            readTrack(file);
        } else {
            Log.e(LOG_TAG, "Can't initialize TrackProvider from file " +
                filename + " (not *kml, *nmea, or *track)");
        }
        setTimes();
    }

    private void setTimes() {
        mBaseTime = System.currentTimeMillis();
        if (mWaypoints.size() >= 2) {
            mMinTime = mWaypoints.get(0).getTime();
            mMaxTime = mWaypoints.get(mWaypoints.size() - 1).getTime();
        } else {
            mMinTime = mMaxTime = 0;
        }
    }

    private double interp(double d0, double d1, float frac) {
        return d0 + frac * (d1 - d0);
    }

    private void update() {
        // Don't update the position at all unless INTERVAL milliseconds
        // have passed since the last request
        long time = System.currentTimeMillis() - mBaseTime;
        if (time - mLastTime < INTERVAL) {
            return;
        }

        List<Waypoint> waypoints = mWaypoints;
        if (waypoints == null) {
            return;
        }
        int size = waypoints.size();
        if (size < 2) {
            return;
        }

        long t = time;
        if (t < mMinTime) {
            t = mMinTime;
        }
        if (mRepeat) {
            t -= mMinTime;
            long deltaT = mMaxTime - mMinTime;
            t %= 2 * deltaT;
            if (t > deltaT) {
                t = 2 * deltaT - t;
            }
            t += mMinTime;
        } else if (t > mMaxTime) {
            t = mMaxTime;
        }

        // Locate the time interval for the current time
        // We slide the window since we don't expect to move
        // much between calls

        Waypoint w0 = waypoints.get(mWaypointIndex);
        Waypoint w1 = waypoints.get(mWaypointIndex + 1);

        // If the right end of the current interval is too early,
        // move forward to the next waypoint
        while (t > w1.getTime()) {
            w0 = w1;
            w1 = waypoints.get(++mWaypointIndex + 1);
        }
        // If the left end of the current interval is too late,
        // move back to the previous waypoint
        while (t < w0.getTime()) {
            w1 = w0;
            w0 = waypoints.get(--mWaypointIndex);
        }

        // Now we know that w0.mTime <= t <= w1.mTime
        long w0Time = w0.getTime();
        long w1Time = w1.getTime();
        long dt = w1Time - w0Time;

        float frac = (dt == 0) ? 0 : ((float) (t - w0Time) / dt);
        mLatitude  = interp(w0.getLatitude(), w1.getLatitude(), frac);
        mLongitude = interp(w0.getLongitude(), w1.getLongitude(), frac);
        mHasAltitude = w0.hasAltitude() && w1.hasAltitude();
        if (mSupportsAltitude && mHasAltitude) {
            mAltitude  = interp(w0.getAltitude(), w1.getAltitude(), frac);
        }
        if (mSupportsBearing) {
            mHasBearing = frac <= 0.5f ? w0.hasBearing() : w1.hasBearing();
            if (mHasBearing) {
                mBearing  = frac <= 0.5f ? w0.getBearing() : w1.getBearing();
            }
        }
        if (mSupportsSpeed) {
            mHasSpeed = frac <= 0.5f ? w0.hasSpeed() : w1.hasSpeed();
            if (mHasSpeed) {
                mSpeed  = frac <= 0.5f ? w0.getSpeed() : w1.getSpeed();
            }
        }
        mLastTime = time;
        mTime = time;
    }

    public void setRequiresNetwork(boolean requiresNetwork) {
        mRequiresNetwork = requiresNetwork;
    }

    @Override public boolean requiresNetwork() {
        return mRequiresNetwork;
    }

    public void setRequiresSatellite(boolean requiresSatellite) {
        mRequiresSatellite = requiresSatellite;
    }

    @Override public boolean requiresSatellite() {
        return mRequiresSatellite;
    }

    public void setRequiresCell(boolean requiresCell) {
        mRequiresCell = requiresCell;
    }

    @Override public boolean requiresCell() {
        return mRequiresCell;
    }

    public void setHasMonetaryCost(boolean hasMonetaryCost) {
        mHasMonetaryCost = hasMonetaryCost;
    }

    @Override public boolean hasMonetaryCost() {
        return mHasMonetaryCost;
    }

    public void setSupportsAltitude(boolean supportsAltitude) {
        mSupportsAltitude = supportsAltitude;
    }

    @Override public boolean supportsAltitude() {
        return mSupportsAltitude;
    }

    public void setSupportsSpeed(boolean supportsSpeed) {
        mSupportsSpeed = supportsSpeed;
    }

    @Override public boolean supportsSpeed() {
        return mSupportsSpeed;
    }

    public void setSupportsBearing(boolean supportsBearing) {
        mSupportsBearing = supportsBearing;
    }

    @Override public boolean supportsBearing() {
        return mSupportsBearing;
    }

    public void setRepeat(boolean repeat) {
        mRepeat = repeat;
    }

    public void setPowerRequirement(int powerRequirement) {
        if (powerRequirement < Criteria.POWER_LOW ||
            powerRequirement > Criteria.POWER_HIGH) {
            throw new IllegalArgumentException("powerRequirement = " +
                powerRequirement);
        }
        mPowerRequirement = powerRequirement;
    }

    @Override public int getPowerRequirement() {
        return mPowerRequirement;
    }

    public void setAccuracy(int accuracy) {
        mAccuracy = accuracy;
    }

    @Override public int getAccuracy() {
        return mAccuracy;
    }

    public void setTrackSpeed(float trackSpeed) {
        mTrackSpeed = trackSpeed;
    }

    @Override public void enable() {
        mEnabled = true;
    }

    @Override public void disable() {
        mEnabled = false;
    }

    @Override public boolean isEnabled() {
        return mEnabled;
    }

    @Override public int getStatus(Bundle extras) {
        return AVAILABLE;
    }

    @Override public boolean getLocation(Location l) {
        if (mEnabled) {
            update();
            l.setProvider(getName());
            l.setTime(mTime + mBaseTime);
            l.setLatitude(mLatitude);
            l.setLongitude(mLongitude);
            if (mSupportsAltitude && mHasAltitude) {
                l.setAltitude(mAltitude);
            }
            if (mSupportsBearing && mHasBearing) {
                l.setBearing(mBearing);
            }
            if (mSupportsSpeed && mHasSpeed) {
                l.setSpeed(mSpeed);
            }
            if (mExtras != null) {
                l.setExtras(mExtras);
            }
            return true;
        } else {
            return false;
        }
    }

    public Location getInitialLocation() {
        return mInitialLocation;
    }
}

/**
 * A simple tuple of (time stamp, latitude, longitude, altitude), plus optional
 * extras.
 *
 * {@hide}
 */
class Waypoint {
    public Location mLocation;

    public Waypoint(Location location) {
        mLocation = location;
    }

    public Waypoint(String providerName, long time, double latitude, double longitude,
        double altitude) {
        mLocation = new Location(providerName);
        mLocation.setTime(time);
        mLocation.setLatitude(latitude);
        mLocation.setLongitude(longitude);
        mLocation.setAltitude(altitude);
    }

    public long getTime() {
        return mLocation.getTime();
    }

    public double getLatitude() {
        return mLocation.getLatitude();
    }

    public double getLongitude() {
        return mLocation.getLongitude();
    }

    public boolean hasAltitude() {
        return mLocation.hasAltitude();
    }

    public double getAltitude() {
        return mLocation.getAltitude();
    }

    public boolean hasBearing() {
        return mLocation.hasBearing();
    }

    public void setBearing(float bearing) {
        mLocation.setBearing(bearing);
    }

    public float getBearing() {
        return mLocation.getBearing();
    }

    public boolean hasSpeed() {
        return mLocation.hasSpeed();
    }

    public void setSpeed(float speed) {
        mLocation.setSpeed(speed);
    }

    public float getSpeed() {
        return mLocation.getSpeed();
    }

    public Bundle getExtras() {
        return mLocation.getExtras();
    }

    public Location getLocation() {
        return new Location(mLocation);
    }

    @Override public String toString() {
        return "Waypoint[mLocation=" + mLocation + "]";
    }
}