OpenLayer
初识OpenLayer
初始化地图渲染
安装依赖
npm i ol
首先准备一个容器用来渲染地图
<div id="map" ref="map" style="width: 100%; height: 100%"/>
导入依赖初始化地图
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
this.map = new Map({
target: "map",
layers: [
new TileLayer({
source: new OSM()
})
],
view: new View({
center: [0, 0],
zoom: 2
})
});
获取当前视图的属性
// 当前层级
this.map.getView().getZoom()
// 当前中心点
this.map.getView().getCenter()
层级切换
在openLayer
当中我们可以发现zoom就是用来控制层级的,所以我们可以直接通过获取View之后直接修改Zoom用来实现层级切换,
this.map.getView().setZoom(10)
但是这样是直接切换层级放大缩小没有动画效果,看起来交互就不是很好,我们可以给这个层级加上一个延时动画,通过view,animate传递一个对象进去用来控制,当然这里不单单只是可以控制zoom、还可以控制其他View里面的属性。
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个点在某个层级之下刚刚好全部展示,这时我们即不知道中心位置,也不知道它到底要放大到什么层级,这个时候可以通过区域定位来实现,也就是地图视图聚焦
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元素当中
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
,可以通过以下代码进行调整对象的布局
import {GeometryLayout} from 'ol/geom/Geometry';
var point = new Point([10, 20, 100]);
point.setLayout(GeometryLayout.XYZ);
绘制颜色填充的点
在Feature对象当中可以给整个对象统一设置样式,通过setStyle(),首先我们给点设置为圆形红色并且半径为6 单位(米)并且给圆的外面加上一个绿色宽度为3的边
marker.setStyle(
new Style({
image: new Circle({
radius: 6,
fill: new Fill({color: "red"}),
stroke: new Stroke({
color: "green",
width: 3
})
})
})
);
绘制多个点
只需要循环创建圆点之后,将圆通过addFeature方法添加进去即可
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表示图标的颜色(对图标进行着色)
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表示文字的描边
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])
把字体也给放大一倍,其余的样式也同理如此进行设置即可。这样就给页面添加了一些交互事件使得页面看起来更加活泼一点。
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对象也就是单个点对象,之后遍历地图层级上所有的点进行相等匹配,如果满足条件则修改其大小、文字,反之还原成原始样式。
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图层,那么这个图层下所有的线都会应用这个样式。
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对象用来判断线是否被选中的事件
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对象,而传给这个对象的值也是多个坐标,坐标会一个个的连起来组成一个面,而绘制多边形只需要塞进去多少个顶点即可
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文件进行加载渲染,
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
import 'ol/ol.css'; // 注意要导入ol对应的样式文件,避免无样式导致控件位置不对而排查问题浪费时间
import {OverviewMap, ZoomSlider} from 'ol/control'
OverviewMap 概览地图
在创建了图层之后再通过OverviewMap控件添加到图层当中,由于所有的控件都是一个个的div元素加到页面上的,所以我们可以轻松的找到对应控件的类名进行样式覆盖
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 | 是否可收起 |
tipLabel | collapseLabel和label的提示文本 |
rotateWithView | 控件视图是否应随主地图视图一起旋转 |
ZoomSlider 缩放滑块
用于缩放的滑块类型的控件。
map.addControl(new ZoomSlider({
// className: 'demo-zoom-slider',
duration: 1000 // 动画持续时间,默认为200 ms
}))
FullScreen 全屏
提供一个按钮,单击该按钮时,地图将填满整个屏幕。 默认情况下,全屏源元素是包含地图视口的元素,除非 通过提供选项进行覆盖。在这种情况下,dom 使用此参数引入的元素将全屏显示。在全屏模式下,将显示一个关闭按钮以退出全屏模式。
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指定一个回调函数用来对经纬度进行格式化处理
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 个按钮的控件,一个用于放大,一个用于缩小。 此控件是地图的默认控件之一。所以即使你未添加任何控件的时候这个控件还是会添加到页面上,那么如何处理掉这个控件呢?
map = new Map({
target: 'map',
layers: [XXXXX],
view: new View(XXXX),
// 在地图上添加控件 默认会加上zoom控件,这里设置为[] 表示不加任何控件上去
controls: [],
})
ZoomToExtent 缩放到范围
一个按钮控件,按下该按钮时,会将地图视图更改为特定的 程度。
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)来更改
map.addControl(new ScaleLine({
bar: true,
steps: 2
}))