GIS

OpenLayers中的GeoJSON

了解OpenLayers如何读取、解析、创建和导出GeoJSON数据,以及GeoJSON与Feature、VectorSource、VectorLayer和坐标系之间的关系。

阅读约 213 分钟

OpenLayers中的GeoJSON

GeoJSON是WebGIS中非常常见的矢量数据格式,而OpenLayers提供了专门的GeoJSON格式类,用于在GeoJSON与OpenLayers Feature之间进行转换。

简单来说:

GeoJSON
    ↓
OpenLayers GeoJSON
    ↓
Feature
    ↓
VectorSource
    ↓
VectorLayer
    ↓
Map

OpenLayers中的GeoJSON主要用于:

  • 加载GeoJSON
  • 将GeoJSON转换为Feature
  • 将Feature转换为GeoJSON
  • 坐标系转换
  • GeoJSON数据导出
  • WebGIS接口数据展示

OpenLayers的GeoJSON类

OpenLayers使用:

import GeoJSON from 'ol/format/GeoJSON.js'

创建GeoJSON格式对象:

const format = new GeoJSON()

之后可以使用:

format.readFeature()
format.readFeatures()
format.writeFeature()
format.writeFeatures()

完成GeoJSON与Feature之间的转换。

GeoJSON与Feature的关系

OpenLayers中的GeoJSON主要负责数据格式转换。

可以理解为:

GeoJSON
    ↓
ol/format/GeoJSON
    ↓
Feature

而Feature是OpenLayers地图运行时使用的对象。

因此:

GeoJSON
    ↓
数据交换格式

Feature
    ↓
OpenLayers运行时对象

读取单个Feature

如果GeoJSON表示一个Feature,可以使用:

const feature = format.readFeature(geojson)

例如:

const geojson = {
  type: 'Feature',
  geometry: {
    type: 'Point',
    coordinates: [116.397, 39.908]
  },
  properties: {
    name: '北京'
  }
}

const feature = format.readFeature(geojson)

读取之后:

GeoJSON Feature
        ↓
    readFeature()
        ↓
OpenLayers Feature

读取FeatureCollection

实际WebGIS接口中更常见的是:

FeatureCollection

例如:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [116.397, 39.908]
      },
      "properties": {
        "name": "北京"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [121.473, 31.230]
      },
      "properties": {
        "name": "上海"
      }
    }
  ]
}

可以使用:

const features = format.readFeatures(geojson)

返回:

Feature[]

即:

GeoJSON FeatureCollection
        ↓
    readFeatures()
        ↓
Feature[]

创建VectorSource

读取Feature之后,通常会放入VectorSource

import VectorSource from 'ol/source/Vector.js'

const source = new VectorSource({
  features
})

完整流程:

GeoJSON
 ↓
readFeatures()
 ↓
Feature[]
 ↓
VectorSource

创建VectorLayer

然后使用VectorLayer显示数据。

import VectorLayer from 'ol/layer/Vector.js'

const layer = new VectorLayer({
  source
})

最后加入地图:

map.addLayer(layer)

完整代码:

import GeoJSON from 'ol/format/GeoJSON.js'
import VectorSource from 'ol/source/Vector.js'
import VectorLayer from 'ol/layer/Vector.js'

const format = new GeoJSON()

const features = format.readFeatures(geojson)

const source = new VectorSource({
  features
})

const layer = new VectorLayer({
  source
})

map.addLayer(layer)

完整数据流:

GeoJSON
 ↓
GeoJSON.readFeatures()
 ↓
Feature[]
 ↓
VectorSource
 ↓
VectorLayer
 ↓
Map

读取GeoJSON字符串

GeoJSON也可以是JSON字符串。

例如:

const json = `{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [116.397, 39.908]
  },
  "properties": {
    "name": "北京"
  }
}`

可以直接:

const feature = format.readFeature(json)

OpenLayers会解析JSON字符串。

读取JSON对象

如果已经通过:

JSON.parse()

获得JavaScript对象:

const data = JSON.parse(json)

也可以:

const features = format.readFeatures(data)

因此:

JSON字符串
    ↓
readFeature / readFeatures

JSON对象
    ↓
readFeature / readFeatures

读取GeoJSON Geometry

除了Feature,也可以直接读取Geometry。

例如:

{
  "type": "Point",
  "coordinates": [116.397, 39.908]
}

可以使用:

const geometry = format.readGeometry(geojson)

得到OpenLayers Geometry对象。

关系:

GeoJSON Geometry
        ↓
readGeometry()
        ↓
ol/geom/Geometry

例如:

const geometry = format.readGeometry({
  type: 'Point',
  coordinates: [116.397, 39.908]
})

