需求
用户点击行政区划,加载对应区域的GeoJSON文件在地图上显示。鼠标悬浮到GeoJSON内部区域的时候,高亮显示鼠标悬浮的区域。
实现过程
-
默认显示地图,点击行政区划使用
new ol.source.Vector加载GeoJSON文件,然后通过new ol.layer.Vector创建图层,最后使用map.addLayer将GeoJSON的图层添加到地图上。其中,加载GeoJSON文件可使用openlayers内置的函数,也可以自己通过fetch或xhr请求GeoJSON数据,再作为参数传递给ol.format.GeoJSON().readFeatures()。 -
鼠标悬浮高亮的效果有两种实现方式:
-
通过
map.on('pointermove', () => {}给地图添加鼠标移动事件,在事件处理函数中,根据鼠标的经纬度信息,通过map.forEachFeatureAtPixel(e.pixel, () => {})获取feature,调用feature.setStyle()对feature进行样式修改。在修改feature的同时,用变量存储feature作为上一个修改的feature。调用feature.setStyle()对feature时,如果上一个feaure存在,将其恢复到默认的样式,进而达到鼠标进去下一个区域时,上一个区域恢复原貌的效果。 -
通过
ol.interaction.Select实现,可以通过condition设置触发方式(点击、鼠标悬浮、Alt+点击)。通过select.on('select', (event) => {})处理鼠标悬浮的逻辑,使用map.addInteraction(select)添加到地图上。这种方式的好处是不需要自己处理鼠标离开区域的逻辑,可通过event.selected获取到鼠标悬浮的feature,通过event.deselected获取到鼠标离开的feature。
-
知识点
-
ol.events.condition.pointerMove鼠标移动。 -
ol.events.condition.altclick鼠标左键+Alt。 -
ol.events.condition.click鼠标点击 -
ol.events.condition.singleclick也是点击,区别是click鼠标单击地图触发的事件(鼠标单击多少次地图,该事件就触发多少次)。singleclick鼠标单击地图触发的事件(鼠标若在250ms内连续单击地图,则不会触发该事件)。 -
ol.style.Style样式,有geometry、fill、image、renderer、stroke配置等。 -
ol.source.Vector主要用于为矢量图层ol.layer.Vector类提供具体的数据来源。它可以用于直接组织或读取矢量数据(如点、线、面等常用的地图元素,即Feature)。用此方法加载GeoJSON有两种方式,(1)指定url和格式,ol.source.Vector自己去加载。(2)xhr或fetch请求到GeoJSON数据,使用ol.format.GeoJSON().readFeatures读取GeoJSON,再将数据传给ol.layer.Vector。-
const vectorLayerGeo = new ol.layer.Vector({ source: new ol.source.Vector({ url: `./420000.json`, format: new ol.format.GeoJSON() }), style: defaultStyle }); -
const sourceJsonData = new ol.source.Vector({ features: new ol.format.GeoJSON().readFeatures(geojsonData, { featureProjection: 'EPSG:3857' }) }); const vectorLayerGeo = new ol.layer.Vector({ source: sourceJsonData, style: defaultStyle });
-
-
source.on('featuresloadend', ()=> {},加载GeoJSON时,可通过featuresloadend事件监测加载的状态。加载完成后,能够获取GeoJSON中的center字段,根据center的经纬度,调整地图中心点到GeoJSON的中心。 -
map.getView().animate({center: ol.proj.fromLonLat(center)})可以调整地图的中心点到某个经纬度。可以通过duration设置animate的时长,设置为0表示没有动画效果。 -
ol.proj.fromLonLat用于将经纬度坐标转换为当前地图投影的坐标。它将经度和纬度值作为参数,并返回转换后的坐标数组。 -
map.un('click', func)解绑通过map.on()绑定的事件。 -
map.removeInteraction()删除地图上添加的交互。 -
GeoJSON文件可通过高德的开放平台获取 高德开放平台获取GeoJSON。
代码HTML+CSS+JS
-
由于要使用
xhr或者fetch加载geojson文件,所以需要以服务的方式访问html文件,电脑安装了node的情况下,推荐安装anywhere(npm i anywhere -g),在任意文件夹内命令行执行anywhere 端口即可以服务的形式访问这个文件夹。或者使用vscode等编译器自带的服务。- 特别注意:监听
geojson内部的鼠标事件时,一定要给Style设置fill属性设置,否则监听的不是整体的geojson平面区域,仅仅边线。
- 特别注意:监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>openlayers加载GeoJson</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/ol.min.css" integrity="sha512-bc9nJM5uKHN+wK7rtqMnzlGicwJBWR11SIDFJlYBe5fVOwjHGtXX8KMyYZ4sMgSL0CoUjo4GYgIBucOtqX/RUQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style type="text/css">
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body,
html {
width: 100%;
height: 100%;
margin: 0;
font-family: "Microsoft YaHei"
}
#map {
width: 100vw;
height: 100vh;
}
.orgs {
display: grid;
grid-template-columns: repeat(2, 50%);
grid-template-rows: repeat(2, 50%);
}
#info {
position: fixed;
right: 10px;
top: 10px;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
width: 350px;
padding: 10px;
text-align: center;
border-radius: 5px;
padding-bottom: 20px;
}
.orgs button {
font-size: 18px;
border: none;
padding: 12px 20px;
border-radius: 4px;
color: #fff;
background-color: #409eff;
border-color: #409eff;
cursor: pointer;
border: 1px solid #dcdfe6;
}
.orgs button:hover {
background-color: rgba(7, 193, 96, 0.8);
}
.orgs button.active {
background-color: #07c160;
}
.hoverMethod,
.loadMethod {
margin-bottom: 10px;
display: flex;
}
.hoverMethod div,
.loadMethod div {
text-align: center;
width: 150px;
cursor: pointer;
height: 45px;
line-height: 45px;
border-radius: 5px;
}
.hoverMethod .active,
.loadMethod .active {
background-color: rgba(0, 255, 168, 0.7);
}
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<div id="map"></div>
<div id="info">
<div class="loadMethod">
<div :class="{active: loadType === 1}" @click='loadType = 1' title="使用openlayers提供的ol.source.Vector加载GeoJSON文件">
加载JSON方式一
</div>
<div :class="{active: loadType === 2}" @click='loadType = 2' title="fetch加载geojson文件">
加载JSON方式二
</div>
</div>
<div class="hoverMethod">
<div :class="{active: hoverType === 1}" @click='changeColorBySelect' title="使用 ol.interaction.Select,实现鼠标悬浮区域变色">
鼠标悬浮方式一
</div>
<div :class="{active: hoverType === 2}" @click='changeColorByPointerMove' title="通过鼠标移动事件,实现鼠标悬浮区域变色">
鼠标悬浮方式二
</div>
</div>
<div class="orgs">
<button v-for='area in areaData' :key='area.code' type='button' @click='handleClickProvience(area.code)' :class='{active: currentCode == area.code}'>{{area.name}}</button>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/dist/ol.min.js" integrity="sha512-+nvfloZUX7awRy1yslYBsicmHKh/qFW5w79+AiGiNcbewg0nBy7AS4G3+aK/Rm+eGPOKlO3tLuVphMxFXeKeOQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.14/vue.global.prod.min.js" integrity="sha512-huEQFMCpBzGkSDSPVAeQFMfvWuQJWs09DslYxQ1xHeaCGQlBiky9KKZuXX7zfb0ytmgvfpTIKKAmlCZT94TAlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
const { createApp } = Vue;
// 悬浮样式
const hoverStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(7, 193, 96, 0.5)'
}),
stroke: new ol.style.Stroke({
color: '#07c160',
width: 2
})
});
// 默认样式
const defaultStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.8)'
}),
stroke: new ol.style.Stroke({
color: '#3399CC',
width: 2
})
});
// 错误的默认样式,使用这个默认样式鼠标事件只在geojson的边线上触发
const errorDefaultStyle = new ol.style.Style({
// fill: new ol.style.Fill({
// color: 'rgba(255, 255, 255, 0.4)'
// }),
stroke: new ol.style.Stroke({
color: '#3399CC',
width: 2
})
});
const vm = createApp({
data() {
return {
map: {}, // 地图实例
areaData: [{
code: '420000_full',
name: '湖北省'
}, {
code: '430000_full',
name: '湖南省'
}, {
code: '130000_full',
name: '河北省'
} ,{
code: '410000_full',
name: '河南省'
}],
currentCode: '', // 当前选中的省份
loadType: 1, // 加载JSON数据的方式
hoverType: 2, // 鼠标悬浮处理方式
prevLayer: null, // 上一个加载的省份图层
select: null // select交互实例
}
},
methods: {
// 初始化地图
initMap() {
// 高德地图瓦片地址
const amapVectorLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
})
});
this.map = new ol.Map({
target: 'map',
layers: [amapVectorLayer],
view: new ol.View({
center: ol.proj.fromLonLat([105.481992, 36.777646]),
zoom: 5
})
});
// 绑定地图点击事件,打印经纬度
this.map.on('click', (e) => {
const point = ol.proj.transform(e.coordinate, 'EPSG:3857', 'EPSG:4326')
console.log(`经度:${point[0].toFixed(6)} 纬度:${point[1].toFixed(6)}`);
});
},
// 点击省份加载json文件
handleClickProvience (code) {
this.currentCode = code;
// 如果上一个json图层存在,先将其删除
this.prevLayer && this.map.removeLayer(this.prevLayer);
if (this.loadType === 1) {
// 指定json的url和格式,ol自己去加载
console.log('指定json的url和格式,ol自己去加载');
this.olLoadJson();
} else {
// 请求json数据,ol读取
console.log('请求json数据,ol读取');
this.featchLoadJson();
}
},
// ol自己去加载json文件
olLoadJson () {
// 加载资源
const source = new ol.source.Vector({
url: `./json/${this.currentCode}.json`,
format: new ol.format.GeoJSON()
});
// 监听加载geojson文件进度
source.on('featuresloadend', (event) => {
// 获取第一个featrue的中心并跳转
this.goToCenterByLonLat(this.prevLayer);
});
// 创建图层,获取geojson文件,根目录下放一个json文件夹存放geojson文件
this.prevLayer = new ol.layer.Vector({
source: source,
style: defaultStyle
});
// 将图层添加到地图中
this.map.addLayer(this.prevLayer);
},
// featch加载json
async featchLoadJson () {
// 请求数据 根目录下放一个json文件夹存放geojson文件
const response = await fetch(`./json/${this.currentCode}.json`);
const json = await response.json();
// 读取json
const sourceJsonData = new ol.source.Vector({
features: new ol.format.GeoJSON().readFeatures(json, { featureProjection: 'EPSG:3857' })
});
// 创建图层
this.prevLayer = new ol.layer.Vector({
source: sourceJsonData,
style: defaultStyle
});
// 获取第一个featrue的中心并跳转
this.goToCenterByLonLat(this.prevLayer);
// 将图层添加到地图中
this.map.addLayer(this.prevLayer);
},
// 使用 ol.interaction.Select,实现鼠标悬浮区域变色
changeColorBySelect () {
this.hoverType = 1;
// 使用 ol.interaction.Select时,将鼠标移动事件解绑
this.map.un('pointermove', this.pointermoveFunc);
// 添加交互
this.select = new ol.interaction.Select({
condition: ol.events.condition.pointerMove
});
// 处理悬浮更改区域颜色
this.select.on('select', function(event) {
console.log('使用 ol.interaction.Select,实现鼠标悬浮区域变色');
if (event.selected.length > 0) {
// 鼠标悬浮时应用悬浮样式
event.selected[0].setStyle(hoverStyle);
}
if (event.deselected.length > 0) {
// 鼠标离开时恢复原始样式
event.deselected[0].setStyle(null);
}
});
// 添加到地图
this.map.addInteraction(this.select);
},
// 通过鼠标移动事件,实现鼠标悬浮区域变色
changeColorByPointerMove () {
this.hoverType = 2;
// 使用鼠标移动事件的时候,将select交互删除
this.select && this.map.removeInteraction(this.select);
this.map.on('pointermove', this.pointermoveFunc);
},
// 单独把鼠标移动的事件提出来,便于解绑使用
pointermoveFunc (e) {
console.log('通过鼠标移动事件,实现鼠标悬浮区域变色');
// 根据经纬度获取feature
const feature = this.map.forEachFeatureAtPixel(e.pixel, function(feature) {
return feature;
});
// 获取不到不处理
if (!feature) {
// 如果此时prevFeature存在,说明鼠标移出了geojson区域,将prevFeature样式恢复
this.prevFeature && this.prevFeature.setStyle(defaultStyle);
this.prevFeature = null;
return;
}
// 如果上一个feature和获取到的feature是同一个feature,不处理,防止移动鼠标频繁设置样式
if (this.prevFeature && this.prevFeature.ol_uid === feature.ol_uid) {
return;
}
// 上一个feature存在,将其样式恢复默认
this.prevFeature && this.prevFeature.setStyle(defaultStyle);
// 改变feature填充颜色
feature.setStyle(hoverStyle);
// 存储当前高亮显示的feature
this.prevFeature = feature;
},
// 获取图层的第一个包含center的feature,根据这个center坐标进行跳转
goToCenterByLonLat (vectorLayerGeo) {
const features = vectorLayerGeo.getSource().getFeatures();
for (let i = 0; i < features.length; i++) {
const feature = features[i];
const center = feature.get('center'); // 如果存在center字段的话
if (center) {
this.map.getView().animate({
center: ol.proj.fromLonLat(center),
duration: 500
});
break; // 如果找到一个center字段,就退出循环
}
}
}
},
mounted() {
// 初始化地图
this.initMap();
// 默认是使用 ol.interaction.Select,实现鼠标悬浮区域变色
this.changeColorByPointerMove();
}
}).mount('#app');
</script>
</body>
</html>
参看文章
OpenLayers v8.2.0 API - Class: VectorSource文档