├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── demo ├── index.html ├── marker.png ├── multi_layer_example.html ├── ways-example.html └── ways-example.js ├── dist ├── OverPassLayer.css ├── OverPassLayer.js └── OverPassLayer.min.js ├── gulpfile.js ├── package.json └── src ├── MinZoomIndicator.js └── OverPassLayer.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Karsten Hinz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | Please note that this software includes other libraries or parts of libraries which have their own license file or license annotation in the source code and may be released under different licences. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Leaflet Layer OverPass 2 | ============================= 3 | [![Bower version](https://badge.fury.io/bo/leaflet-layer-overpass.svg)](http://badge.fury.io/bo/leaflet-layer-overpass) 4 | 5 | ## What is it? 6 | A [Leaflet](http://leafletjs.com/) plugin to create a custom POI overlay - thanks to the [OSM](http://www.openstreetmap.org/) dataset and the [Overpass API](http://overpass-api.de/) 7 | 8 | checkout the [demo](http://kartenkarsten.github.io/leaflet-layer-overpass/demo/) 9 | 10 | 11 | ## Installation 12 | You can use bower to install leaflet-layer-overpass. 13 | 14 | Simply run 15 | ```bash 16 | $ bower install --save leaflet-layer-overpass 17 | ``` 18 | After that you can include and use the `OverpassLayer.css` and `OverpassLayer.js` files (or `OverPassLayer.min.js` if you want the minified version) from the `dist` folder in your html. 19 | 20 | ## How to use it? 21 | ```javascript 22 | var attr_osm = 'Map data © OpenStreetMap contributors', 23 | attr_overpass = 'POI via Overpass API'; 24 | var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {opacity: 0.7, attribution: [attr_osm, attr_overpass].join(', ')}); 25 | 26 | var map = new L.Map('map').addLayer(osm).setView(new L.LatLng(52.265, 10.524), 14); 27 | 28 | //OverPassAPI overlay 29 | var opl = new L.OverPassLayer({ 30 | query: "node(BBOX)['amenity'='post_box'];out;", 31 | }); 32 | 33 | map.addLayer(opl); 34 | ``` 35 | In order to get a valid query the [Overpass-turbo IDE](http://overpass-turbo.eu/) might help. 36 | 37 | ## What are the options? 38 | You can specify an options object as an argument of L.OverPassLayer. 39 | ```javascript 40 | options: { 41 | endpoint: "http://overpass.osm.rambler.ru/cgi/", 42 | query: "node(BBOX)['amenity'='post_box'];out;", 43 | debug: false, 44 | callback: function(data) { 45 | for(var i=0;i geoJSON to -> Leaflet Layer to support ways and areas as well (see also [PoiMap](https://github.com/simon04/POImap/blob/master/railway.html), [OverPassTurbo](https://github.com/tyrasd/overpass-ide/blob/gh-pages/js/overpass.js)) 99 | - improve popup text. use links, format addresses and contact details (compare with [OpenLinkMap](http://www.openlinkmap.org/)) 100 | - improve caching - allow to store data for some days in browser 101 | 102 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-layer-overpass", 3 | "version": "1.0.2", 4 | "homepage": "https://github.com/kartenkarsten/leaflet-layer-overpass", 5 | "authors": [ 6 | "Karsten Hinz ", 7 | "Knut Hühne " 8 | ], 9 | "description": "simply add an overpass layer to a leafleat map", 10 | "main": "OverPassLayer.js", 11 | "keywords": [ 12 | "leaflet", 13 | "overpass", 14 | "openstreetmap", 15 | "osm", 16 | "features", 17 | "layer" 18 | ], 19 | "license": "MIT", 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "demo", 25 | "test", 26 | "tests" 27 | ], 28 | "dependencies": { 29 | "leaflet": "~0.7.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OverPass Layer Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 22 | 23 |
24 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /demo/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartenkarsten/leaflet-layer-overpass/f3ae9897489341d879a2070b112f69557d933e72/demo/marker.png -------------------------------------------------------------------------------- /demo/multi_layer_example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OverPass Layer Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 22 | 23 |
24 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /demo/ways-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | OverPass Layer Demo - way highlighting 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/ways-example.js: -------------------------------------------------------------------------------- 1 | /* 2 | This javascript file demonstrates how to put markers over Ways returned from an Overpass query. 3 | 4 | Author: Philip Shore (pshore2@gmail.com) 5 | */ 6 | 7 | function startmap(map_css_id) { 8 | 9 | var nearAldreth = [52.35624, 0.11827]; 10 | 11 | var mapnikLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 12 | attribution: '© OpenStreetMap'}) ; 13 | 14 | 15 | var overPassOverlay = TrackAccessOverPassLayer(); 16 | 17 | // Start the map with this view. 18 | var map = L.map(map_css_id, { 19 | center: nearAldreth, 20 | zoom: 16, 21 | layers: [ mapnikLayer, overPassOverlay ] 22 | }); 23 | 24 | var baseLayers = { "Streets":mapnikLayer }; 25 | var overlays = { "Track Access":overPassOverlay }; 26 | 27 | L.control.layers( baseLayers , overlays ).addTo(map); 28 | } 29 | 30 | 31 | /** Track Access Overlay */ 32 | function TrackAccessOverPassLayer() { 33 | 34 | //var endpoint = "http://overpass-api.de/api/"; 35 | var endpoint = "http://overpass.osm.rambler.ru/cgi/"; 36 | 37 | var options = { 38 | endpoint: endpoint, 39 | minzoom: 14, 40 | query: "(way[highway='track'](BBOX);>;);out;", 41 | callback: function(data) { 42 | 43 | // create an easy lookup for ways and nodes by id. 44 | var typeDict = { }; // Eg: typeDict['way']['1234'] 45 | typeDict['node'] = {}; 46 | typeDict['way'] = {}; 47 | 48 | //console.log("Query returned "+data.elements.length+" elements."); 49 | 50 | for(i=0;i bicycle -> motorcyle -> motorcar 101 | // yellow -> green -> blue -> pink 102 | // 103 | // adds dashes 104 | // permissive = small space dash. 105 | // undef access = 106 | 107 | if(tags.access=="private") { 108 | return null; 109 | } 110 | 111 | try { 112 | // add access tags based designation, overrides. 113 | if(tags.designation==="byway_open_to_all_traffic") { 114 | if(tags.motor_vehicle==null) { tags.motor_vehicle="yes"; } 115 | if(tags.bicycle==null) { tags.bicycle="yes"; } 116 | if(tags.foot==null) { tags.foot="yes"; } 117 | } 118 | else if(tags.designation==="restricted_byway") { 119 | // rely on access tags otherwise defaults to yellow 120 | tags.access="permissive"; 121 | } 122 | else if(tags.designation==="public_bridleway" || tags.designation==="permissive_bridleway" ) { 123 | if(tags.bicycle==null) { tags.bicycle="yes"; } 124 | if(tags.foot==null) { tags.foot="yes"; } 125 | } 126 | else if(tags.designation==="public_footpath" || tags.designation==="permissive_footpath" ) { 127 | if(tags.foot==null) { tags.foot="yes"; } 128 | } 129 | 130 | // convert designated value to yes. 131 | for (var key in tags) { 132 | if (tags.hasOwnProperty(key) && tags[key]==='designated') { 133 | tags[key]='yes'; 134 | } 135 | } 136 | 137 | /* -- return style --*/ 138 | var motorcarcol ='#CC00CC';// pink 139 | var motorcyclecol='#3399FF';// blue 140 | var bicyclecol ='#33CC33';// green 141 | var footcol ='#FF8080';// red; 142 | var othercol ='#FFFF00';// yellow 143 | var permissivedash='10,5'; 144 | var otherdash ='5,10'; 145 | 146 | // These are the supported tags in our algorithm: 147 | if (tags.designation!=null && 148 | tags.designation!=='byway_open_to_all_traffic' && 149 | tags.designation!=='restricted_byway' && 150 | tags.designation!=='public_bridleway' && 151 | tags.designation!=='public_footpath' && 152 | tags.designation!=='permissive_footpath' && 153 | tags.designation!=='permissive_bridleway') 154 | { 155 | // tag needs correcting? 156 | return { color: '#000000', dashArray:otherdash } 157 | } 158 | 159 | // try to mark up full-time non-permissive first. 160 | if(tags.motor_vehicle==="yes" || tags.motorcar==="yes") { 161 | return { color: motorcarcol } ; 162 | } else if ( tags.motorcycle==="yes") { 163 | return { color: motorcyclecol } ; 164 | } else if ( tags.bicycle==="yes" ) { 165 | return { color: bicyclecol } ; 166 | } else if ( tags.foot==="yes" ) { 167 | return { color: footcol } ; 168 | } else if(tags.motor_vehicle==="permissive" || tags.motorcar==="permissive") { 169 | return { color: motorcarcol, dashArray:permissivedash } ; 170 | } else if ( tags.motorcycle==="permissive") { 171 | return { color: motorcyclecol, dashArray:permissivedash } ; 172 | } else if ( tags.bicycle==="permissive" ) { 173 | return { color: bicyclecol, dashArray:permissivedash } ; 174 | } else if ( tags.foot==="permissive" ) { 175 | return { color: footcol, dashArray:permissivedash } ; 176 | } else if ( tags.designation && tags.access==='permissive' ) { 177 | return { color: othercol, dashArray:permissivedash } ; 178 | } else if ( tags.designation ) { 179 | return { color: othercol } ; 180 | } else { 181 | return { color: othercol, dashArray:otherdash } ; 182 | } 183 | } 184 | catch(err) { 185 | return { color: '#000000' } 186 | } 187 | } 188 | 189 | -------------------------------------------------------------------------------- /dist/OverPassLayer.css: -------------------------------------------------------------------------------- 1 | .leaflet-control-minZoomIndicator { 2 | font-size: 2em; 3 | background: #ffffff; 4 | background-color: rgba(255,255,255,0.7); 5 | border-radius: 10px; 6 | padding: 1px 15px; 7 | opacity: 0.5; 8 | } 9 | -------------------------------------------------------------------------------- /dist/OverPassLayer.js: -------------------------------------------------------------------------------- 1 | L.Control.MinZoomIndicator = L.Control.extend({ 2 | options: { 3 | position: 'bottomleft', 4 | }, 5 | 6 | /** 7 | * map: layerId -> zoomlevel 8 | */ 9 | _layers: {}, 10 | 11 | /** TODO check if nessesary 12 | */ 13 | initialize: function (options) { 14 | L.Util.setOptions(this, options); 15 | this._layers = new Object(); 16 | }, 17 | 18 | /** 19 | * adds a layer with minzoom information to this._layers 20 | */ 21 | _addLayer: function(layer) { 22 | var minzoom = 15; 23 | if (layer.options.minzoom) { 24 | minzoom = layer.options.minzoom; 25 | } 26 | this._layers[layer._leaflet_id] = minzoom; 27 | this._updateBox(null); 28 | }, 29 | 30 | /** 31 | * removes a layer from this._layers 32 | */ 33 | _removeLayer: function(layer) { 34 | this._layers[layer._leaflet_id] = null; 35 | this._updateBox(null); 36 | }, 37 | 38 | _getMinZoomLevel: function() { 39 | var minZoomlevel=-1; 40 | for(var key in this._layers) { 41 | if ((this._layers[key] != null)&&(this._layers[key] > minZoomlevel)) { 42 | minZoomlevel = this._layers[key]; 43 | } 44 | } 45 | return minZoomlevel; 46 | }, 47 | 48 | onAdd: function (map) { 49 | this._map = map; 50 | map.zoomIndicator = this; 51 | 52 | var className = this.className; 53 | var container = this._container = L.DomUtil.create('div', className); 54 | map.on('moveend', this._updateBox, this); 55 | this._updateBox(null); 56 | 57 | // L.DomEvent.disableClickPropagation(container); 58 | return container; 59 | }, 60 | 61 | onRemove: function(map) { 62 | L.Control.prototype.onRemove.call(this, map); 63 | map.off({ 64 | 'moveend': this._updateBox 65 | }, this); 66 | 67 | this._map = null; 68 | }, 69 | 70 | _updateBox: function (event) { 71 | //console.log("map moved -> update Container..."); 72 | if (event != null) { 73 | L.DomEvent.preventDefault(event); 74 | } 75 | var minzoomlevel = this._getMinZoomLevel(); 76 | if (minzoomlevel == -1) { 77 | this._container.innerHTML = this.options.minZoomMessageNoLayer; 78 | }else{ 79 | this._container.innerHTML = this.options.minZoomMessage 80 | .replace(/CURRENTZOOM/, this._map.getZoom()) 81 | .replace(/MINZOOMLEVEL/, minzoomlevel); 82 | } 83 | 84 | if (this._map.getZoom() >= minzoomlevel) { 85 | this._container.style.display = 'none'; 86 | }else{ 87 | this._container.style.display = 'block'; 88 | } 89 | }, 90 | 91 | className : 'leaflet-control-minZoomIndicator' 92 | }); 93 | 94 | L.LatLngBounds.prototype.toOverpassBBoxString = function (){ 95 | var a = this._southWest, 96 | b = this._northEast; 97 | return [a.lat, a.lng, b.lat, b.lng].join(","); 98 | } 99 | 100 | L.OverPassLayer = L.FeatureGroup.extend({ 101 | options: { 102 | debug: false, 103 | minzoom: 15, 104 | endpoint: "http://overpass-api.de/api/", 105 | query: "(node(BBOX)[organic];node(BBOX)[second_hand];);out qt;", 106 | callback: function(data) { 107 | for(var i = 0; i < data.elements.length; i++) { 108 | var e = data.elements[i]; 109 | 110 | if (e.id in this.instance._ids) continue; 111 | this.instance._ids[e.id] = true; 112 | var pos; 113 | if (e.type === "node") { 114 | pos = new L.LatLng(e.lat, e.lon); 115 | } else { 116 | pos = new L.LatLng(e.center.lat, e.center.lon); 117 | } 118 | var popup = this.instance._poiInfo(e.tags,e.id); 119 | var circle = L.circle(pos, 50, { 120 | color: 'green', 121 | fillColor: '#3f0', 122 | fillOpacity: 0.5 123 | }) 124 | .bindPopup(popup); 125 | this.instance.addLayer(circle); 126 | } 127 | }, 128 | beforeRequest: function() { 129 | if (this.options.debug) { 130 | console.debug('about to query the OverPassAPI'); 131 | } 132 | }, 133 | afterRequest: function() { 134 | if (this.options.debug) { 135 | console.debug('all queries have finished!'); 136 | } 137 | }, 138 | minZoomIndicatorOptions: { 139 | position: 'bottomleft', 140 | minZoomMessageNoLayer: "no layer assigned", 141 | minZoomMessage: "current Zoom-Level: CURRENTZOOM all data at Level: MINZOOMLEVEL" 142 | }, 143 | }, 144 | 145 | initialize: function (options) { 146 | L.Util.setOptions(this, options); 147 | this._layers = {}; 148 | // save position of the layer or any options from the constructor 149 | this._ids = {}; 150 | this._requested = {}; 151 | }, 152 | 153 | _poiInfo: function(tags,id) { 154 | var link = document.createElement("a"); 155 | link.href = "http://www.openstreetmap.org/edit?editor=id&node=" + id; 156 | link.appendChild(document.createTextNode("Edit this entry in iD")); 157 | var table = document.createElement('table'); 158 | for (var key in tags){ 159 | var row = table.insertRow(0); 160 | row.insertCell(0).appendChild(document.createTextNode(key)); 161 | row.insertCell(1).appendChild(document.createTextNode(tags[key])); 162 | } 163 | var div = document.createElement("div") 164 | div.appendChild(link); 165 | div.appendChild(table); 166 | return div; 167 | }, 168 | 169 | /** 170 | * splits the current view in uniform bboxes to allow caching 171 | */ 172 | long2tile: function (lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); }, 173 | lat2tile: function (lat,zoom) { 174 | return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); 175 | }, 176 | tile2long: function (x,z) { 177 | return (x/Math.pow(2,z)*360-180); 178 | }, 179 | tile2lat: function (y,z) { 180 | var n=Math.PI-2*Math.PI*y/Math.pow(2,z); 181 | return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)))); 182 | }, 183 | _view2BBoxes: function(l,b,r,t) { 184 | //console.log(l+"\t"+b+"\t"+r+"\t"+t); 185 | //this.addBBox(l,b,r,t); 186 | //console.log("calc bboxes"); 187 | var requestZoomLevel= 14; 188 | //get left tile index 189 | var lidx = this.long2tile(l,requestZoomLevel); 190 | var ridx = this.long2tile(r,requestZoomLevel); 191 | var tidx = this.lat2tile(t,requestZoomLevel); 192 | var bidx = this.lat2tile(b,requestZoomLevel); 193 | 194 | //var result; 195 | var result = new Array(); 196 | for (var x=lidx; x<=ridx; x++) { 197 | for (var y=tidx; y<=bidx; y++) {//in tiles tidx<=bidx 198 | var left = Math.round(this.tile2long(x,requestZoomLevel)*1000000)/1000000; 199 | var right = Math.round(this.tile2long(x+1,requestZoomLevel)*1000000)/1000000; 200 | var top = Math.round(this.tile2lat(y,requestZoomLevel)*1000000)/1000000; 201 | var bottom = Math.round(this.tile2lat(y+1,requestZoomLevel)*1000000)/1000000; 202 | //console.log(left+"\t"+bottom+"\t"+right+"\t"+top); 203 | //this.addBBox(left,bottom,right,top); 204 | //console.log("http://osm.org?bbox="+left+","+bottom+","+right+","+top); 205 | result.push( new L.LatLngBounds(new L.LatLng(bottom, left),new L.LatLng(top, right))); 206 | } 207 | } 208 | //console.log(result); 209 | return result; 210 | }, 211 | 212 | addBBox: function (l,b,r,t) { 213 | var polygon = L.polygon([ 214 | [t, l], 215 | [b, l], 216 | [b, r], 217 | [t, r] 218 | ]).addTo(this._map); 219 | }, 220 | 221 | onMoveEnd: function () { 222 | if (this.options.debug) { 223 | console.debug("load Pois"); 224 | } 225 | //console.log(this._map.getBounds()); 226 | if (this._map.getZoom() >= this.options.minzoom) { 227 | //var bboxList = new Array(this._map.getBounds()); 228 | var bboxList = this._view2BBoxes( 229 | this._map.getBounds()._southWest.lng, 230 | this._map.getBounds()._southWest.lat, 231 | this._map.getBounds()._northEast.lng, 232 | this._map.getBounds()._northEast.lat); 233 | 234 | // controls the after/before (Request) callbacks 235 | var finishedCount = 0; 236 | var queryCount = bboxList.length; 237 | var beforeRequest = true; 238 | 239 | for (var i = 0; i < bboxList.length; i++) { 240 | var bbox = bboxList[i]; 241 | var x = bbox._southWest.lng; 242 | var y = bbox._northEast.lat; 243 | if ((x in this._requested) && (y in this._requested[x]) && (this._requested[x][y] == true)) { 244 | queryCount--; 245 | continue; 246 | } 247 | if (!(x in this._requested)) { 248 | this._requested[x] = {}; 249 | } 250 | this._requested[x][y] = true; 251 | 252 | 253 | var queryWithMapCoordinates = this.options.query.replace(/(BBOX)/g, bbox.toOverpassBBoxString()); 254 | var url = this.options.endpoint + "interpreter?data=[out:json];" + queryWithMapCoordinates; 255 | 256 | if (beforeRequest) { 257 | this.options.beforeRequest.call(this); 258 | beforeRequest = false; 259 | } 260 | 261 | var self = this; 262 | var request = new XMLHttpRequest(); 263 | request.open("GET", url, true); 264 | 265 | request.onload = function() { 266 | if (this.status >= 200 && this.status < 400) { 267 | var reference = {instance: self}; 268 | self.options.callback.call(reference, JSON.parse(this.response)); 269 | if (self.options.debug) { 270 | console.debug('queryCount: ' + queryCount + ' - finishedCount: ' + finishedCount); 271 | } 272 | if (++finishedCount == queryCount) { 273 | self.options.afterRequest.call(self); 274 | } 275 | } 276 | }; 277 | 278 | request.send(); 279 | 280 | 281 | } 282 | } 283 | }, 284 | 285 | onAdd: function (map) { 286 | this._map = map; 287 | if (map.zoomIndicator) { 288 | this._zoomControl = map.zoomIndicator; 289 | this._zoomControl._addLayer(this); 290 | }else{ 291 | this._zoomControl = new L.Control.MinZoomIndicator(this.options.minZoomIndicatorOptions); 292 | map.addControl(this._zoomControl); 293 | this._zoomControl._addLayer(this); 294 | } 295 | 296 | this.onMoveEnd(); 297 | if (this.options.query.indexOf("(BBOX)") != -1) { 298 | map.on('moveend', this.onMoveEnd, this); 299 | } 300 | if (this.options.debug) { 301 | console.debug("add layer"); 302 | } 303 | }, 304 | 305 | onRemove: function (map) { 306 | if (this.options.debug) { 307 | console.debug("remove layer"); 308 | } 309 | L.LayerGroup.prototype.onRemove.call(this, map); 310 | this._ids = {}; 311 | this._requested = {}; 312 | this._zoomControl._removeLayer(this); 313 | 314 | map.off({ 315 | 'moveend': this.onMoveEnd 316 | }, this); 317 | 318 | this._map = null; 319 | }, 320 | 321 | getData: function () { 322 | if (this.options.debug) { 323 | console.debug(this._data); 324 | } 325 | return this._data; 326 | } 327 | 328 | }); 329 | 330 | //FIXME no idea why the browser crashes with this code 331 | //L.OverPassLayer = function (options) { 332 | // return new L.OverPassLayer(options); 333 | //}; 334 | -------------------------------------------------------------------------------- /dist/OverPassLayer.min.js: -------------------------------------------------------------------------------- 1 | L.Control.MinZoomIndicator=L.Control.extend({options:{position:"bottomleft"},_layers:{},initialize:function(t){L.Util.setOptions(this,t),this._layers=new Object},_addLayer:function(t){var e=15;t.options.minzoom&&(e=t.options.minzoom),this._layers[t._leaflet_id]=e,this._updateBox(null)},_removeLayer:function(t){this._layers[t._leaflet_id]=null,this._updateBox(null)},_getMinZoomLevel:function(){var t=-1;for(var e in this._layers)null!=this._layers[e]&&this._layers[e]>t&&(t=this._layers[e]);return t},onAdd:function(t){this._map=t,t.zoomIndicator=this;var e=this.className,o=this._container=L.DomUtil.create("div",e);return t.on("moveend",this._updateBox,this),this._updateBox(null),o},onRemove:function(t){L.Control.prototype.onRemove.call(this,t),t.off({moveend:this._updateBox},this),this._map=null},_updateBox:function(t){null!=t&&L.DomEvent.preventDefault(t);var e=this._getMinZoomLevel();-1==e?this._container.innerHTML=this.options.minZoomMessageNoLayer:this._container.innerHTML=this.options.minZoomMessage.replace(/CURRENTZOOM/,this._map.getZoom()).replace(/MINZOOMLEVEL/,e),this._map.getZoom()>=e?this._container.style.display="none":this._container.style.display="block"},className:"leaflet-control-minZoomIndicator"}),L.LatLngBounds.prototype.toOverpassBBoxString=function(){var t=this._southWest,e=this._northEast;return[t.lat,t.lng,e.lat,e.lng].join(",")},L.OverPassLayer=L.FeatureGroup.extend({options:{debug:!1,minzoom:15,endpoint:"http://overpass-api.de/api/",query:"(node(BBOX)[organic];node(BBOX)[second_hand];);out qt;",callback:function(t){for(var e=0;e=d;d++)for(var u=r;l>=u;u++){var p=Math.round(1e6*this.tile2long(d,i))/1e6,c=Math.round(1e6*this.tile2long(d+1,i))/1e6,m=Math.round(1e6*this.tile2lat(u,i))/1e6,_=Math.round(1e6*this.tile2lat(u+1,i))/1e6;h.push(new L.LatLngBounds(new L.LatLng(_,p),new L.LatLng(m,c)))}return h},addBBox:function(t,e,o,n){L.polygon([[n,t],[e,t],[e,o],[n,o]]).addTo(this._map)},onMoveEnd:function(){if(this.options.debug&&console.debug("load Pois"),this._map.getZoom()>=this.options.minzoom)for(var t=this._view2BBoxes(this._map.getBounds()._southWest.lng,this._map.getBounds()._southWest.lat,this._map.getBounds()._northEast.lng,this._map.getBounds()._northEast.lat),e=0,o=t.length,n=!0,i=0;i=200&&this.status<400){var t={instance:d};d.options.callback.call(t,JSON.parse(this.response)),d.options.debug&&console.debug("queryCount: "+o+" - finishedCount: "+e),++e==o&&d.options.afterRequest.call(d)}},u.send()}}},onAdd:function(t){this._map=t,t.zoomIndicator?(this._zoomControl=t.zoomIndicator,this._zoomControl._addLayer(this)):(this._zoomControl=new L.Control.MinZoomIndicator(this.options.minZoomIndicatorOptions),t.addControl(this._zoomControl),this._zoomControl._addLayer(this)),this.onMoveEnd(),-1!=this.options.query.indexOf("(BBOX)")&&t.on("moveend",this.onMoveEnd,this),this.options.debug&&console.debug("add layer")},onRemove:function(t){this.options.debug&&console.debug("remove layer"),L.LayerGroup.prototype.onRemove.call(this,t),this._ids={},this._requested={},this._zoomControl._removeLayer(this),t.off({moveend:this.onMoveEnd},this),this._map=null},getData:function(){return this.options.debug&&console.debug(this._data),this._data}}); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var concat = require('gulp-concat'); 3 | var uglify = require('gulp-uglify'); 4 | var rename = require('gulp-rename'); 5 | 6 | gulp.task('default', function() { 7 | return gulp.src('./src/*.js') 8 | .pipe(concat('OverPassLayer.js')) 9 | .pipe(gulp.dest('./dist/')) 10 | .pipe(uglify()) 11 | .pipe(rename({ extname: '.min.js' })) 12 | .pipe(gulp.dest('./dist/')); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-layer-overpass", 3 | "version": "1.0.1", 4 | "main": "gulpfile.js", 5 | "devDependencies": { 6 | "gulp-concat": "^2.5.0", 7 | "gulp-rename": "^1.2.0", 8 | "gulp": "^3.8.11", 9 | "gulp-uglify": "^1.1.0" 10 | }, 11 | "dependencies": {}, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/kartenkarsten/leaflet-layer-overpass/" 18 | }, 19 | "author": "Karsten Hinz", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /src/MinZoomIndicator.js: -------------------------------------------------------------------------------- 1 | L.Control.MinZoomIndicator = L.Control.extend({ 2 | options: { 3 | position: 'bottomleft', 4 | }, 5 | 6 | /** 7 | * map: layerId -> zoomlevel 8 | */ 9 | _layers: {}, 10 | 11 | /** TODO check if nessesary 12 | */ 13 | initialize: function (options) { 14 | L.Util.setOptions(this, options); 15 | this._layers = new Object(); 16 | }, 17 | 18 | /** 19 | * adds a layer with minzoom information to this._layers 20 | */ 21 | _addLayer: function(layer) { 22 | var minzoom = 15; 23 | if (layer.options.minzoom) { 24 | minzoom = layer.options.minzoom; 25 | } 26 | this._layers[layer._leaflet_id] = minzoom; 27 | this._updateBox(null); 28 | }, 29 | 30 | /** 31 | * removes a layer from this._layers 32 | */ 33 | _removeLayer: function(layer) { 34 | this._layers[layer._leaflet_id] = null; 35 | this._updateBox(null); 36 | }, 37 | 38 | _getMinZoomLevel: function() { 39 | var minZoomlevel=-1; 40 | for(var key in this._layers) { 41 | if ((this._layers[key] != null)&&(this._layers[key] > minZoomlevel)) { 42 | minZoomlevel = this._layers[key]; 43 | } 44 | } 45 | return minZoomlevel; 46 | }, 47 | 48 | onAdd: function (map) { 49 | this._map = map; 50 | map.zoomIndicator = this; 51 | 52 | var className = this.className; 53 | var container = this._container = L.DomUtil.create('div', className); 54 | map.on('moveend', this._updateBox, this); 55 | this._updateBox(null); 56 | 57 | // L.DomEvent.disableClickPropagation(container); 58 | return container; 59 | }, 60 | 61 | onRemove: function(map) { 62 | L.Control.prototype.onRemove.call(this, map); 63 | map.off({ 64 | 'moveend': this._updateBox 65 | }, this); 66 | 67 | this._map = null; 68 | }, 69 | 70 | _updateBox: function (event) { 71 | //console.log("map moved -> update Container..."); 72 | if (event != null) { 73 | L.DomEvent.preventDefault(event); 74 | } 75 | var minzoomlevel = this._getMinZoomLevel(); 76 | if (minzoomlevel == -1) { 77 | this._container.innerHTML = this.options.minZoomMessageNoLayer; 78 | }else{ 79 | this._container.innerHTML = this.options.minZoomMessage 80 | .replace(/CURRENTZOOM/, this._map.getZoom()) 81 | .replace(/MINZOOMLEVEL/, minzoomlevel); 82 | } 83 | 84 | if (this._map.getZoom() >= minzoomlevel) { 85 | this._container.style.display = 'none'; 86 | }else{ 87 | this._container.style.display = 'block'; 88 | } 89 | }, 90 | 91 | className : 'leaflet-control-minZoomIndicator' 92 | }); 93 | -------------------------------------------------------------------------------- /src/OverPassLayer.js: -------------------------------------------------------------------------------- 1 | L.LatLngBounds.prototype.toOverpassBBoxString = function (){ 2 | var a = this._southWest, 3 | b = this._northEast; 4 | return [a.lat, a.lng, b.lat, b.lng].join(","); 5 | } 6 | 7 | L.OverPassLayer = L.FeatureGroup.extend({ 8 | options: { 9 | debug: false, 10 | minzoom: 15, 11 | endpoint: "//overpass-api.de/api/", 12 | query: "(node(BBOX)[organic];node(BBOX)[second_hand];);out qt;", 13 | callback: function(data) { 14 | for(var i = 0; i < data.elements.length; i++) { 15 | var e = data.elements[i]; 16 | 17 | if (e.id in this.instance._ids) continue; 18 | this.instance._ids[e.id] = true; 19 | var pos; 20 | if (e.type === "node") { 21 | pos = new L.LatLng(e.lat, e.lon); 22 | } else { 23 | pos = new L.LatLng(e.center.lat, e.center.lon); 24 | } 25 | var popup = this.instance._poiInfo(e.tags,e.id); 26 | var circle = L.circle(pos, 50, { 27 | color: 'green', 28 | fillColor: '#3f0', 29 | fillOpacity: 0.5 30 | }) 31 | .bindPopup(popup); 32 | this.instance.addLayer(circle); 33 | } 34 | }, 35 | beforeRequest: function() { 36 | if (this.options.debug) { 37 | console.debug('about to query the OverPassAPI'); 38 | } 39 | }, 40 | afterRequest: function() { 41 | if (this.options.debug) { 42 | console.debug('all queries have finished!'); 43 | } 44 | }, 45 | minZoomIndicatorOptions: { 46 | position: 'bottomleft', 47 | minZoomMessageNoLayer: "no layer assigned", 48 | minZoomMessage: "current Zoom-Level: CURRENTZOOM all data at Level: MINZOOMLEVEL" 49 | }, 50 | }, 51 | 52 | initialize: function (options) { 53 | L.Util.setOptions(this, options); 54 | this._layers = {}; 55 | // save position of the layer or any options from the constructor 56 | this._ids = {}; 57 | this._requested = {}; 58 | }, 59 | 60 | _poiInfo: function(tags,id) { 61 | var link = document.createElement("a"); 62 | link.href = "//www.openstreetmap.org/edit?editor=id&node=" + id; 63 | link.appendChild(document.createTextNode("Edit this entry in iD")); 64 | var table = document.createElement('table'); 65 | for (var key in tags){ 66 | var row = table.insertRow(0); 67 | row.insertCell(0).appendChild(document.createTextNode(key)); 68 | row.insertCell(1).appendChild(document.createTextNode(tags[key])); 69 | } 70 | var div = document.createElement("div") 71 | div.appendChild(link); 72 | div.appendChild(table); 73 | return div; 74 | }, 75 | 76 | /** 77 | * splits the current view in uniform bboxes to allow caching 78 | */ 79 | long2tile: function (lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); }, 80 | lat2tile: function (lat,zoom) { 81 | return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); 82 | }, 83 | tile2long: function (x,z) { 84 | return (x/Math.pow(2,z)*360-180); 85 | }, 86 | tile2lat: function (y,z) { 87 | var n=Math.PI-2*Math.PI*y/Math.pow(2,z); 88 | return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)))); 89 | }, 90 | _view2BBoxes: function(l,b,r,t) { 91 | //console.log(l+"\t"+b+"\t"+r+"\t"+t); 92 | //this.addBBox(l,b,r,t); 93 | //console.log("calc bboxes"); 94 | var requestZoomLevel= 14; 95 | //get left tile index 96 | var lidx = this.long2tile(l,requestZoomLevel); 97 | var ridx = this.long2tile(r,requestZoomLevel); 98 | var tidx = this.lat2tile(t,requestZoomLevel); 99 | var bidx = this.lat2tile(b,requestZoomLevel); 100 | 101 | //var result; 102 | var result = new Array(); 103 | for (var x=lidx; x<=ridx; x++) { 104 | for (var y=tidx; y<=bidx; y++) {//in tiles tidx<=bidx 105 | var left = Math.round(this.tile2long(x,requestZoomLevel)*1000000)/1000000; 106 | var right = Math.round(this.tile2long(x+1,requestZoomLevel)*1000000)/1000000; 107 | var top = Math.round(this.tile2lat(y,requestZoomLevel)*1000000)/1000000; 108 | var bottom = Math.round(this.tile2lat(y+1,requestZoomLevel)*1000000)/1000000; 109 | //console.log(left+"\t"+bottom+"\t"+right+"\t"+top); 110 | //this.addBBox(left,bottom,right,top); 111 | //console.log("http://osm.org?bbox="+left+","+bottom+","+right+","+top); 112 | result.push( new L.LatLngBounds(new L.LatLng(bottom, left),new L.LatLng(top, right))); 113 | } 114 | } 115 | //console.log(result); 116 | return result; 117 | }, 118 | 119 | addBBox: function (l,b,r,t) { 120 | var polygon = L.polygon([ 121 | [t, l], 122 | [b, l], 123 | [b, r], 124 | [t, r] 125 | ]).addTo(this._map); 126 | }, 127 | 128 | onMoveEnd: function () { 129 | if (this.options.debug) { 130 | console.debug("load Pois"); 131 | } 132 | //console.log(this._map.getBounds()); 133 | if (this._map.getZoom() >= this.options.minzoom) { 134 | //var bboxList = new Array(this._map.getBounds()); 135 | var bboxList = this._view2BBoxes( 136 | this._map.getBounds()._southWest.lng, 137 | this._map.getBounds()._southWest.lat, 138 | this._map.getBounds()._northEast.lng, 139 | this._map.getBounds()._northEast.lat); 140 | 141 | // controls the after/before (Request) callbacks 142 | var finishedCount = 0; 143 | var queryCount = bboxList.length; 144 | var beforeRequest = true; 145 | 146 | for (var i = 0; i < bboxList.length; i++) { 147 | var bbox = bboxList[i]; 148 | var x = bbox._southWest.lng; 149 | var y = bbox._northEast.lat; 150 | if ((x in this._requested) && (y in this._requested[x]) && (this._requested[x][y] == true)) { 151 | queryCount--; 152 | continue; 153 | } 154 | if (!(x in this._requested)) { 155 | this._requested[x] = {}; 156 | } 157 | this._requested[x][y] = true; 158 | 159 | 160 | var queryWithMapCoordinates = this.options.query.replace(/(BBOX)/g, bbox.toOverpassBBoxString()); 161 | var url = this.options.endpoint + "interpreter?data=[out:json];" + queryWithMapCoordinates; 162 | 163 | if (beforeRequest) { 164 | this.options.beforeRequest.call(this); 165 | beforeRequest = false; 166 | } 167 | 168 | var self = this; 169 | var request = new XMLHttpRequest(); 170 | request.open("GET", url, true); 171 | 172 | request.onload = function() { 173 | if (this.status >= 200 && this.status < 400) { 174 | var reference = {instance: self}; 175 | self.options.callback.call(reference, JSON.parse(this.response)); 176 | if (self.options.debug) { 177 | console.debug('queryCount: ' + queryCount + ' - finishedCount: ' + finishedCount); 178 | } 179 | if (++finishedCount == queryCount) { 180 | self.options.afterRequest.call(self); 181 | } 182 | } 183 | }; 184 | 185 | request.send(); 186 | 187 | 188 | } 189 | } 190 | }, 191 | 192 | onAdd: function (map) { 193 | this._map = map; 194 | if (map.zoomIndicator) { 195 | this._zoomControl = map.zoomIndicator; 196 | this._zoomControl._addLayer(this); 197 | }else{ 198 | this._zoomControl = new L.Control.MinZoomIndicator(this.options.minZoomIndicatorOptions); 199 | map.addControl(this._zoomControl); 200 | this._zoomControl._addLayer(this); 201 | } 202 | 203 | this.onMoveEnd(); 204 | if (this.options.query.indexOf("(BBOX)") != -1) { 205 | map.on('moveend', this.onMoveEnd, this); 206 | } 207 | if (this.options.debug) { 208 | console.debug("add layer"); 209 | } 210 | }, 211 | 212 | onRemove: function (map) { 213 | if (this.options.debug) { 214 | console.debug("remove layer"); 215 | } 216 | L.LayerGroup.prototype.onRemove.call(this, map); 217 | this._ids = {}; 218 | this._requested = {}; 219 | this._zoomControl._removeLayer(this); 220 | 221 | map.off({ 222 | 'moveend': this.onMoveEnd 223 | }, this); 224 | 225 | this._map = null; 226 | }, 227 | 228 | getData: function () { 229 | if (this.options.debug) { 230 | console.debug(this._data); 231 | } 232 | return this._data; 233 | } 234 | 235 | }); 236 | 237 | //FIXME no idea why the browser crashes with this code 238 | //L.OverPassLayer = function (options) { 239 | // return new L.OverPassLayer(options); 240 | //}; 241 | --------------------------------------------------------------------------------