【OpenLayers】WebGIS的二次开发(1)——代码编写

0、 缘起

本文章主要讲述基于OpenLayers的WebGIS的二次开发功能的主要实现过程并附上部分代码,主要有地图缩放控件、导航控件、比例尺控件、鹰眼控件、全屏显示控件、图层探查控件、动画效果控件、地图切换功能、测量功能、图文标注功能、Popup标注功能、视图联动功能、地图定位功能、热点图、统计图以及搜索功能。

本系列的第二篇文章将主要演示实现的效果。

1、 添加地图及地图切换功能

开发网页地图首先就是需要把地图添加到网页上,首先创建一个名称为map_gaode的由瓦片图片组成的图层,这个图层对象通过高德地图的瓦片服务获得了瓦片地图,并设置了名称、可见性、跨域属性、地理坐标系等属性;其次通过view设置了视角的中心和最小缩放级别为2。最后,在具有 ID 为 ‘map’ 的 HTML 容器元素中显示添加的高德瓦片地图,使用 addLayer 方法将map_gaode图层添加到地图中。

```python
```javascript
// 创建高德地图
      map_gaode = new ol.layer.Tile({ title:'高德地图',
        visible:true,
        source: new ol.source.XYZ({ url: `http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}`,
          crossOrigin:"Anonymous",
          projection: 'EPSG:3857',
          wrapX: false,
        }),
        preload: Infinity,
      });
const view=new ol.View({ center: [12728622.637445983, 3576580.6345832883],
        zoom: 10,
        minZoom:2,
      });
var container = document.getElementById('map');
      var map = new ol.Map({ target: container,
        visible: true,
        view:view,
      });
map.addLayer(map_gaode); //高德地图
 

通过这样的方式将多种瓦片添加到了地图中,并将可见性暂时设置为不可见,通过添加在地图中的控件结合Mapswitch.js文件中设计的代码实现了切换地图的功能。

在地图的container容器中,通过添加图层实现添加切换地图的控件,点击控件时Mapswitch.js文件中的Mapswitch函数会响应请求。

ArcGis地图

Mapswitch函数通过传入的参数结合if函数来设置瓦片地图的可见性,当点击ArcGIS地图切换控件时,会把ArcGIS瓦片地图设置为可见,其余瓦片地图设置为不可见。

function Mapswitch(showFlag){ if (showFlag === 1) { map_gaode.setVisible(true);
        map_arcgis.setVisible(false);
        map_tian_road.setVisible(false);
        map_tian_write.setVisible(false);
        map_arcgis_black.setVisible(false);
        map_warm.setVisible(false);
        map_grey.setVisible(true);
    }
    else if (showFlag === 2){ map_tian_write.setVisible(true);
        map_gaode.setVisible(false);
        map_arcgis.setVisible(true);
        map_tian_road.setVisible(false);
        map_arcgis_black.setVisible(false);
        map_tian_write.setZIndex(1);
        map_arcgis.setZIndex(0);
        map_warm.setVisible(false);
        map_grey.setVisible(false);
    }
……
}

同时在ol.css文件中设置了地图切换控件的格式,插入地图的图片方便用户的选择。

.map-btn-arcgis { position: absolute;
  right: 11.5em;
  bottom: 1.5em;
  z-index: 2;
  width: 70px;
  height: 70px;
  border: 2px solid #ffffff;
  background: url("../imgs/arcgis.png");
  background-size: 100% 100%;
  box-shadow: 0 2px 15px rgba(0,0,0,.5);
  cursor: pointer;
}

2、 地图缩放控件

通过以下的代码添加ZoomSlider滑块部件,该控件使用ol.source.OSM()源构造函数创建的OpenStreetMap瓦片图层,并通过调用map.addControl(zoomslider) 添加到地图的控件列表中,并将在地图上显示。

const zoomslider = new ol.control.ZoomSlider({ layers: [
            new ol.layer.Tile({ source: new ol.source.OSM()
            })
          ],
        });
map.addControl(zoomslider); //缩放大小

3、 导航控件

添加导航控件时利用ol.control.ZoomToExtent函数创建了一个新的控制实例,设置了一个地图图层以及一个矩形范围的坐标值,当用户点击该控制按钮时,地图会自动缩放到指定的矩形范围内;再使用map.addControl()方法将其添加到地图中。

//缩放至特定位置
        const zoomtoextent = new ol.control.ZoomToExtent({ layers:[
                  new ol.layer.Tile({ source: new ol.source.OSM()
                  })
          ],
          extent: [
            // 位置矩形的左下角坐标
            12618541, 3636332,
            // 位置矩形的右上角坐标
            12838092, 3517614
          ]
        });
map.addControl(zoomtoextent); //缩放至指定大小

4、 比例尺控件

