You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

425 lines
12 KiB

7 years ago
7 years ago
  1. var OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader')
  2. var OverpassLayer = require('overpass-layer')
  3. var OverpassLayerList = require('overpass-layer').List
  4. var CategoryBase = require('./CategoryBase')
  5. var state = require('./state')
  6. var tabs = require('modulekit-tabs')
  7. var markers = require('./markers')
  8. var maki = require('./maki')
  9. var qs = require('sheet-router/qs')
  10. var defaultValues = {
  11. feature: {
  12. title: "{{ localizedTag(tags, 'name') |default(localizedTag(tags, 'operator')) | default(localizedTag(tags, 'ref')) | default(trans('unnamed')) }}",
  13. markerSign: '',
  14. 'style:hover': {
  15. color: 'black',
  16. width: 3,
  17. opacity: 1,
  18. radius: 12,
  19. pane: 'hover'
  20. },
  21. 'style:selected': {
  22. color: '#3f3f3f',
  23. width: 3,
  24. opacity: 1,
  25. radius: 12,
  26. pane: 'selected'
  27. },
  28. markerSymbol: "{{ markerPointer({})|raw }}",
  29. listMarkerSymbol: "{{ markerCircle({})|raw }}",
  30. preferredZoom: 16
  31. },
  32. queryOptions: {
  33. split: 64
  34. }
  35. }
  36. CategoryOverpass.prototype = Object.create(CategoryBase.prototype)
  37. CategoryOverpass.prototype.constructor = CategoryOverpass
  38. function CategoryOverpass (options, data) {
  39. var p
  40. CategoryBase.call(this, options, data)
  41. data.id = this.id
  42. // set undefined data properties from defaultValues
  43. for (var k1 in defaultValues) {
  44. if (!(k1 in data)) {
  45. data[k1] = defaultValues[k1]
  46. } else if (typeof defaultValues[k1] === 'object') {
  47. for (var k2 in defaultValues[k1]) {
  48. if (!(k2 in data[k1])) {
  49. data[k1][k2] = defaultValues[k1][k2]
  50. } else if (typeof defaultValues[k1][k2] === 'object') {
  51. for (var k3 in defaultValues[k1][k2]) {
  52. if (!(k3 in data[k1][k2])) {
  53. data[k1][k2][k3] = defaultValues[k1][k2][k3]
  54. }
  55. }
  56. }
  57. }
  58. }
  59. }
  60. // get minZoom
  61. if ('minZoom' in data) {
  62. // has minZoom
  63. } else if (typeof data.query === 'object') {
  64. data.minZoom = Object.keys(data.query)[0]
  65. } else {
  66. data.minZoom = 14
  67. }
  68. data.feature.appUrl = '#' + this.id + '/{{ id }}'
  69. data.styleNoBindPopup = [ 'hover', 'selected' ]
  70. data.stylesNoAutoShow = [ 'hover', 'selected' ]
  71. data.updateAssets = this.updateAssets.bind(this)
  72. this.layer = new OverpassLayer(data)
  73. this.layer.onLoadStart = function (ev) {
  74. this.dom.classList.add('loading')
  75. if (this.parentCategory) {
  76. this.parentCategory.notifyChildLoadStart(this)
  77. }
  78. }.bind(this)
  79. this.layer.onLoadEnd = function (ev) {
  80. this.dom.classList.remove('loading')
  81. if (this.parentCategory) {
  82. this.parentCategory.notifyChildLoadEnd(this)
  83. }
  84. if (ev.error && ev.error !== 'abort') {
  85. alert('Error loading data from Overpass API: ' + ev.error)
  86. }
  87. }.bind(this)
  88. this.layer.onAppear = function (ob) {
  89. // HOVER
  90. if (ob.listItem) {
  91. ob.listItem.onmouseover = function (id) {
  92. if (this.currentHover) {
  93. this.currentHover.hide()
  94. }
  95. this.currentHover = this.layer.show(id, { styles: [ 'hover' ] }, function () {})
  96. }.bind(this, ob.id)
  97. ob.listItem.onmouseout = function (id) {
  98. if (this.currentHover) {
  99. this.currentHover.hide()
  100. }
  101. this.currentHover = null
  102. }.bind(this, ob.id)
  103. }
  104. }.bind(this)
  105. this.layer.onUpdate = function (ob) {
  106. if (!ob.popup || !ob.popup._contentNode || map._popup !== ob.popup) {
  107. return
  108. }
  109. this.updatePopupContent(ob, ob.popup)
  110. if (document.getElementById('content').className === 'details') {
  111. showDetails(ob, this)
  112. }
  113. }.bind(this)
  114. p = document.createElement('div')
  115. p.className = 'loadingIndicator'
  116. p.innerHTML = '<i class="fa fa-spinner fa-pulse fa-fw"></i><span class="sr-only">' + lang('loading') + '</span>'
  117. this.dom.appendChild(p)
  118. p = document.createElement('div')
  119. p.className = 'loadingIndicator2'
  120. p.innerHTML = '<div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div>'
  121. this.dom.appendChild(p)
  122. this.domStatus = document.createElement('div')
  123. this.domStatus.className = 'status'
  124. this.dom.appendChild(this.domStatus)
  125. register_hook('state-get', function (state) {
  126. if (this.isOpen) {
  127. if (state.categories) {
  128. state.categories += ','
  129. } else {
  130. state.categories = ''
  131. }
  132. state.categories += this.id
  133. }
  134. }.bind(this))
  135. register_hook('state-apply', function (state) {
  136. if (!('categories' in state)) {
  137. return
  138. }
  139. var list = state.categories.split(',')
  140. if (list.indexOf(this.id) === -1) {
  141. this.close()
  142. }
  143. // opening categories is handled by src/categories.js
  144. }.bind(this))
  145. }
  146. CategoryOverpass.prototype.updateAssets = function (div) {
  147. var imgs = div.getElementsByTagName('img')
  148. for (var i = 0; i < imgs.length; i++) {
  149. let img = imgs[i]
  150. var src = img.getAttribute('src')
  151. if (src === null) {
  152. } else if (src.match(/^maki:.*/)) {
  153. let m = src.match(/^maki:([a-z0-9\-]*)(?:\?(.*))?$/)
  154. if (m) {
  155. let span = document.createElement('span')
  156. img.parentNode.insertBefore(span, img)
  157. img.parentNode.removeChild(img)
  158. i--
  159. maki(m[1], m[2] ? qs(m[2]) : {}, function (err, result) {
  160. if (err === null) {
  161. span.innerHTML = result
  162. }
  163. })
  164. }
  165. } else if (!src.match(/^(https?:|data:|\.|\/)/)) {
  166. img.setAttribute('src', (typeof openstreetbrowserPrefix === 'undefined' ? './' : openstreetbrowserPrefix) +
  167. 'asset.php?repo=' + this.options.repositoryId + '&file=' + encodeURIComponent(img.getAttribute('src')))
  168. }
  169. }
  170. }
  171. CategoryOverpass.prototype.load = function (callback) {
  172. OpenStreetBrowserLoader.getTemplate('popupBody', this.options, function (err, template) {
  173. if (err) {
  174. console.log("can't load popupBody.html")
  175. } else {
  176. this.popupBodyTemplate = template
  177. }
  178. callback(null)
  179. }.bind(this))
  180. }
  181. CategoryOverpass.prototype.setMap = function (map) {
  182. CategoryBase.prototype.setMap.call(this, map)
  183. this.map.on('zoomend', function () {
  184. this.updateStatus()
  185. this.updateInfo()
  186. }.bind(this))
  187. this.updateStatus()
  188. this.updateInfo()
  189. }
  190. CategoryOverpass.prototype.updateStatus = function () {
  191. this.domStatus.innerHTML = ''
  192. if (typeof this.data.query === 'object') {
  193. var highestZoom = Object.keys(this.data.query).reverse()[0]
  194. if (this.map.getZoom() < highestZoom) {
  195. this.domStatus.innerHTML = lang('zoom_in_more')
  196. }
  197. }
  198. if ('minZoom' in this.data && this.map.getZoom() < this.data.minZoom) {
  199. this.domStatus.innerHTML = lang('zoom_in_appear')
  200. }
  201. }
  202. CategoryOverpass.prototype._getMarker = function (ob) {
  203. if (ob.data.listMarkerSymbol.trim() == 'line') {
  204. var div = document.createElement('div')
  205. div.className = 'marker'
  206. div.innerHTML = markers.line(ob.data)
  207. return div
  208. } else if (ob.data.listMarkerSymbol.trim() == 'polygon') {
  209. var div = document.createElement('div')
  210. div.className = 'marker'
  211. div.innerHTML = markers.polygon(ob.data)
  212. return div
  213. }
  214. return this.origGetMarker(ob)
  215. }
  216. CategoryOverpass.prototype.open = function () {
  217. if (this.isOpen) {
  218. return
  219. }
  220. CategoryBase.prototype.open.call(this)
  221. this.layer.addTo(this.map)
  222. if (!this.list) {
  223. this.list = new OverpassLayerList(this.domContent, this.layer)
  224. this.origGetMarker = this.list._getMarker
  225. this.list._getMarker = this._getMarker.bind(this)
  226. }
  227. this.isOpen = true
  228. state.update()
  229. if ('info' in this.data) {
  230. if (!this.tabInfo) {
  231. this.tabInfo = new tabs.Tab({
  232. id: 'info'
  233. })
  234. this.tools.add(this.tabInfo)
  235. this.tabInfo.header.innerHTML = '<i class="fa fa-info-circle" aria-hidden="true"></i>'
  236. this.tabInfo.header.title = lang('category-info-tooltip')
  237. this.domInfo = this.tabInfo.content
  238. this.domInfo.classList.add('info')
  239. this.templateInfo = OverpassLayer.twig.twig({ data: this.data.info, autoescape: true })
  240. }
  241. this.updateInfo()
  242. }
  243. }
  244. CategoryOverpass.prototype.updateInfo = function () {
  245. if (!this.tabInfo) {
  246. return
  247. }
  248. global.currentCategory = this
  249. var data = {
  250. layer_id: this.id,
  251. 'const': this.data.const,
  252. }
  253. if (this.map) {
  254. data.map = { zoom: map.getZoom() }
  255. }
  256. this.domInfo.innerHTML = this.templateInfo.render(data)
  257. this.updateAssets(this.domInfo)
  258. global.currentCategory = null
  259. }
  260. CategoryOverpass.prototype.recalc = function () {
  261. this.layer.recalc()
  262. }
  263. CategoryOverpass.prototype.close = function () {
  264. if (!this.isOpen) {
  265. return
  266. }
  267. CategoryBase.prototype.close.call(this)
  268. this.layer.remove()
  269. this.list.remove()
  270. state.update()
  271. }
  272. CategoryOverpass.prototype.get = function (id, callback) {
  273. this.layer.get(id, callback)
  274. }
  275. CategoryOverpass.prototype.show = function (id, options, callback) {
  276. if (this.currentDetails) {
  277. this.currentDetails.hide()
  278. }
  279. this.currentDetails = this.layer.show(id,
  280. {
  281. styles: [ 'selected' ]
  282. },
  283. function (err, data) {
  284. if (!err) {
  285. if (options.showDetails && !options.hasLocation) {
  286. var preferredZoom = data.data.preferredZoom || 16
  287. var maxZoom = this.map.getZoom()
  288. maxZoom = maxZoom > preferredZoom ? maxZoom : preferredZoom
  289. this.map.flyToBounds(data.object.bounds.toLeaflet(), {
  290. maxZoom: maxZoom
  291. })
  292. }
  293. }
  294. callback(err, data)
  295. }.bind(this)
  296. )
  297. }
  298. CategoryOverpass.prototype.notifyPopupOpen = function (object, popup) {
  299. if (this.currentSelected) {
  300. this.currentSelected.hide()
  301. }
  302. this.updatePopupContent(object, popup)
  303. this.currentSelected = this.layer.show(object.id, { styles: [ 'selected' ] }, function () {})
  304. }
  305. CategoryOverpass.prototype.notifyPopupClose = function (object, popup) {
  306. if (this.currentSelected) {
  307. this.currentSelected.hide()
  308. this.currentSelected = null
  309. }
  310. if (this.currentDetails) {
  311. this.currentDetails.hide()
  312. this.currentDetails = null
  313. }
  314. }
  315. CategoryOverpass.prototype.updatePopupContent = function (object, popup) {
  316. if (object.data.popupDescription || object.data.description) {
  317. var div = document.createElement('div')
  318. div.className = 'description'
  319. div.innerHTML = object.data.popupDescription || object.data.description
  320. popup._contentNode.insertBefore(div, popup._contentNode.firstChild.nextSibling)
  321. }
  322. if (this.popupBodyTemplate) {
  323. var popupBody = document.createElement('div')
  324. popupBody.className = 'popupBody'
  325. popup._contentNode.appendChild(popupBody)
  326. var data = this.layer.twigData(object.object)
  327. popupBody.innerHTML = this.popupBodyTemplate.render(data)
  328. }
  329. var footer = document.createElement('ul')
  330. footer.className = 'popup-footer'
  331. var footerContent = '<li><a class="showDetails" href="#' + this.id + '/' + object.id + '/details">' + lang('show details') + '</a></li>'
  332. footerContent += '<li><a target="_blank" class="editLink" href="https://www.openstreetmap.org/edit?editor=id&' + object.object.type + '=' + object.object.osm_id + '">' + lang('edit') + '</a></li>'
  333. footer.innerHTML = footerContent
  334. popup._contentNode.appendChild(footer)
  335. call_hooks_callback('show-popup', object, this, popup._contentNode,
  336. function (err) {
  337. if (err.length) {
  338. console.log('show-popup produced errors:', err)
  339. }
  340. }
  341. )
  342. }
  343. CategoryOverpass.prototype.renderTemplate = function (object, templateId, callback) {
  344. OpenStreetBrowserLoader.getTemplate(templateId, this.options, function (err, template) {
  345. if (err) {
  346. err = "can't load " + templateId + ': ' + err
  347. return callback(err, null)
  348. }
  349. var data = this.layer.twigData(object.object)
  350. var result = template.render(data)
  351. callback(null, result)
  352. }.bind(this))
  353. }
  354. OpenStreetBrowserLoader.registerType('overpass', CategoryOverpass)
  355. module.exports = CategoryOverpass