淺談瀏覽器端 WebGIS 開發可能會用到的、提升效率的 js 庫

四季留歌發表於2023-02-05

前置說明

這篇介紹的在 Awesome GIS 基本上都有,經過我的篩選,在 npmjs.com 上也都能找到,方便融入日益強大的 npm 生態。不過這些庫大部分都保留了全域性庫的形式,在非框架中也能使用。有一部分是瀏覽器 + NodeJS 雙端可用的。

1. 與資料格式轉換解析相關

1.1. 解析和轉換 WKT 幾何資料

如果只是完成 WKTGeoJSON 這兩個格式之間的轉換,那麼下面任意一個都能完成你的任務,選庫體積最小的即可。否則,就按需選擇,就個人體驗而言,@terraformer/wkt 這個庫比較均衡。

下面用表格對比這幾個庫。

wkt@terraformer/wktwkt-parser-helper@syncpoint/wkx
ts型別有,額外安裝ts 原始碼
庫大小16.7 KB86.8 KB15.1 KB69.7 KB
純淨度0 依賴0 依賴1 依賴1 依賴
豐富度parse + stringifyparse + stringifyparse + stringify豐富,還包括 WKB
API檔案有,詳盡有,詳盡
更新2019.112022.82022.82021.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/corets 原始碼5.68 MB豐富完善應對較為複雜幾何圖形的幾何計算
geometry-extrudets 原始碼288 KB專一功能齊備擠出 polygon 成體,並三角化;基於 earcut
simplepolygon36.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 血緣,有機會再介紹吧。

相關文章