添加比例尺控件时,新建一个比例尺对象再利用map.addControl()方法将其添加到地图中。

map.addControl(new ol.control.ScaleLine())  //比例尺

5、 鹰眼控件

添加鹰眼图时创建了OverviewMap控件的一个新实例并设置其选项,包括在概览图中显示图层、折叠和展开按钮的标签,以及概览图最初应该折叠还是展开,最后利用map.addControl()方法将其添加到地图中。

const overview = new ol.control.OverviewMap({ layers: [
            new ol.layer.Tile({ visible:true,
              source: new ol.source.TileArcGISRest({ url: ` http://cache1.arcgisonline.cn/arcgis/rest/services/ChinaOnlineCommunity/MapServer`,
              })
            }),
          ],
          collapseLabel: '开', // 鹰眼图展开时按钮标识
          label: '关', // 鹰眼图关闭时按钮标识
          collapsed: false // 设置为false时,鹰眼图在地图初始化时展开
        });
map.addControl(overview); // 添加鹰眼图

6、 全屏显示控件

添加全屏控件时,直接新建一个全屏控件再利用map.addControl()方法将其添加到地图中。

map.addControl(new ol.control.FullScreen());

7、 图层探查控件

图层探查控件的实现主要依赖于对键盘和鼠标的监视,通过监视鼠标的’↑’键和’↓’键实现对探查区域大小的缩放,通过对鼠标位置的监视判断是否需要渲染地图。

地图的裁剪通过在“prerender”事件侦听器和“postrender”事件侦听器来实现。在“prerender”事件侦听器中,代码获取画布的渲染上下文和贴图帧的当前像素比,然后保存当前画布设置并开始绘制路径,如果有鼠标位置它会以指定的半径、线宽和笔划样式围绕鼠标光标绘制一个圆圈。最后,它使用圆形路径剪裁图像层,因此只有圆形内的图像可见。在“postrender”事件侦听器中,代码将恢复保存在“prerender”活动侦听器中的先前画布设置。

var radius = 75;                        // 用于控制图层望远镜的半径
      document.addEventListener('keydown', function(evt){ console.log(100);
        if(evt.keyCode === 38){ console.log(1);
          // 如果用户按下'↑'键,望远镜的半径增加5像素
          radius = Math.min(radius + 5, 150);
          map.render();//重新渲染地图
          evt.preventDefault();//阻止按键默认事件
        }else if(evt.keyCode === 40){ // 如果用户按下'↓'键,望远镜的半径减少5像素
          radius = Math.max(radius - 5, 25);
          map.render();
          evt.preventDefault();
        }
      });
      var mousePosition = null;                       // 用于实时保存鼠标光标所在的像素的位置
      container.addEventListener('mousemove', function(event){ // 每次鼠标移动就获取鼠标光标所在像素相对于地图视口的位置, 并重新渲染一次地图
        mousePosition = map.getEventPixel(event);
        map.render();
      });
      container.addEventListener('mouseout', function(){ // 鼠标移出地图容器,鼠标位置设置为空,并重新渲染一次地图
        mousePosition = null;
        map.render();
      });
      map_grey.on('prerender', function(event){ // 在每次绘制影像图层之前触发
        var ctx = event.context;      // 获取canvase渲染上下文
        var pixelRatio = event.frameState.pixelRatio;  // 获取地图当前帧的像素比率
        ctx.save();                 // 保存当前canvas设置
        ctx.beginPath();            // 开始绘制路径
        if(mousePosition){ // 绘制一个围绕鼠标光标的圆
          ctx.arc(mousePosition[0] * pixelRatio, mousePosition[1] * pixelRatio,
                  radius * pixelRatio, 0, 2 * Math.PI);//x,y为鼠标位置,半径,起始点角度,正好一个圆
          ctx.lineWidth = 5 * pixelRatio;
          ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
          ctx.stroke();
        }
        ctx.clip();   // 使用刚绘制的圆裁剪影像图层,使得影像图层只保留该圆的范围
      });
      // 在每次绘制影像图层之后触发
      map_grey.on('postrender', function(event){ var ctx = event.context;
        ctx.restore();                  // canvas恢复到之前的设置
      })

8、 动画效果控件

旋转定位的动画有两个阶,由两组作为参数传递给view.animate()方法的选项定义。在第一个过程中,视图中心向沈阳变量表示的点移动一半,并将视图旋转180度。放松选项控制旋转的速度。在第二个过程中,视图以沈阳点为中心,旋转360度(即一整圈),具有不同的缓和功能。

总的来说,旋转定位模拟了一种“旋转”效果,将地图视图从初始位置移动到以沈阳点为中心并旋转360度的新位置。

