纯前端 Shapefile 解析与导出工具库,无需后端服务,支持离线使用。
- 读取 Shapefile:支持
.shp、.dbf、.prj、.cpg文件及 ZIP 压缩包 - 多文件同时读取:支持一次读取多个 Shapefile,自动按文件名分组
- 导出 Shapefile:支持导出为 ZIP 压缩包,可选浏览器直接下载
- GeoJSON 互转:读取返回标准 GeoJSON FeatureCollection,写入接受 GeoJSON 输入
- 完整形状类型支持:支持所有 Shapefile 形状类型(Point, MultiPoint, PolyLine, Polygon, PointZ/M, MultiPatch 等)
- MultiPatch 转换:自动将 MultiPatch 转换为 Polygon/MultiPolygon
- 编码自动检测:通过 LDID 和
.cpg文件自动识别 DBF 编码(UTF-8 / GBK / Big5 等) - 字段名处理策略:支持自动转换、截断、严格模式三种策略处理非法字段名
- 坐标系支持:内置 243 个常用 EPSG 坐标系,支持自定义注册
- 非标准 PRJ 修正:自动识别非标准 PRJ 内容,通过参数比对匹配正确的 EPSG 代码
- TypeScript:完整的类型定义
- 无后端依赖:无需后端服务,纯浏览器/Node.js 环境
npm install @sphinx_hq/shapefile-parserimport { ShapefileParser } from '@sphinx_hq/shapefile-parser';
const parser = new ShapefileParser();
// 读取单个 .shp 文件(仅几何,无属性)
const result = await parser.read(shpArrayBuffer);
// 返回: Record<string, GeoJSONFeatureCollection>
// 例如: { "file": FeatureCollection }
// 读取完整 Shapefile(包含属性和坐标系)
const result = await parser.read({
shp: shpArrayBuffer,
dbf: dbfArrayBuffer,
prj: prjArrayBuffer,
});
// 读取 ZIP 压缩包(可能包含多套 Shapefile)
const result = await parser.read(zipArrayBuffer);
// 返回: { "layer1": FeatureCollection, "layer2": FeatureCollection }
// 读取 File 对象(浏览器环境)
const result = await parser.read(file); // 支持 .shp 或 .zip
const result = await parser.read([shpFile, dbfFile, prjFile]);
// 获取第一个 FeatureCollection
const firstKey = Object.keys(result)[0];
const geojson = result[firstKey];import { ShapefileParser } from '@sphinx_hq/shapefile-parser';
const parser = new ShapefileParser();
// 导出为 ZIP(返回 ArrayBuffer)
const result = await parser.write(geojson, {
filename: 'my-shapefile',
});
// 直接触发浏览器下载
const result = await parser.write(geojson, {
filename: 'my-shapefile',
download: true, // 自动下载 my-shapefile.zip
});
// 指定坐标系导出
const result = await parser.write(geojson, {
filename: 'my-shapefile',
epsgCode: 4490, // CGCS2000
download: true,
});主类,提供 Shapefile 读写功能。所有方法均为 async 异步函数。
new ShapefileParser(options?: ShapefileParserOptions)| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
encoding |
string |
'utf-8' |
DBF 文件默认编码 |
defaultOutputFormat |
'shp' | 'zip' |
'zip' |
默认输出格式 |
defaultFilename |
string |
'shapefile' |
默认文件名 |
debug |
boolean |
false |
调试模式 |
异步读取 Shapefile 数据,返回 Record<string, GeoJSONFeatureCollection>。
输入类型:
| 类型 | 说明 |
|---|---|
ArrayBuffer |
.shp 或 .zip 文件 |
File |
单个文件(浏览器) |
File[] | FileList |
多文件(浏览器) |
{ shp, dbf?, prj?, cpg? } |
对象形式,各属性为 ArrayBuffer 或 File |
读取选项:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
encoding |
string |
自动检测 | DBF 编码(覆盖自动检测) |
parsePrj |
boolean |
true |
是否解析 .prj 文件 |
includeNullShapes |
boolean |
false |
是否包含空形状记录 |
preserveZM |
boolean |
false |
是否保留 Z/M 值 |
返回值:
// 返回类型:Record<string, GeoJSONFeatureCollection>
{
"文件名1": {
type: 'FeatureCollection';
features: GeoJSONFeature[];
bbox?: [number, number, number, number];
crs?: string | CrsInfo; // 如 'EPSG:4490'
},
"文件名2": { ... }
}异步将 GeoJSON 导出为 Shapefile。
写入选项:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
output |
'zip' |
'zip' |
输出格式 |
filename |
string |
'shapefile' |
文件名(不含扩展名) |
shapeType |
ShapeType |
自动推断 | 强制指定形状类型 |
encoding |
string |
'utf-8' |
DBF 编码 |
epsgCode |
number | string |
- | EPSG 代码,如 4490 或 'EPSG:4490' |
crsWkt |
string |
- | 自定义 WKT 字符串 |
generateShx |
boolean |
true |
是否生成 .shx 索引文件 |
download |
boolean |
false |
触发浏览器下载 |
fieldNameStrategy |
FieldNameStrategy |
'auto' |
字段名处理策略 |
返回值:
interface WriteResult {
format: 'zip';
zipBuffer: ArrayBuffer;
info: {
featureCount: number;
shapeType: ShapeType;
bbox: [number, number, number, number];
fieldMappings: FieldMapping[]; // 字段名映射信息
};
}
interface FieldMapping {
original: string; // 原始字段名
processed: string; // 处理后的字段名
reason?: 'non_ascii' | 'too_long' | 'invalid_start' | 'invalid_char' | 'duplicate';
}dBASE 格式对字段名有严格限制:
- 最多 10 个 ASCII 字符
- 只允许字母 (A-Z, a-z)、数字 (0-9)、下划线 (_)
- 必须以字母或下划线开头
通过 fieldNameStrategy 选项控制处理方式:
自动转换非法字段名,并在控制台输出警告:
const result = await parser.write(geojson, {
fieldNameStrategy: 'auto',
});
// 控制台输出:
// [shapefile-parser] Field names were modified to comply with dBASE format:
// "名称" -> "FLD001" (contains non-ASCII characters)
// "very_long_field_name" -> "very_long_" (exceeds 10 characters)遇到非法字段名直接抛出错误:
try {
const result = await parser.write(geojson, {
fieldNameStrategy: 'strict',
});
} catch (error) {
// Error: Field name "名称" contains non-ASCII characters.
// Use 'fieldNameStrategy: "auto"' to auto-convert.
}仅截断超长字段名,非 ASCII 字符仍会报错:
const result = await parser.write(geojson, {
fieldNameStrategy: 'truncate',
});
// "very_long_field_name" -> "very_long_"
// "名称" -> 抛出错误(非 ASCII 字符)默认情况下,读取 Shapefile 时会丢弃 Z/M 值,只返回二维坐标 [x, y]。如果需要保留原始的 Z/M 值,可以使用 preserveZM 选项:
// 读取时保留 Z/M 值
const result = await parser.read(zipBuffer, { preserveZM: true });
// 对于 Z 类型(如 PointZ),坐标为 [x, y, z, m]
const coords = result['file'].features[0].geometry.coordinates;
// [116.4, 39.9, 100, 0] - [经度, 纬度, Z值, M值]注意:
- Z 类型(PointZ、PolyLineZ、PolygonZ 等)同时包含 Z 和 M 值
- M 类型(PointM、PolyLineM、PolygonM 等)只包含 M 值
- 未定义的 M 值默认为 0
当使用 fieldNameStrategy: 'auto' 写入时,如果字段名被修改,可以通过 WriteResult.info.fieldMappings 获取映射信息:
const result = await parser.write(geojson, {
fieldNameStrategy: 'auto',
});
// 检查字段名映射
console.log(result.info.fieldMappings);
// [
// { original: "名称", processed: "FLD001", reason: "non_ascii" },
// { original: "very_long_field_name", processed: "very_long_", reason: "too_long" },
// { original: "name", processed: "name" } // 未修改,无 reason
// ]
// 使用映射在应用层进行数据关联
for (const mapping of result.info.fieldMappings) {
if (mapping.reason) {
console.warn(`字段 "${mapping.original}" 被转换为 "${mapping.processed}"`);
}
}| 类型 | 值 | 读取 | 写入 | 说明 |
|---|---|---|---|---|
| Null | 0 | ✅ | ✅ | 空形状 |
| Point | 1 | ✅ | ✅ | 二维点 |
| PolyLine | 3 | ✅ | ✅ | 多段线 |
| Polygon | 5 | ✅ | ✅ | 多边形 |
| MultiPoint | 8 | ✅ | ✅ | 多点 |
| PointZ | 11 | ✅ | ✅ | 带 Z 值的点 |
| PolyLineZ | 13 | ✅ | ✅ | 带 Z 值的多段线 |
| PolygonZ | 15 | ✅ | ✅ | 带 Z 值的多边形 |
| MultiPointZ | 18 | ✅ | ✅ | 带 Z 值的多点 |
| PointM | 21 | ✅ | ✅ | 带 M 值的点 |
| PolyLineM | 23 | ✅ | ✅ | 带 M 值的多段线 |
| PolygonM | 25 | ✅ | ✅ | 带 M 值的多边形 |
| MultiPointM | 28 | ✅ | ✅ | 带 M 值的多点 |
| MultiPatch | 31 | ✅ | - | 自动转换为 Polygon/MultiPolygon |
MultiPatch 是一种复杂的 3D 几何类型,包含多种部分类型(TriangleStrip、TriangleFan、Ring 等)。读取时会自动转换为 Polygon/MultiPolygon:
- 保留:OuterRing、InnerRing、FirstRing、Ring
- 忽略:TriangleStrip、TriangleFan(3D 渲染结构,无法转为多边形)
检测 DBF 文件编码。
import { detectDbfEncoding } from '@sphinx_hq/shapefile-parser';
// 通过 LDID 自动检测
const encoding = detectDbfEncoding(dbfBuffer);
// 使用 .cpg 文件内容
const encoding = detectDbfEncoding(dbfBuffer, 'GBK');检测优先级:用户指定 > .cpg 文件 > LDID(偏移29) > 默认 UTF-8
支持的编码:
| LDID | 编码 | 说明 |
|---|---|---|
| 0x4D | gbk | 简体中文 GBK(ArcGIS 常用) |
| 0x78 | gbk | 简体中文 GBK |
| 0x79 | gb2312 | 简体中文 GB2312 |
| 0x7A | big5 | 繁体中文 Big5 |
| 0x7B | gb18030 | 简体中文 GB18030 |
| 0xC8 | utf-8 | dBASE Level 7 |
| 0x00 | windows-1252 | ANSI |
import {
// 查询
getWktFromEpsg,
identifyEpsgFromWkt,
getAllEpsgCodes,
hasEpsgCode,
getEpsgEntry,
// 解析与构建
parseWkt,
buildWkt,
parseCrsInfo,
resolveWktFromCrs,
// 自定义注册
registerEpsgParams,
registerEpsgWkt,
} from '@sphinx_hq/shapefile-parser';根据 EPSG 代码获取 ESRI WKT 字符串。
const wkt = getWktFromEpsg(4490);
// GEOGCS["GCS_China_Geodetic_Coordinate_System_2000",...]从 WKT 字符串识别 EPSG 代码。通过数值参数比对,忽略名称差异。
const code = identifyEpsgFromWkt(wktString);
// 4490注册自定义坐标系(WKT 字符串形式)。
registerEpsgWkt(999001, customWkt, '自定义坐标系');
const wkt = getWktFromEpsg(999001);当读取 Shapefile 时,如果 .prj 文件包含非标准的坐标系名称,会通过数值参数比对自动识别正确的 EPSG 代码:
// 原始 PRJ 内容(非标准名称)
const nonStandardWkt = `GEOGCS["My_Custom_Name",DATUM["D_China_2000",SPHEROID["CGCS2000",6378137.0,298.257222101]],...]`;
// 自动识别为 EPSG:4490(CGCS2000)
const result = await parser.read({ shp, prj });
console.log(result[Object.keys(result)[0]].crs); // 'EPSG:4490'比对原理:只比较数值参数(椭球体长半轴、扁率倒数、中央经线、偏移量等),忽略字符串名称差异。
共 243 个 EPSG 代码,覆盖中国常用及全球常用坐标系。
| EPSG | 名称 | 说明 |
|---|---|---|
| 4326 | WGS84 | GPS 标准坐标系 |
| 3857 | Web Mercator | Web 地图常用 |
| 32601-32660 | UTM N | 北半球 UTM 投影 |
| 32701-32760 | UTM S | 南半球 UTM 投影 |
| 类型 | EPSG 范围 | 说明 |
|---|---|---|
| 地理坐标系 | 4490 | CGCS2000 地理坐标系 |
| 3度带(带号) | 4513-4533 | Zone 25-45,中央经线 75°E-135°E |
| 3度带(CM) | 4534-4554 | 中央经线 75°E-135°E,无带号偏移 |
| 6度带 | 4491-4501 | Zone 13-23,中央经线 75°E-135°E |
| 类型 | EPSG 范围 | 说明 |
|---|---|---|
| 地理坐标系 | 4214 | 北京54 地理坐标系 |
| 3度带 | 2401-2453 | Zone 25-45 |
| 6度带 | 21413-21423 | Zone 13-23 |
| 类型 | EPSG 范围 | 说明 |
|---|---|---|
| 地理坐标系 | 4610 | 西安80 地理坐标系 |
| 3度带 | 2349-2371 | Zone 25-45 |
| 6度带 | 2327-2337 | Zone 13-23 |
<script type="module">
import { ShapefileParser } from 'https://unpkg.com/@sphinx_hq/shapefile-parser/dist/shapefile-parser.esm.js';
const parser = new ShapefileParser();
document.getElementById('fileInput').addEventListener('change', async (e) => {
const files = e.target.files;
const result = await parser.read(files);
// 获取第一个图层
const firstKey = Object.keys(result)[0];
const geojson = result[firstKey];
console.log(geojson);
// 修改后导出下载
const writeResult = await parser.write(geojson, {
filename: 'modified',
download: true,
});
});
</script><script src="https://unpkg.com/@sphinx_hq/shapefile-parser/dist/shapefile-parser.umd.js"></script>
<script>
const parser = new ShapefileParser();
// ...
</script>| 文件 | 必需性 | 说明 |
|---|---|---|
.shp |
必需 | 几何数据 |
.dbf |
可选 | 属性数据 |
.shx |
可选 | 索引文件(读取时不需要,写入时可选生成) |
.prj |
可选 | 坐标系信息 |
.cpg |
可选 | DBF 编码声明 |
最小文件组合:仅需 .shp 文件即可读取(属性将为空对象)。
import { ShapefileError, ErrorCode } from '@sphinx_hq/shapefile-parser';
try {
const result = await parser.read(input);
} catch (error) {
if (error instanceof ShapefileError) {
switch (error.code) {
case ErrorCode.INVALID_FIELD_NAME:
console.error('字段名不合法:', error.message);
break;
case ErrorCode.MISSING_SHP:
console.error('缺少 .shp 文件');
break;
case ErrorCode.DBF_PARSE_ERROR:
console.error('DBF 解析失败,可能需要指定编码');
break;
default:
console.error(error.message);
}
}
}MIT © YuanYu
本软件完全开源,可自由使用、修改和分发,包括商业用途。唯一要求是保留原始版权声明。
- Author: YuanYu
- Email: yumen2009@vip.qq.com