@sphinx_hq/shapefile-parser
v0.0.6
Published
纯前端 Shapefile 解析与导出工具库,无需后端服务,支持 GeoJSON 互转、坐标系自动识别、多种编码检测
Maintainers
Readme
@sphinx_hq/shapefile-parser
纯前端 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-parser快速开始
读取 Shapefile
import { 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];导出 Shapefile
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,
});API 文档
ShapefileParser
主类,提供 Shapefile 读写功能。所有方法均为 async 异步函数。
构造函数
new ShapefileParser(options?: ShapefileParserOptions)| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| encoding | string | 'utf-8' | DBF 文件默认编码 |
| defaultOutputFormat | 'shp' \| 'zip' | 'zip' | 默认输出格式 |
| defaultFilename | string | 'shapefile' | 默认文件名 |
| debug | boolean | false | 调试模式 |
read(input, options?)
异步读取 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": { ... }
}write(geojson, options?)
异步将 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 选项控制处理方式:
'auto'(默认)
自动转换非法字段名,并在控制台输出警告:
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)'strict'
遇到非法字段名直接抛出错误:
try {
const result = await parser.write(geojson, {
fieldNameStrategy: 'strict',
});
} catch (error) {
// Error: Field name "名称" contains non-ASCII characters.
// Use 'fieldNameStrategy: "auto"' to auto-convert.
}'truncate'
仅截断超长字段名,非 ASCII 字符仍会报错:
const result = await parser.write(geojson, {
fieldNameStrategy: 'truncate',
});
// "very_long_field_name" -> "very_long_"
// "名称" -> 抛出错误(非 ASCII 字符)Z/M 值保留
默认情况下,读取 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 处理
MultiPatch 是一种复杂的 3D 几何类型,包含多种部分类型(TriangleStrip、TriangleFan、Ring 等)。读取时会自动转换为 Polygon/MultiPolygon:
- 保留:OuterRing、InnerRing、FirstRing、Ring
- 忽略:TriangleStrip、TriangleFan(3D 渲染结构,无法转为多边形)
编码检测
detectDbfEncoding(buffer, cpgContent?)
检测 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';getWktFromEpsg(code)
根据 EPSG 代码获取 ESRI WKT 字符串。
const wkt = getWktFromEpsg(4490);
// GEOGCS["GCS_China_Geodetic_Coordinate_System_2000",...]identifyEpsgFromWkt(wkt)
从 WKT 字符串识别 EPSG 代码。通过数值参数比对,忽略名称差异。
const code = identifyEpsgFromWkt(wktString);
// 4490registerEpsgWkt(code, wkt, friendlyName?)
注册自定义坐标系(WKT 字符串形式)。
registerEpsgWkt(999001, customWkt, '自定义坐标系');
const wkt = getWktFromEpsg(999001);非标准 PRJ 文件处理
当读取 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 投影 |
CGCS2000(中国大地坐标系)
| 类型 | 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 |
北京54
| 类型 | EPSG 范围 | 说明 | |------|-----------|------| | 地理坐标系 | 4214 | 北京54 地理坐标系 | | 3度带 | 2401-2453 | Zone 25-45 | | 6度带 | 21413-21423 | Zone 13-23 |
西安80
| 类型 | EPSG 范围 | 说明 | |------|-----------|------| | 地理坐标系 | 4610 | 西安80 地理坐标系 | | 3度带 | 2349-2371 | Zone 25-45 | | 6度带 | 2327-2337 | Zone 13-23 |
浏览器使用
ES Module
<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>UMD
<script src="https://unpkg.com/@sphinx_hq/shapefile-parser/dist/shapefile-parser.umd.js"></script>
<script>
const parser = new ShapefileParser();
// ...
</script>Shapefile 文件说明
| 文件 | 必需性 | 说明 |
|------|--------|------|
| .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);
}
}
}License
MIT © YuanYu
本软件完全开源,可自由使用、修改和分发,包括商业用途。唯一要求是保留原始版权声明。
联系方式
- Author: YuanYu
- Email: [email protected]
