使用JavaScript Canvas模拟绘制带斑马线的十字路口及其细节
最近,在工作中需要模拟绘制一些带斑马线的十字路口,整个实现的过程中用了不少的时间,把具体的绘制过程简单的记录一下。
首先我们看一下手上有哪些数据,在下图中,左边就是我们能拿到的全部数据了。而右边的效果则是我们需要的图形。
实际上我们有2种数据,中心点数据和道路数据。其中中心点顾名思义就是路口的中心,道路数据我们取的是整条道路离中心点较远处的一点(这里为了把道路方向抽象出来,我们先后尝试了道路离中心点最近的点,以及最远的点,最后发现远端的点才能真实的反映出道路的真实方向)。在这个例子中,我们的中心点坐标是[116.159005, 40.1233605]
,其它4条道路的点为[-79.56, -91.27]
, [71.41, 83.24]
, [178.74, -38.50]
, [-110.2.72, 38.03]
。
为了使得整个过程足够简单,不给我们后续的计算添麻烦,我们把数据进行一次归一化处理,所谓归一化就是去中心点坐标为[0,0]
,其他的点位按照比例计算出相对应的值,所以在这个例子中,我们的道路的x轴需要减去116.159005,y轴需要减去40.1233605。
归一化之后,我们就可以简单的画出道路了,这里我们以中心点为起点,然后向各个道路的方向绘制射线,得到如下图:
除了上图的情况外,我们还可能有其他的一些情况:
接下来我们来绘制斑马线,让我们以真实的斑马线为例,通常情况下,斑马线是为了让行人到达道路的另一边,同时斑马线也是非常靠近路口中央的。所以,如果我们能获得每条道路的交叉点,然后连接同一条道路左右两边的交叉点。这样我们连接出来的直线就是我们需要的斑马线了。
万事俱备只欠东风,接下来我们尝试计算出每条道路与其他道路的交叉点。目前,道路的方向是随机的,如果想要拿到一条道路左右2个方向的相邻道路的话,我们必须对道路进行排序。我们取y轴的上半轴为参考轴,这样我们拍完序之后,对于的顺序就是:道路2、道路1、道路3、道路0。
现在我们队道路进行排序之后,就可以开始着手计算交叉点了,请看下图:
我们以道路2和道路1为例,在图上,黄色的角θ1是道路2和y轴的夹角,紫色的角θ2是道路1和y轴的夹角。[x,y]
是我们需要的交点坐标。如果我们从这个点向路2做垂线,我们就能得到一个直角三角形。在这个直角三角形中,我们刚刚做的这条垂线的宽度正好是道路宽度的一半,三角形的一个锐角等于(θ2 - θ1) / 2
,所以根据三角函数公式,我们可以拿到x,y的坐标:
最终使用的公式是:
sinθ = y / Math.sqrt(x**2 + y**2)
cosθ = x / Math.sqrt(x**2 + y**2)
根据公式,我们可以方便的获得下面4个点的坐标:
接下来就是令人激动的斑马线绘制过程了,那么要如何去画斑马线呢?大脑的第一反应是计算出下图的A1、A2、B1、B2...的坐标,然后连接起来就可以了。
但是!我勒个去,计算这些点的坐标太过麻烦,死了N多脑细胞之后,突然发现,我们原来不需要去计算这些坐标。。。
如上图所示,我们只要计算出这个斑马线的中心点,以及斑马线的角度,然后将画布的中心点,通过平移、旋转等操作,移动到斑马线的中心点,且使得斑马线水平。然后只要在这条水平线上绘制出斑马线就可以了,比如斑马线移动之后的首尾坐标是[-20,0]和[20,0],然后我们每隔5像素绘制一个斑马线,那么只要在 [-20,0]、[-15,0]、[-10,0] …… [15,0]、[20,0] 这些位置绘制上斜线就好啦!
最终我们得到了下面的图:
恭喜,恭喜,终于完成了一个比较好看的路口的绘制了(至于箭头嘛,同样的平移、旋转大法可以很容易的搞定)
但是!高兴的太早了,我们会有一些这样的 bad case:
第一眼看着就觉得有点诡异,看左右两条斑马线的交叉点,实际的道路会是这样的么?显然不是,实际向这种接近平行的道路,他的斑马线仅仅是垂直的到马路的对面,绝对不会相交。那么我们就需要进一步的处理了,这里我们假设两条路的夹角大于160°即可以认为他是一条直路,对于这样的道路,我们将其斑马按垂直于道路重新计算,过程就不赘述了,具体的看下图:
这样,经过各种优化之后,我们就可以拿到一条“像模像样”的十字路口了,但是此时仍然会有一些坑,这里由于篇幅的有限就不展开了,有兴趣的小伙伴可以在这篇文章下面留言,我们可以继续探讨。