/** * @module nyc/LocalStorage */ import $ from 'jquery' import nyc from 'nyc' /** * @desc Class to provide access to localStorage and filesystem * @public * @class */ class LocalStorage { /** * @desc Check if download is available * @public * @method * @return {boolean} True if download is available */ canDownload() { return 'download' in $('<a></a>').get(0) } /** * @desc Save GeoJSON data to a file prompting the user with a file dialog * @public * @method * @param {string} name File name * @param {string} data JSON data to write to file */ saveGeoJson(name, data) { const href = `data:application/jsoncharset=utf-8,${encodeURIComponent(data)}` const a = $('<a class="file-dwn"><img></a>') $('body').append(a) a.attr({href: href, download: name}).find('img').trigger('click') a.remove() } /** * @desc Set data in browser's localStorage if available * @public * @method * @param {string} key Storage key * @param {string} data Data to store */ setItem(key, data) { if ('localStorage' in window) { localStorage.setItem(key, data) } } /** * @desc Get data from browser's localStorage if available * @public * @method * @param {string} key Storage key * @return {string} The value of the key in local storage */ getItem(key) { if ('localStorage' in window) { return localStorage.getItem(key) } } /** * @desc Remove data from browser's localStorage if available * @public * @method * @param {string} key Storage key */ removeItem(key) { if ('localStorage' in window) { localStorage.removeItem(key) } } /** * @desc Open a text file from filesystem * @public * @method * @param {module:nyc/LocalStorage~LocalStorage#readTextFileCallback} callback The callback function to receive file content * @param {File=} file File - if not provided the user will be prompted with a file dialog */ readTextFile(callback, file) { const reader = new FileReader() reader.onload = () => { callback(reader.result) } if (!file) { const input = $('<input class="file-in" type="file">') $('body').append(input) input.change(event => { input.remove() reader.readAsText(event.target.files[0]) }) input.trigger('click') } else { reader.readAsText(file) } } /** * @desc Open a GeoJSON file from filesystem * @public * @method * @param {ol.Map|L.Map} map The map in which the data will be displayed * @param {module:nyc/LocalStorage~LocalStorage#loadGeoJsonFileCallback} callback The callback function to receive the new layer * @param {File=} file File - if not provided the user will be prompted with a file dialog */ loadGeoJsonFile(map, callback, file) { this.readTextFile(geoJson => { const layer = this.addToMap(map, geoJson) if (callback) { callback(layer) } }, file) } /** * @desc Open a shapefile from filesystem * @public * @method * @param {ol.Map|L.Map} map The map in which the data will be displayed * @param {module:nyc/LocalStorage~LocalStorage#loadShapeFileCallback} callback The callback function to receive the new layer * @param {FileList=} files Files (.shp, .dbf, .prj) - if not provided the user will be prompted with a file dialog * @see https://github.com/mbostock/shapefile */ loadShapeFile(map, callback, files) { if (!files) { const me = this const input = $('<input class="file-in" type="file" multiple>') $('body').append(input) input.change(event => { me.getShpDbfPrj(map, event.target.files, callback) input.remove() }) input.trigger('click') } else { this.getShpDbfPrj(map, files, callback) } } /** * @private * @method * @param {ol.Map|L.Map} map The map * @param {FileList} files List of files * @param {function} callback Callback function */ getShpDbfPrj(map, files, callback) { let shp, dbf, prj Object.values(files).forEach(file => { const name = file.name const ext = name.substr(name.length - 4).toLowerCase() if (ext === '.shp') { shp = file } else if (ext === '.dbf') { dbf = file } else if (ext === '.prj') { prj = file } }) if (shp) { this.readPrj(prj, projcs => { this.readShpDbf(map, shp, dbf, projcs, callback) }) } else if (callback) { callback() } } /** * @private * @method * @param {File} prj Prj file * @param {function} callback Callback function */ readPrj(prj, callback) { if (prj) { this.readTextFile(callback, prj) } else { callback() } } /** * @private * @method * @param {ol.Map|L.Map} map The map * @param {File} shp The shp file * @param {File} dbf The dbf file * @param {string} projcs Proj coordinate reference system * @param {function} callback Callback function */ readShpDbf(map, shp, dbf, projcs, callback) { let shpBuffer, dbfBuffer const shpReader = new FileReader() shpReader.onload = event => { shpBuffer = event.target.result if (dbfBuffer || !dbf) { this.readShp(map, shpBuffer, dbfBuffer, projcs, callback) } } const dbfReader = new FileReader() dbfReader.onload = event => { dbfBuffer = event.target.result if (shpBuffer) { this.readShp(map, shpBuffer, dbfBuffer, projcs, callback) } } shpReader.readAsArrayBuffer(shp) if (dbf) { dbfReader.readAsArrayBuffer(dbf) } } /** * @private * @method * @param {ol.Map|L.Map} map The map * @param {string|ArrayBuffer} shp The shp file * @param {string|ArrayBuffer} dbf The dbf file * @param {string} projcs Proj coordinate reference system * @param {function} callback Callback function */ readShp(map, shp, dbf, projcs, callback) { const me = this const features = [] LocalStorage.shapefile.open(shp, dbf) .then(source => { source.read() .then(function collect(result) { if (result.done) { const layer = me.addToMap(map, features, projcs) if (callback) { callback(layer) } return } else { features.push(result.value) } return source.read().then(collect) }) }).catch(error => { console.error(error) }) } /** * @public * @abstract * @method * @param {ol.Map|L.Map} map The map on which to display the new layer * @param {string|Array<Object>} features The features from which to create the new layer * @param {string=} projcs The projection * @return {ol.layer.Vector|L.Layer} The new layer */ addToMap(map, features, projcs) { throw 'Not implemented' } /** * @desc Add a new projection to proj4 and return the code * @access protected * @method * @param {string} projcs The projecion as defined in a prj file * @param {Object} proj4 The proj4 instance * @return {string|undefined} The code for the new projection */ customProj(projcs, proj4) { if (projcs) { const code = nyc.nextId('shp:prj') proj4.defs(code, projcs) return code } } } /** * @desc Callback for {@link module:nyc/LocalStorage~LocalStorage#readTextFile} * @public * @callback module:nyc/LocalStorage~LocalStorage#readTextFileCallback * @param {string} fileContents The text contained in the file */ /** * @desc Callback for {@link module:nyc/LocalStorage~LocalStorage#loadGeoJsonFile} * @public * @callback module:nyc/LocalStorage~LocalStorage#loadGeoJsonFileCallback * @param {ol.layer.Vector|L.Layer} layer The layer created from the GeoJSON file */ /** * @desc Callback for {@link module:nyc/LocalStorage~LocalStorage#loadShapeFile} * @public * @callback module:nyc/LocalStorage~LocalStorage#loadShapeFileCallback * @param {ol.layer.Vector|L.Layer} layer The layer created from the shapefile */ LocalStorage.shapefile = require('shapefile') export default LocalStorage