浅谈浏览器端 WebGIS 开发可能会用到的、提升效率的 js 库

时间:2023-02-05 22:05:07


这篇介绍的在 Awesome GIS 基本上都有,经过我的筛选,在 npmjs.com 上也都能找到,方便融入日益强大的 npm 生态。不过这些库大部分都保留了全局库的形式,在非框架中也能使用。有一部分是浏览器 + NodeJS 双端可用的。

1. 与数据格式转换解析相关

1.1. 解析和转换 WKT 几何数据

如果只是完成 WKTGeoJSON 这两个格式之间的转换,那么下面任意一个都能完成你的任务,选库体积最小的即可。否则,就按需选择,就个人体验而言,@terraformer/wkt 这个库比较均衡。

下面用表格对比这几个库。

wkt @terraformer/wkt wkt-parser-helper @syncpoint/wkx
ts类型 有,额外安装 ts 源码
库大小 16.7 KB 86.8 KB 15.1 KB 69.7 KB
纯净度 0 依赖 0 依赖 1 依赖 1 依赖
丰富度 parse + stringify parse + stringify parse + stringify 丰富,还包括 WKB
API文档 有,详尽 有,详尽
更新 2019.11 2022.8 2022.8 2021.5

@syncpoint/wkxwkx 这个库的改良版,主要是升级了一些过期的底层 API(适配 NodeJS)。

其实这些库的更新时间不必太在意,因为 WKT 这种规范已经发布多年,且足够简单,能用就行,主要是用得舒服。

库大小也并不是真正会包含在最终页面程序之中的大小,因为这些库有的用到 bundler,库大小是包含了多个文件的(例如不同模块风格的库文件、源程序文件等)。

1.2. 前端直接读取 GeoPackage - @ngageoint/geopackage

GeoPackage 是一种基于 SQLite3 定义来的简易单文件地理数据存储格式,文件后缀名是 .gpkg,可以被 QGIS、ArcGIS 读取,开源免费,支持扩展。

与 SpatialLite 均基于 SQLite3 是相似的,但是最大的不同是,SpatialLite 支持数据库的基本操作,而 GeoPackage 更像一种存粹仓库,不太像普通数据库一样能做查询。

pnpm add @ngageoint/geopackage

这里有一个使用这个库直接在页面读取 GeoPackage 文件中地理数据的网站 GeoPackage Viewer

此外,这个 @ngageoint 账户下还有一堆比较积极维护的库:

  • @ngageoint/leaflet-geopackage - Leaflet.js 的插件,允许把 gpkg 直接加载到 lf 地图中

剩下的是一些 NodeJS 才能用的(浏览器不能用)格式转换库:

  • @ngageoint/geopackage-geojson-js - NodeJS 端使用,GeoJSON 和 GeoPackage 互转
  • @ngageoint/geopackage-xyz-js - NodeJS 端使用,把 xyz 瓦片的 zip 包转为 GeoPackage
  • @ngageoint/geopackage-pbf-js - NodeJS 端使用,把 pbf 数据转为 GeoPackage
  • @ngageoint/geopackage-mbtiles-js - NodeJS 端使用,把 mbtiles 数据转为 GeoPackage
  • @ngageoint/geopackage-csv-js - NodeJS 端使用,把 csv 数据转为 GeoPackage
  • @ngageoint/geopackage-shapefile-js - NodeJS 端使用,把 shapefile zip 文件转为 GeoPackage

1.3. 前端直接读取 Esri Shapefile - ts-shapefile

Shapefile 是 Esri 的杰作,是一种多文件的数据格式,虽然我在各种场合不遗余力地推荐大家使用新的数据格式,但是总是有一些人还在问前端能不能解析 Shapefile。这个库源代码使用 TypeScript 编写,打包后支持类型提示(自带 d.ts)。

用于浏览器的打包库文件大约 100+ KB,可以接受。

pnpm add ts-shapefile

这个库的用法要到 GitHub 仓库看 README

注意,Shapefile 规范本体并不含坐标系,所以本库不解析 .prj 文件中的坐标系信息。坐标系的操作库可以看本文第二节。