//旋转定位
    document.getElementById('spin').onclick=function(){ var center=view.getCenter();
      view.animate(
              //第一个过程
              { //动画结束时的视图中心,即当前视图中心同目标试图中心连线的中心点
                center:[
                        center[0]+(shenyang[0]-shenyang[0])/2,
                        center[1]+(shenyang[1]-shenyang[1])/2
                ],
                rotation:Math.PI,//动画结束时的旋转角度为180
                easing:ol.easing.easeIn //旋转速度
              },
              //第二个过程
              { center:shenyang,
                rotation:2*Math.PI,
                easing:ol.easing.easeOut
              }
      )
};

弹性伸缩定位效果将触发一个地图动画效果,使地图视图的中心点滑动到北京。动画持续时间为2秒,使用了一个叫做"elastic"的时间变量,它控制了动画的速度和持续时间,使动画具有一种弹性伸缩的效果。

//弹性伸缩效果定位
    document.getElementById('elastic').onclick=function(){ view.animate({ center:beijing, //动画结束时的试图中心
        duration:2000,
        easing:elastic //控制持续时间函数
      });
    };

反弹定位是通过一个点击事件监听器来监听到ID为"bounce"的HTML元素时出发的。当该元素被点击时,它将触发一个地图动画效果,使地图视图的中心点滑动到上。动画持续时间为2秒,使用了一个名为"bounce"的时间变量,它控制了动画的速度和持续时间,使得动画具有一种反弹的效果。这种反弹效果是通过在动画结束前以不同的速度进行减速来实现的,这样使得地图视图看起来像是反弹回来的一样。

//反弹定位
    document.getElementById('bounce').onclick=function(){ view.animate({ center:shanghai,
        duration:2000,
        easing:bounce
      });
    };

飞行定位使地图视图的中心点飞到广州。

动画持续时间为2秒,包括两个不同的部分。第一个部分是将地图视图滑动到广州,持续时间为2秒。第二个部分是将地图缩小一个级别,然后再将其放大到原来的级别,这个过程需要1秒钟。整个动画持续时间为2秒钟。

document.getElementById('fly').onclick=function(){ var duration=2000; //动画持续时间
      var zoom=view.getZoom();
      var parts=2;
      var called=false;
      //动画完成的回调函数
      function callback(complete){ --parts;
        if(called) return;
        if(parts===0||!complete){ called=true;
          done(complete);
        }
      }
      //第一个动画
      view.animate({ center:guangzhou,
        duration:duration
      },callback);
      view.animate({ zoom:zoom-1,
        duration:duration/2
      },{ zoom:zoom,
        duration:duration/2
      },callback);
    };

9、 坐标拾取功能

在mousePositionControl中, coordinateFormat指定显示坐标的格式,使用createStringXY函数来创建一种显示X和Y坐标的格式。projectionSelect和precisionInput变量是HTML元素,用户选择不同的地图投影和不同的显示坐标的精度时返回相应的数值,在projectionSelect的事件监听器函数中,将调用mousePositionControl实例的setProjection方法,以将控件的投影设置为选定值。在precisionInput的事件监听器函数中,使用选定的精度值调用createStringXY函数,并将生成的格式传递给mousePositionControl实例的setCoordinateFormat方法,以更新显示的精度。

const mousePositionControl = new ol.control.MousePosition({ //显示鼠标位置
  coordinateFormat: ol.coordinate.createStringXY(6),
  projection: 'EPSG:4326', //地图投影坐标系(若未设置则输出为默认投影坐标系下的坐标)
  className: 'custom-mouse-position',                 // 控件的CSS类名
  target: document.getElementById('mouse-position'),  //显示鼠标位置信息的目标容器
  undefinedHTML: ' ',  //未定义坐标的标记
});
const projectionSelect = document.getElementById("projection");   // 选取投影的控件
projectionSelect.addEventListener('change', function(event){ // 使mousePositionControl控件的投影与选取投影控件选取的投影一致
  mousePositionControl.setProjection(event.target.value);
});
const precisionInput = document.getElementById('precision');
precisionInput.addEventListener('change', function(event){ // 设置mousePositionControl控件的坐标格式
  const format = ol.coordinate.createStringXY(event.target.valueAsNumber);
  mousePositionControl.setCoordinateFormat(format);
});

10、 测量功能

测量功能设计时实现了一个名为addInteraction的函向地图添加了一个用于测量距离或面积的交互,首先创建一个新的ol.interaction.Draw对象,该对象具有给定的类型、测量层的源和绘制特征的样式。接下来,该函数创建两个工具提示,一个用于显示测量结果,另一个用于在测量过程中显示帮助消息。然后,该函数将drawstart和drawsend事件的监听器添加到ol.interaction.Draw对象中。drawstart监听器为绘制的几何图形创建一个新特征,并将更改事件侦听器绑定到几何图形,更改事件侦听器计算绘制的几何图形的距离或面积,并相应地更新测量工具提示。drawind监听器会更新测量工具提示的样式和位置,并重置绘制的图形和measureTooltipElement变量。最后,该函数将ol.interaction.Draw对象添加到映射中,并返回生成的交互对象。

