OpenLayers:封装Overlay的方法

平时在使用OpenLayers的Overlay时常感觉不便,于是最近我便封装了一些Overlay增删改查的方法,以提高可用性。这边文章中我会介绍我封装的方法,同时记录这个过程中踩的一些坑。

添加Overlay

JavaScript 复制代码
/**
 * @abstract 添加overlay
 * @param {*} map
 * @param {object} options overlay的配置项
 * @param {string} options.id overlay的id,可以用来唯一标识overlay
 * @param {string} options.groupId overlay的组名,可以用来批量操作overlay
 * @param {boolean} options.visibility overlay的初始可见性
 * options中的其它参数参考 ol/Overlay的构造函数参数
 */
export const addOverlay = (map, options) => {
   let {
    groupId = "default",
    positioning = "center-center",
    visibility = true,
    element,
    ...other
  } = options;

  // 将DOMstring转换为DOM元素
  if (!(element instanceof HTMLElement)) {
    const container = document.createElement("div");
    container.innerHTML = element;
    element = container.firstChild;
  }

  // 创建overlay
  const overlay = new Overlay({
    element,
    positioning,
    ...other,
  });

  // 设置额外的属性
  overlay.setProperties({
    groupId,
    visibility,
  });

  // 设置初始的可见性
  overlay.on("change:map", () => {
    if (!visibility) {
      setTimeout(() => {
        overlay.getElement().parentElement.style.display = "none";
      }, 0);
    }
  });

  // 添加到地图
  map.addOverlay(overlay);
};

addOverlay方法的特别之处

我封装的添加Overlay的方法,主要的改造是三个地方。

第一,对Overlay的element属性做了一个封装。

在我第一次使用Overlay的时候我就犯了一个错误,我给element属性传递了一个DOMString(比如<div class="test-overlay pink">粉色overlay</div>),结果就出现了类似于下面的报错。这是因为Overlay的element属性只能接收一个DOM元素。

现在经过我的封装后,我的addOverlay方法允许传入的element属性是一个DOMString

第二,给Overlay添加一些额外的属性,这些属性会在后续帮助我去实现一些对Overlay的操作。

JavaScript 复制代码
  // 设置额外的属性
  overlay.setProperties({
    groupId,
    visibility,
  });

第三,给Overlay设置了初始的可见性,如果传入的visibility参数为false,就会暂时先将overlay隐藏。(我还考虑后续或许可以通过这个来实现popup)

JavaScript 复制代码
// 设置初始的可见性
overlay.on("change:map", () => {
  if (!visibility) {
    setTimeout(() => {
      overlay.getElement().parentElement.style.display = "none";
    }, 0);
  }
});

查询Overlay

JavaScript 复制代码
/**
 * @abstract 查找overlay
 * @param {*} map
 * @param {object} condition 筛选条件
 * @param {string} condition.id overlay的id筛选需要操作的overlay
 * @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
 * @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
 * @returns  {Array} 符合条件的overlay数组
 */
export const findOverlay = (map, { id, groupId, condition }) => {
  const targetOverlays = [];
  const overlays = map.getOverlays().getArray();

  overlays.forEach(overlay => {
    const groupId_ = overlay.get("groupId");
    const id_ = overlay.getId();

    if (id) {
      if (id_ === id) {
        targetOverlays.push(overlay);
      }
    }

    if (groupId) {
      if (groupId_ === groupId) {
        targetOverlays.push(overlay);
      }
    }

    if (condition) {
      if (condition(overlay, id_, groupId_, overlays)) {
        targetOverlays.push(overlay);
      }
    }
  });

  return targetOverlays;
};

findOverlay支持的查询方式

我设计的查找Overlay的方法findOverlay支持通过三种方式进行查找:

  1. 通过id查找,这个id是创建 Overlay时的参数(我建议在创建overlay时最好都要添加这个参数)
JavaScript 复制代码
// 查找id为'overlay-1'的overlay
findOverlay( map , { id : 'overlay-1' })
  1. 通过groupId查找,这个是我在addOverlay方法中添加的一个额外属性,我的计划是通过groupId实现对Overlay的分组管理,可以同时去操作一组的Overlay。
JavaScript 复制代码
// 查找所有groupId 为 stationTooltip 的overlay
findOverlay( map , { groupId : 'stationTooltip' })
  1. 通过一个筛选函数condition来进行查找,这个就类似于js中Array.prototype.find()方法。我会为地图中绑定的每个overlay都调用condition方法,若condition方法返回true则将Overlay返回,若返回false则Overlay会被忽略。

