001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.BufferedWriter; 007import java.io.OutputStream; 008import java.io.OutputStreamWriter; 009import java.io.PrintWriter; 010import java.io.UnsupportedEncodingException; 011import java.util.Collection; 012import java.util.Map; 013import java.util.Map.Entry; 014 015import org.openstreetmap.josm.data.Bounds; 016import org.openstreetmap.josm.data.coor.LatLon; 017import org.openstreetmap.josm.data.gpx.Extensions; 018import org.openstreetmap.josm.data.gpx.GpxConstants; 019import org.openstreetmap.josm.data.gpx.GpxData; 020import org.openstreetmap.josm.data.gpx.GpxLink; 021import org.openstreetmap.josm.data.gpx.GpxRoute; 022import org.openstreetmap.josm.data.gpx.GpxTrack; 023import org.openstreetmap.josm.data.gpx.GpxTrackSegment; 024import org.openstreetmap.josm.data.gpx.IWithAttributes; 025import org.openstreetmap.josm.data.gpx.WayPoint; 026 027/** 028 * Writes GPX files from GPX data or OSM data. 029 */ 030public class GpxWriter extends XmlWriter implements GpxConstants { 031 032 public GpxWriter(PrintWriter out) { 033 super(out); 034 } 035 036 public GpxWriter(OutputStream out) throws UnsupportedEncodingException { 037 super(new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, "UTF-8")))); 038 } 039 040 private GpxData data; 041 private String indent = ""; 042 043 private final static int WAY_POINT = 0; 044 private final static int ROUTE_POINT = 1; 045 private final static int TRACK_POINT = 2; 046 047 public void write(GpxData data) { 048 this.data = data; 049 // We write JOSM specific meta information into gpx 'extensions' elements. 050 // In particular it is noted whether the gpx data is from the OSM server 051 // (so the rendering of clouds of anonymous TrackPoints can be improved) 052 // and some extra synchronization info for export of AudioMarkers. 053 // It is checked in advance, if any extensions are used, so we know whether 054 // a namespace declaration is necessary. 055 boolean hasExtensions = data.fromServer; 056 if (!hasExtensions) { 057 for (WayPoint wpt : data.waypoints) { 058 Extensions extensions = (Extensions) wpt.get(META_EXTENSIONS); 059 if (extensions != null && !extensions.isEmpty()) { 060 hasExtensions = true; 061 break; 062 } 063 } 064 } 065 066 out.println("<?xml version='1.0' encoding='UTF-8'?>"); 067 out.println("<gpx version=\"1.1\" creator=\"JOSM GPX export\" xmlns=\"http://www.topografix.com/GPX/1/1\"\n" + 068 (hasExtensions ? String.format(" xmlns:josm=\"%s\"%n", JOSM_EXTENSIONS_NAMESPACE_URI) : "") + 069 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n" + 070 " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"); 071 indent = " "; 072 writeMetaData(); 073 writeWayPoints(); 074 writeRoutes(); 075 writeTracks(); 076 out.print("</gpx>"); 077 out.flush(); 078 } 079 080 private void writeAttr(IWithAttributes obj) { 081 for (String key : WPT_KEYS) { 082 if (key.equals(META_LINKS)) { 083 @SuppressWarnings("unchecked") 084 Collection<GpxLink> lValue = (Collection<GpxLink>) obj.getCollection(key); 085 if (lValue != null) { 086 for (GpxLink link : lValue) { 087 gpxLink(link); 088 } 089 } 090 } else if (key.equals(META_EXTENSIONS)) { 091 Extensions extensions = (Extensions) obj.get(key); 092 if (extensions != null) { 093 gpxExtensions(extensions); 094 } 095 } else { 096 String value = obj.getString(key); 097 if (value != null) { 098 simpleTag(key, value); 099 } 100 } 101 } 102 } 103 104 @SuppressWarnings("unchecked") 105 private void writeMetaData() { 106 Map<String, Object> attr = data.attr; 107 openln("metadata"); 108 109 // write the description 110 if (attr.containsKey(META_DESC)) { 111 simpleTag("desc", (String)attr.get(META_DESC)); 112 } 113 114 // write the author details 115 if (attr.containsKey(META_AUTHOR_NAME) 116 || attr.containsKey(META_AUTHOR_EMAIL)) { 117 openln("author"); 118 // write the name 119 simpleTag("name", (String) attr.get(META_AUTHOR_NAME)); 120 // write the email address 121 if (attr.containsKey(META_AUTHOR_EMAIL)) { 122 String[] tmp = ((String)attr.get(META_AUTHOR_EMAIL)).split("@"); 123 if (tmp.length == 2) { 124 inline("email", "id=\"" + tmp[0] + "\" domain=\""+tmp[1]+"\""); 125 } 126 } 127 // write the author link 128 gpxLink((GpxLink) attr.get(META_AUTHOR_LINK)); 129 closeln("author"); 130 } 131 132 // write the copyright details 133 if (attr.containsKey(META_COPYRIGHT_LICENSE) 134 || attr.containsKey(META_COPYRIGHT_YEAR)) { 135 openAtt("copyright", "author=\""+ attr.get(META_COPYRIGHT_AUTHOR) +"\""); 136 if (attr.containsKey(META_COPYRIGHT_YEAR)) { 137 simpleTag("year", (String) attr.get(META_COPYRIGHT_YEAR)); 138 } 139 if (attr.containsKey(META_COPYRIGHT_LICENSE)) { 140 simpleTag("license", encode((String) attr.get(META_COPYRIGHT_LICENSE))); 141 } 142 closeln("copyright"); 143 } 144 145 // write links 146 if (attr.containsKey(META_LINKS)) { 147 for (GpxLink link : (Collection<GpxLink>) attr.get(META_LINKS)) { 148 gpxLink(link); 149 } 150 } 151 152 // write keywords 153 if (attr.containsKey(META_KEYWORDS)) { 154 simpleTag("keywords", (String)attr.get(META_KEYWORDS)); 155 } 156 157 Bounds bounds = data.recalculateBounds(); 158 if (bounds != null) { 159 String b = "minlat=\"" + bounds.getMinLat() + "\" minlon=\"" + bounds.getMinLon() + 160 "\" maxlat=\"" + bounds.getMaxLat() + "\" maxlon=\"" + bounds.getMaxLon() + "\"" ; 161 inline("bounds", b); 162 } 163 164 if (data.fromServer) { 165 openln("extensions"); 166 simpleTag("josm:from-server", "true"); 167 closeln("extensions"); 168 } 169 170 closeln("metadata"); 171 } 172 173 private void writeWayPoints() { 174 for (WayPoint pnt : data.waypoints) { 175 wayPoint(pnt, WAY_POINT); 176 } 177 } 178 179 private void writeRoutes() { 180 for (GpxRoute rte : data.routes) { 181 openln("rte"); 182 writeAttr(rte); 183 for (WayPoint pnt : rte.routePoints) { 184 wayPoint(pnt, ROUTE_POINT); 185 } 186 closeln("rte"); 187 } 188 } 189 190 private void writeTracks() { 191 for (GpxTrack trk : data.tracks) { 192 openln("trk"); 193 writeAttr(trk); 194 for (GpxTrackSegment seg : trk.getSegments()) { 195 openln("trkseg"); 196 for (WayPoint pnt : seg.getWayPoints()) { 197 wayPoint(pnt, TRACK_POINT); 198 } 199 closeln("trkseg"); 200 } 201 closeln("trk"); 202 } 203 } 204 205 private void openln(String tag) { 206 open(tag); 207 out.println(); 208 } 209 210 private void open(String tag) { 211 out.print(indent + "<" + tag + ">"); 212 indent += " "; 213 } 214 215 private void openAtt(String tag, String attributes) { 216 out.println(indent + "<" + tag + " " + attributes + ">"); 217 indent += " "; 218 } 219 220 private void inline(String tag, String attributes) { 221 out.println(indent + "<" + tag + " " + attributes + "/>"); 222 } 223 224 private void close(String tag) { 225 indent = indent.substring(2); 226 out.print(indent + "</" + tag + ">"); 227 } 228 229 private void closeln(String tag) { 230 close(tag); 231 out.println(); 232 } 233 234 /** 235 * if content not null, open tag, write encoded content, and close tag 236 * else do nothing. 237 */ 238 private void simpleTag(String tag, String content) { 239 if (content != null && content.length() > 0) { 240 open(tag); 241 out.print(encode(content)); 242 out.println("</" + tag + ">"); 243 indent = indent.substring(2); 244 } 245 } 246 247 /** 248 * output link 249 */ 250 private void gpxLink(GpxLink link) { 251 if (link != null) { 252 openAtt("link", "href=\"" + link.uri + "\""); 253 simpleTag("text", link.text); 254 simpleTag("type", link.type); 255 closeln("link"); 256 } 257 } 258 259 /** 260 * output a point 261 */ 262 private void wayPoint(WayPoint pnt, int mode) { 263 String type; 264 switch(mode) { 265 case WAY_POINT: 266 type = "wpt"; 267 break; 268 case ROUTE_POINT: 269 type = "rtept"; 270 break; 271 case TRACK_POINT: 272 type = "trkpt"; 273 break; 274 default: 275 throw new RuntimeException(tr("Unknown mode {0}.", mode)); 276 } 277 if (pnt != null) { 278 LatLon c = pnt.getCoor(); 279 String coordAttr = "lat=\"" + c.lat() + "\" lon=\"" + c.lon() + "\""; 280 if (pnt.attr.isEmpty()) { 281 inline(type, coordAttr); 282 } else { 283 openAtt(type, coordAttr); 284 writeAttr(pnt); 285 closeln(type); 286 } 287 } 288 } 289 290 private void gpxExtensions(Extensions extensions) { 291 if (extensions != null && !extensions.isEmpty()) { 292 openln("extensions"); 293 for (Entry<String, String> e : extensions.entrySet()) { 294 simpleTag("josm:" + e.getKey(), e.getValue()); 295 } 296 closeln("extensions"); 297 } 298 } 299}