Vue-CLI and Leaflet (10)加载 Esri ArcGIS Map Service

这篇文章讲介绍如何在 Vue-CLI 工程中添加 ESRI 的地图服务 ArcGIS Map Service ,相信需要用到 ArcGIS Map Service 的对 ArcGIS 平台都有一些了解。

这里我们要是实现加载 ArcGIS Map Service 功能都是基于 leaflet.js 的。除了 leaflet.js 这库,还需要用到 esri 提供的 基于 leaflet.js 实现的拓展库esri-leaflet。配合到 leaflet.js 生态,esri 在其基础上适配了大量的用于支持 ArcGIS 平台相关的服务的功能,如 地图服务,地理处理服务,地理编码,路径分析等功能。

这篇文章主要介绍是,其中关于 ArcGIS 各类图层的加载方式,包括: 底图图层动态图层影像图层瓦片图层要素图层聚合图层热力图

二、代码实现

1)代码总览与相关引用说明

所有的代码都是基于第一篇文章中建好的目录结构中添加的,请参考  Vue-CLI and Leaflet (1):显示一个地图

下面对功能的实现,请参考我的 GitHub 源码。

首先在工程中安装esri-leaflet

npm install esri-leaflet --save
复制代码

接着,开始依次实现图层添加的功能。

看以下是主视图里面的代码, 其中组件 Layers 是为了方便演示做的图层开关图层开关

// src\views\ArcgisServices.vue

<template>
  <div class="map-container">
    <div id="map-container"></div>
    <Layers
      @basemapLayer="addBaselayer"
      @tiledMapLayer="addTilelayer"
      @dynamicMapLayer="addDynamiclayer"
      @imageMapLayer="addImageLayer"
      @featureLayer="addFeaturelayer"
      @clusterLayer="addClusterLayer"
      @heatMap="addHeatmap"
    ></Layers>
  </div>
</template>

<script>
import Layers from "@/components/Layers.vue";

