js多边形算法:多边形缩放、获取中心、获取重心/质心、判断是否在多边形内、判断点排序是否顺时针等

news2024/11/26 16:49:26

一、前言

最近做多边形相关的工作,涉及比较多相关算法,总结一下,方便大家,如果帮到您,记得点赞!

二、演示

【在线演示】
【源码gitee】

三、使用

所有核心算法都在utils.js里面,含参数说明,如果不懂看可以看demo的使用。

3.1 多边形缩放

3.1.1 算法

参考:https://blog.csdn.net/sun_and_breeze/article/details/107517088
参考:https://blog.csdn.net/shyjhyp11/article/details/126396170

3.1.2 使用:

需配合‘顺时针判断’方法一起使用

const polygon = [
  { x: 314, y: 61 },
  { x: 385, y: 128 },
  { x: 496, y: 35 },
  { x: 390, y: 182 },
];

// 多边形放大5个单位
const zoomInPolygon = scalePolygon(polygon, 5); // 返回放大后多边形坐标集合

// 多边形缩小5个单位
const zoomOutPolygon = scalePolygon(polygon, -5); // 返回缩小后多边形坐标集合

3.1.3 演示:

请添加图片描述

3.2 获取多边形中心、重心/质心

多边形中心和重心是不同的,如果是规则多边形则都一样,如果是凹多边形,很多情况下中心会在图形外面,所以需要根据实际情况取中心还是重心。

3.2.1 算法

中心算法:比较简单,就是所有坐标点加起来,求平均值
重心/质心算法参考:https://blog.csdn.net/weixin_43847416/article/details/95781817

3.2.2 使用

const polygon = [
  { x: 314, y: 61 },
  { x: 385, y: 128 },
  { x: 496, y: 35 },
  { x: 390, y: 182 },
];

// 获取多边形中心
const center = getPolygonCenter(polygon); // 返回坐标点

// 获取多边形重心/质心
const baryCenter = getPolygonBaryCenter(polygon); // 返回坐标点

3.2.3 演示

请添加图片描述

3.3 判断点是否在多边形内

3.3.1 算法

本例使用的是“射线法”
参考:https://blog.csdn.net/WilliamSun0122/article/details/77994526

3.3.2 使用

const point = { x: 322, y: 90 };
const polygon = [
  { x: 314, y: 61 },
  { x: 385, y: 128 },
  { x: 496, y: 35 },
  { x: 390, y: 182 },
];

// 判断点是否在多边形内部
const isIn = isInPolygon(point,polygon); // true or false

3.3.3 演示

请添加图片描述

3.4 判断多边形是否顺时针

3.4.1 算法

参考:https://blog.csdn.net/qq_34447899/article/details/93991335

3.4.1 使用

const polygon = [
  { x: 314, y: 61 },
  { x: 385, y: 128 },
  { x: 496, y: 35 },
  { x: 390, y: 182 },
];

// 判断多边形坐标是否顺时针
const isClock = isClockwise(polygon); // true or false

3.4.2 演示

这里就不演示了

四、核心代码

/*
 * @Author: 大话主席 superslide2.com
 * @Description: 多边形核心算法
 */

/**
 * 获取多边形中心点
 * @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
 */
function getPolygonCenter(points) {
  if (!Array.isArray(points) || points.length < 3) {
    console.error("多边形坐标集合不能少于3个");
    return;
  }
  const result = { x: 0, y: 0 };
  points.forEach((p) => {
    result.x += p.x;
    result.y += p.y;
  });
  result.x /= points.length;
  result.y /= points.length;
  return result;
}

/**
 * 获取多边形重心(质心)
 * @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
 */
function getPolygonBaryCenter(points) {
  if (!Array.isArray(points) || points.length < 3) {
    console.error("多边形坐标集合不能少于3个");
    return;
  }
  const result = { x: 0, y: 0 };
  let area = 0;
  for (let i = 1; i <= points.length; i++) {
    const curX = points[i % points.length].x;
    const curY = points[i % points.length].y;
    const nextX = points[i - 1].x;
    const nextY = points[i - 1].y;
    const temp = (curX * nextY - curY * nextX) / 2;
    area += temp;
    result.x += (temp * (curX + nextX)) / 3;
    result.y += (temp * (curY + nextY)) / 3;
  }
  result.x /= area;
  result.y /= area;
  return result;
}

