Skip to content

Commit 457e392

Browse files
committed
Add functions to store and retrieve extra bit in osmium::Location
The way coordinates in OSM work, storing a "location" needs 8 bytes, 4 bytes each for the longitude and latitude. But the longitude goes from -180° to +180° while the latitude only goes from -90° to +90°, so there is one unused bit in those 8 bytes that always has the same value as the sign bit. Sometimes it can be useful to use that single bit for something, because there are many processing steps where we need to store *all* locations of all nodes anyway and because there are so many nodes, every bit we can save, counts. So this adds some functionality to the osmium::Location class for storing and retrieving the extra bit without affecting the rest of the use of the Location class. This code does not attach any specific meaning to that extra bit, that is for users of the functionality to decide. Internal storage is backwards compatible for valid coordinates and the "undefined" coordinate. Behaviour for invalid coordinates has changed. For all of this to be activated you need to define the macro OSMIUM_LOCATION_WITH_EXTRA_BIT in your source code before including osmium/osm/location.hpp. Fixes #395
1 parent e45352d commit 457e392

4 files changed

Lines changed: 604 additions & 5 deletions

File tree

include/osmium/osm/location.hpp

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE.
3434
*/
3535

3636
#include <algorithm>
37+
#include <cassert>
3738
#include <cmath>
3839
#include <cstdint>
3940
#include <cstring>
@@ -267,6 +268,13 @@ namespace osmium {
267268
*
268269
* Coordinates are never checked on whether they are inside bounds.
269270
* Call valid() to check this.
271+
*
272+
* If the macro OSMIUM_LOCATION_WITH_EXTRA_BIT is defined, Location values
273+
* can store one extra bit of information, because one bit is not needed
274+
* for the y coordinate. Use the functions set/clear/toggle/get_extra_bit()
275+
* to access it. The extra bit will always be cleared for new locations or
276+
* by setting the y coordinate of a location. You can not store an extra
277+
* bit on an invalid location.
270278
*/
271279
class Location {
272280

@@ -277,6 +285,31 @@ namespace osmium {
277285
return static_cast<double>(detail::coordinate_precision);
278286
}
279287

288+
#ifdef OSMIUM_LOCATION_WITH_EXTRA_BIT
289+
290+
/**
291+
* For normal y coordinates, bit 30 is always equal to bit 31. So
292+
* we can use that to store one extra piece of information.
293+
*/
294+
enum {
295+
mask_b30 = 1UL << 30UL
296+
};
297+
298+
static constexpr int32_t remove_extra_bit(int32_t v) noexcept {
299+
if (v == undefined_coordinate) {
300+
return v;
301+
}
302+
auto const value = static_cast<uint32_t>(v);
303+
auto const n30 = value & ~mask_b30;
304+
auto const b30 = (value >> 1UL) & mask_b30;
305+
return static_cast<int32_t>(n30 | b30);
306+
}
307+
#else
308+
static constexpr int32_t remove_extra_bit(int32_t v) noexcept {
309+
return v;
310+
}
311+
#endif
312+
280313
public:
281314

282315
// this value is used for a coordinate to mark it as undefined
@@ -310,7 +343,7 @@ namespace osmium {
310343
*/
311344
constexpr Location(const int32_t x, const int32_t y) noexcept :
312345
m_x(x),
313-
m_y(y) {
346+
m_y(remove_extra_bit(y)) {
314347
}
315348

316349
/**
@@ -320,7 +353,7 @@ namespace osmium {
320353
*/
321354
constexpr Location(const int64_t x, const int64_t y) noexcept :
322355
m_x(static_cast<int32_t>(x)),
323-
m_y(static_cast<int32_t>(y)) {
356+
m_y(remove_extra_bit(static_cast<int32_t>(y))) {
324357
}
325358

326359
/**
@@ -329,7 +362,7 @@ namespace osmium {
329362
*/
330363
Location(const double lon, const double lat) :
331364
m_x(double_to_fix(lon)),
332-
m_y(double_to_fix(lat)) {
365+
m_y(remove_extra_bit(double_to_fix(lat))) {
333366
}
334367

335368
/**
@@ -379,16 +412,21 @@ namespace osmium {
379412
}
380413

381414
constexpr int32_t y() const noexcept {
382-
return m_y;
415+
return remove_extra_bit(m_y);
383416
}
384417

385418
Location& set_x(const int32_t x) noexcept {
386419
m_x = x;
387420
return *this;
388421
}
389422

423+
/**
424+
* Set the y coordinate of the location.
425+
*
426+
* This will always clear the extra bit.
427+
*/
390428
Location& set_y(const int32_t y) noexcept {
391-
m_y = y;
429+
m_y = remove_extra_bit(y);
392430
return *this;
393431
}
394432

@@ -479,6 +517,73 @@ namespace osmium {
479517
return as_string_without_check(iterator, separator);
480518
}
481519

520+
#ifdef OSMIUM_LOCATION_WITH_EXTRA_BIT
521+
/**
522+
* Get extra bit from location.
523+
*/
524+
constexpr bool get_extra_bit() const noexcept {
525+
assert(m_y != undefined_coordinate);
526+
527+
auto const b = static_cast<uint32_t>(m_y);
528+
auto const b31 = b >> 31UL;
529+
auto const b30 = (b >> 30UL) & 1UL;
530+
return b30 ^ b31;
531+
}
532+
533+
/**
534+
* Set extra bit on location.
535+
*
536+
* @pre valid()
537+
*/
538+
constexpr void set_extra_bit() noexcept {
539+
assert(m_y != undefined_coordinate);
540+
541+
auto const b = static_cast<uint32_t>(m_y);
542+
auto const n30 = b & ~mask_b30;
543+
auto const b30 = (~b >> 1UL) & mask_b30;
544+
m_y = static_cast<int32_t>(n30 | b30);
545+
}
546+
547+
/**
548+
* Set or clear extra bit on location.
549+
*
550+
* @pre valid()
551+
*/
552+
constexpr void set_extra_bit(bool value) noexcept {
553+
assert(m_y != undefined_coordinate);
554+
555+
auto const b = static_cast<uint32_t>(m_y);
556+
auto const n30 = b & ~mask_b30;
557+
auto const b30 = ((b >> 31UL) ^ value) << 30UL;
558+
m_y = static_cast<int32_t>(n30 | b30);
559+
}
560+
561+
/**
562+
* Clear extra bit on location.
563+
*
564+
* @pre valid()
565+
*/
566+
constexpr void clear_extra_bit() noexcept {
567+
assert(m_y != undefined_coordinate);
568+
569+
m_y = y();
570+
}
571+
572+
/**
573+
* Toggle extra bit on location.
574+
*
575+
* @pre valid()
576+
*/
577+
constexpr void toggle_extra_bit() noexcept {
578+
assert(m_y != undefined_coordinate);
579+
580+
auto const b = static_cast<uint32_t>(m_y);
581+
auto const n30 = b & ~mask_b30;
582+
auto const b30 = ~b & mask_b30;
583+
m_y = static_cast<int32_t>(n30 | b30);
584+
}
585+
#endif
586+
482587
}; // class Location
483588

484589
/**

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ add_unit_test(osm test_changeset ENABLE_IF ${ZLIB_FOUND} LIBS ${ZLIB_LIBRARIES})
129129
add_unit_test(osm test_crc ENABLE_IF ${ZLIB_FOUND} LIBS ${ZLIB_LIBRARIES})
130130
add_unit_test(osm test_entity_bits)
131131
add_unit_test(osm test_location)
132+
add_unit_test(osm test_location_extra)
132133
add_unit_test(osm test_metadata)
133134
add_unit_test(osm test_node ENABLE_IF ${ZLIB_FOUND} LIBS ${ZLIB_LIBRARIES})
134135
add_unit_test(osm test_node_ref)

test/t/osm/test_location.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#include "catch.hpp"
22

3+
// This file contains the same tests as test_location_extra.cpp, except those
4+
// tests specific to the location extra bit. OSMIUM_LOCATION_WITH_EXTRA_BIT is
5+
// not set.
6+
37
#include <osmium/osm/location.hpp>
48

59
#include <cstdint>

0 commit comments

Comments
 (0)