├── .gitignore ├── Gruntfile.js ├── README.md ├── dist ├── leaflet-layerjson.min.js └── leaflet-layerjson.src.js ├── examples ├── bar.png ├── calldata.html ├── cluster.html ├── fixed-remote-data.html ├── htmlcolors.txt ├── jquery.html ├── jsonp.html ├── loader.gif ├── overpass.html ├── polygons.html ├── search.php ├── simple.html └── style.css ├── index.html ├── license.txt ├── package.json └── src └── leaflet-layerjson.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | grunt.loadNpmTasks('grunt-contrib-clean'); 6 | grunt.loadNpmTasks('grunt-contrib-concat'); 7 | grunt.loadNpmTasks('grunt-contrib-jshint'); 8 | grunt.loadNpmTasks('grunt-contrib-uglify'); 9 | grunt.loadNpmTasks('grunt-contrib-watch'); 10 | 11 | grunt.initConfig({ 12 | pkg: grunt.file.readJSON('package.json'), 13 | meta: { 14 | banner: 15 | '/* \n'+ 16 | ' * Leaflet JSON Layer v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n'+ 17 | ' * \n'+ 18 | ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> \n'+ 19 | ' * <%= pkg.author.email %> \n'+ 20 | ' * <%= pkg.author.url %> \n'+ 21 | ' * \n'+ 22 | ' * Licensed under the <%= pkg.license %> license. \n'+ 23 | ' * \n'+ 24 | ' * Demo: \n'+ 25 | ' * <%= pkg.homepage %> \n'+ 26 | ' * \n'+ 27 | ' * Source: \n'+ 28 | ' * <%= pkg.repository.url %> \n'+ 29 | ' * \n'+ 30 | ' */\n' 31 | }, 32 | clean: { 33 | dist: { 34 | src: ['dist/*'] 35 | } 36 | }, 37 | jshint: { 38 | options: { 39 | globals: { 40 | console: true, 41 | module: true 42 | }, 43 | "-W099": true, //ignora tabs e space warning 44 | "-W033": true, 45 | "-W044": true, //ignore regexp 46 | "-W061": true //ignore eval in getAjax() 47 | }, 48 | files: ['src/*.js'] 49 | }, 50 | concat: { 51 | options: { 52 | banner: '<%= meta.banner %>' 53 | }, 54 | dist: { 55 | files: { 56 | 'dist/leaflet-layerjson.src.js': ['src/leaflet-layerjson.js'] 57 | } 58 | } 59 | }, 60 | uglify: { 61 | options: { 62 | banner: '<%= meta.banner %>' 63 | }, 64 | dist: { 65 | files: { 66 | 'dist/leaflet-layerjson.min.js': ['dist/leaflet-layerjson.src.js'] 67 | } 68 | } 69 | }, 70 | watch: { 71 | dist: { 72 | options: { livereload: true }, 73 | files: ['src/*','examples/*.html'], 74 | tasks: ['clean','concat','jshint'] 75 | } 76 | } 77 | }); 78 | 79 | grunt.registerTask('default', [ 80 | 'clean', 81 | 'concat', 82 | 'jshint', 83 | 'uglify' 84 | ]); 85 | 86 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Leaflet JSON Layer 2 | ============ 3 | 4 | [![npm version](https://badge.fury.io/js/leaflet-layerjson.svg)](https://badge.fury.io/js/leaflet-layerjson) 5 | 6 | Simple way for transform any JSON data source in a Leaflet Layer! 7 | 8 | A Dynamic Leaflet Layer that load JSON data in layer in the form of markers with attributes 9 | 10 | and minimize remote requests with caching system 11 | 12 | Copyright 2023 Stefano Cudini 13 | 14 | If this project helped your work help me to keep this alive by [Paypal **DONATION ❤**](https://www.paypal.me/stefanocudini) 15 | 16 | Tested in Leaflet 0.7 and 1.1 17 | 18 | # Options 19 | | Option | Data | Description | 20 | | ------------- | --------| ----------------------------------------- | 21 | | url | String | remote url | 22 | | jsonpParam | String | callback parameter name for jsonp request append to url | 23 | | jsonpParam | String | callback parameter name for jsonp request append to url | 24 | | callData | Function | custom function for data source, params: (req: url|bbox, callback: func), return {abort: func} or jQuery jqXHR Object | 25 | | **Filtering** | | | 26 | | propertyItems | String | json property used contains data items | 27 | | propertyLoc | String | json property used as Latlng of marker, if is array: *['lat','lon']* select double fields | 28 | | locAsGeoJSON | String | interpret location data as [lon, lat] value pair instead of [lat, lon] | 29 | | propertyTitle | String | json property used as title in marker | 30 | | filterData | Function | function for pre-filter data | 31 | | **Rendering** | | | 32 | | dataToMarker | Function | function that will be used for creating markers from json points | 33 | | layerTarget | L.Layer | pre-existing layer to add markers(*L.LayerGroup*, *L.MarkerClusterGroup*) | 34 | | buildPopup | Function | function popup builder | 35 | | optsPopup | String | popup options | 36 | | buildIcon | Function | function icon builder | 37 | | **Caching** | | | 38 | | minZoom | Number | min zoom for call data | 39 | | caching | Boolean | remote requests caching | 40 | | cacheId | Function | function to generate id used to uniquely identify data items in cache | 41 | | minShift | Number | min shift for update data(in meters) | 42 | | precision | Number | number of digit send to server for lat,lng precision | 43 | | updateOutBounds| String | request new data only if current bounds higher than last bounds | 44 | | updateMarkers | Boolean | update all markers in map to last results of callData | 45 | 46 | # Events 47 | | Event | Data | Description | 48 | | ---------------------- | ---------------------- | ----------------------------------------- | 49 | | 'dataloading' | {req: url|bbox} | fired before ajax/jsonp request, req is bbox if url option is null | 50 | | 'dataloaded' | {data: json} | fired on ajax/jsonp request success | 51 | 52 | # Usage 53 | 54 | ``` 55 | var l = new L.LayerJSON({url: "search.php?lat1={lat1}&lat2={lat2}&lon1={lon1}&lon2={lon2}" }); 56 | map.addLayer(l); 57 | ``` 58 | 59 | # Where 60 | 61 | **Demos:** 62 | 63 | [https://opengeo.tech/maps/leaflet-layerjson](https://opengeo.tech/maps/leaflet-layerjson/) 64 | 65 | **Source:** 66 | 67 | [Github](https://github.com/stefanocudini/leaflet-layerjson) 68 | 69 | [Atmosphere](https://atmosphere.meteor.com/package/leaflet-layerjson) 70 | 71 | 72 | # Build 73 | 74 | This plugin support [Grunt](https://gruntjs.com/) for building process. 75 | Therefore the deployment require [NPM](https://npmjs.org/) installed in your system. 76 | 77 | After you've made sure to have npm working, run this in command line: 78 | ``` 79 | npm install 80 | grunt 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /dist/leaflet-layerjson.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leaflet JSON Layer v0.3.3 - 2019-12-27 3 | * 4 | * Copyright 2019 Stefano Cudini 5 | * stefano.cudini@gmail.com 6 | * https://opengeo.tech/ 7 | * 8 | * Licensed under the MIT license. 9 | * 10 | * Demo: 11 | * https://opengeo.tech/maps/leaflet-layerjson/ 12 | * 13 | * Source: 14 | * git@github.com:stefanocudini/leaflet-layerjson.git 15 | * 16 | */ 17 | (function(){L.LayerJSON=L.FeatureGroup.extend({includes:"1"===L.version[0]?L.Evented.prototype:L.Mixin.Events,options:{url:"",jsonpParam:null,callData:null,filterData:null,locAsGeoJSON:!1,propertyItems:"",propertyTitle:"title",propertyLoc:"loc",propertyId:"id",layerTarget:null,dataToMarker:null,buildPopup:null,buildIcon:null,optsPopup:null,minZoom:10,caching:!0,cacheId:null,minShift:1e3,precision:6,updateMarkers:!1,attribution:""},initialize:function(a){L.FeatureGroup.prototype.initialize.call(this,[]),L.Util.setOptions(this,a),this._dataToMarker=this.options.dataToMarker||this._defaultDataToMarker,this._buildIcon=this.options.buildIcon||this._defaultBuildIcon,this._filterData=this.options.filterData||null,this._hashUrl=this.options.url,this._hashUrl?(this._callData=this.getAjax,this.options.jsonpParam&&(this._hashUrl+="&"+this.options.jsonpParam+"=",this._callData=this.getJsonp)):this._callData=this.options.callData,this._curReq=null,this._center=null,this._cacheBounds=null,this._markersCache={}},onAdd:function(a){L.FeatureGroup.prototype.onAdd.call(this,a),this._center=a.getCenter(),this._cacheBounds=a.getBounds(),a.on("moveend zoomend",this._onMove,this),this.update()},onRemove:function(a){a.off("moveend zoomend",this._onMove,this),L.FeatureGroup.prototype.onRemove.call(this,a);for(var b in this._layers)this._layers.hasOwnProperty(b)&&L.FeatureGroup.prototype.removeLayer.call(this,this._layers[b])},getAttribution:function(){return this.options.attribution},addLayer:function(a){return this.options.layerTarget?(this.options.layerTarget.addLayer.call(this.options.layerTarget,a),this.fire("layeradd",{layer:a})):(L.FeatureGroup.prototype.addLayer.call(this,a),this)},removeLayer:function(a){return this.options.layerTarget?(this.options.layerTarget.removeLayer.call(this.options.layerTarget,a),this.fire("layerremove",{layer:a})):(L.FeatureGroup.prototype.removeLayer.call(this,a),this)},clearLayers:function(){var a=this,b=this._map.getBounds();return a.options.layerTarget?a.options.layerTarget.eachLayer(function(c){a._contains(b,c)||(a.options.layerTarget.removeLayer(c),delete a._markersCache[c._cacheId])}):a.eachLayer(function(c){a._contains(b,c)||(a.removeLayer(c),delete a._markersCache[c._cacheId])},a),this},_debouncer:function(a,b){var c;return b=b||300,function(){var d=this,e=arguments;clearTimeout(c),c=setTimeout(function(){a.apply(d,Array.prototype.slice.call(e))},b)}},_getPath:function(a,b){var c=b.split("."),d=c.pop(),e=c.length,f=c[0],g=1;if(e>0)for(;(a=a[f])&&e>g;)f=c[g++];return a?a[d]:void 0},_defaultBuildIcon:function(a,b){return new L.Icon.Default},_defaultDataToMarker:function(a,b){var c=this._getPath(a,this.options.propertyTitle),d=L.Util.extend({icon:this._buildIcon(a,c),title:c},a),e=new L.Marker(b,d),f=null;return this.options.buildPopup&&(f=this.options.buildPopup(a,e))&&e.bindPopup(f,this.options.optsPopup),e},addMarker:function(a){var b,c,d=this.options.propertyLoc;if(L.Util.isArray(d))b=L.latLng(parseFloat(this._getPath(a,d[0])),parseFloat(this._getPath(a,d[1])));else if(this.options.locAsGeoJSON){var e=this._getPath(a,d);b=L.latLng(e[1],e[0])}else b=L.latLng(this._getPath(a,d));return c=this.options.cacheId?this.options.cacheId.call(this,a,b):this.options.propertyId?this._getPath(a,this.options.propertyId):b.lat+""+b.lng+this._getPath(a,this.options.propertyTitle),"undefined"==typeof this._markersCache[c]&&(this._markersCache[c]=this._dataToMarker(a,b),this._markersCache[c]._cacheId=c,this.addLayer(this._markersCache[c])),c},_contains:function(a,b){var c;return b.getLatLng?c=b.getLatLng():b.getBounds&&(c=b.getBounds()),a.contains(c)},_loadCacheToBounds:function(a){for(var b in this._markersCache)this._markersCache[b]&&(this._contains(a,this._markersCache[b])?this.addLayer(this._markersCache[b]):this.removeLayer(this._markersCache[b]))},_onMove:function(a){var b=this._map.getZoom(),c=this._map.getCenter(),d=this._map.getBounds();if(b 0) 184 | while((obj = obj[cur]) && i < len) 185 | cur = parts[i++]; 186 | 187 | if(obj) 188 | return obj[last]; 189 | }, 190 | 191 | _defaultBuildIcon: function(data, title) { 192 | return new L.Icon.Default(); 193 | }, 194 | 195 | _defaultDataToMarker: function(data, latlng) { //make marker from data 196 | 197 | var title = this._getPath(data, this.options.propertyTitle), 198 | markerOpts = L.Util.extend({icon: this._buildIcon(data,title), title: title }, data), 199 | marker = new L.Marker(latlng, markerOpts ), 200 | htmlPopup = null; 201 | 202 | if(this.options.buildPopup && (htmlPopup = this.options.buildPopup(data, marker))) 203 | marker.bindPopup(htmlPopup, this.options.optsPopup ); 204 | 205 | return marker; 206 | }, 207 | 208 | addMarker: function(data) { 209 | 210 | var loc, hash, propLoc = this.options.propertyLoc; 211 | 212 | if( L.Util.isArray(propLoc) ) { 213 | loc = L.latLng( parseFloat( this._getPath(data, propLoc[0]) ), 214 | parseFloat( this._getPath(data, propLoc[1]) ) ); 215 | } 216 | else { 217 | if (this.options.locAsGeoJSON) { 218 | var lnglat = this._getPath(data, propLoc); 219 | loc = L.latLng( lnglat[1], lnglat[0] ); 220 | } else { 221 | loc = L.latLng( this._getPath(data, propLoc) ); 222 | } 223 | } 224 | 225 | if(this.options.cacheId) { 226 | hash = this.options.cacheId.call(this, data, loc); 227 | } 228 | else { 229 | if(this.options.propertyId) 230 | hash = this._getPath(data, this.options.propertyId); 231 | else 232 | hash = loc.lat+''+loc.lng+''+this._getPath(data, this.options.propertyTitle); 233 | } 234 | 235 | if(typeof this._markersCache[hash] === 'undefined') { 236 | this._markersCache[hash] = this._dataToMarker(data, loc); 237 | this._markersCache[hash]._cacheId = hash; 238 | this.addLayer( this._markersCache[hash] ); 239 | } 240 | 241 | return hash; 242 | }, 243 | 244 | _contains: function(bounds, el) { 245 | var loc; 246 | 247 | if(el.getLatLng) 248 | loc = el.getLatLng(); 249 | else if(el.getBounds) 250 | loc = el.getBounds(); 251 | 252 | return bounds.contains(loc); 253 | }, 254 | 255 | _loadCacheToBounds: function(bounds) { //show/hide cached markers 256 | for(var i in this._markersCache) 257 | { 258 | if(this._markersCache[i]) 259 | { 260 | if(this._contains(bounds, this._markersCache[i]) ) 261 | this.addLayer(this._markersCache[i]); 262 | else 263 | this.removeLayer(this._markersCache[i]); 264 | } 265 | } 266 | }, 267 | 268 | _onMove: function(e) { 269 | var newZoom = this._map.getZoom(), 270 | newCenter = this._map.getCenter(), 271 | newBounds = this._map.getBounds(); 272 | 273 | if(newZoom < this.options.minZoom) 274 | return false; 275 | 276 | if( this.options.minShift && this._center.distanceTo(newCenter) < this.options.minShift ) 277 | return false; 278 | else 279 | this._center = newCenter; 280 | 281 | if(this.options.caching) { 282 | 283 | if( this._cacheBounds.contains(newBounds) ) 284 | { 285 | this._loadCacheToBounds(newBounds); 286 | return false; 287 | } 288 | else 289 | this._cacheBounds.extend(newBounds); 290 | } 291 | else 292 | this.clearLayers(); 293 | 294 | this.update(); 295 | }, 296 | 297 | update: function() { //populate target layer 298 | 299 | var self = this; 300 | 301 | var prec = self.options.precision, 302 | bb = self._map.getBounds(), 303 | sw = bb.getSouthWest(), 304 | ne = bb.getNorthEast(), 305 | bbox = [ 306 | [ parseFloat(sw.lat.toFixed(prec)), parseFloat(sw.lng.toFixed(prec)) ], 307 | [ parseFloat(ne.lat.toFixed(prec)), parseFloat(ne.lng.toFixed(prec)) ] 308 | ]; 309 | 310 | if(self._hashUrl) //conver bbox to url string 311 | bbox = L.Util.template(self._hashUrl, { 312 | lat1: bbox[0][0], lon1: bbox[0][1], 313 | lat2: bbox[1][0], lon2: bbox[1][1] 314 | }); 315 | 316 | if(self._curReq && self._curReq.abort) 317 | self._curReq.abort(); //prevent parallel requests 318 | 319 | 320 | self.fire('dataloading', {req: bbox }); 321 | self._curReq = self._callData(bbox, function(json) { 322 | 323 | //console.log('callData',json) 324 | 325 | self._curReq = null; 326 | 327 | if(self._filterData) 328 | json = self._filterData(json); 329 | 330 | if(self.options.propertyItems) 331 | json = self._getPath(json, self.options.propertyItems); 332 | 333 | self.fire('dataloaded', {data: json}); 334 | 335 | self.updateMarkers(json); 336 | }); 337 | }, 338 | 339 | updateMarkers: function(json) { 340 | var jsonbyhash = {}; 341 | 342 | for (var k in json) { 343 | if (!isNaN(parseFloat(k)) && isFinite(k)) { 344 | var h = this.addMarker.call(this, json[k]); 345 | jsonbyhash[h] = 1; 346 | } 347 | } 348 | 349 | if(this.options.updateMarkers) { 350 | for (var c in this._markersCache) { 351 | if(!jsonbyhash[ this._markersCache[c]._cacheId ]) 352 | this.removeLayer(this._markersCache[c]); 353 | else 354 | this.addLayer(this._markersCache[c]); 355 | } 356 | } 357 | }, 358 | 359 | 360 | /////////////////ajax jsonp methods 361 | 362 | getAjax: function(url, cb) { //default ajax request 363 | 364 | if (window.XMLHttpRequest === undefined) { 365 | window.XMLHttpRequest = function() { 366 | try { 367 | return new ActiveXObject("Microsoft.XMLHTTP.6.0"); 368 | } 369 | catch (e1) { 370 | try { 371 | return new ActiveXObject("Microsoft.XMLHTTP.3.0"); 372 | } 373 | catch (e2) { 374 | throw new Error("XMLHttpRequest is not supported"); 375 | } 376 | } 377 | }; 378 | } 379 | var request = new XMLHttpRequest(); 380 | request.open('GET', url); 381 | request.onreadystatechange = function() { 382 | var response = {}; 383 | if (request.readyState === 4 && request.status === 200) { 384 | try { 385 | if(window.JSON) 386 | response = JSON.parse(request.responseText); 387 | else 388 | response = eval("("+ request.responseText + ")"); 389 | } catch(err) { 390 | response = {}; 391 | throw new Error('Ajax response is not JSON'); 392 | } 393 | cb(response); 394 | } 395 | }; 396 | request.send(); 397 | return request; 398 | }, 399 | 400 | getJsonp: function(url, cb) { //extract searched records from remote jsonp service 401 | var body = document.getElementsByTagName('body')[0], 402 | script = L.DomUtil.create('script','leaflet-layerjson-jsonp', body ); 403 | 404 | //JSONP callback 405 | L.LayerJSON.callJsonp = function(data) { 406 | cb(data); 407 | body.removeChild(script); 408 | } 409 | script.type = 'text/javascript'; 410 | script.src = url+'L.LayerJSON.callJsonp'; 411 | return { 412 | abort: function() { 413 | script.parentNode.removeChild(script); 414 | } 415 | }; 416 | } 417 | }); 418 | 419 | L.layerJSON = function (options) { 420 | return new L.LayerJSON(options); 421 | }; 422 | 423 | }).call(this); 424 | -------------------------------------------------------------------------------- /examples/bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-layerJSON/959e43ef4f9297cbb9dadedee21a18b12b0cf2b0/examples/bar.png -------------------------------------------------------------------------------- /examples/calldata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 |

Leaflet JSON Layer

23 |

Data by Callback Example: load data from Javascript Static Obejct and highlights nearby markers to the center

24 |
25 |
26 |
27 | 28 | 29 | 88 |
Opengeo.tech
89 | Github 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /examples/cluster.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Leaflet JSON Layer

16 |

Example: Show Bars in Rome

17 |
18 | Using: Overpass API 19 |

20 | Service offer by
21 | overpass-api.de 22 |

23 | Data offer by OpenStreetMap.org 24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 74 |
Opengeo.tech
75 | Github 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /examples/fixed-remote-data.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Leaflet JSON Layer

12 |

Remote Fixed Data: load data from remote static JSON

13 | 14 | 15 |
16 |
17 | Data offer by unhcr.org API 18 |
19 | Population settlements 20 |
21 | 22 | 23 | 63 |
Opengeo.tech
64 | Github 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/htmlcolors.txt: -------------------------------------------------------------------------------- 1 | aliceblue:#f0f8ff 2 | antiquewhite:#faebd7 3 | aqua:#00ffff 4 | aquamarine:#7fffd4 5 | azure:#f0ffff 6 | beige:#f5f5dc 7 | bisque:#ffe4c4 8 | black:#000000 9 | blanchedalmond:#ffebcd 10 | blue:#0000ff 11 | blueviolet:#8a2be2 12 | brown:#a52a2a 13 | burlywood:#deb887 14 | cadetblue:#5f9ea0 15 | chartreuse:#7fff00 16 | chocolate:#d2691e 17 | coral:#ff7f50 18 | cornflowerblue:#6495ed 19 | cornsilk:#fff8dc 20 | crimson:#dc143c 21 | cyan:#00ffff 22 | darkblue:#00008b 23 | darkcyan:#008b8b 24 | darkgoldenrod:#b8860b 25 | darkgray:#a9a9a9 26 | darkgrey:#a9a9a9 27 | darkgreen:#006400 28 | darkkhaki:#bdb76b 29 | darkmagenta:#8b008b 30 | darkolivegreen:#556b2f 31 | darkorange:#ff8c00 32 | darkorchid:#9932cc 33 | darkred:#8b0000 34 | darksalmon:#e9967a 35 | darkseagreen:#8fbc8f 36 | darkslateblue:#483d8b 37 | darkslategray:#2f4f4f 38 | darkslategrey:#2f4f4f 39 | darkturquoise:#00ced1 40 | darkviolet:#9400d3 41 | deeppink:#ff1493 42 | deepskyblue:#00bfff 43 | dimgray:#696969 44 | dimgrey:#696969 45 | dodgerblue:#1e90ff 46 | firebrick:#b22222 47 | floralwhite:#fffaf0 48 | forestgreen:#228b22 49 | fuchsia:#ff00ff 50 | gainsboro:#dcdcdc 51 | ghostwhite:#f8f8ff 52 | gold:#ffd700 53 | goldenrod:#daa520 54 | gray:#808080 55 | grey:#808080 56 | green:#008000 57 | greenyellow:#adff2f 58 | honeydew:#f0fff0 59 | hotpink:#ff69b4 60 | indianred :#cd5c5c 61 | indigo :#4b0082 62 | ivory:#fffff0 63 | khaki:#f0e68c 64 | lavender:#e6e6fa 65 | lavenderblush:#fff0f5 66 | lawngreen:#7cfc00 67 | lemonchiffon:#fffacd 68 | lightblue:#add8e6 69 | lightcoral:#f08080 70 | lightcyan:#e0ffff 71 | lightgoldenrodyellow:#fafad2 72 | lightgray:#d3d3d3 73 | lightgrey:#d3d3d3 74 | lightgreen:#90ee90 75 | lightpink:#ffb6c1 76 | lightsalmon:#ffa07a 77 | lightseagreen:#20b2aa 78 | lightskyblue:#87cefa 79 | lightslategray:#778899 80 | lightslategrey:#778899 81 | lightsteelblue:#b0c4de 82 | lightyellow:#ffffe0 83 | lime:#00ff00 84 | limegreen:#32cd32 85 | linen:#faf0e6 86 | magenta:#ff00ff 87 | maroon:#800000 88 | mediumaquamarine:#66cdaa 89 | mediumblue:#0000cd 90 | mediumorchid:#ba55d3 91 | mediumpurple:#9370db 92 | mediumseagreen:#3cb371 93 | mediumslateblue:#7b68ee 94 | mediumspringgreen:#00fa9a 95 | mediumturquoise:#48d1cc 96 | mediumvioletred:#c71585 97 | midnightblue:#191970 98 | mintcream:#f5fffa 99 | mistyrose:#ffe4e1 100 | moccasin:#ffe4b5 101 | navajowhite:#ffdead 102 | navy:#000080 103 | oldlace:#fdf5e6 104 | olive:#808000 105 | olivedrab:#6b8e23 106 | orange:#ffa500 107 | orangered:#ff4500 108 | orchid:#da70d6 109 | palegoldenrod:#eee8aa 110 | palegreen:#98fb98 111 | paleturquoise:#afeeee 112 | palevioletred:#db7093 113 | papayawhip:#ffefd5 114 | peachpuff:#ffdab9 115 | peru:#cd853f 116 | pink:#ffc0cb 117 | plum:#dda0dd 118 | powderblue:#b0e0e6 119 | purple:#800080 120 | red:#ff0000 121 | rosybrown:#bc8f8f 122 | royalblue:#4169e1 123 | saddlebrown:#8b4513 124 | salmon:#fa8072 125 | sandybrown:#f4a460 126 | seagreen:#2e8b57 127 | seashell:#fff5ee 128 | sienna:#a0522d 129 | silver:#c0c0c0 130 | skyblue:#87ceeb 131 | slateblue:#6a5acd 132 | slategray:#708090 133 | slategrey:#708090 134 | snow:#fffafa 135 | springgreen:#00ff7f 136 | steelblue:#4682b4 137 | tan:#d2b48c 138 | teal:#008080 139 | thistle:#d8bfd8 140 | tomato:#ff6347 141 | turquoise:#40e0d0 142 | violet:#ee82ee 143 | wheat:#f5deb3 144 | white:#ffffff 145 | whitesmoke:#f5f5f5 146 | yellow:#ffff00 147 | yellowgreen:#9acd32 148 | -------------------------------------------------------------------------------- /examples/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Leaflet JSON Layer

12 |

jQuery Example: load json data by Ajax using jQuery

13 |

14 | For simplicity in this example the server side distributes data always random irrespective of the coordinates, in a real case much more likely server
15 | side there will be a spatial database that returns data inside requested bounding box. 16 |

17 |
18 |
19 |
		
20 | 
21 | 22 | 23 | 24 | 25 | 53 |
Opengeo.tech
54 | Github 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/jsonp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Leaflet JSON Layer

12 |

JSONP Example: get data from 3rd party JSONP service and transform in markers

13 | 14 |
15 |
16 | Data offer by 17 | nominatim.osm.org 18 |
19 | 20 | 21 | 44 |
Opengeo.tech
45 | Github 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /examples/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-layerJSON/959e43ef4f9297cbb9dadedee21a18b12b0cf2b0/examples/loader.gif -------------------------------------------------------------------------------- /examples/overpass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet JSON Layer

13 |

Example: Show Bars in Rome

14 |
15 | Using: Overpass API 16 |

17 | Service offer by
18 | overpass-api.de 19 |

20 | Data offer by OpenStreetMap.org 21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 69 |
Opengeo.tech
70 | Github 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/polygons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Leaflet JSON Layer

12 |

Example: Load Paths and Polygons

13 | 14 | 15 |
16 |
17 | Using: Overpass API 18 |
19 | Service offer by
20 | overpass-api.de 21 |
22 | Data offer by OpenStreetMap.org 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 67 |
Opengeo.tech
68 | Github 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/search.php: -------------------------------------------------------------------------------- 1 | $row[0], 'color'=>$row[1], 'id'=>$m); 46 | fclose($cf); 47 | } 48 | return $colors; 49 | } 50 | ?> 51 | -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Leaflet JSON Layer

12 |

AJAX Example: load json data by Ajax request

13 |

14 | For simplicity in this example the server side distributes data always random irrespective of the coordinates, in a real case much more likely server
15 | side there will be a spatial database that returns data inside requested bounding box. 16 |

17 |
18 |
19 |
	
20 | 
21 | 22 | 23 | 39 |
Opengeo.tech
40 | Github 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #def; 3 | color:#456; 4 | } 5 | 6 | h2,h3,h4,h5 {margin:6px 0} 7 | a {text-decoration: none} 8 | p {margin:0 0 20px 0} 9 | ul { 10 | font-size:.85em; 11 | margin:0 0 10px 0; 12 | padding:0; 13 | } 14 | li { 15 | margin:0 0 2px 18px; 16 | } 17 | #map { 18 | border:2px solid #669; 19 | width:800px; 20 | height:340px; 21 | } 22 | #post-it { 23 | width:120px; 24 | height:120px; 25 | padding:1em; 26 | float:left; 27 | background:#fbf5bf; 28 | border:1px solid #c6bb58; 29 | box-shadow: 2px 2px 6px #999; 30 | color:#666; 31 | } 32 | #arrow { 33 | display: block; 34 | line-height: 380px; 35 | font-size: 80px; 36 | float: left; 37 | text-align: center; 38 | width: 50px; 39 | } 40 | #data { 41 | width:800px; 42 | height:150px; 43 | background:#eee; 44 | border:2px solid #669; 45 | padding:2px; 46 | font-size:.85em; 47 | overflow:scroll; 48 | margin:0; 49 | } 50 | #content,#refs { 51 | float:left; 52 | margin-right:1em; 53 | } 54 | #copy { 55 | position:fixed; 56 | z-index:1000; 57 | right:150px; 58 | top:-6px; 59 | font-style:italic; 60 | font-size:.85em; 61 | padding:2px 8px; 62 | background: #ccc; 63 | border: 2px solid #3e5585; 64 | border-radius:.7em; 65 | opacity: 0.8; 66 | } 67 | #copy a { 68 | color:#285585 69 | } 70 | #ribbon { 71 | position: absolute; 72 | top: 0; 73 | right: 0; 74 | border: 0; 75 | filter: alpha(opacity=80); 76 | -khtml-opacity: .8; 77 | -moz-opacity: .8; 78 | opacity: .8; 79 | z-index: 2000; 80 | } 81 | #comments { 82 | clear:both; 83 | } 84 | pre { 85 | background-color: #fff; 86 | color:#333; 87 | line-height: 1.5em; 88 | padding:10px; 89 | float: left; 90 | font-size: 12px; 91 | border: 1px solid #aaa; 92 | box-shadow:inset 2px 2px 4px #ccc 93 | } 94 | #loader { 95 | display: none; 96 | position: absolute; 97 | top:0; 98 | bottom: 0; 99 | left: 0; 100 | right: 0; 101 | background: url('examples/loader.gif') center center no-repeat rgba(255,255,255,0.5); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet JSON Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Leaflet JSON Layer

12 |

13 | Simple way for transform any JSON data source in a Dynamic Leaflet Layer! 14 | 15 |
16 | Other useful stuff for Web Mapping... 17 |

18 | 19 |
20 |

Features

21 | 31 | 32 |

Options

33 | 39 |
40 |
41 | 42 |

Examples

43 | 52 | 53 |

Repositories

54 | Github.com 55 |
56 | Bitbucket.org 57 |
58 | Node Packaged Module 59 |
60 | Atmosphere Meteor JS 61 |
62 | 63 |

Download

64 | 69 |
70 | 71 |
72 |
73 |

Usage

74 |
75 | L.layerJSON({url: "search.php?lat1={lat1}&lat2={lat2}&lon1={lon1}&lon2={lon2}" }).addTo(map);
76 | 
77 |
78 | 79 |
80 |
81 | For questions and bugs I recommend you to create New Issue on Github repository.
82 | Or to obtain a fast response consult Official Leaflet community forum.
83 |
84 | This is a micro discussion area for methods of implementation.
85 |
86 | 87 | Github 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2020 Stefano Cudini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-layerjson", 3 | "version": "0.3.4", 4 | "description": "A Dynamic Leaflet Layer that load/filter dinamically JSON data from Ajax/Jsonp, with caching", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:stefanocudini/leaflet-layerjson.git" 8 | }, 9 | "homepage": "https://opengeo.tech/maps/leaflet-layerjson/", 10 | "author": { 11 | "name": "Stefano Cudini", 12 | "email": "stefano.cudini@gmail.com", 13 | "url": "https://opengeo.tech/" 14 | }, 15 | "license": "MIT", 16 | "keywords": [ 17 | "gis", 18 | "map", 19 | "leaflet" 20 | ], 21 | "main": "dist/leaflet-layerjson.min.js", 22 | "dependencies": { 23 | "leaflet": "*" 24 | }, 25 | "devDependencies": { 26 | "grunt": "~0.4.2", 27 | "grunt-cli": "~0.1.11", 28 | "grunt-contrib-uglify": "~0.2.7", 29 | "grunt-contrib-concat": "~0.3.0", 30 | "grunt-contrib-clean": "~0.5.0", 31 | "grunt-contrib-jshint": "~0.7.2", 32 | "grunt-contrib-watch": "~0.5.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/leaflet-layerjson.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 4 | L.LayerJSON = L.FeatureGroup.extend({ 5 | 6 | includes: L.version[0]==='1' ? L.Evented.prototype : L.Mixin.Events, 7 | // 8 | //Managed Events: 9 | // Event Data passed Description 10 | // dataloading {req: url|bbox} fired before ajax/jsonp request, req is bbox if url option is null 11 | // dataloaded {data: json} fired on ajax/jsonp request success 12 | // 13 | options: { 14 | url: '', //url map: "search.php?lat1={lat1}&lat2={lat2}&lon1={lon1}&lon2={lon2}" 15 | jsonpParam: null, //parameter name for jsonp requests 16 | callData: null, //custom function for data source, params: (req: url|bbox, callback: func), return {abort: func} or jQuery jqXHR Object 17 | filterData: null, //function that filter marker by its data, run before onEachMarker 18 | // 19 | locAsGeoJSON: false, //interpret location data as [lon, lat] value pair instead of [lat, lon] 20 | propertyItems: '', //json property used contains data items 21 | propertyTitle: 'title', //json property used as title(popup, marker, icon) 22 | propertyLoc: 'loc', //json property used as Latlng of marker use array for select double fields(ex. ['lat','lon'] ) 23 | propertyId: 'id', //json property used to uniquely identify data items 24 | // // support dotted format: 'prop.subprop.title' 25 | layerTarget: null, //pre-existing layer to add markers, is a LayerGroup or L.MarkerClusterGroup http://goo.gl/tvmu0 26 | dataToMarker: null, //function that will be used for creating markers from json points, similar to pointToLayer of L.GeoJSON 27 | buildPopup: null, //function popup builder 28 | buildIcon: null, //function icon builder 29 | optsPopup: null, //popup options 30 | // 31 | minZoom: 10, //min zoom for call data 32 | caching: true, //enable requests caching 33 | cacheId: null, //function to generate id used to uniquely identify data items in cache 34 | minShift: 1000, //min shift before update data(in meters) 35 | precision: 6, //number of digit send to server for lat,lng precision 36 | updateMarkers: false, //update all markers in map to last results of callData 37 | attribution: '' //attribution text 38 | //TODO option: enabled, if false 39 | //TODO methods: enable()/disable() 40 | //TODO send map bounds decremented of certain margin 41 | }, 42 | 43 | initialize: function(options) { 44 | L.FeatureGroup.prototype.initialize.call(this, []); 45 | L.Util.setOptions(this, options); 46 | this._dataToMarker = this.options.dataToMarker || this._defaultDataToMarker; 47 | this._buildIcon = this.options.buildIcon || this._defaultBuildIcon; 48 | this._filterData = this.options.filterData || null; 49 | this._hashUrl = this.options.url; 50 | 51 | if(this._hashUrl) 52 | { 53 | this._callData = this.getAjax; 54 | if(this.options.jsonpParam) 55 | { 56 | this._hashUrl += '&'+this.options.jsonpParam+'='; 57 | this._callData = this.getJsonp; 58 | } 59 | } 60 | else 61 | this._callData = this.options.callData; 62 | 63 | this._curReq = null; 64 | this._center = null; 65 | this._cacheBounds = null; 66 | this._markersCache = {}; //used for caching _dataToMarker builds 67 | }, 68 | 69 | onAdd: function(map) { 70 | 71 | L.FeatureGroup.prototype.onAdd.call(this, map); //set this._map 72 | this._center = map.getCenter(); 73 | this._cacheBounds = map.getBounds(); 74 | 75 | map.on('moveend zoomend', this._onMove, this); 76 | 77 | this.update(); 78 | }, 79 | 80 | onRemove: function(map) { 81 | 82 | map.off('moveend zoomend', this._onMove, this); 83 | 84 | L.FeatureGroup.prototype.onRemove.call(this, map); 85 | 86 | for (var i in this._layers) { 87 | if (this._layers.hasOwnProperty(i)) { 88 | L.FeatureGroup.prototype.removeLayer.call(this, this._layers[i]); 89 | } 90 | } 91 | }, 92 | 93 | getAttribution: function() { 94 | return this.options.attribution; 95 | }, 96 | 97 | addLayer: function (layer) { 98 | if(this.options.layerTarget) 99 | { 100 | this.options.layerTarget.addLayer.call(this.options.layerTarget, layer); 101 | return this.fire('layeradd', {layer: layer}); 102 | } 103 | else 104 | L.FeatureGroup.prototype.addLayer.call(this, layer); 105 | return this; 106 | }, 107 | 108 | removeLayer: function (layer) { 109 | if(this.options.layerTarget) 110 | { 111 | this.options.layerTarget.removeLayer.call(this.options.layerTarget, layer); 112 | return this.fire('layerremove', {layer: layer}); 113 | } 114 | else 115 | L.FeatureGroup.prototype.removeLayer.call(this, layer); 116 | return this; 117 | }, 118 | 119 | clearLayers: function () { 120 | 121 | var self = this, 122 | newBounds = this._map.getBounds(); 123 | 124 | //self._markersCache = {}; //cached gen markers 125 | 126 | if(self.options.layerTarget) { 127 | //self.options.layerTarget.clearLayers.call(self.options.layerTarget); 128 | self.options.layerTarget.eachLayer(function(l) { 129 | if(!self._contains(newBounds, l) ) { 130 | self.options.layerTarget.removeLayer(l); 131 | delete self._markersCache[l._cacheId]; 132 | } 133 | }); 134 | } 135 | else { 136 | //L.FeatureGroup.prototype.clearLayers.call(self); 137 | self.eachLayer(function(l) { 138 | if(!self._contains(newBounds, l) ) { 139 | self.removeLayer(l); 140 | delete self._markersCache[l._cacheId]; 141 | } 142 | }, self); 143 | } 144 | 145 | return this; 146 | }, 147 | 148 | _debouncer: function(func, timeout) { 149 | var timeoutID; 150 | timeout = timeout || 300; 151 | return function () { 152 | var scope = this , args = arguments; 153 | clearTimeout( timeoutID ); 154 | timeoutID = setTimeout( function () { 155 | func.apply( scope , Array.prototype.slice.call( args ) ); 156 | }, timeout); 157 | }; 158 | }, 159 | 160 | _getPath: function(obj, prop) { 161 | var parts = prop.split('.'), 162 | last = parts.pop(), 163 | len = parts.length, 164 | cur = parts[0], 165 | i = 1; 166 | 167 | if(len > 0) 168 | while((obj = obj[cur]) && i < len) 169 | cur = parts[i++]; 170 | 171 | if(obj) 172 | return obj[last]; 173 | }, 174 | 175 | _defaultBuildIcon: function(data, title) { 176 | return new L.Icon.Default(); 177 | }, 178 | 179 | _defaultDataToMarker: function(data, latlng) { //make marker from data 180 | 181 | var title = this._getPath(data, this.options.propertyTitle), 182 | markerOpts = L.Util.extend({icon: this._buildIcon(data,title), title: title }, data), 183 | marker = new L.Marker(latlng, markerOpts ), 184 | htmlPopup = null; 185 | 186 | if(this.options.buildPopup && (htmlPopup = this.options.buildPopup(data, marker))) 187 | marker.bindPopup(htmlPopup, this.options.optsPopup ); 188 | 189 | return marker; 190 | }, 191 | 192 | addMarker: function(data) { 193 | 194 | var loc, hash, propLoc = this.options.propertyLoc; 195 | 196 | if( L.Util.isArray(propLoc) ) { 197 | loc = L.latLng( parseFloat( this._getPath(data, propLoc[0]) ), 198 | parseFloat( this._getPath(data, propLoc[1]) ) ); 199 | } 200 | else { 201 | if (this.options.locAsGeoJSON) { 202 | var lnglat = this._getPath(data, propLoc); 203 | loc = L.latLng( lnglat[1], lnglat[0] ); 204 | } else { 205 | loc = L.latLng( this._getPath(data, propLoc) ); 206 | } 207 | } 208 | 209 | if(this.options.cacheId) { 210 | hash = this.options.cacheId.call(this, data, loc); 211 | } 212 | else { 213 | if(this.options.propertyId) 214 | hash = this._getPath(data, this.options.propertyId); 215 | else 216 | hash = loc.lat+''+loc.lng+''+this._getPath(data, this.options.propertyTitle); 217 | } 218 | 219 | if(typeof this._markersCache[hash] === 'undefined') { 220 | this._markersCache[hash] = this._dataToMarker(data, loc); 221 | this._markersCache[hash]._cacheId = hash; 222 | this.addLayer( this._markersCache[hash] ); 223 | } 224 | 225 | return hash; 226 | }, 227 | 228 | _contains: function(bounds, el) { 229 | var loc; 230 | 231 | if(el.getLatLng) 232 | loc = el.getLatLng(); 233 | else if(el.getBounds) 234 | loc = el.getBounds(); 235 | 236 | return bounds.contains(loc); 237 | }, 238 | 239 | _loadCacheToBounds: function(bounds) { //show/hide cached markers 240 | for(var i in this._markersCache) 241 | { 242 | if(this._markersCache[i]) 243 | { 244 | if(this._contains(bounds, this._markersCache[i]) ) 245 | this.addLayer(this._markersCache[i]); 246 | else 247 | this.removeLayer(this._markersCache[i]); 248 | } 249 | } 250 | }, 251 | 252 | _onMove: function(e) { 253 | var newZoom = this._map.getZoom(), 254 | newCenter = this._map.getCenter(), 255 | newBounds = this._map.getBounds(); 256 | 257 | if(newZoom < this.options.minZoom) 258 | return false; 259 | 260 | if( this.options.minShift && this._center.distanceTo(newCenter) < this.options.minShift ) 261 | return false; 262 | else 263 | this._center = newCenter; 264 | 265 | if(this.options.caching) { 266 | 267 | if( this._cacheBounds.contains(newBounds) ) 268 | { 269 | this._loadCacheToBounds(newBounds); 270 | return false; 271 | } 272 | else 273 | this._cacheBounds.extend(newBounds); 274 | } 275 | else 276 | this.clearLayers(); 277 | 278 | this.update(); 279 | }, 280 | 281 | update: function() { //populate target layer 282 | 283 | var self = this; 284 | 285 | var prec = self.options.precision, 286 | bb = self._map.getBounds(), 287 | sw = bb.getSouthWest(), 288 | ne = bb.getNorthEast(), 289 | bbox = [ 290 | [ parseFloat(sw.lat.toFixed(prec)), parseFloat(sw.lng.toFixed(prec)) ], 291 | [ parseFloat(ne.lat.toFixed(prec)), parseFloat(ne.lng.toFixed(prec)) ] 292 | ]; 293 | 294 | if(self._hashUrl) //conver bbox to url string 295 | bbox = L.Util.template(self._hashUrl, { 296 | lat1: bbox[0][0], lon1: bbox[0][1], 297 | lat2: bbox[1][0], lon2: bbox[1][1] 298 | }); 299 | 300 | if(self._curReq && self._curReq.abort) 301 | self._curReq.abort(); //prevent parallel requests 302 | 303 | 304 | self.fire('dataloading', {req: bbox }); 305 | self._curReq = self._callData(bbox, function(json) { 306 | 307 | //console.log('callData',json) 308 | 309 | self._curReq = null; 310 | 311 | if(self._filterData) 312 | json = self._filterData(json); 313 | 314 | if(self.options.propertyItems) 315 | json = self._getPath(json, self.options.propertyItems); 316 | 317 | self.fire('dataloaded', {data: json}); 318 | 319 | self.updateMarkers(json); 320 | }); 321 | }, 322 | 323 | updateMarkers: function(json) { 324 | var jsonbyhash = {}; 325 | 326 | for (var k in json) { 327 | if (!isNaN(parseFloat(k)) && isFinite(k)) { 328 | var h = this.addMarker.call(this, json[k]); 329 | jsonbyhash[h] = 1; 330 | } 331 | } 332 | 333 | if(this.options.updateMarkers) { 334 | for (var c in this._markersCache) { 335 | if(!jsonbyhash[ this._markersCache[c]._cacheId ]) 336 | this.removeLayer(this._markersCache[c]); 337 | else 338 | this.addLayer(this._markersCache[c]); 339 | } 340 | } 341 | }, 342 | 343 | 344 | /////////////////ajax jsonp methods 345 | 346 | getAjax: function(url, cb) { //default ajax request 347 | 348 | if (window.XMLHttpRequest === undefined) { 349 | window.XMLHttpRequest = function() { 350 | try { 351 | return new ActiveXObject("Microsoft.XMLHTTP.6.0"); 352 | } 353 | catch (e1) { 354 | try { 355 | return new ActiveXObject("Microsoft.XMLHTTP.3.0"); 356 | } 357 | catch (e2) { 358 | throw new Error("XMLHttpRequest is not supported"); 359 | } 360 | } 361 | }; 362 | } 363 | var request = new XMLHttpRequest(); 364 | request.open('GET', url); 365 | request.onreadystatechange = function() { 366 | var response = {}; 367 | if (request.readyState === 4 && request.status === 200) { 368 | try { 369 | if(window.JSON) 370 | response = JSON.parse(request.responseText); 371 | else 372 | response = eval("("+ request.responseText + ")"); 373 | } catch(err) { 374 | response = {}; 375 | throw new Error('Ajax response is not JSON'); 376 | } 377 | cb(response); 378 | } 379 | }; 380 | request.send(); 381 | return request; 382 | }, 383 | 384 | getJsonp: function(url, cb) { //extract searched records from remote jsonp service 385 | var body = document.getElementsByTagName('body')[0], 386 | script = L.DomUtil.create('script','leaflet-layerjson-jsonp', body ); 387 | 388 | //JSONP callback 389 | L.LayerJSON.callJsonp = function(data) { 390 | cb(data); 391 | body.removeChild(script); 392 | } 393 | script.type = 'text/javascript'; 394 | script.src = url+'L.LayerJSON.callJsonp'; 395 | return { 396 | abort: function() { 397 | script.parentNode.removeChild(script); 398 | } 399 | }; 400 | } 401 | }); 402 | 403 | L.layerJSON = function (options) { 404 | return new L.LayerJSON(options); 405 | }; 406 | 407 | }).call(this); 408 | --------------------------------------------------------------------------------