/*------------------------------------------------------------------------------ ** Ident: Sogeti Smart Mobile Solutions ** Author: rene ** Copyright: (c) Apr 24, 2011 Sogeti Nederland B.V. All Rights Reserved. **------------------------------------------------------------------------------ ** Sogeti Nederland B.V. | No part of this file may be reproduced ** Distributed Software Engineering | or transmitted in any form or by any ** Lange Dreef 17 | means, electronic or mechanical, for the ** 4131 NJ Vianen | purpose, without the express written ** The Netherlands | permission of the copyright holder. *------------------------------------------------------------------------------ * * This file is part of OpenGPSTracker. * * OpenGPSTracker 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. * * OpenGPSTracker 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 OpenGPSTracker. If not, see <http://www.gnu.org/licenses/>. * */ package nl.sogeti.android.gpstracker.actions.tasks; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.net.Uri; import android.os.AsyncTask; import android.util.Log; import nl.sogeti.android.gpstracker.R; import nl.sogeti.android.gpstracker.actions.utils.ProgressListener; import nl.sogeti.android.gpstracker.db.GPStracking.Tracks; import nl.sogeti.android.gpstracker.db.GPStracking.Waypoints; import nl.sogeti.android.gpstracker.util.ByteProgressAdmin; import nl.sogeti.android.gpstracker.util.ProgressFilterInputStream; import nl.sogeti.android.gpstracker.util.UnicodeReader; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import java.util.Vector; import java.util.concurrent.CancellationException; public class GpxParser extends AsyncTask<Uri, Void, Uri> { private static final String LATITUDE_ATRIBUTE = "lat"; private static final String LONGITUDE_ATTRIBUTE = "lon"; private static final String TRACK_ELEMENT = "trkpt"; private static final String SEGMENT_ELEMENT = "trkseg"; private static final String NAME_ELEMENT = "name"; private static final String TIME_ELEMENT = "time"; private static final String ELEVATION_ELEMENT = "ele"; private static final String COURSE_ELEMENT = "course"; private static final String ACCURACY_ELEMENT = "accuracy"; private static final String SPEED_ELEMENT = "speed"; public static final SimpleDateFormat ZULU_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); public static final SimpleDateFormat ZULU_DATE_FORMAT_MS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); public static final SimpleDateFormat ZULU_DATE_FORMAT_BC = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'UTC'"); protected static final int DEFAULT_UNKNOWN_FILESIZE = 1024 * 1024 * 10; private static final String TAG = "OGT.GpxParser"; static { TimeZone utc = TimeZone.getTimeZone("UTC"); ZULU_DATE_FORMAT.setTimeZone(utc); // ZULU_DATE_FORMAT format ends with Z for UTC so make that true ZULU_DATE_FORMAT_MS.setTimeZone(utc); } private ContentResolver mContentResolver; protected String mErrorDialogMessage; protected Exception mErrorDialogException; protected Context mContext; private ProgressListener mProgressListener; protected GPXParserProgressAdmin mGPXParserProgressAdmin; public GpxParser(Context context, ProgressListener progressListener) { mContext = context; mProgressListener = progressListener; mContentResolver = mContext.getContentResolver(); } public void determineProgressGoal(Uri importFileUri) { mGPXParserProgressAdmin = new GPXParserProgressAdmin(); mGPXParserProgressAdmin.setContentLength(DEFAULT_UNKNOWN_FILESIZE); if (importFileUri != null && importFileUri.getScheme().equals("file")) { File file = new File(importFileUri.getPath()); mGPXParserProgressAdmin.setContentLength(file.length()); } } public Uri importUri(Uri importFileUri) { Uri result = null; String trackName = null; InputStream fis = null; if (importFileUri.getScheme().equals("file")) { trackName = importFileUri.getLastPathSegment(); } try { fis = mContentResolver.openInputStream(importFileUri); } catch (IOException e) { handleError(e, mContext.getString(R.string.error_importgpx_io)); } result = importTrack( fis, trackName); return result; } /** * Read a stream containing GPX XML into the OGT content provider * * @param fis opened stream the read from, will be closed after this call * @param trackName * @return */ public Uri importTrack( InputStream fis, String trackName ) { Uri trackUri = null; int eventType; ContentValues lastPosition = null; Vector<ContentValues> bulk = new Vector<ContentValues>(); boolean speed = false; boolean accuracy = false; boolean bearing = false; boolean elevation = false; boolean name = false; boolean time = false; Long importDate = new Long(new Date().getTime()); try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); XmlPullParser xmlParser = factory.newPullParser(); ProgressFilterInputStream pfis = new ProgressFilterInputStream(fis, mGPXParserProgressAdmin); BufferedInputStream bis = new BufferedInputStream(pfis); UnicodeReader ur = new UnicodeReader(bis, "UTF-8"); xmlParser.setInput(ur); eventType = xmlParser.getEventType(); String attributeName; Uri segmentUri = null; while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { if (xmlParser.getName().equals(NAME_ELEMENT)) { name = true; } else { ContentValues trackContent = new ContentValues(); trackContent.put(Tracks.NAME, trackName); if (xmlParser.getName().equals("trk") && trackUri == null) { trackUri = startTrack(trackContent); } else if (xmlParser.getName().equals(SEGMENT_ELEMENT)) { segmentUri = startSegment(trackUri); } else if (xmlParser.getName().equals(TRACK_ELEMENT)) { lastPosition = new ContentValues(); for (int i = 0; i < 2; i++) { attributeName = xmlParser.getAttributeName(i); if (attributeName.equals(LATITUDE_ATRIBUTE)) { lastPosition.put(Waypoints.LATITUDE, new Double(xmlParser.getAttributeValue(i))); } else if (attributeName.equals(LONGITUDE_ATTRIBUTE)) { lastPosition.put(Waypoints.LONGITUDE, new Double(xmlParser.getAttributeValue(i))); } } } else if (xmlParser.getName().equals(SPEED_ELEMENT)) { speed = true; } else if (xmlParser.getName().equals(ACCURACY_ELEMENT)) { accuracy = true; } else if (xmlParser.getName().equals(COURSE_ELEMENT)) { bearing = true; } else if (xmlParser.getName().equals(ELEVATION_ELEMENT)) { elevation = true; } else if (xmlParser.getName().equals(TIME_ELEMENT)) { time = true; } } } else if (eventType == XmlPullParser.END_TAG) { if (xmlParser.getName().equals(NAME_ELEMENT)) { name = false; } else if (xmlParser.getName().equals(SPEED_ELEMENT)) { speed = false; } else if (xmlParser.getName().equals(ACCURACY_ELEMENT)) { accuracy = false; } else if (xmlParser.getName().equals(COURSE_ELEMENT)) { bearing = false; } else if (xmlParser.getName().equals(ELEVATION_ELEMENT)) { elevation = false; } else if (xmlParser.getName().equals(TIME_ELEMENT)) { time = false; } else if (xmlParser.getName().equals(SEGMENT_ELEMENT)) { if (segmentUri == null) { segmentUri = startSegment( trackUri ); } mContentResolver.bulkInsert(Uri.withAppendedPath(segmentUri, "waypoints"), bulk.toArray(new ContentValues[bulk.size()])); bulk.clear(); } else if (xmlParser.getName().equals(TRACK_ELEMENT)) { if (!lastPosition.containsKey(Waypoints.TIME)) { lastPosition.put(Waypoints.TIME, importDate); } if (!lastPosition.containsKey(Waypoints.SPEED)) { lastPosition.put(Waypoints.SPEED, 0); } bulk.add(lastPosition); lastPosition = null; } } else if (eventType == XmlPullParser.TEXT) { String text = xmlParser.getText(); if (name) { ContentValues nameValues = new ContentValues(); nameValues.put(Tracks.NAME, text); if (trackUri == null) { trackUri = startTrack(new ContentValues()); } mContentResolver.update(trackUri, nameValues, null, null); } else if (lastPosition != null && speed) { lastPosition.put(Waypoints.SPEED, Double.parseDouble(text)); } else if (lastPosition != null && accuracy) { lastPosition.put(Waypoints.ACCURACY, Double.parseDouble(text)); } else if (lastPosition != null && bearing) { lastPosition.put(Waypoints.BEARING, Double.parseDouble(text)); } else if (lastPosition != null && elevation) { lastPosition.put(Waypoints.ALTITUDE, Double.parseDouble(text)); } else if (lastPosition != null && time) { lastPosition.put(Waypoints.TIME, parseXmlDateTime(text)); } } eventType = xmlParser.next(); } } catch (XmlPullParserException e) { handleError(e, mContext.getString(R.string.error_importgpx_xml)); } catch (IOException e) { handleError(e, mContext.getString(R.string.error_importgpx_io)); } finally { try { fis.close(); } catch (IOException e) { Log.w( TAG, "Failed closing inputstream"); } } return trackUri; } private Uri startSegment(Uri trackUri) { if (trackUri == null) { trackUri = startTrack(new ContentValues()); } return mContentResolver.insert(Uri.withAppendedPath(trackUri, "segments"), new ContentValues()); } private Uri startTrack(ContentValues trackContent) { return mContentResolver.insert(Tracks.CONTENT_URI, trackContent); } public static Long parseXmlDateTime(String text) { Long dateTime = 0L; try { if(text==null) { throw new ParseException("Unable to parse dateTime "+text+" of length ", 0); } int length = text.length(); switch (length) { case 20: synchronized (ZULU_DATE_FORMAT) { dateTime = new Long(ZULU_DATE_FORMAT.parse(text).getTime()); } break; case 23: synchronized (ZULU_DATE_FORMAT_BC) { dateTime = new Long(ZULU_DATE_FORMAT_BC.parse(text).getTime()); } break; case 24: synchronized (ZULU_DATE_FORMAT_MS) { dateTime = new Long(ZULU_DATE_FORMAT_MS.parse(text).getTime()); } break; default: throw new ParseException("Unable to parse dateTime "+text+" of length "+length, 0); } } catch (ParseException e) { Log.w(TAG, "Failed to parse a time-date", e); } return dateTime; } /** * * @param dialogException * @param dialogErrorMessage */ protected void handleError(Exception dialogException, String dialogErrorMessage) { Log.e(TAG, "Unable to save ", dialogException); mErrorDialogException = dialogException; mErrorDialogMessage = dialogErrorMessage; cancel(false); throw new CancellationException(dialogErrorMessage); } @Override protected void onPreExecute() { mProgressListener.started(); } @Override protected Uri doInBackground(Uri... params) { Uri importUri = params[0]; determineProgressGoal( importUri); Uri result = importUri( importUri ); return result; } @Override protected void onProgressUpdate(Void... values) { mProgressListener.setProgress(mGPXParserProgressAdmin.getProgress()); } @Override protected void onPostExecute(Uri result) { mProgressListener.finished(result); } @Override protected void onCancelled() { mProgressListener.showError(mContext.getString(R.string.taskerror_gpx_import), mErrorDialogMessage, mErrorDialogException); } public class GPXParserProgressAdmin extends ByteProgressAdmin { public void addBytesProgress(int addedBytes) { super.addBytesProgress(addedBytes); considerPublishProgress(); } @Override public void considerPublishProgress() { if(mustPublishProgress()) { publishProgress(); } } } };