export default {
  name: "mapView",
  components: { Layers },
  data() {
    return {
      map: null,
      layers: [],
      OSMUrl: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
      tileLayer:
        "https://services.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer",
      featureLayer:
        "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Hurricanes/MapServer/0/",
      dynamicLayer:
        "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Hurricanes/MapServer/",
      imageLayer:
        "https://landsat.arcgis.com/arcgis/rest/services/Landsat/PS/ImageServer"
    };
  },
  async mounted() {
    this.map = this.$utils.map.createMap("map-container", {
      maxZoom: 20
    });
  },
  methods: {
    clearLyrs() {
      this.layers.map(lyr => {
        lyr.remove();
      });
      this.layers = [];
    },
    async addBaselayer() {
      this.clearLyrs();
      this.map.setView([22.302437935904464, 114.17198181152345], 8);
      let layer1 = await this.$utils.map.addEsirBasemap(this.map, "DarkGray");
      let layer2 = await this.$utils.map.addEsirBasemap(
        this.map,
        "TerrainLabels"
      );

      this.layers.push(layer1, layer2);
    },
    async addTilelayer() {
      this.clearLyrs();

      this.map.setView([38.369572, -97.681121], 12);
      let layer = await this.$utils.map.addEsirTiledMapLayer(this.map, {
        url: this.tileLayer,
        maxZoom: 15
      });
      this.layers.push(layer);
    },
    async addDynamiclayer() {
      this.clearLyrs();

      this.map.setView([18.930362, -28.243945], 2);
      let layer = await this.$utils.map.addEsirDynamicMapLayer(this.map, {
        url: this.dynamicLayer
      });
      this.layers.push(layer);
    },
    async addImageLayer() {
      this.clearLyrs();

      let layer = await this.$utils.map.addImageMaplayer(this.map, {
        url: this.imageLayer
      });
      this.layers.push(layer);
    },
    async addFeaturelayer() {
      this.clearLyrs();

      this.map.setView([18.930362, -28.243945], 2);
      let layer = await this.$utils.map.addEsirFeatureLayer(this.map, {
        url: this.featureLayer
      });
      this.layers.push(layer);
    },
    async addClusterLayer() {
      this.clearLyrs();

      let self = this;
      this.map.setView([18.930362, -28.243945], 2);
      let layer1 = await this.$utils.map.addEsirBasemap(this.map, "DarkGray");

      let layer2 = await this.$utils.map.addEsirClusterLayer(this.map, {
        url: this.featureLayer,
        // Cluster Options
        polygonOptions: {
          color: "#2d84c8"
        },
        // Feature Layer Options
        pointToLayer: function(geojson, latlng) {
          return self.$utils.map.createCircleMaker(latlng, 10, {
            color: "#2D84C8"
          });
        }
      });

      this.layers.push(layer1, layer2);
    },
    async addHeatmap() {
      this.clearLyrs();

      this.map.setView([18.930362, -28.243945], 2);
      let layer1 = await this.$utils.map.addEsirBasemap(this.map, "DarkGray");

      let layer2 = await this.$utils.map.addEsriHeatmap(this.map, {
        url: this.featureLayer,
        radius: 12,
        minOpacity: 1,
        blur: 50,
        gradient: { 1: "red", 0.65: "lime", 0: "blue" }
      });
      this.layers.push(layer1, layer2);
    }
  }
};
</script>
<style lang="less">
.map-container {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;

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


复制代码

组件 Layers :

// src\components\Layers.vue

<template>
  <div class="map-layers">
    <ul>
      <li @click="$emit('basemapLayer')">basemapLayer</li>
      <li @click="$emit('tiledMapLayer')">tiledMapLayer</li>
      <li @click="$emit('dynamicMapLayer')">dynamicMapLayer</li>
      <li @click="$emit('imageMapLayer')">imageMapLayer</li>
      <li @click="$emit('featureLayer')">featureLayer</li>
      <li @click="$emit('clusterLayer')">clusterLayer</li>
      <li @click="$emit('heatMap')">heatmap</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "map-layers",
  data() {
    return {
      activeTool: ""
    };
  },
  methods: {
    point() {
      if (this.activeTool !== "point") {
        this.activeTool = "point";
        this.$emit("point");
      } else {
        this.activeTool = "";
        this.$emit("end");
      }
    },
    polyline() {
      if (this.activeTool !== "polyline") {
        this.activeTool = "polyline";
        this.$emit("polyline");
      } else {
        this.activeTool = "";
        this.$emit("end");
      }
    },
    polygon() {
      if (this.activeTool !== "polygon") {
        this.activeTool = "polygon";
        this.$emit("polygon");
      } else {
        this.activeTool = "";
        this.$emit("end");
      }
    }
  }
};
</script>
<style lang="less">
.map-layers {
  position: absolute;
  right: 15px;
  top: 15px;
  z-index: 999;

  height: 36px;
  box-shadow: 0px 0px 50px 2px rgba(0, 0, 0, 0.35);
  background-color: #fff;
  ul {
    display: flex;
    padding: 0;
    margin: 0;
    list-style: none;

    li {
      padding: 0 15px;
      height: 36px;
      font-size: 13px;
      line-height: 36px;
      cursor: pointer;
    }
    li.active {
      background-color: rgb(102, 156, 255);
      color: #fff;
    }
    li:hover {
      background-color: rgb(212, 224, 246);
    }
  }
}
</style>

复制代码

然后需要特别说明一下,esri-leaflet 在地图中的引用方方式, esri-leaflet 没有暴露 default ,所以正确的引用方式如下:

// src\utils\map.js

// 全部引用
import * as esri from "esri-leafle"
// 按需引用
import {
  basemapLayer,
  featureLayer,
  tiledMapLayer,
  dynamicMapLayer,
  imageMapLayer
} from "esri-leaflet";

// 错误引用
import "esri-leafle"
import esri from "esri-leafle"

复制代码

2)底图图层 basemapLayer

basemapLayer 是用于显示 Esri 提供的地图和属性数据, 这些底图覆盖了全世界范围的各个层级,目前的底图一共有十三种,如下:

Streets
Topographic
NationalGeographic
Oceans
Gray
DarkGray
Imagery
ImageryClarity
ImageryFirefly
ShadedRelief
Terrain
USATopo
Physical

此外,还有正针对不同底图提供的文字标注标注图层,可根据需要加载:

  • OceansLabels - Labels to pair with the Oceans basemap
  • GrayLabels - Labels to pair with the Gray basemap
  • DarkGrayLabels - Labels to pair with the DarkGray basemap
  • ImageryLabels - Labels including political boundaries to pair with any Imagery basemap
  • ImageryTransportation - Street map labels for pairing with any Imagery basemap
  • ShadedReliefLabels - Labels for pairing with the ShadedRelief basemap
  • TerrainLabels - Labels for pairing with the Terrain or Physical basemap

在 map.js 中添加底图图层的加载方法:

// src\utils\map.js

......

const addEsirBasemap = async (map, layerName) => {
  return await basemapLayer(layerName).addTo(map);
};

......

复制代码

然后添加 ArcGIS.vue 中添加调用方法:

// src\views\ArcgisServices.vue