//测量时根据测量类型实现鼠标交互绘制线或多边形
      function addInteraction(){ // var type=(typeSelect.value === 'area'?'Polygon':'LineString');
        draw = new ol.interaction.Draw({ source:source, //测量图层数据源
          type:/**@type{ol.geom.GeometryType}*/(type), //几何图形类型
          style:new ol.style.Style({//几何图形的样式
            fill:new ol.style.Fill({ //填充样式
              color:'rgba(252,86,49,0.1)'
            }),
            stroke:new ol.style.Stroke({ //边                                线样式
              color:'#d33e1c',
              lineDash:[10,10],
              width:2
            }),
            image:new ol.style.Circle({ //点样式
              radius:5,
              stroke:new ol.style.Stroke({ color:'rgba(0,0,0.7)'
              }),
              fill:new ol.style.Fill({ color:'#d33e1c'
              })
            })
          })
        })
        map.addInteraction(draw);
        createMeasureTooltip();//创建测量工具提示框
        createHelpTooltip(); //创建帮助信息提示框
        var listener;
        //为交互式图形绘制控件队形绑定drawstart事件
        draw.on('drawstart',
                function(evt){ sketch=evt.feature;//绘图要素
                  var tooltipCoord=evt.coordinate;//绘制的坐标@type{ol.Coordinate}
                  //绑定change事件,根据绘制几何图形类型得到测量的距离或面积,并将其添加到测量工具提示框中显示
                  listener=sketch.getGeometry().on('change',function(evt){ var geom=evt.target;//绘制的几何图形
                    var output;
                    if(geom instanceof ol.geom.Polygon){ //输出面积值
                      output=formatArea(/**@type{ol.geom.Polygon}*/(geom));
                      tooltipCoord=geom.getInteriorPoint().getCoordinates();//坐标
                    }
                    else if(geom instanceof ol.geom.LineString){ //输出距离值
                      output=formatLength(/**@type{ol.geom.LineString}*/(geom));
                      tooltipCoord=geom.getLastCoordinate();//坐标
                    }
                    //将测量值添加到测量工具提示框中显示
                    measureTooltipElement.innerHTML=output;
                    //设置测量工具提示框的显示位置
                    measureTooltip.setPosition(tooltipCoord);
                  })
                },this);
        //为交互式图形绘制控件对象绑定drawend事件
        draw.on('drawend',
                function(evt){ //设置测量工具提示框的样式
                  measureTooltipElement.className='tooltip tooltip-static';
                  measureTooltip.setOffset([0,-7]);
                  sketch=null;//置空当前绘图要素对象
                  measureTooltipElement=null;//置空测量工具提示框对象
                  createMeasureTooltip();//重新创建一个测量工具提示框来显示结果
                  ol.Observable.unByKey(listener);
                },this);
      };

formatLength函数用于计算LineString几何体的距离。如果选中大地测量的复选框,则使用ol.sphere.getLength方法计算距离,该方法会考虑地球的曲率;否则将计算顶点之间直线距离的总和为输出的距离。

formatArea函数计算多边形几何体的面积。如果选中大地测量的复选框,则使用ol.sphere.getArea方法计算面积,该方法将考虑地球的曲率;否则,面积将计算为由顶点形成的三角形的面积之和。

//测量距离输出
      var formatLength=function(line){ var length;
        if (geodesicCheckbox.checked){//若使用测地学方法测量
          var sourceProj=map.getView().getProjection();//地图数据源投影坐标系
          length=ol.sphere.getLength(line,{"projection":sourceProj,"radius":6378137});
        }
        else { length=Math.round(line.getLength()*100)/100;//直接得到线的距离
        }
        var output;
        if(length>100){ output=(Math.round(length/1000*100)/100)+''+'km';//以km为单位
        }
        else{ output=(Math.round(length*100)/100)+''+'m';//以m为单位
        }
        return output;//返回线的距离
      };
      //测量面积输出
      var formatArea=function(polygon){ var area;
        if(geodesicCheckbox.checked){ var sourceProj=map.getView().getProjection();//地图数据源投影的坐标系
          //将多边形要素表坐标系投影为EPSG:4326
          var geom = /**@type {ol.geom.Polygon} */(polygon.clone().transform(sourceProj,'EPSG:4326'));
          area=Math.abs(ol.sphere.getArea(geom,{"projectio":sourceProj,"radius":6378137}));//获取面积
        }
        else{ area=polygon.getArea();//直接获取多边形的面积
        }
        var output;
        if(area>10000){ output=(Math.round(area/1000000*100)/100)+''+'km2';//以km为单位
        }
        else{ output=(Math.round(area*100)/100)+''+'m2';//以m为单位
        }
        return output;//返回多边形的面积
      }

