Package sabx10 :: Package map :: Module glineenc
[hide private]
[frames] | no frames]

Source Code for Module sabx10.map.glineenc

  1  ############################################################################### 
  2  # 
  3  # Copyright (c) 2007 Wyatt Baldwin 
  4  # 
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy 
  6  # of this software and associated documentation files (the "Software"), to deal 
  7  # in the Software without restriction, including without limitation the rights 
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  9  # copies of the Software, and to permit persons to whom the Software is 
 10  # furnished to do so, subject to the following conditions: 
 11  # 
 12  # The above copyright notice and this permission notice shall be included in 
 13  # all copies or substantial portions of the Software. 
 14  # 
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 21  # SOFTWARE. 
 22  # 
 23  ############################################################################### 
 24  # 
 25  # Please note that this is not a part of the sabx10 package as far as licensing 
 26  # goes.  This is a completely seperate entity that I included for convenience. 
 27  # This license applies ONLY to this file (glineenc.py), and to no other 
 28  # components of the sabx10 package. 
 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   
49 -def encode_pairs(points):
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
92 -def encode_lat_or_long(x, prev_int):
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
118 -def encode_signed(n):
119 tmp = n << 1 120 if n < 0: 121 tmp = ~tmp 122 return encode_unsigned(tmp)
123
124 -def encode_unsigned(n):
125 tmp = [] 126 # while there are more than 5 bits left (that aren't all 0)... 127 while n >= 32: # 32 == 0xf0 == 100000 128 tmp.append(n & 31) # 31 == 0x1f == 11111 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
137 -def douglas_peucker_distances(points):
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
160 -def distance(point, A, B):
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
190 -def compute_level(distance):
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
201 -def test_encode_negative():
202 f = -179.9832104 203 assert encode_lat_or_long(f, 0)[0] == '`~oia@' 204 205 f = -120.2 206 assert encode_lat_or_long(f, 0)[0] == '~ps|U'
207
208 -def test_encode_positive():
209 f = 38.5 210 assert encode_lat_or_long(f, 0)[0] == '_p~iF'
211
212 -def test_encode_one_pair():
213 pairs = [(38.5, -120.2)] 214 expected_encoding = '_p~iF~ps|U', 'B' 215 assert encode_pairs(pairs) == expected_encoding
216
217 -def test_encode_pairs():
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