初识webGIS库—OpenLayers


前言

GIS 作为获取、存储、分析和管理地理空间数据的重要工具,用 GIS 技术绘制地图比用传统的手工操作或自动制图工具更加灵活。今天给大家分享一个专为 GIS 客户端开发提供的 JavaScript 类库包 — OpenLayers


一、OpenLayers是什么?

OpenLayers 是一个专为 Web GIS 客户端开发提供的 JavaScript 类库包,用于实现标准格式发布的地图数据访问。它的主要作用就是用于展现数据并且提供相应的地图操作工具。官方文档地址:https://openlayers.org/


二、快速开始

1.安装

npm i --save ol
cnpm i --save ol

2.入门使用

2.1 为地图准备一个 div 的容器

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

2.2 绑定 id 初始化一个地图;将图层 Layer 添加到 Map 中,指定数据源服务(url);设置 Map 容器的视图窗口。

initMap() {
  this.map = new Map({
    target: "map", // html中的地图id
    // 图层的基类
    layers: [
      new TileLayer({
        source: new OSM(), //指定了图层的数据来源,图层作用是以一定的样式渲染数据,source则指定数据
      }),
    ],
    view: new View({
      projection: "EPSG:4326", //坐标系,有EPSG:4326和EPSG:3857
      center: [116.23, 39.54], //中心坐标点
      zoom: 10, //默认缩放级别
    }),
  });
},

完整代码

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

<script>
import "ol/ol.css";
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import OSM from "ol/source/OSM";
export default {
  data() {
    return {
      map: null,
    };
  },
  mounted() {
    this.initMap(); //定义一个方法
  },  
  methods: {
    // 初始化地图
    initMap() {
      this.map = new Map({
        target: "map", // html中的地图id
        // 图层的基类(添加图层)
        layers: [
          new TileLayer({
            source: new OSM(), //指定了图层的数据来源,图层作用是以一定的样式渲染数据,source则指定数据
          }),
        ],
        view: new View({
          projection: "EPSG:4326", //坐标系,有EPSG:4326和EPSG:3857
          center: [116.23, 39.54], //中心坐标点
          zoom: 10, //默认缩放级别
        }),
      });
    },
  },
};
</script>

<style>
#map {
  width: 100%;
  height: 100%;
}
</style>

实现效果
在这里插入图片描述


三、配置

在所有配置中,MapLayerSourceViewControlInteractionOpenlayers 的核心配置;下面为大家一一讲解。

1.Map

MapOpenlayers 使用基础,所有的图层、地图控件、地图工具等必须添加到 Map 中才能使用。

1.1 Map 常用的属性、方法和事件

属性:

  • layers 图层
  • view 视图
  • controls 地图控件
  • target 地图容器
  • overlays 叠加图层

例:view 方法的使用

 view: new View({
   projection: "EPSG:4326", 
   center: [116.23, 39.54], 
   zoom: 10,
 }),

方法:

  • addControl(control) 添加控件
  • addInteraction(interaction) 添加交互
  • addLayer(layer) 添加图层
  • addOverlay(overlay) 添加覆盖物
  • dispatchEvent(event) 调度事件并调用所有侦听此类型事件的侦听器
  • on(type, listener) 侦听某种类型的事件
  • getOverlays() 获取所有覆盖物
  • removeOverlay(overlay) 删除指定覆盖物

例:addControl 方法的使用

this.map.addControl(
  new control.OverviewMap({
    collapsed: true,
  })
);

事件:

  • click 地图点击事件
  • dblclick 地图双击事件
  • moveend 地图移动时触发
  • movestart 地图开始移动时触发
  • postrender 地图渲染后触发
  • singleclick 地图单击事件

例:click 事件的使用

this.map.on("click", (evt) => {
  this.mapPointerClick(evt);
});

2.Layer

LayerMap 的核心组成部分,ol 定义了四种基本的图层类型,分别是分别是 Tile(瓦片)Image(图片)Vector(矢量)VectorTile(矢量切片),这四种类有一个共同的基类 Layer,它们的大多数属性和方法都继承自这个类。

2.1 Layer 常用的属性和方法