11、 图文标注功能

当用户点击地图时,图文标记功能设计的代码会通选中的复选框输需要创建的标注的类型。如果所选标签类型为“vector”,则代码会调用函数addVectorLabel()并传入用户单击的点。如果所选标签类型为“overlay”,则代码会调用函数addOverlayLabel(),并传入用户单击的点。

map.on('click',function(evt){ var type = $("input[name='label']:checked").val();
        var point = evt.coordinate; //鼠标点击的坐标
        if(type==="vector"){ addVectorLabel(point);
        }
        else if(type==="overlay"){ addOverlayLabel(point); //添加新的图文标注
        }
      })

添加矢量点标注是通过addVectorLabel函数来实现,在该函数中首先创建一个新的矢量点要素 ol.Feature,设置它的几何信息和名称属性,然后使用 createLabelStyle 函数为该要素设置样式,并将它添加到数据源 vectorSource 中。

添加图文标注通过addOverlayLabel函数来实现,在该函数中首先创建一个新的 div 元素,设置它的类名和标题属性后将它作为子节点添加到 id 为 label 的元素中。接下来创建两个新的 ol.Overlay 对象,一个用于显示图标,另一个用于显示文本。它们的位置都是指定的坐标点 coordinate,并分别将 div 元素和 a 元素作为其元素属性。最后,将这两个 Overlay 对象添加到地图中。

//添加一个新的标注(矢量层标注)
      function addVectorLabel(coordinate){ //新建一个矢量点要素 ol.Feature
        var newFeature = new ol.Feature({ geometry:new ol.geom.Point(coordinate), //几何信息
            name:'标注点', //名称属性
          });
          newFeature.setStyle(createLabelStyle(newFeature)); //设置矢量点要素的形式
          vectorSource.addFeature(newFeature); //将是亮点要素提娜佳到数据源中
      }
      //添加一个新的图文标注
      function addOverlayLabel(coordinate){ var elementDiv = document.createElement('div'); //新增div图层
        elementDiv.className="marker";
        elementDiv.title="标注点";
        // var elementDiv = document.createElement('div'); // 创建要添加的div元素
        var overlay = document.getElementById('label'); // 获取id为label的元素
        overlay.appendChild(elementDiv); // 将新元素添加为label元素的子节点
        var elementA=document.createElement("a");
        elementA.className="address";
        elementA.href="#";
        setInnerText(elementA,elementDiv.title); //设置文本
        elementDiv.appendChild(elementA);
        var newMarker = new ol.Overlay({ position:coordinate,
          positioning:'center-center',
          element:elementDiv,
          stopEvent:false,
        })
        map.addOverlay(newMarker);
        var newText=new ol.Overlay({ position:coordinate,
          element:elementA,
        })
        map.addOverlay(newText);
      }

12、 Popup标注功能

当用户点击地图时,Popup标注功能设计的代码会通选中的复选框输需要创建的标注的类型。如果所选标签为Popup标注时,则代码读取点击位置的经纬度并调用setPosition函数在该点弹出标注内容框。如果所选标为北京市标注时,则代码会调用addFeatureInfo函数,并传已经设置好的featureInfo,该要素存储了需要输出到屏幕中的标注内容。

