当前位置: 首页 > news >正文

三十四、openlayers官网示例Dynamic clusters解析——动态的聚合图层

官网demo地址:

https://openlayers.org/en/latest/examples/clusters-dynamic.html

这篇绘制了多个聚合图层。

先初始化地图 ,设置了地图视角的边界extent,限制了地图缩放的范围

 initMap() {const raster = new TileLayer({source: new XYZ({attributions:'Base map: <a target="_blank" href="https://basemap.at/">basemap.at</a>',url: "https://maps{1-4}.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png",}),});this.map = new Map({layers: [raster],target: "map",view: new View({center: [0, 0],zoom: 2,maxZoom: 19,extent: [...fromLonLat([16.1793, 48.1124]),...fromLonLat([16.5559, 48.313]),],showFullExtent: true,}),});this.map.on("pointermove", this.moveEvent);this.map.on("click", this.clickEvent);},

创建一个聚合数据源,数据是geoJson格式的。

    const vectorSource = new VectorSource({format: new GeoJSON(),url: "https://openlayers.org/en/latest/examples/data/geojson/photovoltaic.json",});const clusterSource = new Cluster({attributions:'Data: <a href="https://www.data.gv.at/auftritte/?organisation=stadt-wien">Stadt Wien</a>',distance: 35,source: vectorSource,});

 然后创建一个聚合图层

//聚合图层this.clustersLayer = new VectorLayer({source: clusterSource,style: this.clusterStyle,});this.map.addLayer(this.clustersLayer);

因为每个feature的样式不一样,所以样式这里都绑定了函数。

这里的outerCircle定义为全局变量而并非局部变量,主要是因为clusterStyle函数是个高频触发函数,将outerCircle写成全局的可以不用new那么多次。使用样式组的方法绘制出来了外发光效果,其实就是画了两次圆形。

//黄色圆圈 内外两层 发光效果clusterStyle(feature) {const size = feature.get("features").length;//还有下级if (size > 1) {return [new Style({image: this.outerCircle,}),new Style({image: this.innerCircle,text: new Text({text: size.toString(),fill: this.textFill,stroke: this.textStroke,}),}),];}//没有下级const originalFeature = feature.get("features")[0];return this.clusterMemberStyle(originalFeature);},
 this.textFill = new Fill({color: "#fff",});this.textStroke = new Stroke({color: "rgba(0, 0, 0, 0.6)",width: 3,});this.innerCircle = new CircleStyle({radius: 14,fill: new Fill({color: "rgba(255, 165, 0, 0.7)",}),});this.outerCircle = new CircleStyle({radius: 20,fill: new Fill({color: "rgba(255, 153, 102, 0.3)",}),});

有下级的时候图形是橙色发光的样式,没有下级的时候。根据feature的LEISTUNG字段显示为不同的icon图形。

clusterMemberStyle(clusterMember) {return new Style({geometry: clusterMember.getGeometry(),image:clusterMember.get("LEISTUNG") > 5 ? this.darkIcon : this.lightIcon,});},
 this.darkIcon = new Icon({src: "data/icons/emoticon-cool.svg",});this.lightIcon = new Icon({src: "data/icons/emoticon-cool-outline.svg",});

 创建一个凸包图层。

凸包是包含给定点集的最小凸多边形,在许多计算几何应用中非常重要,如图形学、地理信息系统(GIS)和形状分析。计算凸包的算法通常基于点的排序和几何性质,可以有效地处理大规模的数据。 

下载引入monotone-chain-convex-hull

npm i monotone-chain-convex-hull
import monotoneChainConvexHull from "monotone-chain-convex-hull";
      //凸包图层样式this.convexHullFill = new Fill({color: "rgba(255, 153, 0, 0.4)",});this.convexHullStroke = new Stroke({color: "rgba(204, 85, 0, 1)",width: 1.5,});
      //凸包图层this.clusterHulls = new VectorLayer({source: clusterSource,style: this.clusterHullStyle,});
 clusterHullStyle(cluster) {if (cluster !== this.hoverFeature) {return null;}const originalFeatures = cluster.get("features");const points = originalFeatures.map((feature) =>feature.getGeometry().getCoordinates());return new Style({geometry: new Polygon([monotoneChainConvexHull(points)]),fill: this.convexHullFill,stroke: this.convexHullStroke,});},

