diff --git a/examples/geojson.html b/examples/geojson.html
new file mode 100644
index 0000000000..b4c2b25192
--- /dev/null
+++ b/examples/geojson.html
@@ -0,0 +1,168 @@
+
+
+ Vector Formats
+
+
+
+
+
+
+
+
OpenLayers Vector Formats Example
+
+
+
+
+
Use the tools to the left to draw new polygons, lines, and points.
+ After drawing some new features, hover over a feature to see the
+ serialized version below.
+
+
+
+
diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js
index 8a0263708d..41b65b9864 100644
--- a/lib/OpenLayers.js
+++ b/lib/OpenLayers.js
@@ -166,6 +166,8 @@
"OpenLayers/Format/GeoRSS.js",
"OpenLayers/Format/WFS.js",
"OpenLayers/Format/WKT.js",
+ "OpenLayers/Format/JSON.js",
+ "OpenLayers/Format/GeoJSON.js",
"OpenLayers/Layer/WFS.js",
"OpenLayers/Control/MouseToolbar.js",
"OpenLayers/Control/NavToolbar.js",
diff --git a/lib/OpenLayers/Format/GeoJSON.js b/lib/OpenLayers/Format/GeoJSON.js
new file mode 100644
index 0000000000..0b90b05035
--- /dev/null
+++ b/lib/OpenLayers/Format/GeoJSON.js
@@ -0,0 +1,644 @@
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/JSON.js
+ *
+ * Class: OpenLayers.Format.GeoJSON
+ * Read and write GeoJSON. Create a new parser with the
+ * constructor.
+ *
+ * Inherits from:
+ * -
+ */
+OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
+
+ /**
+ * Constructor: OpenLayers.Format.GeoJSON
+ * Create a new parser for GeoJSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.JSON.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize a GeoJSON string.
+ *
+ * Parameters:
+ * json - {String} A GeoJSON string
+ * type - {String} Optional string that determines the structure of
+ * the output. Supported values are "Geometry", "Feature",
+ * "GeometryCollection", and "FeatureCollection". If absent or null,
+ * a default of "FeatureCollection" is assumed.
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} The return depends on the value of the type argument. If type
+ * is "FeatureCollection" (the default), the return will be an array
+ * of . If type is "Geometry", the input json
+ * must represent a single geometry, and the return will be an
+ * . If type is "Feature", the input json must
+ * represent a single feature, and the return will be an
+ * . If type is "GeometryCollection", the
+ * input json must represent a geometry collection, and the return will
+ * be an array of .
+ */
+ read: function(json, type, filter) {
+ type = (type) ? type : "FeatureCollection";
+ var results = null;
+ var obj = null;
+ if (typeof json == "string") {
+ obj = OpenLayers.Format.JSON.prototype.read.apply(this,
+ [json, filter]);
+ } else {
+ obj = json;
+ }
+ if(!obj) {
+ OpenLayers.Console.error("Bad JSON: " + json);
+ } else if(typeof(obj.type) != "string") {
+ OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
+ } else if(this.isValidType(obj, type)) {
+ switch(type) {
+ case "Geometry":
+ try {
+ results = this.parseGeometry(obj);
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "Feature":
+ try {
+ results = this.parseFeature(obj);
+ results.type = "Feature";
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "GeometryCollection":
+ results = [];
+ for(var i=0; i.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {} A feature.
+ */
+ parseFeature: function(obj) {
+ var feature, geometry, attributes;
+ attributes = (obj.properties) ? obj.properties : {};
+ try {
+ geometry = this.parseGeometry(obj.geometry);
+ } catch(err) {
+ // deal with bad geometries
+ throw err;
+ }
+ feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ if(obj.id) {
+ feature.fid = obj.id;
+ }
+ return feature;
+ },
+
+ /**
+ * Method: parseGeometry
+ * Convert a geometry object from GeoJSON into an .
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ parseGeometry: function(obj) {
+ var geometry;
+ if(!(obj.coordinates instanceof Array)) {
+ throw "Geometry must have coordinates array: " + obj;
+ }
+ if(!this.parseCoords[obj.type.toLowerCase()]) {
+ throw "Unsupported geometry type: " + obj.type;
+ }
+ try {
+ geometry = this.parseCoords[obj.type.toLowerCase()].apply(this, [obj.coordinates]);
+ } catch(err) {
+ // deal with bad coordinates
+ throw err;
+ }
+ return geometry;
+ },
+
+ /**
+ * Property: parseCoords
+ * Object with properties corresponding to the GeoJSON geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parseCoords: {
+ /**
+ * Method: parseCoords.point
+ * Convert a coordinate array from GeoJSON into an
+ * .
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ "point": function(array) {
+ if(array.length != 2) {
+ throw "Only 2D points are supported: " + array;
+ }
+ return new OpenLayers.Geometry.Point(array[0], array[1]);
+ },
+
+ /**
+ * Method: parseCoords.multipoint
+ * Convert a coordinate array from GeoJSON into an
+ * .
+ *
+ * Parameters:
+ * array {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ "multipoint": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0; i.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ "linestring": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0; i.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ "multilinestring": function(array) {
+ var lines = [];
+ var l = null;
+ for(var i=0; i.
+ *
+ * Returns:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ "polygon": function(array) {
+ var rings = [];
+ var r, l;
+ for(var i=0; i.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ "multipolygon": function(array) {
+ var polys = [];
+ var p = null;
+ for(var i=0; i.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {} A geometry.
+ */
+ "box": function(array) {
+ if(array.length != 2) {
+ throw "GeoJSON box coordinates must have 2 elements";
+ }
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1])
+ ])
+ ]);
+ }
+
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature, geometry, array of features, or array of geometries
+ * into a GeoJSON string.
+ *
+ * Parameters:
+ * obj - {Object} An , ,
+ * or an array of either features or geometries.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The GeoJSON string representation of the input geometry,
+ * features, array of geometries, or array of features.
+ */
+ write: function(obj, pretty) {
+ var geojson = {
+ "type": null
+ };
+ if(obj instanceof Array) {
+ geojson.members = [];
+ for(var i=0; i}
+ *
+ * Returns:
+ * {Object} An object representing the point.
+ */
+ 'feature': function(feature) {
+ var geom = this.extract.geometry.apply(this, [feature.geometry]);
+ return {
+ "type": "Feature",
+ "id": feature.fid == null ? feature.id : feature.fid,
+ "properties": feature.attributes,
+ "geometry": geom
+ }
+ },
+
+ /**
+ * Method: extract.geometry
+ * Return a GeoJSON object representing a single geometry.
+ *
+ * Parameters:
+ * geometry - {}
+ *
+ * Returns:
+ * {Object} An object representing the geometry.
+ */
+ 'geometry': function(geometry) {
+ var geometryType = geometry.CLASS_NAME.split('.')[2];
+ var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
+ return {
+ "type": geometryType,
+ "coordinates": data
+ }
+ },
+
+ /**
+ * Method: extract.poin
+ * Return an array of coordinates from a point.
+ *
+ * Parameters:
+ * point - {}
+ *
+ * Returns:
+ * {Array} An array of coordinates representing the point.
+ */
+ 'point': function(point) {
+ return [point.x, point.y];
+ },
+
+ /**
+ * Method: extract.multipoint
+ * Return an array of point coordinates from a multipoint.
+ *
+ * Parameters:
+ * multipoint - {}
+ *
+ * Returns:
+ * {Array} An array of point coordinate arrays representing
+ * the multipoint.
+ */
+ 'multipoint': function(multipoint) {
+ var array = [];
+ for(var i=0; i}
+ *
+ * Returns:
+ * {Array} An array of coordinate arrays representing
+ * the linestring.
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0; i}
+ *
+ * Returns:
+ * {Array} An array of linestring arrays representing
+ * the multilinestring.
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0; i}
+ *
+ * Returns:
+ * {Array} An array of linear ring arrays representing the polygon.
+ */
+ 'polygon': function(polygon) {
+ var array = [];
+ for(var i=0; i}
+ *
+ * Returns:
+ * {Array} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0; i constructor.
+ *
+ * Inherits from:
+ * -
+ */
+OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: indent
+ * {String} For "pretty" printing, the indent string will be used once for
+ * each indentation level.
+ */
+ indent: " ",
+
+ /**
+ * APIProperty: space
+ * {String} For "pretty" printing, the space string will be used after
+ * the ":" separating a name/value pair.
+ */
+ space: " ",
+
+ /**
+ * APIProperty: newline
+ * {String} For "pretty" printing, the newline string will be used at the
+ * end of each name/value pair or array item.
+ */
+ newline: "\n",
+
+ /**
+ * Property: level
+ * {Integer} For "pretty" printing, this is incremented/decremented during
+ * serialization.
+ */
+ level: 0,
+
+ /**
+ * Property: pretty
+ * {Boolean} Serialize with extra whitespace for structure. This is set
+ * by the method.
+ */
+ pretty: false,
+
+ /**
+ * Constructor: OpenLayers.Format.JSON
+ * Create a new parser for JSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize a json string.
+ *
+ * Parameters:
+ * json - {String} A JSON string
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} An object, array, string, or number .
+ */
+ read: function(json, filter) {
+ /**
+ * Parsing happens in three stages. In the first stage, we run the text
+ * against a regular expression which looks for non-JSON
+ * characters. We are especially concerned with '()' and 'new'
+ * because they can cause invocation, and '=' because it can cause
+ * mutation. But just to be safe, we will reject all unexpected
+ * characters.
+ */
+ try {
+ if(/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.
+ test(json)) {
+
+ /**
+ * In the second stage we use the eval function to compile the
+ * text into a JavaScript structure. The '{' operator is
+ * subject to a syntactic ambiguity in JavaScript - it can
+ * begin a block or an object literal. We wrap the text in
+ * parens to eliminate the ambiguity.
+ */
+ var object = eval('(' + json + ')');
+
+ /**
+ * In the optional third stage, we recursively walk the new
+ * structure, passing each name/value pair to a filter
+ * function for possible transformation.
+ */
+ if(typeof filter === 'function') {
+ function walk(k, v) {
+ if(v && typeof v === 'object') {
+ for(var i in v) {
+ if(v.hasOwnProperty(i)) {
+ v[i] = walk(i, v[i]);
+ }
+ }
+ }
+ return filter(k, v);
+ }
+ object = walk('', object);
+ }
+ return object;
+ }
+ } catch(e) {
+ // Fall through if the regexp test fails.
+ }
+ return null;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize an object into a JSON string.
+ *
+ * Parameters:
+ * value - {String} The object, array, string, number, boolean or date
+ * to be serialized.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The JSON string representation of the input value.
+ */
+ write: function(value, pretty) {
+ this.pretty = !!pretty;
+ var json = null;
+ var type = typeof value;
+ if(this.serialize[type]) {
+ json = this.serialize[type].apply(this, [value]);
+ }
+ return json;
+ },
+
+ /**
+ * Method: writeIndent
+ * Output an indentation string depending on the indentation level.
+ *
+ * Returns:
+ * {String} An appropriate indentation string.
+ */
+ writeIndent: function() {
+ var pieces = [];
+ if(this.pretty) {
+ for(var i=0; i 0) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(), json);
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), ']');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.string
+ * Transform a string into a JSON string.
+ *
+ * Parameters
+ * string - {String} The string to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the string.
+ */
+ 'string': function(string) {
+ // If the string contains no control characters, no quote characters, and no
+ // backslash characters, then we can simply slap some quotes around it.
+ // Otherwise we must also replace the offending characters with safe
+ // sequences.
+ var m = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+ if(/["\\\x00-\x1f]/.test(string)) {
+ return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c) {
+ return c;
+ }
+ c = b.charCodeAt();
+ return '\\u00' +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + string + '"';
+ },
+
+ /**
+ * Method: serialize.number
+ * Transform a number into a JSON string.
+ *
+ * Parameters:
+ * number - {Number} The number to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the number.
+ */
+ 'number': function(number) {
+ return isFinite(number) ? String(number) : "null";
+ },
+
+ /**
+ * Method: serialize.boolean
+ * Transform a boolean into a JSON string.
+ *
+ * Parameters:
+ * bool - {Boolean} The boolean to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the boolean.
+ */
+ 'boolean': function(bool) {
+ return String(bool);
+ },
+
+ /**
+ * Method: serialize.object
+ * Transform a date into a JSON string.
+ *
+ * Parameters:
+ * date - {Date} The date to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the date.
+ */
+ 'date': function(date) {
+ function format(number) {
+ // Format integers to have at least two digits.
+ return (number < 10) ? '0' + number : number;
+ }
+ return '"' + date.getFullYear() + '-' +
+ format(date.getMonth() + 1) + '-' +
+ format(date.getDate()) + 'T' +
+ format(date.getHours()) + ':' +
+ format(date.getMinutes()) + ':' +
+ format(date.getSeconds()) + '"';
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.JSON"
+
+});
diff --git a/tests/Format/test_GeoJSON.html b/tests/Format/test_GeoJSON.html
new file mode 100644
index 0000000000..50e8527c0b
--- /dev/null
+++ b/tests/Format/test_GeoJSON.html
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+
diff --git a/tests/Format/test_JSON.html b/tests/Format/test_JSON.html
new file mode 100644
index 0000000000..d9520e9eb9
--- /dev/null
+++ b/tests/Format/test_JSON.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
diff --git a/tests/list-tests.html b/tests/list-tests.html
index 177ce47a97..61b681489b 100644
--- a/tests/list-tests.html
+++ b/tests/list-tests.html
@@ -22,6 +22,8 @@
test_Format.html
Format/test_XML.html
Format/test_GeoRSS.html
+ Format/test_JSON.html
+ Format/test_GeoJSON.html
Format/test_GML.html
Format/test_WKT.html
test_Icon.html