map.on('singleclick',function(evt){ var type = $("input[name='label']:checked").val();
  if(type==="popup_beijing"){ content.innerHTML="";
    addFeatureInfo(featureInfo);
    popup.setPosition(featureInfo.geo);
  }
  else if(type==="popup"){ const coordinate = evt.coordinate;
    const lonLat = ol.proj.toLonLat(coordinate);
    const hdms = ol.coordinate.toStringHDMS(lonLat);
    content.innerHTML = '

你点击了这里(经纬度):

' + hdms + ''; popup.setPosition(coordinate); }

13、 视图联动功能

增加视图联动功能时,新增加了一个map对象,并使用了getView函数将map1的视角和map设置为相同的视角。

const map1 = new ol.Map({ //右侧容器
        target: 'map1', //地图容器的id
        visible: true,
        view:map.getView(), //设置为主图的视图对象
      });

当用户单击并开始拖动元素时,会调用startDragging函数,,使用e.preventDefault函数阻止事件的默认行为。然后将startX设置为拖动开始时鼠标光标的x坐标,将startWidth设置为map元素左窗格的宽度。

然后该函数将两个事件监听器添加到documentElement中。mousemove事件由doDrag函数处理,该函数计算鼠标光标的当前x坐标和初始startX之间的差值。然后,它通过将此差异添加到初始startWidth来设置左侧窗格的宽度。

当用户停止拖动元素时,会调用stopDragging函数。它从documentElement中删除mousemove和mouseup事件侦听器。

function startDragging(e) { e.preventDefault();
    startX = e.clientX;
    startWidth = parseInt(document.defaultView.getComputedStyle(document.querySelector('.map')).width, 10000);
    document.documentElement.addEventListener('mousemove', doDrag, false);
    document.documentElement.addEventListener('mouseup', stopDragging, false);
}
function doDrag(e) { const diff = e.clientX - startX;
    document.querySelector('.left-pane').style.width = (startWidth + diff) + 'px';
}
function stopDragging(e) { document.documentElement.removeEventListener('mousemove', doDrag, false);
    document.documentElement.removeEventListener('mouseup', stopDragging, false);
}

14、 地图定位功能

地图定位功能首先通过创建一个ol.Geolocation对象,设置了投影参考系并读取了精度,还创建了一个feature对象,当触发“change:accurcyGeometry”事件时,该特征将随精度几何图形更新。最后程序通过飞行动画跳转到用户的位置。

var geolocation=new ol.Geolocation({ //设置投影参考系
          projection:map.getView().getProjection(),
          //追踪参数
          trackingOptions:{ maximumAge:10000, //最大周期
            enableHighAccuracy:true, //启用高精度
            timeout:600000, //超时
          }
        });
geolocation.on('change',function(){ $('#accuracy').text(geolocation.getAccuracy()+'[m]');
          $('#altitude').text(geolocation.getAltitude()+'[m]');
          $('#altitudeAccuracy').text(geolocation.getAltitudeAccuracy()+'[m]');
          $('#heading').text(geolocation.getHeading()+'[m]');
          $('#speed').text(geolocation.getSpeed()+'[m]');
        });
        //精准模式定位点要素
        var accuracyFeature=new ol.Feature();
        geolocation.on('change:accuracyGeometry',function(){ accuracyFeature.setGeometry(geolocation.getAccuracyGeometry());
        });
        //定位点要素
        var positionFeature=new ol.Feature();
        positionFeature.setStyle(new ol.style.Style({ image:new ol.style.Circle({ radius:6,
            fill:new ol.style.Fill({ color:'#3399CC',
            }),
            stroke:new ol.style.Stroke({ color:'#fff',
              width:2,
            })
          })
        }));
        //添加定位导航控件的导航位置变更事件处理
        geolocation.on('change:position',function(){ var coordinates=geolocation.getPosition();
          positionFeature.setGeometry(coordinates?new ol.geom.Point(coordinates):null);
          flyLocation(coordinates); //飞行动画模式定位到定位点
        });

15、 热点图

创建热力图主要通过读取输入的点数据并结合半径大小和模糊大小的数值来实现,把数据和半径大小和模糊大小设置为新建的Heatmap的属性来实现。半径大小和模糊大小则是通过函数来得到用户需要的数值,

 var dataVector = new ol.source.Vector({ url: "../data/data.geojson",
          format: new ol.format.GeoJSON()
        });
        //定义热力图图层
        let vector = new ol.layer.Heatmap({ source: dataVector,
          blur: parseInt(blur.value, 10),
          radius: parseInt(radius.value, 10),
        });
let blurHandler = function (){ vector.setBlur(parseInt(blur.value, 10));
        };
        blur.addEventListener("input", blurHandler);
        blur.addEventListener("change", blurHandler);

16、 统计图

用户按下添加统计图的按钮会调用对应的创建统计图的函数,统计图的创建主要用到了echarts这个第三方库,通过在options中设置的标题、数据、x轴、y轴等来进行创建对应的统计图。

function series(){ // based on prepared DOM, initialize echarts instance
  var myChart = echarts.init(document.getElementById('main'));
  // specify chart configuration item and data
  var option = { title: { text: '江苏省GDP'
    },
    tooltip: {},
    legend: { data:['GDP']
    },
    xAxis: { data: ["2015","2016","2017”,“2018","2019","2020","2021"]
    },
    yAxis: { min: 50000,
      max: 120000,
      axisLabel: { margin: 10,  // 刻度标签与轴线之间的距离
      }
    },
    grid: { left: '5%',
      right: '5%',
      top: '5%',
      bottom: '5%',
      containLabel: true
    },
    series: [{ name: 'GDP',
      type: 'bar',
      data: [71255.93,77350.85, 85869.76, 93207.55, 98656.82, 102807.68, 116364.20]
    }]
  };
  // use configuration item and data specified to show chart
  myChart.setOption(option);
}

17、 搜索功能

forEach函数首先对每个 POI 点位的经纬度坐标进行转换,利用transform函数将预先输入的经纬度数据转换为 EPSG:3857 坐标下的数据。然后,对于每个 POI 点位,创建一个 OpenLayers 的 Feature 对象,并将其添加到矢量图层的 FeatureCollection 中。

接着,如果传入的 POI 点位数组不为空,该函数会计算出所有 POI 点位的坐标范围,并将地图视图定位到这个范围的中心位,使用了 OpenLayers 的 ol.extent.boundingExtent方法来获取所有 POI 点位的最小和最大坐标,并将其转换为 EPSG:3857 坐标系。然后,使用 OpenLayers 的 View 类的 fit() 方法来调整地图视图,使其能够显示所有 POI 点。

此处使用的点数据存储在了search.js文件的poiData数组中, 数组中的每个对象包了lon表示POI 点位的经度、lat:表示POI 点位的纬度、name表POI 点位的名称以及type:表示POI 点位的类型。

// 将POI添加到矢量图层上

poiArray.forEach(function (poi) {

const lonLat = [poi.lon, poi.lat];

const coordinates = ol.proj.transform(lonLat, ‘EPSG:4326’, ‘EPSG:3857’); // 转换经纬度坐标为 EPSG:3857 坐标系

var feature = new ol.Feature({

geometry: new ol.geom.Point(coordinates),

name: poi.name,

type:poi.type

});

vectorSource.addFeature(feature);

});

// 将地图视图定位到POI中心

if (poiArray.length > 0) {

var poiCoords = poiArray.map(function (poi) {

return [poi.lon, poi.lat];

});

// 获取所有POI点位的坐标范围的,它将POI点位的坐标数组 poiCoords 作为参数传递给 ol.extent.boundingExtent() 方法,

// 该方法将返回一个由最小和最大坐标组成的数组,这个数组就代表了所有POI点位的坐标范围。该方法可以用于调整地图视图,

// 使所有POI点位都能在地图范围内可见。

var extent = ol.extent.boundingExtent(poiCoords);

var view = map.getView();

//使用view.fit函数将地图视图定位到POI的中心。

var sourceProj = ‘EPSG:4326’; // 原始坐标系为 EPSG:4326

var destProj = ‘EPSG:3857’; // 目标坐标系为 EPSG:3857

var extent3857 = ol.proj.transformExtent(extent, sourceProj, destProj);

view.fit(extent3857, map.getSize(), { maxZoom: 7, minZoom: 7 });

}

}

18、 导出地图

通过html设置的“导出地图”按键调用download.js中的downloadMapImage函数,这个函数该函数首先使用document.querySelector函数来选择包含映射的画布元素;然后再创建一个新的画布元素,尺寸设置为与原始地图画布的尺寸相同;再使用getContex函数来来获取新画布的2D渲染,然后再使用drawImage函数将原始地图画布的内容绘制到新画布上。

最后,使用toDataURL函数将新画布转换为图像文件。创建一个带有下载属性的链接元素,该属性为下载的图像指定所需的文件名和文件扩展名。链接的href属性设置为新画布图像的数据URL。

function downloadMapImage() { // 获取地图的canvas元素
    const mapCanvas = document.querySelector('#map canvas');
    // 创建新的canvas元素
    const downloadCanvas = document.createElement('canvas');
    downloadCanvas.width = mapCanvas.width;
    downloadCanvas.height = mapCanvas.height;
    // 将地图canvas画到新的canvas上
    const context = downloadCanvas.getContext('2d');
    context.drawImage(mapCanvas, 0, 0);
    // 将新的canvas转换为图片文件并下载
    const link = document.createElement('a');
    link.download = 'map.png'; // 修改文件名和扩展名
    link.href = downloadCanvas.toDataURL();
    link.click();
}

19、 军事标绘

军事标绘通过超链接在新的网页上来实现,编写代码时先通过init函数初始化了地图网页,完成了添加网页以及鹰眼图、缩放控件等控件。通过drawArrow函数实现了具体图标的绘制,通过switch来进行判断用户选择的需要绘制的图形样式,通过设置MilStd.MilstdParams对象的样式实现绘制。

function drawArrow(type) { removeInteractions();
  switch (type) { case "SimpleArrow":
      var milParam = new MilStd.MilstdParams({ headHeightFactor: 0.15,
        headWidthFactor: 0.4,
        neckHeightFactor: 0.75,
        neckWidthFactor: 0.1,
        tailWidthFactor: 0.1,
        hasSwallowTail: true,
        swallowTailFactor: 0.1
      });
      drawTool.activate(MilStd.EnumMilstdType.SimpleArrow, milParam, "drawSimpleArrow");
      break;
   ......
  }
};

当绘制结束时,函数将创建一个名为 drawStyle 的新样式对象,该样式对象具有填充、描边和图像属性。填充和描边属性的颜色和宽度值来自于对应的表单输入框的值,如 $(‘#FillClr’).val() 和 $(‘#LinClr’).val()。线条的线帽和连接类型也来自于表单输入框的值,如 $(‘#LinHeadType’).val() 和 $(‘#LinJointType’).val()。线条的虚线也由表单输入框的值定义,如 $(‘#LinDash’).val() 和 $(‘#LinDot’).val()。圆形点的半径由表单输入框的值 $(‘#PntRadius’).val() 定义。最后,样式对象作为参数传递给 OpenLayers 绘图事件的回调函数绘制一个符号化要素。

function onDrawEnd(event) { var drawStyle = new ol.style.Style({ fill: new ol.style.Fill({ color: $('#FillClr').val()
    }),
    stroke: new ol.style.Stroke({ color: $('#LinClr').val(),
      lineCap: $('#LinHeadType').val(),
      lineJoin: $('#LinJointType').val(),
      lineDash: [parseInt($('#LinDash').val()), parseInt($('#LinDot').val())],
      width: parseInt($('#LinWidth').val())
    }),
    image: new ol.style.Circle({ radius: parseInt($('#PntRadius').val()),
      fill: new ol.style.Fill({ color: $('#FillClr').val()
      })
    })
  });

修改样式、移除军标、移动军标都通过具体的函数来实现,修改设置的样式。

20、 卫星云图

在页面中创建一个用于展示卫星云图的dialog,并且在打开页面时利用init函数进行初始化,初始化时首先使用jQuery选择器选中一个ID为"dialog"的元素,并使用jQuery UI库中的dialog方法对其进行初始化。设置modal属性为true,表示创建一个模式对话框。利用autoOpen属性设置默认情况下对话框是隐藏的。设置对话框的高度、宽度、最小宽度和最小高度。当对话框被打开时,使用jQuery UI中的open方法执行一个回调函数,将ID为"wxytIframe"的iframe元素的src属性设置为"newYunTu.htm",从而加载卫星云图功能页面。如果对话框被关闭,使用jQuery UI中的close方法执行另一个回调函数。这个函数将名为"wxyt"的复选框的选中状态设置为false,这样下次打开对话框时它就不会被选中了。

function init() { // 初始化卫星云图对话框 $("#dialog").dialog({ modal: true, // 创建模式对话框 autoOpen: false, //默认隐藏对话框 height: 590, width: 920, minWidth: 920, minHeight: 590, open: function (event, ui) { $("#wxytIframe").attr("src", "newYunTu.htm"); //打开对话框时加载卫星云图功能页面 } , close: function (event, ui) { $('#wxyt').attr('checked', false); //关闭对话框时不选中【卫星云图】功能项 } }); //给卫星云图添加关闭按钮的样式 $(".ui-dialog-titlebar-close").addClass("ui-button"); $(".ui-dialog-titlebar-close").addClass("ui-widget"); $(".ui-dialog-titlebar-close").addClass("ui-state-default"); $(".ui-dialog-titlebar-close").addClass("ui-corner-all"); $(".ui-dialog-titlebar-close").addClass("ui-button-icon-only"); $(".ui-dialog-titlebar-close").append(''); }

同时在yunToFun.js中实现了卫星云图的播放和停止播放,在初始化图片之后,利用startShowCloud函数进行图片播放,首先使用 jQuery 选择器 “$ (”# spinner2").spinner(“value”)" 检索一个名为 “time” 的变量,接下来检查变量 “ytInfoTimer” 是否为 null,如果不是,则清除计时器。然后,它使用 setInterval 函数设置一个新的计时器。setInterval 函数以由 “time” 变量乘以 1000 得到的延迟重复执行一个回调函数。回调函数会检查当前时间控制变量 “timeControl” 是否已经到达幻灯片中图像的数量,并且在需要时将图像向上滚动,同时设置当前选定的图像并切换云图图片。如果所有图像都已经显示过,则计时器将被清除。

function startShowCloud() { var time = $("#spinner2").spinner("value");
    if (ytInfoTimer != null) { clearTimer();
    }
    //设置计时器动态播放
    ytInfoTimer = setInterval(function () { if (timeControl * 20 > 470) { $("#selectImgList").scrollTop(timeControl * 20 - 400);
        }
        if (timeControl != 0) { $(".selectImg ul li:eq(" + (timeControl - 1) + ")").removeClass("a");
        }
        $(".selectImg ul li:eq(" + timeControl + ")").addClass("a");
        if (timeControl < timeShowStrArr.length) { var imgUrl = timeShowStrArr[timeControl++];
            var url = "url(" + imgUrl + ")";
            $('#yuntuImg img').attr('src', imgUrl);//切换云图图片
        } else { timeControl = 0;
            clearTimer();
        }
    }, time * 1000);
}