001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.io;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.io.BufferedWriter;
007    import java.io.OutputStream;
008    import java.io.OutputStreamWriter;
009    import java.io.PrintWriter;
010    import java.io.UnsupportedEncodingException;
011    import java.util.Arrays;
012    import java.util.Collection;
013    import java.util.List;
014    import java.util.Map;
015    
016    import org.openstreetmap.josm.data.Bounds;
017    import org.openstreetmap.josm.data.coor.LatLon;
018    import org.openstreetmap.josm.data.gpx.GpxData;
019    import org.openstreetmap.josm.data.gpx.GpxLink;
020    import org.openstreetmap.josm.data.gpx.GpxRoute;
021    import org.openstreetmap.josm.data.gpx.GpxTrack;
022    import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
023    import org.openstreetmap.josm.data.gpx.WayPoint;
024    
025    /**
026     * Writes GPX files from GPX data or OSM data.
027     */
028    public class GpxWriter extends XmlWriter {
029    
030        public GpxWriter(PrintWriter out) {
031            super(out);
032        }
033    
034        public GpxWriter(OutputStream out) throws UnsupportedEncodingException {
035            super(new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))));
036        }
037    
038        public GpxWriter() {
039            super(null);
040            //sorry for this one here, this will be cleaned up once the new scheme works
041        }
042    
043        private GpxData data;
044        private String indent = "";
045    
046        private final static int WAY_POINT = 0;
047        private final static int ROUTE_POINT = 1;
048        private final static int TRACK_POINT = 2;
049    
050        public void write(GpxData data) {
051            this.data = data;
052            out.println("<?xml version='1.0' encoding='UTF-8'?>");
053            out.println("<gpx version=\"1.1\" creator=\"JOSM GPX export\" xmlns=\"http://www.topografix.com/GPX/1/1\"\n" +
054                    "    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n" +
055            "    xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
056            indent = "  ";
057            writeMetaData();
058            writeWayPoints();
059            writeRoutes();
060            writeTracks();
061            out.print("</gpx>");
062            out.flush();
063        }
064    
065        public static List<String> WPT_KEYS = Arrays.asList("ele", "time", "magvar", "geoidheight",
066                "name", "cmt", "desc", "src", GpxData.META_LINKS, "sym", "number", "type",
067                "fix", "sat", "hdop", "vdop", "pdop", "ageofdgpsdata", "dgpsid");
068        @SuppressWarnings("unchecked")
069        private void writeAttr(Map<String, Object> attr) {
070            for (String key : WPT_KEYS) {
071                Object value = attr.get(key);
072                if (value != null) {
073                    if (key.equals(GpxData.META_LINKS)) {
074                        for (GpxLink link : (Collection<GpxLink>) value) {
075                            gpxLink(link);
076                        }
077                    } else {
078                        simpleTag(key, value.toString());
079                    }
080                }
081            }
082        }
083    
084        @SuppressWarnings("unchecked")
085        private void writeMetaData() {
086            Map<String, Object> attr = data.attr;
087            openln("metadata");
088    
089            // write the description
090            if (attr.containsKey(GpxData.META_DESC)) {
091                simpleTag("desc", (String)attr.get(GpxData.META_DESC));
092            }
093    
094            // write the author details
095            if (attr.containsKey(GpxData.META_AUTHOR_NAME)
096                    || attr.containsKey(GpxData.META_AUTHOR_EMAIL)) {
097                openln("author");
098                // write the name
099                simpleTag("name", (String) attr.get(GpxData.META_AUTHOR_NAME));
100                // write the email address
101                if(attr.containsKey(GpxData.META_AUTHOR_EMAIL)) {
102                    String[] tmp = ((String)attr.get(GpxData.META_AUTHOR_EMAIL)).split("@");
103                    if(tmp.length == 2) {
104                        inline("email", "id=\"" + tmp[0] + "\" domain=\""+tmp[1]+"\"");
105                    }
106                }
107                // write the author link
108                gpxLink((GpxLink) attr.get(GpxData.META_AUTHOR_LINK));
109                closeln("author");
110            }
111    
112            // write the copyright details
113            if(attr.containsKey(GpxData.META_COPYRIGHT_LICENSE)
114                    || attr.containsKey(GpxData.META_COPYRIGHT_YEAR)) {
115                openAtt("copyright", "author=\""+ attr.get(GpxData.META_COPYRIGHT_AUTHOR) +"\"");
116                if(attr.containsKey(GpxData.META_COPYRIGHT_YEAR)) {
117                    simpleTag("year", (String) attr.get(GpxData.META_COPYRIGHT_YEAR));
118                }
119                if(attr.containsKey(GpxData.META_COPYRIGHT_LICENSE)) {
120                    simpleTag("license", encode((String) attr.get(GpxData.META_COPYRIGHT_LICENSE)));
121                }
122                closeln("copyright");
123            }
124    
125            // write links
126            if(attr.containsKey(GpxData.META_LINKS)) {
127                for (GpxLink link : (Collection<GpxLink>) attr.get(GpxData.META_LINKS)) {
128                    gpxLink(link);
129                }
130            }
131    
132            // write keywords
133            if (attr.containsKey(GpxData.META_KEYWORDS)) {
134                simpleTag("keywords", (String)attr.get(GpxData.META_KEYWORDS));
135            }
136    
137            Bounds bounds = data.recalculateBounds();
138            if(bounds != null)
139            {
140                String b = "minlat=\"" + bounds.getMin().lat() + "\" minlon=\"" + bounds.getMin().lon() +
141                "\" maxlat=\"" + bounds.getMax().lat() + "\" maxlon=\"" + bounds.getMax().lon() + "\"" ;
142                inline("bounds", b);
143            }
144    
145            closeln("metadata");
146        }
147    
148        private void writeWayPoints() {
149            for (WayPoint pnt : data.waypoints) {
150                wayPoint(pnt, WAY_POINT);
151            }
152        }
153    
154        private void writeRoutes() {
155            for (GpxRoute rte : data.routes) {
156                openln("rte");
157                writeAttr(rte.attr);
158                for (WayPoint pnt : rte.routePoints) {
159                    wayPoint(pnt, ROUTE_POINT);
160                }
161                closeln("rte");
162            }
163        }
164    
165        private void writeTracks() {
166            for (GpxTrack trk : data.tracks) {
167                openln("trk");
168                writeAttr(trk.getAttributes());
169                for (GpxTrackSegment seg : trk.getSegments()) {
170                    openln("trkseg");
171                    for (WayPoint pnt : seg.getWayPoints()) {
172                        wayPoint(pnt, TRACK_POINT);
173                    }
174                    closeln("trkseg");
175                }
176                closeln("trk");
177            }
178        }
179    
180        private void openln(String tag) {
181            open(tag);
182            out.println();
183        }
184    
185        private void open(String tag) {
186            out.print(indent + "<" + tag + ">");
187            indent += "  ";
188        }
189    
190        private void openAtt(String tag, String attributes) {
191            out.println(indent + "<" + tag + " " + attributes + ">");
192            indent += "  ";
193        }
194    
195        private void inline(String tag, String attributes) {
196            out.println(indent + "<" + tag + " " + attributes + "/>");
197        }
198    
199        private void close(String tag) {
200            indent = indent.substring(2);
201            out.print(indent + "</" + tag + ">");
202        }
203    
204        private void closeln(String tag) {
205            close(tag);
206            out.println();
207        }
208    
209        /**
210         * if content not null, open tag, write encoded content, and close tag
211         * else do nothing.
212         */
213        private void simpleTag(String tag, String content) {
214            if (content != null && content.length() > 0) {
215                open(tag);
216                out.print(encode(content));
217                out.println("</" + tag + ">");
218                indent = indent.substring(2);
219            }
220        }
221    
222        /**
223         * output link
224         */
225        private void gpxLink(GpxLink link) {
226            if (link != null) {
227                openAtt("link", "href=\"" + link.uri + "\"");
228                simpleTag("text", link.text);
229                simpleTag("type", link.type);
230                closeln("link");
231            }
232        }
233    
234        /**
235         * output a point
236         */
237        private void wayPoint(WayPoint pnt, int mode) {
238            String type;
239            switch(mode) {
240            case WAY_POINT:
241                type = "wpt";
242                break;
243            case ROUTE_POINT:
244                type = "rtept";
245                break;
246            case TRACK_POINT:
247                type = "trkpt";
248                break;
249            default:
250                throw new RuntimeException(tr("Unknown mode {0}.", mode));
251            }
252            if (pnt != null) {
253                LatLon c = pnt.getCoor();
254                String coordAttr = "lat=\"" + c.lat() + "\" lon=\"" + c.lon() + "\"";
255                if (pnt.attr.isEmpty()) {
256                    inline(type, coordAttr);
257                } else {
258                    openAtt(type, coordAttr);
259                    writeAttr(pnt.attr);
260                    closeln(type);
261                }
262            }
263        }
264    }