1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """
32 Encode a list of lat,lon points in the base64 format that Google Maps
33 understands.
34
35 This is code (very) gratefully borrowed from the
36 U{bycycle.org<http://bycycle.org/>} project. All credit goes to them. I
37 simply took advantage of their good work.
38 """
39
40 import math
41
42 threshold = .00001
43 num_levels = 4
44 zoom_factor = 32
45 zoom_level_breaks = []
46 for i in range(num_levels):
47 zoom_level_breaks.append(threshold * (zoom_factor ** (num_levels - i - 1)))
48
50 """
51 Encode a set of lat/long points. Generate:
52 - An encoded string representing points within our error
53 C{threshold},
54 - An encoded string representing the maximum zoom level for each of
55 those points
56
57 Example::
58
59 >>> pairs = ((38.5, -120.2), (43.252, -126.453), (40.7, -120.95))
60 >>> encode_pairs(pairs)
61 ('_p~iF~ps|U_c_\\\\fhde@~lqNwxq`@', 'BBB')
62
63 @param points: a list of lat/long points ((lat, long), ...) Note carefully
64 that the order is latitude, longitude
65 @type points: C{list} of (C{float},C{float})
66
67 @return: points string, zoom string
68 @rtype: C{string},C{string}
69 """
70 encoded_points = []
71 encoded_levels = []
72
73 distances = douglas_peucker_distances(points)
74 points_of_interest = []
75 for i, d in enumerate(distances):
76 if d is not None:
77 lat, long = points[i]
78 points_of_interest.append((lat, long, d))
79
80 lat_prev, long_prev = 0, 0
81 for lat, long, d in points_of_interest:
82 encoded_lat, lat_prev = encode_lat_or_long(lat, lat_prev)
83 encoded_long, long_prev = encode_lat_or_long(long, long_prev)
84 encoded_points += [encoded_lat, encoded_long]
85 encoded_level = encode_unsigned(num_levels - compute_level(d) - 1)
86 encoded_levels.append(encoded_level)
87
88 encoded_points_str = ''.join(encoded_points)
89 encoded_levels_str = ''.join([str(l) for l in encoded_levels])
90 return encoded_points_str, encoded_levels_str
91
93 """
94 Encode a single latitude or longitude.
95
96 Example::
97
98 >>> x = -179.9832104
99 >>> encoded_x, prev = encode_lat_or_long(x, 0)
100 >>> encoded_x
101 '`~oia@'
102 >>> prev
103 -17998321
104 >>> x = -120.2
105 >>> encode_lat_or_long(x, prev)
106 ('al{kJ', -12020000)
107
108 @param x: the latitude or longitude to encode
109 @param prev_int: the integer value of the previous latitude or longitude
110
111 @return: the encoded value and its int value, which is used
112 @rtype: C{string}, C{int}
113 """
114 int_value = int(x * 1e5)
115 delta = int_value - prev_int
116 return encode_signed(delta), int_value
117
119 tmp = n << 1
120 if n < 0:
121 tmp = ~tmp
122 return encode_unsigned(tmp)
123
125 tmp = []
126
127 while n >= 32:
128 tmp.append(n & 31)
129 n = n >> 5
130 tmp = [(c | 0x20) for c in tmp]
131 tmp.append(n)
132 tmp = [(i + 63) for i in tmp]
133 tmp = [chr(i) for i in tmp]
134 tmp = ''.join(tmp)
135 return tmp
136
138 distances = [None] * len(points)
139 distances[0] = threshold * (zoom_factor ** num_levels)
140 distances[-1] = distances[0]
141
142 if(len(points) < 3):
143 return distances
144
145 stack = [(0, len(points) - 1)]
146 while stack:
147 a, b = stack.pop()
148 max_dist = 0
149 for i in range(a + 1, b):
150 dist = distance(points[i], points[a], points[b])
151 if dist > max_dist:
152 max_dist = dist
153 max_i = i
154 if max_dist > threshold:
155 distances[max_i] = max_dist
156 stack += [(a, max_i), (max_i, b)]
157
158 return distances
159
161 """
162 Compute distance of C{point} from line C{A}, C{B}.
163 """
164 if A == B:
165 out = math.sqrt(
166 (B[0] - point[0]) ** 2 +
167 (B[1] - point[1]) ** 2
168 )
169 else:
170 u = (
171 (((point[0] - A[0]) * (B[0] - A[0])) +
172 ((point[1] - A[1]) * (B[1] - A[1]))) /
173 (((B[0] - A[0]) ** 2) + ((B[1] - A[1]) ** 2))
174 )
175 if u <= 0:
176 out = math.sqrt(
177 ((point[0] - A[0]) ** 2) + ((point[1] - A[1]) ** 2)
178 )
179 elif u >= 1:
180 out = math.sqrt(
181 ((point[0] - B[0]) ** 2) + ((point[1] - B[1]) ** 2)
182 )
183 elif 0 < u < 1:
184 out = math.sqrt(
185 ((((point[0] - A[0]) - (u * (B[0] - A[0]))) ** 2)) +
186 ((((point[1] - A[1]) - (u * (B[1] - A[1]))) ** 2))
187 )
188 return out
189
191 """
192 Compute the appropriate zoom level of a point in terms of its distance from
193 the relevant segment in the DP algorithm.
194 """
195 if distance > threshold:
196 level = 0
197 while distance < zoom_level_breaks[level]:
198 level += 1
199 return level
200
207
211
213 pairs = [(38.5, -120.2)]
214 expected_encoding = '_p~iF~ps|U', 'B'
215 assert encode_pairs(pairs) == expected_encoding
216
218 pairs = (
219 (38.5, -120.2),
220 (40.7, -120.95),
221 (43.252, -126.453),
222 (40.7, -120.95),
223 )
224 expected_encoding = '_p~iF~ps|U_ulLnnqC_mqNvxq`@~lqNwxq`@', 'BBBB'
225 assert encode_pairs(pairs) == expected_encoding
226
227 pairs = (
228 (37.4419, -122.1419),
229 (37.4519, -122.1519),
230 (37.4619, -122.1819),
231 )
232 expected_encoding = 'yzocFzynhVq}@n}@o}@nzD', 'B@B'
233 assert encode_pairs(pairs) == expected_encoding
234