GIS

OpenLayers Map是什么

了解OpenLayers中Map的基本概念、核心组成、View、Layer、Source、Interaction、Control以及Map常用操作和事件。

阅读约 166 分钟

OpenLayers Map是什么

Map是OpenLayers中最核心的对象。

它代表一个完整的Web地图实例,负责组织:

  • 地图图层
  • 地图视图
  • 地图控件
  • 地图交互
  • 地图事件
  • 地图渲染
  • 地图目标容器

简单来说:

Map就是OpenLayers地图应用的核心容器。

例如:

Map
├── View
├── Layers
├── Controls
├── Interactions
└── Target

一个完整的OpenLayers地图通常从创建Map开始。

Map的基本结构

OpenLayers中的核心关系可以理解为:

Map
│
├── View
│
├── Layer
│   └── Source
│       └── Feature
│           └── Geometry
│
├── Interaction
│
└── Control

其中:

Map
 ↓
管理整个地图

View
 ↓
决定地图看哪里、缩放多少

Layer
 ↓
决定显示哪些内容

Source
 ↓
提供地图数据

Feature
 ↓
表示空间要素

Geometry
 ↓
表示空间形状

Interaction
 ↓
处理用户操作

Control
 ↓
提供地图控件

创建Map

首先引入Map:

import Map from 'ol/Map.js'

然后创建:

const map = new Map({
  target: 'map'
})

这里的:

target

表示地图需要挂载到哪个HTML元素。

例如:

<div id="map"></div>

然后:

const map = new Map({
  target: 'map'
})

这样OpenLayers就会将地图渲染到:

#map

这个元素中。

Map的target

target用于指定地图容器。

例如:

const map = new Map({
  target: document.getElementById('map')
})

也可以直接使用元素ID:

const map = new Map({
  target: 'map'
})

对应:

<div id="map"></div>

因此:

HTML
 ↓
<div id="map">
 ↓
OpenLayers Map

地图容器必须有尺寸

这是OpenLayers开发中非常常见的问题。

例如:

<div id="map"></div>

如果没有设置高度:

#map {
  height: 0;
}

地图可能不会正常显示。

通常需要:

#map {
  width: 100%;
  height: 500px;
}

或者:

html,
body,
#map {
  width: 100%;
  height: 100%;
}

因此地图显示异常时,除了检查JavaScript,也需要检查地图容器尺寸。

Map的View

Map最重要的配置之一是View。

例如:

import View from 'ol/View.js'

const view = new View({
  center: [12950000, 4850000],
  zoom: 10
})

然后:

const map = new Map({
  target: 'map',
  view
})

结构:

Map
 ↓
View

View负责控制:

  • 中心点
  • 缩放
  • 旋转
  • 投影
  • 分辨率

可以理解为:

View决定用户从哪里、以什么缩放级别观察地图。

Map的center

center表示地图中心坐标。

例如:

const view = new View({
  center: [12950000, 4850000],
  zoom: 10
})

这里的坐标通常是:

X = 12950000
Y = 4850000

具体含义取决于View使用的投影。

如果使用:

EPSG:3857

则通常使用Web Mercator投影坐标。

Map的zoom

zoom表示地图缩放级别。

例如:

const view = new View({
  center: [12950000, 4850000],
  zoom: 10
})

通常:

zoom较小
 ↓
显示范围较大

zoom较大
 ↓
显示范围较小

例如:

zoom 3
 ↓
大范围地图

zoom 10
 ↓
城市范围

zoom 16
 ↓
街道和建筑

具体显示范围取决于地图投影和分辨率配置。

Map的projection

投影通常配置在View中:

const view = new View({
  projection: 'EPSG:3857'
})

常见:

EPSG:4326
EPSG:3857

例如:

const view = new View({
  projection: 'EPSG:3857',
  center: [12950000, 4850000],
  zoom: 10
})

可以理解为:

Map
 ↓
View
 ↓
Projection
 ↓
决定地图坐标体系

一个完整Map

一个简单的OpenLayers地图通常如下:

import Map from 'ol/Map.js'
import View from 'ol/View.js'
import TileLayer from 'ol/layer/Tile.js'
import OSM from 'ol/source/OSM.js'

const map = new Map({
  target: 'map',

  layers: [
    new TileLayer({
      source: new OSM()
    })
  ],

  view: new View({
    center: [12950000, 4850000],
    zoom: 10
  })
})

完整结构:

Map
│
├── TileLayer
│   └── OSM
│
└── View
    ├── center
    └── zoom

Map与Layer

Map负责管理Layer。

例如:

map.addLayer(layer)

地图可以拥有多个Layer:

Map
├── BaseLayer
├── RoadLayer
├── BuildingLayer
├── RiverLayer
└── POILayer

