source: josm/trunk/src/org/openstreetmap/josm/io/GpxWriter.java@ 14243

Last change on this file since 14243 was 14243, checked in by Don-vip, 6 years ago

fix #16725 - numeric attributes of GPX waypoints were no longer exported (regression from #14103)

  • Property svn:eol-style set to native
File size: 10.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedWriter;
7import java.io.OutputStream;
8import java.io.OutputStreamWriter;
9import java.io.PrintWriter;
10import java.nio.charset.StandardCharsets;
11import java.text.DateFormat;
12import java.util.Collection;
13import java.util.Date;
14import java.util.List;
15import java.util.Map;
16import java.util.Map.Entry;
17
18import javax.xml.XMLConstants;
19
20import org.openstreetmap.josm.data.Bounds;
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.gpx.Extensions;
23import org.openstreetmap.josm.data.gpx.GpxConstants;
24import org.openstreetmap.josm.data.gpx.GpxData;
25import org.openstreetmap.josm.data.gpx.GpxLink;
26import org.openstreetmap.josm.data.gpx.GpxRoute;
27import org.openstreetmap.josm.data.gpx.GpxTrack;
28import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
29import org.openstreetmap.josm.data.gpx.IWithAttributes;
30import org.openstreetmap.josm.data.gpx.WayPoint;
31import org.openstreetmap.josm.tools.JosmRuntimeException;
32import org.openstreetmap.josm.tools.Logging;
33import org.openstreetmap.josm.tools.date.DateUtils;
34
35/**
36 * Writes GPX files from GPX data or OSM data.
37 */
38public class GpxWriter extends XmlWriter implements GpxConstants {
39
40 private final DateFormat gpxFormat = DateUtils.getGpxFormat();
41
42 /**
43 * Constructs a new {@code GpxWriter}.
44 * @param out The output writer
45 */
46 public GpxWriter(PrintWriter out) {
47 super(out);
48 }
49
50 /**
51 * Constructs a new {@code GpxWriter}.
52 * @param out The output stream
53 */
54 public GpxWriter(OutputStream out) {
55 super(new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))));
56 }
57
58 private GpxData data;
59 private String indent = "";
60
61 private static final int WAY_POINT = 0;
62 private static final int ROUTE_POINT = 1;
63 private static final int TRACK_POINT = 2;
64
65 /**
66 * Writes the given GPX data.
67 * @param data The data to write
68 */
69 public void write(GpxData data) {
70 this.data = data;
71 // We write JOSM specific meta information into gpx 'extensions' elements.
72 // In particular it is noted whether the gpx data is from the OSM server
73 // (so the rendering of clouds of anonymous TrackPoints can be improved)
74 // and some extra synchronization info for export of AudioMarkers.
75 // It is checked in advance, if any extensions are used, so we know whether
76 // a namespace declaration is necessary.
77 boolean hasExtensions = data.fromServer;
78 if (!hasExtensions) {
79 for (WayPoint wpt : data.waypoints) {
80 Extensions extensions = (Extensions) wpt.get(META_EXTENSIONS);
81 if (extensions != null && !extensions.isEmpty()) {
82 hasExtensions = true;
83 break;
84 }
85 }
86 }
87
88 out.println("<?xml version='1.0' encoding='UTF-8'?>");
89 out.println("<gpx version=\"1.1\" creator=\"JOSM GPX export\" xmlns=\"http://www.topografix.com/GPX/1/1\"");
90 out.println((hasExtensions ? String.format(" xmlns:josm=\"%s\"%n", JOSM_EXTENSIONS_NAMESPACE_URI) : "") +
91 " xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
92 out.println(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
93 indent = " ";
94 writeMetaData();
95 writeWayPoints();
96 writeRoutes();
97 writeTracks();
98 out.print("</gpx>");
99 out.flush();
100 }
101
102 private void writeAttr(IWithAttributes obj, List<String> keys) {
103 for (String key : keys) {
104 if (META_LINKS.equals(key)) {
105 Collection<GpxLink> lValue = obj.<GpxLink>getCollection(key);
106 if (lValue != null) {
107 for (GpxLink link : lValue) {
108 gpxLink(link);
109 }
110 }
111 } else if (META_EXTENSIONS.equals(key)) {
112 Extensions extensions = (Extensions) obj.get(key);
113 if (extensions != null) {
114 gpxExtensions(extensions);
115 }
116 } else {
117 String value = obj.getString(key);
118 if (value != null) {
119 simpleTag(key, value);
120 } else {
121 Object val = obj.get(key);
122 if (val instanceof Date) {
123 simpleTag(key, gpxFormat.format(val));
124 } else if (val instanceof Number) {
125 simpleTag(key, val.toString());
126 } else if (val != null) {
127 Logging.warn("GPX attribute '"+key+"' not managed: " + val);
128 }
129 }
130 }
131 }
132 }
133
134 private void writeMetaData() {
135 Map<String, Object> attr = data.attr;
136 openln("metadata");
137
138 // write the description
139 if (attr.containsKey(META_DESC)) {
140 simpleTag("desc", data.getString(META_DESC));
141 }
142
143 // write the author details
144 if (attr.containsKey(META_AUTHOR_NAME)
145 || attr.containsKey(META_AUTHOR_EMAIL)) {
146 openln("author");
147 // write the name
148 simpleTag("name", data.getString(META_AUTHOR_NAME));
149 // write the email address
150 if (attr.containsKey(META_AUTHOR_EMAIL)) {
151 String[] tmp = data.getString(META_AUTHOR_EMAIL).split("@");
152 if (tmp.length == 2) {
153 inline("email", "id=\"" + tmp[0] + "\" domain=\""+tmp[1]+'\"');
154 }
155 }
156 // write the author link
157 gpxLink((GpxLink) data.get(META_AUTHOR_LINK));
158 closeln("author");
159 }
160
161 // write the copyright details
162 if (attr.containsKey(META_COPYRIGHT_LICENSE)
163 || attr.containsKey(META_COPYRIGHT_YEAR)) {
164 openAtt("copyright", "author=\""+ data.get(META_COPYRIGHT_AUTHOR) +'\"');
165 if (attr.containsKey(META_COPYRIGHT_YEAR)) {
166 simpleTag("year", (String) data.get(META_COPYRIGHT_YEAR));
167 }
168 if (attr.containsKey(META_COPYRIGHT_LICENSE)) {
169 simpleTag("license", encode((String) data.get(META_COPYRIGHT_LICENSE)));
170 }
171 closeln("copyright");
172 }
173
174 // write links
175 if (attr.containsKey(META_LINKS)) {
176 for (GpxLink link : data.<GpxLink>getCollection(META_LINKS)) {
177 gpxLink(link);
178 }
179 }
180
181 // write keywords
182 if (attr.containsKey(META_KEYWORDS)) {
183 simpleTag("keywords", data.getString(META_KEYWORDS));
184 }
185
186 Bounds bounds = data.recalculateBounds();
187 if (bounds != null) {
188 String b = "minlat=\"" + bounds.getMinLat() + "\" minlon=\"" + bounds.getMinLon() +
189 "\" maxlat=\"" + bounds.getMaxLat() + "\" maxlon=\"" + bounds.getMaxLon() + '\"';
190 inline("bounds", b);
191 }
192
193 if (data.fromServer) {
194 openln("extensions");
195 simpleTag("josm:from-server", "true");
196 closeln("extensions");
197 }
198
199 closeln("metadata");
200 }
201
202 private void writeWayPoints() {
203 for (WayPoint pnt : data.getWaypoints()) {
204 wayPoint(pnt, WAY_POINT);
205 }
206 }
207
208 private void writeRoutes() {
209 for (GpxRoute rte : data.getRoutes()) {
210 openln("rte");
211 writeAttr(rte, RTE_TRK_KEYS);
212 for (WayPoint pnt : rte.routePoints) {
213 wayPoint(pnt, ROUTE_POINT);
214 }
215 closeln("rte");
216 }
217 }
218
219 private void writeTracks() {
220 for (GpxTrack trk : data.getTracks()) {
221 openln("trk");
222 writeAttr(trk, RTE_TRK_KEYS);
223 for (GpxTrackSegment seg : trk.getSegments()) {
224 openln("trkseg");
225 for (WayPoint pnt : seg.getWayPoints()) {
226 wayPoint(pnt, TRACK_POINT);
227 }
228 closeln("trkseg");
229 }
230 closeln("trk");
231 }
232 }
233
234 private void openln(String tag) {
235 open(tag);
236 out.println();
237 }
238
239 private void open(String tag) {
240 out.print(indent + '<' + tag + '>');
241 indent += " ";
242 }
243
244 private void openAtt(String tag, String attributes) {
245 out.println(indent + '<' + tag + ' ' + attributes + '>');
246 indent += " ";
247 }
248
249 private void inline(String tag, String attributes) {
250 out.println(indent + '<' + tag + ' ' + attributes + "/>");
251 }
252
253 private void close(String tag) {
254 indent = indent.substring(2);
255 out.print(indent + "</" + tag + '>');
256 }
257
258 private void closeln(String tag) {
259 close(tag);
260 out.println();
261 }
262
263 /**
264 * if content not null, open tag, write encoded content, and close tag
265 * else do nothing.
266 * @param tag GPX tag
267 * @param content content
268 */
269 private void simpleTag(String tag, String content) {
270 if (content != null && !content.isEmpty()) {
271 open(tag);
272 out.print(encode(content));
273 out.println("</" + tag + '>');
274 indent = indent.substring(2);
275 }
276 }
277
278 /**
279 * output link
280 * @param link link
281 */
282 private void gpxLink(GpxLink link) {
283 if (link != null) {
284 openAtt("link", "href=\"" + link.uri + '\"');
285 simpleTag("text", link.text);
286 simpleTag("type", link.type);
287 closeln("link");
288 }
289 }
290
291 /**
292 * output a point
293 * @param pnt waypoint
294 * @param mode {@code WAY_POINT} for {@code wpt}, {@code ROUTE_POINT} for {@code rtept}, {@code TRACK_POINT} for {@code trkpt}
295 */
296 private void wayPoint(WayPoint pnt, int mode) {
297 String type;
298 switch(mode) {
299 case WAY_POINT:
300 type = "wpt";
301 break;
302 case ROUTE_POINT:
303 type = "rtept";
304 break;
305 case TRACK_POINT:
306 type = "trkpt";
307 break;
308 default:
309 throw new JosmRuntimeException(tr("Unknown mode {0}.", mode));
310 }
311 if (pnt != null) {
312 LatLon c = pnt.getCoor();
313 String coordAttr = "lat=\"" + c.lat() + "\" lon=\"" + c.lon() + '\"';
314 if (pnt.attr.isEmpty()) {
315 inline(type, coordAttr);
316 } else {
317 openAtt(type, coordAttr);
318 writeAttr(pnt, WPT_KEYS);
319 closeln(type);
320 }
321 }
322 }
323
324 private void gpxExtensions(Extensions extensions) {
325 if (extensions != null && !extensions.isEmpty()) {
326 openln("extensions");
327 for (Entry<String, String> e : extensions.entrySet()) {
328 simpleTag("josm:" + e.getKey(), e.getValue());
329 }
330 closeln("extensions");
331 }
332 }
333}
Note: See TracBrowser for help on using the repository browser.