condition方法将会接收到四个参数

    • overlay,当前的overlay对象;
    • id,当前的overlay对象的id;
    • groupId,当前overlay对象的groupId;
    • overlays,地图中的所有overlay对象组成的数组
JavaScript 复制代码
// 查找所有groupId 为 stationTooltip 的overlay
findOverlay( map , { condition:(overlay , id , groupId , overlays)=> groupId == "stationTooltip" })

最后,注意findOverlay方法返回一个包含所有符合条件的Overlay对象的数组。

删除Overlay

JavaScript 复制代码
/**
 * @abstract 移除overlay
 * @param {*} map
 * @param {object} condition 筛选条件
 * @param {string} condition.id overlay的id筛选需要操作的overlay
 * @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
 * @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
 */
export const removeOverlay = (map, { id, groupId, condition }) => {
  const targetOverlays = findOverlay(map, { id, groupId, condition });

  targetOverlays.forEach(overlay => {
    map.removeOverlay(overlay);
  });
};

/**
 *@abstract 清除所有overlay
 * @param {*} map
 */
export const clearOverlay = map => {
  const overlays = map.getOverlays();
  overlays.clear();
};

我封装了两个删除Overlay的方法removeOverlayclearOverlay,前者删除符合条件的Overlay,后者清空地图中的所有Overlay。

错误的删除方式

之前我在网上看到下面的这种删除Overlay的方式。也就是边遍历边删除,这种方法是错误的。会有删除不尽的问题。

JavaScript 复制代码
  const overlays = map.getOverlays();
  overlays.forEach((overlay, index) => {
    if (overlay.getId() === id) {
      overlays.removeAt(index);
    }
  });

正确的写法是先通过遍历将想要删除Overlay收集到一个数组中,然后再循环数组进行删除.。

JavaScript 复制代码
  const overlays = map.getOverlays();

  const targetOverlays = []
  overlays.forEach((overlay, index) => {
    if (overlay.getId() === id) {
      targetOverlays.push(overlay)
    }
  });

  targetOverlays.forEach((overlay)=>{
    map.removeOverlay(overlay) 
  })

显示/隐藏Overlay

JavaScript 复制代码
/**
 * @abstract 设置overlay的可见性
 * @param {*} map
 * @param {object} condition 筛选条件
 * @param {string} condition.id overlay的id筛选需要操作的overlay
 * @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
 * @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
 * @param {boolean} visible 要设置的可见性
 */
export const setOverlayVisibility = (
  map,
  { id, groupId, condition, visible }
) => {
  const targetOverlays = findOverlay(map, { id, groupId, condition });

  targetOverlays.forEach(overlay => {
    overlay.setProperties({
      visibility: visible,
    });

    const element = overlay.getElement();
    element.parentElement.style.display = visible ? "flex" : "none";
  });
};

/**
 * @abstract 切换overlay的可见性
 * @param {*} map
 * @param {object} condition 筛选条件
 * @param {string} condition.id overlay的id筛选需要操作的overlay
 * @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
 * @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
 */
export const toggleOverlayVisibility = (map, { id, groupId, condition }) => {
  const targetOverlays = findOverlay(map, { id, groupId, condition });

  targetOverlays.forEach(overlay => {
    const newVisibility = !overlay.get("visibility");
    overlay.setProperties({
      visibility: newVisibility,
    });

    const element = overlay.getElement();
    element.parentElement.style.display = newVisibility ? "flex" : "none";
  });
};

显示/隐藏Overlay的方法我也封装了两个 setOverlayVisibility用于手动的设置显隐,toggleOverlayVisibility用于自动设置显隐。

隐藏Overlay的方法是去修改Overlay的element的父元素的display。我们在创建Overlay的时候传入的element实际上会被用另一个元素包装起来,这个元素(默认class为ol-overlay-container ol-selectable的元素)才是Overlay的根元素。

参考资料

  1. OpenLayers v10.5.0 API - Class: Overlay
  2. OpenLayers v10.5.0 API - Class: Collection
  3. OpenLayers之 OverLay问题汇总_openlayers overlay zindex-CSDN博客
相关推荐
zhougl9961 小时前
html处理Base文件流
linux·前端·html
花花鱼1 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_1 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo3 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)3 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法