获取Layer:

const layers = map.getLayers()

删除Layer:

map.removeLayer(layer)

因此:

Map
 ↓
管理Layer集合

Map与Source

Map通常不会直接管理Source。

它们之间一般是:

Map
 ↓
Layer
 ↓
Source

例如:

Map
 ↓
VectorLayer
 ↓
VectorSource

所以如果需要操作Feature,通常需要:

const source = vectorLayer.getSource()

const features = source.getFeatures()

而不是直接从Map获取Feature。

Map与Feature

Map本身不直接表示Feature。

典型关系:

Map
 ↓
VectorLayer
 ↓
VectorSource
 ↓
Feature
 ↓
Geometry

例如:

Map
└── VectorLayer
    └── VectorSource
        ├── Feature
        │   └── Point
        ├── Feature
        │   └── LineString
        └── Feature
            └── Polygon

Map与Interaction

Interaction负责处理用户与地图之间的交互。

常见Interaction:

DragPan
MouseWheelZoom
DoubleClickZoom
DragZoom
Select
Draw
Modify
Translate

例如添加Draw:

import Draw from 'ol/interaction/Draw.js'

const draw = new Draw({
  source,
  type: 'Point'
})

map.addInteraction(draw)

结构:

Map
 ↓
Interaction
 ↓
用户操作

Map与Control

Control用于提供地图控件。

常见Control:

Zoom
Rotate
FullScreen
ScaleLine
MousePosition
OverviewMap
ZoomSlider

例如:

import ScaleLine from 'ol/control/ScaleLine.js'

const scaleLine = new ScaleLine()

map.addControl(scaleLine)

这样地图上就会出现比例尺。

Map与事件

Map提供大量事件。

例如:

click
singleclick
dblclick
pointermove
pointerdrag
movestart
moveend

最常用的是:

map.on('singleclick', event => {
  console.log(event.coordinate)
})

这里:

event.coordinate

表示地图坐标。

singleclick

singleclick用于监听地图单击。

例如:

map.on('singleclick', event => {
  console.log('点击坐标:', event.coordinate)
})

可以用于:

  • 查询地图位置
  • 点击Feature
  • 获取坐标
  • 弹出信息窗口

click与singleclick

OpenLayers中经常使用:

singleclick

来处理地图单击。

例如:

map.on('singleclick', event => {
  console.log(event.coordinate)
})

如果需要区分双击等交互行为,应根据具体业务选择合适的事件。

pointermove

pointermove用于监听鼠标移动。

例如:

map.on('pointermove', event => {
  console.log(event.coordinate)
})

常用于:

  • 鼠标位置坐标
  • Feature悬停
  • 鼠标样式切换
  • 高亮Feature

例如:

map.on('pointermove', event => {
  const feature = map.forEachFeatureAtPixel(
    event.pixel,
    feature => feature
  )

  if (feature) {
    console.log('悬停Feature:', feature)
  }
})

movestart

当地图开始移动时触发:

map.on('movestart', () => {
  console.log('地图开始移动')
})

例如:

用户拖动地图
 ↓
movestart
 ↓
地图移动

moveend

当地图移动完成后触发:

map.on('moveend', () => {
  console.log('地图移动结束')
})

常用于:

  • 获取当前地图范围
  • 根据地图范围加载数据
  • 更新业务数据
  • 更新URL参数

例如:

map.on('moveend', () => {
  const extent = map.getView().calculateExtent(
    map.getSize()
  )

  console.log(extent)
})

获取View

可以通过Map获取View:

const view = map.getView()

然后:

view.getCenter()
view.getZoom()

例如:

const center = map.getView().getCenter()
const zoom = map.getView().getZoom()

设置地图中心

可以:

map.getView().setCenter([
  12950000,
  4850000
])

地图就会移动到指定位置。

设置地图缩放

可以:

map.getView().setZoom(12)

例如:

当前 zoom = 10
        ↓
setZoom(12)
        ↓
放大地图

动画移动

OpenLayers可以通过View动画改变地图位置。

例如:

map.getView().animate({
  center: [12950000, 4850000],
  zoom: 12,
  duration: 1000
})

这样地图会平滑移动到指定位置。

fit

fit是OpenLayers中非常重要的地图操作。

例如有一个Feature:

const extent = feature.getGeometry().getExtent()

map.getView().fit(extent)

地图会自动调整视图,使Geometry进入当前地图范围。

常用于:

  • 定位Feature
  • 缩放到行政区
  • 缩放到道路
  • 缩放到GeoJSON
  • 显示查询结果

例如:

Feature
 ↓
Geometry
 ↓
Extent
 ↓
View.fit()
 ↓
