org.runnerup.export.format.RunKeeper.java Source code

Java tutorial

Introduction

Here is the source code for org.runnerup.export.format.RunKeeper.java

Source

/*
 * Copyright (C) 2012 jonas.oreland@gmail.com
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.runnerup.export.format;

import android.annotation.TargetApi;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.runnerup.common.util.Constants;
import org.runnerup.common.util.Constants.DB;
import org.runnerup.db.entities.ActivityEntity;
import org.runnerup.db.entities.LapEntity;
import org.runnerup.db.entities.LocationEntity;
import org.runnerup.export.RunKeeperSynchronizer;
import org.runnerup.util.JsonWriter;
import org.runnerup.workout.Sport;

import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

/**
 * @author jonas.oreland@gmail.com
 * 
 */

@TargetApi(Build.VERSION_CODES.FROYO)
public class RunKeeper {

    long mID = 0;
    SQLiteDatabase mDB = null;

    public RunKeeper(SQLiteDatabase db) {
        mDB = db;
    }

    static String formatTime(long time) {
        return new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.US).format(new Date(time));
    }

    public void export(long activityId, Writer writer) throws IOException {

        ActivityEntity ae = new ActivityEntity();
        ae.readByPrimaryKey(mDB, activityId);
        long startTime = ae.getStartTime();
        double distance = ae.getDistance();
        long duration = ae.getTime();
        String comment = ae.getComment();
        try {
            JsonWriter w = new JsonWriter(writer);
            w.beginObject();
            Sport s = Sport.valueOf(ae.getSport());
            if (!RunKeeperSynchronizer.sport2runkeeperMap.containsKey(s)) {
                s = Sport.OTHER;
            }
            w.name("type").value(RunKeeperSynchronizer.sport2runkeeperMap.get(s));
            w.name("equipment").value("None");
            w.name("start_time").value(formatTime(startTime * 1000));
            w.name("total_distance").value(distance);
            w.name("duration").value(duration);
            if (comment != null && comment.length() > 0) {
                w.name("notes").value(comment);
            }
            //it seems that upload fails if writting an empty array...
            if (ae.getMaxHr() != null) {
                w.name("heart_rate");
                w.beginArray();
                exportHeartRate(activityId, startTime, w);
                w.endArray();
            }
            exportPath("path", activityId, startTime, w);
            w.name("post_to_facebook").value(false);
            w.name("post_to_twitter").value(false);
            w.endObject();
        } catch (IOException e) {
            throw e;
        }
    }

    private void exportHeartRate(long activityId, long startTime, JsonWriter w) throws IOException {
        String[] pColumns = { DB.LOCATION.TIME, DB.LOCATION.HR };
        Cursor cursor = mDB.query(DB.LOCATION.TABLE, pColumns, DB.LOCATION.ACTIVITY + " = " + activityId, null,
                null, null, null);
        if (cursor.moveToFirst()) {
            startTime = cursor.getLong(0);
            do {
                if (!cursor.isNull(1)) {
                    w.beginObject();
                    w.name("timestamp").value((cursor.getLong(0) - startTime) / 1000);
                    w.name("heart_rate").value(Integer.toString(cursor.getInt(1)));
                    w.endObject();
                }
            } while (cursor.moveToNext());
        }
        cursor.close();
    }

    private void exportPath(String name, long activityId, long startTime, JsonWriter w) throws IOException {
        String[] pColumns = { DB.LOCATION.TIME, DB.LOCATION.LATITUDE, DB.LOCATION.LONGITUDE, DB.LOCATION.ALTITUDE,
                DB.LOCATION.TYPE };
        Cursor cursor = mDB.query(DB.LOCATION.TABLE, pColumns, DB.LOCATION.ACTIVITY + " = " + activityId, null,
                null, null, null);
        if (cursor.moveToFirst()) {
            w.name(name);
            w.beginArray();
            startTime = cursor.getLong(0);
            do {
                w.beginObject();
                w.name("timestamp").value((cursor.getLong(0) - startTime) / 1000);
                w.name("latitude").value(cursor.getDouble(1));
                w.name("longitude").value(cursor.getDouble(2));
                if (!cursor.isNull(3)) {
                    w.name("altitude").value(cursor.getDouble(3));
                }
                if (cursor.getLong(4) == DB.LOCATION.TYPE_START) {
                    w.name("type").value("start");
                } else if (cursor.getLong(4) == DB.LOCATION.TYPE_END) {
                    w.name("type").value("end");
                } else if (cursor.getLong(4) == DB.LOCATION.TYPE_PAUSE) {
                    w.name("type").value("pause");
                } else if (cursor.getLong(4) == DB.LOCATION.TYPE_RESUME) {
                    w.name("type").value("resume");
                } else if (cursor.getLong(4) == DB.LOCATION.TYPE_GPS) {
                    w.name("type").value("gps");
                } else {
                    w.name("type").value("manual");
                }
                w.endObject();
            } while (cursor.moveToNext());
            w.endArray();
        }
        cursor.close();
    }

    public static ActivityEntity parseToActivity(JSONObject response, double unitMeters) throws JSONException {
        ActivityEntity newActivity = new ActivityEntity();
        newActivity.setSport(RunKeeperSynchronizer.runkeeper2sportMap.get(response.getString("type")).getDbValue());
        if (response.has("notes")) {
            newActivity.setComment(response.getString("notes"));
        }
        newActivity.setTime((long) Float.parseFloat(response.getString("duration")));
        newActivity.setDistance(Float.parseFloat(response.getString("total_distance")));

        String startTime = response.getString("start_time");
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.US);
        try {
            newActivity.setStartTime(format.parse(startTime));
        } catch (ParseException e) {
            Log.e(Constants.LOG, e.getMessage());
            return null;
        }

        List<LapEntity> laps = new ArrayList<LapEntity>();
        List<LocationEntity> locations = new ArrayList<LocationEntity>();

        JSONArray distance = response.getJSONArray("distance");
        JSONArray path = response.getJSONArray("path");
        JSONArray hr = response.getJSONArray("heart_rate");

        SortedMap<Long, HashMap<String, String>> pointsValueMap = createPointsMap(distance, path, hr);
        Iterator<Map.Entry<Long, HashMap<String, String>>> points = pointsValueMap.entrySet().iterator();

        //lap hr
        int maxHr = 0;
        int sumHr = 0;
        int count = 0;
        //point speed
        long time = 0;
        float meters = 0.0f;
        //activity hr
        int maxHrOverall = 0;
        int sumHrOverall = 0;
        int countOverall = 0;

        while (points.hasNext()) {
            Map.Entry<Long, HashMap<String, String>> timePoint = points.next();
            HashMap<String, String> values = timePoint.getValue();

            LocationEntity lv = new LocationEntity();
            lv.setActivityId(newActivity.getId());
            lv.setTime(TimeUnit.SECONDS.toMillis(newActivity.getStartTime()) + timePoint.getKey());

            String dist = values.get("distance");
            if (dist == null) {
                continue;
            }
            String lat = values.get("latitude");
            String lon = values.get("longitude");
            String alt = values.get("altitude");
            String heart = values.get("heart_rate");
            String type = values.get("type");

            if (lat == null || lon == null) {
                continue;
            } else {
                lv.setLatitude(Double.valueOf(lat));
                lv.setLongitude(Double.valueOf(lon));
            }
            if (alt != null) {
                lv.setAltitude(Double.valueOf(alt));
            }

            if (pointsValueMap.firstKey().equals(timePoint.getKey())) {
                lv.setType(DB.LOCATION.TYPE_START);
            } else if (!points.hasNext()) {
                lv.setType(DB.LOCATION.TYPE_END);
            } else if (type != null) {
                lv.setType(RunKeeperSynchronizer.POINT_TYPE.get(type));
            }
            // lap and activity max and avg hr
            if (heart != null) {
                lv.setHr(Integer.valueOf(heart));
                maxHr = Math.max(maxHr, lv.getHr());
                maxHrOverall = Math.max(maxHrOverall, lv.getHr());
                sumHr += lv.getHr();
                sumHrOverall += lv.getHr();
                count++;
                countOverall++;
            }

            meters = Float.valueOf(dist) - meters;
            time = timePoint.getKey() - time;
            if (time > 0) {
                float speed = meters / (float) TimeUnit.MILLISECONDS.toSeconds(time);
                BigDecimal s = new BigDecimal(speed);
                s = s.setScale(2, BigDecimal.ROUND_UP);
                lv.setSpeed(s.floatValue());
            }

            // create lap if distance greater than configured lap distance

            if (Float.valueOf(dist) >= unitMeters * laps.size()) {
                LapEntity newLap = new LapEntity();
                newLap.setLap(laps.size());
                newLap.setDistance(Float.valueOf(dist));
                newLap.setTime((int) TimeUnit.MILLISECONDS.toSeconds(timePoint.getKey()));
                newLap.setActivityId(newActivity.getId());
                laps.add(newLap);

                // update previous lap with duration and distance
                if (laps.size() > 1) {
                    LapEntity previousLap = laps.get(laps.size() - 2);
                    previousLap.setDistance(Float.valueOf(dist) - previousLap.getDistance());
                    previousLap.setTime(
                            (int) TimeUnit.MILLISECONDS.toSeconds(timePoint.getKey()) - previousLap.getTime());

                    if (hr != null && hr.length() > 0) {
                        previousLap.setMaxHr(maxHr);
                        previousLap.setAvgHr(sumHr / count);
                    }
                    maxHr = 0;
                    sumHr = 0;
                    count = 0;
                }
            }
            // update last lap with duration and distance
            if (!points.hasNext()) {
                LapEntity previousLap = laps.get(laps.size() - 1);
                previousLap.setDistance(Float.valueOf(dist) - previousLap.getDistance());
                previousLap
                        .setTime((int) TimeUnit.MILLISECONDS.toSeconds(timePoint.getKey()) - previousLap.getTime());

                if (hr != null && hr.length() > 0) {
                    previousLap.setMaxHr(maxHr);
                    previousLap.setAvgHr(sumHr / count);
                }
            }

            lv.setLap(laps.size() - 1);

            locations.add(lv);
        }
        // calculate avg and max hr
        // update the activity
        newActivity.setMaxHr(maxHrOverall);
        if (countOverall > 0) {
            newActivity.setAvgHr(sumHrOverall / countOverall);
        }

        newActivity.putPoints(locations);
        newActivity.putLaps(laps);

        return newActivity;
    }

    private static SortedMap<Long, HashMap<String, String>> createPointsMap(JSONArray distance, JSONArray path,
            JSONArray hr) throws JSONException {
        SortedMap<Long, HashMap<String, String>> result = new TreeMap<Long, HashMap<String, String>>();

        if (distance != null && distance.length() > 0) {
            for (int i = 0; i < distance.length(); i++) {
                JSONObject o = distance.getJSONObject(i);
                Long key = TimeUnit.SECONDS.toMillis((long) Float.parseFloat(o.getString("timestamp")));
                HashMap<String, String> value = new HashMap<String, String>();
                String valueMapKey = "distance";
                String valueMapValue = o.getString(valueMapKey);
                value.put(valueMapKey, valueMapValue);
                result.put(key, value);
            }
        }

        if (path != null && path.length() > 0) {
            for (int i = 0; i < path.length(); i++) {
                JSONObject o = path.getJSONObject(i);
                Long key = TimeUnit.SECONDS.toMillis((long) Float.parseFloat(o.getString("timestamp")));
                HashMap<String, String> value = result.get(key);
                if (value == null) {
                    value = new HashMap<String, String>();
                }
                String[] attrs = new String[] { "latitude", "longitude", "altitude", "type" };
                for (String valueMapKey : attrs) {
                    String valueMapValue = o.getString(valueMapKey);
                    value.put(valueMapKey, valueMapValue);
                }
                result.put(key, value);
            }
        }

        if (hr != null && hr.length() > 0) {
            for (int i = 0; i < hr.length(); i++) {
                JSONObject o = hr.getJSONObject(i);
                Long key = TimeUnit.SECONDS.toMillis((long) Float.parseFloat(o.getString("timestamp")));
                HashMap<String, String> value = result.get(key);
                if (value == null) {
                    value = new HashMap<String, String>();
                }
                String valueMapKey = "heart_rate";
                String valueMapValue = o.getString(valueMapKey);
                value.put(valueMapKey, valueMapValue);
                result.put(key, value);
            }
        }
        return result;
    }
}