说在前面

  还是接着上回那个活~
  他们以前用百度地图实现了一个跨时间线绘制轨迹线的功能,效果如下:

  之所以需要这种功能,是因为海洋上没有实际的轨迹点,只能美化处理两端点连线,否则当从小于经度180度到大于-180的两个点相连时,若不做特殊处理,会反向拉一条直线,而不是跨域太平洋拉轨迹线。处理思路主要是判断从小于经度180度到大于-180的两个点,使用二阶贝塞尔曲线进行绘制,这样可以示意从东半球跨域到西半球的轨迹(为了美观,还需要判断跨越经度超过20度时,也做贝塞尔曲线处理,这个后面说)。

开发环境

  开发环境:vue+openlayers6,底图为百度地图,可根据实际情况进行坐标转换

第一步:做轨迹线跨日期判断,把对应的坐标点对存放起来

  核心方法就是要将超出最大轨迹线的那部分点的经度坐标偏移系数(40075016.68也就是最大经度的两倍值),这样在绘制时,才能接着最大轨迹往右边继续绘制(实际这样在ol里会出bug,后面会说如何解决)。

/*** 处理轨迹线,特别是跨日期线,目前只考虑从180~-180,后续应增加-180到180的处理* @param pointsArr*/dealTimeLineOfPoints(pointsArr) {if (!pointsArr || pointsArr.length < 1) {return null}//返回两类数据,一类是处理后的轨迹数据,用于单点定位到轨迹上;再存一个实际绘制的轨迹点对,可能是multipolyline,一类是绘制贝塞尔曲线的起始点对(默认超过50度需要绘制曲线,看起来比较圆滑好看)let reObject = {dealedLine: [],multiLine: [[]],bezierPoints: []}let eastToWestNum = 0//计算从东半球到西半球的次数,即系数n,用后一个坐标的经度减去n*360,实际绘制还得回归180~-180的坐标范围,否则会出现超出坐标范围的线消失的问题let pyNum = 40075016.68//偏移系数 360//for (let i = 0; i < pointsArr.length - 1; i++) {let p = pointsArr[i]//lngX和latY保存原始的坐标数据,用于前端列表显示// p.lngX=p.lng// p.latY=p.latlet pNext = pointsArr[i + 1]let newSt = [p.lng, p.lat]//第一个点的坐标点对let newEnd = [pNext.lng, pNext.lat]//第二个点的坐标点对//先判断相邻两个点是否第一个在0~180,第二个一个在-180~0,若是,则这一段线不绘制,用贝塞尔单独绘制,原始坐标不做处理,这种情况下的贝塞尔第二个点存储为处理后的let isMuli = false //经过时间线截断;let isBez = false//需要贝塞尔曲线的情况:经过时间线截断;两个经度相差超过50if (geosUtil.isInEast(newSt[0]) && !geosUtil.isInEast(newEnd[0]) && Math.abs(newEnd[0] - newSt[0]) > 180) {//每从东半球跨到西半球,且两者只差大于180度,就次数+1eastToWestNum++isMuli = true}//判断两个点经度是否相差20度,是的话则需要绘制曲线let cutBez = 20if (isMuli) {//在跨时间线的情况下不用判断是否差20度,需要直接绘制贝塞尔曲线isBez = true} else {//非跨时间线情况if (Math.abs(newEnd[0] - newSt[0]) >= cutBez) {isBez = true}}//坐标转换,这里把地理坐标(WGS84)转换为投影坐标(BD09),详见另一篇文章中的转换方法//https://blog.csdn.net/weixin_42356271/article/details/126125853?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22126125853%22%2C%22source%22%3A%22weixin_42356271%22%7DnewSt = geomUtil.zc_format_coordinate(newSt[0], newSt[1])newEnd = geomUtil.zc_format_coordinate(newEnd[0], newEnd[1])if (isBez) {if (isMuli) {let newEndLon = newEnd[0] + pyNum //eastToWestNum*pyNum 由于最后都得回归180到-180,所以不用计算跨日期线的次数了//计算实际应该绘制的经度(绘制贝塞尔用)reObject.bezierPoints.push([newSt, [newEndLon, newEnd[1]]])//跨时间线且经度相差超过50度,则第二个坐标的经度保存处理后的} else {reObject.bezierPoints.push([newSt, newEnd])//只是普通的绘制贝塞尔曲线,则只保存正常坐标}}if (i == 0) {//第一个点添加reObject.dealedLine.push(newSt)if (!isBez) {//只有不绘制贝塞尔时,第一个点才加入多段线reObject.multiLine[0].push(newSt)reObject.multiLine[0].push(newEnd)} else {//绘制时,将第二个点加入多段线reObject.multiLine[0].push(newEnd)}} else {//不是第一个点if (!isBez) {//不绘制贝塞尔时(正常绘制),则增加进多段线中reObject.multiLine[reObject.multiLine.length - 1].push(newEnd)//上一段轨迹添加坐标}else {//如果因为跨日期线或者两点跨度太大需要弧线美化,不需要在多段线中增加此坐标reObject.multiLine.push([newEnd])//新建下一段轨迹,且添加第二个坐标,否则会漏一个点}}reObject.dealedLine.push(newEnd)}console.dir(reObject)return reObject},/*** 判断经度是否在东半球,也就是0-180度,是返回true,否返回false* @param lon*/isInEast(lon) {if (lon <= 180 && lon > 0) {return true} else {return false}},

  直接绘制效果

第二步:绘制贝塞尔曲线(二阶)

  网上找了很多绘制贝塞尔曲线的例子,都不太理想,这里重新梳理了下代码,可以支持自定义绘制二阶贝塞尔曲线的角度以及绘制精细度(拆分段数)。
  这里还增加了对跨度超过20度(可自定义)的坐标点对增加贝塞尔曲线绘制,可以美化效果。
PS:此处有个bug需要处理,即超出20037508.34的值需截断成两部分绘制(将第二个点处理,整体左移2倍经度,详见代码),否则会出现挪动时这部分轨迹(超过最大经度的那部分轨迹线)消失,这应该是openlayers的一个bug,网上有资料说将wrapX设置为false就解决了,这个不适用于当前情况,因为我们就是要左右无限拖动的

    maxLon : 20037508.34,//最大经度maxLat : 20037508.34,//最大纬度/** 绘制贝塞尔曲线--------------------------------------------*/ConstantMultiVector2(c, pos) {return [c * pos[0], c * pos[1]];},vector2Add(a, b) {return [a[0] + b[0], a[1] + b[1]];},/*** a = > [ lng,lat]* b = > [ lng,lat]* n => ratio 比例,一般为50%,即0.5,此时拿的是中点位置* 二维向量线性插值*/linerInperpote(a, b, n) {let curA = this.ConstantMultiVector2(1.0 - n, a);let curB = this.ConstantMultiVector2(n, b);return this.vector2Add(curA, curB);},conHopCount: 50,//拆分段数,默认50段/*** 获取 当前贝塞尔曲线上的 点坐标* @param originPos1* @param center* @param originPos2* @param times* @param hopCount 拆分段数,默认50段* @returns {*}*/getCurrentEnd(originPos1, center, originPos2, times, hopCount) {hopCount = hopCount ? hopCount : this.conHopCountlet curTimes = times / hopCount;let a = this.ConstantMultiVector2(Math.pow(1.0 - curTimes, 2), originPos1);let b = this.ConstantMultiVector2(2 * curTimes * (1 - curTimes), center);let c = this.ConstantMultiVector2(Math.pow(curTimes, 2), originPos2);return this.vector2Add(this.vector2Add(a, b), c);},/*** 获取控控制点:,若未提供偏移量,则默认起点正向偏移10度。* @param originPos1 起点* @param originPos2 终点* @param offset 偏移角度*/getControlP(originPos1, originPos2, offset) {offset = offset ? offset : 10offset = 2 * Math.PI / 360 * offset //计算弧度//偏移量需要优化,可以设置根据角度(暂时设置30度夹角计算两个点之间的控制点)let centerPos = this.linerInperpote(originPos1, originPos2, 0.5);//开始点 与结束点 的中点 的位置let st2centerLen = calculateUtil.calculateLength(originPos1[0], originPos1[1], centerPos[0], centerPos[1])//起点到中点的距离let st2ControlLen = st2centerLen / Math.cos(offset)//起点到控制点的距离let shortLen = Math.abs(centerPos[1] - originPos1[1])//中点和起点的短边let longLen = Math.abs(centerPos[0] - originPos1[0])//中点和起点的长边let otherAngle = Math.atan(shortLen / longLen)//起止点与x轴夹角(弧度)let sumAngle = otherAngle + offset//起点-控制点与x轴的夹角(弧度)let sShort = st2ControlLen * Math.cos(sumAngle)//起点到控制点的短边:x轴let sLong = st2ControlLen * Math.sin(sumAngle)//起点到控制点的长边:y轴let controlX = originPos1[0] //控制点xlet controlY = originPos1[1]//控制点y//这里根据第二个点与第一个点进行比对,来决定弧度向上或向下,若统一都向上,则可不进行判断,直接+if (originPos2[0] >= originPos1[0]) {controlX += sShort} else {controlX -= sShort}if (originPos2[1] >= originPos1[1]) {controlY += sLong} else {controlY -= sLong}return [controlX, controlY]}, /** --------------------------------------------- *//*** 贝塞尔曲线绘制* @param bezierPoints 绘制贝塞尔的点集合,支持多段绘制,[[[1,1],[2,2]],[[3,3],[4,4]]]* @param offsetValue 偏移角度*/drawBezelLine(bezierPoints, offsetValue) {//第一步,遍历需要绘制贝塞尔曲线的起始点对for (let i in bezierPoints) {let startPos = bezierPoints[i][0]let endPos = bezierPoints[i][1]let centerPos = this.getControlP(startPos, endPos, offsetValue)let res = [startPos]let times = 1while (times % geomUtil.conHopCount != 0) {res.push(this.getCurrentEnd(startPos, centerPos, endPos, times))times++}res.push(endPos)bezierPoints[i] = res}//第二步,判断经过时间线的两个坐标点对,其他正常的进行分组let mergeArr = [] //正常的坐标点对let cutBzArr = []//跨时间线的第二段贝塞尔的坐标点对let outBzArr = [] //全部超出时间线的部分let outNum=0for (let j in bezierPoints) {let curBezierPoints = bezierPoints[j]outNum = 0for (let i = 0; i < curBezierPoints.length - 1; i++) {let st = curBezierPoints[i]if(i==0){mergeArr.push([])//先加一个空的mergeArr[mergeArr.length-1].push(st) //第一个先加进去}let end = curBezierPoints[i + 1]let compareV = this.isCrossTimeLine(st,end)if(compareV=="FAN"){//如果正向跨时间线let inPoint = geosUtil.calculateIntersectionPointOfTwoLines(st,end,[this.maxLon,this.maxLat/2],[this.maxLon,-this.maxLat/2])//计算交点let halfLat = inPoint[1]//(end[1] + st[1]) / 2//以前取纬度中点作为交点效果略差let lastLon = end[0] - 2 * this.maxLon//将第二个点处理,整体左移2倍经度let lastLat = end[1]//第二个点纬度存储用于新增第二截的mergeArr[mergeArr.length-1].push([this.maxLon,halfLat])//中间点加进去//这一段要截断,从新开始增加下一段的,正常情况不会出现——————————————————————mergeArr.push([])//第二段起止坐标放到这个arr里cutBzArr.push([[-this.maxLon,halfLat],[lastLon,lastLat]])}else if(compareV=="ZHENG"){//如果反向跨时间线}else if(compareV=="OUT"){//两个点都超出最大lonif(outNum==0){//第一次查到,加[]outBzArr.push([])outBzArr[outBzArr.length-1].push([st[0] - 2 * this.maxLon,st[1]])//加第一个点}outBzArr[outBzArr.length-1].push([end[0] - 2 * this.maxLon,end[1]])outNum++}else{//正常情况mergeArr[mergeArr.length-1].push(end)}}}//合并arrlet newBz_1 = [...mergeArr, ...cutBzArr]bezierPoints = [...newBz_1, ...outBzArr]return bezierPoints},//判断是否经过时间线isCrossTimeLine(bezArrSt,bezArrEnd){if(bezArrSt[0] > this.maxLon){//第一个点超出最大经度范围if(bezArrEnd[0] <= this.maxLon){//第二个点小于等于最大经度范围return "ZHENG" //表示反方向跨时间线}else{//第二个点超出最大经度范围return "OUT"}}else if(bezArrSt[0] <= this.maxLon) {//第一个点在正常经度范围内(小于等于)if (bezArrEnd[0] > this.maxLon) {//第二个点超出最大经度范围return "FAN" //表示正方向跨时间线}}return "IN" //表示不跨时间线},

  增加贝塞尔曲线效果

第三步:优化:计算两条线的交点

  由于开始使用跨日期线的坐标点对与[180,90],[180,-90]的中点进行截断,效果不是很好,所以考虑还是使用两条线的交点进行截断,这样绘制出来效果就比较完美了。

/*** 计算两条线的交点* @param x1* @param y1* @param x2* @param y2* @param x3* @param y3* @param x4* @param y4* @returns {*[]}*/calculateIntersectionPointOfTwoLines([x1, y1], [x2, y2], [x3, y3], [x4, y4]) {debuggerlet a = {x: x1,y: y1}let b = {x: x2,y: y2}let c = {x: x3,y: y3}let d = {x: x4,y: y4}let rePs = this.segmentsIntr(a, b, c, d)return [rePs.x, rePs.y]},/*** 计算两条线的交点* @param a* @param b* @param c* @param d* @returns {*}*/segmentsIntr(a, b, c, d) {// 三角形abc 面积的2倍var area_abc = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);// 三角形abd 面积的2倍var area_abd = (a.x - d.x) * (b.y - d.y) - (a.y - d.y) * (b.x - d.x);// 面积符号相同则两点在线段同侧,不相交 (对点在线段上的情况,本例当作不相交处理);if (area_abc * area_abd >= 0) {return false;}// 三角形cda 面积的2倍var area_cda = (c.x - a.x) * (d.y - a.y) - (c.y - a.y) * (d.x - a.x);// 三角形cdb 面积的2倍// 注意: 这里有一个小优化.不需要再用公式计算面积,而是通过已知的三个面积加减得出.var area_cdb = area_cda + area_abc - area_abd;if (area_cda * area_cdb >= 0) {return false;}//计算交点坐标var t = area_cda / (area_abd - area_abc);var dx = t * (b.x - a.x),dy = t * (b.y - a.y);return {x: a.x + dx, y: a.y + dy};}

  放大后看180度位置,完美跨越~

openlayers实现跨时间轨迹线(贝塞尔曲线)相关推荐

  1. 7.组件连线(贝塞尔曲线)--从零起步实现基于Html5的WEB设计器Jquery插件(含源码)...

    上节讲到如何创建组件,清除设计器视图,以及设计视图的持久化和恢复,本节将重点讲如何实现组件间的连线,前面章节有提到为了方便从持久化文件中恢复,组件和连线是分别存放的:nodes和lines对象,两个组 ...

  2. 轨迹规划-贝塞尔曲线

    1. 简介 贝塞尔曲线于 1962 年,由法国工程师皮埃尔·贝济埃(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计,贝塞尔曲线最初由保尔·德·卡斯特里奥于1959年运用 ...

  3. 前端动画之贝塞尔曲线推导及应用

    hello,大家好,今天豆皮范儿给大家带来了贝塞尔曲线推导和应用,优美的贝塞尔曲线想起了大学时候老师在给我们讲如何实现,如何推导,如何实现和应用.本来也来详细介绍一下,纯纯的干货- 作者:lff 生活 ...

  4. 【转】贝塞尔曲线介绍

    原文链接: http://blog.csdn.net/sangxiaonian/article/details/51984013 http://blog.csdn.net/sangxiaonian/a ...

  5. 多视角探析贝塞尔曲线匀速化技术、实现及其应用

    概述 就在三年前,我于CSDN博客上发布了一篇题为<贝塞尔曲线运动n阶追踪方程的数学原理及其匀速化方法和应用>的博客文章,主要探讨的是贝塞尔曲线由一阶至n阶在数学层面的生成过程,以及匀速化 ...

  6. 【Android UI】贝塞尔曲线 ① ( 一阶贝塞尔曲线 | 二阶贝塞尔曲线 )

    文章目录 一.一阶贝塞尔曲线 二.二阶贝塞尔曲线 贝塞尔曲线参考 : https://github.com/venshine/BezierMaker 一.一阶贝塞尔曲线 一阶贝塞尔曲线 本质 是一条直 ...

  7. 贝塞尔曲线和贝塞尔曲面_TimelineMax:处理贝塞尔(Bézier)补间

    贝塞尔曲线和贝塞尔曲面 当您需要高级功能时,GSAP插件非常有用. 我将在本教程中解释的BezierPlugin可以帮助沿定义为点/值数组的弯曲贝塞尔曲线路径上的几乎任何一个或多个属性设置动画. 在跳 ...

  8. 史上最全的贝塞尔曲线(Bezier)全解(一):初识贝塞尔曲线

      作为一个有只志向的码农,除了知道一些基本的知识够自己努力搬砖以外,还应该get一些更炫酷的技能,用更优雅的姿势进行搬砖;想要实现一些十分炫酷的效果,贝塞尔曲线就必须进行一些研究了; 最近一段时间, ...

  9. openlayers 绘制动态迁徙线、曲线

    前言:本来懒得写这个博客,实在深感无聊,没啥事情做,出篇博客让大家看看.文章会尽可能简短. 简单效果 掉帧属录屏效果,尚未测试过性能,因为这个可以看自己调节.以下为一条贝塞尔曲线分了180段的效果描述 ...

最新文章

  1. mysql ldf文件太大_sqlserver 2008R2 数据库文件过大解决办法
  2. 算法--------设计哈希集合
  3. java中的stack类和C++中的stack类的区别
  4. git 修改分支名字_开发中必须要掌握的 Git 技巧
  5. 强烈推荐|我做系统架构的一些原则
  6. ibm v3700添加硬盘_机 · 科普帖丨从大到小又从小到大,硬盘这些年是怎么过来的...
  7. 2014.7.7模拟赛【无线通讯网】
  8. IIS发布web网站
  9. css中的盒模型box-sizing
  10. 转:Andriod Phone模块相关
  11. CSS 选择器(超级详细,欢迎补充)
  12. ctf解题--当眼花的时候,会显示两张图(隐写)
  13. 脑残的NODE_MODULE_VERSION,node冷眼看着electron
  14. 计算机对未来和生活,从计算机的进化引发对未来生活的展望
  15. System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d
  16. 听故事的男同学,没了666
  17. Windows环境运行shell脚本
  18. 定时任务Schedule的使用
  19. Firefox - 附加组件 - 插件 - Shockwave Flash
  20. 测试结果-HTML测试报告

热门文章

  1. 送5本刚出版的Flink实战书籍!
  2. Revit坐标系概念深入理解及应用:内部点、原点、项目基点、测量点、共享坐标系、地理坐标及之间关系和衍生概念操作(详细)
  3. 【中国善网】爱心接力,呵护女生健康
  4. matlab编程中函数randperm用法
  5. 右键文件夹时资源管理器重启
  6. Python一键下载原神官网漫画,送给同样喜欢原神的人美心善你们
  7. 【CSUOJ 1623】Inspectors
  8. 学生党平价无线蓝牙耳机推荐,2022性价比高的无线蓝牙耳机品牌推荐
  9. 查找算法——二分查找【代码实现】
  10. oracle游标查询工资,oracle游标:查询并打印员工的姓名和薪水