1/* 2 * Load files *locally* (GeoJSON, KML, GPX) into the map 3 * using the HTML5 File API. 4 * 5 * Requires Mapbox's togeojson.js to be in global scope 6 * https://github.com/mapbox/togeojson 7 */ 8 9(function (factory, window) { 10 // define an AMD module that relies on 'leaflet' 11 if (typeof define === 'function' && define.amd && window.toGeoJSON) { 12 define(['leaflet'], function (L) { 13 factory(L, window.toGeoJSON); 14 }); 15 } else if (typeof module === 'object' && module.exports) { 16 // require('LIBRARY') returns a factory that requires window to 17 // build a LIBRARY instance, we normalize how we use modules 18 // that require this pattern but the window provided is a noop 19 // if it's defined 20 module.exports = function (root, L, toGeoJSON) { 21 if (L === undefined) { 22 if (typeof window !== 'undefined') { 23 L = require('leaflet'); 24 } else { 25 L = require('leaflet')(root); 26 } 27 } 28 if (toGeoJSON === undefined) { 29 if (typeof window !== 'undefined') { 30 toGeoJSON = require('togeojson'); 31 } else { 32 toGeoJSON = require('togeojson')(root); 33 } 34 } 35 factory(L, toGeoJSON); 36 return L; 37 }; 38 } else if (typeof window !== 'undefined' && window.L && window.toGeoJSON) { 39 factory(window.L, window.toGeoJSON); 40 } 41}(function fileLoaderFactory(L, toGeoJSON) { 42 var FileLoader = L.Layer.extend({ 43 options: { 44 layer: L.geoJson, 45 layerOptions: {}, 46 fileSizeLimit: 1024 47 }, 48 49 initialize: function (map, options) { 50 this._map = map; 51 L.Util.setOptions(this, options); 52 53 this._parsers = { 54 geojson: this._loadGeoJSON, 55 json: this._loadGeoJSON, 56 gpx: this._convertToGeoJSON, 57 kml: this._convertToGeoJSON 58 }; 59 }, 60 61 load: function (file, ext) { 62 var parser, 63 reader; 64 65 // Check file is defined 66 if (this._isParameterMissing(file, 'file')) { 67 return false; 68 } 69 70 // Check file size 71 if (!this._isFileSizeOk(file.size)) { 72 return false; 73 } 74 75 // Get parser for this data type 76 parser = this._getParser(file.name, ext); 77 if (!parser) { 78 return false; 79 } 80 81 // Read selected file using HTML5 File API 82 reader = new FileReader(); 83 reader.onload = L.Util.bind(function (e) { 84 var layer; 85 try { 86 this.fire('data:loading', { filename: file.name, format: parser.ext }); 87 layer = parser.processor.call(this, e.target.result, parser.ext); 88 this.fire('data:loaded', { 89 layer: layer, 90 filename: file.name, 91 format: parser.ext 92 }); 93 } catch (err) { 94 this.fire('data:error', { error: err }); 95 } 96 }, this); 97 // Testing trick: tests don't pass a real file, 98 // but an object with file.testing set to true. 99 // This object cannot be read by reader, just skip it. 100 if (!file.testing) { 101 reader.readAsText(file); 102 } 103 // We return this to ease testing 104 return reader; 105 }, 106 107 loadMultiple: function (files, ext) { 108 var readers = []; 109 if (files[0]) { 110 files = Array.prototype.slice.apply(files); 111 while (files.length > 0) { 112 readers.push(this.load(files.shift(), ext)); 113 } 114 } 115 // return first reader (or false if no file), 116 // which is also used for subsequent loadings 117 return readers; 118 }, 119
120 loadData: function (data, name, ext) { 121 var parser; 122 var layer; 123 124 // Check required parameters 125 if ((this._isParameterMissing(data, 'data')) 126 || (this._isParameterMissing(name, 'name'))) { 127 return; 128 } 129 130 // Check file size 131 if (!this._isFileSizeOk(data.length)) { 132 return; 133 } 134 135 // Get parser for this data type 136 parser = this._getParser(name, ext); 137 if (!parser) { 138 return; 139 } 140 141 // Process data 142 try { 143 this.fire('data:loading', { filename: name, format: parser.ext }); 144 layer = parser.processor.call(this, data, parser.ext); 145 this.fire('data:loaded', { 146 layer: layer, 147 filename: name, 148 format: parser.ext 149 }); 150 } catch (err) { 151 this.fire('data:error', { error: err }); 152 } 153 }, 154 155 _isParameterMissing: function (v, vname) { 156 if (typeof v === 'undefined') { 157 this.fire('data:error', { 158 error: new Error('Missing parameter: ' + vname) 159 }); 160 return true; 161 } 162 return false; 163 }, 164 165 _getParser: function (name, ext) { 166 var parser; 167 ext = ext || name.split('.').pop(); 168 parser = this._parsers[ext]; 169 if (!parser) { 170 this.fire('data:error', { 171 error: new Error('Unsupported file type (' + ext + ')') 172 }); 173 return undefined; 174 } 175 return { 176 processor: parser, 177 ext: ext 178 }; 179 }, 180 181 _isFileSizeOk: function (size) { 182 var fileSize = (size / 1024).toFixed(4); 183 if (fileSize > this.options.fileSizeLimit) { 184 this.fire('data:error', { 185 error: new Error( 186 'File size exceeds limit (' + 187 fileSize + ' > ' + 188 this.options.fileSizeLimit + 'kb)' 189 ) 190 }); 191 return false; 192 } 193 return true; 194 }, 195 196 _loadGeoJSON: function _loadGeoJSON(content) { 197 var layer; 198 if (typeof content === 'string') { 199 content = JSON.parse(content); 200 } 201 layer = this.options.layer(content, this.options.layerOptions); 202 203 if (layer.getLayers().length === 0) { 204 throw new Error('GeoJSON has no valid layers.'); 205 } 206 207 if (this.options.addToMap) { 208 layer.addTo(this._map); 209 } 210 return layer; 211 }, 212 213 _convertToGeoJSON: function _convertToGeoJSON(content, format) { 214 var geojson; 215 // Format is either 'gpx' or 'kml' 216 if (typeof content === 'string') { 217 content = (new window.DOMParser()).parseFromString(content, 'text/xml'); 218 } 219 geojson = toGeoJSON[format](content); 220 return this._loadGeoJSON(geojson); 221 } 222 }); 223 224 var FileLayerLoad = L.Control.extend({ 225 statics: { 226 TITLE: 'Load local file (GPX, KML, GeoJSON)', 227 LABEL: '⌅' 228 }, 229 options: { 230 position: 'topright', 231 fitBounds: true, 232 layerOptions: {}, 233 addToMap: true, 234 fileSizeLimit: 1024 235 }, 236 237 initialize: function (options) { 238 L.Util.setOptions(this, options); 239 this.loader = null; 240 }, 241 242 onAdd: function (map) { 243 this.loader = L.FileLayer.fileLoader(map, this.options); 244 245 this.loader.on('data:loaded', function (e) { 246 // Fit bounds after loading 247 if (this.options.fitBounds) { 248 window.setTimeout(function () { 249 map.fitBounds(e.layer.getBounds()); 250 }, 500); 251 } 252 }, this); 253 254 // Initialize Drag-and-drop 255 this._initDragAndDrop(map); 256 257 // Initialize map control 258 return this._initContainer(); 259 }, 260 261 _initDragAndDrop: function (map) { 262 var callbackName; 263 var thisLoader = this.loader; 264 var dropbox = map._container; 265 266 var callbacks = { 267 dragenter: function () { 268 map.scrollWheelZoom.disable(); 269 }, 270 dragleave: function () { 271 map.scrollWheelZoom.enable(); 272 }, 273 dragover: function (e) { 274 e.stopPropagation(); 275 e.preventDefault(); 276 }, 277 drop: function (e) { 278 e.stopPropagation(); 279 e.preventDefault(); 280 281 thisLoader.loadMultiple(e.dataTransfer.files); 282 map.scrollWheelZoom.enable(); 283 } 284 }; 285 for (callbackName in callbacks) { 286 if (callbacks.hasOwnProperty(callbackName)) { 287 dropbox.addEventListener(callbackName, callbacks[callbackName], false); 288 } 289 } 290 }, 291 292 _initContainer: function () { 293 var thisLoader = this.loader; 294 295 // Create a button, and bind click on hidden file input 296 var fileInput; 297 var zoomName = 'leaflet-control-filelayer leaflet-control-zoom'; 298 var barName = 'leaflet-bar'; 299 var partName = barName + '-part'; 300 var container = L.DomUtil.create('div', zoomName + ' ' + barName); 301 var link = L.DomUtil.create('a', zoomName + '-in ' + partName, container); 302 link.innerHTML = L.Control.FileLayerLoad.LABEL; 303 link.href = '#'; 304 link.title = L.Control.FileLayerLoad.TITLE; 305 306 // Create an invisible file input 307 fileInput = L.DomUtil.create('input', 'hidden', container); 308 fileInput.type = 'file'; 309 fileInput.multiple = 'multiple'; 310 if (!this.options.formats) { 311 fileInput.accept = '.gpx,.kml,.json,.geojson'; 312 } else { 313 fileInput.accept = this.options.formats.join(','); 314 } 315 fileInput.style.display = 'none'; 316 // Load on file change 317 fileInput.addEventListener('change', function () { 318 thisLoader.loadMultiple(this.files); 319 // reset so that the user can upload the same file again if they want to 320 this.value = ''; 321 }, false); 322 323 L.DomEvent.disableClickPropagation(container); 324 L.DomEvent.on(link, 'click', function (e) { 325 fileInput.click(); 326 e.preventDefault(); 327 }); 328 return container; 329 } 330 }); 331 332 L.FileLayer = {}; 333 L.FileLayer.FileLoader = FileLoader; 334 L.FileLayer.fileLoader = function (map, options) { 335 return new L.FileLayer.FileLoader(map, options); 336 }; 337 338 L.Control.FileLayerLoad = FileLayerLoad; 339 L.Control.fileLayerLoad = function (options) { 340 return new L.Control.FileLayerLoad(options); 341 }; 342}, window));
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.