]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Format/GeoJSON.js
fixes notices
[syp.git] / openlayers / lib / OpenLayers / Format / GeoJSON.js
1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2  * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3  * full text of the license. */
4
5 /**
6  * @requires OpenLayers/Format/JSON.js
7  * @requires OpenLayers/Feature/Vector.js
8  * @requires OpenLayers/Geometry/Point.js
9  * @requires OpenLayers/Geometry/MultiPoint.js
10  * @requires OpenLayers/Geometry/LineString.js
11  * @requires OpenLayers/Geometry/MultiLineString.js
12  * @requires OpenLayers/Geometry/Polygon.js
13  * @requires OpenLayers/Geometry/MultiPolygon.js
14  * @requires OpenLayers/Console.js
15  */
16
17 /**
18  * Class: OpenLayers.Format.GeoJSON
19  * Read and write GeoJSON. Create a new parser with the
20  *     <OpenLayers.Format.GeoJSON> constructor.
21  *
22  * Inherits from:
23  *  - <OpenLayers.Format.JSON>
24  */
25 OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
26
27     /**
28      * Constructor: OpenLayers.Format.GeoJSON
29      * Create a new parser for GeoJSON.
30      *
31      * Parameters:
32      * options - {Object} An optional object whose properties will be set on
33      *     this instance.
34      */
35     initialize: function(options) {
36         OpenLayers.Format.JSON.prototype.initialize.apply(this, [options]);
37     },
38
39     /**
40      * APIMethod: read
41      * Deserialize a GeoJSON string.
42      *
43      * Parameters:
44      * json - {String} A GeoJSON string
45      * type - {String} Optional string that determines the structure of
46      *     the output.  Supported values are "Geometry", "Feature", and
47      *     "FeatureCollection".  If absent or null, a default of
48      *     "FeatureCollection" is assumed.
49      * filter - {Function} A function which will be called for every key and
50      *     value at every level of the final result. Each value will be
51      *     replaced by the result of the filter function. This can be used to
52      *     reform generic objects into instances of classes, or to transform
53      *     date strings into Date objects.
54      *
55      * Returns: 
56      * {Object} The return depends on the value of the type argument. If type
57      *     is "FeatureCollection" (the default), the return will be an array
58      *     of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
59      *     must represent a single geometry, and the return will be an
60      *     <OpenLayers.Geometry>.  If type is "Feature", the input json must
61      *     represent a single feature, and the return will be an
62      *     <OpenLayers.Feature.Vector>.
63      */
64     read: function(json, type, filter) {
65         type = (type) ? type : "FeatureCollection";
66         var results = null;
67         var obj = null;
68         if (typeof json == "string") {
69             obj = OpenLayers.Format.JSON.prototype.read.apply(this,
70                                                               [json, filter]);
71         } else { 
72             obj = json;
73         }    
74         if(!obj) {
75             OpenLayers.Console.error("Bad JSON: " + json);
76         } else if(typeof(obj.type) != "string") {
77             OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
78         } else if(this.isValidType(obj, type)) {
79             switch(type) {
80                 case "Geometry":
81                     try {
82                         results = this.parseGeometry(obj);
83                     } catch(err) {
84                         OpenLayers.Console.error(err);
85                     }
86                     break;
87                 case "Feature":
88                     try {
89                         results = this.parseFeature(obj);
90                         results.type = "Feature";
91                     } catch(err) {
92                         OpenLayers.Console.error(err);
93                     }
94                     break;
95                 case "FeatureCollection":
96                     // for type FeatureCollection, we allow input to be any type
97                     results = [];
98                     switch(obj.type) {
99                         case "Feature":
100                             try {
101                                 results.push(this.parseFeature(obj));
102                             } catch(err) {
103                                 results = null;
104                                 OpenLayers.Console.error(err);
105                             }
106                             break;
107                         case "FeatureCollection":
108                             for(var i=0, len=obj.features.length; i<len; ++i) {
109                                 try {
110                                     results.push(this.parseFeature(obj.features[i]));
111                                 } catch(err) {
112                                     results = null;
113                                     OpenLayers.Console.error(err);
114                                 }
115                             }
116                             break;
117                         default:
118                             try {
119                                 var geom = this.parseGeometry(obj);
120                                 results.push(new OpenLayers.Feature.Vector(geom));
121                             } catch(err) {
122                                 results = null;
123                                 OpenLayers.Console.error(err);
124                             }
125                     }
126                 break;
127             }
128         }
129         return results;
130     },
131     
132     /**
133      * Method: isValidType
134      * Check if a GeoJSON object is a valid representative of the given type.
135      *
136      * Returns:
137      * {Boolean} The object is valid GeoJSON object of the given type.
138      */
139     isValidType: function(obj, type) {
140         var valid = false;
141         switch(type) {
142             case "Geometry":
143                 if(OpenLayers.Util.indexOf(
144                     ["Point", "MultiPoint", "LineString", "MultiLineString",
145                      "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
146                     obj.type) == -1) {
147                     // unsupported geometry type
148                     OpenLayers.Console.error("Unsupported geometry type: " +
149                                               obj.type);
150                 } else {
151                     valid = true;
152                 }
153                 break;
154             case "FeatureCollection":
155                 // allow for any type to be converted to a feature collection
156                 valid = true;
157                 break;
158             default:
159                 // for Feature types must match
160                 if(obj.type == type) {
161                     valid = true;
162                 } else {
163                     OpenLayers.Console.error("Cannot convert types from " +
164                                               obj.type + " to " + type);
165                 }
166         }
167         return valid;
168     },
169     
170     /**
171      * Method: parseFeature
172      * Convert a feature object from GeoJSON into an
173      *     <OpenLayers.Feature.Vector>.
174      *
175      * Parameters:
176      * obj - {Object} An object created from a GeoJSON object
177      *
178      * Returns:
179      * {<OpenLayers.Feature.Vector>} A feature.
180      */
181     parseFeature: function(obj) {
182         var feature, geometry, attributes, bbox;
183         attributes = (obj.properties) ? obj.properties : {};
184         bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
185         try {
186             geometry = this.parseGeometry(obj.geometry);
187         } catch(err) {
188             // deal with bad geometries
189             throw err;
190         }
191         feature = new OpenLayers.Feature.Vector(geometry, attributes);
192         if(bbox) {
193             feature.bounds = OpenLayers.Bounds.fromArray(bbox);
194         }
195         if(obj.id) {
196             feature.fid = obj.id;
197         }
198         return feature;
199     },
200     
201     /**
202      * Method: parseGeometry
203      * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
204      *
205      * Parameters:
206      * obj - {Object} An object created from a GeoJSON object
207      *
208      * Returns: 
209      * {<OpenLayers.Geometry>} A geometry.
210      */
211     parseGeometry: function(obj) {
212         if (obj == null) {
213             return null;
214         }
215         var geometry, collection = false;
216         if(obj.type == "GeometryCollection") {
217             if(!(obj.geometries instanceof Array)) {
218                 throw "GeometryCollection must have geometries array: " + obj;
219             }
220             var numGeom = obj.geometries.length;
221             var components = new Array(numGeom);
222             for(var i=0; i<numGeom; ++i) {
223                 components[i] = this.parseGeometry.apply(
224                     this, [obj.geometries[i]]
225                 );
226             }
227             geometry = new OpenLayers.Geometry.Collection(components);
228             collection = true;
229         } else {
230             if(!(obj.coordinates instanceof Array)) {
231                 throw "Geometry must have coordinates array: " + obj;
232             }
233             if(!this.parseCoords[obj.type.toLowerCase()]) {
234                 throw "Unsupported geometry type: " + obj.type;
235             }
236             try {
237                 geometry = this.parseCoords[obj.type.toLowerCase()].apply(
238                     this, [obj.coordinates]
239                 );
240             } catch(err) {
241                 // deal with bad coordinates
242                 throw err;
243             }
244         }
245         // We don't reproject collections because the children are reprojected
246         // for us when they are created.
247         if (this.internalProjection && this.externalProjection && !collection) {
248             geometry.transform(this.externalProjection, 
249                                this.internalProjection); 
250         }                       
251         return geometry;
252     },
253     
254     /**
255      * Property: parseCoords
256      * Object with properties corresponding to the GeoJSON geometry types.
257      *     Property values are functions that do the actual parsing.
258      */
259     parseCoords: {
260         /**
261          * Method: parseCoords.point
262          * Convert a coordinate array from GeoJSON into an
263          *     <OpenLayers.Geometry>.
264          *
265          * Parameters:
266          * array - {Object} The coordinates array from the GeoJSON fragment.
267          *
268          * Returns:
269          * {<OpenLayers.Geometry>} A geometry.
270          */
271         "point": function(array) {
272             if(array.length != 2) {
273                 throw "Only 2D points are supported: " + array;
274             }
275             return new OpenLayers.Geometry.Point(array[0], array[1]);
276         },
277         
278         /**
279          * Method: parseCoords.multipoint
280          * Convert a coordinate array from GeoJSON into an
281          *     <OpenLayers.Geometry>.
282          *
283          * Parameters:
284          * array {Object} The coordinates array from the GeoJSON fragment.
285          *
286          * Returns:
287          * {<OpenLayers.Geometry>} A geometry.
288          */
289         "multipoint": function(array) {
290             var points = [];
291             var p = null;
292             for(var i=0, len=array.length; i<len; ++i) {
293                 try {
294                     p = this.parseCoords["point"].apply(this, [array[i]]);
295                 } catch(err) {
296                     throw err;
297                 }
298                 points.push(p);
299             }
300             return new OpenLayers.Geometry.MultiPoint(points);
301         },
302
303         /**
304          * Method: parseCoords.linestring
305          * Convert a coordinate array from GeoJSON into an
306          *     <OpenLayers.Geometry>.
307          *
308          * Parameters:
309          * array - {Object} The coordinates array from the GeoJSON fragment.
310          *
311          * Returns:
312          * {<OpenLayers.Geometry>} A geometry.
313          */
314         "linestring": function(array) {
315             var points = [];
316             var p = null;
317             for(var i=0, len=array.length; i<len; ++i) {
318                 try {
319                     p = this.parseCoords["point"].apply(this, [array[i]]);
320                 } catch(err) {
321                     throw err;
322                 }
323                 points.push(p);
324             }
325             return new OpenLayers.Geometry.LineString(points);
326         },
327         
328         /**
329          * Method: parseCoords.multilinestring
330          * Convert a coordinate array from GeoJSON into an
331          *     <OpenLayers.Geometry>.
332          *
333          * Parameters:
334          * array - {Object} The coordinates array from the GeoJSON fragment.
335          *
336          * Returns:
337          * {<OpenLayers.Geometry>} A geometry.
338          */
339         "multilinestring": function(array) {
340             var lines = [];
341             var l = null;
342             for(var i=0, len=array.length; i<len; ++i) {
343                 try {
344                     l = this.parseCoords["linestring"].apply(this, [array[i]]);
345                 } catch(err) {
346                     throw err;
347                 }
348                 lines.push(l);
349             }
350             return new OpenLayers.Geometry.MultiLineString(lines);
351         },
352         
353         /**
354          * Method: parseCoords.polygon
355          * Convert a coordinate array from GeoJSON into an
356          *     <OpenLayers.Geometry>.
357          *
358          * Returns:
359          * {<OpenLayers.Geometry>} A geometry.
360          */
361         "polygon": function(array) {
362             var rings = [];
363             var r, l;
364             for(var i=0, len=array.length; i<len; ++i) {
365                 try {
366                     l = this.parseCoords["linestring"].apply(this, [array[i]]);
367                 } catch(err) {
368                     throw err;
369                 }
370                 r = new OpenLayers.Geometry.LinearRing(l.components);
371                 rings.push(r);
372             }
373             return new OpenLayers.Geometry.Polygon(rings);
374         },
375
376         /**
377          * Method: parseCoords.multipolygon
378          * Convert a coordinate array from GeoJSON into an
379          *     <OpenLayers.Geometry>.
380          *
381          * Parameters:
382          * array - {Object} The coordinates array from the GeoJSON fragment.
383          *
384          * Returns:
385          * {<OpenLayers.Geometry>} A geometry.
386          */
387         "multipolygon": function(array) {
388             var polys = [];
389             var p = null;
390             for(var i=0, len=array.length; i<len; ++i) {
391                 try {
392                     p = this.parseCoords["polygon"].apply(this, [array[i]]);
393                 } catch(err) {
394                     throw err;
395                 }
396                 polys.push(p);
397             }
398             return new OpenLayers.Geometry.MultiPolygon(polys);
399         },
400
401         /**
402          * Method: parseCoords.box
403          * Convert a coordinate array from GeoJSON into an
404          *     <OpenLayers.Geometry>.
405          *
406          * Parameters:
407          * array - {Object} The coordinates array from the GeoJSON fragment.
408          *
409          * Returns:
410          * {<OpenLayers.Geometry>} A geometry.
411          */
412         "box": function(array) {
413             if(array.length != 2) {
414                 throw "GeoJSON box coordinates must have 2 elements";
415             }
416             return new OpenLayers.Geometry.Polygon([
417                 new OpenLayers.Geometry.LinearRing([
418                     new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
419                     new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
420                     new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
421                     new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
422                     new OpenLayers.Geometry.Point(array[0][0], array[0][1])
423                 ])
424             ]);
425         }
426
427     },
428
429     /**
430      * APIMethod: write
431      * Serialize a feature, geometry, array of features into a GeoJSON string.
432      *
433      * Parameters:
434      * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
435      *     or an array of features.
436      * pretty - {Boolean} Structure the output with newlines and indentation.
437      *     Default is false.
438      *
439      * Returns:
440      * {String} The GeoJSON string representation of the input geometry,
441      *     features, or array of features.
442      */
443     write: function(obj, pretty) {
444         var geojson = {
445             "type": null
446         };
447         if(obj instanceof Array) {
448             geojson.type = "FeatureCollection";
449             var numFeatures = obj.length;
450             geojson.features = new Array(numFeatures);
451             for(var i=0; i<numFeatures; ++i) {
452                 var element = obj[i];
453                 if(!element instanceof OpenLayers.Feature.Vector) {
454                     var msg = "FeatureCollection only supports collections " +
455                               "of features: " + element;
456                     throw msg;
457                 }
458                 geojson.features[i] = this.extract.feature.apply(
459                     this, [element]
460                 );
461             }
462         } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
463             geojson = this.extract.geometry.apply(this, [obj]);
464         } else if (obj instanceof OpenLayers.Feature.Vector) {
465             geojson = this.extract.feature.apply(this, [obj]);
466             if(obj.layer && obj.layer.projection) {
467                 geojson.crs = this.createCRSObject(obj);
468             }
469         }
470         return OpenLayers.Format.JSON.prototype.write.apply(this,
471                                                             [geojson, pretty]);
472     },
473
474     /**
475      * Method: createCRSObject
476      * Create the CRS object for an object.
477      *
478      * Parameters:
479      * object - {<OpenLayers.Feature.Vector>} 
480      *
481      * Returns:
482      * {Object} An object which can be assigned to the crs property
483      * of a GeoJSON object.
484      */
485     createCRSObject: function(object) {
486        var proj = object.layer.projection.toString();
487        var crs = {};
488        if (proj.match(/epsg:/i)) {
489            var code = parseInt(proj.substring(proj.indexOf(":") + 1));
490            if (code == 4326) {
491                crs = {
492                    "type": "OGC",
493                    "properties": {
494                        "urn": "urn:ogc:def:crs:OGC:1.3:CRS84"
495                    }
496                };
497            } else {    
498                crs = {
499                    "type": "EPSG",
500                    "properties": {
501                        "code": code 
502                    }
503                };
504            }    
505        }
506        return crs;
507     },
508     
509     /**
510      * Property: extract
511      * Object with properties corresponding to the GeoJSON types.
512      *     Property values are functions that do the actual value extraction.
513      */
514     extract: {
515         /**
516          * Method: extract.feature
517          * Return a partial GeoJSON object representing a single feature.
518          *
519          * Parameters:
520          * feature - {<OpenLayers.Feature.Vector>}
521          *
522          * Returns:
523          * {Object} An object representing the point.
524          */
525         'feature': function(feature) {
526             var geom = this.extract.geometry.apply(this, [feature.geometry]);
527             return {
528                 "type": "Feature",
529                 "id": feature.fid == null ? feature.id : feature.fid,
530                 "properties": feature.attributes,
531                 "geometry": geom
532             };
533         },
534         
535         /**
536          * Method: extract.geometry
537          * Return a GeoJSON object representing a single geometry.
538          *
539          * Parameters:
540          * geometry - {<OpenLayers.Geometry>}
541          *
542          * Returns:
543          * {Object} An object representing the geometry.
544          */
545         'geometry': function(geometry) {
546             if (geometry == null) {
547                 return null;
548             }
549             if (this.internalProjection && this.externalProjection) {
550                 geometry = geometry.clone();
551                 geometry.transform(this.internalProjection, 
552                                    this.externalProjection);
553             }                       
554             var geometryType = geometry.CLASS_NAME.split('.')[2];
555             var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
556             var json;
557             if(geometryType == "Collection") {
558                 json = {
559                     "type": "GeometryCollection",
560                     "geometries": data
561                 };
562             } else {
563                 json = {
564                     "type": geometryType,
565                     "coordinates": data
566                 };
567             }
568             
569             return json;
570         },
571
572         /**
573          * Method: extract.point
574          * Return an array of coordinates from a point.
575          *
576          * Parameters:
577          * point - {<OpenLayers.Geometry.Point>}
578          *
579          * Returns: 
580          * {Array} An array of coordinates representing the point.
581          */
582         'point': function(point) {
583             return [point.x, point.y];
584         },
585
586         /**
587          * Method: extract.multipoint
588          * Return an array of point coordinates from a multipoint.
589          *
590          * Parameters:
591          * multipoint - {<OpenLayers.Geometry.MultiPoint>}
592          *
593          * Returns:
594          * {Array} An array of point coordinate arrays representing
595          *     the multipoint.
596          */
597         'multipoint': function(multipoint) {
598             var array = [];
599             for(var i=0, len=multipoint.components.length; i<len; ++i) {
600                 array.push(this.extract.point.apply(this, [multipoint.components[i]]));
601             }
602             return array;
603         },
604         
605         /**
606          * Method: extract.linestring
607          * Return an array of coordinate arrays from a linestring.
608          *
609          * Parameters:
610          * linestring - {<OpenLayers.Geometry.LineString>}
611          *
612          * Returns:
613          * {Array} An array of coordinate arrays representing
614          *     the linestring.
615          */
616         'linestring': function(linestring) {
617             var array = [];
618             for(var i=0, len=linestring.components.length; i<len; ++i) {
619                 array.push(this.extract.point.apply(this, [linestring.components[i]]));
620             }
621             return array;
622         },
623
624         /**
625          * Method: extract.multilinestring
626          * Return an array of linestring arrays from a linestring.
627          * 
628          * Parameters:
629          * linestring - {<OpenLayers.Geometry.MultiLineString>}
630          * 
631          * Returns:
632          * {Array} An array of linestring arrays representing
633          *     the multilinestring.
634          */
635         'multilinestring': function(multilinestring) {
636             var array = [];
637             for(var i=0, len=multilinestring.components.length; i<len; ++i) {
638                 array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
639             }
640             return array;
641         },
642         
643         /**
644          * Method: extract.polygon
645          * Return an array of linear ring arrays from a polygon.
646          *
647          * Parameters:
648          * polygon - {<OpenLayers.Geometry.Polygon>}
649          * 
650          * Returns:
651          * {Array} An array of linear ring arrays representing the polygon.
652          */
653         'polygon': function(polygon) {
654             var array = [];
655             for(var i=0, len=polygon.components.length; i<len; ++i) {
656                 array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
657             }
658             return array;
659         },
660
661         /**
662          * Method: extract.multipolygon
663          * Return an array of polygon arrays from a multipolygon.
664          * 
665          * Parameters:
666          * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
667          * 
668          * Returns:
669          * {Array} An array of polygon arrays representing
670          *     the multipolygon
671          */
672         'multipolygon': function(multipolygon) {
673             var array = [];
674             for(var i=0, len=multipolygon.components.length; i<len; ++i) {
675                 array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
676             }
677             return array;
678         },
679         
680         /**
681          * Method: extract.collection
682          * Return an array of geometries from a geometry collection.
683          * 
684          * Parameters:
685          * collection - {<OpenLayers.Geometry.Collection>}
686          * 
687          * Returns:
688          * {Array} An array of geometry objects representing the geometry
689          *     collection.
690          */
691         'collection': function(collection) {
692             var len = collection.components.length;
693             var array = new Array(len);
694             for(var i=0; i<len; ++i) {
695                 array[i] = this.extract.geometry.apply(
696                     this, [collection.components[i]]
697                 );
698             }
699             return array;
700         }
701         
702
703     },
704
705     CLASS_NAME: "OpenLayers.Format.GeoJSON" 
706
707 });