git-svn-id: http://svn.openlayers.org/trunk/openlayers@9975 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
457 lines
15 KiB
JavaScript
457 lines
15 KiB
JavaScript
/* Copyright (c) 2006-2007 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/XML.js
|
|
* @requires OpenLayers/Feature/Vector.js
|
|
* @requires OpenLayers/Geometry/Point.js
|
|
* @requires OpenLayers/Geometry/LineString.js
|
|
* @requires OpenLayers/Geometry/Polygon.js
|
|
* @requires OpenLayers/Projection.js
|
|
*/
|
|
|
|
/**
|
|
* Class: OpenLayers.Format.OSM
|
|
* OSM parser. Create a new instance with the
|
|
* <OpenLayers.Format.OSM> constructor.
|
|
*
|
|
* Inherits from:
|
|
* - <OpenLayers.Format.XML>
|
|
*/
|
|
OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
|
|
|
|
/**
|
|
* APIProperty: checkTags
|
|
* {Boolean} Should tags be checked to determine whether something
|
|
* should be treated as a seperate node. Will slow down parsing.
|
|
* Default is false.
|
|
*/
|
|
checkTags: false,
|
|
|
|
/**
|
|
* Property: interestingTagsExclude
|
|
* {Array} List of tags to exclude from 'interesting' checks on nodes.
|
|
* Must be set when creating the format. Will only be used if checkTags
|
|
* is set.
|
|
*/
|
|
interestingTagsExclude: null,
|
|
|
|
/**
|
|
* APIProperty: areaTags
|
|
* {Array} List of tags indicating that something is an area.
|
|
* Must be set when creating the format. Will only be used if
|
|
* checkTags is true.
|
|
*/
|
|
areaTags: null,
|
|
|
|
/**
|
|
* Constructor: OpenLayers.Format.OSM
|
|
* Create a new parser for OSM.
|
|
*
|
|
* Parameters:
|
|
* options - {Object} An optional object whose properties will be set on
|
|
* this instance.
|
|
*/
|
|
initialize: function(options) {
|
|
var layer_defaults = {
|
|
'interestingTagsExclude': ['source', 'source_ref',
|
|
'source:ref', 'history', 'attribution', 'created_by'],
|
|
'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins',
|
|
'historic', 'landuse', 'military', 'natural', 'sport']
|
|
};
|
|
|
|
layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
|
|
|
|
var interesting = {};
|
|
for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
|
|
interesting[layer_defaults.interestingTagsExclude[i]] = true;
|
|
}
|
|
layer_defaults.interestingTagsExclude = interesting;
|
|
|
|
var area = {};
|
|
for (var i = 0; i < layer_defaults.areaTags.length; i++) {
|
|
area[layer_defaults.areaTags[i]] = true;
|
|
}
|
|
layer_defaults.areaTags = area;
|
|
|
|
// OSM coordinates are always in longlat WGS84
|
|
this.externalProjection = new OpenLayers.Projection("EPSG:4326");
|
|
|
|
OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
|
|
},
|
|
|
|
/**
|
|
* APIMethod: read
|
|
* Return a list of features from a OSM doc
|
|
|
|
* Parameters:
|
|
* data - {Element}
|
|
*
|
|
* Returns:
|
|
* An Array of <OpenLayers.Feature.Vector>s
|
|
*/
|
|
read: function(doc) {
|
|
if (typeof doc == "string") {
|
|
doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
|
|
}
|
|
|
|
var nodes = this.getNodes(doc);
|
|
var ways = this.getWays(doc);
|
|
|
|
// Geoms will contain at least ways.length entries.
|
|
var feat_list = new Array(ways.length);
|
|
|
|
for (var i = 0; i < ways.length; i++) {
|
|
// We know the minimal of this one ahead of time. (Could be -1
|
|
// due to areas/polygons)
|
|
var point_list = new Array(ways[i].nodes.length);
|
|
|
|
var poly = this.isWayArea(ways[i]) ? 1 : 0;
|
|
for (var j = 0; j < ways[i].nodes.length; j++) {
|
|
var node = nodes[ways[i].nodes[j]];
|
|
|
|
var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
|
|
|
|
// Since OSM is topological, we stash the node ID internally.
|
|
point.osm_id = parseInt(ways[i].nodes[j]);
|
|
point_list[j] = point;
|
|
|
|
// We don't display nodes if they're used inside other
|
|
// elements.
|
|
node.used = true;
|
|
}
|
|
var geometry = null;
|
|
if (poly) {
|
|
geometry = new OpenLayers.Geometry.Polygon(
|
|
new OpenLayers.Geometry.LinearRing(point_list));
|
|
} else {
|
|
geometry = new OpenLayers.Geometry.LineString(point_list);
|
|
}
|
|
if (this.internalProjection && this.externalProjection) {
|
|
geometry.transform(this.externalProjection,
|
|
this.internalProjection);
|
|
}
|
|
var feat = new OpenLayers.Feature.Vector(geometry,
|
|
ways[i].tags);
|
|
feat.osm_id = parseInt(ways[i].id);
|
|
feat.fid = "way." + feat.osm_id;
|
|
feat_list[i] = feat;
|
|
}
|
|
for (var node_id in nodes) {
|
|
var node = nodes[node_id];
|
|
if (!node.used || this.checkTags) {
|
|
var tags = null;
|
|
|
|
if (this.checkTags) {
|
|
var result = this.getTags(node.node, true);
|
|
if (node.used && !result[1]) {
|
|
continue;
|
|
}
|
|
tags = result[0];
|
|
} else {
|
|
tags = this.getTags(node.node);
|
|
}
|
|
|
|
var feat = new OpenLayers.Feature.Vector(
|
|
new OpenLayers.Geometry.Point(node['lon'], node['lat']),
|
|
tags);
|
|
if (this.internalProjection && this.externalProjection) {
|
|
feat.geometry.transform(this.externalProjection,
|
|
this.internalProjection);
|
|
}
|
|
feat.osm_id = parseInt(node_id);
|
|
feat.fid = "node." + feat.osm_id;
|
|
feat_list.push(feat);
|
|
}
|
|
// Memory cleanup
|
|
node.node = null;
|
|
}
|
|
return feat_list;
|
|
},
|
|
|
|
/**
|
|
* Method: getNodes
|
|
* Return the node items from a doc.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement} node to parse tags from
|
|
*/
|
|
getNodes: function(doc) {
|
|
var node_list = doc.getElementsByTagName("node");
|
|
var nodes = {};
|
|
for (var i = 0; i < node_list.length; i++) {
|
|
var node = node_list[i];
|
|
var id = node.getAttribute("id");
|
|
nodes[id] = {
|
|
'lat': node.getAttribute("lat"),
|
|
'lon': node.getAttribute("lon"),
|
|
'node': node
|
|
};
|
|
}
|
|
return nodes;
|
|
},
|
|
|
|
/**
|
|
* Method: getWays
|
|
* Return the way items from a doc.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement} node to parse tags from
|
|
*/
|
|
getWays: function(doc) {
|
|
var way_list = doc.getElementsByTagName("way");
|
|
var return_ways = [];
|
|
for (var i = 0; i < way_list.length; i++) {
|
|
var way = way_list[i];
|
|
var way_object = {
|
|
id: way.getAttribute("id")
|
|
};
|
|
|
|
way_object.tags = this.getTags(way);
|
|
|
|
var node_list = way.getElementsByTagName("nd");
|
|
|
|
way_object.nodes = new Array(node_list.length);
|
|
|
|
for (var j = 0; j < node_list.length; j++) {
|
|
way_object.nodes[j] = node_list[j].getAttribute("ref");
|
|
}
|
|
return_ways.push(way_object);
|
|
}
|
|
return return_ways;
|
|
|
|
},
|
|
|
|
/**
|
|
* Method: getTags
|
|
* Return the tags list attached to a specific DOM element.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement} node to parse tags from
|
|
* interesting_tags - {Boolean} whether the return from this function should
|
|
* return a boolean indicating that it has 'interesting tags' --
|
|
* tags like attribution and source are ignored. (To change the list
|
|
* of tags, see interestingTagsExclude)
|
|
*
|
|
* Returns:
|
|
* tags - {Object} hash of tags
|
|
* interesting - {Boolean} if interesting_tags is passed, returns
|
|
* whether there are any interesting tags on this element.
|
|
*/
|
|
getTags: function(dom_node, interesting_tags) {
|
|
var tag_list = dom_node.getElementsByTagName("tag");
|
|
var tags = {};
|
|
var interesting = false;
|
|
for (var j = 0; j < tag_list.length; j++) {
|
|
var key = tag_list[j].getAttribute("k");
|
|
tags[key] = tag_list[j].getAttribute("v");
|
|
if (interesting_tags) {
|
|
if (!this.interestingTagsExclude[key]) {
|
|
interesting = true;
|
|
}
|
|
}
|
|
}
|
|
return interesting_tags ? [tags, interesting] : tags;
|
|
},
|
|
|
|
/**
|
|
* Method: isWayArea
|
|
* Given a way object from getWays, check whether the tags and geometry
|
|
* indicate something is an area.
|
|
*
|
|
* Returns:
|
|
* {Boolean}
|
|
*/
|
|
isWayArea: function(way) {
|
|
var poly_shaped = false;
|
|
var poly_tags = false;
|
|
|
|
if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
|
|
poly_shaped = true;
|
|
}
|
|
if (this.checkTags) {
|
|
for(var key in way.tags) {
|
|
if (this.areaTags[key]) {
|
|
poly_tags = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return poly_shaped && (this.checkTags ? poly_tags : true);
|
|
},
|
|
|
|
/**
|
|
* APIMethod: write
|
|
* Takes a list of features, returns a serialized OSM format file for use
|
|
* in tools like JOSM.
|
|
*
|
|
* Parameters:
|
|
* features - {Array(<OpenLayers.Feature.Vector>)}
|
|
*/
|
|
write: function(features) {
|
|
if (!(features instanceof Array)) {
|
|
features = [features];
|
|
}
|
|
|
|
this.osm_id = 1;
|
|
this.created_nodes = {};
|
|
var root_node = this.createElementNS(null, "osm");
|
|
root_node.setAttribute("version", "0.5");
|
|
root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER);
|
|
|
|
// Loop backwards, because the deserializer puts nodes last, and
|
|
// we want them first if possible
|
|
for(var i = features.length - 1; i >= 0; i--) {
|
|
var nodes = this.createFeatureNodes(features[i]);
|
|
for (var j = 0; j < nodes.length; j++) {
|
|
root_node.appendChild(nodes[j]);
|
|
}
|
|
}
|
|
return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
|
|
},
|
|
|
|
/**
|
|
* Method: createFeatureNodes
|
|
* Takes a feature, returns a list of nodes from size 0->n.
|
|
* Will include all pieces of the serialization that are required which
|
|
* have not already been created. Calls out to createXML based on geometry
|
|
* type.
|
|
*
|
|
* Parameters:
|
|
* feature - {<OpenLayers.Feature.Vector>}
|
|
*/
|
|
createFeatureNodes: function(feature) {
|
|
var nodes = [];
|
|
var className = feature.geometry.CLASS_NAME;
|
|
var type = className.substring(className.lastIndexOf(".") + 1);
|
|
type = type.toLowerCase();
|
|
var builder = this.createXML[type];
|
|
if (builder) {
|
|
nodes = builder.apply(this, [feature]);
|
|
}
|
|
return nodes;
|
|
},
|
|
|
|
/**
|
|
* Method: createXML
|
|
* Takes a feature, returns a list of nodes from size 0->n.
|
|
* Will include all pieces of the serialization that are required which
|
|
* have not already been created.
|
|
*
|
|
* Parameters:
|
|
* feature - {<OpenLayers.Feature.Vector>}
|
|
*/
|
|
createXML: {
|
|
'point': function(point) {
|
|
var id = null;
|
|
var geometry = point.geometry ? point.geometry : point;
|
|
var already_exists = false; // We don't return anything if the node
|
|
// has already been created
|
|
if (point.osm_id) {
|
|
id = point.osm_id;
|
|
if (this.created_nodes[id]) {
|
|
already_exists = true;
|
|
}
|
|
} else {
|
|
id = -this.osm_id;
|
|
this.osm_id++;
|
|
}
|
|
if (already_exists) {
|
|
node = this.created_nodes[id];
|
|
} else {
|
|
var node = this.createElementNS(null, "node");
|
|
}
|
|
this.created_nodes[id] = node;
|
|
node.setAttribute("id", id);
|
|
node.setAttribute("lon", geometry.x);
|
|
node.setAttribute("lat", geometry.y);
|
|
if (point.attributes) {
|
|
this.serializeTags(point, node);
|
|
}
|
|
this.setState(point, node);
|
|
return already_exists ? [] : [node];
|
|
},
|
|
linestring: function(feature) {
|
|
var nodes = [];
|
|
var geometry = feature.geometry;
|
|
if (feature.osm_id) {
|
|
id = feature.osm_id;
|
|
} else {
|
|
id = -this.osm_id;
|
|
this.osm_id++;
|
|
}
|
|
var way = this.createElementNS(null, "way");
|
|
way.setAttribute("id", id);
|
|
for (var i = 0; i < geometry.components.length; i++) {
|
|
var node = this.createXML['point'].apply(this, [geometry.components[i]]);
|
|
if (node.length) {
|
|
node = node[0];
|
|
var node_ref = node.getAttribute("id");
|
|
nodes.push(node);
|
|
} else {
|
|
node_ref = geometry.components[i].osm_id;
|
|
node = this.created_nodes[node_ref];
|
|
}
|
|
this.setState(feature, node);
|
|
var nd_dom = this.createElementNS(null, "nd");
|
|
nd_dom.setAttribute("ref", node_ref);
|
|
way.appendChild(nd_dom);
|
|
}
|
|
this.serializeTags(feature, way);
|
|
nodes.push(way);
|
|
|
|
return nodes;
|
|
},
|
|
polygon: function(feature) {
|
|
var attrs = OpenLayers.Util.extend({'area':'yes'}, feature.attributes);
|
|
var feat = new OpenLayers.Feature.Vector(feature.geometry.components[0], attrs);
|
|
feat.osm_id = feature.osm_id;
|
|
return this.createXML['linestring'].apply(this, [feat]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: serializeTags
|
|
* Given a feature, serialize the attributes onto the given node.
|
|
*
|
|
* Parameters:
|
|
* feature - {<OpenLayers.Feature.Vector>}
|
|
* node - {DOMNode}
|
|
*/
|
|
serializeTags: function(feature, node) {
|
|
for (var key in feature.attributes) {
|
|
var tag = this.createElementNS(null, "tag");
|
|
tag.setAttribute("k", key);
|
|
tag.setAttribute("v", feature.attributes[key]);
|
|
node.appendChild(tag);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: setState
|
|
* OpenStreetMap has a convention that 'state' is stored for modification or deletion.
|
|
* This allows the file to be uploaded via JOSM or the bulk uploader tool.
|
|
*
|
|
* Parameters:
|
|
* feature - {<OpenLayers.Feature.Vector>}
|
|
* node - {DOMNode}
|
|
*/
|
|
setState: function(feature, node) {
|
|
if (feature.state) {
|
|
var state = null;
|
|
switch(feature.state) {
|
|
case OpenLayers.State.UPDATE:
|
|
state = "modify";
|
|
case OpenLayers.State.DELETE:
|
|
state = "delete";
|
|
}
|
|
if (state) {
|
|
node.setAttribute("action", state);
|
|
}
|
|
}
|
|
},
|
|
|
|
CLASS_NAME: "OpenLayers.Format.OSM"
|
|
});
|