......
methods:{
	...
    async addBaselayer() {
      this.clearLyrs();
      this.map.setView([22.302437935904464, 114.17198181152345], 8);
      let layer1 = await this.$utils.map.addEsirBasemap(this.map, "DarkGray");
      let layer2 = await this.$utils.map.addEsirBasemap(
        this.map,
        "TerrainLabels"
      );

      this.layers.push(layer1, layer2);
    },
    ...
}
......

复制代码

这里需要注意,所有 arcgis mapservice 的构造方法都是异步方法,我在使用过程中都是用 async , await 保证能正确图层缓存图层。后面的所有图层都是如此。

调用成功之后看到的效果如下:

3)瓦片图层 tiledMapLayer ,动态图层 dynamicMapLayer , 影像图层 imageMapLayer

因为这三个地图加载方式都非常相似所以就放在一起讲了, 几乎都是一样的 map.js 中添加底图图层的加载方法。

其中 opts 参数是构造图层的初始化参数,根据需要去 esri-lealfet 官网API 文档 上的查询详细描述。

参数中 url 是必须参数,且必须为有效的 ArcGIS Map Service 或 Image Service

// src\utils\map.js

......

const addEsirTiledMapLayer = async (map, opts) => {
  return await tiledMapLayer(opts).addTo(map);
};

const addEsirDynamicMapLayer = async (map, opts) => {
  return await dynamicMapLayer(opts).addTo(map);
};
const addImageMaplayer = async (map, opts) => {
  return await imageMapLayer(opts).addTo(map);
};

......

复制代码
// src\views\ArcgisServices.vue

......
methods:{
	...
	async addTilelayer() {
      this.clearLyrs();

      this.map.setView([38.369572, -97.681121]);
      let layer = await this.$utils.map.addEsirTiledMapLayer(this.map, {
        url: this.tileLayer,
        maxZoom: 15
      });
      this.layers.push(layer);
    },
    async addDynamiclayer() {
      this.clearLyrs();

      this.map.setView([18.930362, -28.243945], 2);
      let layer = await this.$utils.map.addEsirDynamicMapLayer(this.map, {
        url: this.dynamicLayer
      });
      this.layers.push(layer);
    },
    async addImageLayer() {
      this.clearLyrs();

      let layer = await this.$utils.map.addImageMaplayer(this.map, {
        url: this.imageLayer
      });
      this.layers.push(layer);
    },
    ...
}
......

复制代码

如果根据我提供的代码调用成功之后,你可以看到正确的地图显示效果如下

瓦片图层 tiledMapLayer

动态图层 dynamicMapLayer

影像图层 imageMapLayer

, 影像图加载需要一定的时间。

3)要素图层 featureLayer

3.1 直接加载 featureLayer

同样的, opts 参数是构造图层的初始化参数,根据需要去 esri-lealfet 官网API 文档 上的查询详细描述,其中必须参数中必须包含有 url , 且此 url 必须为有效的 ArcGIS Map Service Feature Layer

// src\utils\map.js

......

const addEsirFeatureLayer = async (map, opts) => {
  return await featureLayer(opts).addTo(map);
};

......

复制代码
// src\views\ArcgisServices.vue

......
methods:{
	...    

async addFeaturelayer() {
      this.clearLyrs();
        // 根据每个图层展示内容的位置不同,调整视图中心坐标
      this.map.setView([18.930362, -28.243945], 2);
      let layer = await this.$utils.map.addEsirFeatureLayer(this.map, {
        url: this.featureLayer
      });
      this.layers.push(layer);
    }
  ...
}
.......
复制代码

调用图层加载方法成功你会看到的画面如下(无底图)

3.2 显示原来 featureLayer 配置的底图样式

上一步骤中加载的的图层成果之后,会看到点要素图层的图标是 leaflet 中默认图标。根据 esri-leaflet 的官方描述如果想要加载 图层原本配置好的样式需要用到一个插件 esriLeaflet-render

npm install esri-leaflet-renderers --save
复制代码

然后再再 map.js 中引用,正确成功的引用之后,则会看到的图层样式如下:

// src\utils\map.js
......

// 在 leaflet, esri-leallet 引用成功之后引用
import "esri-leaflet-renderers";
......

复制代码

3.3 自定义图标样式

除了使用默认图标和显示图层所配置的样式之后,feature layer 也提供了自定义图标样式的接口。

构建方法也非常简单 pointToLayer 接口会返回图层中所包含点要素的 geojso n 与 latlng , 根据这个来构造 markercirlcle maker 等点要素并将其作为返回值 return。

这里有一点需要注意,如果引用 3.2 中的插件显示图层配置的样式,自定图标的接口将自动失效。

