Ticket #481: geocode.3.cc

File geocode.3.cc, 7.1 KB (added by Richard Boulton, 13 years ago)

Updated encoding to swap priority of lat and long, to make the end rounding safe, to make the encoding append to a string, and to make the decoding take a pointer and length

Line 
1/** @file geocode.cc
2 * @brief Encodings for geospatial coordinates.
3 */
4/* Copyright (C) 2011 Richard Boulton
5 * Based closely on a python version, copyright (C) 2010 Olly Betts
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to
9 * deal in the Software without restriction, including without limitation the
10 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11 * sell copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23 * IN THE SOFTWARE.
24 */
25
26#include <config.h>
27
28#include <cmath>
29#include <string>
30
31using namespace std;
32
33/** Angles, split into degrees, minutes and seconds.
34 *
35 * Only designed to work with positive angles.
36 */
37struct DegreesMinutesSeconds {
38 int degrees;
39 int minutes;
40 int seconds;
41 int sec16ths;
42
43 // Initialise with a (positive) angle, as an integer representing the
44 // number of 16ths of a second, rounding to nearest. The range of valid
45 // angles is assumed to be 0 <= angle in degres < 360, so range of
46 // angle_16th_secs is 0..20735999, which fits easily into a 32 bit int.
47 DegreesMinutesSeconds(int angle_16th_secs) {
48 degrees = angle_16th_secs / (3600 * 16);
49 angle_16th_secs = angle_16th_secs % (3600 * 16);
50 minutes = angle_16th_secs / (60 * 16);
51 angle_16th_secs = angle_16th_secs % (60 * 16);
52 seconds = angle_16th_secs / 16;
53 sec16ths = angle_16th_secs % 16;
54 }
55};
56
57bool
58geo_encode(double lat, double lon, string & result)
59{
60 // Check range of latitude.
61 if (rare(lat < -90.0 || lat > 90.0)) {
62 return false;
63 }
64 // Calc latitude and longitude in integral number of 16ths of a second
65 int lat_int = round((lat + 90.0) * 57600.0);
66 int lon_int;
67 if (lat_int == 0 || lat_int == 57600 * 180) {
68 lon_int = 0;
69 } else {
70 // Wrap longitude to range [0,360).
71 lon = fmod(lon, 360.0);
72 if (lon < 0) {
73 lon += 360;
74 }
75 lon_int = round(lon * 57600.0);
76 if (lon_int == 57600 * 360) {
77 lon_int = 0;
78 }
79 }
80
81 DegreesMinutesSeconds lat_dms(lat_int);
82 DegreesMinutesSeconds lon_dms(lon_int);
83
84 size_t old_len = result.size();
85 result.resize(old_len + 6);
86
87 // Add degrees parts as first two bytes.
88 unsigned dd = lat_dms.degrees + lon_dms.degrees * 181;
89 // dd is in range 0..180*360+359 = 0..65159
90 result[old_len] = char(dd >> 8);
91 result[old_len + 1] = char(dd & 0xff);
92
93 // Add minutes next; 4 bits from each in the first byte.
94 result[old_len + 2] = char(((lat_dms.minutes / 4) << 4) |
95 (lon_dms.minutes / 4)
96 );
97
98 result[old_len + 3] = char(
99 ((lat_dms.minutes % 4) << 6) |
100 ((lon_dms.minutes % 4) << 4) |
101 ((lat_dms.seconds / 15) << 2) |
102 (lon_dms.seconds / 15)
103 );
104
105 result[old_len + 4] = char(
106 ((lat_dms.seconds % 15) << 4) |
107 (lon_dms.seconds % 15)
108 );
109
110 result[old_len + 5] = char(
111 (lat_dms.sec16ths << 4) |
112 lon_dms.sec16ths
113 );
114
115 return true;
116}
117
118struct LatLongCoord {
119 double lat;
120 double lon;
121};
122
123LatLongCoord
124geo_decode(const char * value, size_t len)
125{
126 const unsigned char * ptr
127 = reinterpret_cast<const unsigned char *>(value);
128 LatLongCoord result;
129 unsigned tmp = (ptr[0] & 0xff) << 8 | (ptr[1] & 0xff);
130 result.lat = tmp % 181;
131 result.lon = tmp / 181;
132 if (len > 2) {
133 tmp = ptr[2];
134 double lat_m = (tmp >> 4) * 4;
135 double lon_m = (tmp & 0xf) * 4;
136
137 if (len > 3) {
138 tmp = ptr[3];
139 lat_m += (tmp >> 6) & 3;
140 lon_m += (tmp >> 4) & 3;
141 double lat_s = ((tmp >> 2) & 3) * 15;
142 double lon_s = (tmp & 3) * 15;
143
144 if (len > 4) {
145 tmp = ptr[4];
146 lat_s += (tmp >> 4) & 0xf;
147 lon_s += tmp & 0xf;
148
149 if (len > 5) {
150 tmp = ptr[5];
151 lat_s += ((tmp >> 4) / 16.0);
152 lon_s += ((tmp & 0xf) / 16.0);
153 }
154 }
155
156 lat_m += lat_s / 60.0;
157 lon_m += lon_s / 60.0;
158 }
159
160 result.lat += lat_m / 60.0;
161 result.lon += lon_m / 60.0;
162 }
163
164 result.lat -= 90.0;
165 return result;
166}
167
168LatLongCoord
169geo_decode(const string & value)
170{
171 return geo_decode(value.data(), value.size());
172}
173
174// Test code
175
176#include <stdio.h>
177#include <stdlib.h>
178
179bool check(double lat, double lon, double expected_lat, double expected_lon) {
180 string encoded;
181 bool ok = geo_encode(lat, lon, encoded);
182 if (!ok) {
183 fprintf(stderr, "encoding failed\n");
184 return false;
185 }
186 LatLongCoord result = geo_decode(encoded);
187 if (fabs(result.lat - expected_lat) > 0.00000001) {
188 fprintf(stderr, "result.lat != expected_lat: %.15g != %.15g (input=%.15g,%.15g)\n", result.lat, expected_lat, lat, lon);
189 return false;
190 }
191 if (fabs(result.lon - expected_lon) > 0.00000001) {
192 fprintf(stderr, "result.lon != expected_lon: %.15g != %.15g (input=%.15g,%.15g)\n", result.lon, expected_lon, lat, lon);
193 return false;
194 }
195 return true;
196}
197
198bool check(double lat, double lon) {
199 return check(lat, lon, lat, lon);
200}
201
202bool check_fail(double lat, double lon) {
203 string encoded;
204 bool ok = geo_encode(lat, lon, encoded);
205 if (ok) {
206 fprintf(stderr, "expected failure but encoded without error\n");
207 return false;
208 }
209 return true;
210}
211
212int main() {
213 // Check some roundtrips of things which encode precisely.
214 // (encoding resolution is 16ths of a second).
215 check(0, 0);
216 check(0.2, 23.8);
217 check((7)/(3600.0 * 16),
218 (7)/(3600.0 * 16));
219
220 check(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16),
221 10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16));
222 check(-(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)),
223 -(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)),
224 -(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)),
225 360 -(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)));
226
227 // This one is worth checking because it encodes the second byte as zero.
228 check(10, 96);
229
230 // Check the poles
231 check(-90, 0);
232 check(90, 0);
233 check(-90, 1, -90, 0);
234 check(90, 1, 90, 0);
235 check(-89.9999999, 1, -90, 0);
236 check(89.9999999, 1, 90, 0);
237 check(0, 359.9999999, 0, 0);
238 check(0, -359.9999999, 0, 0);
239
240 check(-89.9999999, 359.9999999, -90, 0);
241 check(-89.9999999, -359.9999999, -90, 0);
242 check(89.9999999, 359.9999999, 90, 0);
243 check(89.9999999, -359.9999999, 90, 0);
244
245 // Check
246 check_fail(91, 0);
247 check_fail(-91, 0);
248
249 for (int i = 0; i != 1000000; ++i) {
250 double lat = ((random() * 180.0) / RAND_MAX) - 90.0;
251 double lon = ((random() * 360.0 * 10) / RAND_MAX) - (360.0 * 5);
252 check(lat, lon,
253 round(lat * (3600.0 * 16)) / (3600.0 * 16),
254 round(fmod(lon + 3600.0, 360.0) * (3600.0 * 16)) / (3600.0 * 16)
255 );
256 }
257}