文章目录
前言
之前写的关于利用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并返回。
另外,这里会涉及到你输入的遮罩数据的坐标系的处理。有三种情况:
- 如果地图坐标系和数据坐标系一致。这时不需要处理数据的坐标系,因此方法只需输入geometry参数和view参数。
- 如果地图坐标系和数据坐标系不一致,就需要对数据进行坐标转换,而后才能和地图正确叠加。这时就需要用dataProjection指定数据的坐标系。
之前的代码坐标系这里没处理,也是导致看不到遮罩的一个原因。
扫描二维码关注公众号,回复: 14887078 查看本文章坐标转换这里仅支持4326与3857之间的转换,如果你涉及到其它的坐标系,那你就要自己想办法转咯。
- 如果你不知道自己输入数据的坐标系,那就找提供给你数据的人那里咨询。
-
erase 擦除操作
erase是GIS中的一种空间运算,参考ArcGIS。想象你有一张涂满黑色的纸,然后用橡皮擦擦掉其中一部分后的效果。
回到这个方法,它会根据view参数,创建一个覆盖地图全范围的但是中空的geometry对象。中间空的部分就是你输入geometry对象,也就是你遮罩数据的覆盖范围。输入的geometry类型支持LineString、MultiLineString、Polygon、MultiPolygon。之前很多说没有看到遮罩的,就是因为之前的代码,只支持polygon。
想想为啥支持这几个类型。
-
getCoordsGroup 获取地理对象的坐标并分组
这里是对LineString、MultiLineString、Polygon、MultiPolygon等几种类型的地理对象的坐标,进行统一的处理分组,以便在erase方法中创建LinearRing(线性环)。不同的geometry类型,分组数量也不一样。
- LineString、Polygon -> 1组
- MultiLineString、MultiPolygon -> 1组或者n组。
其中1组指的是包含1系统坐标的数组,如:
[ [12, 23], [23, 25], [55, 32], ... ]