前语
文中的代码表达采用Java,假如不想看原理,能够直接跳到博客末尾,有我写的一个Java版的demo 一般来说,轨道是由若干个轨道点组成的数组来表达的,每个轨道点能够表达为 p=(x,y,t,A)p = (x, y, t, A)。其间 x,yx, y 是其空间坐标(一般来说,空间坐标用经纬度表明,即xx一般对应经度(longitude),yy一般对应纬度(latitude)),tt是记录这个轨道点的时刻戳(Timestamp), AA则是这个轨道点的特点。假如只想保留一个轨道点最基本的信息,则只需要保留其空间信息和时刻信息,即能够将一个轨道点表明为 p=(x,y,t)p = (x, y, t)。在写代码时,一个轨道点的类型能够界说如下:
class PointTs {
double lng;
double lat;
long timestamp;
}
而这样保存一个轨道点,xx和yy一般运用double类型,tt一般运用long类型,再加上至少3个字符的分隔符(分隔x,y,t和每个轨道点),一个轨道点就要占 Double.SIZE * 2 + Long.SIZE + Character.SIZE * 3 == 27 * Byte.SIZE
, 也便是说保存一个轨道点至少需要占27个字节。在现在轨道数量越来越大的情况下,有必要运用轨道紧缩的算法来处理轨道数据量过大的问题。在查阅了资料后,我在Google地图开发渠道中看到了Google采用的轨道数据紧缩算法,名为Polyline encoding algorithm(需要科学上网),能够将若干个轨道点编码成一串字符串,而且完成轨道的紧缩。这是一个有损的紧缩算法,而且能够紧缩有符号的数字。
接下来我将从编码和解码两个方面解析这个算法,并给出示例代码。
编码
Polyline encoding算法的核心思维是对一个浮点数进行紧缩,比方说,有一个经度为:
-179.9832104
关于这样的浮点型数字,首要需要做的是想办法去掉其小数点。所以,能够预设一个10的整数倍的值来与这个浮点数相乘,并获得整数。
-
在这里,我预设这个值为10510^5,将这个经度乘以10510^5,得到:
-17998321
-
将上一步得到的整数转换成二进制,由于我们的值是负数,得到的是
17998321
的补码:11111110 11101101 01011110 00001111
-
将二进制数左移一位:
11111101 11011010 10111100 00011110
-
假如本来的数是负数,则取反码:
00000010 00100101 01000011 11100001
3、4两步的目的是为了区分正负数,这样做后,负数的最终一位都是1
,正数的最终一位都是0
-
从右到左,每5位分为一组(一组称为一个chunk):
00001 00010 01010 10000 11111 00001
这样能够放弃高位的0,节省空间。 -
将这些分组次序回转(reverse order):
00001 11111 10000 01010 00010 00001
这样做的目的是在写代码时能够每次从低位取数据,更适合代码完成。由于一般来说,写代码读二进制时,读高位比较复杂,而读低位较简单(例如,读最终五位只需要&0x1F
),读到最终几位后能够直接append到成果中,不必刺进到成果的前端,效率更高。 -
除了最终一个chunk,其他chunk和
0x20
进行或
操作:100001 111111 110000 101010 100010 000001
这一步的目的是确保只要最终一个chunk是小于0x20
的。起到了分隔的作用。 -
分别将每个二进制的chunk转化为十进制的数字:
33 63 48 42 34 1
-
对每个数字都加上63:
96 126 111 105 97 64
由于ASCII码前63位中有许多不行显示的控制符(比方NULL、换行符之类的),而且第127个是删去符,所以这一步将数字范围控制在了64~126之间。 -
将数字转化成ASCII表中对应的字符:
`~oia@
综上,一个浮点数被编码成了一串字符串,而且自带分隔符。关于时刻戳这样的长整型来说,不进行第1步也能够编码为一串字符串,那么,将经度、纬度和时刻戳的编码拼接起来就能够组成一个轨道点的编码,还不必额外的分隔符。
其实假如独自对一个数字来说,这个方法的紧缩率并不高,有时候还会因小失大。比方上述的数字-179.9832104
,运用这种方法紧缩后成为5个字符,占5个字节。假如将直接乘以10510^5,能够直接用有符号的整型表明,还只占4个字节呢!所以,这个算法还有一个思维便是:后一个值用其与前一个值的差值表明,由于差值比较小,所以在上述的第5步时能够将高位的零放弃掉,节省空间。下面是Google地图开发渠道中给出的比如(这个比如中没有带时刻戳):
能够看到,这个比如中除了第一个轨道点存储时全量信息(完整的经纬度数据)之外,后面的轨道点都是存储的相关于前一个轨道点偏移量。
解码
解码其实就比较简单了,相当于把编码过程倒过来履行一遍。编码中的过程4能够使得我们在解码时知道这个数字是正数仍是负数,过程7能够让我们知道数字的鸿沟在哪,所以仍是比较便利解码的,我在此就不再赘述解码过程了。
Talk is cheap, show me the code
原理知道了之后,代码就比较简单了,贴一个我写的代码: SimpleTrajectoryCompressor