root/spinoffs/glineenc.py

Revision 1199, 6.5 KB (checked in by arb, 3 months ago)

added MIT license to file

  • Property svn:executable set to *
Line 
1# Copyright (c) 2007 Wyatt Baldwin
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation
5# files (the "Software"), to deal in the Software without
6# restriction, including without limitation the rights to use,
7# copy, modify, merge, publish, distribute, sublicense, and/or sell
8# copies of the Software, and to permit persons to whom the
9# Software is furnished to do so, subject to the following
10# conditions:
11#
12# The above copyright notice and this permission notice shall be
13# included in all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22# OTHER DEALINGS IN THE SOFTWARE.
23
24import math
25
26
27threshold = .00001
28num_levels = 4
29zoom_factor = 32
30zoom_level_breaks = []
31for i in range(num_levels):
32    zoom_level_breaks.append(threshold * (zoom_factor ** (num_levels - i - 1)))
33   
34
35def encode_pairs(points):
36    """Encode a set of lat/long points.
37
38    ``points``
39        A list of lat/long points ((lat, long), ...)
40        Note carefully that the order is latitude, longitude
41
42    return
43        - An encoded string representing points within our error
44          ``threshold``,
45        - An encoded string representing the maximum zoom level for each of
46          those points
47
48    Example::
49
50        >>> pairs = ((38.5, -120.2), (43.252, -126.453), (40.7, -120.95))
51        >>> encode_pairs(pairs)
52        ('_p~iF~ps|U_c_\\\\fhde@~lqNwxq`@', 'BBB')
53
54    """
55    encoded_points = []
56    encoded_levels = []
57   
58    distances = douglas_peucker_distances(points)
59    points_of_interest = []
60    for i, d in enumerate(distances):
61        if d is not None:
62            lat, long = points[i]
63            points_of_interest.append((lat, long, d))
64   
65    lat_prev, long_prev = 0, 0
66    for lat, long, d in points_of_interest:
67        encoded_lat, lat_prev = encode_lat_or_long(lat, lat_prev)
68        encoded_long, long_prev = encode_lat_or_long(long, long_prev)
69        encoded_points += [encoded_lat, encoded_long]
70        encoded_level = encode_unsigned(num_levels - compute_level(d) - 1)
71        encoded_levels.append(encoded_level)
72
73    encoded_points_str = ''.join(encoded_points)
74    encoded_levels_str = ''.join([str(l) for l in encoded_levels])
75    return encoded_points_str, encoded_levels_str
76
77def encode_lat_or_long(x, prev_int):
78    """Encode a single latitude or longitude.
79   
80    ``x``
81        The latitude or longitude to encode
82       
83    ``prev_int``
84        The integer value of the previous latitude or longitude
85       
86    Return the encoded value and its int value, which is used
87       
88    Example::
89   
90        >>> x = -179.9832104
91        >>> encoded_x, prev = encode_lat_or_long(x, 0)
92        >>> encoded_x
93        '`~oia@'
94        >>> prev
95        -17998321
96        >>> x = -120.2
97        >>> encode_lat_or_long(x, prev)
98        ('al{kJ', -12020000)
99   
100    """
101    int_value = int(x * 1e5)
102    delta = int_value - prev_int
103    return encode_signed(delta), int_value
104
105def encode_signed(n):
106    tmp = n << 1
107    if n < 0:
108        tmp = ~tmp
109    return encode_unsigned(tmp)
110
111def encode_unsigned(n):
112    tmp = []
113    # while there are more than 5 bits left (that aren't all 0)...
114    while n >= 32:  # 32 == 0xf0 == 100000
115        tmp.append(n & 31)  # 31 == 0x1f == 11111
116        n = n >> 5
117    tmp = [(c | 0x20) for c in tmp]
118    tmp.append(n)
119    tmp = [(i + 63) for i in tmp]
120    tmp = [chr(i) for i in tmp]
121    tmp = ''.join(tmp)
122    return tmp   
123
124def douglas_peucker_distances(points):
125    distances = [None] * len(points)
126    distances[0] = threshold * (zoom_factor ** num_levels)
127    distances[-1] = distances[0]
128
129    if(len(points) < 3):
130        return distances
131
132    stack = [(0, len(points) - 1)]
133    while stack:
134        a, b = stack.pop()
135        max_dist = 0
136        for i in range(a + 1, b):
137            dist = distance(points[i], points[a], points[b])
138            if dist > max_dist:
139                max_dist = dist
140                max_i = i
141        if max_dist > threshold:
142            distances[max_i] = max_dist
143            stack += [(a, max_i), (max_i, b)]
144
145    return distances
146
147def distance(point, A, B):
148    """Compute distance of ``point`` from line ``A``, ``B``."""
149    if A == B:
150        out = math.sqrt(
151            (B[0] - point[0]) ** 2 +
152            (B[1] - point[1]) ** 2
153        )
154    else:
155        u = (
156            (((point[0] - A[0]) * (B[0] - A[0])) +
157             ((point[1] - A[1]) * (B[1] - A[1]))) /
158            (((B[0] - A[0]) ** 2) +  ((B[1] - A[1]) ** 2))
159        )
160        if u <= 0:
161            out = math.sqrt(
162                ((point[0] - A[0]) ** 2) + ((point[1] - A[1]) ** 2)
163            )
164        elif u >= 1:
165            out = math.sqrt(
166                ((point[0] - B[0]) ** 2) + ((point[1] - B[1]) ** 2)
167            )
168        elif 0 < u < 1:
169            out = math.sqrt(
170                ((((point[0] - A[0]) - (u * (B[0] - A[0]))) ** 2)) +
171                ((((point[1] - A[1]) - (u * (B[1] - A[1]))) ** 2))
172            )
173    return out
174
175def compute_level(distance):
176    """Compute the appropriate zoom level of a point in terms of its
177    distance from the relevant segment in the DP algorithm."""
178    if distance > threshold:
179        level = 0
180    while distance < zoom_level_breaks[level]:
181        level += 1
182    return level
183
184def test_encode_negative():
185    f = -179.9832104
186    assert encode_lat_or_long(f, 0)[0] == '`~oia@'
187   
188    f = -120.2
189    assert encode_lat_or_long(f, 0)[0] == '~ps|U'
190
191def test_encode_positive():
192    f = 38.5
193    assert encode_lat_or_long(f, 0)[0] == '_p~iF'
194
195def test_encode_one_pair():
196    pairs = [(38.5, -120.2)]
197    expected_encoding = '_p~iF~ps|U', 'B'
198    assert encode_pairs(pairs) == expected_encoding
199
200def test_encode_pairs():
201    pairs = (
202        (38.5, -120.2),
203        (40.7, -120.95),
204        (43.252, -126.453),
205        (40.7, -120.95),
206    )
207    expected_encoding = '_p~iF~ps|U_ulLnnqC_mqNvxq`@~lqNwxq`@', 'BBBB'
208    assert encode_pairs(pairs) == expected_encoding
209   
210    pairs = (
211        (37.4419, -122.1419),
212        (37.4519, -122.1519),
213        (37.4619, -122.1819),
214    )
215    expected_encoding = 'yzocFzynhVq}@n}@o}@nzD', 'B@B'
216    assert encode_pairs(pairs) == expected_encoding   
Note: See TracBrowser for help on using the browser.