2 * @brief Encodings for geospatial coordinates.
4 /* Copyright (C) 2011 Richard Boulton
5 * Based closely on a python version, copyright (C) 2010 Olly Betts
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:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
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
27 #include "geoencode.h"
33 /** Angles, split into degrees, minutes and seconds.
35 * Only designed to work with positive angles.
37 struct DegreesMinutesSeconds
{
38 /** Number of degrees.
40 * Range 0 <= degrees <= 180 for latitude, 0 <= degrees < 360 for
45 /** Number of minutes: 0 to 59 */
48 /** Number of seconds: 0 to 59 */
51 /** Number of 16ths of a second: 0 to 15 */
54 /** Initialise with a (positive) angle, as an integer representing the
55 * number of 16ths of a second, rounding to nearest.
57 * The range of valid angles is assumed to be 0 <= angle in degrees < 360,
58 * so range of angle_16th_secs is 0..20735999, which fits easily into a 32
59 * bit int. (Latitudes are represented in the range 0 <= angle <= 180,
60 * where 0 is the south pole.)
62 explicit DegreesMinutesSeconds(int angle_16th_secs
) {
63 degrees
= angle_16th_secs
/ (3600 * 16);
64 angle_16th_secs
= angle_16th_secs
% (3600 * 16);
65 minutes
= angle_16th_secs
/ (60 * 16);
66 angle_16th_secs
= angle_16th_secs
% (60 * 16);
67 seconds
= angle_16th_secs
/ 16;
68 sec16ths
= angle_16th_secs
% 16;
73 GeoEncode::encode(double lat
, double lon
, string
& result
)
75 // Check range of latitude.
76 if (rare(lat
< -90.0 || lat
> 90.0)) {
80 // Wrap longitude to range [0,360).
81 lon
= fmod(lon
, 360.0);
86 int lat_16ths
, lon_16ths
;
87 lat_16ths
= lround((lat
+ 90.0) * 57600.0);
88 if (lat_16ths
== 0 || lat_16ths
== 57600 * 180) {
91 lon_16ths
= lround(lon
* 57600.0);
92 if (lon_16ths
== 57600 * 360) {
97 DegreesMinutesSeconds
lat_dms(lat_16ths
);
98 DegreesMinutesSeconds
lon_dms(lon_16ths
);
100 size_t old_len
= result
.size();
101 result
.resize(old_len
+ 6);
103 // Add degrees parts as first two bytes.
104 unsigned dd
= lat_dms
.degrees
+ lon_dms
.degrees
* 181;
105 // dd is in range 0..180*360+359 = 0..65159
106 result
[old_len
] = char(dd
>> 8);
107 result
[old_len
+ 1] = char(dd
& 0xff);
109 // Add minutes next; 4 bits from each in the first byte.
110 result
[old_len
+ 2] = char(((lat_dms
.minutes
/ 4) << 4) |
111 (lon_dms
.minutes
/ 4)
114 result
[old_len
+ 3] = char(
115 ((lat_dms
.minutes
% 4) << 6) |
116 ((lon_dms
.minutes
% 4) << 4) |
117 ((lat_dms
.seconds
/ 15) << 2) |
118 (lon_dms
.seconds
/ 15)
121 result
[old_len
+ 4] = char(
122 ((lat_dms
.seconds
% 15) << 4) |
123 (lon_dms
.seconds
% 15)
126 result
[old_len
+ 5] = char(
127 (lat_dms
.sec16ths
<< 4) |
135 GeoEncode::decode(const char * value
, size_t len
,
136 double & lat_ref
, double & lon_ref
)
138 const unsigned char * ptr
139 = reinterpret_cast<const unsigned char *>(value
);
140 unsigned tmp
= (ptr
[0] & 0xff) << 8 | (ptr
[1] & 0xff);
145 double lat_m
= (tmp
>> 4) * 4;
146 double lon_m
= (tmp
& 0xf) * 4;
150 lat_m
+= (tmp
>> 6) & 3;
151 lon_m
+= (tmp
>> 4) & 3;
152 double lat_s
= ((tmp
>> 2) & 3) * 15;
153 double lon_s
= (tmp
& 3) * 15;
157 lat_s
+= (tmp
>> 4) & 0xf;
162 lat_s
+= ((tmp
>> 4) / 16.0);
163 lon_s
+= ((tmp
& 0xf) / 16.0);
167 lat_m
+= lat_s
/ 60.0;
168 lon_m
+= lon_s
/ 60.0;
171 lat_ref
+= lat_m
/ 60.0;
172 lon_ref
+= lon_m
/ 60.0;
178 /// Calc latitude and longitude in integral number of 16ths of a second
180 calc_latlon_16ths(double lat
, double lon
, int & lat_16ths
, int & lon_16ths
)
182 lat_16ths
= lround((lat
+ 90.0) * 57600.0);
183 lon_16ths
= lround(lon
* 57600.0);
184 if (lon_16ths
== 57600 * 360) {
189 GeoEncode::DecoderWithBoundingBox::DecoderWithBoundingBox
190 (double lat1
, double lon1_
, double lat2
, double lon2_
)
191 : lon1(lon1_
), lon2(lon2_
),
192 min_lat(lat1
), max_lat(lat2
),
195 // Wrap longitudes to range [0,360).
196 lon1
= fmod(lon1
, 360.0);
201 lon2
= fmod(lon2
, 360.0);
207 int lat_16ths
, lon_16ths
;
208 calc_latlon_16ths(lat1
, lon1
, lat_16ths
, lon_16ths
);
209 if (lat_16ths
== 0 || lat_16ths
== 57600 * 180) {
210 include_poles
= true;
212 unsigned dd
= lat_16ths
/ (3600 * 16) + (lon_16ths
/ (3600 * 16)) * 181;
213 start1
= char(dd
>> 8);
215 calc_latlon_16ths(lat2
, lon2
, lat_16ths
, lon_16ths
);
216 if (lat_16ths
== 0 || lat_16ths
== 57600 * 180) {
217 include_poles
= true;
219 dd
= lat_16ths
/ (3600 * 16) + (lon_16ths
/ (3600 * 16)) * 181;
220 start2
= char(dd
>> 8);
222 discontinuous_longitude_range
= (lon1
> lon2
);
226 GeoEncode::DecoderWithBoundingBox::decode(const std::string
& value
,
228 double & lon_ref
) const
230 unsigned char start
= value
[0];
231 if (discontinuous_longitude_range
) {
232 // start must be outside range of (start2..start1)
233 // (start2 will be > start1)
234 if (start2
< start
&& start
< start1
) {
235 if (!(include_poles
&& start
== 0))
239 // start must be inside range of [start1..start2] (inclusive of ends).
240 if (start
< start1
|| start2
< start
) {
241 if (!(include_poles
&& start
== 0))
246 GeoEncode::decode(value
, lat
, lon
);
247 if (lat
< min_lat
|| lat
> max_lat
) {
250 if (lat
== -90 || lat
== 90) {
251 // It's a pole, so the longitude isn't meaningful (will be zero)
252 // and we've already checked that the latitude is in range.
257 if (discontinuous_longitude_range
) {
258 if (lon2
< lon
&& lon
< lon1
)
261 if (lon
< lon1
|| lon2
< lon
)