Merging GPX import fix

This commit is contained in:
Rodrigo Damazio
2010-09-07 15:08:54 -03:00
4 changed files with 180 additions and 65 deletions
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import android.location.Location;
import android.location.LocationManager;
@@ -28,11 +29,8 @@ import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.SimpleTimeZone;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
@@ -54,22 +52,6 @@ import org.xml.sax.helpers.DefaultHandler;
*/
public class GpxImporter extends DefaultHandler {
/*
* Different date formats used in GPX files
*/
static final SimpleDateFormat DATE_FORMAT1 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssZ");
static final SimpleDateFormat DATE_FORMAT2 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss'Z'");
static final SimpleDateFormat DATE_FORMAT3 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSSZ");
static final SimpleTimeZone UTC_TIMEZONE = new SimpleTimeZone(0, "UTC");
static {
DATE_FORMAT1.setTimeZone(UTC_TIMEZONE);
DATE_FORMAT2.setTimeZone(UTC_TIMEZONE);
DATE_FORMAT3.setTimeZone(UTC_TIMEZONE);
}
/*
* GPX-XML tag names and attributes.
*/
@@ -211,7 +193,7 @@ public class GpxImporter extends DefaultHandler {
this.providerUtils = providerUtils;
tracksWritten = new ArrayList<Long>();
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String newContent = new String(ch, start, length);
@@ -453,7 +435,16 @@ public class GpxImporter extends DefaultHandler {
private void onTimeElementEnd() throws SAXException {
if (location == null) { return; }
long time = parseTimeForAllFormats(content);
// Parse the time
long time;
try {
time = StringUtils.parseXmlDateTime(content.trim());
} catch (IllegalArgumentException e) {
String msg = createErrorMessage("Unable to parse time: " + content);
throw new SAXException(msg, e);
}
// Calculate derived attributes from previous point
if (lastSegmentLocation != null) {
long timeDifference = time - lastSegmentLocation.getTime();
@@ -473,7 +464,9 @@ public class GpxImporter extends DefaultHandler {
location.setBearing(lastSegmentLocation.bearingTo(location));
}
// Fill in the time
location.setTime(time);
// initialize start time with time of first track point
if (statsBuilder == null) {
statsBuilder = new TripStatisticsBuilder();
@@ -511,44 +504,6 @@ public class GpxImporter extends DefaultHandler {
return result;
}
/**
* Parse time trying different formats used in GPX files.
*
* @param timeContents string with time information
* @return time as long
* @throws SAXException on time parsing errors
*/
private long parseTimeForAllFormats(String timeContents) throws SAXException {
long time;
timeContents = timeContents.trim();
// 1st try with time zone at end a la "+0000"
time = parseTime(timeContents, DATE_FORMAT1);
if (time > -1) { return time; }
// if that fails, try with a literal "Z" at the end
// (this is not according to xml standard, but some gpx files are like
// that):
time = parseTime(timeContents, DATE_FORMAT2);
if (time > -1) { return time; }
// some gpx timestamps have 3 additional digits at the end.
time = parseTime(timeContents, DATE_FORMAT3);
if (time > -1) { return time; }
// everything failed - abort the import
String msg = createErrorMessage("Invalid time format: " + timeContents);
throw new SAXException(msg);
}
private long parseTime(String timeContents, SimpleDateFormat format) {
try {
return format.parse(timeContents).getTime();
} catch (ParseException ex) {
return -1;
}
}
/**
* Returns the ID of the last point inserted into the database.
*/
@@ -16,17 +16,22 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.SimpleTimeZone;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Various string manipulation methods.
@@ -76,6 +81,75 @@ public class StringUtils implements DescriptionGenerator {
return "<![CDATA[" + escaped + "]]>";
}
private static final SimpleDateFormat BASE_XML_DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
static {
BASE_XML_DATE_FORMAT.setTimeZone(new SimpleTimeZone(0, "UTC"));
}
private static final Pattern XML_DATE_EXTRAS_PATTERN =
Pattern.compile("^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$");
/**
* Parses an XML dateTime element as defined by
* {@link http://www.w3.org/TR/xmlschema-2/#dateTime}.
*/
public static long parseXmlDateTime(String xmlTime) {
// Parse the base date (fixed format)
ParsePosition position = new ParsePosition(0);
Date date = BASE_XML_DATE_FORMAT.parse(xmlTime, position);
if (date == null) {
throw new IllegalArgumentException("Invalid XML dateTime value: '" + xmlTime
+ "' (at position " + position.getErrorIndex() + ")");
}
// Parse the extras
Matcher matcher =
XML_DATE_EXTRAS_PATTERN.matcher(xmlTime.substring(position.getIndex()));
if (!matcher.matches()) {
// This will match even an empty string as all groups are optional,
// so a non-match means some other garbage was there
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlTime);
}
long time = date.getTime();
// Account for fractional seconds
String fractional = matcher.group(1);
if (fractional != null) {
// Regex ensures fractional part is in (0,1(
float fractionalSeconds = Float.parseFloat(fractional);
long fractionalMillis = (long) (fractionalSeconds * 1000.0f);
time += fractionalMillis;
}
// Account for timezones
String sign = matcher.group(2);
String offsetHoursStr = matcher.group(3);
String offsetMinsStr = matcher.group(4);
if (sign != null && offsetHoursStr != null && offsetMinsStr != null) {
// Regex ensures sign is + or -
boolean plusSign = sign.equals("+");
int offsetHours = Integer.parseInt(offsetHoursStr);
int offsetMins = Integer.parseInt(offsetMinsStr);
// Regex ensures values are >= 0
if (offsetHours > 14 || offsetMins > 59) {
throw new IllegalArgumentException("Bad timezone in " + xmlTime);
}
long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L;
// Make time go back to UTC
if (plusSign) {
time -= totalOffsetMillis;
} else {
time += totalOffsetMillis;
}
}
return time;
}
/**
* Formats a number of milliseconds as a string.
*