使用JavaScript Canvas模拟绘制带斑马线的十字路口及其细节

最近,在工作中需要模拟绘制一些带斑马线的十字路口,整个实现的过程中用了不少的时间,把具体的绘制过程简单的记录一下。

首先我们看一下手上有哪些数据,在下图中,左边就是我们能拿到的全部数据了。而右边的效果则是我们需要的图形。

https://static.zhuwenlong.com/upload/image/1509690126944-20171103-1.png?imageView2/0/w/900/

实际上我们有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。

归一化之后,我们就可以简单的画出道路了,这里我们以中心点为起点,然后向各个道路的方向绘制射线,得到如下图:

https://static.zhuwenlong.com/upload/image/1509691616255-2.png?imageView2/0/w/900/

除了上图的情况外,我们还可能有其他的一些情况:

https://static.zhuwenlong.com/upload/image/1509691759423-3.png

https://static.zhuwenlong.com/upload/image/1509691789621-4.png

接下来我们来绘制斑马线,让我们以真实的斑马线为例,通常情况下,斑马线是为了让行人到达道路的另一边,同时斑马线也是非常靠近路口中央的。所以,如果我们能获得每条道路的交叉点,然后连接同一条道路左右两边的交叉点。这样我们连接出来的直线就是我们需要的斑马线了。

万事俱备只欠东风,接下来我们尝试计算出每条道路与其他道路的交叉点。目前,道路的方向是随机的,如果想要拿到一条道路左右2个方向的相邻道路的话,我们必须对道路进行排序。我们取y轴的上半轴为参考轴,这样我们拍完序之后,对于的顺序就是:道路2、道路1、道路3、道路0。

现在我们队道路进行排序之后,就可以开始着手计算交叉点了,请看下图:

https://static.zhuwenlong.com/upload/image/1509694632931-4.png

我们以道路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个点的坐标:

https://static.zhuwenlong.com/upload/image/1509696361421-5.png

接下来就是令人激动的斑马线绘制过程了,那么要如何去画斑马线呢?大脑的第一反应是计算出下图的A1、A2、B1、B2...的坐标,然后连接起来就可以了。

https://static.zhuwenlong.com/upload/image/1509697211216-6.png

但是!我勒个去,计算这些点的坐标太过麻烦,死了N多脑细胞之后,突然发现,我们原来不需要去计算这些坐标。。。

https://static.zhuwenlong.com/upload/image/1509697737386-7.png

如上图所示,我们只要计算出这个斑马线的中心点,以及斑马线的角度,然后将画布的中心点,通过平移、旋转等操作,移动到斑马线的中心点,且使得斑马线水平。然后只要在这条水平线上绘制出斑马线就可以了,比如斑马线移动之后的首尾坐标是[-20,0]和[20,0],然后我们每隔5像素绘制一个斑马线,那么只要在 [-20,0]、[-15,0]、[-10,0] …… [15,0]、[20,0] 这些位置绘制上斜线就好啦!

最终我们得到了下面的图:

https://static.zhuwenlong.com/upload/image/1509698015365-8.png

恭喜,恭喜,终于完成了一个比较好看的路口的绘制了(至于箭头嘛,同样的平移、旋转大法可以很容易的搞定)

但是!高兴的太早了,我们会有一些这样的 bad case:

https://static.zhuwenlong.com/upload/image/1509698109152-9.png

第一眼看着就觉得有点诡异,看左右两条斑马线的交叉点,实际的道路会是这样的么?显然不是,实际向这种接近平行的道路,他的斑马线仅仅是垂直的到马路的对面,绝对不会相交。那么我们就需要进一步的处理了,这里我们假设两条路的夹角大于160°即可以认为他是一条直路,对于这样的道路,我们将其斑马按垂直于道路重新计算,过程就不赘述了,具体的看下图:

https://static.zhuwenlong.com/upload/image/1509699435218-10.png?imageView2/0/w/900/

这样,经过各种优化之后,我们就可以拿到一条“像模像样”的十字路口了,但是此时仍然会有一些坑,这里由于篇幅的有限就不展开了,有兴趣的小伙伴可以在这篇文章下面留言,我们可以继续探讨。

Write a response...
Mofei Zhu
publish
Mofei
2017-11-16 13:31
@falost  多张图片转成base64再合并起来?这种情况我到时候没有尝试过~~ 不过觉得可以现场重现的话,我们倒是可以去排除一下到底是哪里出了问题。
0
 Replay
@Mofei  
Replay
falost
2017-11-16 11:43
学习了,之前尝试在手机端,将多张图片合成一张图片,因为将图片转成了base64, 在合成的时候出现了手机卡死的状态,不知道是方法的原因 还是性能的问题呢 
0
 Replay
@falost  
Replay
Mofei
2017-11-16 11:13
@falost  可视化的话,比较常见的有下面的几个方式,1. DOM模拟 2. SVG 3. Canvas 4. WebGL, 在可视化领域几乎不太会去用DOM,其性能是最差的。其次是SVG和Canvas,SVG的优点在于其矢量绘制,但是缺点和DOM类似大量使用的情况下会产生很多的DOM元素,对于页面来说性能也是很受影响的。最常用的是Canvas,对于浏览器来说,他仅仅是一个可以绘制的DOM元素,所有的计算和绘制是在内存或者GPU中进行,浏览器只需要负责展示绘制的结果就可以了,这种情况下,我们甚至可以尝试在一个页面中绘制百万级别的数据,DOM是远远达不到的。WebGL是相对来说最理想的可视化解决方案,但是它的学习成本偏高,外加兼容性和稳定性不是特别的理想,目前驾驭的比较好的项目还是比较少的。
2
 Replay
@Mofei  
Replay
falost
2017-11-16 11:06
厉害厉害,canvas 很神奇,但是好像也很费性能吧
0
 Replay
@falost  
Replay
Mofei
2017-11-07 13:40
@剧中人「大神」  剧大神英明
0
 Replay
@Mofei  
Replay
剧中人「大神」
2017-11-07 11:36
很深入浅出的文章,我为 Mofei 大神打电话! 
0
 Replay
@剧中人「大神」  
Replay