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. */
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
14 * Class: OpenLayers.Format.OSM
15 * OSM parser. Create a new instance with the
16 * <OpenLayers.Format.OSM> constructor.
19 * - <OpenLayers.Format.XML>
21 OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
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.
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
37 interestingTagsExclude: null,
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
48 * Constructor: OpenLayers.Format.OSM
49 * Create a new parser for OSM.
52 * options - {Object} An optional object whose properties will be set on
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']
63 layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
66 for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
67 interesting[layer_defaults.interestingTagsExclude[i]] = true;
69 layer_defaults.interestingTagsExclude = interesting;
72 for (var i = 0; i < layer_defaults.areaTags.length; i++) {
73 area[layer_defaults.areaTags[i]] = true;
75 layer_defaults.areaTags = area;
77 OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
82 * Return a list of features from a OSM doc
88 * An Array of <OpenLayers.Feature.Vector>s
91 if (typeof doc == "string") {
92 doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
95 var nodes = this.getNodes(doc);
96 var ways = this.getWays(doc);
98 // Geoms will contain at least ways.length entries.
99 var feat_list = new Array(ways.length);
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);
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]];
110 var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
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;
116 // We don't display nodes if they're used inside other
122 geometry = new OpenLayers.Geometry.Polygon(
123 new OpenLayers.Geometry.LinearRing(point_list));
125 geometry = new OpenLayers.Geometry.LineString(point_list);
127 if (this.internalProjection && this.externalProjection) {
128 geometry.transform(this.externalProjection,
129 this.internalProjection);
131 var feat = new OpenLayers.Feature.Vector(geometry,
133 feat.osm_id = parseInt(ways[i].id);
134 feat.fid = "way." + feat.osm_id;
137 for (var node_id in nodes) {
138 var node = nodes[node_id];
139 if (!node.used || this.checkTags) {
142 if (this.checkTags) {
143 var result = this.getTags(node.node, true);
144 if (node.used && !result[1]) {
149 tags = this.getTags(node.node);
152 var feat = new OpenLayers.Feature.Vector(
153 new OpenLayers.Geometry.Point(node['lon'], node['lat']),
155 if (this.internalProjection && this.externalProjection) {
156 feat.geometry.transform(this.externalProjection,
157 this.internalProjection);
159 feat.osm_id = parseInt(node_id);
160 feat.fid = "node." + feat.osm_id;
161 feat_list.push(feat);
171 * Return the node items from a doc.
174 * node - {DOMElement} node to parse tags from
176 getNodes: function(doc) {
177 var node_list = doc.getElementsByTagName("node");
179 for (var i = 0; i < node_list.length; i++) {
180 var node = node_list[i];
181 var id = node.getAttribute("id");
183 'lat': node.getAttribute("lat"),
184 'lon': node.getAttribute("lon"),
193 * Return the way items from a doc.
196 * node - {DOMElement} node to parse tags from
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];
204 id: way.getAttribute("id")
207 way_object.tags = this.getTags(way);
209 var node_list = way.getElementsByTagName("nd");
211 way_object.nodes = new Array(node_list.length);
213 for (var j = 0; j < node_list.length; j++) {
214 way_object.nodes[j] = node_list[j].getAttribute("ref");
216 return_ways.push(way_object);
224 * Return the tags list attached to a specific DOM element.
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)
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.
238 getTags: function(dom_node, interesting_tags) {
239 var tag_list = dom_node.getElementsByTagName("tag");
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]) {
251 return interesting_tags ? [tags, interesting] : tags;
256 * Given a way object from getWays, check whether the tags and geometry
257 * indicate something is an area.
262 isWayArea: function(way) {
263 var poly_shaped = false;
264 var poly_tags = false;
266 if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
269 if (this.checkTags) {
270 for(var key in way.tags) {
271 if (this.areaTags[key]) {
277 return poly_shaped && (this.checkTags ? poly_tags : true);
282 * Takes a list of features, returns a serialized OSM format file for use
283 * in tools like JOSM.
286 * features - {Array(<OpenLayers.Feature.Vector>)}
288 write: function(features) {
289 if (!(features instanceof Array)) {
290 features = [features];
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);
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]);
307 return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
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
318 * feature - {<OpenLayers.Feature.Vector>}
320 createFeatureNodes: function(feature) {
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];
327 nodes = builder.apply(this, [feature]);
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.
339 * feature - {<OpenLayers.Feature.Vector>}
342 'point': function(point) {
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
349 if (this.created_nodes[id]) {
350 already_exists = true;
356 if (already_exists) {
357 node = this.created_nodes[id];
359 var node = this.createElementNS(null, "node");
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);
368 this.setState(point, node);
369 return already_exists ? [] : [node];
371 linestring: function(feature) {
373 var geometry = feature.geometry;
374 if (feature.osm_id) {
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]]);
386 var node_ref = node.getAttribute("id");
389 node_ref = geometry.components[i].osm_id;
390 node = this.created_nodes[node_ref];
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);
397 this.serializeTags(feature, way);
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]);
411 * Method: serializeTags
412 * Given a feature, serialize the attributes onto the given node.
415 * feature - {<OpenLayers.Feature.Vector>}
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);
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.
433 * feature - {<OpenLayers.Feature.Vector>}
436 setState: function(feature, node) {
439 switch(feature.state) {
440 case OpenLayers.State.UPDATE:
442 case OpenLayers.State.DELETE:
446 node.setAttribute("action", state);
451 CLASS_NAME: "OpenLayers.Format.OSM"