giser-geometry-parse
v1.0.0
Published
Zero-dependency WKT parser/builder and WKT↔GeoJSON converter
Maintainers
Readme
giser-geometry-parse
一个零依赖、轻量级的 TypeScript/JavaScript 库,用于:
- WKT 解析:将 WKT 字符串解析为 GeoJSON Geometry 对象
- WKT 构建:将 GeoJSON Geometry 对象序列化为 WKT 字符串
- WKT ↔ GeoJSON 互转:在 WKT 与 GeoJSON Geometry / Feature / FeatureCollection 之间自由转换
- GeoJSON 工厂方法:快速创建各种 GeoJSON 几何对象
目录
支持的几何类型
| WKT 类型 | GeoJSON 类型 | 说明 |
|---------|-------------|------|
| POINT | Point | 点 |
| LINESTRING | LineString | 线 |
| POLYGON | Polygon | 多边形(支持空洞) |
| MULTIPOINT | MultiPoint | 点集合 |
| MULTILINESTRING | MultiLineString | 线集合 |
| MULTIPOLYGON | MultiPolygon | 多边形集合 |
| GEOMETRYCOLLECTION | GeometryCollection | 几何集合 |
支持以下 WKT 扩展语法:
- Z 坐标:
POINT (x y z)/POINT Z (x y z) - 维度修饰符:
Z、M、ZM(M值会被忽略,仅保留 XYZ) - EMPTY:
LINESTRING EMPTY、POLYGON EMPTY等(POINT EMPTY会抛出错误,见注意事项)
安装与构建
# 安装依赖
npm install
# 构建(生成 dist/ 目录)
npm run build
# 仅做 TypeScript 类型检查(不生成文件)
npm run typecheck构建产物:
| 文件 | 格式 | 用途 |
|------|------|------|
| dist/index.esm.js | ES Module | 浏览器 / 现代打包工具 |
| dist/index.cjs.js | CommonJS | Node.js |
| dist/index.umd.js | UMD | <script> 标签直接引入 |
快速上手
Node.js
npm install giser-geometry-parse// CommonJS
const { parse, build, wktToFeature } = require('giser-geometry-parse');
// ES Module - 按需导入
import { parse, build, wktToFeature } from 'giser-geometry-parse';
// ES Module - 命名空间导入(避免命名冲突)
import WKT from 'giser-geometry-parse';
const geom = WKT.parse('POINT (116.39 39.91)');浏览器 (script 标签)
<!-- UMD 方式:通过 script 标签直接引入,全局变量 WKTGeoJSON -->
<script src="https://unpkg.com/giser-geometry-parse/dist/index.umd.js"></script>
<script>
// 方式一:使用命名空间(推荐,避免命名冲突)
const geom = WKTGeoJSON.parse('POINT (116.39 39.91)');
console.log(geom);
// 方式二:解构赋值(需注意命名冲突)
const { parse, build } = WKTGeoJSON;
const wkt = build(geom);
</script>浏览器 (ES Module)
<script type="module">
import { parse, build } from 'giser-geometry-parse';
const geom = parse('POINT (116.39 39.91)');
console.log(geom);
// → { type: 'Point', coordinates: [116.39, 39.91] }
const wkt = build(geom);
console.log(wkt);
// → "POINT (116.39 39.91)"
</script>API 文档
1. parse(wkt)
将 WKT 字符串解析为 GeoJSON Geometry 对象。
参数:
wktstring— WKT 格式的字符串
返回: Geometry
抛出: 输入格式错误、未知几何类型、坐标值无效时抛出 Error
// ── POINT ─────────────────────────────────────────────────────────────
parse('POINT (116.39 39.91)')
// → { type: 'Point', coordinates: [116.39, 39.91] }
// 带 Z 坐标
parse('POINT (116.39 39.91 50)')
// → { type: 'Point', coordinates: [116.39, 39.91, 50] }
// 带维度修饰符(Z 关键字)
parse('POINT Z (116.39 39.91 50)')
// → { type: 'Point', coordinates: [116.39, 39.91, 50] }
// ── LINESTRING ────────────────────────────────────────────────────────
parse('LINESTRING (0 0, 1 1, 2 0)')
// → { type: 'LineString', coordinates: [[0,0],[1,1],[2,0]] }
// ── POLYGON ───────────────────────────────────────────────────────────
// 无空洞
parse('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))')
// → { type: 'Polygon', coordinates: [[[0,0],[10,0],[10,10],[0,10],[0,0]]] }
// 带空洞(外环 + 内环)
parse('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 4 2, 4 4, 2 4, 2 2))')
// → { type: 'Polygon', coordinates: [
// [[0,0],[10,0],[10,10],[0,10],[0,0]], ← 外环
// [[2,2],[4,2],[4,4],[2,4],[2,2]] ← 内环(空洞)
// ]}
// ── MULTIPOINT ────────────────────────────────────────────────────────
// 标准写法(每个点用括号包裹)
parse('MULTIPOINT ((0 0), (1 1), (2 2))')
// → { type: 'MultiPoint', coordinates: [[0,0],[1,1],[2,2]] }
// 非标准写法(兼容)
parse('MULTIPOINT (0 0, 1 1, 2 2)')
// → { type: 'MultiPoint', coordinates: [[0,0],[1,1],[2,2]] }
// ── MULTILINESTRING ───────────────────────────────────────────────────
parse('MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))')
// → { type: 'MultiLineString', coordinates: [[[0,0],[1,1]], [[2,2],[3,3]]] }
// ── MULTIPOLYGON ──────────────────────────────────────────────────────
parse('MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 2, 3 2, 3 3, 2 3, 2 2)))')
// → { type: 'MultiPolygon', coordinates: [
// [[[0,0],[1,0],[1,1],[0,1],[0,0]]],
// [[[2,2],[3,2],[3,3],[2,3],[2,2]]]
// ]}
// ── GEOMETRYCOLLECTION ────────────────────────────────────────────────
parse('GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 0, 1 1))')
// → { type: 'GeometryCollection', geometries: [
// { type: 'Point', coordinates: [0,0] },
// { type: 'LineString', coordinates: [[0,0],[1,1]] }
// ]}
// ── EMPTY ─────────────────────────────────────────────────────────────
parse('LINESTRING EMPTY')
// → { type: 'LineString', coordinates: [] }
parse('POLYGON EMPTY')
// → { type: 'Polygon', coordinates: [] }2. build(geometry)
将 GeoJSON Geometry 对象转换为 WKT 字符串。
参数:
geometryGeometry— GeoJSON Geometry 对象
返回: string
build({ type: 'Point', coordinates: [116.39, 39.91] })
// → 'POINT (116.39 39.91)'
build({ type: 'Point', coordinates: [116.39, 39.91, 50] })
// → 'POINT Z (116.39 39.91 50)'
build({ type: 'LineString', coordinates: [[0,0],[1,1],[2,0]] })
// → 'LINESTRING (0 0, 1 1, 2 0)'
build({ type: 'Polygon', coordinates: [[[0,0],[10,0],[10,10],[0,10],[0,0]]] })
// → 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))'
// 带空洞的多边形
build({
type: 'Polygon',
coordinates: [
[[0,0],[10,0],[10,10],[0,10],[0,0]],
[[2,2],[4,2],[4,4],[2,4],[2,2]]
]
})
// → 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 4 2, 4 4, 2 4, 2 2))'
// MULTIPOINT 输出符合 OGC 标准(每个点带括号)
build({ type: 'MultiPoint', coordinates: [[0,0],[1,1],[2,2]] })
// → 'MULTIPOINT ((0 0), (1 1), (2 2))'
// 空几何
build({ type: 'LineString', coordinates: [] })
// → 'LINESTRING EMPTY'
build({ type: 'GeometryCollection', geometries: [] })
// → 'GEOMETRYCOLLECTION EMPTY'3. wktToGeoJSON(wkt)
将 WKT 字符串转换为 GeoJSON Geometry 对象(parse 的语义化别名)。
import { wktToGeoJSON } from 'giser-geometry-parse';
const geom = wktToGeoJSON('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
// → { type: 'Polygon', coordinates: [[[0,0],[1,0],[1,1],[0,1],[0,0]]] }4. wktToFeature(wkt, properties?, id?)
将 WKT 字符串转换为 GeoJSON Feature 对象。
参数:
wktstring— WKT 字符串propertiesRecord<string, unknown> | null(可选)— Feature 属性,默认nullidstring | number(可选)— Feature ID
返回: Feature
import { wktToFeature } from 'giser-geometry-parse';
// 带属性
wktToFeature('POINT (116.39 39.91)', { name: '北京', pop: 21540000 })
// → {
// type: 'Feature',
// geometry: { type: 'Point', coordinates: [116.39, 39.91] },
// properties: { name: '北京', pop: 21540000 }
// }
// 带 ID
wktToFeature('LINESTRING (0 0, 1 1)', null, 42)
// → { type: 'Feature', geometry: {...}, properties: null, id: 42 }
// 无属性
wktToFeature('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))')
// → { type: 'Feature', geometry: {...}, properties: null }5. wktToFeatureCollection(wkts, properties?)
将多个 WKT 字符串批量转换为 GeoJSON FeatureCollection。
参数:
wktsstring[]— WKT 字符串数组propertiesArray<Record<string, unknown> | null>(可选)— 每个 Feature 的属性数组
返回: FeatureCollection
import { wktToFeatureCollection } from 'giser-geometry-parse';
const fc = wktToFeatureCollection(
['POINT (116.39 39.91)', 'POINT (121.47 31.23)', 'POINT (113.26 23.13)'],
[{ name: '北京' }, { name: '上海' }, { name: '广州' }]
);
// → {
// type: 'FeatureCollection',
// features: [
// { type: 'Feature', geometry: { type: 'Point', ... }, properties: { name: '北京' } },
// { type: 'Feature', geometry: { type: 'Point', ... }, properties: { name: '上海' } },
// { type: 'Feature', geometry: { type: 'Point', ... }, properties: { name: '广州' } }
// ]
// }
// 不传属性
const fc2 = wktToFeatureCollection(['POINT (0 0)', 'LINESTRING (0 0, 1 1)']);6. geojsonToWkt(geometry)
将 GeoJSON Geometry 对象转换为 WKT 字符串(build 的语义化别名)。
import { geojsonToWkt } from 'giser-geometry-parse';
geojsonToWkt({ type: 'Point', coordinates: [116.39, 39.91] })
// → 'POINT (116.39 39.91)'7. featureToWkt(feature)
将 GeoJSON Feature 转换为 WKT 字符串(取 geometry 部分)。
抛出: 若 Feature.geometry 为 null,则抛出 Error
import { featureToWkt } from 'giser-geometry-parse';
featureToWkt({
type: 'Feature',
geometry: { type: 'Point', coordinates: [116.39, 39.91] },
properties: { name: '北京' }
})
// → 'POINT (116.39 39.91)'8. featureCollectionToWkt(fc)
将 GeoJSON FeatureCollection 中所有 Feature 转换为 WKT 字符串数组。geometry 为 null 的 Feature 对应位置返回 null。
返回: Array<string | null>
import { featureCollectionToWkt } from 'giser-geometry-parse';
featureCollectionToWkt({
type: 'FeatureCollection',
features: [
{ type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: null },
{ type: 'Feature', geometry: { type: 'LineString', coordinates: [[0,0],[1,1]] }, properties: null },
{ type: 'Feature', geometry: null, properties: null } // ← null geometry
]
})
// → ['POINT (0 0)', 'LINESTRING (0 0, 1 1)', null]9. GeoJSON 工厂方法
快速创建 GeoJSON Geometry 对象。所有工厂方法均支持简化输入(自动包装)和完整输入两种形式。
createPoint(x, y, z?)
createPoint(116.39, 39.91)
// → { type: 'Point', coordinates: [116.39, 39.91] }
createPoint(116.39, 39.91, 50)
// → { type: 'Point', coordinates: [116.39, 39.91, 50] }createLineString(coordinates)
createLineString([[0,0],[1,1],[2,0]])
// → { type: 'LineString', coordinates: [[0,0],[1,1],[2,0]] }createPolygon(coordinates)
支持两种输入:
// ① 传入单个外环 Position[](自动包装)
createPolygon([[0,0],[1,0],[1,1],[0,1],[0,0]])
// → { type: 'Polygon', coordinates: [[[0,0],[1,0],[1,1],[0,1],[0,0]]] }
// ② 传入完整环列表 Position[][](外环 + 内环/空洞)
createPolygon([
[[0,0],[10,0],[10,10],[0,10],[0,0]], // 外环
[[2,2],[4,2],[4,4],[2,4],[2,2]] // 内环(空洞)
])
// → { type: 'Polygon', coordinates: [[...外环...], [...内环...]] }createMultiPoint(coordinates)
支持两种输入:
// ① 传入单个点 Position(自动包装)
createMultiPoint([0, 0])
// → { type: 'MultiPoint', coordinates: [[0,0]] }
// ② 传入多个点 Position[]
createMultiPoint([[0,0],[1,1],[2,2]])
// → { type: 'MultiPoint', coordinates: [[0,0],[1,1],[2,2]] }createMultiLineString(coordinates)
支持两种输入:
// ① 传入单条线 Position[](自动包装)
createMultiLineString([[0,0],[1,1],[2,0]])
// → { type: 'MultiLineString', coordinates: [[[0,0],[1,1],[2,0]]] }
// ② 传入多条线 Position[][]
createMultiLineString([[[0,0],[1,1]], [[2,2],[3,3]]])
// → { type: 'MultiLineString', coordinates: [[[0,0],[1,1]], [[2,2],[3,3]]] }createMultiPolygon(coordinates)
支持两种输入:
// ① 传入单个多边形的环列表 Position[][](自动包装)
createMultiPolygon([[[0,0],[1,0],[1,1],[0,1],[0,0]]])
// → { type: 'MultiPolygon', coordinates: [[[[0,0],[1,0],[1,1],[0,1],[0,0]]]] }
// ② 传入多个多边形 Position[][][]
createMultiPolygon([
[[[0,0],[1,0],[1,1],[0,1],[0,0]]],
[[[2,2],[3,2],[3,3],[2,3],[2,2]]]
])
// → { type: 'MultiPolygon', coordinates: [...] }createGeometryCollection(geometries)
支持两种输入:
// ① 传入单个 Geometry(自动包装)
createGeometryCollection(createPoint(0, 0))
// → { type: 'GeometryCollection', geometries: [{ type: 'Point', coordinates: [0,0] }] }
// ② 传入 Geometry[]
createGeometryCollection([
createPoint(0, 0),
createLineString([[0,0],[1,1]]),
createPolygon([[0,0],[10,0],[10,10],[0,10],[0,0]])
])
// → { type: 'GeometryCollection', geometries: [...] }10. 校验工具
validateWKT(wkt)
校验 WKT 字符串格式是否合法。
import { validateWKT } from 'giser-geometry-parse';
validateWKT('POINT (30.5 40.5)')
// → { valid: true }
validateWKT('POINT EMPTY')
// → { valid: false, error: 'POINT EMPTY cannot be represented...' }
validateWKT('INVALID WKT')
// → { valid: false, error: 'Unknown geometry type: INVALID' }validateGeoJSON(geojson)
校验 GeoJSON Geometry 对象是否合法。
import { validateGeoJSON } from 'giser-geometry-parse';
validateGeoJSON({ type: 'Point', coordinates: [30.5, 40.5] })
// → { valid: true }
validateGeoJSON({ type: 'Point' })
// → { valid: false, error: 'Point must have "coordinates"' }
validateGeoJSON({ type: 'InvalidType', coordinates: [] })
// → { valid: false, error: 'Invalid geometry type: InvalidType...' }tryFixWKT(wkt)
尝试修复不规范的 WKT 字符串(处理尾部多余字符)。
import { tryFixWKT } from 'giser-geometry-parse';
tryFixWKT('POINT (30.5 40.5) garbage')
// → { fixed: 'POINT (30.5 40.5)', changed: true }cloneGeometry(geometry)
深度克隆几何对象,避免意外修改原对象。
import { cloneGeometry } from 'giser-geometry-parse';
const original = { type: 'Point', coordinates: [0, 0] };
const cloned = cloneGeometry(original);
cloned.coordinates[0] = 100;
// original.coordinates[0] === 0 (未改变)geometryEquals(a, b)
判断两个几何对象是否相等。
import { geometryEquals } from 'giser-geometry-parse';
const a = { type: 'Point', coordinates: [30.5, 40.5] };
const b = { type: 'Point', coordinates: [30.5, 40.5] };
geometryEquals(a, b)
// → true
geometryEquals(a, { type: 'Point', coordinates: [0, 0] })
// → false类型定义
// 坐标点(二维或三维)
type Position = [number, number] | [number, number, number];
// 几何类型
type Geometry =
| Point // { type: 'Point'; coordinates: Position }
| LineString // { type: 'LineString'; coordinates: Position[] }
| Polygon // { type: 'Polygon'; coordinates: Position[][] }
| MultiPoint // { type: 'MultiPoint'; coordinates: Position[] }
| MultiLineString // { type: 'MultiLineString'; coordinates: Position[][] }
| MultiPolygon // { type: 'MultiPolygon'; coordinates: Position[][][] }
| GeometryCollection // { type: 'GeometryCollection'; geometries: Geometry[] }
// Feature:包含一个 Geometry 和任意属性
interface Feature<G extends Geometry = Geometry> {
type: 'Feature';
geometry: G | null;
properties: Record<string, unknown> | null;
id?: string | number;
}
// FeatureCollection:包含多个 Feature
interface FeatureCollection {
type: 'FeatureCollection';
features: Feature[];
}
// 所有 GeoJSON 对象的联合类型
type GeoJSONObject = Geometry | Feature | FeatureCollection;注意事项与边界行为
POINT EMPTY
POINT EMPTY 在 GeoJSON 规范中没有对应表示(GeoJSON Point 不允许 null 坐标)。
解析时会抛出错误,建议改用 Feature 的 null geometry:
// ❌ 会抛出错误
parse('POINT EMPTY');
// ✅ 推荐方式:用 null geometry Feature 表示空点
const emptyFeature = {
type: 'Feature',
geometry: null,
properties: null
};LINESTRING / POLYGON 等 EMPTY
其他 EMPTY 几何会解析为空坐标数组,并在构建时输出 EMPTY:
parse('LINESTRING EMPTY')
// → { type: 'LineString', coordinates: [] }
build({ type: 'LineString', coordinates: [] })
// → 'LINESTRING EMPTY'科学计数法坐标
WKT 标准不支持科学计数法(如 1e-7)。build() 内部已做格式化处理,确保输出为标准十进制:
build({ type: 'Point', coordinates: [0.0000001, 1.0000000] })
// → 'POINT (0.0000001 1)' 而非 'POINT (1e-7 1)'MULTIPOINT 标准格式
build() 输出符合 OGC/ISO 标准格式(每个点用括号包裹),可被 PostGIS、QGIS 等工具正确识别:
build({ type: 'MultiPoint', coordinates: [[0,0],[1,1]] })
// → 'MULTIPOINT ((0 0), (1 1))' ✅ 标准
// 不是:'MULTIPOINT (0 0, 1 1)' ❌ 非标准尾部垃圾字符检测
解析器会严格校验输入,发现几何体后的多余字符时抛出错误:
parse('POINT (0 0) garbage')
// → Error: Unexpected trailing token after geometry: "garbage"完整示例
WKT → GeoJSON Feature → 回写 WKT
import { wktToFeature, featureToWkt } from 'giser-geometry-parse';
const wkt = 'POLYGON ((116 39, 117 39, 117 40, 116 40, 116 39))';
// 解析为 Feature
const feature = wktToFeature(wkt, { name: '某区域', area: 12345 });
// 修改属性
feature.properties.verified = true;
// 取回 WKT
const outputWkt = featureToWkt(feature);
console.log(outputWkt);
// → 'POLYGON ((116 39, 117 39, 117 40, 116 40, 116 39))'批量城市点构建 FeatureCollection
import { wktToFeatureCollection } from 'giser-geometry-parse';
const cities = [
{ wkt: 'POINT (116.39 39.91)', props: { name: '北京', code: 'BJ' } },
{ wkt: 'POINT (121.47 31.23)', props: { name: '上海', code: 'SH' } },
{ wkt: 'POINT (113.26 23.13)', props: { name: '广州', code: 'GZ' } },
];
const fc = wktToFeatureCollection(
cities.map(c => c.wkt),
cities.map(c => c.props)
);
// 直接输出为 GeoJSON 字符串
console.log(JSON.stringify(fc, null, 2));使用工厂方法组合复杂几何
import { createPoint, createLineString, createPolygon, createGeometryCollection, build } from 'giser-geometry-parse';
const collection = createGeometryCollection([
createPoint(0, 0),
createPoint(10, 10),
createLineString([[0,0],[5,5],[10,0]]),
createPolygon([[20,0],[30,0],[30,10],[20,10],[20,0]]) // 单环,自动包装
]);
console.log(build(collection));
// → 'GEOMETRYCOLLECTION (POINT (0 0), POINT (10 10), LINESTRING (0 0, 5 5, 10 0), POLYGON ((20 0, 30 0, 30 10, 20 10, 20 0)))'本地调试
项目根目录提供两个测试页面:
| 文件 | 用途 | 加载方式 |
|------|------|----------|
| index-local.html | 本地版本测试 | 使用本地 dist/index.umd.js |
| index-online.html | 线上版本测试 | 使用 unpkg CDN 加载 npm 包 |
启动本地 HTTP 服务后打开对应页面:
# 方式一:npx serve(推荐)
npx serve .
# 方式二:Python
python -m http.server 8080
# 方式三:http-server
npx http-server -p 8080访问 http://localhost:8080/index-local.html 测试本地构建版本。
访问 http://localhost:8080/index-online.html 测试线上 npm 版本。
提示:本地测试前需先运行 npm run build 构建项目。
License
MIT
