shapefile(.shp,.dbf,.prj)导入导出
网页应用 shapefile(.shp,.dbf,.prj)的导入导出。纯前端 vue 导入导出 shapefile
文章目录
注意!!!: 仓库代码node版本使用的是 16,vue-cli4.5不支持使用 node18,vue-cli5使用的是webpack5,polyfill需要自行引用
请注意:jszip
、mapshaper
、ol
的版本,不同版本库代码可能有变。
shapefile(.shp,.dbf,.prj)导入导出
网页应用(Vue) 导入导出 shapefile(.shp,.dbf,.prj)
说明
如果网页应用只需要导入功能,只需使用 shapefile
库即可,但是 shapefile
库是无法实现导出功能的。
如果同时需导入导出功能,则需要使用 mapshaper
库。
shapefile-import
shapefile-export
1. 获取示例源码
https://gitee.com/liushuai1125/shapefile-import-export
- 获取源码
- 使用
shapefile
导入git clone https://gitee.com/liushuai1125/shapefile-import-export.git
- 使用
mapshaper
导入导出git clone -b mapshaper https://gitee.com/liushuai1125/shapefile-import-export.git
-
安装依赖
npm install # cnpm install
-
启动项目
npm run serve
预览地址: http://localhost:9566/
2. 使用 Shapefile
导入
流式 Shapefile 解析器
2.1. 文档
- npm仓库:https://www.npmjs.com/package/shapefile
- Repository:github.com/mbostock/shapefile
- Homepage:github.com/mbostock/shapefile
2.2. 说明
在网页应用中,我们使用文档里面的解析示例解析时,发现是无法读取选上传的文件的。观察 shapefile
的源码可知
export function openShp(source, options) {
if (typeof source === "string") {
if (!/\.shp$/.test(source)) source += ".shp";
source = path(source, options);
} else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
source = array(source);
} else {
source = stream(source);
}
return Promise.resolve(source).then(shp);
}
shp
和 dbf
的参数类型可以是 ArrayBuffer、Uint8Array等类型,这样我们可以使用网页里的 FileReader
的 readAsArrayBuffer
方法获取选择上传的文件数据,再传给 shapefile
进行解析。
2.3. 代码示例
<template>
<div class="shapefile-parser">
<div class="panel">
<input ref="file" type="file" multiple accept=".shp,.dbf">
<button @click="handleSelect">确认</button>
</div>
</div>
</template>
<script>
// eslint-disable-next-line no-unused-vars
import { open as shapeOpen, read as shapeRead } from 'shapefile'
export default {
name: 'ShapefileParser',
data() {
return {
}
},
methods: {
handleSelect() {
let files = this.$refs.file.files
console.log(files, typeof files, 'files FileList')
// files = Array.from(new Array(files.length), (i, idx) => files[idx]) // 等效下面写法
files = Array.from(files) // FileList => Array, 方便使用 Array 方法
console.log(files, typeof files, 'files Array')
this.parseShapefile(files) // 解析选择的 shp 并绘制显示
},
parseShapefile(files) {
const shpFile = files.find(f => f.name.endsWith('.shp'))
const dbfFile = files.find(f => f.name.endsWith('.dbf'))
// 可使用 Promise.all 优化为下面写法
// this.readInputFile(shpFile, 'ArrayBuffer').then(async(fileData) => {
// const featureJsons = []
//
// await shapeOpen(fileData).then(async(source) => {
// await source.read().then(function log(result) {
// if (result.done) {
// return
// }
// featureJsons.push(result.value)
// console.log(result.value)
// return source.read().then(log)
// })
// }).catch(error => console.error(error.stack))
//
// console.log(featureJsons, typeof featureJsons, 'featureJsons')
// }).catch(e => {
// console.log(e, typeof e, 'l e')
// })
const promises = [shpFile, dbfFile].map(i => this.readInputFile(i))
Promise.all(promises).then(([shp, dbf]) => {
// return shapeOpen(shp, dbf)
// `shapefile` 提供了 read 方法直接返回一个 FeatureCollection geojson,封装了类似于下面注释掉的代码
// 指定 dbf 编码 'utf-8'
return shapeRead(shp, dbf, { encoding:'utf-8' })
}).then(async(source) => {
// const featureJsons = []
// await source.read().then(function log(result) {
// if (result.done) {
// return
// }
// featureJsons.push(result.value)
// console.log(result.value)
// return source.read().then(log)
// })
// console.log(featureJsons, typeof featureJsons, 'shapeOpen featureJsons')
console.log(source, typeof source, 'shapeRead source')
// do something
}).catch(error => console.error(error.stack))
},
async readInputFile(file, type = 'ArrayBuffer') {
// 读取文件
return new Promise((resolve, reject) => {
const reader = new FileReader()
switch (type) {
case 'ArrayBuffer':
reader.readAsArrayBuffer(file)
break
case 'Text':
reader.readAsText(file)
break
case 'BinaryString':
reader.readAsBinaryString(file)
break
case 'DataURL':
reader.readAsDataURL(file)
break
}
reader.onload = function() {
// this.result 就是读取到的文件的数据
resolve(this.result)
}
reader.onerror = function() {
reject(this)
}
})
}
}
}
</script>
<style scoped lang="scss">
.shapefile-parser {
width: 1000px;
height: 700px;
margin: 0 auto;
display: flex;
flex-direction: column;
.panel {
padding: 10px 0;
}
}
</style>
3. 使用 mapshaper
导入导出
作为一个 WebGISer
,你如果不知道 mapshaper
,那你一定是一个不合格的 WebGISer
。
3.1. 文档
3.2. 说明
阅读 mapshaper.js
主要源码部分添加了以下一些 书签
有兴趣者可自行查看。
简而言之,在 mapshaper
里导入的数据被抽象为一个 dataset
,dataset
里存放着解析的数据,后续的操作都是在此基础上。
由 GeoJSON
文件生成的dataset
的格式如下图所示。
获取 dataset
需要用到 importContent
方法,该方法可以导入 .json
和 .shp
类型的文件。
由源码上的说明可知,参数 obj
是一个对象,具有 filename
和 content
属性,content
属性的类型可以是 Buffer
、ArrayBuffer
、String
、Object
。
// Parse content of one or more input files and return a dataset
// @obj: file data, indexed by file type
// File data objects have two properties:
// content: Buffer, ArrayBuffer, String or Object
// filename: String or null
//
function importContent(obj, opts) {
// ...
}
3.3. 导入代码示例
主要代码如下:
<script>
import mapshaper from 'mapshaper'
import { GeoJSON } from 'ol/format'
import GEOJSON from '@/assets/411600.json'
const {
importContent,
guessInputType,
exportDatasetAsGeoJSON
} = mapshaper.internal
export default {
created() {
// 直接 解析 geojson 字符串
const content = this.analyze({ json: { filename: '411600.json', content: JSON.stringify(GEOJSON) }})
console.log(content, typeof content, 'content')
},
methods: {
handleSelect() {
let files = this.$refs.file.files
console.log(files, typeof files, 'files FileList')
// files = Array.from(new Array(files.length), (i, idx) => files[idx]) // 等效下面写法
files = Array.from(files) // FileList => Array, 方便使用 Array 方法
console.log(files, typeof files, 'files Array')
// 解析 shp
const promises = files.map(i => this.readInputFileByType(i))
Promise.all(promises).then((res) => {
console.log(res, typeof res, 'readInputFileByType res')
// 将选取的多个文件作为一个数据集
return Object.fromEntries(res.map((i, idx) => {
const filename = files[idx].name
// 推断文件的解析类型
return [guessInputType(filename, i), { filename: filename, content: i }]
}))
}).then((res) => {
console.log(res, typeof res, 'merge res')
const content = this.analyze(res)
const readFeatures = new GeoJSON().readFeatures(content)
console.log(readFeatures, typeof readFeatures, 'readFeatures')
}).catch(error => console.error(error.stack))
},
analyze(obj) {
// @obj: file data, indexed by file type
// File data objects have two properties:
// content: Buffer, ArrayBuffer, String or Object
// filename: String or null
// obj: {json:{filename:'',content:''}}
const dataset = importContent(obj)
console.log(dataset, typeof dataset, 'dataset')
const content = exportDatasetAsGeoJSON(dataset, {})
console.log(content, typeof content, 'exportDatasetAsGeoJSON content')
return content
},
readInputFileByType(file) {
// 以 以下 后缀名结尾的文件 读取成文本内容
const asTest = ['.prj', '.cpg', '.json'].map(i => file.name.endsWith(i)).includes(true)
return this.readInputFile(file, asTest ? 'Text' : 'ArrayBuffer')
},
async readInputFile(file, type = 'ArrayBuffer') {
// 读取文件
return new Promise((resolve, reject) => {
const reader = new FileReader()
switch (type) {
case 'ArrayBuffer':
reader.readAsArrayBuffer(file)
break
case 'Text':
reader.readAsText(file)
break
case 'BinaryString':
reader.readAsBinaryString(file)
break
case 'DataURL':
reader.readAsDataURL(file)
break
}
reader.onload = function() {
// this.result 就是读取到的文件的数据
resolve(this.result)
}
reader.onerror = function() {
reject(this)
}
})
}
}
}
</script>
3.4. 导出 shapefile 代码示例
主要代码如下:
<script>
import mapshaper from 'mapshaper'
import GEOJSON from '@/assets/411600.json'
import JSZip from 'jszip'
import { saveAs } from 'file-saver'
const {
importContent,
setDatasetCRS,
exportFileContent
} = mapshaper.internal
export default {
methods: {
handleExport() {
const dataset = importContent({ json: { filename: '411600.json', content: JSON.stringify(GEOJSON) }})
// https://epsg.io/4326.esriwkt
setDatasetCRS(dataset, { prj: 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]' })
console.log(dataset, typeof dataset, 'dataset')
this.saveDataset(dataset)
},
saveDataset(dataset, type = 'shapefile') {
// format=shapefile|geojson|topojson|json|dbf|csv|tsv|svg
const fileContent = exportFileContent(dataset, { format: type })
console.log(fileContent, typeof fileContent, 'fileContent')
this.saveFileContent(fileContent)
},
saveFileContent(fileContent) {
var zip = new JSZip()
fileContent.forEach(i => {
zip.file(i.filename, i.content)
})
zip.generateAsync({ type: 'blob' }, function updateCallback(metadata) {
var msg = 'progression : ' + metadata.percent.toFixed(2) + ' %'
if (metadata.currentFile) {
msg += ', current file = ' + metadata.currentFile
}
console.log(msg)
}).then((blob) => {
// see FileSaver.js
saveAs(blob, 'example.zip')
console.log('done !')
}).catch(error => console.error(error.stack))
}
}
}
</script>
更多推荐
所有评论(0)