/**
* @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