root/spinoffs/glineenc.py

Revision 921, 5.4 KB (checked in by bycycle, 19 months ago)

Moved glineenc module from byCycle core model package to spinoffs. It
needs to be made installable as a separate package.

  • Property svn:executable set to *
Line 
1import math
2
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), ...)
17        Note carefully that the order is latitude, longitude
18
19    return
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
24
25    Example::
26
27        >>> pairs = ((38.5, -120.2), (43.252, -126.453), (40.7, -120.95))
28        >>> encode_pairs(pairs)
29        ('_p~iF~ps|U_c_\\\\fhde@~lqNwxq`@', 'BBB')
30
31    """
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   
42    lat_prev, long_prev = 0, 0
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)
49
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):
55    """Encode a single latitude or longitude.
56   
57    ``x``
58        The latitude or longitude to encode
59       
60    ``prev_int``
61        The integer value of the previous latitude or longitude
62       
63    Return the encoded value and its int value, which is used
64       
65    Example::
66   
67        >>> x = -179.9832104
68        >>> encoded_x, prev = encode_lat_or_long(x, 0)
69        >>> encoded_x
70        '`~oia@'
71        >>> prev
72        -17998321
73        >>> x = -120.2
74        >>> encode_lat_or_long(x, prev)
75        ('al{kJ', -12020000)
76   
77    """
78    int_value = int(x * 1e5)
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 = []
90    # while there are more than 5 bits left (that aren't all 0)...
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
160
161def test_encode_negative():
162    f = -179.9832104
163    assert encode_lat_or_long(f, 0)[0] == '`~oia@'
164   
165    f = -120.2
166    assert encode_lat_or_long(f, 0)[0] == '~ps|U'
167
168def test_encode_positive():
169    f = 38.5
170    assert encode_lat_or_long(f, 0)[0] == '_p~iF'
171
172def test_encode_one_pair():
173    pairs = [(38.5, -120.2)]
174    expected_encoding = '_p~iF~ps|U', 'B'
175    assert encode_pairs(pairs) == expected_encoding
176
177def test_encode_pairs():
178    pairs = (
179        (38.5, -120.2),
180        (40.7, -120.95),
181        (43.252, -126.453),
182        (40.7, -120.95),
183    )
184    expected_encoding = '_p~iF~ps|U_ulLnnqC_mqNvxq`@~lqNwxq`@', 'BBBB'
185    assert encode_pairs(pairs) == expected_encoding
186   
187    pairs = (
188        (37.4419, -122.1419),
189        (37.4519, -122.1519),
190        (37.4619, -122.1819),
191    )
192    expected_encoding = 'yzocFzynhVq}@n}@o}@nzD', 'B@B'
193    assert encode_pairs(pairs) == expected_encoding   
Note: See TracBrowser for help on using the browser.