如何使用向量计算路径步骤

背景

类似于地图导航的应用场景,当在地图里面,知道了起点,知道了终点,那么必然有一条,最短(或者最合适)的路径,有了路径数据,就能在地图上绘制一根路线,当用户遵循这个路线走的过程,就会分解成多个步骤。本片文章旨在如何利用路线的数据,计算出步骤的数据。

问题

1.什么是路径? 2.什么是步骤?

路径图.png

如图所示,以苏州站到苏州中心这段地图导航为例。
路径就是指截图中绿色的线条,拐点就是指路线变化方向时候的那个点,在这个截图里面,蓝色的点基本都是拐点(别杠,有几个确实不是)。
为了简化问题,先暂时不考虑曲线路径(要考虑也可以,把曲线理解成无数个拐点之间组成的折线,这样会产生很多相近数据,不便于分析),那么其实路径就是一个个拐点之间连接产生的折线。换句话说,有拐点就能画线。
步骤就是左侧截图框框的部分,可以看出几乎每次拐弯之前都被拆解成了一个步骤。

那么,我们就可以这么理解,一个导航的过程,就是由若干个步骤组合而成,同时这些步骤是跟路径和拐点息息相关的。所以在项目开发的时候,就会有如题的需求。

分析

既然如此,我们是知道路径的,即我们都知道所有的拐点,比方说为pointList,里面的元素均为CGPoint坐标,这个坐标是指拐点,在当前地图上的坐标,对于iOS而言,就理解为frame。
我们来看看每一条步骤,包含了哪些基本信息:
1.方向,这一步是直走还是左拐还是右拐 2.距离,这一步我要走多少。
至于走完了一步,是从哪条路到了哪条街,这个属于上层业务的范畴,不予讨论。

由易到难,距离很好算,既然知道了两个点,直接勾股定理直接能算出来,再根据你的地图比例尺,换算成实际长度单位。基本代码如下

let distance = sqrt(pow(point.x - previousPonit.x, 2) + pow(point.y - previousPonit.y, 2))

看来主要问题是在第1个,如何去确定步骤的方向。这个时候就需要用到了高中数学学到的向量知识了。如果忘记了,还请自行百度。
其实每一个步骤,都可以抽象一个向量。从拐点中取出任意3点A,B,C,那么向量AB就是第一步,向量BC就是第二步。如下图所示,我们要求的就是如何描述,到达B点时,C距离B的方位。


向量.png

从图上就可以看出,只要将B往左偏移θ即可,那么如何动态的用代码计算呢?

1.角度大小

由图看出,θ就是向量AB和向量BC的夹角,那么可以根据向量数量积来计算出θ的余弦值以及大小。


公式.png
let ab = CGPoint(x: secondPoint.x - firstPoint.x, y: secondPoint.y - firstPoint.y)
let bc = CGPoint(x: thirdPoint.x - secondPoint.x, y: thirdPoint.y - secondPoint.y)
let cosA = (ab.x * bc.y + bc.x * ab.y) / ( sqrt(pow(ab.x, 2) + pow(ab.y, 2)) + sqrt(pow(bc.x, 2) + pow(bc.y, 2)) )
let A = acos(Double(cosA))

有人可能会注意到,我图里有两个C,一个是C1另个C2,因为光知道一个角度的大小,站在B点时有两种选择的,顺时针和逆时针方向旋转,所以必须要想办法确定旋转方向。

2.偏移方向

关于便宜方向的计算,也许有人会说,这个很简单啊,你看向量AB和X轴的夹角为α,θ已经算出来了,比较这两根大小,α < θ,逆时针反之则顺时针。非也非也!
首先第一步计算出向量余弦值,用反函数求出角度的时候,也有个问题,余弦函数是有周期的为2π,B点可选的角度范围也是0到2π,其实在这个范围内,取出的角度有可能是两个值。当然,在实际场景中,用户都回取最小的θ,为什么呢?因为能左转90°的事情办到的事情,没人愿意右转270°达到同一目标。
那么应该如何做处理呢?

顺逆时针.png

如图所示,我们把步骤1,也就是AB向量现在坐标轴中体现出来,虚线就是向量AB所在的直线,由图分析可知,从B 出发,到达在直线上方的点,逆时针旋转的角度最小;到达直线下方的点,顺时针旋转角度最小;
那么如何表示直线? 这还不简单,一次函数 y=kx + b, b为0,斜率k = y1/x1 。但是并不是每次都是这样的,当前AB向量所在区域为第一象限,如果是在其他象限,结果会有不同。所以最终结果如下
最终结果.png

第一四象限情况相同,第二三象限情况相同。那么问题就迎刃而解了。但是还要考虑一些特殊情况,那就是水平和竖直的时候,是没有斜率的,要特殊处理一下。

func caculateDetial(firstPoint: CGPoint, secondPoint: CGPoint, thirdPoint: CGPoint) -> (Bool, Double) {
        
        let ab = CGPoint(x: secondPoint.x - firstPoint.x, y: secondPoint.y - firstPoint.y)
        let bc = CGPoint(x: thirdPoint.x - secondPoint.x, y: thirdPoint.y - secondPoint.y)
        let cosA = (ab.x * bc.y + bc.x * ab.y) / ( sqrt(pow(ab.x, 2) + pow(ab.y, 2)) + sqrt(pow(bc.x, 2) + pow(bc.y, 2)) )
        let a = acos(Double(cosA))
        var isClockWise = false
        if ab.y == 0 {
            // horizontal
            if ab.x > 0 {
                isClockWise = bc.y < 0
            } else if ab.x < 0 {
                isClockWise = bc.y > 0
            }
        } else if ab.x == 0 {
            // vertical
            if ab.y > 0 {
                isClockWise = bc.x > 0
            } else if ab.y < 0 {
                isClockWise = bc.x < 0
            }
        } else {
            // general
            let k = CGFloat(ab.y / ab.x)
            if ab.x > 0 {
                // first fourth qudrant
                if bc.y < k * bc.x {
                    isClockWise = true
                } else if bc.y > k * bc.x {
                    isClockWise = false
                }
            } else if ab.x < 0 {
                // second third qudrant
                if bc.y < k * bc.x {
                    isClockWise = false
                } else if bc.y > k * bc.x {
                    isClockWise = true
                }
            }
        }
        // 要取反,笛卡尔坐标系和frame的坐标系有区别
        return (!isClockWise, a)
    }

特别提醒
数学里面用的坐标系都是笛卡尔坐标系,坐标原点是在左下角,而iPhone手机屏幕的frame的坐标原点是在左上角,所以要在最后对结果做取反操作。
经过这样的计算,就能把每一步要走多少,往哪个方向拐多少角度都能够计算出来。

总结

数学真的很有用!最后,数学帝镇楼!


数学帝.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342