地图自动定位

Map的size

可以通过:

const size = map.getSize()

获得地图尺寸。

例如:

[
  1200,
  800
]

表示地图容器大约为:

宽度 = 1200
高度 = 800

Map的pixel与coordinate

OpenLayers中经常需要在:

Pixel

和:

Coordinate

之间转换。

例如:

Pixel
 ↓
屏幕位置

Coordinate
 ↓
地图坐标

获取像素:

event.pixel

获取地图坐标:

event.coordinate

Pixel转Coordinate

可以使用:

map.getCoordinateFromPixel(pixel)

例如:

const coordinate = map.getCoordinateFromPixel([
  500,
  300
])

得到地图坐标。

Coordinate转Pixel

反过来:

map.getPixelFromCoordinate(coordinate)

例如:

const pixel = map.getPixelFromCoordinate([
  12950000,
  4850000
])

这样可以获得对应的屏幕像素位置。

Feature点击查询

Map提供:

map.forEachFeatureAtPixel()

例如:

map.on('singleclick', event => {
  map.forEachFeatureAtPixel(
    event.pixel,
    feature => {
      console.log(feature)
    }
  )
})

流程:

用户点击
 ↓
Pixel
 ↓
Map
 ↓
forEachFeatureAtPixel
 ↓
Feature

这是OpenLayers中非常常见的Feature查询方式。

获取地图Extent

地图当前显示范围可以通过View计算:

const extent = map
  .getView()
  .calculateExtent(map.getSize())

得到:

[minX, minY, maxX, maxY]

例如:

[
  12800000,
  4700000,
  13100000,
  5000000
]

可以用于:

  • 空间查询
  • 按范围加载数据
  • 服务请求
  • 地图范围统计

Map的渲染

OpenLayers会根据:

View
Layer
Source
Feature
Geometry

进行地图渲染。

可以理解为:

View
 ↓
确定当前显示范围
 ↓
Layer
 ↓
确定需要显示的内容
 ↓
Source
 ↓
提供数据
 ↓
Feature
 ↓
空间对象
 ↓
Geometry
 ↓
几何形状
 ↓
Renderer
 ↓
屏幕地图

Map的渲染方式

OpenLayers支持多种渲染方式,例如:

Canvas
WebGL

普通VectorLayer通常使用Canvas渲染。

在大规模数据场景下,也可以根据数据类型选择WebGL相关Layer。

因此实际项目中需要根据数据规模选择合适的渲染方式。

Map的渲染更新

当Layer、Source或Feature发生变化时,OpenLayers通常会自动触发重新渲染。

某些情况下也可以:

map.render()

主动请求重新渲染。

例如:

map.render()

可以用于某些自定义渲染场景。

Map的控件管理

获取Controls:

const controls = map.getControls()

添加:

map.addControl(control)

删除:

map.removeControl(control)

因此:

Map
 ↓
Controls

可以统一管理地图上的控制组件。

Map的交互管理

获取Interactions:

const interactions = map.getInteractions()

添加:

map.addInteraction(interaction)

删除:

map.removeInteraction(interaction)

例如:

map.addInteraction(draw)

Map的Layer管理

获取Layers:

const layers = map.getLayers()

添加:

map.addLayer(layer)

删除:

map.removeLayer(layer)

因此Map实际上是整个地图对象的管理中心。

Map的核心方法

开发OpenLayers时,经常使用:

getView()
getLayers()
addLayer()
removeLayer()

getControls()
addControl()
removeControl()

getInteractions()
addInteraction()
removeInteraction()

getSize()

getPixelFromCoordinate()
getCoordinateFromPixel()

forEachFeatureAtPixel()

render()

这些方法基本覆盖了大量日常地图开发需求。

Map的完整结构

一个比较完整的OpenLayers Map可以理解为:

Map
│
├── Target
│   └── HTML地图容器
│
├── View
│   ├── Center
│   ├── Zoom
│   ├── Rotation
│   ├── Projection
│   └── Resolution
│
├── Layers
│   ├── TileLayer
│   │   └── TileSource
│   │
│   ├── VectorLayer
│   │   └── VectorSource
│   │       └── Feature
│   │           └── Geometry
│   │
│   └── VectorTileLayer
│       └── VectorTileSource
│
├── Interactions
│   ├── Select
│   ├── Draw
│   ├── Modify
│   └── DragPan
│
└── Controls
    ├── Zoom
    ├── ScaleLine
    ├── FullScreen
    └── MousePosition

一个实际地图的结构

例如一个城市GIS系统:

