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