// for point GeoJSON, Leafet expects custom panes to be returned in pointToLayer()

 featureLayer({
    url: blockPointsUrl,
    pointToLayer: function (geojson, latlng) {
      // return $L.circleMarker(latlng);
      return $L.marker(latlng);
    }
  }).addTo(map);

复制代码

4)聚合图层 cluster

之前的文章有介绍过如何使用 Leaflet.markercluster 来实现点聚合的功能, esri-leaflet 也支持基于 arcgis mapservice 的 featurelayer 来实现点要素图层的聚合功能,而且esri-leaflet的 cluster 图层也是基于 Leaflet.markercluster 这个插件图层实现的。因此,在使用之前需要同时安装 esri-leaflet-cluster , Leaflet.markercluster ,

npm install esri-leaflet-cluster leaflet.markercluster --save
复制代码

但想要在 vue-cli 中使用 esri-leaflet 提供的 cluster 图层,直接按之前的引用方式是无法成功安装官方示例来使用的,我试过很多方法都不行,于是,我看了插件的源码,在源码上做了简单的修改。这里因为篇幅的原因,就不知将修改的源码贴出来了,请项目源码 src\utils\esri-cluster.js 。下面直接讲使用方法。

注意工程中 src\utils\ 新增了这个 esri-cluster.js

// src\utils\map.js
......

import Cluster from "./esri-cluster";

......


const addEsirClusterLayer = async (map, opts) => {
  return await Cluster(opts).addTo(map);
};

......

复制代码
// src\views\ArcgisServices.vue

......
methods:{
	...    

async addFeaturelayer() {
      this.clearLyrs();

      let self = this;
      // 根据每个图层展示内容的位置不同,调整视图中心坐标
      this.map.setView([18.930362, -28.243945], 2);
      let layer1 = await this.$utils.map.addEsirBasemap(this.map, "DarkGray");

      let layer2 = await this.$utils.map.addEsirClusterLayer(this.map, {
        url: this.featureLayer,
        // Cluster Options
        polygonOptions: {
          color: "#2d84c8"
        },
        // Feature Layer Options
        pointToLayer: function(geojson, latlng) {
          return self.$utils.map.createCircleMaker(latlng, 10, {
            color: "#2D84C8"
          });
        }
      });

      this.layers.push(layer1, layer2);
    }
  ...
}
.......
复制代码

调用成功之后看到的正确的显示结果如下。如果有其它需求,可查阅 esri API 文档 cluster-feature-layer

![clusterLayer](D:\Sync Files\我的坚果云\2-文档\Leaflet\assets/clusterLayer.gif)

5)热力图图层 heatmap

跟 clusterLayer非常相似,esri-leaflet的 热力图图层 heatmap 是基于另一个leaflet插件 Leaflet.heat 实现的 esri-leaflet-heatmap ,并且和 esri-leaflet-cluster 一样,在调用这个插件时,按照之前的方式是无法成功的按 esri-leaflet 官方示例代码的方式使用的。因此,同样的处理方法,从插件源码中修改出 src\utils\esri-cluster.js 。使用前先安装依赖插件:

npm install esri-leaflet-heatmap leaflet-heat --save
复制代码

注意工程中 src\utils\ 新增了这个 esri-heatmap.js ,在 map.js 引用

// src\utils\map.js 

......

import Heatmap from "./esri-heatmap";

......

const addEsriHeatmap = async (map, opts) => {
  return await Heatmap(opts).addTo(map);
};

......

复制代码
// src\views\ArcgisServices.vue

......
methods:{
	...    

async addFeaturelayer() {
       this.clearLyrs();

      this.map.setView([18.930362, -28.243945], 2);
      let layer1 = await this.$utils.map.addEsirBasemap(this.map, "DarkGray");

      let layer2 = await this.$utils.map.addEsriHeatmap(this.map, {
        url: this.featureLayer,
        radius: 12,
        minOpacity: 1,
        blur: 50,
        gradient: { 1: "red", 0.65: "lime", 0: "blue" }
      });
      this.layers.push(layer1, layer2);
    }
  ...
}
.......
复制代码

调用成功之后看到的正确的显示结果如下。如果有其它需求,可查阅 Esri API 文档 heatmap-feature-layer

三、总结

除此之外,还有 vectorlayer 但这共功能跟 basemap 类似只能加载 esri 提供的图层资源,且目前仍是 beta 版的因此我觉得暂时没有必要做 更深入的研究。

如果项目使用的 esri arcgis 平台,功能相对很复杂没有三维显示的需求,且又嫌 ArcGIS API for Javascript 太重。 esri-leaflet 配合到 leaflet及其周边生态确实是一个非常好的选择。如果有什么讲的错的地方,请各位指正。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章