利用Openlayers4实现地图遮罩效果(三)

前言

之前写的关于利用openlayer创建遮罩的文章(原文),那篇文章主要是讲如何创建一个四周遮罩(即四周外围遮罩但露出目标中心区域)。后面有很多同学在留言,说按我的方法,遮罩出不来或者运行报错,甚至私信在线求解。

的确,那篇文件的代码确实写的有一些漏洞,因为是作为示例给大伙演示,追求简单,希望大家明白实现原理,而没有对输入数据、地图坐标系等这些依赖用户环境的问题,进行统一处理。但是大伙在使用时,生搬代码,就很容易出错。

因此,今天抽空升级下之前的代码,希望能帮到大家。

完整代码

import './style.css';
import {
    
    Feature, Map, View} from 'ol';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import {
    
    Fill, Stroke, Style} from "ol/style";
import GeoJSON from 'ol/format/GeoJSON'
import {
    
     createProjection, Projection } from 'ol/proj'
import {
    
     Geometry, Polygon, LinearRing, LineString, MultiLineString, MultiPolygon } from 'ol/geom' 
import {
    
     fromExtent } from 'ol/geom/Polygon'

const map = new Map({
    
    
  target: 'map',
  layers: [
    new TileLayer({
    
    
      source: new OSM()
    })
  ],
  view: new View({
    
    
    center: [0, 0],
    zoom: 2
  })
});


// 遮罩样式
const shadeStyle = new Style({
    
    
  fill: new Fill({
    
    
    color: 'rgba(0, 0, 0, 0.5)'
  }),
  stroke: new Stroke({
    
    
    width: 1,
    color: '#ccc'
  })
})
// 遮罩数据源
const vtSource = new VectorSource()
// 遮罩图层
const vtLayer = new VectorLayer({
    
    
  source: vtSource,
  style: shadeStyle
})

map.addLayer(vtLayer)

/**
 * 添加遮罩
 * @param {LineString | MultiLineString | Polygon | MultiPolygon} geom 必选参数
 * @param { View } view 必选参数
 * @param { String } dataProjection geom的坐标系,只支持epsg:4326 || epsg:3857, 可选参数, 当地图的坐标系与遮罩数据的坐标系不一致时,需求提供数据的坐标系
 * @returns { Feature }
 */
function createShade (geom, view, dataProjection) {
    
    
  if (geom instanceof Geometry) {
    
    
    dataProjection = dataProjection.toUpperCase()
    const source = geom.clone()
    if(['EPSG:4326', 'EPSG:3857'].includes(dataProjection) && view instanceof View) {
    
    
      const projection = createProjection(dataProjection)
      const mapProjection = view.getProjection()
      const mapProjetionCode = mapProjection.getCode()
      if (projection.getCode() !== mapProjetionCode && ['EPSG:4326', 'EPSG:3857'].includes(mapProjetionCode)) {
    
    
        source.transform(projection, mapProjection)
      }
    }

    var polygon = erase(source, view)
    var feature = new Feature({
    
    
      geometry: polygon
    })
    return {
    
    
      feature,
      shade: source
    }
  }
}

/**
 * 擦除操作
 * @param {LineString | MultiLineString | Polygon | MultiPolygon} geom 必选参数
 * @param { View } view 必选参数
 * @returns {Polygon}
 */
function erase (geom, view) {
    
    
  var part = getCoordsGroup(geom)
  if (!part) {
    
    
    return
  }
  var extent = view.getProjection().getExtent()
  var polygonRing = fromExtent(extent);
  part.forEach((item) => {
    
    
    let linearRing = new LinearRing(item);
    polygonRing.appendLinearRing(linearRing);
  })
  return polygonRing
}

/**
 * geom转坐标数组
 * @param {LineString | MultiLineString | Polygon | MultiPolygon} geom 
 * @returns {Array<Array<import("ol/coordinate").Coordinate>>} 返回geom中的坐标
 */
function getCoordsGroup(geom) {
    
    
  var group = [] // 
  var geomType = geom.getType()
  if (geomType === 'LineString') {
    
    
    group.push(geom.getCoordinates())
  } else if (geomType === 'MultiLineString') {
    
    
    group = geom.getCoordinates()
  } else if (geomType === 'Polygon') {
    
    
    group = geom.getCoordinates()
  } else if (geomType === 'MultiPolygon') {
    
    
    geom.getPolygons().forEach((poly) => {
    
    
      var coords = poly.getCoordinates()
      group = group.concat(coords)
    })
  } else {
    
    
    console.log('暂时不支持的类型')
  }
  return group
}

function testAddShade () {
    
    
  fetch('./data/bound.geojson').then((res) => {
    
    
    return res.json()
  }).then((data) => {
    
    
    const features = new GeoJSON().readFeatures(data)
    const ft = features[0]
    const bound = ft.getGeometry()
    const result = createShade(bound, map.getView(), 'epsg:4326')
    if (result) {
    
    
      vtSource.addFeature(result.feature)
      map.getView().fit(result.shade)
    }
  })
}

testAddShade()

效果

在这里插入图片描述

代码解读

代码中有3个方法,

  • createShade 创建一个遮罩。是添加遮罩的方法的入口。

    这个方法,会根据你输入的geometry对象,创建一个包含遮罩的Feature并返回。

    另外,这里会涉及到你输入的遮罩数据的坐标系的处理。有三种情况:

    1. 如果地图坐标系和数据坐标系一致。这时不需要处理数据的坐标系,因此方法只需输入geometry参数和view参数。
    2. 如果地图坐标系和数据坐标系不一致,就需要对数据进行坐标转换,而后才能和地图正确叠加。这时就需要用dataProjection指定数据的坐标系。

    之前的代码坐标系这里没处理,也是导致看不到遮罩的一个原因。

    扫描二维码关注公众号,回复: 14887078 查看本文章

    坐标转换这里仅支持4326与3857之间的转换,如果你涉及到其它的坐标系,那你就要自己想办法转咯。

    1. 如果你不知道自己输入数据的坐标系,那就找提供给你数据的人那里咨询。
  • erase 擦除操作

    erase是GIS中的一种空间运算,参考ArcGIS。想象你有一张涂满黑色的纸,然后用橡皮擦擦掉其中一部分后的效果。

    回到这个方法,它会根据view参数,创建一个覆盖地图全范围的但是中空的geometry对象。中间空的部分就是你输入geometry对象,也就是你遮罩数据的覆盖范围。输入的geometry类型支持LineString、MultiLineString、Polygon、MultiPolygon。之前很多说没有看到遮罩的,就是因为之前的代码,只支持polygon。

    想想为啥支持这几个类型。

  • getCoordsGroup 获取地理对象的坐标并分组

    这里是对LineString、MultiLineString、Polygon、MultiPolygon等几种类型的地理对象的坐标,进行统一的处理分组,以便在erase方法中创建LinearRing(线性环)。不同的geometry类型,分组数量也不一样。

    1. LineString、Polygon -> 1组
    2. MultiLineString、MultiPolygon -> 1组或者n组。

    其中1组指的是包含1系统坐标的数组,如:

    [
      [12, 23],
      [23, 25],
      [55, 32],
      ...
    ]
    

完整项目代码(含测试数据)

代码地址

猜你喜欢

转载自blog.csdn.net/u012413551/article/details/122739501