Skip to content

OpenLayer

初识OpenLayer

初始化地图渲染

安装依赖

bash
npm i ol

首先准备一个容器用来渲染地图

html

<div id="map" ref="map" style="width: 100%; height: 100%"/>

导入依赖初始化地图

js
import 'ol/ol.css';
import OSM from 'ol/source/OSM.js';
import {Map, View} from 'ol';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';

this.map = new Map({
    target: "map",
    layers: [
        new TileLayer({
            source: new XYZ({
                url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg"
            })
        })
    ],
    view: new View({
        center: [116.403218, 39.92372],
        zoom: 0,
        minZoom: 1,
        maxZoom: 12,
        // 视角旋转
        rotation: Math.PI / 5,
        // 视图约束
        extent: [110, 20, 120, 30],
        // 地图坐标系的类型
        projection: "EPSG:4326"
    })
});

其中视图View对象全部可以使用的属性如下

属性名说明
center地图视图的中心点坐标。它是一个包含经度和纬度的数组,例如[116.403218, 39.92372]
zoom地图的缩放级别
minZoom地图的最小缩放级别
maxZoom地图的最大缩放级别
rotation地图视图的旋转角度。它以弧度为单位,例如Math.PI / 5表示逆时针旋转36度
extent地图视图的可见范围。它是一个包含最小经度、最小纬度、最大经度和最大纬度的数组,例如[110, 20, 120, 30]。这个属性定义了地图可见区域的边界
projection地图视图的投影方式。它定义了地图坐标系的类型,例如EPSG:4326表示使用WGS84坐标系
resolutions地图的分辨率数组。它定义了每个缩放级别对应的地图分辨率。数组元素的顺序应该与缩放级别一致
constrainResolution布尔值,表示是否约束地图分辨率。当该属性设置为true时,地图将自动根据可用的分辨率调整缩放级别
enableRotation布尔值,表示是否允许旋转地图。当该属性设置为false时,禁止用户旋转地图视图
enableZoom布尔值,表示是否允许缩放地图。当该属性设置为false时,禁止用户缩放地图视图
enablePan布尔值,表示是否允许平移地图。当该属性设置为false时,禁止用户平移地图视图
smoothExtentConstraint布尔值,表示是否使用平滑约束范围。当该属性设置为true时,地图平移和缩放时会平滑过渡到约束范围内
smoothResolutionConstraint布尔值,表示是否使用平滑约束分辨率。当该属性设置为true时,地图缩放时会平滑过渡到约束分辨率

openLayer当中还提供了一个地图图层OSM

js
this.map = new Map({
    target: "map",
    layers: [
        new TileLayer({
            source: new OSM()
        })
    ],
    view: new View({
        center: [0, 0],
        zoom: 2
    })
});

获取当前视图的属性

js
// 当前层级
this.map.getView().getZoom()
// 当前中心点
this.map.getView().getCenter()

层级切换

openLayer当中我们可以发现zoom就是用来控制层级的,所以我们可以直接通过获取View之后直接修改Zoom用来实现层级切换,

js
this.map.getView().setZoom(10)

但是这样是直接切换层级放大缩小没有动画效果,看起来交互就不是很好,我们可以给这个层级加上一个延时动画,通过view,animate传递一个对象进去用来控制,当然这里不单单只是可以控制zoom、还可以控制其他View里面的属性。

js
const view = this.map.getView();
const zoom = view.getZoom();
const duration = 2000;

view.animate({
    zoom: zoom + 1,
    center: [16.403218, 39.92372],
    rotation: Math.PI / 3,
    duration
});

区域定位

在实际开发过程当中,比方说在地图上展示了20个点,而后我们想刚刚好让这20个点在某个层级之下刚刚好全部展示,这时我们即不知道中心位置,也不知道它到底要放大到什么层级,这个时候可以通过区域定位来实现,也就是地图视图聚焦

js
const bounds = [116, 28, 125, 34];
const width = this.map.getSize()[0];
const height = this.map.getSize()[1];

