import JSZip from 'jszip'
import olSourceVector from 'ol/source/Vector'
import olCollection from 'ol/Collection'
import olLayerVector from 'ol/layer/Vector'
import olFeature from 'ol/Feature'
import olGeomPoint from 'ol/geom/Point'
import olFormatFeature from 'ol/format/Feature'
import olFormatWKT from 'ol/format/WKT'
import olFormatGeoJSON from 'ol/format/GeoJSON'
import olFormatKML from 'ol/format/KML'
import Papa from 'papaparse'
import shp from 'shpjs'
export function arrRegexIndexOf (arr, re) {
for (const i in arr) {
if (arr[i].match(re)) {
return Number(i)
}
}
return -1
}
/**
* Converts the given file into a layer
* @function convertFileToLayer
* @category LayerPanel
* @param {Blob} [file] - the file to be converted. Accepts, 'kmz', 'kml', 'geojson', 'wkt', 'csv', 'zip', and 'json' file types.
* @param {olMap} [map] - the openlayers map
*/
export function convertFileToLayer (file, map) {
return new Promise((resolve, reject) => {
read(file)
.then(getFormatForFileType)
.then((res) => {
resolve({
name: file.name,
layer: convertFormatToLayer(res, map, file.name)
})
})
.catch(reject)
})
}
/**
* Converts the given file into an array of features
* @function convertFileToFeatures
* @category LayerPanel
* @since 7.0.0
* @param {Blob} [file] - the file to be converted. Accepts, 'kmz', 'kml', 'geojson', 'wkt', 'csv', 'zip', and 'json' file types.
* @param {olMap} [map] - the openlayers map
*/
export function convertFileToFeatures (file, map) {
return new Promise((resolve, reject) => {
read(file)
.then(getFormatForFileType)
.then((res) => {
resolve({
name: file.name,
features: convertFormatToFeatures(res, map)
})
})
.catch(reject)
})
}
const acceptedExtensions = [
'kmz',
'kml',
'geojson',
'wkt',
'csv',
'zip',
'json'
]
export function validFile (file, fileTypes) {
return file && fileTypes.find((type) => file.name.endsWith(type.extension.toLowerCase()))
}
export function createFeaturesVectorSource (features = []) {
const vectorSource = new olSourceVector({
wrapX: true
})
vectorSource.addFeatures(features)
return vectorSource
}
export function stackedJsonStringToJsonArray (stackedJsonString) {
const splitString = stackedJsonString.split('}{')
return splitString.map((s) => {
let jsonString = s
if (!(s[0] === '{')) jsonString = `{${jsonString}`
if (!(s[s.length - 1] === '}')) jsonString = `${jsonString}}`
return JSON.parse(jsonString)
})
}
export function jsonArrayToFeatureArray (jsonArray, format) {
// Convert Array<JSON> into Array<Array<Features>>
const deepFeaturesArray = jsonArray.map((json) => {
return format.readFeatures(json)
})
// Return flattened Array<Features> results using reduce
return deepFeaturesArray.reduce((accumulator, value) => {
return accumulator.concat(value)
}, [])
}
export function getFeaturesFromFormat (format, results) {
if (!format || !results) return []
// The conversion service returns stacked feature collections (e.g. '{FeatureCollection}{FeatureCollection}{FeatureCollection}') when there are multiple shapefiles within the payload zip archive
if (typeof results === 'string' && results.includes('}{')) {
const jsonArray = stackedJsonStringToJsonArray(results)
return jsonArrayToFeatureArray(jsonArray, format)
} else {
return format.readFeatures(results)
}
}
export function transformFeature (opts) {
const getCode = args => {
const {
code,
projection,
map
} = args
if (code) {
return code
} else if (projection) {
return projection.getCode()
} else {
try {
return map.getView().getProjection().getCode()
} catch (e) {
throw new Error('Arguments are invalid.')
}
}
}
const proj = getCode(opts)
return new olFeature({ ...opts.feature.getProperties(), geometry: opts.feature.clone().getGeometry().transform('EPSG:4326', proj) })
}
export function convertFormatToLayer({ format, results }, map, fileName) {
const features = convertFormatToFeatures({ format, results }, map, fileName)
const buildLayer = (feats) => {
return new olLayerVector({
source: createFeaturesVectorSource(feats),
renderMode: 'image',
name: fileName
})
}
return Array.isArray(features) ? features.map(buildLayer) : [buildLayer(features)]
}
export function convertFormatToFeatures({ format, results }, map) {
if (!format || !results) throw new Error('File failed to properly')
const getFeatures = (res) => {
if (res instanceof olCollection) return res.getArray()
return getFeaturesFromFormat(format, res)
}
const nonGeomFeatures = f => f.getGeometry()
const convert = (res) => {
return {
featureArray: (getFeatures(res)
.filter(nonGeomFeatures)
.map(feature => transformFeature({ feature, map }))),
fileName: res.fileName
}
}
return Array.isArray(results) ? results.map(convert) : [convert(results)]
}
export function getFileType (file) {
return acceptedExtensions.find((type) => {
return file.name.endsWith(type)
})
}
export function getOlFormat (extension) {
switch (extension.toLowerCase()) {
case 'kml':
return new olFormatKML()
case 'geojson':
return new olFormatGeoJSON()
case 'json':
return new olFormatGeoJSON()
case 'wkt':
return new olFormatWKT()
default:
return new olFormatGeoJSON()
}
}
export function read (file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (val) => {
resolve({
file,
reader,
results: reader.result
})
}
reader.onerror = (err) => reject(err)
reader.readAsText(file)
})
}
export function getFormatForFileType ({ file, reader, results }) {
const extension = getFileType(file).toLowerCase()
// If the type doesn't match the whitelist, bail
if (!extension) throw new Error('File type is not supported')
// for formats with no OL format, special flows are used
if (extension === 'kmz') {
return processKMZ(file)
} else if (extension === 'zip') {
return processShapefile(file)
} else if (extension === 'csv') {
return processCSV(reader.result)
} else {
return Promise.resolve({
results: results,
format: getOlFormat(extension)
})
}
}
export function processKMZ (file) {
return JSZip.loadAsync(file).then(zip => {
const match = zip.filter((relativePath, file) => {
return relativePath.endsWith('.kml')
})[0]
return match.async('string')
}).then(results => ({ format: new olFormatKML(), results }))
}
export function processShapefile (file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(file)
fileReader.onloadend = function (e) {
shp(e.target.result)
.then(geojson => {
resolve({
results: geojson,
format: new olFormatGeoJSON()
})
})
.catch(reject)
}
})
}
export function processCSV (result) {
return new Promise((resolve, reject) => {
Papa.parse(result, {
complete: (res) => {
resolve(convertCsvArrayToCollection(res.data))
}
})
})
}
export function convertCsvArrayToCollection (csvArr) {
if (!csvArr || !(csvArr instanceof Array) || !csvArr.length) return false
const columns = csvArr.shift()
const longIndex = arrRegexIndexOf(columns, /^long(itude)?$/i)
const latIndex = arrRegexIndexOf(columns, /^lat(itude)?$/i)
if (latIndex < 0 || longIndex < 0) return false
const features = csvArr.map((row) => {
if (!Number(row[longIndex]) || !Number(row[latIndex])) return false
const feature = new olFeature({
geometry: new olGeomPoint([Number(row[longIndex]), Number(row[latIndex])])
})
const properties = {}
columns.map((column, index) => {
properties[column] = row[index] || ''
})
feature.setProperties(properties)
return feature
}).filter((t) => t)
return {
results: new olCollection(features),
format: olFormatFeature
}
}