/* * Copyright (c) 2013, Will Szumski * Copyright (c) 2013, Doug Szumski * * This file is part of Cyclismo. * * Cyclismo 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. * * Cyclismo 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 Cyclismo. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cowboycoders.cyclismo.io.file; import android.content.Context; import android.location.Location; import com.google.common.annotations.VisibleForTesting; import org.cowboycoders.cyclismo.R; import org.cowboycoders.cyclismo.content.DescriptionGenerator; import org.cowboycoders.cyclismo.content.DescriptionGeneratorImpl; import org.cowboycoders.cyclismo.content.MyTracksLocation; import org.cowboycoders.cyclismo.content.Sensor; import org.cowboycoders.cyclismo.content.Sensor.SensorData; import org.cowboycoders.cyclismo.content.Sensor.SensorDataSet; import org.cowboycoders.cyclismo.content.Track; import org.cowboycoders.cyclismo.content.Waypoint; import org.cowboycoders.cyclismo.io.file.TrackWriterFactory.TrackFileFormat; import org.cowboycoders.cyclismo.util.StringUtils; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; /** * Write track as KML to a file. * * @author Leif Hendrik Wilden */ public class KmlTrackWriter implements TrackFormatWriter { /** * ID of the KML feature to play a tour. */ public static final String TOUR_FEATURE_ID = "tour"; private static final String WAYPOINT_STYLE = "waypoint"; private static final String STATISTICS_STYLE = "statistics"; private static final String START_STYLE = "start"; private static final String END_STYLE = "end"; private static final String TRACK_STYLE = "track"; private static final String SCHEMA_ID = "schema"; private static final String CADENCE = "cadence"; private static final String HEART_RATE = "heart_rate"; private static final String POWER = "power"; private static final String WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png"; private static final String STATISTICS_ICON = "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"; private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png"; private static final String END_ICON = "http://maps.google.com/mapfiles/kml/paddle/red-circle.png"; private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png"; private final Context context; private final DescriptionGenerator descriptionGenerator; private Track track; private PrintWriter printWriter; private ArrayList<Integer> powerList = new ArrayList<Integer>(); private ArrayList<Integer> cadenceList = new ArrayList<Integer>(); private ArrayList<Integer> heartRateList = new ArrayList<Integer>(); private boolean hasPower; private boolean hasCadence; private boolean hasHeartRate; public KmlTrackWriter(Context context) { this(context, new DescriptionGeneratorImpl(context)); } @VisibleForTesting KmlTrackWriter(Context context, DescriptionGenerator descriptionGenerator) { this.context = context; this.descriptionGenerator = descriptionGenerator; } @Override public String getExtension() { return TrackFileFormat.KML.getExtension(); } @Override public void prepare(Track aTrack, OutputStream outputStream) { this.track = aTrack; this.printWriter = new PrintWriter(outputStream); } @Override public void close() { if (printWriter != null) { printWriter.close(); printWriter = null; } } @Override public void writeHeader() { if (printWriter != null) { printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.2\""); printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\""); printWriter.println("xmlns:gx=\"http://www.google.com/kml/ext/2.2\">"); printWriter.println("<Document>"); printWriter.println("<open>1</open>"); printWriter.println("<visibility>1</visibility>"); printWriter.println( "<description>" + StringUtils.formatCData(track.getDescription()) + "</description>"); printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>"); printWriter.println("<atom:author><atom:name>" + StringUtils.formatCData( context.getString(R.string.send_google_by_my_tracks, "", "")) + "</atom:name></atom:author>"); writeTrackStyle(); writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1); writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1); writePlacemarkerStyle(STATISTICS_STYLE, STATISTICS_ICON, 20, 2); writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2); printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">"); writeSensorStyle(POWER, context.getString(R.string.description_sensor_power)); writeSensorStyle(CADENCE, context.getString(R.string.description_sensor_cadence)); writeSensorStyle(HEART_RATE, context.getString(R.string.description_sensor_heart_rate)); printWriter.println("</Schema>"); } } @Override public void writeFooter() { if (printWriter != null) { printWriter.println("</Document>"); printWriter.println("</kml>"); } } @Override public void writeBeginWaypoints() { if (printWriter != null) { printWriter.println( "<Folder><name>" + StringUtils.formatCData(context.getString(R.string.menu_markers)) + "</name>"); } } @Override public void writeEndWaypoints() { if (printWriter != null) { printWriter.println("</Folder>"); } } @Override public void writeWaypoint(Waypoint waypoint) { if (printWriter != null) { String styleName = waypoint.getType() == Waypoint.TYPE_STATISTICS ? STATISTICS_STYLE : WAYPOINT_STYLE; writePlacemark( waypoint.getName(), waypoint.getDescription(), styleName, waypoint.getLocation()); } } @Override public void writeBeginTrack(Location firstLocation) { if (printWriter != null) { String name = context.getString(R.string.marker_label_start, track.getName()); writePlacemark(name, track.getDescription(), START_STYLE, firstLocation); printWriter.println("<Placemark id=\"" + TOUR_FEATURE_ID + "\">"); printWriter.println( "<description>" + StringUtils.formatCData(track.getDescription()) + "</description>"); printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>"); printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>"); printWriter.println("<gx:MultiTrack>"); printWriter.println("<altitudeMode>absolute</altitudeMode>"); printWriter.println("<gx:interpolate>1</gx:interpolate>"); } } @Override public void writeEndTrack(Location lastLocation) { if (printWriter != null) { printWriter.println("</gx:MultiTrack>"); printWriter.println("</Placemark>"); String name = context.getString(R.string.marker_label_end, track.getName()); String description = descriptionGenerator.generateTrackDescription(track, null, null, false); writePlacemark(name, description, END_STYLE, lastLocation); } } @Override public void writeOpenSegment() { if (printWriter != null) { printWriter.println("<gx:Track>"); hasPower = false; hasCadence = false; hasHeartRate = false; powerList.clear(); cadenceList.clear(); heartRateList.clear(); } } @Override public void writeCloseSegment() { if (printWriter != null) { printWriter.println("<ExtendedData>"); printWriter.println("<SchemaData schemaUrl=\"#" + SCHEMA_ID + "\">"); if (hasPower) { writeSensorData(powerList, POWER); } if (hasCadence) { writeSensorData(cadenceList, CADENCE); } if (hasHeartRate) { writeSensorData(heartRateList, HEART_RATE); } printWriter.println("</SchemaData>"); printWriter.println("</ExtendedData>"); printWriter.println("</gx:Track>"); } } @Override public void writeLocation(Location location) { if (printWriter != null) { printWriter.println("<when>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</when>"); printWriter.println( "<gx:coord>" + location.getLongitude() + " " + location.getLatitude() + " " + location.getAltitude() + "</gx:coord>"); if (location instanceof MyTracksLocation) { SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet(); int power = -1; int cadence = -1; int heartRate = -1; if (sensorDataSet != null) { if (sensorDataSet.hasPower()) { SensorData sensorData = sensorDataSet.getPower(); if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) { hasPower = true; power = Math.round(sensorData.getValue()); } } if (sensorDataSet.hasCadence()) { SensorData sensorData = sensorDataSet.getCadence(); if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) { hasCadence = true; cadence = Math.round(sensorData.getValue()); } } if (sensorDataSet.hasHeartRate()) { SensorData sensorData = sensorDataSet.getHeartRate(); if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) { hasHeartRate = true; heartRate = Math.round(sensorData.getValue()); } } } powerList.add(power); cadenceList.add(cadence); heartRateList.add(heartRate); } } } /** * Writes the sensor data. * * @param list a list of sensor data * @param name the name of the sensor data */ private void writeSensorData(ArrayList<Integer> list, String name) { printWriter.println("<gx:SimpleArrayData name=\"" + name + "\">"); for (int i = 0; i < list.size(); i++) { printWriter.println("<gx:value>" + list.get(i) + "</gx:value>"); } printWriter.println("</gx:SimpleArrayData>"); } /** * Writes a placemark. * * @param name the name of the placemark * @param description the description * @param styleName the style name * @param location the location */ private void writePlacemark( String name, String description, String styleName, Location location) { if (location != null) { printWriter.println("<Placemark>"); printWriter.println( "<description>" + StringUtils.formatCData(description) + "</description>"); printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>"); printWriter.println("<styleUrl>#" + styleName + "</styleUrl>"); printWriter.println("<Point>"); printWriter.println( "<coordinates>" + location.getLongitude() + "," + location.getLatitude() + "," + location.getAltitude() + "</coordinates>"); printWriter.println("</Point>"); printWriter.println("</Placemark>"); } } /** * Writes the track style. */ private void writeTrackStyle() { printWriter.println("<Style id=\"" + TRACK_STYLE + "\">"); printWriter.println("<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>"); printWriter.println("<IconStyle>"); printWriter.println("<scale>1.3</scale>"); printWriter.println("<Icon><href>" + TRACK_ICON + "</href></Icon>"); printWriter.println("</IconStyle>"); printWriter.println("</Style>"); } /** * Writes a placemarker style. * * @param name the name of the style * @param url the url of the style icon * @param x the x position of the hotspot * @param y the y position of the hotspot */ private void writePlacemarkerStyle(String name, String url, int x, int y) { printWriter.println("<Style id=\"" + name + "\"><IconStyle>"); printWriter.println("<scale>1.3</scale>"); printWriter.println("<Icon><href>" + url + "</href></Icon>"); printWriter.println( "<hotSpot x=\"" + x + "\" y=\"" + y + "\" xunits=\"pixels\" yunits=\"pixels\"/>"); printWriter.println("</IconStyle></Style>"); } /** * Writes a sensor style. * * @param name the name of the sesnor * @param displayName the sensor display name */ private void writeSensorStyle(String name, String displayName) { printWriter.println("<gx:SimpleArrayField name=\"" + name + "\" type=\"int\">"); printWriter.println( "<displayName>" + StringUtils.formatCData(displayName) + "</displayName>"); printWriter.println("</gx:SimpleArrayField>"); } }