Ticket #481: geocode.2.cc

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

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