001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.io;
003    
004    import java.util.Map;
005    import java.util.Map.Entry;
006    import org.openstreetmap.josm.data.coor.LatLon;
007    import org.openstreetmap.josm.data.osm.Changeset;
008    import org.openstreetmap.josm.data.osm.Node;
009    import org.openstreetmap.josm.data.osm.OsmPrimitive;
010    import org.openstreetmap.josm.data.osm.Relation;
011    import org.openstreetmap.josm.data.osm.Way;
012    import org.openstreetmap.josm.data.osm.visitor.Visitor;
013    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
014    
015    public class GeoJSONWriter implements Visitor {
016    
017        private OsmDataLayer layer;
018        private StringBuilder out;
019        private final boolean skipEmptyNodes = true;
020        private boolean insertComma = false;
021    
022        public GeoJSONWriter(OsmDataLayer layer) {
023            this.layer = layer;
024        }
025    
026        public String write() {
027            out = new StringBuilder(1 << 12);
028            out.append("{\"type\": \"FeatureCollection\",\n");
029            out.append("\"features\": [\n");
030            for (Node n : layer.data.getNodes()) {
031                appendPrimitive(n);
032            }
033            for (Way w : layer.data.getWays()) {
034                appendPrimitive(w);
035            }
036            out.append("\n]\n}");
037            return out.toString();
038        }
039    
040        @Override
041        public void visit(Node n) {
042            out.append("\"type\": \"Point\", \"coordinates\": ");
043            appendCoord(n.getCoor());
044        }
045    
046        @Override
047        public void visit(Way w) {
048            out.append("\"type\": \"LineString\", \"coordinates\": [");
049            boolean insertCommaCoords = false;
050            for (Node n : w.getNodes()) {
051                if (insertCommaCoords) {
052                    out.append(", ");
053                }
054                insertCommaCoords = true;
055                appendCoord(n.getCoor());
056            }
057            out.append("]");
058        }
059    
060        @Override
061        public void visit(Relation e) {
062        }
063    
064        @Override
065        public void visit(Changeset cs) {
066        }
067    
068        protected String escape(String s) {
069            return s.replace("\"", "\\\"").replace("\\", "\\\\").replace("\n", "\\n");
070        }
071    
072        protected void appendPrimitive(OsmPrimitive p) {
073            if (p.isIncomplete()) {
074                return;
075            } else if (skipEmptyNodes && p instanceof Node && p.getKeys().isEmpty()) {
076                return;
077            }
078            if (insertComma) {
079                out.append(",\n");
080            }
081            insertComma = true;
082            out.append("{\"type\": \"Feature\",\n");
083            Map<String, String> tags = p.getKeys();
084            if (!tags.isEmpty()) {
085                out.append("\t\"properties\": {\n");
086                boolean insertCommaTags = false;
087                for (Entry<String, String> t : tags.entrySet()) {
088                    if (insertCommaTags) {
089                        out.append(",\n");
090                    }
091                    insertCommaTags = true;
092                    out.append("\t\t\"").append(escape(t.getKey())).append("\": ");
093                    out.append("\"").append(escape(t.getValue())).append("\"");
094                }
095                out.append("\n\t},\n");
096            }
097            { // append primitive specific
098                out.append("\t\"geometry\": {");
099                p.visit(this);
100                out.append("}");
101            }
102            out.append("}");
103        }
104    
105        protected void appendCoord(LatLon c) {
106            if (c != null) {
107                out.append("[").append(c.lon()).append(", ").append(c.lat()).append("]");
108            }
109        }
110    }