// 将地图视图聚焦至初始范围
this.map.getView().fit(bounds, {
    size: [width, height],
    padding: [50, 50, 50, 50],
    minResolution: 0,
    duration: 2000,
    easeOut: function (t) {
        return 1 - Math.pow(1 - t, 2);
    }
});

这里的属性配置在下面简单说明一下

属性说明默认值
bounds数组,聚焦的四个顶点
size数组,地图视图的宽度和高度
padding设置了地图视图与边界的间距,单位(px)
minResolution地图视图的最小分辨率
duration数字,表示动画过渡的持续时间,单位(毫秒)undefined
easing函数,表示动画过渡的缓动函数undefined
maxZoom数字,表示最大缩放级别
nearest布尔值,表示是否使用最近的分辨率false
constrainResolution布尔值,表示是否限制分辨率true

openLayer绘制点线

openLayer当中,图层Layer与地图源Source是一对一的关系。当创建了一个图层Layer,相应的需要给图层添加地图源Source,然后将图层Layer添加到地图Map上,就可以得到我们想要的地图了

在Source当中主要包含以下三种Tile、Image、Vector

  • ol.source.Tile对应的是瓦片数据源,现在网页地图服务中,绝大多数都是使用的瓦片地图,而OpenLayers 作为一个WebGIS引擎,理所当然应该支持瓦片。
  • ol.source.Image对应的是一整张图,而不像瓦片那样很多张图,从而无需切片,也可以加载一些地图,适用于一些小场景地图。
  • ol.source.Vector对应的是矢量地图源,点,线,面等等常用的地图元素(Feature),就囊括到这里面了。

绘制点

绘制单个默认点

所以在这一步,我们首先需要创建一个Layer和Source对象,并且把layer加到地图上,最后再往source对象当中添加对应点对象就完成了点的绘制,这里的点(Point)对象需要添加给到source.Vector的Feature元素当中

js
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";

const vectorSource = new VectorSource();
const vectorLayer = new VectorLayer({
    source: vectorSource
});
this.map.addLayer(vectorLayer);

const marker = new Feature({
    geometry: new Point([120, 20])
});
vectorSource.addFeature(marker);

如上代码就添加了一个点到地图上了,在官网可以看到new Point() 构造需要传递两个参数,一个是点的位置,一个是对象的布局,其是一个可选参数,默认参数是ol.geom.GeometryLayout.XY,还可以设置为ol.geom.GeometryLayout.XYZ、ol.geom.GeometryLayout.XYM 或 ol.geom.GeometryLayout.XYZM,可以通过以下代码进行调整对象的布局

js
import {GeometryLayout} from 'ol/geom/Geometry';

var point = new Point([10, 20, 100]);
point.setLayout(GeometryLayout.XYZ);

绘制颜色填充的点

在Feature对象当中可以给整个对象统一设置样式,通过setStyle(),首先我们给点设置为圆形红色并且半径为6 单位(米)并且给圆的外面加上一个绿色宽度为3的边

js
marker.setStyle(
    new Style({
        image: new Circle({
            radius: 6,
            fill: new Fill({color: "red"}),
            stroke: new Stroke({
                color: "green",
                width: 3
            })
        })
    })
);

绘制多个点

只需要循环创建圆点之后,将圆通过addFeature方法添加进去即可

js
const vectorSource = new VectorSource();
const vectorLayer = new VectorLayer({
    source: vectorSource
});
this.map.addLayer(vectorLayer);

for (let i = 0; i < 500; i++) {
    const marker = new Feature({
        geometry: new Point(this.getRandomCoordinate())
    });

    marker.setStyle(
        new Style({
            image: new Circle({
                radius: 6,
                fill: new Fill({color: "red"}),
                stroke: new Stroke({
                    color: "green",
                    width: 3
                })
            })
        })
    );
    vectorSource.addFeature(marker);
}

/**
 * 随机生成经纬度
 */