Mapbox 团队有一个用于写入 Shapefile 并下载为 zip 的库可供参考(纯 js,浏览器可用):shp-write

注意,部分 npm 上的 shapefile 库是 NodeJS 后端库,浏览器不可用,例如 shp2jsonshp-write-streamshp-stream

1.4. 把 GDAL 搬进浏览器 - gdal3.js

GIS 开发界 GDAL 可谓是祖师爷级别的库,它为多种空间数据格式提供了驱动程序(解析器),集成了一些简单的算法实现。

与 npm 上的 gdal 库不同,gdal 库是 NodeJS 对 GDAL 的接口绑定,是后端库,浏览器不可用。(p.s,gdal-async 解决了 gdal 库非异步的问题,然而浏览器还是用不了)。

这个 gdal3.js 使用 emscripten 把 GDAL 转成了 WebAssembly,这样浏览器就能使用 GDAL 了,不过与绑定版本性能还是有所差异的。这个库的源码使用 TypeScript 编写。

pnpm add gdal3.js

由于支持了多种格式,携带的 WebAssembly 文件略多,这个库的体积也膨胀到了 38.4 MB,浏览器真的想用请谨慎考虑。

文档很详尽,因为携带了 wasm 等额外文件,所以在打包环境使用也要注意文件的拷贝,很时髦地在文档中告知了 Webpack、Vite 环境的浏览器应用应该怎么做。

1.5. 格式库小结

我在很多场合都表明:浏览器 + JavaScript 不应用于重度的数据转换,所以大部分重量级的格式转换应该由后端程序完成,其它语言(运行时)有更出色的实现,例如 C++、Java、C#.NET、Rustlang、NodeJS、Python 都有。

在浏览器端有时候是不得而为之。应付一些简单的格式转换还是能胜任的。

2. 操作空间坐标系

2.1. 重投影 proj4

著名 C++ 投影库 PROJ 的 JavaScript 版本,ts 类型需要额外安装 @types/proj4

pnpm add proj4

这个库仅支持单个坐标的转换,对于复杂的数据格式投影转换,需要借助更高层级的封装库,见下文。

2.2. 纠“火星”、“百度”加密坐标

pnpm add gcoord

国人开发的专门解决 “火星”、“百度” 等加密算法的问题。坊间通常称他们 “火星坐标系”、“百度坐标系”,实际上只是一些非公开的加密算法,这个库能将加密后的坐标通过拟合的方式比较准确地纠正。

其官方文档指出支持坐标数组和 GeoJSON 的转换。

2.3. 空间坐标系的 WKT 定义 - spatialreference

这个库并不是拿来做数据坐标系转换的,而是根据 WKID 取坐标系的 WKT 定义用的。

它没有 ts 定义,这是比较可惜的一点。它的默认查询模式需要联网,通过 epsg.io 在线 API 请求查询,所以在大陆环境可能有影响。

pnpm add spatialreference

用法举例:

import SR from 'spatialreference'

new SR({}).wkidToWkt(4326, (err, wkt) => {
  if (!err) console.log(wkt)
})

这个库还支持在传入对象中添加数据库方面的数据,参考它的文档,允许从数据库中获取 WKT。

2.4. 空间坐标系的 PROJ 定义 - epsg

如果你没有在线环境,用不了 2.3 的库,那么可以使用 epsg 库获得坐标系的 PROJ4 定义。这个库自带了一个 JSON 字典。

pnpm add epsg

例子:

import epsg from 'epsg'
console.log(epsg['EPSG:3857'])

// +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs

这个库是 commonjs 模块,需要借助 bundle 转换。

2.5. 从坐标系定义字符串推导 WKID - get-epsg-code

这个与 2.3、2.4 就是相反的操作了。

pnpm add get-epsg-code

例子:

import getEPSGCode from 'get-epsg-code'

const proj4string = `+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs`
const wkid = getEPSGCode(proj4string)
// 3857

它的文档说支持几乎所有格式,WKT、proj4、esriproj 均可尝试:在线测试

2.6. 对 GeoJSON 的重投影 - reproj-helperreproject

