OSM.js 16 KB

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