属性:

  • source 指定了图层的数据来源
  • className 图层各个元素的样式
  • opacity 透明度,默认为 1
  • visible 是否可见,默认是 true
  • zIndex 图层的叠加顺序,默认是 0
  • extent 图层渲染的区域(浏览器窗口中可见的地图区域)
  • minResolution 图层最小分辨率,当 layer 缩放级别小于这个分辨率时,图层隐藏
  • maxResolution 图层最大分辨率
  • minZoom 图层最小缩放级别
  • maxZoo 图层最大缩放级别

例:source 属性的使用

layers: [
  new TileLayer({
    source: new OSM(),
  }),
],

方法:

  • getLayersArray() 拿到所有图层
  • getLayerStatesArray() 拿到所有图层状态
  • getSource() 拿到相应图层的来源
  • getSourceState() 拿到相应图层的来源状态
  • setSource() 设置图层 source 属性,参数为一个 source 对象
  • setMap() 添加 Layer 到 Map,并由 Map 管理

例:getSourceState 方法的使用

this.clusterSource.getSource().forEachFeature((feature) => {
    //执行操作
});

3.Source

Source 就是数据来源和格式。简单理解就是在使用 layers(图层) 时,不同的图层需要传入不同的数据类型才能渲染地图。它们需要的数据格式都是通过 Source 定义好的,我们只需要把现有的数据按照规定传入数据源中即可。

3.1 Source 的数据类型

分类:

  • ol.source.BingMaps Bing 地图图块数据的图层源
  • ol.source.CartoDB CartoDB Maps API 的图层源
  • ol.source.Cluster 聚簇矢量数据
  • ol.source.Vector 提供矢量图层数据
  • ol.source.Image 提供单一图片数据的类型
  • ol.source.ImageCanvas 数据来源是一个 canvas 元素,其中的数据是图片
  • ol.source.ImageMapGuide Mapguide 服务器提供的图片地图数据
  • ol.source.ImageStatic 提供单一的静态图片地图
  • ol.source.ImageVector数据来源是一个 canvas 元素,但是其中的数据是矢量来源
  • ol.source.ImageWMS WMS 服务提供的单一的图片数据
  • ol.source.MapQuest MapQuest 提供的切片数据
  • ol.source.Stamen Stamen 提供的地图切片数据
  • ol.source.Tile 提供被切分为网格切片的图片数据
  • ol.source.TileVector 被切分为网格的矢量数据
  • ol.source.TileDebug 并不从服务器获取数据
  • ol.source.TileImage 提供切分成切片的图片数据
  • ol.source.TileUTFGrid TileJSON 格式 的 UTFGrid 交互数据
  • ol.source.TileJSON TileJSON 格式的切片数据
  • ol.source.TileArcGISRest ArcGIS Rest 服务提供的切片数据
  • ol.source.WMTS WMTS 服务提供的切片数据
  • ol.source.Zoomify Zoomify 格式的切片数据
  • ol.source.OSM OpenStreetMap 提供的切片数据
  • ol.source.XYZ 具有在 URL 模板中定义的一组 XYZ 格式的 URL 的切片数据的图层源

例:通过 layer 使用 source

this.layer = new TileLayer({
    source: new XYZ({
    url: "http://192.168.0.138/OSM/{z}_{x}_{y}.png",
    wrapX: true,
    }),
});

4.View

view 的作用主要是控制地图的交互,例如设置地图的展示位置范围、地图中心位置以及当前地图使用的投影坐标系等等。

4.1 View 常用的属性和方法

属性:

  • center 视图初始化中心点位
  • enableRotation 是否启用旋转
  • constrainRotation 旋转约束;false 意味着没有约束;true 意味着没有约束,但在 0 附近捕捉到 0。数字将旋转限制为该数量的值,就是设置 90 只能旋转 90 度
  • extent 限制视图的范围;可选:[minx, miny, maxx, maxy]
  • constrainOnlyCenter 若为 true,则范围约束将仅适用于视图中心而不是整个范围
  • smoothExtentConstraint 若为 true,范围约束将被平滑地应用,即允许视图稍微超出给定的 extent
  • maxResolution 用于确定分辨率约束的最大分辨率
  • minResolution 用于确定分辨率约束的最小分辨率
  • maxZoom 用于确定分辨率约束的最大缩放级别
  • minZoom 用于确定分辨率约束的最小缩放级别
  • constrainResolution 若为 true,则视图将始终在交互后以最接近的缩放级别进行动画处理;false 表示允许中间缩放级别
  • resolutions决定缩放级别的分辨率
  • zoom 只有 resolution 未定义时使用;缩放级别用于计算视图的初始分辨率
  • rotation 以弧度为单位的视图初始旋转(顺时针旋转,0 表示北)

