Index: trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 4885)
+++ trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 4886)
@@ -62,5 +62,6 @@
                 "org.openstreetmap.josm.io.OsmExporter",
                 "org.openstreetmap.josm.io.OsmGzipExporter",
-                "org.openstreetmap.josm.io.OsmBzip2Exporter"
+                "org.openstreetmap.josm.io.OsmBzip2Exporter",
+                "org.openstreetmap.josm.io.GeoJSONExporter",
         };
 
Index: trunk/src/org/openstreetmap/josm/io/GeoJSONExporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/GeoJSONExporter.java	(revision 4886)
+++ trunk/src/org/openstreetmap/josm/io/GeoJSONExporter.java	(revision 4886)
@@ -0,0 +1,29 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class GeoJSONExporter extends FileExporter {
+
+    public GeoJSONExporter() {
+        super(new ExtensionFileFilter("json,geojson", "json", tr("GeoJSON Files") + " (*.json *.geojson)"));
+    }
+
+    @Override
+    public void exportData(File file, Layer layer) throws IOException {
+        if (layer instanceof OsmDataLayer) {
+            String json = new GeoJSONWriter((OsmDataLayer) layer).write();
+            FileWriter out = new FileWriter(file);
+            out.write(json);
+            out.close();
+        } else {
+            throw new IllegalArgumentException(tr("Layer ''{}'' not supported", layer.getClass().toString()));
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/io/GeoJSONWriter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/GeoJSONWriter.java	(revision 4886)
+++ trunk/src/org/openstreetmap/josm/io/GeoJSONWriter.java	(revision 4886)
@@ -0,0 +1,108 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+public class GeoJSONWriter implements Visitor {
+
+    private OsmDataLayer layer;
+    private StringBuilder out;
+    private final boolean skipEmptyNodes = true;
+    private boolean insertComma = false;
+
+    public GeoJSONWriter(OsmDataLayer layer) {
+        this.layer = layer;
+    }
+
+    public String write() {
+        out = new StringBuilder(1 << 12);
+        out.append("{\"type\": \"FeatureCollection\",\n");
+        out.append("\"features\": [\n");
+        for (Node n : layer.data.getNodes()) {
+            appendPrimitive(n);
+        }
+        for (Way w : layer.data.getWays()) {
+            appendPrimitive(w);
+        }
+        out.append("\n]\n}");
+        return out.toString();
+    }
+
+    @Override
+    public void visit(Node n) {
+        out.append("\"type\": \"Point\", \"coordinates\": ");
+        appendCoord(n.getCoor());
+    }
+
+    @Override
+    public void visit(Way w) {
+        out.append("\"type\": \"LineString\", \"coordinates\": [");
+        boolean insertCommaCoords = false;
+        for (Node n : w.getNodes()) {
+            if (insertCommaCoords) {
+                out.append(", ");
+            }
+            insertCommaCoords = true;
+            appendCoord(n.getCoor());
+        }
+        out.append("]");
+    }
+
+    @Override
+    public void visit(Relation e) {
+    }
+
+    @Override
+    public void visit(Changeset cs) {
+    }
+
+    protected String escape(String s) {
+        return s.replace("\"", "\\\"").replace("'", "\\'").replace("\n", "\\n");
+    }
+
+    protected void appendPrimitive(OsmPrimitive p) {
+        if (p.isIncomplete()) {
+            return;
+        } else if (skipEmptyNodes && p instanceof Node && p.getKeys().isEmpty()) {
+            return;
+        }
+        if (insertComma) {
+            out.append(",\n");
+        }
+        insertComma = true;
+        out.append("{\"type\": \"Feature\",\n");
+        Map<String, String> tags = p.getKeys();
+        if (!tags.isEmpty()) {
+            out.append("\t\"properties\": {\n");
+            boolean insertCommaTags = false;
+            for (Entry<String, String> t : tags.entrySet()) {
+                if (insertCommaTags) {
+                    out.append(",\n");
+                }
+                insertCommaTags = true;
+                out.append("\t\t\"").append(escape(t.getKey())).append("\": ");
+                out.append("\"").append(escape(t.getValue())).append("\"");
+            }
+            out.append("\n\t},\n");
+        }
+        { // append primitive specific
+            out.append("\t\"geometry\": {");
+            p.visit(this);
+            out.append("}");
+        }
+        out.append("}");
+    }
+
+    protected void appendCoord(LatLon c) {
+        out.append("[").append(c.lon()).append(", ").append(c.lat()).append("]");
+    }
+}