getRandomCoordinate()
{
    const minLon = -180; // 最小经度
    const maxLon = 180; // 最大经度
    const minLat = -90; // 最小纬度
    const maxLat = 90; // 最大纬度

    const lon = Math.random() * (maxLon - minLon) + minLon;
    const lat = Math.random() * (maxLat - minLat) + minLat;

    return [lon, lat];
}

绘制图标点

绘制图标点只需要给style加上image的相关图标配置即可绘制图标到地图上,如下,这里的anchor表示图标的锚点(图标的中心位置)、opacity表示透明度、scale表示放大缩小层级,src指向对应的图标文件的位置、color表示图标的颜色(对图标进行着色)

js
marker.setStyle(
    new Style({
        image: new Icon({
            anchor: [0.5, 1],
            opacity: 1,
            scale: 1,
            src: require("./icon/point.png"),
            color: 'green'
        })
    })
);

其中对Icon的属性可以参考官方文档:OpenLayers v8.2.0 API - 类:图标

给图标点加上文字

给图标加文字和前面设置图标一样,都是直接给style当中添加属性即可,这里添加的是text属性。这里的fill的color表示文字的填充颜色为白色、font属性可以设置文字大小和字体类型、text表示展示的文字内容、scale是一个数组表示横向( x)和纵向(y)的放大缩小层级、offsetX和offsetY分别表示文本在水平和垂直上的偏移量、最后的stroke表示文字的描边

js
feature.setStyle(
    new Style({
        image: new Icon({
            anchor: [0.5, 1],
            opacity: 1,
            scale: 1,
            src: require("./icon/point.png"),
            color: "green"
        }),
        text: new Text({
            fill: new Fill({
                color: "rgba(255,255,255,0.9)"
            }),
            font: "16px monospace",
            text: "文字",
            scale: [1, 1],
            offsetY: -30,
            stroke: new Stroke({color: "rgba(0,0,0,0.9)", width: 2})
        })
    })
);

这里对Text的属性可以参考官方文档:OpenLayers v8.2.0 API - 类:文本

鼠标移入和鼠标移出点事件监听

实现事件监听

在这里可以在给每一个Feature初始化好了之后通过map的pointermove事件(当鼠标在地图上进行移动时触发事件)用来监听,并且获取当前鼠标移动的位置是否包含了这个Feature对象,之后通过目标的featureTarget(地图监听得到的)和最开始初始化得到的进行对比,如果相同则表示鼠标移入到了当前图标,获取对应的样式scale(放大缩小的层级)设置为2即为放大一倍,不相同表示移出,设置会成原来的样式,

当然同理,这里我简化了样式的设置,如果给图标设置了文字的属性可以通过originalStyle.getText().setScale([2, 2]) 把字体也给放大一倍,其余的样式也同理如此进行设置即可。这样就给页面添加了一些交互事件使得页面看起来更加活泼一点。

js
let source = new VectorSource();
for (let i = 0; i < 10; i++) {
    let feature = new Feature({
        id: "ddss",
        geometry: new Point(this.getRandomCoordinate())
    });
    feature.setStyle(
        new Style({
            image: new Icon({
                scale: 1,
                src: require("./icon/point.png")
            })
        })
    );
    this.map.on("pointermove", function (event) {
        const featureTarget = this.forEachFeatureAtPixel(event.pixel, function (feature) {
            return feature;
        });

        if (feature === featureTarget) {
            // 鼠标进入
            const originalStyle = feature.getStyle();
            originalStyle.getImage().setScale(2);
            feature.setStyle(originalStyle);
        } else {
            // 鼠标移出
            const originalStyle = feature.getStyle();
            originalStyle.getImage().setScale(1);
            feature.setStyle(originalStyle);
        }
    });
    source.addFeature(feature);
}
let layer = new VectorLayer({
    opacity: 1
});
layer.setSource(source);
this.map.addLayer(layer);
上述实现存在的问题 —— n次监听

