三十四、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>
相关文章:
三十四、openlayers官网示例Dynamic clusters解析——动态的聚合图层
官网demo地址: https://openlayers.org/en/latest/examples/clusters-dynamic.html 这篇绘制了多个聚合图层。 先初始化地图 ,设置了地图视角的边界extent,限制了地图缩放的范围 initMap() {const raster new TileLayer({source: new XYZ…...
SpringBoot登录认证--衔接SpringBoot案例通关版
文章目录 登录认证登录校验-概述登录校验 会话技术什么是会话呢?cookie Session令牌技术登录认证-登录校验-JWT令牌-介绍JWT SpringBoot案例通关版,上接这篇 登录认证 先讲解基本的登录功能 登录功能本质就是查询操作 那么查询完毕后返回一个Emp对象 如果Emp对象不为空,那…...
vue3状态管理,pinia的使用
状态管理 我们知道组件与组件之间可以传递信息,那么我们就可以将一个信息作为组件的独立状态(例如,单个组件的颜色)或者共有状态(例如,多个组件是否显示)在组件之传递,…...
入门到实践,手把手教你用AI绘画!
前言 一款无需魔法的PS插件!下载即用,自带提示词插件,无论你是小白还是大神都能轻松上手,无配置要求,win/mac通通能用! AI绘画工具——StartAI 官网:StartAI官网 (istarry.com.cn) 近段时间…...
大模型应用框架-LangChain
LangChain的介绍和入门 💥 什么是LangChain LangChain由 Harrison Chase 创建于2022年10月,它是围绕LLMs(大语言模型)建立的一个框架,LLMs使用机器学习算法和海量数据来分析和理解自然语言,GPT3.5、GPT4是…...
探索Linux中的强大文本处理工具——sed命令
探索Linux中的强大文本处理工具——sed命令 在Linux系统中,文本处理是一项日常且重要的任务。sed命令作为一个流编辑器,以其强大的文本处理能力而著称。它允许我们在不修改原始文件的情况下,对输入流(文件或管道)进行…...
冯喜运:6.3黄金原油晚间最新行情及独家操作策略指导
【黄金消息面分析】:在全球经济的波动和不确定性中,黄金作为传统的避险资产,其价格走势和市场分析一直是投资者关注的焦点。本周一(北京时间6月3日),现货黄金价格基本持平,交易商正在等待本周公…...
Spark_SparkOnHive_海豚调度跑任务写入Hive表失败解决
背景 前段时间我在海豚上打包程序写hive出现了一个问题,spark程序向hive写数据时,报了如下bug, org.apache.spark.sql.AnalysisException: The format of the existing table test.xx is HiveFileFormat It doesnt match the specified for…...
SaaS 电商设计 (十一) 那些高并发电商系统的限流方案设计
目录 一.什么是限流二.怎么做限流呢2.1 有哪些常见的系统限流算法2.1.1 固定窗口2.1.1 滑动窗口2.1.2 令牌桶2.1.3 漏桶算法 2.2 常见的限流方式2.2.1 单机限流&集群限流2.2.2 前置限流&后置限流 2.3 实际落地是怎么做的2.3.1 流量链路2.3.2 各链路限流2.3.2.1 网关层2…...
【算法】MT2 棋子翻转
✨题目链接: MT2 棋子翻转 ✨题目描述 在 4x4 的棋盘上摆满了黑白棋子,黑白两色棋子的位置和数目随机,其中0代表白色,1代表黑色;左上角坐标为 (1,1) ,右下角坐标为 (4,4) 。 现在依次有一些翻转操作&#…...
头颈肿瘤在PET/CT中的分割:HECKTOR挑战赛| 文献速递-深度学习肿瘤自动分割
Title 题目 Head and neck tumor segmentation in PET/CT: The HECKTOR challenge 头颈肿瘤在PET/CT中的分割:HECKTOR挑战赛 01 文献速递介绍 高通量医学影像分析,常被称为放射组学,已显示出其在揭示定量影像生物标志物与癌症预后之间关…...
Kafka重平衡导致无限循环消费问题
1. 问题描述 Kafka消费者消费消息超过了5分钟,不停的触发重平衡,消费者的offset因为重平衡提交失败,重复拉取消费,重复消费。 2. 问题原因 kafka默认的消息消费超时时间max.poll.interval.ms 300000, 也就是5分钟,…...
执行shell脚本时为什么要写成./test.sh,而不是test.sh?
一定要写成 ./test.sh,而不是 test.sh 运行其它二进制的程序也一样! 直接写 test.sh,linux 系统会去 PATH (系统环境)里寻找有没有叫 test.sh 的! 而只有 /bin, /sbin, /usr/bin,/usr/sbin 这…...
【人工智能】第一部分:ChatGPT的基本概念和技术背景
人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 目录 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌…...
雪花算法详解及源码分析
雪花算法的简介: 雪花算法用来实现全局唯一ID的业务主键,解决分库分表之后主键的唯一性问题,所以就单从全局唯一性来说,其实有很多的解决方法,比如说UUID、数据库的全局表的自增ID 但是在实际的开发过程中࿰…...
Golang TCP网络编程
文章目录 网络编程介绍TCP网络编程服务器监听客户端连接服务器服务端获取连接向连接中写入数据从连接中读取数据关闭连接/监听器 简易的TCP回声服务器效果展示服务端处理逻辑客户端处理逻辑 网络编程介绍 网络编程介绍 网络编程是指通过计算机网络实现程序间通信的一种编程技术…...
先进制造aps专题十 aps项目成功指南
aps项目成功指南 为了保证aps项目的成功 现在国内的aps项目 一是看aps软件本身是不是实现了复杂的排程算法和优化算法,算法引擎使用c高性能编译语言开发,支持工序的复杂关系,考虑副资源约束和特殊规格约束,提供了能考虑各种约束…...
实现Dropdown下拉菜单监听键盘上下键选中功能-React
用过ant design的小伙伴都知道,select组件是支持联想搜索跟上下键选中的效果的,但是在项目中我们可能会遇到用select组件无法实现我们的需求的情况,比如说一个div框,里面有input,又有tag标签,在input中输入…...
Ubuntu系统升级k8s节点的node节点遇到的问题
从1.23版本升级到1.28版本 node节点的是Ubuntu系统20.04的版本 Q1 node节点版本1.23升级1.28失败 解决办法: # 改为阿里云镜像 vim /etc/apt/sources.list.d/kubernetes.list# 新增 deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main# 执…...
前端将DOM元素导出为图片
前端工作中经常会用到把一些元素导出,比如表格,正好项目有遇到导出为excel和导出为图片,就都封装实现了一下,以供其他需求的开发者使用: 1.导出为文档 这个说白了就是下载的功能,传过去检索参数ÿ…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