Map
│
├── View
│
├── BaseMap
│   └── TileLayer
│
├── GIS Layer
│   ├── 行政区
│   ├── 道路
│   ├── 河流
│   └── 建筑物
│
├── Business Layer
│   ├── 设备
│   ├── 车辆
│   └── POI
│
├── Interaction
│   ├── Select
│   ├── Draw
│   └── Modify
│
└── Controls
    ├── Zoom
    └── ScaleLine

Map与Layer的关系

可以记住:

Map
 ↓
管理Layer

Layer负责:

显示什么

Map负责:

在哪里显示
如何交互
如何管理

Map与View的关系

可以记住:

Map
 ↓
View

View决定:

中心在哪里
缩放多少
旋转多少
使用什么投影

例如:

Map
 ↓
View
 ├── center
 ├── zoom
 ├── rotation
 └── projection

Map与Interaction的关系

可以记住:

Map
 ↓
Interaction
 ↓
用户操作

例如:

用户绘制
 ↓
Draw

用户选择
 ↓
Select

用户编辑
 ↓
Modify

用户拖动地图
 ↓
DragPan

Map与Control的关系

可以记住:

Map
 ↓
Control
 ↓
地图操作组件

例如:

Zoom
 ↓
缩放按钮

ScaleLine
 ↓
比例尺

FullScreen
 ↓
全屏按钮

Map初始化流程

一个典型OpenLayers项目可以按照下面流程初始化:

创建HTML容器
    ↓
创建Layer
    ↓
创建Source
    ↓
加载数据
    ↓
创建View
    ↓
创建Map
    ↓
添加Controls
    ↓
添加Interactions
    ↓
监听地图事件

最终:

HTML
 ↓
Map
 ↓
View + Layer + Interaction + Control
 ↓
WebGIS地图

Vue中使用Map

在Vue项目中,通常需要在组件挂载完成后创建Map。

例如:

import { onMounted, onBeforeUnmount } from 'vue'

let map

onMounted(() => {
  map = new Map({
    target: 'map',
    layers: [
      new TileLayer({
        source: new OSM()
      })
    ],
    view: new View({
      center: [12950000, 4850000],
      zoom: 10
    })
  })
})

模板:

<template>
  <div id="map"></div>
</template>

组件销毁时可以解除地图与容器的关联:

onBeforeUnmount(() => {
  map?.setTarget(undefined)
})

这样可以避免组件销毁后地图实例继续占用DOM容器。

Map开发中的常见问题

地图不显示

首先检查:

1. target是否正确
2. HTML元素是否存在
3. 地图容器是否有高度
4. View是否正确
5. Layer是否正确
6. Source是否正常

地图位置错误

重点检查:

数据坐标系
View坐标系
数据Projection

例如:

EPSG:4326
    ↓
EPSG:3857

是否正确转换。

Feature不显示

检查:

Feature
 ↓
VectorSource
 ↓
VectorLayer
 ↓
Map

是否完整。

Layer不显示

检查:

layer.getVisible()

以及:

layer.getOpacity()
layer.getZIndex()

还需要检查Layer的Zoom和Extent限制。

Map知识结构

可以用下面的结构记忆:

OpenLayers Map
│
├── Target
│
├── View
│   ├── Center
│   ├── Zoom
│   ├── Rotation
│   ├── Projection
│   └── Resolution
│
├── Layers
│   ├── TileLayer
│   ├── VectorLayer
│   ├── ImageLayer
│   └── VectorTileLayer
│
├── Interactions
│   ├── Select
│   ├── Draw
│   ├── Modify
│   └── DragPan
│
├── Controls
│   ├── Zoom
│   ├── ScaleLine
│   └── FullScreen
│
└── Events
    ├── singleclick
    ├── pointermove
    ├── movestart
    └── moveend

总结

Map是OpenLayers中最核心的对象,它代表一个完整的WebGIS地图实例。

最重要的关系是:

Map
│
├── View
├── Layer
├── Interaction
├── Control
└── Event

其中:

Map
 ↓
整个地图

View
 ↓
控制地图视角

Layer
 ↓
组织地图内容

Source
 ↓
提供地图数据

Feature
 ↓
表示空间要素

Geometry
 ↓
表示空间形状

Interaction
 ↓
处理用户交互

Control
 ↓
提供地图控件

可以记住:

Map是OpenLayers地图的核心管理对象,View决定怎么看地图,Layer决定显示什么,Source提供数据,Feature表示空间要素,Geometry表示空间形状,Interaction负责用户操作,Control负责地图控件。

理解Map之后,OpenLayers最核心的一条知识链就是:

Map
 ↓
View
 ↓
Layer
 ↓
Source
 ↓
Feature
 ↓
Geometry

再结合:

Interaction
Control
Event

就基本构成了OpenLayers WebGIS开发的核心体系。

🧰

相关工具

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