/**
 * 判断点是否在多边形内部
 * @param {Point} point 点坐标
 * @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
 * @returns
 */
function isInPolygon(point, points) {
  if (!Array.isArray(points) || points.length < 3) {
    console.error("多边形坐标集合不能少于3个");
    return;
  }
  const n = points.length;
  let nCross = 0;
  for (let i = 0; i < n; i++) {
    const p1 = points[i];
    const p2 = points[(i + 1) % n];
    // 求解 y=p.y 与 p1 p2 的交点
    // p1p2 与 y=p0.y平行
    if (p1.y === p2.y) continue;
    // 交点在p1p2延长线上
    if (point.y < Math.min(p1.y, p2.y)) continue;
    // 交点在p1p2延长线上
    if (point.y >= Math.max(p1.y, p2.y)) continue;
    // 求交点的 X 坐标
    const x = ((point.y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y) + p1.x;
    // 只统计单边交点
    if (x > point.x) nCross++;
  }
  return nCross % 2 === 1;
}

/**
 * 缩放多边形坐标
 * @decoration 需配合顺时针判断方法一起使用
 * @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
 * @param {number} extra 外延大小。为正: 向外扩; 为负: 向内缩
 * @return {Point[]} 扩展或缩小后的多边形点坐标数组
 */
function scalePolygon(points, extra) {
  if (!Array.isArray(points) || points.length < 3) {
    console.error("多边形坐标集合不能少于3个");
    return;
  }
  const ps = points;
  // 通过顺时针判断取正值还是负值
  const extra0 = isClockwise(ps) ? -extra : extra;

  const norm = (x, y) => Math.sqrt(x * x + y * y);

  const len = ps.length;
  const polygon = [];
  for (let i = 0; i < len; i++) {
    const point = ps[i];
    const point1 = ps[i === 0 ? len - 1 : i - 1];
    const point2 = ps[i === len - 1 ? 0 : i + 1];

    // 向量PP1
    const vectorX1 = point1.x - point.x; // 向量PP1 横坐标
    const vectorY1 = point1.y - point.y; // 向量PP1 纵坐标
    const n1 = norm(vectorX1, vectorY1); // 向量的平方根 为了对向量PP1做单位化
    let vectorUnitX1 = vectorX1 / n1; // 向量单位化 横坐标
    let vectorUnitY1 = vectorY1 / n1; // 向量单位化 纵坐标

    // 向量PP2
    const vectorX2 = point2.x - point.x; // 向量PP2 横坐标
    const vectorY2 = point2.y - point.y; // 向量PP2 纵坐标
    const n2 = norm(vectorX2, vectorY2); // 向量的平方根 为了对向量PP1做单位化
    let vectorUnitX2 = vectorX2 / n2; // 向量单位化 横坐标
    let vectorUnitY2 = vectorY2 / n2; // 向量单位化 纵坐标

    // PQ距离
    const vectorLen =
      -extra0 /
      Math.sqrt(
        (1 - (vectorUnitX1 * vectorUnitX2 + vectorUnitY1 * vectorUnitY2)) / 2
      );

    // 根据向量的叉乘积来判断角是凹角还是凸角
    if (vectorX1 * vectorY2 + -1 * vectorY1 * vectorX2 < 0) {
      vectorUnitX2 *= -1;
      vectorUnitY2 *= -1;
      vectorUnitX1 *= -1;
      vectorUnitY1 *= -1;
    }

    // PQ的方向
    const vectorX = vectorUnitX1 + vectorUnitX2;
    const vectorY = vectorUnitY1 + vectorUnitY2;
    const n = vectorLen / norm(vectorX, vectorY);
    const vectorUnitX = vectorX * n;
    const vectorUnitY = vectorY * n;

    const polygonX = vectorUnitX + point.x;
    const polygonY = vectorUnitY + point.y;

    polygon[i] = { x: polygonX, y: polygonY };
  }

  return polygon;
}

/**
 * 判断坐标数组是否顺时针(默认为false)
 * @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
 * @returns {boolean} 是否顺时针
 */
function isClockwise(points) {
  // 三个点可以判断矢量是顺时针旋转还是逆时针旋转的,但由于可能存在凹边,所以并不是任意三点都可以正确反映多边形的走向
  // 因此需要取多边形中绝对是凸边的点来判断,
  // 多边形中的极值点(x最大或x最小或y最大或y最小)它与相邻两点构成的边必然是凸边,因此我们先取出多边形中的极值点,再由极值点和其前后两点去判断矢量的走向,从而判断出多边形的走向。
  if (!Array.isArray(points) || points.length < 3) {
    console.error("多边形坐标集合不能少于3个");
    return false;
  }
  let coords = JSON.parse(JSON.stringify(points));

  if (coords[0] === coords[coords.length - 1]) {
    coords = coords.slice(0, coords.length - 1);
  }
  coords = coords.reverse();
  let maxXIndex = 0;
  let maxX = parseFloat(coords[maxXIndex].x);
  let c1;
  let c2;
  let c3;
  for (let i = 0; i < coords.length; i++) {
    if (parseFloat(coords[i].x) > maxX) {
      maxX = parseFloat(coords[i].x);
      maxXIndex = i;
    }
  }
  if (maxXIndex === 0) {
    c1 = coords[coords.length - 1];
    c2 = coords[maxXIndex];
    c3 = coords[maxXIndex + 1];
  } else if (maxXIndex === coords.length - 1) {
    c1 = coords[maxXIndex - 1];
    c2 = coords[maxXIndex];
    c3 = coords[0];
  } else {
    c1 = coords[maxXIndex - 1];
    c2 = coords[maxXIndex];
    c3 = coords[maxXIndex + 1];
  }
  const x1 = parseFloat(c1.x);
  const y1 = parseFloat(c1.y);
  const x2 = parseFloat(c2.x);
  const y2 = parseFloat(c2.y);
  const x3 = parseFloat(c3.x);
  const y3 = parseFloat(c3.y);
  const s = (x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3);
  return s < 0;
}

五、点赞

如果帮到您,点个赞再走!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/90174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

扩散模型代码剖析

前言 相信大家对扩散模型早有耳闻&#xff0c;其着实大火了一把&#xff0c;效果也确实是好。今天写这篇博客的主要动机就是想真正进入到代码层面去看看其到底是怎么实现的。 其实在看完代码后&#xff0c;会觉得其实现的非常简单&#xff0c;而且也会对原理的理解有一个更好的…

如何快速构建企业级数据湖仓?

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 本文整理自火山引擎开发者社区技术大讲堂第四期演讲&#xff0c;主要介绍了数据湖仓开源趋势、火山引擎 EMR 的架构及特点&#xff0c;以及如何基于火山引擎 EMR 构…

Python+Yolov5人脸口罩识别

程序示例精选 PythonYolov5人脸口罩识别 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 Yolov5比较Yolov4,Yolov3等其他识别框架&#xff0c;速度快&#xff0c;代码结构简单&#xff0c;识别效率高&#xf…

【王道计算机网络笔记】网络层-网络层协议

文章目录地址解析协议ARP动态主机配置协议DHCP国际控制报文协议ICMPICMP差错报文ICMP询问报文ICMP的应用地址解析协议ARP 由于在实际网络的链路上传送数据帧时&#xff0c;最终必须使用MAC地址 ARP协议&#xff1a;完成主机或路由器IP地址到MAC地址的映射。解决下一跳走哪的问…

Metal每日分享,海报画滤镜效果

本案例的目的是理解如何用Metal实现海报画效果滤镜&#xff0c;主要就是改变颜色级别数量从而获取到新的像素颜色&#xff1b; Demo HarbethDemo地址 实操代码 // 海报画滤镜 let filter C7Posterize.init(colorLevels: 2.3)// 方案1: ImageView.image try? BoxxIO(eleme…

不用虚拟机也能在Windows下使用Linux

想学习热门的Linux系统&#xff0c;可是一开始就需要安装虚拟机软件&#xff0c;这样很容易消耗Linux初学者的热情。比如常用的VMWare虚拟机&#xff0c;虽然步骤并不复杂&#xff0c;但是一开始的搭建和配置过程&#xff0c; 容易劝退一部分新手。我认为学习新的操作系统&…

看了这篇文章后,面试官再也不敢问你非结构化存储的原理了

那么你可能会说&#xff0c;是不是我无限制地增加从库的数量就可以抵抗大量的并发呢&#xff1f; 实际上并不是的。因为随着从库数量增加&#xff0c;从库连接上来的 IO 线程比较多&#xff0c;主库也需要创建同样多的 log dump 线程来处理复制的请求&#xff0c;对于主库资源消…

[附源码]Python计算机毕业设计飞羽羽毛球馆管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等…

人工智能课后作业_python实现深度优先遍历搜索(DFS算法)(附源码)

1 深度优先遍历搜索(DFS) 1.1算法介绍1.2实验代码1.3实验结果1.4实验总结 1.1算法介绍 深度优先搜索算法&#xff08;Depth-First-Search&#xff0c;DFS&#xff09;是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点&#xff0c;尽可能深的搜索树的分支。当节点…

知识图谱-KGE-第三方库:OpenKE库【清华开源】

GitHub - thunlp/OpenKE: An Open-Source Package for Knowledge Embedding (KE) OpenKE是THUNLP基于TensorFlow、PyTorch开发的用于将知识图谱嵌入到低维连续向量空间进行表示的开源框架。在OpenKE中&#xff0c;我们提供了快速且稳定的各类接口&#xff0c;也实现了诸多经典…

生态流量智能终端机介绍 功能 特点

平升电子生态流量智能终端机是一款集人机交互、视频叠加、4G路由、数据采集、逻辑运算与远程传输功能于一体的多媒体智能终端设备。 此款产品为水电站生态流量监测项目的专用产品&#xff0c;便于监管单位及时掌握水电站的流量下泄情况&#xff0c;以保障河湖生态用水&#xf…

Java序列化_unknown object tag -126

项目场景&#xff1a; 第一次进入获取员工信息的方法时&#xff0c;会先通过序列化数据库的对应员工信息并保存到 Redis 中。 第二次进入获取员工信息的方法时&#xff0c;直接取出 Redis 里序列化后员工信息&#xff0c;进行反序列化后返回。 问题描述 这里是第一次保存成功…

重温经典,推箱子游戏,你能闯到第几关?可自行添加关卡

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

WebDAV之葫芦儿·派盘+厚墨

厚墨 支持WebDAV方式连接葫芦儿派盘。 如果你喜欢看电子书又时常书荒,搜索不到想要的电子书,那就快来试试厚墨阅读APP吧。与你一同搜索极简阅读中的最佳体验。 厚墨是目前网络上非常方便的一款电子阅读软件,采用独家数据采集分析技术,汇合了移动互联网各种资源网站大数据…

【JavaSE成神之路】可变参数

哈喽&#xff0c;我是兔哥呀&#xff0c;今天就让我们继续这个JavaSE成神之路&#xff01; 这一节啊&#xff0c;咱们要学习的内容是Java的可变参数。 1.什么是可变参数 首先来看下概念。 Java的可变参数指的是在方法中设置不定数量的参数。可变参数使得代码更加简洁&#x…

用cocos creator实现《我的世界》

摘要 《我的世界》是一款非常流行的游戏&#xff0c;不过网上大多都是用unity还原实现的。那么用cocos实现一版&#xff0c;会是怎样的开发体验呢&#xff1f; 使用版本 使用最新的cocos creator 3.6.2版本 目前主要功能 生成地形方块创建与销毁角色移动、碰撞、重力和简单…

Java-MySQL-SQL函数

SQL函数 函数介绍 函数是 SQL 的一个非常强有力的特性&#xff0c;函数能够用于下面的目的&#xff1a; ● 执行数据计算 ● 修改单个数据项 ● 操纵输出进行行分组 ● 格式化显示的日期和数字 ● 转换列数据类型 SQL 函数有输入参数&#xff0c;并且总有一个返回值。 …

【云原生系列CKA备考】Kubernetes架构

目录前言一、Kubernetes架构1.1Master节点1.2 Node节点1.3 Add-ons1.3 Kubeadm二、相关命令2.1 查看组件运行状态2.2 kubeadm容器化组件三、总结前言 ​ OpenStack是管理虚拟机的&#xff0c;底层依靠虚拟化技术&#xff1b;kubernetes是管理容器的&#xff0c;底层也是依靠虚…

juery笔记

文章目录Jquery一、什么是 jQuery二、如何使用 jQuery三、如何选择 jQuery 版本四、jQuery 的运行原理实例方法1、一般通过一个字符串来标识匹配的元素2、支持多个选择器任意组合使用3、jQuery 特有的选择器&#xff0c;当然也可以和其他选择器任意组合使用4、元素筛选&#xf…