本文主要用于记录个人openlayers学习心得体会和代码,主要功能有:定位用户当前城市、地址搜索、行政区高亮、图形绘制、添加标注和聚类、实时交通路况显示。
1 定位用户所在城市
1.1 获取用户所在城市
请求高德定位数据,等待位置信息的获取。注意:这种方法无法获取用户精确位置,只能获取一个大概的地理范围rectangle
请求定位的url:`https://restapi.amap.com/v3/ip?key=${高德web端Key}`
const url =`https://restapi.amap.com/v3/ip?key=${this.gaodeApiKey}`;
try{
const response = await fetch(url);//获取Promise
const data = await response.json();//从Promise中解析json数据
if(data.status ==='1'){ //表示获取数据成功
this.city = data.city;
return data;
}else{
console.log('获取用户位置失败',data.info);
}
}catch(error){ //处理错误信息
console.log('Error fetching location',error);
}
请求结果如下:
1.2 计算视图中心点
根据用户的地理范围rectangle,计算rectangle的中心,用于地图视图的中心。
rectangle要处理之后才能获得经纬度数值,"x1,y1;x2,y2",字符串split两次,第一次split";"把一个长字符串切割成两个短字符串,["x1,y1","x2,y2"],第二次split","得到["x1","y1","x2","y2"],接着把数组里面的字符串逐个转换成Number[x1,y1,x2,y2]。拿到这些数据之后就可以计算中心点
代码如下:
getCenterOfRectangle(coordinatesString){//这个rectangle类型是string 按照;和,分割开一个个数值
const points = coordinatesString.split(';');
console.log(points);
console.log(points[0][0]);//只能打印出1,还要继续切割
const coordinates = points.map(point=>{
const temp = point.split(',');//.map(Number);//Number是JS的一个内置函数 把字符串转换为数值
return temp.map(Number);
})
console.log(coordinates);
const minX= coordinates[0][0];
const minY= coordinates[0][1];
const maxX= coordinates[1][0];
const maxY= coordinates[1][1];
const centerX = (minX+maxX)/2;
const centerY = (minY+maxY)/2;
console.log(centerX,centerY);
return [centerX,centerY];
}
1.3 地图初始化
地图初始化的View中心点用成刚刚计算的经纬度。
用View.animate实现缓慢放大的用户当前位置的效果。
async initMap(){
const locationData = await this.getUserLocation();//记得加上await,接收Promise
const view = new View({
center:this.getCenterOfRectangle(locationData.rectangle),
projection:'EPSG:4326',
zoom:8
})
const gaodelayer = new TileLayer({//用高德矢量底图url
source: new XYZ({
url:'http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
})
})
this.map = new Map({
target:this.$refs.mapContainer,
view,
layers:[gaodelayer]
})
view.animate({zoom: 11,duration:2000});//做了一个动画效果,缓慢放大到用户当前位置
}
1.4 html和css设置
<div class = 'top-bar'>
<span>当前城市:{{ city }}</span>
</div>
.top-bar{
height:70px;
position:fixed;
top:0;
left:0;
width: 100%;
background-color: rgb(167, 234, 204,0.7);
color:rgb(0, 0, 0);
font-family: '黑体','SimHei', sans-serif;
font-size: 19px;
padding:10px;/*内边距为10px 确保内容与边框之间有一定的空间 */
display:flex;/*使用Flexbox布局,使按钮水平排列 */
justify-content:space-around;/*按钮均匀分布在容器中 */
align-items: center;/*在垂直方向居中对齐子元素 */
z-index: 1000;/*设置元素的堆叠顺序,确保它在其他元素之上显示 */
box-shadow: 0 2px 5px rgba(0,0,0,0.5);/*添加阴影效果,使元素看起来更立体 */
}
2 搜索地址并高亮行政区
效果展示:
除了行政区搜索,也可以搜索并平移至其他地点,不过就没有行政区高亮显示了
2.1 地理编码
接收用户输入的地址,然后用高德地理编码API获取数据,数据里面有地址的经纬度,处理之后更新地图视图。
const url=`https://restapi.amap.com/v3/geocode/geo?address=${this.adress}&output=JSON&key=${this.gaodeApiKey}`;
try{
const response = await fetch(url);
const data = await response.json();
if(data.status ==='1'){//获取到数据
//更新用户的输入地址
//可以让用户不用完整正确输入省级行政区名就能匹配上数据实现行政区高亮
const gaodeAddress = data.geocodes[0].formatted_address;
const gaodeCity = data.geocodes[0].city;
//观察数据结构发现gaodeCity为空的是省,用于判断用户输入的是不是省级数据
if(gaodeCity.length==0){
this.adress = gaodeAddress;//把高德标准的地理地址传入我的adress参数中
}
const extent = await this.hightlightAdress();//这个函数是用于高亮行政区的,并且返回行政区要素的范围,hightlightAdress()函数在下一段放出来
/*计算新视图与当前视图距离的代码*/
//计算新视图和当前视图的距离
const center = data.geocodes[0].location.split(',').map(Number);//map会遍历每一个元素,把数组里面的字符串逐个转换为数值
const currentCenter = this.map.getView().getCenter();
const distance = Math.sqrt(Math.pow(center[0]-currentCenter[0],2)+Math.pow(center[1]-currentCenter[1],2));
//设置距离阈值
const threshold =1;//单位是度
if(!extent){//如果输入的地址不是省/市行政区,视图就直接更新
if(distance<阈值){//如果新视图距当前视图不远
const currentZoom = this.map.getView().getZoom();
let newZoom;
if(currentZoom<=11){
newZoom = currentZoom+5;
}else{
newZoom = 16;
}
this.map.getView().animate({
center:center,
zoom:newZoom,
duration:2500,
easing:olEasing.inAndOut,
padding:[70,0,0,0]
});
}
}else{
this.map.getView().setCenter(center);
this.map.getView().setZoom(11);
}
/*更新地图视图*/
//更新地图视图
if(distance<threshold){
this.map.getView().fit(extent,{//fit也能实现动画效果,用fit是为了让视图适应行政区要素的范围
duration:2500,
padding:[70,0,0,0],
easing:olEasing.inAndOut
})
}else{
this.map.getView().fit(extent,{
padding:[70,0,0,0]
})
}
}
}catch(error){
console.log('Error fetching address',error);
}
2.2 行政区数据获取
通过阿里云地理小工具获取各省市的GeoJSON数据。
2.3 行政区高亮
//用高德地理编码服务的api解析用户输入的地址
async hightlightAdress(){//高亮省/市? 根据用户输入的做一个判断?
const ShengName =['北京市', '天津市', '河北省', '山西省', '内蒙古自治区', '辽宁省', '吉林省', '黑龙江省', '上海市', '江苏省', '浙江省', '安徽省', '福建省', '江西省', '山东省', '河南省', '湖北省', '湖南省', '广东省', '广西壮族自治区', '海南省', '重庆市', '四川省', '贵州省', '云南省', '西藏自治区', '陕西省', '甘肃省', '青海省', '宁夏回族自治区', '新疆维吾尔自治区', '台湾省', '香港特别行政区', '澳门特别行政区'];
let isSheng = false;
ShengName.forEach(async name=>{
if(this.adress.includes(name)){
isSheng = true;
return isSheng;
}
})
if(isSheng){
//获取阿里云行政区JSON数据
const urlShengJSON =await fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json');
const dataSheng = await urlShengJSON.json();
const featuresSheng = dataSheng.features;
return this.FindAdress(featuresSheng);//FindAdress()写在下一段代码
}else{
const responseShi = await fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full_city.json');
const dataShi = await responseShi.json();
const featuresShi = dataShi.features;
return this.FindAdress(featuresShi);
}
}
FindAdress(features){
let AdressFeature;
features.forEach(feature=>{
const name = feature.properties.name;
if(name == this.adress){
console.log('打印feature',feature);
AdressFeature = feature
}
})
if(AdressFeature){
//把之前标记的行政区矢量图形给清除
this.map.getLayers().forEach(layer=>{
if(layer instanceof VectorLayer){
this.map.removeLayer(layer);
}
})
const format = new GeoJSON();
const olFeature = format.readFeature(AdressFeature);//把JSON数据中的feature转换成openlayers能识别的Features
const xingzhengDistrict = new VectorSource({
features:[olFeature]
})
const xingzhengLayer = new VectorLayer({
source:xingzhengDistrict,
style:new Style({
fill:new Fill({color:'rgba(188,0,0,0.2)'}),
stroke:new Stroke({color:'rgba(255,255,0,0.8)',width:2})
})
})
this.map.addLayer(xingzhengLayer);
const extent = olFeature.getGeometry().getExtent();//获取要素的范围
return extent;
}
}
动画效果:如果地址的经纬度和当前经纬度距离相差太远,就没有平移动画效果,地图视图直接切换至新地址。
下面是判断当前视图和新视图距离计算,更新地图视图:
//计算新视图和当前视图的距离
const center = data.geocodes[0].location.split(',').map(Number);//map会遍历每一个元素,把数组里面的字符串逐个转换为数值
const currentCenter = this.map.getView().getCenter();
const distance = Math.sqrt(Math.pow(center[0]-currentCenter[0],2)+Math.pow(center[1]-currentCenter[1],2));
//设置距离阈值
const threshold =1;//单位是度
//更新地图视图
if(distance<threshold){
this.map.getView().fit(extent,{//fit也能实现动画效果,用fit是为了让视图适应行政区要素的范围
duration:2500,
padding:[70,0,0,0],
easing:olEasing.inAndOut
})
}else{
this.map.getView().fit(extent,{
padding:[70,0,0,0]
})
}
2.4 html和css设置
<!--html引入一个样式链接 在输入框上面加上一个搜索图标-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<div class="input-container">
<i class="fas fa-search"></i><!--这个是引入的搜索图标-->
<input type="text" v-model="adress" placeholder="请输入地址" @keydown.enter="moveToAddress"/><!--v-model绑定输入和vue组件的值,双向绑定-->
</div>
.input-container{
left:0px;
display: flex;/*水平排列*/
align-items:center;/*垂直居中*/
position:relative;/*相对定位*/
}
.input-container i{
position:absolute;/*绝对定位*/
left:15px;/*距离左边10px*/
color:#888;/*颜色*/
}
.input-container input{
padding-left: 35px !important;/*距离左边35px 暴力解决*/
font-size:16px;
margin-left:10px;
padding:5px;
width:100%;
height: 35px;
box-sizing:border-box;
border-radius: 4px; /* 设置圆角 */
border: 1px solid #ccc; /* 设置边框 */
}
3 交互图形绘制
效果展示:
3.1 Draw交互事件
Draw是Openlayers中交互类(Interaction)中的一种,在绘制的时候有光标提示。比自己创建要素添加到数据源再添加到图层要方便。
因为还有标记图标的功能,在绘制图形之前需要移除图形标记的监听事件,不要影响图形的绘制。
if (this.hasListener && this.map.clickListener) {
this.map.un('click', this.map.clickListener);
this.hasListener = false;
}
清除绘制解释:定义了全局图层数组,数据源是每次执行绘制的时候重新创建的,数据源添加到全局图层数组中,所以每次清除绘制只需要移除全局图层数组并清空图层(即清除数据源)。
数据源写在函数里面,每次都临时创建三个数据源备用,数据源都添加到全局图层中,所以数据源清除了也没有关系。
if(this.drawTypeSelect=='clearDraw'){
//index是图层的索引
this.vectorLayerDrawArr.forEach((layer,index)=>{
if(layer){
this.map.removeLayer(layer);
this.vectorLayerDrawArr[index] = null;
}
})
return;
}
图形绘制的完整代码如下:
changeDrawType(){
//停止标记
//移除所有监听
//移除现有的点击事件监听器
if (this.hasListener && this.map.clickListener) {
this.map.un('click', this.map.clickListener);
this.hasListener = false;
}
if(this.drawTypeSelect=='clearDraw'){
this.vectorLayerDrawArr.forEach((layer,index)=>{
if(layer){
this.map.removeLayer(layer);
this.vectorLayerDrawArr[index] = null;
}
})
return;
}
//移除现有的Draw交互对象,不要各个Draw交互事件互相干扰,比如说绘制线切换到绘制点的时候绘制点和线互相干扰
this.map.getInteractions().forEach(interaction=>{
if(interaction instanceof Draw){
this.map.removeInteraction(interaction);
}
})
console.log('绘制类型',this.drawTypeSelect);
let i;
if(this.drawTypeSelect=='Point'){
i=0;
}else if(this.drawTypeSelect=='LineString'){
i=1;
}else if(this.drawTypeSelect=='Polygon'){
i=2;
}else if(this.drawTypeSelect=='Freehand'){
i=3;
}
//创建三个数据源备用
const PointSource = new VectorSource()
const LineStringSource = new VectorSource()
const PolygonSource = new VectorSource()
const FreehandSource = new VectorSource()
const SourceArr = [PointSource,LineStringSource,PolygonSource,FreehandSource]
this.vectorLayerDrawArr[i] = new VectorLayer({
source:SourceArr[i],
style:new Style({
//面要素的样式
fill: new Fill({
color:'rgba(255,0,0,0.2)'
}),
//线要素的样式以及面要素的边框
stroke:new Stroke({
color:'red',
width:2
}),
//点要素的样式
image: new CircleStyle({
radius:5,
fill:new Fill({
color:'orange'
}),
stroke:new Stroke({
color:'red',
width:2
})
})
})
})
let drawFeatures;//创建Draw对象
if(this.drawTypeSelect=='Freehand'){
drawFeatures = new Draw({
type:'LineString',
freehand:true,//自由绘制就是线类型加freehand为true
source:SourceArr[i]//将绘制的要素添加到source中
});
}else{
drawFeatures = new Draw({
type:this.drawTypeSelect,
source:SourceArr[i]
});
}
this.map.addInteraction(drawFeatures);//给map添加交互对象Draw
//避免重复添加,因为绘制完切换到其他类型有可能又切换回来,之前已经添加过该图层了
if(!this.map.getLayers().getArray().includes(this.vectorLayerDrawArr[i])){
this.map.addLayer(this.vectorLayerDrawArr[i]);
}
}
3.2 html和css设置
<div class="selectDrawType-container">
<select v-model="drawTypeSelect" @change="changeDrawType">
<option value="" disabled>选择绘制图形</option>
<option value="Point">绘制点</option>
<option value="LineString">绘制线</option>
<option value="Polygon">绘制面</option>
<option value="Freehand">自由绘制</option>
<option value="clearDraw">清除绘制</option>
</select>
</div>
.selectDrawType-container{
position:relative;
align-items:center;/*垂直居中*/
align-items: flex-start; /* 左对齐子元素 */
}
.selectDrawType-container select{
width:155px;
height: 35px;
font-size: 14px; /* 设置字体大小 */
padding: 5px 20px; /* 设置内边距 */
border-radius: 4px; /* 设置圆角 */
outline: none;
/*outline-color: rgba(76, 225, 254, 0);*/
line-height: 30px !important;
}
.selectDrawType-container option{
font-size: 16px; /* 设置字体大小 */
font-family: '黑体','SimHei', sans-serif;
/*outline-color: rgba(76, 225, 254, 0);*/
}
4 图标标记
效果展示:
功能概述:视图zoom放大到13级以上才可以标记图标,有一个视图变化监听事件,一直监听,可以绘制的时候也要监听,如果用户把视图缩小zoom<13的话又不可以添加图标。
用Cluster类聚合距离近的图标。Cluster是数据源类中的Cluster类。
const clusterSource = new Cluster({
distance:40,//单位是像素
source:this.sourceMap[type],
})
4.1 全局图层/数据源
定义全局图层和全局数据源很方便。三种标记分别对应三种数据源和图层,方便主要体现在:1.退出图标可以直接清除数据源和移除图层;2.用户图标切换之后切换回来原来标记的图标不会被清除;3.可以存储标记的图标。
如何获取全局对象里面的图层?获取对象里面的值需要Object.values(对象).forEach()
layerMap:{
'充电站':new VectorLayer(),
'公交站':new VectorLayer(),
'停车场':new VectorLayer(),
},
//错误写法,layer不是一个数组,是一个对象
this.layerMap.forEach((layer)=>{
if(layer){
this.map.removeLayer(layer);
}
})
//正确写法1
Object.values(this.layerMap).forEach(layer=>{
if(layer){
this.map.remove(layer)
}
})
//正确写法2
for(const key in this.layerMap){
const layer = this.layerMap[key]
if(layer){
this.map.remove(layer)
}
}
4.2 监听视图缩放变化
zoom>=13的时候才可以添加图标
setupZoomListener(){
if(this.zoomListenerAdded) return;//避免重复添加监听
//添加视图zoom监听
const view = this.map.getView();
this.zoomListener =()=>{//zoomListener是函数
const zoom = view.getZoom();
if(zoom<13){
this.showZoomAlertBiaoji = true;//弹窗提示用户当前缩放级别小于13,无法标记
setTimeout(()=>{
this.showZoomAlertBiaoji = false;
},1000);
//监听到zoom小于13,移除点击事件,不可以(再)标记
if(this.hasListener && this.map.clickListener){
this.map.un('click',this.map.clickListener);//移除点击事件 点击事件解绑
this.hasListener =false;//hasListener为false,表示没有监听点击事件
}
}else{//如果zoom大于13,且存在待处理的标记类型,则恢复触发标记功能
if(!this.hasListener && this.pendingBiaojiSelect){//hasListener等添加了点击事件的时候为true
this.biaojiSelect = this.pendingBiaojiSelect;//将保存的 pending 选择赋值回去 → 恢复用户选择的标记类型。
this.pendingBiaojiSelect = null;
this.biaojiType();//恢复标记功能
}
}
};
//当视图变化的时候就执行zoomListener这个函数,只有在销毁前移除监听
view.on('change:resolution',this.zoomListener);
this.zoomListenerAdded = true;
}
4.3 标记点位与聚合图层的构建
在进行标记操作前,需要先清除之前可能存在的绘图交互,以避免冲突。接着,为地图视图添加缩放级别监听器(该监听器的实现详见上一节),用于动态判断用户当前是否具备标记权限。当地图缩放级别低于指定阈值(如13)时,将弹出提示,阻止继续标记。
如果用户选择“退出标记”,则应立即移除当前地图中的所有标记图层,并清空对应的数据源。同时,还需注销缩放监听器,彻底关闭标记模式。
当标记功能正式启用时,应先移除之前的点击事件监听器,以防止事件重复绑定。为了便于后续移除,新添加的点击事件应以具名函数方式注册,并保存引用。在该点击事件中,用户每点击地图一次,都会在对应位置添加一个带图标的点要素,加入当前数据源中。
随后,使用 Cluster
聚合类对该数据源进行要素聚合处理,得到聚合后的数据源。添加聚合图层前,需检查并移除之前可能已存在的同类图层,以防重复渲染。然后创建新的 VectorLayer
,将聚合结果作为数据源,并设置图层样式:
-
若聚合内仅包含一个要素,则直接使用其原本样式(图标)。
-
若聚合包含多个要素,则显示为一个彩色圆形气泡,并在中心展示聚合数量。
最后,将该图层添加至地图,实现可视化标记聚合效果。
biaojiType(){
//停止绘制图形
this.map.getInteractions().forEach(interaction=>{
if(interaction instanceof Draw){
this.map.removeInteraction(interaction);
}
})
//保存标记类型 记住用户当前想标记的类型
this.pendingBiaojiSelect = this.biaojiSelect;
const type = this.biaojiSelect;
//添加视图缩放级别监听器
if(!this.zoomListenerAdded){
this.setupZoomListener();
}
if(this.map.getView().getZoom()<13){
console.log('当前缩放级别小于13,无法标记');
this.showZoomAlertBiaoji = true;
setTimeout(()=>{
this.showZoomAlertBiaoji = false;
},1000);
return;
}
//退出标记:移除图层、清除数据、注销监听器
if(this.biaojiSelect=='退出标记'){
//地图上多个矢量图层的数据源都移除了,不包括交通数据
// 移除所有标记图层
Object.values(this.layerMap).forEach(layer=>{
if(layer){
this.map.removeLayer(layer);
}
})
Object.values(this.sourceMap).forEach(source=>{
if(source){
source.clear();
}
})
//移除视图zoom监听
if(this.zoomListenerAdded){
this.map.getView().un('change:resolution',this.zoomListener);
this.zoomListener = null;
this.zoomListenerAdded = false;
}
this.pendingBiaojiSelect = null;
return;
}
const srcArr=['./public/充电站.svg','./public/公交站.svg','./public/停车场.svg']
let i=0;
if(this.biaojiSelect=='公交站'){
i=1;
}else if(this.biaojiSelect=='停车场'){
i=2;
}
//移除所有监听
// 移除现有的点击事件监听器,避免干扰新图标绘制
if (this.hasListener && this.map.clickListener) {
this.map.un('click', this.map.clickListener);
this.hasListener = false;
}
//给点击事件赋予名字,并且赋予this.map.clickListener属性,在其他方法也能调用。为了能够移除这个点击事件.
this.map.clickListener = (event)=>{
const coordinate = event.coordinate;
const feature = new Feature({
geometry:new Point(coordinate),
});
feature.setStyle(new Style({
image: new Icon({
src:srcArr[i],
scale:0.2
})
}))
this.sourceMap[type].addFeature(feature);
}
this.map.on('click',this.map.clickListener);
this.hasListener = true;
const clusterSource = new Cluster({
distance:40,//单位是像素
source:this.sourceMap[type],
})
const colorArr = ['rgba(255,0,0,0.2)','rgba(0,255,0,0.2)','rgba(0,0,255,0.2)'];
//移除掉之前添加的图层,避免重复添加
if(this.layerMap[type]){
this.map.removeLayer(this.layerMap[type]);
}
this.layerMap[type] = new VectorLayer({
properties:{
name:'biaoji'
},
source:clusterSource,
//设置单个图标样式和聚类样式
style: function(feature){//feature是聚类后的聚类要素 这个函数返回new Style
const features = feature.get('features')//features 属性是一个数组,包含了所有聚合到一起的原始要素
if(features.length === 1){//表示这个聚类只有一个要素,等价于没聚合
// 如果聚合内只有一个要素,使用它自己定义的样式(比如图标)
return features[0].getStyle() //直接使用该要素自己设置的样式
}
// 否则,返回一个聚合样式:圆圈 + 数字文本表示聚合数量
return new Style({
image: new CircleStyle({
radius:25,
fill:new Fill({
color: colorArr[i]
}),
stroke: new Stroke({
color:'#fff',
width:2
})
}),
text: new Text({
text:features.length.toString(),// 显示聚合数量
font:'16px Arial, sans-serif',
fill:new Fill({
color:'#fff'
}),
stroke: new Stroke({
color:'#000',
width:1
})
})
})
}
})
this.map.addLayer(this.layerMap[type]);
}
4.4 html和css设置
<div class="biaozhu-container">
<select v-model="biaojiSelect" @change="biaojiType">
<option value="" disabled>选择标记类型</option>
<option value="充电站">充电站</option>
<option value="公交站">公交站</option>
<option value="停车场">停车场</option>
<option value="退出标记">退出标记</option>
</select>
</div>
.biaozhu-container{
position:relative;
align-items:center;/*垂直居中*/
align-items: flex-start; /* 左对齐子元素 */
}
.biaozhu-container select{
width:155px;
height: 35px;
font-size: 14px; /* 设置字体大小 */
padding: 5px 20px; /* 设置内边距 */
border-radius: 4px; /* 设置圆角 */
outline: none;
/*outline-color: rgba(76, 225, 254, 0);*/
/*line-height: 30px !important;*/
}
.biaozhu-container option{
font-size: 16px; /* 设置字体大小 */
font-family: '黑体','SimHei', sans-serif;
/*outline-color: rgba(76, 225, 254, 0);*/
}
5 高德实时路况图层加载
async initgaodeMap(){
await this.loadGaodeMapScript();
const locationData = await this.getUserLocation();
//创建高德地图实例
this.gaodeMap = new AMap.Map(this.$refs.gaodemapContainer,{
resizeEnable:true,
center:this.getCenterOfRectangle(locationData.rectangle),
zoom:11
});
if(this.gaodeMap){
//添加实时交通图层
const trafficLayer = new AMap.TileLayer.Traffic({
zIndex:10
})
trafficLayer.setMap(this.gaodeMap);
}
}
6 高德实时路况数据获取
因为加载高德实时路况图层就要用高德的API创建地图实例,之前用Openlayers实现的功能用不了。于是集成高德路况API获取路况数据,然后再渲染到地图上。不过只能获取矩形框对角线不超过10公里的数据。(还能获取圆形区域的数据,或者是指定查询某条路的路况数据)
代码解释:①首先计算当前矩形视图对角线的距离,不超过10公里的话就请求高德路况数据;②接着创建线要素,用经纬度坐标确定LineString几何形状,再加上属性信息(路名,拥堵情况,通行速度);③根据拥堵情况设置要素样式,然后把要素添加到数据源上,再把数据源添加到图层渲染到地图上。
async fetchTrafficData(){
//计算当前视图的范围
const view = this.map.getView();
const extent = view.calculateExtent(this.map.getSize());
console.log(extent[0],extent[1],extent[2],extent[3]);
const minX = extent[0];
const minY = extent[1];
const maxX = extent[2];
const maxY = extent[3];
//计算对角线长度
const diagonalLength = Math.sqrt(Math.pow(minX-maxX,2)+Math.pow(minY-maxY,2));
console.log('对角线长度:',diagonalLength);
//将对角线长度转换为10km
const earthRadiusKM = 6371;//地球平均半径,单位km
const distance = diagonalLength*(Math.PI/180)*earthRadiusKM;
console.log('距离km:',distance);
if(distance>10){
console.log('距离大于10km,不获取交通数据');
this.showZoomAlert = true;
setTimeout(()=>{
this.showZoomAlert = false;
},1000);
}else{
//获取到交通数据
//矩形对角线不能超过10公里,计算距离,确定zoom级别
const url=`https://restapi.amap.com/v3/traffic/status/rectangle?rectangle=${extent[0]},${extent[1]};${extent[2]},${extent[3]}&output=json&extensions=all&key=${this.gaodeApiKey}&level=6`;
try{
const response = await fetch(url);
const data = await response.json();
console.log('打印traffic data',data);
if(data.status=='1'){
console.log('打印traffic data',data);
const roads = data.trafficinfo.roads;
const roadsFeatures = roads.map(road=>{
const coordinates = road.polyline.split(';').map(point=>{
const temp = point.split(',');
return temp.map(Number);
})
const lineString = new LineString(coordinates);
const feature = new Feature({
geometry:lineString,
properties:{
name:road.name,
status:road.status,
direction:road.direction,
angle:road.angle,
lcodes:road.lcodes,
speed:road.speed,
}
})
return feature;
})
roadsFeatures.forEach(feature=>{
console.log(feature.getProperties().properties.status)
feature.setStyle(this.GetRoadStyle(feature.getProperties().properties.status));
})
console.log('打印roadsFeatures打印roadsFeatures',roadsFeatures);
const roadsSource = new VectorSource({
features:roadsFeatures
})
const roadLayer = new VectorLayer({
source:roadsSource
})
this.map.addLayer(roadLayer);
}
}catch(error){
console.log('Error fetching traffic data',error);
}
}
}