Changeset 886

Show
Ignore:
Timestamp:
05/12/07 15:49:33 (5 years ago)
Author:
bycycle
Message:

Added proper levels encoding using info/algorithms from
 http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/

Updated Route entity to use the properly encoded levels instead of just
hardcoding all Ps

Location:
core/trunk/byCycle/model
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • core/trunk/byCycle/model/glineenc.py

    r883 r886  
    1 def encode_pairs(pairs): 
    2     """Encode a set of lat/long pairs. 
     1import math 
    32 
    4     ``pairs`` 
    5         A list of lat/long pairs ((lat, long), ...) 
     3 
     4threshold = .00001 
     5num_levels = 4 
     6zoom_factor = 32 
     7zoom_level_breaks = [] 
     8for i in range(num_levels): 
     9    zoom_level_breaks.append(threshold * (zoom_factor ** (num_levels - i - 1))) 
     10     
     11 
     12def encode_pairs(points): 
     13    """Encode a set of lat/long points. 
     14 
     15    ``points`` 
     16        A list of lat/long points ((lat, long), ...) 
    617        Note carefully that the order is latitude, longitude 
    718 
    819    return 
    9         An encoded string representing all pairs 
     20        - An encoded string representing points within our error 
     21          ``threshold``, 
     22        - An encoded string representing the maximum zoom level for each of 
     23          those points 
    1024 
    1125    Example:: 
     
    1327        >>> pairs = ((38.5, -120.2), (43.252, -126.453), (40.7, -120.95)) 
    1428        >>> encode_pairs(pairs) 
    15         '_p~iF~ps|U_c_\\\\fhde@~lqNwxq`@' 
     29        ('_p~iF~ps|U_c_\\\\fhde@~lqNwxq`@', 'BBB') 
    1630 
    1731    """ 
    18     result = [] 
     32    encoded_points = [] 
     33    encoded_levels = [] 
     34     
     35    distances = douglas_peucker_distances(points) 
     36    points_of_interest = [] 
     37    for i, d in enumerate(distances): 
     38        if d is not None: 
     39            lat, long = points[i] 
     40            points_of_interest.append((lat, long, d)) 
     41     
    1942    lat_prev, long_prev = 0, 0 
    20     for pair in pairs: 
    21         encoded_val, lat_prev = encode(pair[0], lat_prev) 
    22         result.append(encoded_val) 
    23         encoded_val, long_prev = encode(pair[1], long_prev) 
    24         result.append(encoded_val) 
    25     result = ''.join(result) 
    26     return result 
     43    for lat, long, d in points_of_interest: 
     44        encoded_lat, lat_prev = encode_lat_or_long(lat, lat_prev) 
     45        encoded_long, long_prev = encode_lat_or_long(long, long_prev) 
     46        encoded_points += [encoded_lat, encoded_long] 
     47        encoded_level = encode_unsigned(num_levels - compute_level(d) - 1) 
     48        encoded_levels.append(encoded_level) 
    2749 
    28 def encode(x, prev_int): 
     50    encoded_points_str = ''.join(encoded_points) 
     51    encoded_levels_str = ''.join([str(l) for l in encoded_levels]) 
     52    return encoded_points_str, encoded_levels_str 
     53 
     54def encode_lat_or_long(x, prev_int): 
    2955    """Encode a single latitude or longitude. 
    3056     
     
    3359         
    3460    ``prev_int`` 
    35         The int value of the previous latitude or longitude 
     61        The integer value of the previous latitude or longitude 
    3662         
    3763    Return the encoded value and its int value, which is used 
     
    4066     
    4167        >>> x = -179.9832104 
    42         >>> encoded_x, prev = encode(x, 0) 
     68        >>> encoded_x, prev = encode_lat_or_long(x, 0) 
    4369        >>> encoded_x 
    4470        '`~oia@' 
     
    4672        -17998321 
    4773        >>> x = -120.2 
    48         >>> encode(x, prev) 
     74        >>> encode_lat_or_long(x, prev) 
    4975        ('al{kJ', -12020000) 
    5076     
    5177    """ 
    5278    int_value = int(x * 1e5) 
    53     res = int_value - prev_int 
    54     res = res << 1 
    55     if res < 0: 
    56         res = ~res 
    57     chunk = res 
    58     res = [] 
     79    delta = int_value - prev_int 
     80    return encode_signed(delta), int_value 
     81 
     82def encode_signed(n): 
     83    tmp = n << 1 
     84    if n < 0: 
     85        tmp = ~tmp 
     86    return encode_unsigned(tmp) 
     87 
     88def encode_unsigned(n): 
     89    tmp = [] 
    5990    # while there are more than 5 bits left (that aren't all 0)... 
    60     while chunk >= 32:  # 32 == 0xf0 == 100000 
    61         res.append(chunk & 31)  # 31 == 0x1f == 11111 
    62         chunk = chunk >> 5 
    63     res = [(c | 0x20) for c in res] 
    64     res.append(chunk) 
    65     res = [(i + 63) for i in res] 
    66     res = [chr(i) for i in res] 
    67     res = ''.join(res) 
    68     return res, int_value 
     91    while n >= 32:  # 32 == 0xf0 == 100000 
     92        tmp.append(n & 31)  # 31 == 0x1f == 11111 
     93        n = n >> 5 
     94    tmp = [(c | 0x20) for c in tmp] 
     95    tmp.append(n) 
     96    tmp = [(i + 63) for i in tmp] 
     97    tmp = [chr(i) for i in tmp] 
     98    tmp = ''.join(tmp) 
     99    return tmp     
     100 
     101def douglas_peucker_distances(points): 
     102    distances = [None] * len(points) 
     103    distances[0] = threshold * (zoom_factor ** num_levels) 
     104    distances[-1] = distances[0] 
     105 
     106    if(len(points) < 3): 
     107        return distances 
     108 
     109    stack = [(0, len(points) - 1)] 
     110    while stack: 
     111        a, b = stack.pop() 
     112        max_dist = 0 
     113        for i in range(a + 1, b): 
     114            dist = distance(points[i], points[a], points[b]) 
     115            if dist > max_dist: 
     116                max_dist = dist 
     117                max_i = i 
     118        if max_dist > threshold: 
     119            distances[max_i] = max_dist 
     120            stack += [(a, max_i), (max_i, b)] 
     121 
     122    return distances 
     123 
     124def distance(point, A, B): 
     125    """Compute distance of ``point`` from line ``A``, ``B``.""" 
     126    if A == B: 
     127        out = math.sqrt( 
     128            (B[0] - point[0]) ** 2 + 
     129            (B[1] - point[1]) ** 2 
     130        ) 
     131    else: 
     132        u = ( 
     133            (((point[0] - A[0]) * (B[0] - A[0])) + 
     134             ((point[1] - A[1]) * (B[1] - A[1]))) /  
     135            (((B[0] - A[0]) ** 2) +  ((B[1] - A[1]) ** 2)) 
     136        ) 
     137        if u <= 0: 
     138            out = math.sqrt( 
     139                ((point[0] - A[0]) ** 2) + ((point[1] - A[1]) ** 2) 
     140            ) 
     141        elif u >= 1: 
     142            out = math.sqrt( 
     143                ((point[0] - B[0]) ** 2) + ((point[1] - B[1]) ** 2) 
     144            ) 
     145        elif 0 < u < 1: 
     146            out = math.sqrt( 
     147                ((((point[0] - A[0]) - (u * (B[0] - A[0]))) ** 2)) + 
     148                ((((point[1] - A[1]) - (u * (B[1] - A[1]))) ** 2)) 
     149            ) 
     150    return out 
     151 
     152def compute_level(distance): 
     153    """Compute the appropriate zoom level of a point in terms of its  
     154    distance from the relevant segment in the DP algorithm.""" 
     155    if distance > threshold: 
     156        level = 0 
     157    while distance < zoom_level_breaks[level]: 
     158        level += 1 
     159    return level 
    69160 
    70161def test_encode_negative(): 
    71162    f = -179.9832104 
    72     assert encode(f, 0)[0] == '`~oia@' 
     163    assert encode_lat_or_long(f, 0)[0] == '`~oia@' 
    73164     
    74165    f = -120.2 
    75     assert encode(f, 0)[0] == '~ps|U' 
     166    assert encode_lat_or_long(f, 0)[0] == '~ps|U' 
    76167 
    77168def test_encode_positive(): 
    78169    f = 38.5 
    79     assert encode(f, 0)[0] == '_p~iF' 
     170    assert encode_lat_or_long(f, 0)[0] == '_p~iF' 
    80171 
    81172def test_encode_one_pair(): 
    82173    pairs = [(38.5, -120.2)] 
    83     expected_encoding = '_p~iF~ps|U' 
     174    expected_encoding = '_p~iF~ps|U', 'B' 
    84175    assert encode_pairs(pairs) == expected_encoding 
    85176 
     
    91182        (40.7, -120.95), 
    92183    ) 
    93     expected_encoding = '_p~iF~ps|U_ulLnnqC_mqNvxq`@~lqNwxq`@' 
     184    expected_encoding = '_p~iF~ps|U_ulLnnqC_mqNvxq`@~lqNwxq`@', 'BBBB' 
    94185    assert encode_pairs(pairs) == expected_encoding 
    95186     
     
    99190        (37.4619, -122.1819), 
    100191    ) 
    101     expected_encoding = 'yzocFzynhVq}@n}@o}@nzD' 
     192    expected_encoding = 'yzocFzynhVq}@n}@o}@nzD', 'B@B' 
    102193    assert encode_pairs(pairs) == expected_encoding     
  • core/trunk/byCycle/model/route.py

    r883 r886  
    4949        bounds = self.linestring_ll.envelope() 
    5050        centroid = bounds.centroid() 
     51        google_points, google_levels = glineenc.encode_pairs(pairs) 
    5152        route = { 
    5253            'start': dict(self.start), 
     
    6061            'directions': self.directions, 
    6162            'distance': self.distance, 
    62             'google_points': glineenc.encode_pairs(pairs), 
    63             'google_levels': 'P' * len(pairs), 
     63            'google_points': google_points, 
     64            'google_levels': google_levels, 
    6465        } 
    6566        route['start']['geocode'] = route['start']['geocode'].to_builtin()