Ticket #481: geocode.cc

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

Implementation of geo encoding in C++

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 <math.h>
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.
44 DegreesMinutesSeconds(double angle) {
45 // Convert to an integer representing the number of 16ths of a second,
46 // rounding to nearest. Range of angle is assumed to be 0..360, so
47 // range of val is 0..20736000, which fits easily into a 32 bit int.
48 int val = round(angle * (3600.0 * 16.0));
49 degrees = val / (3600 * 16);
50 val = val % (3600 * 16);
51 minutes = val / (60 * 16);
52 val = val % (60 * 16);
53 seconds = val / 16;
54 sec16ths = val % 16;
55 }
56};
57
58string
59geo_encode(double lat, double lon, bool & failed)
60{
61 // Check range of latitude.
62 if (rare(lat < -90.0 || lat > 90.0)) {
63 failed = true;
64 return "Latitude out of range";
65 }
66 if (rare(lat == -90)) {
67 // South Pole special case.
68 return string();
69 }
70 if (rare(lat == 90)) {
71 // North Pole special case.
72 return "\xff\xff";
73 }
74
75 // Wrap longitude to range [0,360).
76 lon = fmod(lon, 360.0);
77 if (lon < 0) {
78 lon += 360;
79 }
80
81 DegreesMinutesSeconds lat_dms(lat + 90);
82 DegreesMinutesSeconds lon_dms(lon);
83
84 string result;
85 result.resize(6);
86
87 // Add degrees parts as first two bytes.
88 unsigned dd = lat_dms.degrees * 360 + lon_dms.degrees;
89 // dd is in range 0..180*360+359 = 0..65159
90 result[0] = char(dd >> 8);
91 result[1] = char(dd & 0xff);
92
93 // Add minutes next; 4 bits from each in the first byte.
94 result[2] = char(((lat_dms.minutes / 4) << 4) | (lon_dms.minutes / 4));
95
96 result[3] = char(
97 ((lat_dms.minutes % 4) << 6) |
98 ((lon_dms.minutes % 4) << 4) |
99 ((lat_dms.seconds / 15) << 2) |
100 (lon_dms.seconds / 15)
101 );
102
103 result[4] = char(
104 ((lat_dms.seconds % 15) << 4) |
105 (lon_dms.seconds % 15)
106 );
107
108 result[5] = char(
109 (lat_dms.sec16ths << 4) |
110 lon_dms.sec16ths
111 );
112
113 return result;
114}
115
116struct LatLongCoord {
117 double lat;
118 double lon;
119};
120
121LatLongCoord
122geo_decode(const string & value)
123{
124 const unsigned char * ptr
125 = reinterpret_cast<const unsigned char *>(value.data());
126 size_t len = value.size();
127 LatLongCoord result;
128 if (len == 0) {
129 result.lat = -90.0;
130 result.lon = 0.0;
131 return result;
132 }
133 if (value == "\xff\xff") {
134 result.lat = 90.0;
135 result.lon = 0.0;
136 return result;
137 }
138 unsigned tmp = (ptr[0] & 0xff) << 8 | (ptr[1] & 0xff);
139 result.lat = floor(tmp / 360);
140 result.lon = fmod(tmp, 360);
141 if (len > 2) {
142 tmp = ptr[2];
143 double lat_m = (tmp >> 4) * 4;
144 double lon_m = (tmp & 0xf) * 4;
145
146 if (len > 3) {
147 tmp = ptr[3];
148 lat_m += (tmp >> 6) & 3;
149 lon_m += (tmp >> 4) & 3;
150 double lat_s = ((tmp >> 2) & 3) * 15;
151 double lon_s = (tmp & 3) * 15;
152
153 if (len > 4) {
154 tmp = ptr[4];
155 lat_s += (tmp >> 4) & 0xf;
156 lon_s += tmp & 0xf;
157
158 if (len > 5) {
159 tmp = ptr[5];
160 lat_s += ((tmp >> 4) / 16.0);
161 lon_s += ((tmp & 0xf) / 16.0);
162 }
163 }
164
165 lat_m += lat_s / 60.0;
166 lon_m += lon_s / 60.0;
167 }
168
169 result.lat += lat_m / 60.0;
170 result.lon += lon_m / 60.0;
171 }
172
173 result.lat -= 90.0;
174 return result;
175}
176
177// Test code
178
179#include <stdio.h>
180#include <stdlib.h>
181
182bool check(double lat, double lon, double expected_lat, double expected_lon) {
183 bool failed = false;
184 string encoded = geo_encode(lat, lon, failed);
185 if (failed) {
186 fprintf(stderr, "check failed: %s\n", encoded.c_str());
187 return false;
188 }
189 LatLongCoord result = geo_decode(encoded);
190 if (fabs(result.lat - expected_lat) > 0.00000001) {
191 fprintf(stderr, "result.lat != expected_lat: %.15g != %.15g\n", result.lat, expected_lat);
192 return false;
193 }
194 if (fabs(result.lon - expected_lon) > 0.00000001) {
195 fprintf(stderr, "result.lon != expected_lon: %.15g != %.15g\n", result.lon, expected_lon);
196 return false;
197 }
198 return true;
199}
200
201bool check(double lat, double lon) {
202 return check(lat, lon, lat, lon);
203}
204
205bool check_fail(double lat, double lon) {
206 bool failed = false;
207 string encoded = geo_encode(lat, lon, failed);
208 if (!failed) {
209 fprintf(stderr, "expected failure but encoded without error\n");
210 return false;
211 }
212 return true;
213}
214
215int main() {
216 // Check some roundtrips of things which encode precisely.
217 // (encoding resolution is 16ths of a second).
218 check(0, 0);
219 check(0.2, 23.8);
220 check((7)/(3600.0 * 16),
221 (7)/(3600.0 * 16));
222
223 check(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16),
224 10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16));
225 check(-(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)),
226 -(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)),
227 -(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)),
228 360 -(10 + 7/60.0 + 5/3600.0 + 7/(3600.0 * 16)));
229
230 // This one is worth checking because it encodes the second byte as zero.
231 check(10, 96);
232
233 // Check the poles
234 check(-90, 0);
235 check(90, 0);
236 check(-90, 1, -90, 0);
237 check(90, 1, 90, 0);
238
239 // Check
240 check_fail(91, 0);
241 check_fail(-91, 0);
242
243 for (int i = 0; i != 10000; ++i) {
244 double lat = ((random() * 180.0) / RAND_MAX) - 90.0;
245 double lon = ((random() * 360.0 * 10) / RAND_MAX) - (360.0 * 5);
246 check(lat, lon,
247 round(lat * (3600.0 * 16)) / (3600.0 * 16),
248 round(fmod(lon + 3600.0, 360.0) * (3600.0 * 16)) / (3600.0 * 16)
249 );
250 }
251}