使用openLayers在地图上绘画行政区域并高亮选中的区域

305 阅读7分钟

效果图:

Snipaste_2024-10-17_09-10-23.png

Snipaste_2024-10-17_09-09-00.png

Snipaste_2024-10-17_09-11-53.png

Snipaste_2024-10-22_14-49-37.png

代码实现:

1.通过dataV获取GeoJSON数据

网址:datav.aliyun.com/portal/scho… 通过左侧地图选择范围,在复制右侧JSON AIP地址

 // 1.获取geojson数据 
    // const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/100000_full_city.json" //全国
    // const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/440000_full.json" //广州
    const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/440300_full.json" //深圳市

2.创建图层并且添加到地图上

 // 2.创建矢量数据源
    const vectorSource = new VectorSource({
        url: geojsonDataUrl,
        format: new GeoJSON(),
    });
    // 创建图层
    // 定义矢量图层样式
    geoLayer.value = new VectorLayer({
        source: vectorSource,// 数据源
        style: new Style({
            fill: new Fill({
                color: 'rgba(0, 0, 255, 0.1)', // 区域填充颜色
            }),
            stroke: new Stroke({
                color: '#319FD3', // 边界线颜色
                width: 1, // 边界线宽度
                lineDash: [5, 5], // 虚线设置,[实线长度, 空白长度]
            }),
        }),
    });
    // 将图层添加到地图上
    map.value.addLayer(geoLayer.value);

3.效果

Snipaste_2024-10-17_09-09-00.png 整体代码

/**
 * 绘画出行政边界
 * 1.通过dataV获取geoJSON数据
 */
// 是否显示geojson数据
const showGeoJSOM = ref(false)
watch(showGeoJSOM, (newVal) => {
    if (newVal) {
        loadGeoJSON()
    } else {
       // 清除图层
        map.value.removeLayer(geoLayer.value)
    }
})
// 创建行政区域边界图层
const geoLayer = ref(null)
function loadGeoJSON() {
    // 1.获取geojson数据 
    // const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/100000_full_city.json" //全国
    // const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/440000_full.json" //广州
    const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/440300_full.json" //深圳市


    // 2.创建矢量数据源
    const vectorSource = new VectorSource({
        url: geojsonDataUrl,
        format: new GeoJSON(),
    });
    // 创建图层
    // 定义矢量图层样式
    geoLayer.value = new VectorLayer({
        source: vectorSource,// 数据源
        style: new Style({
            fill: new Fill({
                color: 'rgba(0, 0, 255, 0.1)', // 区域填充颜色
            }),
            stroke: new Stroke({
                color: '#319FD3', // 边界线颜色
                width: 1, // 边界线宽度
                lineDash: [5, 5], // 虚线设置,[实线长度, 空白长度]
            }),
        }),
    });
    // 将图层添加到地图上
    map.value.addLayer(geoLayer.value);
}

4.高亮选中的区域

1.封装一下高亮与默认的区域样式
// 默认样式
function defaultStyle() {
  return new Style({
    fill: new Fill({
      color: "rgba(0, 0, 255, 0.1)", // 区域填充颜色
    }),
    stroke: new Stroke({
      color: "#319FD3", // 边界线颜色
      width: 1, // 边界线宽度
      lineDash: [5, 5], // 虚线设置,[实线长度, 空白长度]
    }),
  });
}
// 凸显样式
function highlightStyle() {
  return new Style({
    fill: new Fill({
      color: "rgba(255, 0, 0, 0.3)", // 区域填充颜色,红色凸显
    }),
    stroke: new Stroke({
      color: "#FF0000", // 边界线颜色
      width: 2, // 边界线宽度
    }),
  });
}
2.侦听地图的点击事件 --在初始化地图的时候添加进去
// 监听地图点击事件
  map.value.on("singleclick", watchMapClick);
3.处理事件
// 存储当前选中的区域
const selectedFeature = ref(null);
function watchMapClick(event) {
  // 使用 forEachFeatureAtPixel 方法,检查点击的像素位置上是否有对应的 feature(地理对象)
  // 该方法会遍历点击像素处的所有 feature,并对每个 feature 执行回调函数
  map.value.forEachFeatureAtPixel(event.pixel, function (feature) {
    // 如果当前点击的 feature 不等于之前选中的 feature,才进行处理
    // selectedFeature.value 用来保存上一次被选中的 feature
    if (selectedFeature.value !== feature) {
      // 检查之前是否已经有一个选中的 feature
      // 如果有,需要将其样式恢复为默认样式(defaultStyle)
      if (selectedFeature.value) {
        selectedFeature.value.setStyle(defaultStyle); // 恢复上一次选中的 feature 的默认样式
      }

      // 将当前点击的 feature 的样式设置为高亮样式(highlightStyle)
      feature.setStyle(highlightStyle);

      // 将当前选中的 feature 赋值给 selectedFeature.value,记录当前的选中状态
      selectedFeature.value = feature;
    }

    // 输出当前点击的 feature 信息到控制台,便于调试和查看具体的 feature 对象
    console.log("当前点击的feature", feature);
  });
}