例:maxZoom 事件的使用

 view: new View({
   // 地图视图
   projection: "EPSG:4326", 
   center: [118.27, 39.71],
   zoom: 10, 
   maxZoom: 20,
 }),

方法:

  • getCenter 获取视图中心,返回地图中心坐标
  • getZoom 获取当前的缩放级别
  • getMaxZoom 获取视图的最大缩放级别
  • getMinZoom 获取视图的最小缩放级别
  • getProjection 获取地图使用的“投影坐标系统”,如 EPSG:3857
  • getMaxResolution 获取视图的最大分辨率
  • getMinResolution 获取视图的最低分辨率
  • getRotation 获取视图旋转
  • getZoomForResolution 获取分辨率的缩放级别
  • setCenter 设置当前视图的中心
  • setConstrainResolution 设置视图是否应允许中间缩放级别
  • setZoom 缩放到特定的缩放级别。任何分辨率限制都将适用
  • setMaxZoom 为视图设置新的最大缩放级别
  • setMinZoom 为视图设置新的最小缩放级别
  • setRotation 设置该视图的旋转角度

例:getZoom 事件的使用

//amend 点击事件
amend() {
  const view = this.map.getView();
  const zoom = view.getZoom();
  view.setZoom(zoom - 1);
  console.log(view, "缩放级别:" + zoom);
},

在这里插入图片描述


5.Control

Control 控件,操作地图相关的工具。

5.1 Control 常用的控件

控件:

  • FullScreen() 全屏控制
  • MousePosition() 坐标拾取控件
  • ScaleLine() 比例尺控件
  • ZoomSlider() 滑块缩放控件
  • OverviewMap() 鹰眼控件
  • Attribution() 官方 LoGo

例:Control 控件的使用

this.map = new Map({
    controls: control.defaults().extend([
      // 根据需要打开关闭即可
      new control.FullScreen(), //全屏控制
      // new control.MousePosition(), //坐标拾取控件
      // new control.ScaleLine(),//比例尺控件
      // new control.ZoomSlider(),//滑块缩放控件
      // new control.OverviewMap(),//鹰眼控件
    ]),
});

6.Interaction

Interaction 也是用来控制地图的,但与控件不同的是 Interaction 是在地图上绘制要素、选择、修改、移动、拉伸等等操作。

6.1 Interaction 常用的交互功能

常用交互功能:

  • doubleclickzoom 双击地图进行缩放
  • draganddrop 以“拖文件到地图中”的交互添加图层
  • dragbox 用于划定一个矩形范围
  • dragpan 拖拽平移地图
  • dragrotateandzoom 拖拽方式进行缩放和旋转地图
  • dragrotate 拖拽方式旋转地图
  • dragzoom 拖拽方式缩放地图
  • draw 绘制地理要素功能
  • keyboardpan 键盘方式平移地图
  • keyboardzoom 键盘方式缩放地图
  • select 选择要素功能
  • modify 更改要素
  • mousewheelzoom 鼠标滚轮缩放功能
  • pinchrotate 手指旋转地图,针对触摸屏
  • pinchzoom 手指进行缩放,针对触摸屏
  • pointer 鼠标的用户自定义事件基类
  • snap 鼠标捕捉,当鼠标距离某个要素一定距离之内,自动吸附到要素

例:dragrotateandzoom 交互的使用

this.map = new Map({
  interactions: defaultInteractions().extend([new DragRotateAndZoom()]),
});

在这里插入图片描述


四、实践

经过以上的学习,相信大家对 OpenLayers 已经有了初步的认识,下面我们来写一个简单的操作,根据接口数据中的经纬度在地图上渲染点位,点击点位展示当前点击点位的详情数据,如下图:

在这里插入图片描述


实现思路

1.引入所需组件;
2.初始化一个地图;
3.循环接口数据拿到所需经纬度,通过 addLayer 方法添加到地图中;
4.添加弹详情出层,通过 id 拿到定义的容器,添加 overlay 到 map;
5.创建点击事件,点击点位后,利用 feature.get 方法拿到数据渲染点位详情即可。

