]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Format/OSM.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Format / OSM.js
1 /* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license.
2  * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
3  * for the full text of the license. */
4
5 /**
6  * @requires OpenLayers/Format/XML.js
7  * @requires OpenLayers/Feature/Vector.js
8  * @requires OpenLayers/Geometry/Point.js
9  * @requires OpenLayers/Geometry/LineString.js
10  * @requires OpenLayers/Geometry/Polygon.js
11  */
12
13 /**  
14  * Class: OpenLayers.Format.OSM
15  * OSM parser. Create a new instance with the 
16  *     <OpenLayers.Format.OSM> constructor.
17  *
18  * Inherits from:
19  *  - <OpenLayers.Format.XML>
20  */
21 OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
22     
23     /**
24      * APIProperty: checkTags
25      * {Boolean} Should tags be checked to determine whether something
26      * should be treated as a seperate node. Will slow down parsing.
27      * Default is false.
28      */
29     checkTags: false,
30
31     /**
32      * Property: interestingTagsExclude
33      * {Array} List of tags to exclude from 'interesting' checks on nodes.
34      * Must be set when creating the format. Will only be used if checkTags
35      * is set.
36      */
37     interestingTagsExclude: null, 
38     
39     /**
40      * APIProperty: areaTags
41      * {Array} List of tags indicating that something is an area.  
42      * Must be set when creating the format. Will only be used if 
43      * checkTags is true.
44      */
45     areaTags: null, 
46     
47     /**
48      * Constructor: OpenLayers.Format.OSM
49      * Create a new parser for OSM.
50      *
51      * Parameters:
52      * options - {Object} An optional object whose properties will be set on
53      *     this instance.
54      */
55     initialize: function(options) {
56         var layer_defaults = {
57           'interestingTagsExclude': ['source', 'source_ref', 
58               'source:ref', 'history', 'attribution', 'created_by'],
59           'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins',
60               'historic', 'landuse', 'military', 'natural', 'sport'] 
61         };
62           
63         layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
64         
65         var interesting = {};
66         for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
67             interesting[layer_defaults.interestingTagsExclude[i]] = true;
68         }
69         layer_defaults.interestingTagsExclude = interesting;
70         
71         var area = {};
72         for (var i = 0; i < layer_defaults.areaTags.length; i++) {
73             area[layer_defaults.areaTags[i]] = true;
74         }
75         layer_defaults.areaTags = area;
76         
77         OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
78     },
79     
80     /**
81      * APIMethod: read
82      * Return a list of features from a OSM doc
83      
84      * Parameters:
85      * data - {Element} 
86      *
87      * Returns:
88      * An Array of <OpenLayers.Feature.Vector>s
89      */
90     read: function(doc) {
91         if (typeof doc == "string") { 
92             doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
93         }
94
95         var nodes = this.getNodes(doc);
96         var ways = this.getWays(doc);
97         
98         // Geoms will contain at least ways.length entries.
99         var feat_list = new Array(ways.length);
100         
101         for (var i = 0; i < ways.length; i++) {
102             // We know the minimal of this one ahead of time. (Could be -1
103             // due to areas/polygons)
104             var point_list = new Array(ways[i].nodes.length);
105             
106             var poly = this.isWayArea(ways[i]) ? 1 : 0; 
107             for (var j = 0; j < ways[i].nodes.length; j++) {
108                var node = nodes[ways[i].nodes[j]];
109                
110                var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
111                
112                // Since OSM is topological, we stash the node ID internally. 
113                point.osm_id = parseInt(ways[i].nodes[j]);
114                point_list[j] = point;
115                
116                // We don't display nodes if they're used inside other 
117                // elements.
118                node.used = true; 
119             }
120             var geometry = null;
121             if (poly) { 
122                 geometry = new OpenLayers.Geometry.Polygon(
123                     new OpenLayers.Geometry.LinearRing(point_list));
124             } else {    
125                 geometry = new OpenLayers.Geometry.LineString(point_list);
126             }
127             if (this.internalProjection && this.externalProjection) {
128                 geometry.transform(this.externalProjection, 
129                     this.internalProjection);
130             }        
131             var feat = new OpenLayers.Feature.Vector(geometry,
132                 ways[i].tags);
133             feat.osm_id = parseInt(ways[i].id);
134             feat.fid = "way." + feat.osm_id;
135             feat_list[i] = feat;
136         } 
137         for (var node_id in nodes) {
138             var node = nodes[node_id];
139             if (!node.used || this.checkTags) {
140                 var tags = null;
141                 
142                 if (this.checkTags) {
143                     var result = this.getTags(node.node, true);
144                     if (node.used && !result[1]) {
145                         continue;
146                     }
147                     tags = result[0];
148                 } else { 
149                     tags = this.getTags(node.node);
150                 }    
151                 
152                 var feat = new OpenLayers.Feature.Vector(
153                     new OpenLayers.Geometry.Point(node['lon'], node['lat']),
154                     tags);
155                 if (this.internalProjection && this.externalProjection) {
156                     feat.geometry.transform(this.externalProjection, 
157                         this.internalProjection);
158                 }        
159                 feat.osm_id = parseInt(node_id); 
160                 feat.fid = "node." + feat.osm_id;
161                 feat_list.push(feat);
162             }   
163             // Memory cleanup
164             node.node = null;
165         }        
166         return feat_list;
167     },
168
169     /**
170      * Method: getNodes
171      * Return the node items from a doc.  
172      *
173      * Parameters:
174      * node - {DOMElement} node to parse tags from
175      */
176     getNodes: function(doc) {
177         var node_list = doc.getElementsByTagName("node");
178         var nodes = {};
179         for (var i = 0; i < node_list.length; i++) {
180             var node = node_list[i];
181             var id = node.getAttribute("id");
182             nodes[id] = {
183                 'lat': node.getAttribute("lat"),
184                 'lon': node.getAttribute("lon"),
185                 'node': node
186             };
187         }
188         return nodes;
189     },
190
191     /**
192      * Method: getWays
193      * Return the way items from a doc.  
194      *
195      * Parameters:
196      * node - {DOMElement} node to parse tags from
197      */
198     getWays: function(doc) {
199         var way_list = doc.getElementsByTagName("way");
200         var return_ways = [];
201         for (var i = 0; i < way_list.length; i++) {
202             var way = way_list[i];
203             var way_object = {
204               id: way.getAttribute("id")
205             };
206             
207             way_object.tags = this.getTags(way);
208             
209             var node_list = way.getElementsByTagName("nd");
210             
211             way_object.nodes = new Array(node_list.length);
212             
213             for (var j = 0; j < node_list.length; j++) {
214                 way_object.nodes[j] = node_list[j].getAttribute("ref");
215             }  
216             return_ways.push(way_object);
217         }
218         return return_ways; 
219         
220     },  
221     
222     /**
223      * Method: getTags
224      * Return the tags list attached to a specific DOM element.
225      *
226      * Parameters:
227      * node - {DOMElement} node to parse tags from
228      * interesting_tags - {Boolean} whether the return from this function should
229      *    return a boolean indicating that it has 'interesting tags' -- 
230      *    tags like attribution and source are ignored. (To change the list
231      *    of tags, see interestingTagsExclude)
232      * 
233      * Returns:
234      * tags - {Object} hash of tags
235      * interesting - {Boolean} if interesting_tags is passed, returns
236      *     whether there are any interesting tags on this element.
237      */
238     getTags: function(dom_node, interesting_tags) {
239         var tag_list = dom_node.getElementsByTagName("tag");
240         var tags = {};
241         var interesting = false;
242         for (var j = 0; j < tag_list.length; j++) {
243             var key = tag_list[j].getAttribute("k");
244             tags[key] = tag_list[j].getAttribute("v");
245             if (interesting_tags) {
246                 if (!this.interestingTagsExclude[key]) {
247                     interesting = true;
248                 }
249             }    
250         }  
251         return interesting_tags ? [tags, interesting] : tags;     
252     },
253
254     /** 
255      * Method: isWayArea
256      * Given a way object from getWays, check whether the tags and geometry
257      * indicate something is an area.
258      *
259      * Returns:
260      * {Boolean}
261      */
262     isWayArea: function(way) { 
263         var poly_shaped = false;
264         var poly_tags = false;
265         
266         if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
267             poly_shaped = true;
268         }
269         if (this.checkTags) {
270             for(var key in way.tags) {
271                 if (this.areaTags[key]) {
272                     poly_tags = true;
273                     break;
274                 }
275             }
276         }    
277         return poly_shaped && (this.checkTags ? poly_tags : true);            
278     }, 
279
280     /**
281      * APIMethod: write 
282      * Takes a list of features, returns a serialized OSM format file for use
283      * in tools like JOSM.
284      *
285      * Parameters:
286      * features - {Array(<OpenLayers.Feature.Vector>)}
287      */
288     write: function(features) { 
289         if (!(features instanceof Array)) {
290             features = [features];
291         }
292         
293         this.osm_id = 1;
294         this.created_nodes = {};
295         var root_node = this.createElementNS(null, "osm");
296         root_node.setAttribute("version", "0.5");
297         root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER);
298
299         // Loop backwards, because the deserializer puts nodes last, and 
300         // we want them first if possible
301         for(var i = features.length - 1; i >= 0; i--) {
302             var nodes = this.createFeatureNodes(features[i]);
303             for (var j = 0; j < nodes.length; j++) {
304                 root_node.appendChild(nodes[j]);
305             }    
306         }
307         return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
308     },
309
310     /**
311      * Method: createFeatureNodes
312      * Takes a feature, returns a list of nodes from size 0->n.
313      * Will include all pieces of the serialization that are required which
314      * have not already been created. Calls out to createXML based on geometry
315      * type.
316      *
317      * Parameters:
318      * feature - {<OpenLayers.Feature.Vector>}
319      */
320     createFeatureNodes: function(feature) {
321         var nodes = [];
322         var className = feature.geometry.CLASS_NAME;
323         var type = className.substring(className.lastIndexOf(".") + 1);
324         type = type.toLowerCase();
325         var builder = this.createXML[type];
326         if (builder) {
327             nodes = builder.apply(this, [feature]);
328         }
329         return nodes;
330     },
331     
332     /**
333      * Method: createXML
334      * Takes a feature, returns a list of nodes from size 0->n.
335      * Will include all pieces of the serialization that are required which
336      * have not already been created.
337      *
338      * Parameters:
339      * feature - {<OpenLayers.Feature.Vector>}
340      */
341     createXML: {
342         'point': function(point) {
343             var id = null;
344             var geometry = point.geometry ? point.geometry : point;
345             var already_exists = false; // We don't return anything if the node
346                                         // has already been created
347             if (point.osm_id) {
348                 id = point.osm_id;
349                 if (this.created_nodes[id]) {
350                     already_exists = true;
351                 }    
352             } else {
353                id = -this.osm_id;
354                this.osm_id++; 
355             }
356             if (already_exists) {
357                 node = this.created_nodes[id];
358             } else {    
359                 var node = this.createElementNS(null, "node");
360             }
361             this.created_nodes[id] = node;
362             node.setAttribute("id", id);
363             node.setAttribute("lon", geometry.x); 
364             node.setAttribute("lat", geometry.y);
365             if (point.attributes) {
366                 this.serializeTags(point, node);
367             }
368             this.setState(point, node);
369             return already_exists ? [] : [node];
370         }, 
371         linestring: function(feature) {
372             var nodes = [];
373             var geometry = feature.geometry;
374             if (feature.osm_id) {
375                 id = feature.osm_id;
376             } else {
377                id = -this.osm_id;
378                this.osm_id++; 
379             }
380             var way = this.createElementNS(null, "way");
381             way.setAttribute("id", id);
382             for (var i = 0; i < geometry.components.length; i++) {
383                 var node = this.createXML['point'].apply(this, [geometry.components[i]]);
384                 if (node.length) {
385                     node = node[0];
386                     var node_ref = node.getAttribute("id");
387                     nodes.push(node);
388                 } else {
389                     node_ref = geometry.components[i].osm_id;
390                     node = this.created_nodes[node_ref];
391                 }
392                 this.setState(feature, node);
393                 var nd_dom = this.createElementNS(null, "nd");
394                 nd_dom.setAttribute("ref", node_ref);
395                 way.appendChild(nd_dom);
396             }
397             this.serializeTags(feature, way);
398             nodes.push(way);
399             
400             return nodes;
401         },
402         polygon: function(feature) {
403             var attrs = OpenLayers.Util.extend({'area':'yes'}, feature.attributes);
404             var feat = new OpenLayers.Feature.Vector(feature.geometry.components[0], attrs); 
405             feat.osm_id = feature.osm_id;
406             return this.createXML['linestring'].apply(this, [feat]);
407         }
408     },
409
410     /**
411      * Method: serializeTags
412      * Given a feature, serialize the attributes onto the given node.
413      *
414      * Parameters:
415      * feature - {<OpenLayers.Feature.Vector>}
416      * node - {DOMNode}
417      */
418     serializeTags: function(feature, node) {
419         for (var key in feature.attributes) {
420             var tag = this.createElementNS(null, "tag");
421             tag.setAttribute("k", key);
422             tag.setAttribute("v", feature.attributes[key]);
423             node.appendChild(tag);
424         }
425     },
426
427     /**
428      * Method: setState 
429      * OpenStreetMap has a convention that 'state' is stored for modification or deletion.
430      * This allows the file to be uploaded via JOSM or the bulk uploader tool.
431      *
432      * Parameters:
433      * feature - {<OpenLayers.Feature.Vector>}
434      * node - {DOMNode}
435      */
436     setState: function(feature, node) {
437         if (feature.state) {
438             var state = null;
439             switch(feature.state) {
440                 case OpenLayers.State.UPDATE:
441                     state = "modify";
442                 case OpenLayers.State.DELETE:
443                     state = "delete";
444             }
445             if (state) {
446                 node.setAttribute("action", state);
447             }
448         }    
449     },
450
451     CLASS_NAME: "OpenLayers.Format.OSM" 
452 });