在这里其实乍一看这样子用来实现事件监听没什么问题,每一个点都给加上了监听事件,而且去页面上测试一下当鼠标移入移出图标的大小确实会发生变化。那问题究竟在哪呢?

大数据量的情况:

在上面只初始化了10个点到页面上,当初始化100个点呢?1000个点呢?10000个点呢?在里面是循环加上的监听事件,而这个监听事件是很消耗性能的,10个点100个点看起来还不卡,但是1000个点就明显会有卡顿了,当鼠标移入后要两三秒后才回放大,当10000个点整上去之后整个页面直接卡死了,这个时候就需要给这段代码进行优化了,如何优化这个呢?很显然只需要把监听事件挪出来,由10000次监听变成1次监听就好了,后面监听完之后再去判断给哪个图标去放大缩小也就只有n次循环的事了。用循环n次+1次监听来替换掉n次监听性能消耗一下子就被降下去了。

解决方案 —— 一次监听+n次循环

在前面去掉那个事件监听,在把图层Layer添加进去之后,再进行监听,实现如下,在移动的时候进行监听,在监听的时候获取到feature对象也就是单个点对象,之后遍历地图层级上所有的点进行相等匹配,如果满足条件则修改其大小、文字,反之还原成原始样式。

js
this.map.addLayer(layer);

this.map.on("pointermove", event => {
    const featureTarget = this.map.forEachFeatureAtPixel(event.pixel, feature => {
        return feature;
    });

    source.getFeatures().forEach(feature => {
        const originalStyle = feature.getStyle();
        if (feature === featureTarget) {
            originalStyle.getImage().setScale(2);
            originalStyle.getText().setScale([1, 1]);
            featureTarget.setStyle(originalStyle);
        } else {
            console.log("鼠标移出");
            originalStyle.getImage().setScale(1);
            originalStyle.getText().setScale([0, 0]);
            feature.setStyle(originalStyle);
        }
    });
});

绘制线

绘制单个默认线

绘制线和绘制点是一样一样的,就是将点对象换成线对象即可。这里的LineString就是线对象。实例化线对象和点对象也是一样,入参分别是一个坐标和一个布局。之后就是创建一个Source把线的Feature添加进去,再创建一个Layer添加到Source里面,最后将Layer线图层添加给到Map即可。这个的LineString对象的坐标可以添加多个,表示多个点连成的线。

let featureLine = new Feature({
  geometry: new LineString([
    [120,20],
    [130,22],
    [135,26]
  ])
});
let source = new VectorSource();
source.addFeature(featureLine);
let layer = new VectorLayer({ opacity: 1 });
layer.setSource(source);
this.map.addLayer(layer);

添加样式线

和前面给点添加样式一样,这里只需要给线加上Style也就是给线加上了样式,这里还可以直接把style加给Layer图层,那么这个图层下所有的线都会应用这个样式。

js
const vectorLayer = new VectorLayer({
    source: new VectorSource(),
    style: new Style({
        stroke: new Stroke({
            color: "red",
            width: 2
        })
    })
});

这里的stroke对象可以参考官网:OpenLayers v8.2.0 API - 类:Stroke

给线添加文字

同2.2一样把text属性添加到Style当中去,但是这里设置的样式是layer的,所以字是在图层上的,如果你需要吧字添加到线上面去,就需要在得到线的Feature对象featureLine,给他重新设置样式

text: new Text({
	// 这里就省略了,和给点加的text属性一致
});