完整代码

<template>
  <div class="openlayers-map">
    <div id="map"></div>
    <!-- 工具栏 -->
    <div class="but_box">
      <button @click="showGeoJSOM = !showGeoJSOM">
        {{ showGeoJSOM ? "隐藏" : "显示" }}行政区域
      </button>
    </div>
  </div>
</template>

<script setup name="openlayersMap">
import { ref, onMounted, watch } from "vue";
import { Map, View, Feature } from "ol"; // 引入OpenLayers中的Map、View、Feature
import { Vector as VectorSource } from "ol/source"; // 引入矢量数据源
import { Vector as VectorLayer } from "ol/layer"; // 引入矢量图层
import { fromLonLat, toLonLat } from "ol/proj"; // 引入坐标转换方法
import { Fill, Stroke, Style, Text, Icon } from "ol/style"; // 引入样式和图标模块
import TileLayer from "ol/layer/Tile"; // 引入瓦片图层
import { easeOut } from "ol/easing"; // 引入缓动动画效果
import Overlay from "ol/Overlay"; // 引入覆盖层用于工具提示
import GeoJSON from "ol/format/GeoJSON";
import { Point } from "ol/geom"; // 引入几何点
import XYZ from "ol/source/XYZ"; // 引入XYZ瓦片源
const key = ref("3f0514d9051775ff8b59cfb08d4bb86f");
/**
 * 获取天地图图层
 *
 * @param {string} layerName - 图层名称,例如 'cva_c' 表示矢量注记图层
 * @param {string} layerType - 图层类型,例如 'cva' 表示矢量注记
 * @returns {Promise<TileLayer>} 返回一个 Promise,该 Promise 解析为一个 TileLayer 对象
 */
function getLayer(layerName, layerType) {
  return new Promise((resolve, reject) => {
    try {
      // 创建 TileLayer 图层实例
      const layer = new TileLayer({
        source: new XYZ({
          // 天地图 WMTS 服务的 URL
          url: `http://t0.tianditu.gov.cn/${layerName}/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${layerType}&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${key.value}`,
        }),
        className: layerName, //
      });

      if (layer) {
        resolve(layer);
      } else {
        reject(new Error("获取图层失败"));
      }
    } catch (error) {
      // 捕获任何异常并拒绝 Promise,返回错误信息
      reject(new Error(`创建图层时发生错误: ${error.message}`));
    }
  });
}

const map = ref(null);
const mapCenter = ref([114.42742665908969, 23.127858267967294]);
const mapZoom = ref(15); // 默认缩放级别
async function initMap() {
  // 判断是否已经存在地图实例,如果存在则销毁
  if (map.value) {
    map.value.setTarget(undefined); // 解除当前地图实例的 target,确保 DOM 元素不会被重复使用
    map.value = null; // 将 map 实例置为空,以便后续创建新地图实例
  }
  //根据给定的图层名称和类型,获取相应的图层
  const layer1 = await getLayer("img_w", "img"); // 影像底图 - 经纬度投影
  const layer2 = await getLayer("vec_w", "vec"); // 影像底图 - 经纬度投影
  const layer3 = await getLayer("cva_w", "cva"); // 矢量注记 - 球面墨卡托投影
  // 渲染地图视图
  map.value = new Map({
    target: "map", // 地图将绑定到 HTML 元素 id 为 'map' 的 DOM 元素
    layers: [layer2, layer3], // 添加三层异步获取的图层到地图上
    view: new View({
      center: fromLonLat(mapCenter.value), // 将经纬度转换为地图投影坐标系,设置地图中心点
      zoom: mapZoom.value, // 设置初始缩放等级,
      maxZoom: 18, // 设置地图最大缩放等级为 18,防止缩放过大 天地图最大就是18再大就不会显示地图
      projection: "EPSG:3857", // 地图的投影坐标系,
    }),
  });
  // 给地图添加点击事件
  map.value.on("click", clickMap);
  // 监听地图点击事件
  map.value.on("singleclick", watchMapClick);
  // 监听地图的渲染完成事件 整个图层
  map.value.once("rendercomplete", () => {
    console.log("地图渲染完成");
    // 添加滤镜到背景图层
    document.querySelector(".vec_w canvas").style.filter =
      "brightness(0.88) contrast(1.22) grayscale(0) hue-rotate(380deg) opacity(1) saturate(1.1) sepia(0.94) invert(0.9)";
    document.querySelector(".cva_w canvas").style.filter =
      "brightness(0.7) contrast(2) grayscale(5) hue-rotate(360deg) opacity(1) saturate(1.8) sepia(0.94) invert(1)";
  });
  // loadGeoJSON()
}

