import olFormatGeoJSON from 'ol/format/GeoJSON'
import olFormatKml from 'ol/format/KML'
import shpwrite from 'shp-write' // mapbox shapefile writer
import olFeature from 'ol/Feature'
import ugh from 'ugh'
let fs
try {
fs = require('fs') // fs is used to test downloads with jest in a node env
} catch (e) {
// do nothing
}
function groupBy (list, getGroupName) { // eslint-disable-line
return list.reduce((groups, item) => {
const val = getGroupName(item)
groups[val] = groups[val] || []
groups[val].push(item)
return groups
}, {})
}
/**
* Exports the passed features as a file of the desired type.
* @category LayerPanel
* @function
* @since 0.9.0
* @param {String} [type] - The desired file type ('shp' or 'kml').
* @param {Object[]} [features] - An array of the features to be included in the generated file.
*/
export function exportFeatures (type, features, opts) {
const visibleFeatures = features.map(feature => {
const clone = feature.clone()
// this removes a ref to _ol_kit_parent to solve circularJSON bug
clone.set('_ol_kit_parent', null)
clone.setId(feature.getId())
return clone
}).filter(feature => feature.get('_ol_kit_feature_visibility'))
switch (type) {
case 'shp':
return exportShapefile({
format: new olFormatGeoJSON(),
visibleFeatures,
sourceProjection: 'EPSG:3857',
...opts
})
case 'kml':
return exportKml({
format: new olFormatKml(),
visibleFeatures,
sourceProjection: 'EPSG:3857',
targetProjection: 'EPSG:4326',
...opts
})
default:
return exportGeoJSON({
format: new olFormatGeoJSON(),
visibleFeatures,
sourceProjection: 'EPSG:3857',
...opts
})
}
}
function saveAs (blob, filename) {
try {
// create an <a> element
const a = document.createElement('a')
// create an object URL from our blob
const url = URL.createObjectURL(blob)
// set our element properties
a['data-testid'] = 'Export.download'
a.href = url
a.download = filename
a.style = 'display: none;'
// append to the document so we can click it
document.body.appendChild(a)
// download the file
a.click()
// clean-up
URL.revokeObjectURL(url)
document.body.removeChild(a)
} catch (err) {
ugh.error(`There was a problem downloading the exported file: ${err.message}`)
}
}
function flattenFeatures (features) {
return features.reduce((acc, feature) => {
const geom = feature.getGeometry()
const type = geom.getType()
switch (type) {
default:
acc.push(feature)
break
case 'MultiPoint': {
const featureProps = feature.getProperties()
geom.getPoints().forEach(point => {
acc.push(new olFeature({ ...featureProps, geometry: point }))
})
break
}
case 'MultiLineString': {
const featureProps = feature.getProperties()
geom.getLineStrings().forEach(lineString => {
acc.push(new olFeature({ ...featureProps, geometry: lineString }))
})
break
}
case 'MultiPolygon': {
const featureProps = feature.getProperties()
geom.getPolygons().forEach(polygon => {
acc.push(new olFeature({ ...featureProps, geometry: polygon }))
})
break
}
case 'GeometryCollection': {
const featureProps = feature.getProperties()
const spreadFeatures = geom.getGeometriesArray().map(g => new olFeature({ ...featureProps, geometry: g }))
const flattenedFeatures = flattenFeatures(spreadFeatures) // GeometryCollections can contain Multi geometries so we call flattenFeatures recusively
flattenedFeatures.forEach(f => {
acc.push(f)
})
break
}
}
return acc
}, [])
}
function exportShapefile ({ format, visibleFeatures, sourceProjection, targetProjection = 'EPSG:4326', filename = 'export.zip' }) {
const flattenedFeatures = flattenFeatures(visibleFeatures) // as of writing this shpwrite is bugged and can't handle multi geometries or GeometryCollections so we flatten these into their constituent parts.
const featureCollection = format.writeFeaturesObject(flattenedFeatures, {
dataProjection: targetProjection,
featureProjection: sourceProjection
})
const types = Array.from(new Set(featureCollection.features.map(feature => feature.geometry.type)))
const options = { folder: filename, types }
return shpwrite.download(featureCollection, options)
}
function exportGeoJSON ({ format, visibleFeatures, sourceProjection, targetProjection = 'EPSG:4326', filename = 'export.geojson' }) {
const featureCollection = format.writeFeaturesObject(visibleFeatures, {
dataProjection: targetProjection,
featureProjection: sourceProjection
})
const jsonString = JSON.stringify(featureCollection)
// download the file for testing in a node env
fs?.writeFileSync?.(filename, jsonString, { encoding: 'utf8' }) // eslint-disable-line
return saveAs(new Blob([jsonString], { type: 'octet/stream' }), filename)
}
function exportKml (args) {
const {
format,
visibleFeatures,
sourceProjection,
targetProjection = 'EPSG:4326',
filename = 'export.kml'
} = args
const source = format.writeFeatures(visibleFeatures, {
dataProjection: targetProjection,
featureProjection: sourceProjection
})
const blob = new Blob([source], { type: 'kml' })
// download the file for testing in a node env
// fs is used for testing the download in node
fs?.writeFileSync?.(filename, source, { encoding: 'utf8' }) // eslint-disable-line
return saveAs(blob, filename)
}