Source: OverpassWay.js

  1. /* global L:false */
  2. const async = require('async')
  3. const BoundingBox = require('boundingbox')
  4. const OverpassObject = require('./OverpassObject')
  5. const OverpassFrontend = require('./defines')
  6. const turf = require('./turf')
  7. /**
  8. * A way
  9. * @property {string} id ID of this object, starting with 'w'.
  10. * @property {number} osm_id Numeric id.
  11. * @property {string} type Type: 'way'.
  12. * @property {object} tags OpenStreetMap tags.
  13. * @property {object} meta OpenStreetMap meta information.
  14. * @property {Point[]} geometry of the object
  15. * @property {object} data Data as loaded from Overpass API.
  16. * @property {bit_array} properties Which information about this object is known?
  17. * @property {object[]} memberOf List of relations where this object is member of.
  18. * @property {string} memberOf.id ID of the relation where this way is member of.
  19. * @property {string} memberOf.role Role of this object in the relation.
  20. * @property {number} memberOf.sequence This object is the nth member in the relation.
  21. * @property {BoundingBox} bounds Bounding box of this object.
  22. * @property {Point} center Centroid of the bounding box.
  23. * @property {object[]} members Nodes of the way.
  24. * @property {string} members.id ID of the member.
  25. * @property {number} members.ref Numeric ID of the member.
  26. * @property {string} members.type 'node'.
  27. */
  28. class OverpassWay extends OverpassObject {
  29. updateData (data, options) {
  30. if (data.nodes) {
  31. this.nodes = data.nodes
  32. }
  33. if (data.geometry) {
  34. this.geometry = data.geometry
  35. this.properties |= OverpassFrontend.GEOM
  36. }
  37. super.updateData(data, options)
  38. if (typeof this.data.nodes !== 'undefined') {
  39. this.members = []
  40. this.properties |= OverpassFrontend.MEMBERS
  41. for (let i = 0; i < this.data.nodes.length; i++) {
  42. this.members.push({
  43. id: 'n' + this.data.nodes[i],
  44. ref: this.data.nodes[i],
  45. type: 'node'
  46. })
  47. let obProperties = OverpassFrontend.ID_ONLY
  48. const ob = {
  49. id: this.data.nodes[i],
  50. type: 'node'
  51. }
  52. if (data.geometry && data.geometry[i]) {
  53. obProperties = obProperties | OverpassFrontend.GEOM
  54. ob.lat = data.geometry[i].lat
  55. ob.lon = data.geometry[i].lon
  56. }
  57. const memberOb = this.overpass.createOrUpdateOSMObject(ob, {
  58. properties: obProperties
  59. })
  60. memberOb.notifyMemberOf(this, null, i)
  61. }
  62. }
  63. this.checkGeometry()
  64. }
  65. notifyMemberUpdate (memberObs) {
  66. super.notifyMemberUpdate(memberObs)
  67. this.checkGeometry()
  68. }
  69. checkGeometry () {
  70. if (this.members && (this.properties & OverpassFrontend.GEOM) === 0) {
  71. this.geometry = this.members.map(
  72. member => {
  73. const node = this.overpass.cacheElements[member.id]
  74. return node ? node.geometry : null
  75. }
  76. ).filter(geom => geom)
  77. if (this.geometry.length === 0) {
  78. delete this.geometry
  79. return
  80. }
  81. if (this.geometry.length === this.members.length) {
  82. this.properties = this.properties | OverpassFrontend.GEOM
  83. }
  84. }
  85. if (this.geometry && (this.properties & OverpassFrontend.BBOX) === 0) {
  86. this.bounds = new BoundingBox(this.geometry[0])
  87. this.geometry.slice(1).forEach(geom => this.bounds.extend(geom))
  88. }
  89. if (this.bounds && (this.properties & OverpassFrontend.CENTER) === 0) {
  90. this.center = this.bounds.getCenter()
  91. }
  92. if ((this.properties & OverpassFrontend.GEOM) === OverpassFrontend.GEOM) {
  93. this.properties = this.properties | OverpassFrontend.BBOX | OverpassFrontend.CENTER
  94. }
  95. }
  96. memberIds () {
  97. if (this._memberIds) {
  98. return this._memberIds
  99. }
  100. if (!this.nodes) {
  101. return null
  102. }
  103. this._memberIds = []
  104. for (let i = 0; i < this.nodes.length; i++) {
  105. const member = this.nodes[i]
  106. this._memberIds.push('n' + member)
  107. }
  108. return this._memberIds
  109. }
  110. member_ids () { // eslint-disable-line
  111. console.log('called deprecated OverpassWay.member_ids() function - replace by memberIds()')
  112. return this.memberIds()
  113. }
  114. GeoJSON () {
  115. const result = {
  116. type: 'Feature',
  117. id: this.type + '/' + this.osm_id,
  118. properties: this.GeoJSONProperties()
  119. }
  120. if (this.geometry) {
  121. const coordinates = this.geometry
  122. .filter(point => point) // discard non-loaded points
  123. .map(point => [point.lon, point.lat])
  124. const isClosed = coordinates.length > 1 && this.members && this.members[0].id === this.members[this.members.length - 1].id
  125. if (isClosed) {
  126. result.geometry = {
  127. type: 'Polygon',
  128. coordinates: [coordinates]
  129. }
  130. } else {
  131. result.geometry = {
  132. type: 'LineString',
  133. coordinates: coordinates
  134. }
  135. }
  136. }
  137. return result
  138. }
  139. exportOSMXML (options, parentNode, callback) {
  140. super.exportOSMXML(options, parentNode,
  141. (err, result) => {
  142. if (err) {
  143. return callback(err)
  144. }
  145. if (!result) { // already included
  146. return callback(null)
  147. }
  148. if (this.members) {
  149. async.each(this.members,
  150. (member, done) => {
  151. const memberOb = this.overpass.cacheElements[member.id]
  152. const nd = parentNode.ownerDocument.createElement('nd')
  153. nd.setAttribute('ref', memberOb.osm_id)
  154. result.appendChild(nd)
  155. memberOb.exportOSMXML(options, parentNode, done)
  156. },
  157. (err) => {
  158. callback(err, result)
  159. }
  160. )
  161. } else {
  162. callback(null, result)
  163. }
  164. }
  165. )
  166. }
  167. exportOSMJSON (conf, elements, callback) {
  168. super.exportOSMJSON(conf, elements,
  169. (err, result) => {
  170. if (err) {
  171. return callback(err)
  172. }
  173. if (!result) { // already included
  174. return callback(null)
  175. }
  176. if (this.members) {
  177. result.nodes = []
  178. async.each(this.members,
  179. (member, done) => {
  180. const memberOb = this.overpass.cacheElements[member.id]
  181. result.nodes.push(memberOb.osm_id)
  182. memberOb.exportOSMJSON(conf, elements, done)
  183. },
  184. (err) => {
  185. callback(err, result)
  186. }
  187. )
  188. } else {
  189. callback(null, result)
  190. }
  191. }
  192. )
  193. }
  194. /**
  195. * return a leaflet feature for this object. If the ways is closed, a L.polygon will be returned, otherwise a L.polyline.
  196. * @param {object} [options] options Options will be passed to the leaflet function
  197. * @param {number[]} [options.shiftWorld=[0, 0]] Shift western (negative) longitudes by shiftWorld[0], eastern (positive) longitudes by shiftWorld[1] (e.g. by 360, 0 to show objects around lon=180)
  198. * @return {L.layer}
  199. */
  200. leafletFeature (options = {}) {
  201. if (!this.geometry) {
  202. return null
  203. }
  204. if (!('shiftWorld' in options)) {
  205. options.shiftWorld = [0, 0]
  206. }
  207. const geom = this.geometry
  208. .filter(g => g)
  209. .map(g => {
  210. return { lat: g.lat, lon: g.lon + options.shiftWorld[g.lon < 0 ? 0 : 1] }
  211. })
  212. if (this.geometry[this.geometry.length - 1] && this.geometry[0] &&
  213. this.geometry[this.geometry.length - 1].lat === this.geometry[0].lat &&
  214. this.geometry[this.geometry.length - 1].lon === this.geometry[0].lon) {
  215. return L.polygon(geom, options)
  216. }
  217. return L.polyline(geom, options)
  218. }
  219. intersects (bbox) {
  220. const result = super.intersects(bbox)
  221. if (result === 0 || result === 2) {
  222. return result
  223. }
  224. if (this.geometry) {
  225. let intersects
  226. if (bbox.toGeoJSON) {
  227. // bbox is BoundingBox
  228. intersects = turf.booleanIntersects(this.GeoJSON(), bbox.toGeoJSON())
  229. } else {
  230. // bbox is GeoJSON
  231. intersects = turf.booleanIntersects(this.GeoJSON(), bbox)
  232. }
  233. return intersects ? 2 : 0
  234. }
  235. return 1
  236. }
  237. }
  238. module.exports = OverpassWay