/*------------------------------------------------------------------------------ ** 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.Context; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore.MediaColumns; import android.util.Log; import android.util.Xml; import nl.sogeti.android.gpstracker.R; import nl.sogeti.android.gpstracker.actions.utils.ProgressListener; import nl.sogeti.android.gpstracker.db.GPStracking; import nl.sogeti.android.gpstracker.db.GPStracking.Media; import nl.sogeti.android.gpstracker.db.GPStracking.Segments; import nl.sogeti.android.gpstracker.db.GPStracking.Tracks; import nl.sogeti.android.gpstracker.db.GPStracking.Waypoints; import nl.sogeti.android.gpstracker.util.Constants; import org.xmlpull.v1.XmlSerializer; import java.io.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; /** * Create a KMZ version of a stored track * * @version $Id: KmzCreator.java 1142 2011-10-30 11:18:51Z rcgroot $ * @author rene (c) Mar 22, 2009, Sogeti B.V. */ public class KmzCreator extends XmlCreator { public static final String NS_SCHEMA = "http://www.w3.org/2001/XMLSchema-instance"; public static final String NS_KML_22 = "http://www.opengis.net/kml/2.2"; public static final SimpleDateFormat ZULU_DATE_FORMATER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); static { TimeZone utc = TimeZone.getTimeZone("UTC"); ZULU_DATE_FORMATER.setTimeZone(utc); // ZULU_DATE_FORMAT format ends with Z for UTC so make that true } private String TAG = "OGT.KmzCreator"; public KmzCreator(Context context, Uri trackUri, String chosenFileName, ProgressListener listener) { super(context, trackUri, chosenFileName, listener); } @Override protected Uri doInBackground(Void... params) { determineProgressGoal(); Uri resultFilename = exportKml(); return resultFilename; } private Uri exportKml() { if (mFileName.endsWith(".kmz") || mFileName.endsWith(".zip")) { setExportDirectoryPath(Constants.getSdCardDirectory(mContext) + mFileName.substring(0, mFileName.length() - 4)); } else { setExportDirectoryPath(Constants.getSdCardDirectory(mContext) + mFileName); } new File(getExportDirectoryPath()).mkdirs(); String xmlFilePath = getExportDirectoryPath() + "/doc.kml"; String resultFilename = null; FileOutputStream fos = null; BufferedOutputStream buf = null; try { verifySdCardAvailibility(); XmlSerializer serializer = Xml.newSerializer(); File xmlFile = new File(xmlFilePath); fos = new FileOutputStream(xmlFile); buf = new BufferedOutputStream(fos, 8192); serializer.setOutput(buf, "UTF-8"); serializeTrack(mTrackUri, mFileName, serializer); buf.close(); buf = null; fos.close(); fos = null; resultFilename = bundlingMediaAndXml(xmlFile.getParentFile().getName(), ".kmz"); mFileName = new File(resultFilename).getName(); } catch (IllegalArgumentException e) { String text = mContext.getString(R.string.ticker_failed) + " \"" + xmlFilePath + "\" " + mContext.getString(R.string.error_filename); handleError(mContext.getString(R.string.taskerror_kmz_write), e, text); } catch (IllegalStateException e) { String text = mContext.getString(R.string.ticker_failed) + " \"" + xmlFilePath + "\" " + mContext.getString(R.string.error_buildxml); handleError(mContext.getString(R.string.taskerror_kmz_write), e, text); } catch (IOException e) { String text = mContext.getString(R.string.ticker_failed) + " \"" + xmlFilePath + "\" " + mContext.getString(R.string.error_writesdcard); handleError(mContext.getString(R.string.taskerror_kmz_write), e, text); } finally { if (buf != null) { try { buf.close(); } catch (IOException e) { Log.e(TAG, "Failed to close buf after completion, ignoring.", e); } } if (fos != null) { try { fos.close(); } catch (IOException e) { Log.e(TAG, "Failed to close fos after completion, ignoring.", e); } } } return Uri.fromFile(new File(resultFilename)); } private void serializeTrack(Uri trackUri, String trackName, XmlSerializer serializer) throws IOException { serializer.startDocument("UTF-8", true); serializer.setPrefix("xsi", NS_SCHEMA); serializer.setPrefix("kml", NS_KML_22); serializer.startTag("", "kml"); serializer.attribute(NS_SCHEMA, "schemaLocation", NS_KML_22 + " http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd"); serializer.attribute(null, "xmlns", NS_KML_22); serializer.text("\n"); serializer.startTag("", "Document"); serializer.text("\n"); quickTag(serializer, "", "name", trackName); /* from <name/> upto <Folder/> */ serializeTrackHeader(serializer, trackUri); serializer.text("\n"); serializer.endTag("", "Document"); serializer.endTag("", "kml"); serializer.endDocument(); } private String serializeTrackHeader(XmlSerializer serializer, Uri trackUri) throws IOException { ContentResolver resolver = mContext.getContentResolver(); Cursor trackCursor = null; String name = null; try { trackCursor = resolver.query(trackUri, new String[] { Tracks.NAME }, null, null, null); if (trackCursor.moveToFirst()) { serializer.text("\n"); serializer.startTag("", "Style"); serializer.attribute(null, "id", "lineStyle"); serializer.startTag("", "LineStyle"); serializer.text("\n"); serializer.startTag("", "color"); serializer.text("99ffac59"); serializer.endTag("", "color"); serializer.text("\n"); serializer.startTag("", "width"); serializer.text("6"); serializer.endTag("", "width"); serializer.text("\n"); serializer.endTag("", "LineStyle"); serializer.text("\n"); serializer.endTag("", "Style"); serializer.text("\n"); serializer.startTag("", "Folder"); name = trackCursor.getString(0); serializer.text("\n"); quickTag(serializer, "", "name", name); serializer.text("\n"); serializer.startTag("", "open"); serializer.text("1"); serializer.endTag("", "open"); serializer.text("\n"); serializeSegments(serializer, Uri.withAppendedPath(trackUri, "segments")); serializer.text("\n"); serializer.endTag("", "Folder"); } } finally { if (trackCursor != null) { trackCursor.close(); } } return name; } /** * <pre> * <Folder> * <Placemark> * serializeSegmentToTimespan() * <LineString> * serializeWaypoints() * </LineString> * </Placemark> * <Placemark/> * <Placemark/> * </Folder> * </pre> * * @param serializer * @param segments * @throws IOException */ private void serializeSegments(XmlSerializer serializer, Uri segments) throws IOException { Cursor segmentCursor = null; ContentResolver resolver = mContext.getContentResolver(); try { segmentCursor = resolver.query(segments, new String[] { Segments._ID }, null, null, null); if (segmentCursor.moveToFirst()) { do { Uri waypoints = Uri.withAppendedPath(segments, segmentCursor.getLong(0) + "/waypoints"); serializer.text("\n"); serializer.startTag("", "Folder"); serializer.text("\n"); serializer.startTag("", "name"); serializer.text(String.format("Segment %d", 1 + segmentCursor.getPosition())); serializer.endTag("", "name"); serializer.text("\n"); serializer.startTag("", "open"); serializer.text("1"); serializer.endTag("", "open"); /* Single <TimeSpan/> element */ serializeSegmentToTimespan(serializer, waypoints); serializer.text("\n"); serializer.startTag("", "Placemark"); serializer.text("\n"); serializer.startTag("", "name"); serializer.text("Path"); serializer.endTag("", "name"); serializer.text("\n"); serializer.startTag("", "styleUrl"); serializer.text("#lineStyle"); serializer.endTag("", "styleUrl"); serializer.text("\n"); serializer.startTag("", "LineString"); serializer.text("\n"); serializer.startTag("", "tessellate"); serializer.text("0"); serializer.endTag("", "tessellate"); serializer.text("\n"); serializer.startTag("", "altitudeMode"); serializer.text("clampToGround"); serializer.endTag("", "altitudeMode"); /* Single <coordinates/> element */ serializeWaypoints(serializer, waypoints); serializer.text("\n"); serializer.endTag("", "LineString"); serializer.text("\n"); serializer.endTag("", "Placemark"); serializeWaypointDescription(serializer, Uri.withAppendedPath(segments, "/" + segmentCursor.getLong(0) + "/media")); serializer.text("\n"); serializer.endTag("", "Folder"); } while (segmentCursor.moveToNext()); } } finally { if (segmentCursor != null) { segmentCursor.close(); } } } /** * <TimeSpan><begin>...</begin><end>...</end></TimeSpan> * * @param serializer * @param waypoints * @throws IOException */ private void serializeSegmentToTimespan(XmlSerializer serializer, Uri waypoints) throws IOException { Cursor waypointsCursor = null; Date segmentStartTime = null; Date segmentEndTime = null; ContentResolver resolver = mContext.getContentResolver(); try { waypointsCursor = resolver.query(waypoints, new String[] { Waypoints.TIME }, null, null, null); if (waypointsCursor.moveToFirst()) { segmentStartTime = new Date(waypointsCursor.getLong(0)); if (waypointsCursor.moveToLast()) { segmentEndTime = new Date(waypointsCursor.getLong(0)); serializer.text("\n"); serializer.startTag("", "TimeSpan"); serializer.text("\n"); serializer.startTag("", "begin"); synchronized (ZULU_DATE_FORMATER) { serializer.text(ZULU_DATE_FORMATER.format(segmentStartTime)); serializer.endTag("", "begin"); serializer.text("\n"); serializer.startTag("", "end"); serializer.text(ZULU_DATE_FORMATER.format(segmentEndTime)); } serializer.endTag("", "end"); serializer.text("\n"); serializer.endTag("", "TimeSpan"); } } } finally { if (waypointsCursor != null) { waypointsCursor.close(); } } } /** * <coordinates>...</coordinates> * * @param serializer * @param waypoints * @throws IOException */ private void serializeWaypoints(XmlSerializer serializer, Uri waypoints) throws IOException { Cursor waypointsCursor = null; ContentResolver resolver = mContext.getContentResolver(); try { waypointsCursor = resolver.query(waypoints, new String[] { Waypoints.LONGITUDE, Waypoints.LATITUDE, Waypoints.ALTITUDE }, null, null, null); if (waypointsCursor.moveToFirst()) { serializer.text("\n"); serializer.startTag("", "coordinates"); do { mXmlCreatorProgressAdmin.addWaypointProgress(1); // Single Coordinate tuple serializeCoordinates(serializer, waypointsCursor); serializer.text(" "); } while (waypointsCursor.moveToNext()); serializer.endTag("", "coordinates"); } } finally { if (waypointsCursor != null) { waypointsCursor.close(); } } } /** * lon,lat,alt tuple without trailing spaces * * @param serializer * @param waypointsCursor * @throws IOException */ private void serializeCoordinates(XmlSerializer serializer, Cursor waypointsCursor) throws IOException { serializer.text(Double.toString(waypointsCursor.getDouble(0))); serializer.text(","); serializer.text(Double.toString(waypointsCursor.getDouble(1))); serializer.text(","); serializer.text(Double.toString(waypointsCursor.getDouble(2))); } private void serializeWaypointDescription(XmlSerializer serializer, Uri media) throws IOException { String mediaPathPrefix = Constants.getSdCardDirectory(mContext); Cursor mediaCursor = null; ContentResolver resolver = mContext.getContentResolver(); try { mediaCursor = resolver.query(media, new String[] { Media.URI, Media.TRACK, Media.SEGMENT, Media.WAYPOINT }, null, null, null); if (mediaCursor.moveToFirst()) { do { Uri mediaUri = Uri.parse(mediaCursor.getString(0)); Uri singleWaypointUri = Uri.withAppendedPath(Tracks.CONTENT_URI, mediaCursor.getLong(1) + "/segments/" + mediaCursor.getLong(2) + "/waypoints/" + mediaCursor.getLong(3)); String lastPathSegment = mediaUri.getLastPathSegment(); if (mediaUri.getScheme().equals("file")) { if (lastPathSegment.endsWith("3gp")) { String includedMediaFile = includeMediaFile(lastPathSegment); serializer.text("\n"); serializer.startTag("", "Placemark"); serializer.text("\n"); quickTag(serializer, "", "name", lastPathSegment); serializer.text("\n"); serializer.startTag("", "description"); String kmlAudioUnsupported = mContext.getString(R.string.kmlVideoUnsupported); serializer.text(String.format(kmlAudioUnsupported, includedMediaFile)); serializer.endTag("", "description"); serializeMediaPoint(serializer, singleWaypointUri); serializer.text("\n"); serializer.endTag("", "Placemark"); } else if (lastPathSegment.endsWith("jpg")) { String includedMediaFile = includeMediaFile(mediaPathPrefix + lastPathSegment); serializer.text("\n"); serializer.startTag("", "Placemark"); serializer.text("\n"); quickTag(serializer, "", "name", lastPathSegment); serializer.text("\n"); quickTag(serializer, "", "description", "<img src=\"" + includedMediaFile + "\" width=\"500px\"/><br/>" + lastPathSegment); serializer.text("\n"); serializeMediaPoint(serializer, singleWaypointUri); serializer.text("\n"); serializer.endTag("", "Placemark"); } else if (lastPathSegment.endsWith("txt")) { serializer.text("\n"); serializer.startTag("", "Placemark"); serializer.text("\n"); quickTag(serializer, "", "name", lastPathSegment); serializer.text("\n"); serializer.startTag("", "description"); BufferedReader buf = new BufferedReader(new FileReader(mediaUri.getEncodedPath())); String line; while ((line = buf.readLine()) != null) { serializer.text(line); serializer.text("\n"); } serializer.endTag("", "description"); serializeMediaPoint(serializer, singleWaypointUri); serializer.text("\n"); serializer.endTag("", "Placemark"); } } else if (mediaUri.getScheme().equals("content")) { if (mediaUri.getAuthority().equals(GPStracking.AUTHORITY + ".string")) { serializer.text("\n"); serializer.startTag("", "Placemark"); serializer.text("\n"); quickTag(serializer, "", "name", lastPathSegment); serializeMediaPoint(serializer, singleWaypointUri); serializer.text("\n"); serializer.endTag("", "Placemark"); } else if (mediaUri.getAuthority().equals("media")) { Cursor mediaItemCursor = null; try { mediaItemCursor = resolver.query(mediaUri, new String[] { MediaColumns.DATA, MediaColumns.DISPLAY_NAME }, null, null, null); if (mediaItemCursor.moveToFirst()) { String includedMediaFile = includeMediaFile(mediaItemCursor.getString(0)); serializer.text("\n"); serializer.startTag("", "Placemark"); serializer.text("\n"); quickTag(serializer, "", "name", mediaItemCursor.getString(1)); serializer.text("\n"); serializer.startTag("", "description"); String kmlAudioUnsupported = mContext.getString(R.string.kmlAudioUnsupported); serializer.text(String.format(kmlAudioUnsupported, includedMediaFile)); serializer.endTag("", "description"); serializeMediaPoint(serializer, singleWaypointUri); serializer.text("\n"); serializer.endTag("", "Placemark"); } } finally { if (mediaItemCursor != null) { mediaItemCursor.close(); } } } } } while (mediaCursor.moveToNext()); } } finally { if (mediaCursor != null) { mediaCursor.close(); } } } /** * <Point>...</Point> <shape>rectangle</shape> * * @param serializer * @param singleWaypointUri * @throws IllegalArgumentException * @throws IllegalStateException * @throws IOException */ private void serializeMediaPoint(XmlSerializer serializer, Uri singleWaypointUri) throws IllegalArgumentException, IllegalStateException, IOException { Cursor waypointsCursor = null; ContentResolver resolver = mContext.getContentResolver(); try { waypointsCursor = resolver.query(singleWaypointUri, new String[] { Waypoints.LONGITUDE, Waypoints.LATITUDE, Waypoints.ALTITUDE }, null, null, null); if (waypointsCursor.moveToFirst()) { serializer.text("\n"); serializer.startTag("", "Point"); serializer.text("\n"); serializer.startTag("", "coordinates"); serializeCoordinates(serializer, waypointsCursor); serializer.endTag("", "coordinates"); serializer.text("\n"); serializer.endTag("", "Point"); serializer.text("\n"); } } finally { if (waypointsCursor != null) { waypointsCursor.close(); } } } @Override protected String getContentType() { return "application/vnd.google-earth.kmz"; } @Override public boolean needsBundling() { return true; } }