完整代码

<template>
  <div>
    <div class="mapBox" id="map">
      <div id="popup" class="ol-popup">
        <a href="#" id="popup-closer" class="ol-popup-closer"></a>
        <div id="popup-content">
          <div class="detailsBox">
            <div>所属区县:{{mapDataDetail.qx}}</div>
            <div>加油站地址:{{mapDataDetail.jyzdz}}</div>
            <div>加油站负责人:{{mapDataDetail.jyzfzr}}</div>
            <div>联系电话:{{mapDataDetail.lxdh}}</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import areaGeo from "./map.json";
import { Map, View, Feature, ol } from "ol";
import TileLayer from "ol/layer/Tile";
import * as control from "ol/control";
import { Polygon, MultiPolygon } from "ol/geom";
import VectorLayer from "ol/layer/Vector";
import { Cluster, OSM, Vector as VectorSource, XYZ } from "ol/source";
import "ol/ol.css";
import {
  Style,
  Stroke,
  Fill,
  Icon,
  Circle as CircleStyle,
  Text,
} from "ol/style";
import GeoJSON from "ol/format/GeoJSON";
import Point from "ol/geom/Point";
import { toStringHDMS } from "ol/coordinate";
import { toLonLat } from "ol/proj";
import Overlay from "ol/Overlay";
export default {
  data() {
    return {
      mapData: [], //地图点位
      mapPointVectorLayer: null, //地图点位图层控制器
      geoRouteLayer: null, //geo市局边界图层
      clusterSource: null,
      map: null,
      overlay: null, //业务点聚合图层详情图层
      mapDataDetail: {
        qx: "",
        jyzdz: "",
        jyzfzr: "",
        lxdh: "",
        jyzzz: "",
      }, //点位详情数据
    };
  },
  methods: {
    //初始化地图
    initMap() {
      // 地图实例
      this.map = new Map({
        target: "map", // 对应页面里 id 为 map 的元素
        controls: control.defaults().extend([
          // 根据需要打开关闭即可
          new control.FullScreen(), //全屏控制
        ]),
        layers: [
          new TileLayer({
            source: new OSM(),
          }),
        ],
        view: new View({
          // 地图视图
          projection: "EPSG:4326", // 坐标系,有EPSG:4326和EPSG:3857
          center: [118.27, 39.71], // 默认打开的中心坐标
          zoom: 10.1, // 地图缩放级别(打开页面时默认级别)
          minZoom: 10, //最小缩放级别
          maxZoom: 20, //最大缩放级别
        }),
      });
    },
    //模拟请求接口
    mapList() {
      let data = [
        {
          district: "路北区",
          lat: "118.080181",
          lng: "39.588275",
          oilStationAddress: "唐山市路北区安立路",
          oilStationCharge: "吴彦祖",
          oilStationChargePhone: "18888888888",
          oilStationName: "唐山加油站",
          oilStationStatus: "0",
        },
        {
          district: "路北区",
          lat: "118.202271",
          lng: "39.651675",
          oilStationAddress: "唐山市路北区上海路",
          oilStationCharge: "谢霆锋",
          oilStationChargePhone: "18888888888",
          oilStationName: "唐山",
          oilStationStatus: "1",
        },
      ];
      this.mapData = data;
      this.showPoints();
      this.addPopup();
      this.map.on("click", (evt) => {
        this.mapPointerClick(evt);
      });
    },
    //添加弹出层
    addPopup() {
      this.overlay = new Overlay({
        element: document.getElementById("popup"),
        offset: [0, -13],
      });
      //添加 overlay 到 map
      this.map.addOverlay(this.overlay);
      // 关闭弹出层
      var closer = document.getElementById("popup-closer");
      closer.onclick = () => {
        this.overlay.setPosition(undefined);
        closer.blur();
        return false;
      };
    },
    //展示聚合点位
    showPoints() {
      // 聚合图层数据源
      this.clusterSource = new Cluster({
        distance: 100,
        source: new VectorSource({
          features: [],
        }),
      });

      this.mapPointVectorLayer = new VectorLayer({
        source: this.clusterSource,
        style: (feature) => {
          return this.setClusterStyle(feature);
        },
      });

      let features = [];
      this.mapData.forEach((item) => {
        if (item.lng != "" && item.lat != "") {
          let newObj = Object.assign({}, item);
          newObj.geometry = new Point([Number(item.lat), Number(item.lng)]);

          features.push(new Feature(newObj));
        }
      });
      this.clusterSource.getSource().addFeatures(features);
      this.map.addLayer(this.mapPointVectorLayer);
    },
    //鼠标点击事件。若悬浮到设备上,则现在设备的具体信息
    mapPointerClick(evt) {
      let pixel = this.map.getEventPixel(evt.originalEvent);
      let featureMouseOver = this.map.forEachFeatureAtPixel(
        pixel,
        function (feature, layer) {
          return feature;
        }
      );
      let coordinate;
      if (featureMouseOver) {
        if (featureMouseOver.getProperties().features) {
          //聚合情况下
          if (featureMouseOver.getProperties().features.length == 1) {
            console.log(1111);
            //只有一个要素000000000000000
            let f = featureMouseOver.getProperties().features[0]; //获取该要素
            this.clusterSource.getSource().forEachFeature((feature) => {
              if (f == feature) {
                coordinate = [feature.get("lat"), feature.get("lng")];
                this.mapDataDetail.qx = feature.get("district");
                this.mapDataDetail.jyzdz = feature.get("oilStationAddress");
                this.mapDataDetail.jyzfzr = feature.get("oilStationCharge");
                this.mapDataDetail.lxdh = feature.get("oilStationChargePhone");
                this.mapDataDetail.jyzzz = feature.get("oilStationStatus");
                console.log(feature.get("oilStationAddress"));
                this.overlay.setPosition(coordinate);
              }
            });
          }
        }
      }
      if (!coordinate) {
        this.overlay.setPosition(undefined);
      }
    },
    //设置聚合图层的样式
    setClusterStyle(feature) {
      var features = feature.get("features");
      var size = features.length;
      var style;
      if (size == 1) {
        style = [
          new Style({
            image: new Icon({
              src: "https://s1.ax1x.com/2022/07/06/ja5kvQ.png", //详情点位icon
            }),
          }),
        ];
      } else {
        // 样式处理
        style = new Style({
          image: new CircleStyle({
            radius: 18,
            stroke: new Stroke({
              color: "#fff",
            }),
            fill: new Fill({
              color: "#3399CC",
            }),
          }),
          text: new Text({
            font: "15px sans-serif",
            text: size.toString(),
            fill: new Fill({
              color: "#fff",
            }),
          }),
        });
      }
      return style;
    },
    //设置区域
    addArea(geo = []) {
      if (geo.length == 0) {
        return false;
      }
      let features = [];
      var geoserver = geo[0].features;
      geoserver.forEach((g) => {
        let lineData = g;
        let routeFeature = "";
        if (lineData.geometry.type == "MultiPolygon") {
          routeFeature = new Feature({
            geometry: new MultiPolygon(lineData.geometry.coordinates),
          });
        } else if (lineData.geometry.type == "Polygon") {
          routeFeature = new Feature({
            geometry: new Polygon(lineData.geometry.coordinates),
          });
        }
        routeFeature.setStyle(
          new Style({
            fill: new Fill({
              color: "#4e98f444", //填充颜色
            }),
            stroke: new Stroke({
              width: 3, //边界宽度
              color: [71, 137, 227, 1], //边界颜色
            }),
          })
        );
        features.push(routeFeature);
      });

      // 设置图层
      this.geoRouteLayer = new VectorLayer({
        source: new VectorSource({
          features: features,
        }),
      });
      // 添加图层
      this.map.addLayer(this.geoRouteLayer);
    },
  },
  mounted() {
    this.initMap(); //加载默认图层
    this.addArea(areaGeo); //加载边界图层
    this.mapList(); //模拟接口
  },
};
</script>

<style scoped>
.mapBox {
  width: 100%;
  height: 100vh;
}
.detailsBox {
  padding: 14px;
  font-size: 14px;
  color: white;
  box-shadow: 0px 0px 30px 0px #0085f7 inset;
  background: rgba(6, 13, 45, 0.8);
}
</style>

总结

OpebLayers 作为 webGIS 客户端的 JavaScript 包,如今的 OpebLayers 功能也在日益完善,在很多项目中已经可以完全可以替代 ArcGIS API for JavaScript,也希望日后 OpebLayers 的更新能带给用户更丰富的体验。

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水星记_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值