lineFeature.setStyle(
  new Style({
    stroke: new Stroke({...省略}),
    text: new Text({
      fill: new Fill({...省略})
  })
);

选中线

在进行选中线的操作和上面选中点是一样的,可以直接沿用前面的方法进行操作。但是这里还可以通过Select对象用来判断线是否被选中的事件

js
import {Select} from 'ol/interaction'
import {pointerMove} from 'ol/events/condition';

const interaction = new Select({
    condition: pointerMove, // 设置条件为鼠标移动
    layers: [vectorLayer], // 设置监听的图层
    style: function () {
        const style = lineFeature.getStyle();

        // 修改线的样式
        const stroke = style.getStroke();
        stroke.setWidth(5);

        // 修改文字样式
        const text = style.getText();
        text.setScale([1, 1]);

        // 返回新的样式
        return new Style({
            stroke,
            text
        });
    }
});

// 将交互对象添加到地图上
this.map.addInteraction(interaction);

这里的Select对象用于选择矢量特征的交互。默认情况下,所选功能包括 样式不同,因此这种交互可用于视觉突出显示, 以及为其他操作选择功能。同样的在这里对于上面鼠标移入点事件也可以通过该方法进行实现,这里就不做说明了。

属性说明
condition设置监听事件,默认是singleClick单击事件,还可以设置以下事件
pointerMove:鼠标移动时触发事件。 singleClick:鼠标单击时触发事件。 dblclick:鼠标双击时触发事件。 pointerDown:鼠标按下时触发事件。 pointerUp:鼠标释放时触发事件。 pointerEnter:鼠标进入图层时触发事件。 pointerLeave:鼠标离开图层时触发事件。 pointerDrag:鼠标拖动时触发事件。
layers从中选择要素的图层列表
style所选要素的样式,未设置则使用默认样式

这里的Select对象可以参考官网:OpenLayers v8.2.0 API - 类:Select

多边形&GeoJson

绘制多边形

在绘制多边形和前面绘制线有异曲同工之妙,多边形本质上就是由多个点组成的线然后连接组成的面,这个面就是最终的结果,那么这里使用到的是Polygon对象,而传给这个对象的值也是多个坐标,坐标会一个个的连起来组成一个面,而绘制多边形只需要塞进去多少个顶点即可

js
const vectorSource = new VectorSource();
const vectorLayer = new VectorLayer({
    source: vectorSource
});
this.map.addLayer(vectorLayer);
const coordinates = [
    this.getRandomSmallCoordinate(),
    this.getRandomSmallCoordinate(),
    this.getRandomSmallCoordinate(),
    this.getRandomSmallCoordinate()
];
const polygonGeometry = new Polygon([coordinates]);

const polygonFeature = new Feature(polygonGeometry);

polygonFeature.setStyle(
    new Style({
        stroke: new Stroke({
            color: "red",
            width: 2
        }),
        fill: new Fill({
            color: "rgba(255,255,0,0.7)"
        })
    })
);

vectorSource.addFeature(polygonFeature);

绘制geoJson数据

在这里可以通过 GeoJSON 读取 GeoJSON 格式读取和写入数据的要素格式,在Echart 当中渲染地图也是使用这种数据格式的,那么这样的话就可以获取对应的geojson文件来把对应的地图渲染到地图上。

这里用到的json文件可以去网站上【https://datav.aliyun.com/portal/school/atlas/area_selector】进行下载,这里使用一个json文件进行加载渲染,

js
let features = new GeoJSON().readFeatures(require('./mapJson/changsha.json'));
var vectorSource = new VectorSource({features: features});
let lineLayer = new VectorLayer({
    id: item.id,
    name: "hunan border",
    opacity: 1,
    zIndex: 1,
    source: vectorSource
});
this.map.addLayer(lineLayer);

控件

控件是一个可见的小部件,其 DOM 元素位于 屏幕。它们可以涉及用户输入(按钮),也可以仅供参考; 位置是使用 CSS 确定的。默认情况下,它们位于 容器,但可以使用 任何外部 DOM 元素。

其中ol/control是所有控件的基类,这里直接在vue3里面来实现了,前面的文章是基于vue2实现的,后续有时间的话会将其更新到vue3

js
import 'ol/ol.css'; // 注意要导入ol对应的样式文件,避免无样式导致控件位置不对而排查问题浪费时间
import {OverviewMap, ZoomSlider} from 'ol/control'

OverviewMap 概览地图

在创建了图层之后再通过OverviewMap控件添加到图层当中,由于所有的控件都是一个个的div元素加到页面上的,所以我们可以轻松的找到对应控件的类名进行样式覆盖

js
  const overviewMapControl = new OverviewMap({
    layers: [
        new TileLayer({
            source: new XYZ({
                url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg'
            })
        })
    ],
    // Unicode 
    collapseLabel: '\u00AB', // 收起概览地图时的标签
    label: '\u00BB', // 展开概览地图时的标签
    collapsed: false, // true为收起来
    collapsible: true, // 是否可收起
    tipLabel: '这是一个提示'
})
map.addControl(overviewMapControl)

这里对OverviewMap 控件的属性进行简要说明

属性说明
className类名,默认类名是ol-overviewmap。使用了该属性将替换该类名,要考虑到新类名的样式
layers概览的图层,默认和初始化地图的layer保持一致即可
collapseLabel收起概览地图时的标签(都是Unicode编码的字符)
label展开概览地图时的标签
collapsed是否可收起
tipLabelcollapseLabel和label的提示文本
rotateWithView控件视图是否应随主地图视图一起旋转

ZoomSlider 缩放滑块

用于缩放的滑块类型的控件。

  map.addControl(new ZoomSlider({
    // className: 'demo-zoom-slider',
    duration: 1000	// 动画持续时间,默认为200 ms
  }))

FullScreen 全屏

提供一个按钮,单击该按钮时,地图将填满整个屏幕。 默认情况下,全屏源元素是包含地图视口的元素,除非 通过提供选项进行覆盖。在这种情况下,dom 使用此参数引入的元素将全屏显示。在全屏模式下,将显示一个关闭按钮以退出全屏模式。

js
  map.addControl(new FullScreen({
    label: '\u0046', // 默认展示为F
    labelActive: '\u0047', // 全屏时展示为G
    activeClassName: 'text-blue', // 全屏时展示G的css类名
    inactiveClassName: 'text-red', // 默认展示F的css类名
    tipLabel: '全屏 -- 文本' // 按钮的tip
}))

MousePosition 鼠标位置

用于显示鼠标光标的 2D 坐标的控件。默认情况下,这些 位于视图投影中,但可以位于任何受支持的投影中。 默认情况下,该控件显示在地图的右上角,但 可以使用 CSS 选择器(ol-mouse-position)进行更改。

其中coordinateFormat指定一个回调函数用来对经纬度进行格式化处理

js
  map.addControl(new MousePosition(
    {
        coordinateFormat: function (coordinate: any) {
            const [x, y] = coordinate
            return x.toFixed(2) + ' , ' + y.toFixed(2);
        },
        placeholder: '等待鼠标移入', // 当鼠标不在地图上时展示这个
        projection: 'EPSG:4326',
    }
))

Zoom 缩放

具有 2 个按钮的控件,一个用于放大,一个用于缩小。 此控件是地图的默认控件之一。所以即使你未添加任何控件的时候这个控件还是会添加到页面上,那么如何处理掉这个控件呢?

js
  map = new Map({
    target: 'map',
    layers: [XXXXX],
    view: new View(XXXX),
    // 在地图上添加控件 默认会加上zoom控件,这里设置为[] 表示不加任何控件上去
    controls: [],
})

ZoomToExtent 缩放到范围

一个按钮控件,按下该按钮时,会将地图视图更改为特定的 程度。

js
  map.addControl(new ZoomToExtent({
    label: '\u0057',
    tipLabel: '缩放到特定等级',
    // minX, minY, maxX, maxY
    extent: [115, 20, 120, 30]
}))

ScaleLine 比例尺

针对视窗显示粗略 y 轴距离的控件。默认情况下,比例线将显示在地图的左下角。可以通过使用 CSS 选择器(ol-scale-line、ol-scale-bar)来更改

js
  map.addControl(new ScaleLine({
    bar: true,
    steps: 2
}))

By Modify.