Esri Leaflet Dynamic TileLayer Plugin Example

While working in one my Esri-Leaflet based application, I am stumbled on rendering dynamic map layers on tiled basis. Esri leaflet-api does the perfect job on non-tiled layer display but L.esri.DynamicMapLayer, doesn’t provide the tiled dynamic map layers. Looking around I came across the Tiled dynamic layer plugin for esri-leaflet, for displaying the DynamicMapLayer in tiled mode.

Here I have created both non-tiled and tiled DynamicMapLayer version for comparison using plain esri-leaflet and esri-leaflet with plugin.


1:  <!DOCTYPE html>  
2:  <html>  
3:  <head>  
4:    <title>ESRI Leaflet Dynamic Map Layer With Map Tiles</title>  
5:    <meta charset="utf-8" />  
6:    <link   
7:      rel="stylesheet"   
8:      href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css"  
9:    />  
10:  </head>  
11:  <body>  
12:    <div id="map-non-tiled" style="width: 600px; height: 400px"></div>  
13:    <div id="map-tiled" style="width: 600px; height: 400px"></div>  
14:    
15:  <script src="//cdn.jsdelivr.net/leaflet/0.7.3/leaflet.js"></script>  
16:  <script src="//cdn.jsdelivr.net/leaflet.esri/1.0.0-rc.7/esri-leaflet.js"></script>  
17:     
18:  <script>  
19:  (function (factory) {  
20:   //define an AMD module that relies on 'leaflet'  
21:   if (typeof define === 'function' && define.amd) {  
22:    define(['leaflet', 'esri-leaflet'], function (L, EsriLeaflet) {  
23:     return factory(L, EsriLeaflet);  
24:    });  
25:   //define a common js module that relies on 'leaflet'  
26:   } else if (typeof module === 'object' && typeof module.exports === 'object') {  
27:    module.exports = factory(require('leaflet'), require('esri-leaflet'));  
28:   }  
29:    
30:   if(typeof window !== 'undefined' && window.L){  
31:    factory(window.L, L.esri);  
32:   }  
33:  }(function (L, EsriLeaflet) {  
34:    
35:  EsriLeaflet.Layers.TiledDynamicMapLayer = L.TileLayer.extend({  
36:    
37:   options: L.Util.extend({}, EsriLeaflet.Layers.DynamicMapLayer.prototype.options),  
38:    
39:   _requests: [],  
40:    
41:   /**  
42:    * @constructor  
43:    * @extends {L.TileLayer}  
44:    * @param {String} url  
45:    * @param {Object} options  
46:    */  
47:   initialize: function(url, options) {  
48:    L.TileLayer.prototype.initialize.call(this, url, options);  
49:    EsriLeaflet.DynamicMapLayer.prototype.initialize.call(this, url, options);  
50:   },  
51:    
52:   /**  
53:    * @param {L.Map} map  
54:    */  
55:   onAdd: function(map) {  
56:    if (map.options.crs && map.options.crs.code) {  
57:     var sr = map.options.crs.code.split(':')[1];  
58:     this.options.bboxSR = sr;  
59:     this.options.imageSR = sr;  
60:    }  
61:    
62:    map.on('zoomstart zoomend', this._onZoomChange, this);  
63:    return L.TileLayer.prototype.onAdd.call(this, map);  
64:   },  
65:    
66:   /**  
67:    * @param {L.Map} map  
68:    */  
69:   onRemove: function(map) {  
70:    map.off('zoomstart zoomend', this._onZoomChange, this);  
71:    L.TileLayer.prototype.onRemove.call(this, map);  
72:    },  
73:    
74:   /**  
75:    * @param {Array.<Number>|Array.<String>} layers  
76:    * @return {L.esri.Layers.TiledDynamicMapLayer} self  
77:    */  
78:   setLayers: function(layers) {  
79:    this._reset();  
80:    return EsriLeaflet.Layers.DynamicMapLayer.prototype.setLayers.call(this, layers);  
81:   },  
82:    
83:   /**  
84:    * @param {Array.<Object>} layerDefs  
85:    * @return {L.esri.Layers.TiledDynamicMapLayer} self  
86:    */  
87:   setLayerDefs: function(layerDefs) {  
88:    this._reset();  
89:    return EsriLeaflet.Layers.DynamicMapLayer.prototype.setLayerDefs.call(this, layerDefs);  
90:   },  
91:    
92:   /**  
93:    * @param {Object} timeOptions  
94:    * @return {L.esri.Layers.TiledDynamicMapLayer} self  
95:    */  
96:   setTimeOptions: function(timeOptions) {  
97:    this._reset();  
98:    return EsriLeaflet.Layers.DynamicMapLayer.prototype.setTimeOptions.call(this, timeOptions);  
99:   },  
100:    
101:   /**  
102:    * Set/unset zooming flag to avoid unneeded requests  
103:    * @param {Object} e  
104:    */  
105:   _onZoomChange: function(e) {  
106:    this._zooming = (e.type === 'zoomstart');  
107:   },  
108:    
109:   /**  
110:    * @param {L.LatLngBounds} bounds  
111:    * @param {L.Point}    size  
112:    * @return {Object}  
113:    */  
114:   _buildExportParams: function(bounds, size) {  
115:    var ne = this._map.options.crs.project(bounds._northEast);  
116:    var sw = this._map.options.crs.project(bounds._southWest);  
117:    
118:    //ensure that we don't ask ArcGIS Server for a taller image than we have actual map displaying  
119:    var top = this._map.latLngToLayerPoint(bounds._northEast);  
120:    var bottom = this._map.latLngToLayerPoint(bounds._southWest);  
121:    
122:    if (top.y > 0 || bottom.y < size.y) {  
123:     size.y = bottom.y - top.y;  
124:    }  
125:    
126:    var params = {  
127:     bbox: [sw.x, sw.y, ne.x, ne.y].join(','),  
128:     size: size.x + ',' + size.y,  
129:     dpi: 96,  
130:     format: this.options.format,  
131:     transparent: this.options.transparent,  
132:     bboxSR: this.options.bboxSR,  
133:     imageSR: this.options.imageSR  
134:    };  
135:    
136:    if (this.options.layers) {  
137:     params.layers = 'show:' + this.options.layers.join(',');  
138:    }  
139:    
140:    if (this.options.layerDefs) {  
141:     params.layerDefs = JSON.stringify(this.options.layerDefs);  
142:    }  
143:    
144:    if (this.options.timeOptions) {  
145:     params.timeOptions = JSON.stringify(this.options.timeOptions);  
146:    }  
147:    
148:    if (this.options.from && this.options.to) {  
149:     params.time = this.options.from.valueOf() + ',' + this.options.to.valueOf();  
150:    }  
151:    
152:    if (this._service.options.token) {  
153:     params.token = this._service.options.token;  
154:    }  
155:    
156:    return params;  
157:   },  
158:    
159:   /**  
160:    * @param {Object} tile  
161:    * @param {L.Point} tilePoint  
162:    */  
163:   _loadTile: function(tile, tilePoint) {  
164:    tile._layer = this;  
165:    tile.onload = this._tileOnLoad;  
166:    tile.onerror = this._tileOnError;  
167:    
168:    this._adjustTilePoint(tilePoint);  
169:    this.getTileUrl(tilePoint, function(err, url) {  
170:     tile.src = url;  
171:    });  
172:    
173:    this.fire('tileloadstart', {  
174:     tile: tile  
175:    });  
176:   },  
177:    
178:   /**  
179:    * Async request tile url  
180:    * @param {L.Point} tilePoint  
181:    * @param {Function} callback  
182:    */  
183:   getTileUrl: function(tilePoint, callback) { // (Point, Number) -> String  
184:    
185:    var map = this._map,  
186:     tileSize = this.options.tileSize,  
187:    
188:     nwPoint = tilePoint.multiplyBy(tileSize),  
189:     sePoint = nwPoint.add([tileSize, tileSize]);  
190:    
191:    var bounds = new L.LatLngBounds(map.unproject(nwPoint, tilePoint.z),  
192:     map.unproject(sePoint, tilePoint.z));  
193:    var size = new L.Point(this.options.tileSize, this.options.tileSize);  
194:    
195:    var params = this._buildExportParams(bounds, size);  
196:    this._requestExport(params, bounds, callback);  
197:   },  
198:    
199:   /**  
200:    * Export call, json or image straight awy  
201:    * @param {Object}     params  
202:    * @param {L.LatLngBounds} bounds  
203:    * @param {Function}    callback  
204:    */  
205:   _requestExport: function(params, bounds, callback) {  
206:    if (this.options.f === 'json') {  
207:     this._requests.push(this._service.get('export', params, function(error, response) {  
208:      callback(null, response.href, bounds);  
209:     }, this));  
210:    } else {  
211:     params.f = 'image';  
212:     callback(null, this.options.url + 'export' + L.Util.getParamString(params), bounds);  
213:    }  
214:   },  
215:    
216:   /**  
217:    * Bounds or params changed  
218:    */  
219:   _update: function() {  
220:    if (this._map && this._map._animatingZoom) {  
221:     return;  
222:    }  
223:    L.TileLayer.prototype._update.call(this);  
224:   }  
225:    
226:  });  
227:    
228:  (function(methods) {  
229:   for (var i = 0, len = methods.length; i < len; i++) {  
230:    EsriLeaflet.Layers.TiledDynamicMapLayer.prototype[methods[i]] =  
231:     EsriLeaflet.Layers.DynamicMapLayer.prototype[methods[i]];  
232:   }  
233:  })([  
234:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype.getLayers as getLayers */  
235:   'getLayers',  
236:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype.getLayerDefs as getLayerDefs */  
237:   'getLayerDefs',  
238:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype.getTimeOptions as getTimeOptions */  
239:   'getTimeOptions',  
240:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype.metadata as metadata */  
241:   'metadata',  
242:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype.query as query */  
243:   'query',  
244:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype.identify as identify */  
245:   'identify',  
246:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype.find as find */  
247:   'find',  
248:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype._getPopupData as _getPopupData */  
249:   '_getPopupData',  
250:   /** @borrows L.esri.Layers.DynamicMapLayer.prototype._propagateEvent as _propagateEvent */  
251:   '_propagateEvent'  
252:  ]);  
253:    
254:  // factory  
255:  EsriLeaflet.tiledDynamicMapLayer = function(url, options) {  
256:   return new EsriLeaflet.Layers.TiledDynamicMapLayer(url, options);  
257:  };  
258:    
259:    
260:   return EsriLeaflet;  
261:  }));  
262:  //# sourceMappingURL=esri-leaflet-dynamic-tilelayer-src.js.map  
263:    
264:  </script>  
265:    
266:    
267:    
268:    
269:  <script>  
270:         
271:  var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';  
272:           
273:  // create map1  
274:  var map1 = new L.Map('map-non-tiled');  
275:  L.tileLayer(osmUrl, {  
276:    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'  
277:  }).addTo(map1);  
278:  // set the map1's starting view  
279:  map1.setView( new L.LatLng(39.828328,-98.57941), 4 );  
280:    
281:    
282:  // create map2  
283:  var map2 = new L.Map('map-tiled');  
284:  L.tileLayer(osmUrl, {  
285:    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'  
286:  }).addTo(map2);  
287:    
288:  // set the map2's starting view  
289:  map2.setView( new L.LatLng(39.828328,-98.57941), 4 );   
290:     
291:   var urlLayer = "//server.arcgisonline.com/ArcGIS/rest/services/Specialty/Soil_Survey_Map/MapServer";  
292:   var layer1 =L.esri.dynamicMapLayer(urlLayer,{  
293:     layers:0,  
294:     bboxSR:102100,  
295:     imageSR:102100,       
296:     format:'png24',  
297:     transparent:true,  
298:     f:'image',  
299:     tiled:true,  
300:     zoom:8  
301:   });  
302:     
303:   var layer2 = L.esri.tiledDynamicMapLayer(urlLayer,{  
304:     layers:0,  
305:     bboxSR:102100,  
306:     imageSR:102100,    
307:     format:'png24',  
308:     transparent:true,  
309:     f:'image',  
310:     tiled:true,  
311:     zoom:8  
312:   });  
313:     
314:   layer1.addTo(map1);  
315:   layer2.addTo(map2);   
316:    </script>  
317:  </body>  
318:  </html>  
319:    

Hope some will find this helpful in the future.

Comments

Popular posts from this blog

Extract Raster Values from Points

Update WPF Interaction Triggers in from .Net Framework 4.8 to .Net 5

Instruct GIT not to track a specific file