前者由 TypeScript 写成,在其 dist/ 下的打包成果文件中,是将 proj4 打包进去了,所以体积会略大(因为 proj4 本身就有 900+KB),好处是 API、说明文档较齐全,也有类型提示,不过缺点是对 GeoJSON 没有使用定义,而是粗暴地设为 any

pnpm add reproj-helper

举例:

import RH from 'reproj-helper'

const pointFeature = {
  "type": "Feature",
  "properties": {},
  "geometry": {
    "coordinates": [
      27.896140109578766,
      20.219492193232625
    ],
    "type": "Point"
  }
}
const rp = new RH.ReProjector()
rp.from('4326')
rp.to('3857')
rp.feature(pointFeature)
const result = await rp.project()

后者就比较轻量化了,发布到 npmjs.com 上的包不含打包成果,但是在安装依赖时也会把 proj4 安装进来,专心于 GeoJSON 对象的转换,支持校验 GeoJSON 是否存在坐标系定义。这个就没有 ts 类型提示了:

pnpm add reproject

举例:

import { reproject } from 'reproject'

const pointFeature = {
  "type": "Feature",
  "properties": {},
  "geometry": {
    "coordinates": [
      27.896140109578766,
      20.219492193232625
    ],
    "type": "Point"
  }
}
console.log(reproject(
  pointFeature,
  '+proj=longlat +datum=WGS84 +no_defs +type=crs',
  '+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs'
))

3. 简易空间分析与几何运算

3.1. JTS 的移植 - jsts

JTS 有个 C++ 的儿子 GEOS,然后又有个 JavaScript 的儿子 jstsJTS 是 Java 编写的几何库,其地位无需多言。

pnpm add jsts
pnpm add @types/jsts -D

之前写过一篇如何用它的文章,以缓冲分析为例:

import JSTSWKTReader from 'jsts/org/locationtech/jts/io/WKTReader'
import JSTSGeoJSONWriter from 'jsts/org/locationtech/jts/io/GeoJSONWriter'
import JSTSBufferOp from 'jsts/org/locationtech/jts/operation/buffer/BufferOp'
 ​
const wkt = `POINT (0 0)`
const bufferCenter = new JSTSWKTReader().read(wkt)
const bufferResult = JSTSBufferOp.bufferOp(
  bufferCenter,
  10
) // instanceof Geometry
 ​
const bufferResultGeoJSON = new JSTSGeoJSONWriter().write(bufferResult)

如果不熟悉其包结构和 JTS 的用法,我不是很推荐直接上手使用 jsts。

3.2. 给 GeoJSON 设计的简易版 “turf” - @terraformer/spatial

上文介绍 WKT 库时这个 @terraformer 账号有出现。这个库可以简易地对 GeoJSON 对象进行简单的空间分析,可以说是一个简单版本的 turf:

pnpm add @terraformer/spatial

它的类型库需要额外安装 @types/terraformer__spatial -D

它具备 applyConverter(GeoJSON 要素顶点遍历器)、intersectsconvexHullcontains 等非常简单的分析功能,见其 README 文档。

3.3. 其他一些几何图形变换与计算库

接下来这些几何库的 “GIS” 血缘就比较淡了,不过辅助用用也还是可以的。

类型提示 库大小 丰富度 文档 用途建议
earcut 额外安装 95.2 KB 专一功能 齐备 离散几何多边形的三角化,生成三角网格
hextile 小于 50KB 专一功能 基本齐全 生成渔网(支持若干种内置形状)
geometric 额外安装 107 KB 中等 齐备 简单几何运算,替代部分 turf 需求
@flatten-js/core ts 源码 5.68 MB 丰富 完善 应对较为复杂几何图形的几何计算
geometry-extrude ts 源码 288 KB 专一功能 齐备 挤出 polygon 成体,并三角化;基于 earcut
simplepolygon 36.6 KB 专一功能 齐备 处理 GeoJSON。将复杂的(即自交的)GeoJSON 多边形,分解为复合的简单、非自交的单环多边形。见 README

3.4. 几何空间分析库小结

