概述
最近在用node做一个shp文件的上传入库的功能,使用的是shapefile库,但是在入库的时候由于dbf文件字符集的原因导致数据的入库失败或者不能正确的显示。于是几经搜索资料,找到了一个可行的方法,跟大家分享一下。
shapefile简介
Shapefile-js提供了编写简单的JavaScript程序以读取ESRI Shapefile 以及关联的属性文件的功能,它可以在网页端使用,也可在Node.js环境下使用,node安装后也可在终端使用。
shell
npm i shapefile
Node中使用:
js
var shapefile = require("shapefile");
shapefile.open("example.shp")
.then(source => source.read()
.then(function log(result) {
if (result.done) return;
console.log(result.value);
return source.read().then(log);
}))
.catch(error => console.error(error.stack));
web中使用:
html
<!DOCTYPE html>
<script src="https://unpkg.com/shapefile@0.6"></script>
<script>
shapefile.open("https://cdn.rawgit.com/mbostock/shapefile/master/test/points.shp")
.then(source => source.read()
.then(function log(result) {
if (result.done) return;
console.log(result.value);
return source.read().then(log);
}))
.catch(error => console.error(error.stack));
</script>
终端中可以快速将shp转换为json文件。
shell
shp2json example.shp
dbf文件
dbf文件由头记录及数据记录组成。头记录定义该表的结构并包含与表相关的其他信息。

BDF文件头的详细格式如下表:

如上表,dbf文件的语言驱动ID在第29个字节。下表为各字符集对应的编码。
| ID | Codepage | Description |
|---|---|---|
| 1 | 0x01 | 437 |
| 2 | 0x02 | 850 |
| 3 | 0x03 | 1252 |
| 4 | 0x04 | 10000 |
| 8 | 0x08 | 865 |
| 9 | 0x09 | 437 |
| 10 | 0x0A | 850 |
| 11 | 0x0B | 437 |
| 13 | 0x0D | 437 |
| 14 | 0x0E | 850 |
| 15 | 0x0F | 437 |
| 16 | 0x10 | 850 |
| 17 | 0x11 | 437 |
| 18 | 0x12 | 850 |
| 19 | 0x13 | 932 |
| 20 | 0x14 | 850 |
| 21 | 0x15 | 437 |
| 22 | 0x16 | 850 |
| 23 | 0x17 | 865 |
| 24 | 0x18 | 437 |
| 25 | 0x19 | 437 |
| 26 | 0x1A | 850 |
| 27 | 0x1B | 437 |
| 28 | 0x1C | 863 |
| 29 | 0x1D | 850 |
| 31 | 0x1F | 852 |
| 34 | 0x22 | 852 |
| 35 | 0x23 | 852 |
| 36 | 0x24 | 860 |
| 37 | 0x25 | 850 |
| 38 | 0x26 | 866 |
| 55 | 0x37 | 850 |
| 64 | 0x40 | 852 |
| 77 | 0x4D | 936 |
| 78 | 0x4E | 949 |
| 79 | 0x4F | 950 |
| 80 | 0x50 | 874 |
| 87 | 0x57 | Current ANSI CP |
| 88 | 0x58 | 1252 |
| 89 | 0x59 | 1252 |
| 100 | 0x64 | 852 |
| 101 | 0x65 | 866 |
| 102 | 0x66 | 865 |
| 103 | 0x67 | 861 |
| 104 | 0x68 | 895 |
| 105 | 0x69 | 620 |
| 106 | 0x6A | 737 |
| 107 | 0x6B | 857 |
| 108 | 0x6C | 863 |
| 120 | 0x78 | 950 |
| 121 | 0x79 | 949 |
| 122 | 0x7A | 936 |
| 123 | 0x7B | 932 |
| 124 | 0x7C | 874 |
| 134 | 0x86 | 737 |
| 135 | 0x87 | 852 |
| 136 | 0x88 | 857 |
| 150 | 0x96 | 10007 |
| 151 | 0x97 | 10029 |
| 152 | 0x98 | 10006 |
| 200 | 0xC8 | 1250 |
| 201 | 0xC9 | 1251 |
| 202 | 0xCA | 1254 |
| 203 | 0xCB | 1253 |
| 204 | 0xCC | 1257 |
通过上表,我们可得知中文对应的Id为77,因此代码实现的时候我们可以这么做判断:
js
async parseFields(file) {
try {
// 检查DBF文件是否存在
const dbfExists = await existsAsync(file.dbfFilePath);
if (!dbfExists) {
logger.error(`DBF文件不存在: ${file.dbfFilePath}`);
return [];
}
console.log('start open Dbf file...', file.dbfFilePath);
// 读取DBF文件的前几个字节来检测编码
const buffer = await readFileAsync(file.dbfFilePath);
// DBF文件的语言驱动ID在第29个字节(0-based索引为28)
// http://shapelib.maptools.org/codepage.html?d=1563413688103
const languageDriverId = buffer[29];
let charset = 'UTF-8';
if (languageDriverId === 77) {
charset = 'GBK';
}
logger.info(`DBF文件字符集检测结果: ${charset} (languageDriverId: ${languageDriverId})`);
// 使用检测到的字符集打开DBF文件
let source = null;
try {
source = await shapefile.openDbf(file.dbfFilePath, {
encoding: charset
});
} catch (error) {
// 如果使用检测到的字符集打开失败,尝试使用GBK(中文环境常用)
logger.error(`使用${charset}打开DBF文件失败:`, error);
charset = 'GBK';
source = await shapefile.openDbf(file.dbfFilePath, {
encoding: charset
});
}
// 使用shapefile库读取DBF文件
const fields = [];
// 获取字段信息
if (source._fields) {
for (const field of source._fields) {
const fieldTypeDixt = {
C: 'String',
N: 'Number',
D: 'Date',
F: 'Number',
}
fields.push({
name: field.name,
type: fieldTypeDixt[field.type] || 'String',
size: field.length,
decimal: field.decimal
});
}
}
return {
fields: fields,
charset: charset
};
} catch (error) {
logger.error('解析字段信息失败:', error);
return [];
}
}
}