当鼠标移动到点图层时,显示凸包效果。 

  moveEvent(event) {this.clustersLayer.getFeatures(event.pixel).then((features) => {if (features[0] !== this.hoverFeature) {this.hoverFeature = features[0];this.clusterHulls.setStyle(this.clusterHullStyle);this.map.getTargetElement().style.cursor =this.hoverFeature && this.hoverFeature.get("features").length > 1? "pointer": "";}});},

然后是点线图层

this.clusterCircles = new VectorLayer({source: clusterSource,style: this.clusterCircleStyle,});

当前视图的缩放级别达到了最大缩放级别或者范围的宽度和高度都小于当前视图的分辨率      

往点线图层的style数组中添加两个样式。。

clusterCircleStyle(cluster, resolution) {if (cluster !== this.clickFeature || resolution !== this.clickResolution) {return null;}const clusterMembers = cluster.get("features");const centerCoordinates = cluster.getGeometry().getCoordinates();return this.generatePointsCircle( clusterMembers.length,cluster.getGeometry().getCoordinates(),resolution).reduce((styles, coordinates, i) => {const point = new Point(coordinates);const line = new LineString([centerCoordinates, coordinates]);styles.unshift(new Style({geometry: line,stroke: this.convexHullStroke,}));styles.push(this.clusterMemberStyle(new Feature({...clusterMembers[i].getProperties(),geometry: point,})));return styles;}, []);},

generatePointsCircle 方法根据聚类成员的数量、中心坐标和当前分辨率,生成一个圆周上的点坐标数组。 