其实上述这些库还远远不够,开源社区总能找到宝藏,不过应该也足够使用了。我为什么没有把 turf 列出来呢?是因为 turf 的算法准确性、性能实在是拿不出手,群友们都反馈过这个问题,能不用还是尽量不要用了。

而且,复杂的几何计算我觉得还是交给后台计算程序比较好,前端能运算的终究有限。

4. 地图库扩展

4.1. 为 ol、mapboxgl、leaflet.js 扩展绘图工具 - terra-draw

这个库是前端几个地图库(支持 OpenLayers V7、Leaflet.js V1.9、MapboxGL V2、Maplibre V2、GoogleMapAPI V3)的插件,由 TypeScript 写成,文档齐全,也有在线例子。

pnpm add terra-draw

在我写这篇文章时,它还在 alpha 阶段,不过可用性已经很不错了。官方示例指路:TerraDraw

4.2. 为 leaflet.js 扩展的绘图工具

如题。这个绘图插件很强大,基本满足绝大多数 2D 绘制要求,体积也来到了 450 KB+。

pnpm add @geoman-io/leaflet-geoman-free

由 TypeScript 编写,支持多国语言,具有十分完善的文档。不过,它自带的按钮对于某些场景可能不太合适使用了(想自定义按钮样式、UI 逻辑的情况)。总体来说我打 98 分。

5. 杂项

5.1. *行政区划编码库

这个像个字典,小型项目(不具备数据库接口调用条件)适合使用。

pnpm add china-region

另有带类型提示的版本 china-regions-ts,不过我个人建议在找这类行政编码库时,尽量找最新版的。

5.2. 为 GeoJSON 增强类型提示 - @types/geojson

这个可以替代 GeoJSON 各级对象在你 TypeScript 项目中的 any 问题,注意要安装到开发依赖,这东西只是个类型库。

pnpm add @types/geojson -D

用法简单:

import type { Polygon } from 'geojson'

// ...

5.3. 与 WFS 类似的 OGC Feature API 客户端实现库 - @ogcapi-js/features

OGC API 是什么我之前写过一篇入门介绍,是 OGC 正在开发推进的下一代 GIS 网络规范,涵盖方方面面。其中,OGC Feature API 就是 WFS 的下一代规范,也即原来打算的 WFS 3.0

当满足 OGC Feature API 的服务启动后,可以用这个包来调用 OGC Feature API 规范定义的功能。

当前 OGC API 仍未完全落定,以最新版为准,可以关注 @ogcapi-js 这个账户下的新包。

pnpm add @ogcapi-js/features

用法也很简单。

import { Service } from `@ogcapi-js/features`;

// 创建一个服务对象
const service = new Service({
  baseUrl: 'https://ogcapi.service.com'
});

// 调用接口获取数据集清单
const collections = await services.getCollections();

5.4. 操作 Esri 的投影定义对象

Esri 用户的小工具,不过也适用于部分需要坐标系 WKT 的场景,暴露一个 lookup 函数,传入 WKID 来查找坐标系对象:

pnpm add @esri/proj-codes

这个包略大,如无必要则不用,查找坐标系的工作应该让后端数据库完成。

import codes from '@esri/proj-codes'

const crs = codes.lookup(3857)

crs.name
// 'WGS_1984_Web_Mercator_Auxiliary_Sphere'

crs.wkt
// 'PROJCS["WGS_1984_Web_Mercator_Auxiliary_Sphere",GEOGCS["GCS_WGS_1984"...'

crs.description
// 'WGS 1984 Web Mercator Major Auxiliary Sphere'

crs.authority
// 'EPSG'

crs.deprecated
// 'no'

crs.extent
// { "slat": -85.06, "nlat": 85.06, "llon": -180.0, "rlon": 180.0 }

// this works too
codes.lookup(3857).name
// 'WGS_1984_Web_Mercator_Auxiliary_Sphere'

6. 小结

这篇主要以浏览器前端为主,其实有一部分库在 NodeJS 后端也能用,譬如坐标系、格式转换、几何空间分析等。单独给后端 NodeJS 的也有,数据库、格式转换、图像算法的偏多,不过也逐渐淡去了 GIS 血缘,有机会再介绍吧。