// 组件挂载完成后初始化地图
onMounted(() => {
  initMap();
});

// 地图点击事件
function clickMap(e) {
  // 获取点击坐标
  createdUserLaer(e.coordinate);
}
/**
 * 创建userlayer图层
 */
const userLayer = ref(null);
import location from "@/assets/images/location.png"; // 本地图片
function createdUserLaer(coordinate) {
  // 1.创建图层
  userLayer.value = new VectorLayer({
    source: new VectorSource(), // 创建矢量数据源
  });
  // 2.创建要素
  const feature = new Feature({
    geometry: new Point(coordinate),
  });
  // 3.设置样式要素
  feature.setStyle(
    new Style({
      image: new Icon({
        src: location,
        scale: 0.2,
      }),
    })
  );
  // 3.将要素添加到矢量数据源中
  userLayer.value.getSource().addFeature(feature);
  // 4.将图层添加到地图上
  map.value.addLayer(userLayer.value);
}
/**
 * 绘画出行政边界
 * 1.通过dataV获取geoJSON数据
 */
// 是否显示geojson数据
const showGeoJSOM = ref(false);
watch(showGeoJSOM, (newVal) => {
  if (newVal) {
    loadGeoJSON();
  } else {
    map.value.removeLayer(geoLayer.value);
  }
});
// 创建行政区域边界图层
const geoLayer = ref(null);
function loadGeoJSON() {
  // 1.获取geojson数据
  // const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/100000_full_city.json" //全国
  // const geojsonDataUrl = "https://geo.datav.aliyun.com/areas_v3/bound/440000_full.json" //广州
  const geojsonDataUrl =
    "https://geo.datav.aliyun.com/areas_v3/bound/440300_full.json"; //深圳市

  // 2.创建矢量数据源
  const vectorSource = new VectorSource({
    url: geojsonDataUrl,
    format: new GeoJSON(),
  });
  // 创建图层
  // 定义矢量图层样式 --默认样式
  geoLayer.value = new VectorLayer({
    source: vectorSource, // 数据源
    style: defaultStyle(),
    id: "loadGeoJSON",
  });
  // 将图层添加到地图上
  map.value.addLayer(geoLayer.value);
}
/**
 * 侦听地图点击事件
 */
// 默认样式
function defaultStyle() {
  return new Style({
    fill: new Fill({
      color: "rgba(0, 0, 255, 0.1)", // 区域填充颜色
    }),
    stroke: new Stroke({
      color: "#319FD3", // 边界线颜色
      width: 1, // 边界线宽度
      lineDash: [5, 5], // 虚线设置,[实线长度, 空白长度]
    }),
  });
}
// 凸显样式
function highlightStyle() {
  return new Style({
    fill: new Fill({
      color: "rgba(255, 0, 0, 0.3)", // 区域填充颜色,红色凸显
    }),
    stroke: new Stroke({
      color: "#FF0000", // 边界线颜色
      width: 2, // 边界线宽度
    }),
  });
}
// 存储当前选中的区域
const selectedFeature = ref(null);
function watchMapClick(event) {
  // 使用 forEachFeatureAtPixel 方法,检查点击的像素位置上是否有对应的 feature(地理对象)
  // 该方法会遍历点击像素处的所有 feature,并对每个 feature 执行回调函数
  map.value.forEachFeatureAtPixel(event.pixel, function (feature) {
    // 如果当前点击的 feature 不等于之前选中的 feature,才进行处理
    // selectedFeature.value 用来保存上一次被选中的 feature
    if (selectedFeature.value !== feature) {
      // 检查之前是否已经有一个选中的 feature
      // 如果有,需要将其样式恢复为默认样式(defaultStyle)
      if (selectedFeature.value) {
        selectedFeature.value.setStyle(defaultStyle); // 恢复上一次选中的 feature 的默认样式
      }

      // 将当前点击的 feature 的样式设置为高亮样式(highlightStyle)
      feature.setStyle(highlightStyle);

      // 将当前选中的 feature 赋值给 selectedFeature.value,记录当前的选中状态
      selectedFeature.value = feature;
    }

    // 输出当前点击的 feature 信息到控制台,便于调试和查看具体的 feature 对象
    console.log("当前点击的feature", feature);
  });
}
</script>

<style scoped lang="scss">
.openlayers-map {
  width: 100vw;
  height: 100vh;
  background-color: #191936;
  position: relative;

  .but_box {
    position: absolute;
    top: 0;
    left: 0;
  }

  #map {
    width: 100%;
    height: 100%;

    ::v-deep {
      .ol-overlaycontainer-stopevent {
        display: none;
      }
    }
  }
}
</style>