1/* 2 * Leaflet JSON Layer v0.2.8 - 2017-08-11 3 * 4 * Copyright 2017 Stefano Cudini 5 * [email protected] 6 * http://labs.easyblog.it/ 7 * 8 * Licensed under the MIT license. 9 * 10 * Demo: 11 * http://labs.easyblog.it/maps/leaflet-layerjson/ 12 * 13 * Source: 14 * [email protected]:stefanocudini/leaflet-layerjson.git 15 * 16 */ 17 18if (typeof L != 'undefined') { 19 20 (function() { 21 22 L.LayerJSON = L.FeatureGroup.extend({ 23 24 includes: L.version[0]==='1' ? L.Evented.prototype : L.Mixin.Events, 25 // 26 //Managed Events: 27 // Event Data passed Description 28 // dataloading {req: url|bbox} fired before ajax/jsonp request, req is bbox if url option is null 29 // dataloaded {data: json} fired on ajax/jsonp request success 30 // 31 options: { 32 url: '', //url map: "search.php?lat1={lat1}&lat2={lat2}&lon1={lon1}&lon2={lon2}" 33 jsonpParam: null, //parameter name for jsonp requests 34 callData: null, //custom function for data source, params: (req: url|bbox, callback: func), return {abort: func} or jQuery jqXHR Object 35 filterData: null, //function that filter marker by its data, run before onEachMarker 36 // 37 locAsGeoJSON: false, //interpret location data as [lon, lat] value pair instead of [lat, lon] 38 propertyItems: '', //json property used contains data items 39 propertyTitle: 'title', //json property used as title(popup, marker, icon) 40 propertyLoc: 'loc', //json property used as Latlng of marker use array for select double fields(ex. ['lat','lon'] ) 41 propertyId: 'id', //json property used to uniquely identify data items 42 // // support dotted format: 'prop.subprop.title' 43 layerTarget: null, //pre-existing layer to add markers, is a LayerGroup or L.MarkerClusterGroup http://goo.gl/tvmu0 44 dataToMarker: null, //function that will be used for creating markers from json points, similar to pointToLayer of L.GeoJSON 45 onEachMarker: null, //function called on each marker created, similar to option onEachFeature of L.GeoJSON 46 buildPopup: null, //function popup builder 47 buildIcon: null, //function icon builder 48 optsPopup: null, //popup options 49 // 50 minZoom: 10, //min zoom for call data 51 caching: true, //enable requests caching 52 cacheId: null, //function to generate id used to uniquely identify data items in cache 53 minShift: 1000, //min shift before update data(in meters) 54 precision: 6, //number of digit send to server for lat,lng precision 55 attribution: '' //attribution text 56 //TODO option: enabled, if false 57 //TODO methods: enable()/disable() 58 //TODO send map bounds decremented of certain margin 59 }, 60 61 initialize: function(options) { 62 L.FeatureGroup.prototype.initialize.call(this, []); 63 L.Util.setOptions(this, options); 64 this._dataToMarker = this.options.dataToMarker || this._defaultDataToMarker; 65 this._buildIcon = this.options.buildIcon || this._defaultBuildIcon; 66 this._filterData = this.options.filterData || null; 67 this._hashUrl = this.options.url; 68 69 if(this._hashUrl) 70 { 71 this._callData = this.getAjax; 72 if(this.options.jsonpParam) 73 { 74 this._hashUrl += '&'+this.options.jsonpParam+'='; 75 this._callData = this.getJsonp; 76 } 77 } 78 else 79 this._callData = this.options.callData; 80 81 this._curReq = null; 82 this._center = null; 83 this._cacheBounds = null; 84 this._markersCache = {}; //used for caching _dataToMarker builds 85 }, 86 87 onAdd: function(map) { 88 89 L.FeatureGroup.prototype.onAdd.call(this, map); //set this._map 90 this._center = map.getCenter(); 91 this._cacheBounds = map.getBounds(); 92 93 map.on('moveend zoomend', this._onMove, this); 94 95 this.update(); 96 }, 97 98 onRemove: function(map) { 99 100 map.off('moveend zoomend', this._onMove, this); 101 102 L.FeatureGroup.prototype.onRemove.call(this, map); 103 104 for (var i in this._layers) { 105 if (this._layers.hasOwnProperty(i)) { 106 L.FeatureGroup.prototype.removeLayer.call(this, this._layers[i]); 107 } 108 } 109 }, 110 111 getAttribution: function() { 112 return this.options.attribution; 113 }, 114 115 addLayer: function (layer) { 116 if(this.options.layerTarget) 117 { 118 this.options.layerTarget.addLayer.call(this.options.layerTarget, layer); 119 return this.fire('layeradd', {layer: layer}); 120 } 121 else 122 L.FeatureGroup.prototype.addLayer.call(this, layer); 123 return this; 124 }, 125 126 removeLayer: function (layer) { 127 if(this.options.layerTarget) 128 { 129 this.options.layerTarget.removeLayer.call(this.options.layerTarget, layer); 130 return this.fire('layerremove', {layer: layer}); 131 } 132 else 133 L.FeatureGroup.prototype.removeLayer.call(this, layer); 134 return this; 135 }, 136 137 clearLayers: function () { 138 139 this._markersCache = {}; //cached gen markers 140 141 if(this.options.layerTarget) 142 this.options.layerTarget.clearLayers.call(this.options.layerTarget); 143 else 144 L.FeatureGroup.prototype.clearLayers.call(this); 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 222 if(this.options.onEachMarker)//maybe useless 223 this.options.onEachMarker(data, this._markersCache[hash]); 224 225 if(this._markersCache[hash]) 226 this.addLayer( this._markersCache[hash] ); 227 }, 228
229 _contains: function(bounds, el) { 230 var loc; 231 232 if(el.getLatLng) 233 loc = el.getLatLng(); 234 else if(el.getBounds) 235 loc = el.getBounds(); 236 237 return bounds.contains(loc); 238 }, 239 240 _loadCacheToBounds: function(bounds) { //show/hide cached markers 241 for(var i in this._markersCache) 242 { 243 if(this._markersCache[i]) 244 { 245 if(this._contains(bounds, this._markersCache[i]) ) 246 this.addLayer(this._markersCache[i]); 247 else 248 this.removeLayer(this._markersCache[i]); 249 } 250 } 251 }, 252 253 _onMove: function(e) { 254 var newZoom = this._map.getZoom(), 255 newCenter = this._map.getCenter(), 256 newBounds = this._map.getBounds(); 257 258 if(newZoom < this.options.minZoom) 259 return false; 260 261 if( this.options.minShift && this._center.distanceTo(newCenter) < this.options.minShift ) 262 return false; 263 else 264 this._center = newCenter; 265 266 if(this.options.caching) { 267 268 if( this._cacheBounds.contains(newBounds) ) 269 { 270 this._loadCacheToBounds(newBounds); 271 return false; 272 } 273 else 274 this._cacheBounds.extend(newBounds); 275 } 276 else 277 this.clearLayers(); 278 279 this.update(); 280 }, 281 282 update: function() { //populate target layer 283 284 var self = this; 285 286 var prec = self.options.precision, 287 bb = self._map.getBounds(),
288 sw = bb.getSouthWest(), 289 ne = bb.getNorthEast(), 290 bbox = [ 291 [ parseFloat(sw.lat.toFixed(prec)), parseFloat(sw.lng.toFixed(prec)) ], 292 [ parseFloat(ne.lat.toFixed(prec)), parseFloat(ne.lng.toFixed(prec)) ] 293 ]; 294 295 if(self._hashUrl) //conver bbox to url string 296 bbox = L.Util.template(self._hashUrl, { 297 lat1: bbox[0][0], lon1: bbox[0][1], 298 lat2: bbox[1][0], lon2: bbox[1][1] 299 }); 300 301 if(self._curReq && self._curReq.abort) 302 self._curReq.abort(); //prevent parallel requests 303 304 305 self.fire('dataloading', {req: bbox }); 306 self._curReq = self._callData(bbox, function(json) { 307 308 self._curReq = null; 309 310 if(self._filterData) 311 json = self._filterData(json); 312 313 if(self.options.propertyItems) 314 json = self._getPath(json, self.options.propertyItems); 315 316 self.fire('dataloaded', {data: json}); 317 318 for (var k in json) { 319 if (!isNaN(parseFloat(k)) && isFinite(k)) 320 self.addMarker.call(self, json[k]); 321 } 322 }); 323 }, 324 325 326 /////////////////ajax jsonp methods 327 328 getAjax: function(url, cb) { //default ajax request 329 330 if (window.XMLHttpRequest === undefined) { 331 window.XMLHttpRequest = function() { 332 try { 333 return new ActiveXObject("Microsoft.XMLHTTP.6.0"); 334 } 335 catch (e1) { 336 try { 337 return new ActiveXObject("Microsoft.XMLHTTP.3.0"); 338 } 339 catch (e2) { 340 throw new Error("XMLHttpRequest is not supported"); 341 } 342 } 343 }; 344 } 345 var request = new XMLHttpRequest(); 346 request.open('GET', url); 347 request.onreadystatechange = function() { 348 var response = {}; 349 if (request.readyState === 4 && request.status === 200) { 350 try { 351 if(window.JSON) 352 response = JSON.parse(request.responseText); 353 else 354 response = eval("("+ request.responseText + ")"); 355 } catch(err) { 356 response = {}; 357 throw new Error('Ajax response is not JSON'); 358 } 359 cb(response); 360 } 361 }; 362 request.send(); 363 return request; 364 }, 365 366 getJsonp: function(url, cb) { //extract searched records from remote jsonp service 367 var body = document.getElementsByTagName('body')[0], 368 script = L.DomUtil.create('script','leaflet-layerjson-jsonp', body ); 369 370 //JSONP callback 371 L.LayerJSON.callJsonp = function(data) { 372 cb(data); 373 body.removeChild(script); 374 } 375 script.type = 'text/javascript'; 376 script.src = url+'L.LayerJSON.callJsonp'; 377 return { 378 abort: function() { 379 script.parentNode.removeChild(script); 380 } 381 }; 382 } 383 }); 384 385 L.layerJSON = function (options) { 386 return new L.LayerJSON(options); 387 }; 388 389 }).call(this); 390 391}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.