generatePointsCircle(count, clusterCenter, resolution) {//计算圆周长度和每个点的角度步长const circumference =this.circleDistanceMultiplier * this.circleFootSeparation * (2 + count);let legLength = circumference / (Math.PI * 2); const angleStep = (Math.PI * 2) / count;const res = [];let angle;//调整线段长度 确保线段长度至少为 35,并根据分辨率进行调整。legLength = Math.max(legLength, 35) * resolution; //生成圆周上的点坐标for (let i = 0; i < count; ++i) {angle = this.circleStartAngle + i * angleStep;res.push([clusterCenter[0] + legLength * Math.cos(angle),clusterCenter[1] + legLength * Math.sin(angle),]);}return res;},

 点击事件时,获取当前点击的feature的边界值,定位到指定位置。

clickEvent(event) {this.clustersLayer.getFeatures(event.pixel).then((features) => {if (features.length > 0) {const clusterMembers = features[0].get("features");if (clusterMembers.length > 1) {const extent = createEmpty();clusterMembers.forEach((feature) =>extend(extent, feature.getGeometry().getExtent()));const view = this.map.getView();const resolution = this.map.getView().getResolution();//如果当前视图的缩放级别达到了最大缩放级别 如果范围的宽度和高度都小于当前视图的分辨率if (view.getZoom() === view.getMaxZoom() ||(getWidth(extent) < resolution && getHeight(extent) < resolution)) {this.clickFeature = features[0];this.clickResolution = resolution;this.clusterCircles.setStyle(this.clusterCircleStyle);} else {view.fit(extent, { duration: 500, padding: [50, 50, 50, 50] });}}}});},

完整代码:

<template><div class="box"><h1>Dynamic clusters</h1><div id="map"></div></div>
</template><script>
import Feature from "ol/Feature.js";
import GeoJSON from "ol/format/GeoJSON.js";
import Map from "ol/Map.js";
import View from "ol/View.js";
import {Circle as CircleStyle,Fill,Icon,Stroke,Style,Text,
} from "ol/style.js";
import { Cluster, Vector as VectorSource, XYZ } from "ol/source.js";
import { LineString, Point, Polygon } from "ol/geom.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import { createEmpty, extend, getHeight, getWidth } from "ol/extent.js";
import { fromLonLat } from "ol/proj.js";
// import convexHull from "convex-hull";
import monotoneChainConvexHull from "monotone-chain-convex-hull";
export default {name: "",components: {},data() {return {map: null,hoverFeature: null,convexHullFill: null,convexHullStroke: null,textFill: null,textStroke: null,innerCircle: null,outerCircle: null,darkIcon: null,lightIcon: null,circleDistanceMultiplier: 1,circleFootSeparation: 28,circleStartAngle: Math.PI / 2,clickFeature: null,clustersLayer: null,clusterHulls: null,clusterCircles: null,clickResolution:null,};},computed: {},created() {},mounted() {this.initMap();this.initStyle();this.addClusterLayers();},methods: {initMap() {const raster = new TileLayer({source: new XYZ({attributions:'Base map: <a target="_blank" href="https://basemap.at/">basemap.at</a>',url: "https://maps{1-4}.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png",}),});this.map = new Map({layers: [raster],target: "map",view: new View({center: [0, 0],zoom: 2,maxZoom: 19,extent: [...fromLonLat([16.1793, 48.1124]),...fromLonLat([16.5559, 48.313]),],showFullExtent: true,}),});this.map.on("pointermove", this.moveEvent);this.map.on("click", this.clickEvent);},addClusterLayers() {const vectorSource = new VectorSource({format: new GeoJSON(),url: "https://openlayers.org/en/latest/examples/data/geojson/photovoltaic.json",});const clusterSource = new Cluster({attributions:'Data: <a href="https://www.data.gv.at/auftritte/?organisation=stadt-wien">Stadt Wien</a>',distance: 35,source: vectorSource,});//凸包图层this.clusterHulls = new VectorLayer({source: clusterSource,style: this.clusterHullStyle,});//聚合图层this.clustersLayer = new VectorLayer({source: clusterSource,style: this.clusterStyle,});//特定情况下的图层this.clusterCircles = new VectorLayer({source: clusterSource,style: this.clusterCircleStyle,});this.map.addLayer(this.clusterHulls);this.map.addLayer(this.clustersLayer);this.map.addLayer(this.clusterCircles);},initStyle() {//凸包图层样式this.convexHullFill = new Fill({color: "rgba(255, 153, 0, 0.4)",});this.convexHullStroke = new Stroke({color: "rgba(204, 85, 0, 1)",width: 1.5,});this.textFill = new Fill({color: "#fff",});this.textStroke = new Stroke({color: "rgba(0, 0, 0, 0.6)",width: 3,});this.innerCircle = new CircleStyle({radius: 14,fill: new Fill({color: "rgba(255, 165, 0, 0.7)",}),});this.outerCircle = new CircleStyle({radius: 20,fill: new Fill({color: "rgba(255, 153, 102, 0.3)",}),});this.darkIcon = new Icon({src: "data/icons/emoticon-cool.svg",});this.lightIcon = new Icon({src: "data/icons/emoticon-cool-outline.svg",});},clusterMemberStyle(clusterMember) {return new Style({geometry: clusterMember.getGeometry(),image:clusterMember.get("LEISTUNG") > 5 ? this.darkIcon : this.lightIcon,});},clusterCircleStyle(cluster, resolution) {if (cluster !== this.clickFeature || resolution !== this.clickResolution) {return null;}const clusterMembers = cluster.get("features");const centerCoordinates = cluster.getGeometry().getCoordinates();return this.generatePointsCircle( clusterMembers.length,cluster.getGeometry().getCoordinates(),resolution).reduce((styles, coordinates, i) => {const point = new Point(coordinates);const line = new LineString([centerCoordinates, coordinates]);styles.unshift(new Style({geometry: line,stroke: this.convexHullStroke,}));styles.push(this.clusterMemberStyle(new Feature({...clusterMembers[i].getProperties(),geometry: point,})));return styles;}, []);},generatePointsCircle(count, clusterCenter, resolution) {//计算圆周长度和每个点的角度步长const circumference =this.circleDistanceMultiplier * this.circleFootSeparation * (2 + count);let legLength = circumference / (Math.PI * 2); const angleStep = (Math.PI * 2) / count;const res = [];let angle;//调整线段长度 确保线段长度至少为 35,并根据分辨率进行调整。legLength = Math.max(legLength, 35) * resolution; //生成圆周上的点坐标for (let i = 0; i < count; ++i) {angle = this.circleStartAngle + i * angleStep;res.push([clusterCenter[0] + legLength * Math.cos(angle),clusterCenter[1] + legLength * Math.sin(angle),]);}return res;},clusterHullStyle(cluster) {if (cluster !== this.hoverFeature) {return null;}const originalFeatures = cluster.get("features");const points = originalFeatures.map((feature) =>feature.getGeometry().getCoordinates());return new Style({geometry: new Polygon([monotoneChainConvexHull(points)]),fill: this.convexHullFill,stroke: this.convexHullStroke,});},//黄色圆圈 内外两层 发光效果clusterStyle(feature) {const size = feature.get("features").length;//还有下级if (size > 1) {return [new Style({image: this.outerCircle,}),new Style({image: this.innerCircle,text: new Text({text: size.toString(),fill: this.textFill,stroke: this.textStroke,}),}),];}//没有下级const originalFeature = feature.get("features")[0];return this.clusterMemberStyle(originalFeature);},moveEvent(event) {this.clustersLayer.getFeatures(event.pixel).then((features) => {if (features[0] !== this.hoverFeature) {this.hoverFeature = features[0];this.clusterHulls.setStyle(this.clusterHullStyle);this.map.getTargetElement().style.cursor =this.hoverFeature && this.hoverFeature.get("features").length > 1? "pointer": "";}});},clickEvent(event) {this.clustersLayer.getFeatures(event.pixel).then((features) => {if (features.length > 0) {const clusterMembers = features[0].get("features");if (clusterMembers.length > 1) {const extent = createEmpty();clusterMembers.forEach((feature) =>extend(extent, feature.getGeometry().getExtent()));const view = this.map.getView();const resolution = this.map.getView().getResolution();//如果当前视图的缩放级别达到了最大缩放级别 如果范围的宽度和高度都小于当前视图的分辨率if (view.getZoom() === view.getMaxZoom() ||(getWidth(extent) < resolution && getHeight(extent) < resolution)) {this.clickFeature = features[0];this.clickResolution = resolution;this.clusterCircles.setStyle(this.clusterCircleStyle);} else {view.fit(extent, { duration: 500, padding: [50, 50, 50, 50] });}}}});},},
};
</script><style lang="scss" scoped>
#map {width: 100%;height: 500px;
}
.box {height: 100%;
}#info {width: 100%;height: 24rem;overflow: scroll;display: flex;align-items: baseline;border: 1px solid black;justify-content: flex-start;
}
</style>

相关文章:

  • Docker安装Redis(云服务器)
  • dubbo复习:(15)泛化调用
  • Oracle AVDF(审计保险库和数据库防火墙)常见问题
  • PDF高效编辑器革新:一键智能转换PDF至HTML,轻松开启文件处理全新时代!
  • [JAVA数组] 三个数的最大乘积
  • 2024-5-14——完成所有任务需要的最少轮数
  • 不是从APP store下载的APP在mac上一直提示有损坏,打不开怎么办?
  • PostgreSQL启动报错“could not map anonymous shared memory: Cannot allocate memory”
  • Python学习从0开始——Kaggle机器学习003总结
  • [线程与网络] 网络编程与通信原理(五): 深入理解网络层IP协议与数据链路层以太网协议
  • 3DGS语义分割之LangSplat
  • 步进电机双闭环细分控制(matlab仿真)内含课设等参考文件
  • 新闻出版署发布新规定,腾讯游戏限制未成年人端午期间每天一小时
  • 搭载昇腾310NPU的Orange Pi AIpro开箱体验以及深度学习样例测试
  • LAMP分布式安全方案搭建网页 (LinuxCentOS7+Apache+Mariadb+PHP)包括服务端口及防火墙规则配置
  • 《Java编程思想》读书笔记-对象导论
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • Android优雅地处理按钮重复点击
  • ES10 特性的完整指南
  • github从入门到放弃(1)
  • LintCode 31. partitionArray 数组划分
  • Vue全家桶实现一个Web App
  • 产品三维模型在线预览
  • 从零开始在ubuntu上搭建node开发环境
  • 关于Java中分层中遇到的一些问题
  • 机器学习中为什么要做归一化normalization
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 消息队列系列二(IOT中消息队列的应用)
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 用mpvue开发微信小程序
  • 再谈express与koa的对比
  • ​ssh免密码登录设置及问题总结
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #git 撤消对文件的更改
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (175)FPGA门控时钟技术
  • (CPU/GPU)粒子继承贴图颜色发射
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (三) diretfbrc详解
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • .NET Core中的去虚
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .NET大文件上传知识整理
  • .NET的数据绑定
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?
  • @RequestMapping-占位符映射
  • [ vulhub漏洞复现篇 ] Apache Flink目录遍历(CVE-2020-17519)
  • [AI 大模型] 百度 文心一言
  • [Android]常见的数据传递方式
  • [AutoSar NVM] 存储架构