GeoJSONcn
GeoJSONcn / 技术专栏 / GeoJSON 与 WKT 互转…

GeoJSON 与 WKT 互转:把前端边界数据送进空间数据库

发布于 2026-07-23 · GeoJSONcn 技术团队 · 阅读约 13 分钟

--- title: GeoJSON 与 WKT 互转:把前端边界数据送进空间数据库 ---

前端地图几乎都用 GeoJSON 描述边界,但一旦要把数据落库做空间查询,主流数据库(PostGIS、MySQL 空间扩展、SQLite 的 SpatiaLite)认的不是 GeoJSON,而是 WKT(Well-Known Text,OGC 制定的空间文本标准)。这篇聊聊 GeoJSON 和 WKT 怎么互相转,并给出能直接跑的 JavaScript 代码。

WKT 到底是什么

WKT 是一种用纯文本表示几何的规范,由 OGC 制定。和 GeoJSON 不同,它不包含任何属性,只描述"形状":点、线、面怎么摆。一个 Polygon 在 WKT 里长这样:

POLYGON ((116 39, 116 40, 117 40, 116 39))

坐标写法是 经度 纬度(空格分隔),外面套两层括号。GeoJSON 的三层结构和坐标顺序,可以回顾 GeoJSON 到底是什么 那篇。

为什么要互转

两个常见场景:

1. 采集入库。用户在地图上画了一个区,前端拿到的是 GeoJSON,要写进 PostGIS 得先变成 WKT。 2. 查询出库。数据库里用 ST_AsText(geom) 取出的也是 WKT,前端渲染前得转回 GeoJSON。

本系列讲过的 GeoJSON 格式转换(KML/SVG/CSV/PNG)是"出图格式",WKT 是"数据库格式"这一支,正好补上。

GeoJSON → WKT

一个函数覆盖 Point、LineString、Polygon、MultiPolygon,也认 Feature 和 FeatureCollection:

// GeoJSON 几何 → WKT 字符串
function coordsToWkt(type, coordinates) {
  const pair = c => `${c[0]} ${c[1]}`;            // [lon, lat] → "lon lat"
  const ring = r => '(' + r.map(pair).join(', ') + ')';
  switch (type) {
    case 'Point':              return `POINT (${pair(coordinates)})`;
    case 'LineString':         return `LINESTRING (${coordinates.map(pair).join(', ')})`;
    case 'Polygon':            return `POLYGON (${coordinates.map(ring).join(', ')})`;
    case 'MultiPolygon':       return `MULTIPOLYGON (${coordinates.map(p => '(' + p.map(ring).join(', ') + ')').join(', ')})`;
    default: throw new Error('暂不支持的几何类型: ' + type);
  }
}

function geojsonToWkt(geojson) {
  if (geojson.type === 'Feature') return coordsToWkt(geojson.geometry.type, geojson.geometry.coordinates);
  if (geojson.type === 'FeatureCollection') {
    return geojson.features.map(f => coordsToWkt(f.geometry.type, f.geometry.coordinates)).join('; ');
  }
  return coordsToWkt(geojson.type, geojson.coordinates);
}

// 天安门一个点
console.log(geojsonToWkt({ type: 'Point', coordinates: [116.397428, 39.90923] }));
// → POINT (116.397428 39.90923)

WKT → GeoJSON

反向更绕,因为 WKT 的括号是嵌套的。下面用一个"按顶层逗号切分"的小函数处理 Polygon 和 MultiPolygon:

function wktToGeojson(wkt) {
  const m = wkt.trim().match(/^(\w+)\s*\(([\s\S]*)\)$/);
  if (!m) throw new Error('无法解析的 WKT: ' + wkt);
  const tag = m[1].toUpperCase();
  const body = m[2];

  // 按顶层逗号切分(忽略括号内的逗号)
  const splitTop = s => {
    const out = []; let depth = 0, cur = '';
    for (const ch of s) {
      if (ch === '(') depth++;
      if (ch === ')') depth--;
      if (ch === ',' && depth === 0) { out.push(cur); cur = ''; }
      else cur += ch;
    }
    if (cur) out.push(cur);
    return out;
  };
  const toCoord = s => s.trim().split(/\s+/).map(Number);   // "lon lat" → [lon, lat]
  const toRing  = s => s.trim().replace(/^\(|\)$/g, '').split(',').map(toCoord);

  if (tag === 'POINT')      return { type: 'Point', coordinates: toCoord(body) };
  if (tag === 'LINESTRING') return { type: 'LineString', coordinates: splitTop(body).map(toCoord) };
  if (tag === 'POLYGON')    return { type: 'Polygon', coordinates: splitTop(body).map(toRing) };
  if (tag === 'MULTIPOLYGON') {
    return { type: 'MultiPolygon', coordinates: splitTop(body).map(poly => poly.trim().slice(1, -1).split('),(').map(toRing)) };
  }
  throw new Error('暂不支持的几何类型: ' + tag);
}

落到 PostGIS

WKT 不带坐标系信息(没有 SRID),所以建表和写入时一定把 4326(WGS84)显式写上,否则数据库不知道这是经纬度:

-- 建表:geometry 列指定 SRID 4326(WGS84)
CREATE TABLE districts (
  id   serial PRIMARY KEY,
  name text,
  geom geometry(Polygon, 4326)
);

-- 把 GeoJSON→WKT 的结果写进去(务必带 SRID)
INSERT INTO districts (name, geom)
VALUES ('示例区', ST_GeomFromText('POLYGON ((116 39, 116 40, 117 40, 116 39))', 4326));

-- 反向:从库里取 WKT 交给前端
SELECT name, ST_AsText(geom) FROM districts;

坐标系的事,GeoJSONcn 的坐标系文章 专门写过:边界统一存 WGS84,落库时 SRID 也别丢。

性能提醒

如果你手上是 TopoJSON(本系列讲过的瘦身方案),得先还原成 GeoJSON 再走上面的转换。另外,全国 4.6 万页的行政区划边界,别在请求里现转,生成期转好存进数据库,查询时直接 ST_Within 做空间关系判断,配合 点在多边形内判定 就能做围栏。

WKT 是纯文本,体积小、传输省,做数据库之间的几何交换格式很合适。