Geometry与Feature的区别

如果GeoJSON是:

Geometry

使用:

readGeometry()

如果GeoJSON是:

Feature

使用:

readFeature()

如果GeoJSON是:

FeatureCollection

使用:

readFeatures()

可以记住:

Geometry
    ↓
readGeometry()

Feature
    ↓
readFeature()

FeatureCollection
    ↓
readFeatures()

坐标系转换

这是OpenLayers读取GeoJSON时非常重要的功能。

GeoJSON数据可能使用:

EPSG:4326

而OpenLayers地图通常使用:

EPSG:3857

例如:

GeoJSON
EPSG:4326
    ↓
坐标转换
    ↓
Feature
EPSG:3857

可以通过:

const features = format.readFeatures(geojson, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

其中:

dataProjection
    ↓
GeoJSON数据本身的坐标系

featureProjection
    ↓
OpenLayers Feature使用的坐标系

为什么需要坐标转换

例如GeoJSON:

[116.397, 39.908]

通常表示:

经度 = 116.397
纬度 = 39.908

属于:

EPSG:4326

而Web地图经常使用:

EPSG:3857

如果直接把两种坐标混合使用:

EPSG:4326
    ↓
直接当成EPSG:3857

地图上的位置就会出现错误。

因此应该明确:

数据坐标系
        ↓
dataProjection

地图坐标系
        ↓
featureProjection

readFeature中的坐标转换

例如:

const feature = format.readFeature(geojson, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

OpenLayers会在读取过程中完成坐标转换。

因此:

GeoJSON
EPSG:4326
    ↓
GeoJSON.readFeature()
    ↓
坐标转换
    ↓
Feature
EPSG:3857

readFeatures中的坐标转换

FeatureCollection通常使用:

const features = format.readFeatures(geojson, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

这是WebGIS项目中非常常见的写法。

例如:

const format = new GeoJSON()

const features = format.readFeatures(data, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

然后:

const source = new VectorSource({
  features
})

从Feature生成GeoJSON

OpenLayers也可以将Feature转换为GeoJSON。

例如:

const geojson = format.writeFeature(feature)

结果:

Feature
    ↓
writeFeature()
    ↓
GeoJSON

例如:

const feature = new Feature({
  geometry: new Point([12958000, 4850000])
})

const geojson = format.writeFeature(feature)

返回的是GeoJSON字符串。

writeFeatureObject

如果希望获得JavaScript对象,可以使用:

const object = format.writeFeatureObject(feature)

例如:

const data = format.writeFeatureObject(feature)

console.log(data)

得到类似:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [116.397, 39.908]
  },
  "properties": {}
}

因此:

writeFeature()
        ↓
GeoJSON字符串

writeFeatureObject()
        ↓
GeoJSON对象

导出多个Feature

多个Feature可以使用:

const geojson = format.writeFeatures(features)

例如:

const features = source.getFeatures()

const geojson = format.writeFeatures(features)

流程:

VectorSource
    ↓
getFeatures()
    ↓
Feature[]
    ↓
writeFeatures()
    ↓
GeoJSON

writeFeaturesObject

如果需要GeoJSON对象:

const object = format.writeFeaturesObject(features)

结果通常是:

{
  "type": "FeatureCollection",
  "features": []
}

因此:

writeFeatures()
    ↓
GeoJSON字符串

writeFeaturesObject()
    ↓
GeoJSON对象

导出时进行坐标转换

读取时可以:

dataProjection
featureProjection

写出时也需要注意坐标系。

例如:

const geojson = format.writeFeatures(features, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

可以理解为:

OpenLayers Feature
EPSG:3857
    ↓
writeFeatures()
    ↓
转换
    ↓
GeoJSON
EPSG:4326

这对于GeoJSON下载功能非常重要。

GeoJSON加载HTTP接口

实际项目中GeoJSON通常来自后端API。

例如:

const response = await fetch('/api/places')

const data = await response.json()

然后:

const features = format.readFeatures(data, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

再:

source.addFeatures(features)

完整流程:

HTTP API
 ↓
JSON
 ↓
GeoJSON
 ↓
readFeatures()
 ↓
Feature[]
 ↓
VectorSource
 ↓
Map

动态加载GeoJSON

也可以创建空的VectorSource:

const source = new VectorSource()

之后动态加载:

const response = await fetch('/data/city.geojson')
const data = await response.json()

const features = format.readFeatures(data, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

source.addFeatures(features)

这样可以在地图初始化之后继续加载数据。

GeoJSON与VectorSource

GeoJSON本身不是图层。

这是OpenLayers初学者容易混淆的地方。

GeoJSON
 ↓
数据格式

而:

VectorSource
 ↓
Feature数据容器

以及:

VectorLayer
 ↓
地图图层

因此:

GeoJSON
    ↓
Feature
    ↓
VectorSource
    ↓
VectorLayer

不能直接把GeoJSON理解成Layer。

GeoJSON与VectorLayer

正确结构:

GeoJSON
 ↓
GeoJSON Format
 ↓
Feature
 ↓
VectorSource
 ↓
VectorLayer

例如:

const features = new GeoJSON().readFeatures(data)

const source = new VectorSource({
  features
})

const layer = new VectorLayer({
  source
})

给GeoJSON设置样式

GeoJSON转换成Feature之后,就可以按照普通Feature进行样式设置。

例如:

const layer = new VectorLayer({
  source,
  style: new Style({
    image: new CircleStyle({
      radius: 6,
      fill: new Fill({
        color: '#1677ff'
      })
    })
  })
})

因此:

GeoJSON
 ↓
Feature
 ↓
Style
 ↓
VectorLayer

GeoJSON本身不负责地图显示样式。

根据属性设置样式

GeoJSON的:

properties

可以用于动态设置样式。

例如:

style: (feature) => {
  const status = feature.get('status')

  if (status === 'warning') {
    return warningStyle
  }

  return normalStyle
}

GeoJSON:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [116.397, 39.908]
  },
  "properties": {
    "status": "warning"
  }
}

读取后:

feature.get('status')

就可以获得:

warning

因此:

GeoJSON properties
        ↓
Feature属性
        ↓
样式判断
        ↓
地图显示

GeoJSON中的属性读取

例如:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [116.397, 39.908]
  },
  "properties": {
    "name": "北京",
    "type": "city"
  }
}

读取之后:

feature.get('name')

得到:

北京

读取:

feature.get('type')

得到:

city

也可以:

feature.getProperties()

获得全部属性。

获取Geometry

OpenLayers Feature中可以通过:

const geometry = feature.getGeometry()

获取Geometry。

关系:

Feature
 ↓
getGeometry()
 ↓
Geometry

例如:

const geometry = feature.getGeometry()

console.log(geometry.getType())

可能得到:

Point

获取GeoJSON Geometry

如果想获得Feature中的Geometry对应GeoJSON,可以使用:

const geometry = format.writeGeometryObject(
  feature.getGeometry()
)

结果类似:

{
  "type": "Point",
  "coordinates": [116.397, 39.908]
}

流程:

Feature
 ↓
getGeometry()
 ↓
Geometry
 ↓
writeGeometryObject()
 ↓
GeoJSON Geometry

GeoJSON Geometry转换

OpenLayers还可以直接:

format.writeGeometry(geometry)

或者:

format.writeGeometryObject(geometry)

因此:

readGeometry()
    ↓
GeoJSON → Geometry

writeGeometry()
    ↓
Geometry → GeoJSON

GeoJSON Feature转换总结

读取:

format.readFeature(data)

多个:

format.readFeatures(data)

Geometry:

format.readGeometry(data)

写出:

format.writeFeature(feature)

多个:

format.writeFeatures(features)

Geometry:

format.writeGeometry(geometry)

对象形式:

format.writeFeatureObject(feature)
format.writeFeaturesObject(features)
format.writeGeometryObject(geometry)

常见方法总结

方法作用
readFeatureGeoJSON Feature → Feature
readFeaturesFeatureCollection → Feature
readGeometryGeoJSON Geometry → Geometry
writeFeatureFeature → GeoJSON字符串
writeFeaturesFeature → GeoJSON字符串
writeGeometryGeometry → GeoJSON字符串
writeFeatureObjectFeature → GeoJSON对象
writeFeaturesObjectFeature → FeatureCollection对象
writeGeometryObjectGeometry → GeoJSON Geometry对象

GeoJSON完整加载示例

import GeoJSON from 'ol/format/GeoJSON.js'
import VectorSource from 'ol/source/Vector.js'
import VectorLayer from 'ol/layer/Vector.js'

const format = new GeoJSON()

const response = await fetch('/data/cities.geojson')

const data = await response.json()

const features = format.readFeatures(data, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

const source = new VectorSource({
  features
})

const layer = new VectorLayer({
  source
})

map.addLayer(layer)

数据流:

cities.geojson
      ↓
fetch()
      ↓
JSON
      ↓
readFeatures()
      ↓
Feature[]
      ↓
VectorSource
      ↓
VectorLayer
      ↓
Map

GeoJSON完整导出示例

const format = new GeoJSON()

const features = source.getFeatures()

const geojson = format.writeFeatures(features, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

console.log(geojson)

流程:

VectorSource
      ↓
getFeatures()
      ↓
Feature[]
      ↓
writeFeatures()
      ↓
GeoJSON

下载GeoJSON

可以结合浏览器Blob实现下载。

const geojson = format.writeFeatures(features, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

const blob = new Blob(
  [geojson],
  {
    type: 'application/geo+json'
  }
)

const url = URL.createObjectURL(blob)

const link = document.createElement('a')

link.href = url
link.download = 'data.geojson'

link.click()

URL.revokeObjectURL(url)

完整流程:

Feature[]
 ↓
GeoJSON
 ↓
Blob
 ↓
浏览器下载
 ↓
data.geojson

GeoJSON与WKT

OpenLayers也经常同时处理GeoJSON和WKT。

例如:

WKT
 ↓
WKT Format
 ↓
Feature

以及:

GeoJSON
 ↓
GeoJSON Format
 ↓
Feature

最终都可以进入:

Feature
 ↓
VectorSource
 ↓
VectorLayer

因此:

WKT ───────┐
           ↓
        Feature
           ↑
GeoJSON ───┘

GeoJSON与API

典型后端接口:

GET /api/features

返回:

{
  "type": "FeatureCollection",
  "features": []
}

前端:

const data = await fetch('/api/features')
  .then(res => res.json())

const features = new GeoJSON().readFeatures(data, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

然后:

source.addFeatures(features)

这是非常典型的OpenLayers业务开发模式。

GeoJSON与PostGIS

常见后端架构:

PostGIS
 ↓
Spatial Query
 ↓
ST_AsGeoJSON
 ↓
GeoJSON
 ↓
HTTP API
 ↓
OpenLayers

例如:

数据库Geometry
        ↓
GeoJSON
        ↓
HTTP
        ↓
OpenLayers
        ↓
Feature

OpenLayers负责的是:

GeoJSON
 ↓
Feature

而空间数据库负责:

Geometry
 ↓
查询
 ↓
GeoJSON

常见问题

GeoJSON加载后位置错误

首先检查:

dataProjection
featureProjection

例如:

format.readFeatures(data, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
})

GeoJSON点跑到错误位置

首先检查坐标顺序:

[经度, 纬度]

而不是:

[纬度, 经度]

Polygon显示异常

检查:

coordinates嵌套层级
Ring是否闭合
Geometry是否有效

Feature属性获取不到

检查GeoJSON中的:

properties

例如:

"properties": {
  "name": "北京"
}

OpenLayers:

feature.get('name')

GeoJSON无法显示

检查:

GeoJSON格式
type
coordinates
geometry
properties
FeatureCollection
坐标系

OpenLayers GeoJSON核心关系

GeoJSON
│
├── Geometry
│
├── Feature
│   ├── geometry
│   └── properties
│
└── FeatureCollection
        │
        └── features[]
                ↓
        GeoJSON.readFeatures()
                ↓
             Feature[]
                ↓
          VectorSource
                ↓
          VectorLayer
                ↓
               Map

OpenLayers GeoJSON读写关系

读取:

GeoJSON
    ↓
readGeometry()
    ↓
Geometry
GeoJSON Feature
    ↓
readFeature()
    ↓
Feature
GeoJSON FeatureCollection
    ↓
readFeatures()
    ↓
Feature[]

写出:

Geometry
    ↓
writeGeometry()
    ↓
GeoJSON
Feature
    ↓
writeFeature()
    ↓
GeoJSON
Feature[]
    ↓
writeFeatures()
    ↓
FeatureCollection

总结

OpenLayers中的GeoJSON主要通过:

import GeoJSON from 'ol/format/GeoJSON.js'

完成GeoJSON与OpenLayers对象之间的转换。

最核心的方法是:

readGeometry()
readFeature()
readFeatures()

writeGeometry()
writeFeature()
writeFeatures()

典型加载流程:

GeoJSON
 ↓
readFeatures()
 ↓
Feature[]
 ↓
VectorSource
 ↓
VectorLayer
 ↓
Map

典型导出流程:

Feature[]
 ↓
writeFeatures()
 ↓
GeoJSON
 ↓
下载 / API / 数据交换

而在实际WebGIS项目中,还需要特别注意:

GeoJSON坐标顺序
坐标参考系
dataProjection
featureProjection
Feature属性
Geometry结构

可以记住:

OpenLayers中的GeoJSON不是地图图层,而是负责GeoJSON与Feature、Geometry之间数据转换的格式类。GeoJSON经过解析后进入Feature,再由VectorSource和VectorLayer负责管理和显示。

🧰

相关工具

使用 IYATools 在线工具快速处理 GIS 数据