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