[libosmium] 01/05: Imported Upstream version 2.10.0

Bas Couwenberg sebastic at debian.org
Sat Nov 12 05:24:51 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository libosmium.

commit e92062cbdfc249ec7221a678a044e5ac0d5ca416
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat Nov 12 05:54:31 2016 +0100

    Imported Upstream version 2.10.0
---
 CHANGELOG.md                                       |  58 ++-
 CMakeLists.txt                                     |   6 +-
 README.md                                          |   4 +-
 benchmarks/CMakeLists.txt                          |   3 +
 benchmarks/download_data.sh                        |  10 +-
 benchmarks/osmium_benchmark_count.cpp              |   6 +-
 benchmarks/osmium_benchmark_count_tag.cpp          |   6 +-
 ...ark_count.cpp => osmium_benchmark_mercator.cpp} |  26 +-
 benchmarks/run_benchmark_mercator.sh               |  22 +
 benchmarks/setup.sh                                |  16 +-
 cmake/FindOsmium.cmake                             |  62 ++-
 examples/CMakeLists.txt                            |   8 +-
 examples/README.md                                 |  12 +
 examples/osmium_area_test.cpp                      |   4 +-
 examples/osmium_change_tags.cpp                    | 203 ++++++++++
 examples/osmium_convert.cpp                        |   8 +-
 examples/osmium_create_pois.cpp                    |  96 +++++
 examples/osmium_dump_internal.cpp                  | 179 +++++++++
 examples/osmium_filter_discussions.cpp             |   2 +-
 examples/osmium_index.cpp                          | 260 ------------
 examples/osmium_index_lookup.cpp                   | 333 ++++++++++++++++
 examples/osmium_pub_names.cpp                      |   0
 examples/osmium_road_length.cpp                    |   0
 examples/osmium_serdump.cpp                        | 206 ----------
 include/osmium/area/assembler.hpp                  |  52 +--
 include/osmium/area/detail/node_ref_segment.hpp    |   4 +-
 include/osmium/area/detail/segment_list.hpp        |   2 +-
 include/osmium/area/detail/vector.hpp              |  20 +-
 include/osmium/builder/attr.hpp                    |  64 +--
 include/osmium/builder/builder.hpp                 |  91 ++---
 include/osmium/builder/osm_object_builder.hpp      | 314 +++++++++++++--
 include/osmium/geom/factory.hpp                    |   2 +-
 include/osmium/geom/geos.hpp                       |  16 +-
 include/osmium/geom/relations.hpp                  |  10 +-
 include/osmium/geom/tile.hpp                       |  20 +-
 include/osmium/handler/disk_store.hpp              |   5 +-
 include/osmium/handler/node_locations_for_ways.hpp |   2 +-
 include/osmium/handler/object_relations.hpp        |   2 +
 include/osmium/index/bool_vector.hpp               |  41 +-
 include/osmium/index/detail/vector_map.hpp         |   6 +-
 include/osmium/index/id_set.hpp                    | 431 ++++++++++++++++++++
 include/osmium/index/index.hpp                     |  12 +-
 include/osmium/index/map.hpp                       |  27 +-
 include/osmium/index/map/dummy.hpp                 |   2 +-
 include/osmium/index/map/sparse_mem_map.hpp        |   2 +-
 include/osmium/index/map/sparse_mem_table.hpp      |   4 +-
 include/osmium/io/compression.hpp                  |  60 ++-
 include/osmium/io/detail/input_format.hpp          |  23 +-
 include/osmium/io/detail/o5m_input_format.hpp      | 104 +++--
 include/osmium/io/detail/opl_input_format.hpp      |   8 +-
 include/osmium/io/detail/opl_parser_functions.hpp  |  80 ++--
 include/osmium/io/detail/pbf_decoder.hpp           | 170 ++++++--
 include/osmium/io/detail/pbf_input_format.hpp      |  10 +-
 include/osmium/io/detail/queue_util.hpp            |  13 +-
 include/osmium/io/detail/xml_input_format.hpp      |  94 +++--
 include/osmium/io/file_format.hpp                  |   5 +
 include/osmium/io/header.hpp                       |  79 +++-
 include/osmium/io/reader.hpp                       |  63 ++-
 include/osmium/memory/buffer.hpp                   |  82 ++--
 include/osmium/memory/collection.hpp               |  47 ++-
 include/osmium/memory/item.hpp                     |   4 +-
 include/osmium/osm/area.hpp                        |  12 +-
 include/osmium/osm/changeset.hpp                   |  18 +-
 include/osmium/osm/crc.hpp                         |  42 +-
 include/osmium/osm/entity_bits.hpp                 |  24 +-
 include/osmium/osm/location.hpp                    |  52 ++-
 include/osmium/osm/node.hpp                        |   8 +-
 include/osmium/osm/node_ref_list.hpp               |  33 +-
 include/osmium/osm/object.hpp                      |   8 +
 include/osmium/osm/relation.hpp                    |  17 +-
 include/osmium/osm/tag.hpp                         |  19 +-
 include/osmium/osm/way.hpp                         |   6 +-
 include/osmium/relations/collector.hpp             |  58 ++-
 include/osmium/relations/detail/member_meta.hpp    |  40 +-
 include/osmium/thread/queue.hpp                    |  57 ++-
 include/osmium/util/progress_bar.hpp               |  12 +
 include/osmium/version.hpp                         |   4 +-
 test/CMakeLists.txt                                |  52 ++-
 test/data-tests/testdata-xml.cpp                   |   2 +-
 test/examples/CMakeLists.txt                       |  21 +
 test/examples/t/pub_names/CMakeLists.txt           |   7 +
 test/examples/t/pub_names/pubs.osm                 |   7 +
 test/examples/t/road_length/CMakeLists.txt         |   8 +
 test/examples/t/road_length/road.osm               |  59 +++
 test/include/catch.hpp                             |  50 ++-
 test/t/basic/test_entity_bits.cpp                  |  32 --
 test/t/builder/test_object_builder.cpp             | 444 +++++++++++++++++++++
 test/t/geom/helper.hpp                             |  15 -
 test/t/geom/test_crs.cpp                           |  10 +-
 test/t/geom/test_exception.cpp                     |   8 +-
 test/t/geom/test_factory_with_projection.cpp       |  40 +-
 test/t/geom/test_geojson.cpp                       | 193 ++++-----
 test/t/geom/test_geos.cpp                          |  56 +--
 test/t/geom/test_geos_wkb.cpp                      |  92 -----
 test/t/geom/test_ogr.cpp                           | 128 +++---
 test/t/geom/test_ogr_wkb.cpp                       | 100 +++++
 test/t/geom/test_projection.cpp                    | 143 +++----
 test/t/geom/test_tile.cpp                          |   2 -
 test/t/geom/test_wkb.cpp                           |  18 +-
 test/t/geom/wnl_helper.hpp                         |   6 +-
 test/t/index/test_id_set.cpp                       | 166 ++++++++
 test/t/index/test_id_to_location.cpp               | 165 ++++----
 test/t/io/test_compression_factory.cpp             |  27 ++
 test/t/io/test_reader_with_mock_parser.cpp         |   8 +-
 test/t/{buffer => memory}/test_buffer_basics.cpp   |   0
 test/t/{buffer => memory}/test_buffer_node.cpp     | 140 +++----
 test/t/{buffer => memory}/test_buffer_purge.cpp    |  82 ++--
 test/t/{basic => osm}/test_area.cpp                |   0
 test/t/{basic => osm}/test_box.cpp                 |   0
 test/t/{basic => osm}/test_changeset.cpp           |  67 ++--
 test/t/{basic => osm}/test_crc.cpp                 |   0
 test/t/osm/test_entity_bits.cpp                    |  62 +++
 test/t/{basic => osm}/test_location.cpp            | 119 +++---
 test/t/{basic => osm}/test_node.cpp                |   0
 test/t/{basic => osm}/test_node_ref.cpp            |   0
 test/t/{basic => osm}/test_object_comparisons.cpp  |   0
 test/t/{basic => osm}/test_relation.cpp            |   0
 test/t/{basic => osm}/test_timestamp.cpp           |   0
 test/t/{basic => osm}/test_types_from_string.cpp   |   0
 test/t/{basic => osm}/test_way.cpp                 |   2 +-
 120 files changed, 4211 insertions(+), 2031 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d132ef..25327c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,53 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 
+## [2.10.0] - 2016-11-11
+
+### Added
+
+- The `Reader` can take an additional optional `read_meta` flag. If this is
+  set to false the PBF input will ignore metadata on OSM objects (like version,
+  timestamp, uid, ...) which speeds up file reading by 10 to 20%.
+- New `IdSet` virtual class with two implementations: `IdSetDense` and
+  `IdSetSmall`. Used to efficiently store a set of Ids. This is often needed
+  to track, for instance, which nodes are needed for ways, etc.
+- Added more examples and better documented existing examples.
+- Add a benchmark "mercator" converting all node locations in a file to
+  WebMercator and creating geometries in WKB format.
+
+### Changed
+
+- Better queue handling makes I/O faster in some circumstances.
+- The `FindOsmium.cmake` CMake script can now check a current enough libosmium
+  version is found.
+- Builders can now be constructed with a reference to parent builder.
+- Made builders more robust by adding asserts that will catch common usage
+  problems.
+- Calling `OSMObjectBuilder::add_user()` is now optional, and the method was
+  renamed to `set_user()`. (`add_user()` is marked as deprecated.)
+- Benchmarks now show compiler and compiler options used.
+- `Builder::add_item()` now takes a reference instead of pointer (old version
+  of the function marked as deprecated).
+- GEOS support is deprecated. It does not work any more for GEOS 3.6 or newer.
+  Reason is the changed interface in GEOS 3.6. If there is interest for the
+  GEOS support, we can add support back in later (but probably using the
+  GEOS C API which is more stable than the C++ API). Some tests using GEOS
+  were rewritten to work without it.
+- The `BoolVector` has been deprecated in favour of the new `IdSet` classes.
+- Lots of code cleanups and improved API documentation in many places.
+- The relations collector can now tell you whether a relation member was in
+  the input data. See the new `is_available()` and
+  `get_availability_and_offset()` methods.
+- Updated embedded Catch unit test header to version 1.5.8.
+
+### Fixed
+
+- Parsing of coordinates starting with decimal dot and coordinates in
+  scientific notation.
+- `~` operator for `entity_bits` doesn't set unused bits any more.
+- Progress bar can now be (temporarily) removed, to allow other output.
+
+
 ## [2.9.0] - 2016-09-15
 
 ### Added
@@ -110,7 +157,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - New functions for iterating over specific item types in buffers
   (`osmium::memory::Buffer::select()`), over specific subitems
   (`osmium::OSMObject::subitems()`), and for iterating over all rings of
-  an area (`osmium::Areas::outer_rings(`), `inner_rings()`).
+  an area (`osmium::Areas::outer_rings()`, `inner_rings()`).
 - Debug output optionally prints CRC32 when `add_crc32` file option is set.
 
 ### Changed
@@ -267,9 +314,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
   one in Writer. Calling flush() on the OutputIterator isn't needed any
   more.
 - Reader now throws when trying to read after eof or an error.
-- I/O functions that used to throw std::runtime_error now throw
-  osmium::io_error or derived.
-- Optional parameters on osmium::io::Writer now work in any order.
+- I/O functions that used to throw `std::runtime_error` now throw
+  `osmium::io_error` or derived.
+- Optional parameters on `osmium::io::Writer` now work in any order.
 
 ### Fixed
 
@@ -410,7 +457,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
   Doxygen (up to version 1.8.8). This version contains a workaround to fix
   this.
 
-[unreleased]: https://github.com/osmcode/libosmium/compare/v2.9.0...HEAD
+[unreleased]: https://github.com/osmcode/libosmium/compare/v2.10.0...HEAD
+[2.10.0]: https://github.com/osmcode/libosmium/compare/v2.9.0...v2.10.0
 [2.9.0]: https://github.com/osmcode/libosmium/compare/v2.8.0...v2.9.0
 [2.8.0]: https://github.com/osmcode/libosmium/compare/v2.7.2...v2.8.0
 [2.7.2]: https://github.com/osmcode/libosmium/compare/v2.7.1...v2.7.2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 095137b..21cf98e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev;Cover
 project(libosmium)
 
 set(LIBOSMIUM_VERSION_MAJOR 2)
-set(LIBOSMIUM_VERSION_MINOR 9)
+set(LIBOSMIUM_VERSION_MINOR 10)
 set(LIBOSMIUM_VERSION_PATCH 0)
 
 set(LIBOSMIUM_VERSION
@@ -285,6 +285,10 @@ if(BUILD_DATA_TESTS)
     add_subdirectory(test/data-tests)
 endif()
 
+if(BUILD_EXAMPLES)
+    add_subdirectory(test/examples)
+endif()
+
 
 #-----------------------------------------------------------------------------
 #
diff --git a/README.md b/README.md
index 68fc2f6..d526f13 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ http://osmcode.org/libosmium
 
 A fast and flexible C++ library for working with OpenStreetMap data.
 
-[![Build Status](https://secure.travis-ci.org/osmcode/libosmium.png)](https://travis-ci.org/osmcode/libosmium)
-[![Build status](https://ci.appveyor.com/api/projects/status/mkbg6e6stdgq7c1b?svg=true)](https://ci.appveyor.com/project/Mapbox/libosmium)
+[![Build Status](https://secure.travis-ci.org/osmcode/libosmium.svg)](https://travis-ci.org/osmcode/libosmium)
+[![Build status](https://ci.appveyor.com/api/projects/status/github/osmcode/libosmium?svg=true)](https://ci.appveyor.com/project/Mapbox/libosmium)
 
 Libosmium is developed on Linux, but also works on OSX and Windows (with some
 limitations).
diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt
index e46c833..4b76fcb 100644
--- a/benchmarks/CMakeLists.txt
+++ b/benchmarks/CMakeLists.txt
@@ -12,6 +12,7 @@ set(BENCHMARKS
     count
     count_tag
     index_map
+    mercator
     static_vs_dynamic_index
     write_pbf
     CACHE STRING "Benchmark programs"
@@ -37,6 +38,8 @@ foreach(benchmark ${BENCHMARKS})
                    @ONLY)
 endforeach()
 
+string(TOUPPER "${CMAKE_BUILD_TYPE}" _cmake_build_type)
+set(_cxx_flags "${CMAKE_CXX_FLAGS_${_cmake_build_type}}")
 foreach(file setup run_benchmarks)
     configure_file(${file}.sh ${CMAKE_CURRENT_BINARY_DIR}/${file}.sh @ONLY)
 endforeach()
diff --git a/benchmarks/download_data.sh b/benchmarks/download_data.sh
index 8a6a8ff..be6adb9 100755
--- a/benchmarks/download_data.sh
+++ b/benchmarks/download_data.sh
@@ -4,9 +4,9 @@
 #
 
 cd $DATA_DIR
-curl --location --output 1_liechtenstein.osm.pbf http://download.geofabrik.de/europe/liechtenstein-latest.osm.pbf   # about   1 MB
-curl --location --output 2_bremen.osm.pbf        http://download.geofabrik.de/europe/germany/bremen-latest.osm.pbf  # about  13 MB
-curl --location --output 3_sachsen.osm.pbf       http://download.geofabrik.de/europe/germany/sachsen-latest.osm.pbf # about 120 MB
-curl --location --output 4_germany.osm.pbf       http://download.geofabrik.de/europe/germany-latest.osm.pbf         # about   2 GB
-curl --location --output 5_planet.osm.pbf        http://planet.osm.org/pbf/planet-latest.osm.pbf                    # about  26 GB
+curl --location --output 1_liechtenstein.osm.pbf http://download.geofabrik.de/europe/liechtenstein-latest.osm.pbf   # about   2 MB
+curl --location --output 2_bremen.osm.pbf        http://download.geofabrik.de/europe/germany/bremen-latest.osm.pbf  # about  16 MB
+curl --location --output 3_sachsen.osm.pbf       http://download.geofabrik.de/europe/germany/sachsen-latest.osm.pbf # about 160 MB
+curl --location --output 4_germany.osm.pbf       http://download.geofabrik.de/europe/germany-latest.osm.pbf         # about   3 GB
+curl --location --output 5_planet.osm.pbf        http://planet.osm.org/pbf/planet-latest.osm.pbf                    # about  35 GB
 
diff --git a/benchmarks/osmium_benchmark_count.cpp b/benchmarks/osmium_benchmark_count.cpp
index 1a16275..41f9aa0 100644
--- a/benchmarks/osmium_benchmark_count.cpp
+++ b/benchmarks/osmium_benchmark_count.cpp
@@ -19,15 +19,15 @@ struct CountHandler : public osmium::handler::Handler {
     uint64_t ways = 0;
     uint64_t relations = 0;
 
-    void node(osmium::Node&) {
+    void node(const osmium::Node&) {
         ++nodes;
     }
 
-    void way(osmium::Way&) {
+    void way(const osmium::Way&) {
         ++ways;
     }
 
-    void relation(osmium::Relation&) {
+    void relation(const osmium::Relation&) {
         ++relations;
     }
 
diff --git a/benchmarks/osmium_benchmark_count_tag.cpp b/benchmarks/osmium_benchmark_count_tag.cpp
index 6062ecc..5b82a7c 100644
--- a/benchmarks/osmium_benchmark_count_tag.cpp
+++ b/benchmarks/osmium_benchmark_count_tag.cpp
@@ -18,7 +18,7 @@ struct CountHandler : public osmium::handler::Handler {
     uint64_t counter = 0;
     uint64_t all = 0;
 
-    void node(osmium::Node& node) {
+    void node(const osmium::Node& node) {
         ++all;
         const char* amenity = node.tags().get_value_by_key("amenity");
         if (amenity && !strcmp(amenity, "post_box")) {
@@ -26,11 +26,11 @@ struct CountHandler : public osmium::handler::Handler {
         }
     }
 
-    void way(osmium::Way&) {
+    void way(const osmium::Way&) {
         ++all;
     }
 
-    void relation(osmium::Relation&) {
+    void relation(const osmium::Relation&) {
         ++all;
     }
 
diff --git a/benchmarks/osmium_benchmark_count.cpp b/benchmarks/osmium_benchmark_mercator.cpp
similarity index 52%
copy from benchmarks/osmium_benchmark_count.cpp
copy to benchmarks/osmium_benchmark_mercator.cpp
index 1a16275..091e5c0 100644
--- a/benchmarks/osmium_benchmark_count.cpp
+++ b/benchmarks/osmium_benchmark_mercator.cpp
@@ -12,23 +12,15 @@
 #include <osmium/io/any_input.hpp>
 #include <osmium/handler.hpp>
 #include <osmium/visitor.hpp>
+#include <osmium/geom/wkb.hpp>
+#include <osmium/geom/mercator_projection.hpp>
 
-struct CountHandler : public osmium::handler::Handler {
+struct GeomHandler : public osmium::handler::Handler {
 
-    uint64_t nodes = 0;
-    uint64_t ways = 0;
-    uint64_t relations = 0;
+    osmium::geom::WKBFactory<osmium::geom::MercatorProjection> factory;
 
-    void node(osmium::Node&) {
-        ++nodes;
-    }
-
-    void way(osmium::Way&) {
-        ++ways;
-    }
-
-    void relation(osmium::Relation&) {
-        ++relations;
+    void node(const osmium::Node& node) {
+        const std::string geom = factory.create_point(node);
     }
 
 };
@@ -44,12 +36,8 @@ int main(int argc, char* argv[]) {
 
     osmium::io::Reader reader{input_filename};
 
-    CountHandler handler;
+    GeomHandler handler;
     osmium::apply(reader, handler);
     reader.close();
-
-    std::cout << "Nodes: "     << handler.nodes << "\n";
-    std::cout << "Ways: "      << handler.ways << "\n";
-    std::cout << "Relations: " << handler.relations << "\n";
 }
 
diff --git a/benchmarks/run_benchmark_mercator.sh b/benchmarks/run_benchmark_mercator.sh
new file mode 100755
index 0000000..a520727
--- /dev/null
+++ b/benchmarks/run_benchmark_mercator.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+#  run_benchmark_mercator.sh
+#
+
+set -e
+
+BENCHMARK_NAME=mercator
+
+. @CMAKE_BINARY_DIR@/benchmarks/setup.sh
+
+CMD=$OB_DIR/osmium_benchmark_$BENCHMARK_NAME
+
+echo "# file size num mem time cpu_kernel cpu_user cpu_percent cmd options"
+for data in $OB_DATA_FILES; do
+    filename=`basename $data`
+    filesize=`stat --format="%s" --dereference $data`
+    for n in $OB_SEQ; do
+        $OB_TIME_CMD -f "$filename $filesize $n $OB_TIME_FORMAT" $CMD $data 2>&1 >/dev/null | sed -e "s%$DATA_DIR/%%" | sed -e "s%$OB_DIR/%%"
+    done
+done
+
diff --git a/benchmarks/setup.sh b/benchmarks/setup.sh
index 9733bfe..f8901c2 100755
--- a/benchmarks/setup.sh
+++ b/benchmarks/setup.sh
@@ -9,6 +9,10 @@ if [ -z $DATA_DIR ]; then
 fi
 
 OB_DIR=@CMAKE_BINARY_DIR@/benchmarks
+OB_BUILD_TYPE=@CMAKE_BUILD_TYPE@
+OB_COMPILER=@CMAKE_CXX_COMPILER@
+OB_COMPILER_VERSION=`$OB_COMPILER --version | head -1`
+OB_CXXFLAGS="@_cxx_flags@"
 
 OB_RUNS=3
 OB_SEQ=`seq -s' ' 1 $OB_RUNS`
@@ -20,11 +24,17 @@ OB_DATA_FILES=`find -L $DATA_DIR -mindepth 1 -maxdepth 1 -type f | sort`
 
 echo "BENCHMARK: $BENCHMARK_NAME"
 echo "---------------------"
+echo "BUILD:"
+echo "build type\t: $OB_BUILD_TYPE"
+echo "compiler\t: $OB_COMPILER"
+echo "CXX version\t: $OB_COMPILER_VERSION"
+echo "CXX flags\t: $OB_CXXFLAGS"
+echo "---------------------"
 echo "CPU:"
 grep '^model name' /proc/cpuinfo | tail -1
-grep '^cpu MHz' /proc/cpuinfo | tail -1
-grep '^cpu cores' /proc/cpuinfo | tail -1
-grep '^siblings' /proc/cpuinfo | tail -1
+grep '^cpu MHz'    /proc/cpuinfo | tail -1
+grep '^cpu cores'  /proc/cpuinfo | tail -1
+grep '^siblings'   /proc/cpuinfo | tail -1
 
 echo "---------------------"
 echo "MEMORY:"
diff --git a/cmake/FindOsmium.cmake b/cmake/FindOsmium.cmake
index fba8ffb..2224e18 100644
--- a/cmake/FindOsmium.cmake
+++ b/cmake/FindOsmium.cmake
@@ -2,8 +2,8 @@
 #
 #  FindOsmium.cmake
 #
-#  Find the Libosmium headers and, optionally, several components needed for
-#  different Libosmium functions.
+#  Find the Libosmium headers and, optionally, several components needed
+#  for different Libosmium functions.
 #
 #----------------------------------------------------------------------
 #
@@ -18,9 +18,12 @@
 #
 #    Then add the following in your CMakeLists.txt:
 #
-#      find_package(Osmium REQUIRED COMPONENTS <XXX>)
+#      find_package(Osmium [version] REQUIRED COMPONENTS <XXX>)
 #      include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS})
 #
+#    The version number is optional. If it is not set, any version of
+#    libosmium will do.
+#
 #    For the <XXX> substitute a space separated list of one or more of the
 #    following components:
 #
@@ -51,10 +54,9 @@
 #
 #----------------------------------------------------------------------
 
-# Look for the header file.
-find_path(OSMIUM_INCLUDE_DIR osmium/osm.hpp
-    PATH_SUFFIXES include
-    PATHS
+# This is the list of directories where we look for osmium and protozero
+# includes.
+set(_osmium_include_path
         ../libosmium
         ~/Library/Frameworks
         /Library/Frameworks
@@ -62,6 +64,22 @@ find_path(OSMIUM_INCLUDE_DIR osmium/osm.hpp
         /opt
 )
 
+# Look for the header file.
+find_path(OSMIUM_INCLUDE_DIR osmium/version.hpp
+    PATH_SUFFIXES include
+    PATHS ${_osmium_include_path}
+)
+
+# Check libosmium version number
+if(Osmium_FIND_VERSION)
+    file(STRINGS "${OSMIUM_INCLUDE_DIR}/osmium/version.hpp" _libosmium_version_define REGEX "#define LIBOSMIUM_VERSION_STRING")
+    if("${_libosmium_version_define}" MATCHES "#define LIBOSMIUM_VERSION_STRING \"([0-9.]+)\"")
+        set(_libosmium_version "${CMAKE_MATCH_1}")
+    else()
+        set(_libosmium_version "unknown")
+    endif()
+endif()
+
 set(OSMIUM_INCLUDE_DIRS "${OSMIUM_INCLUDE_DIR}")
 
 #----------------------------------------------------------------------
@@ -95,17 +113,31 @@ if(Osmium_USE_PBF)
     find_package(ZLIB)
     find_package(Threads)
 
-    list(APPEND OSMIUM_EXTRA_FIND_VARS ZLIB_FOUND Threads_FOUND)
-    if(ZLIB_FOUND AND Threads_FOUND)
+    message(STATUS "Looking for protozero")
+    find_path(PROTOZERO_INCLUDE_DIR protozero/version.hpp
+        PATH_SUFFIXES include
+        PATHS ${_osmium_include_path}
+              ${OSMIUM_INCLUDE_DIR}
+    )
+    if(PROTOZERO_INCLUDE_DIR)
+        message(STATUS "Looking for protozero - found")
+    else()
+        message(STATUS "Looking for protozero - not found")
+    endif()
+
+    list(APPEND OSMIUM_EXTRA_FIND_VARS ZLIB_FOUND Threads_FOUND PROTOZERO_INCLUDE_DIR)
+    if(ZLIB_FOUND AND Threads_FOUND AND PROTOZERO_INCLUDE_DIR)
         list(APPEND OSMIUM_PBF_LIBRARIES
             ${ZLIB_LIBRARIES}
             ${CMAKE_THREAD_LIBS_INIT}
         )
         if(WIN32)
+            # This is needed for the ntohl() function
             list(APPEND OSMIUM_PBF_LIBRARIES ws2_32)
         endif()
         list(APPEND OSMIUM_INCLUDE_DIRS
             ${ZLIB_INCLUDE_DIR}
+            ${PROTOZERO_INCLUDE_DIR}
         )
     else()
         message(WARNING "Osmium: Can not find some libraries for PBF input/output, please install them or configure the paths.")
@@ -203,7 +235,7 @@ if(Osmium_USE_SPARSEHASH)
     if(SPARSEHASH_INCLUDE_DIR)
         # Find size of sparsetable::size_type. This does not work on older
         # CMake versions because they can do this check only in C, not in C++.
-        if (NOT CMAKE_VERSION VERSION_LESS 3.0)
+        if(NOT CMAKE_VERSION VERSION_LESS 3.0)
            include(CheckTypeSize)
            set(CMAKE_REQUIRED_INCLUDES ${SPARSEHASH_INCLUDE_DIR})
            set(CMAKE_EXTRA_INCLUDE_FILES "google/sparsetable")
@@ -253,13 +285,15 @@ endif()
 #  Check that all required libraries are available
 #
 #----------------------------------------------------------------------
-if (OSMIUM_EXTRA_FIND_VARS)
+if(OSMIUM_EXTRA_FIND_VARS)
     list(REMOVE_DUPLICATES OSMIUM_EXTRA_FIND_VARS)
 endif()
-# Handle the QUIETLY and REQUIRED arguments and set OSMIUM_FOUND to TRUE if
-# all listed variables are TRUE.
+# Handle the QUIETLY and REQUIRED arguments and the optional version check
+# and set OSMIUM_FOUND to TRUE if all listed variables are TRUE.
 include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(Osmium REQUIRED_VARS OSMIUM_INCLUDE_DIR ${OSMIUM_EXTRA_FIND_VARS})
+find_package_handle_standard_args(Osmium
+                                  REQUIRED_VARS OSMIUM_INCLUDE_DIR ${OSMIUM_EXTRA_FIND_VARS}
+                                  VERSION_VAR _libosmium_version)
 unset(OSMIUM_EXTRA_FIND_VARS)
 
 #----------------------------------------------------------------------
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index b47cfdc..ac07896 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -10,18 +10,20 @@ message(STATUS "Configuring examples")
 
 set(EXAMPLES
     area_test
+    change_tags
     convert
     count
+    create_pois
     debug
+    dump_internal
     filter_discussions
-    index
+    index_lookup
     location_cache_create
     location_cache_use
     pub_names
     read
     read_with_progress
     road_length
-    serdump
     tiles
     CACHE STRING "Example programs"
 )
@@ -32,7 +34,7 @@ set(EXAMPLES
 #  Examples depending on wingetopt
 #
 #-----------------------------------------------------------------------------
-set(GETOPT_EXAMPLES area_test convert index serdump)
+set(GETOPT_EXAMPLES area_test convert index_lookup)
 if(NOT GETOPT_MISSING)
     foreach(example ${GETOPT_EXAMPLES})
         list(APPEND EXAMPLE_LIBS_${example} ${GETOPT_LIBRARY})
diff --git a/examples/README.md b/examples/README.md
index e291032..55bd406 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -18,12 +18,24 @@ them.
 
 ## Still reasonably simple examples
 
+* `osmium_read_with_progress`
 * `osmium_filter_discussions`
 * `osmium_convert`
+* `osmium_pub_names`
+* `osmium_road_length`
 
 ## More advanced examples
 
 * `osmium_area_test`
+* `osmium_create_pois`
+
+## Even more advanced examples
+
+* `osmium_change_tags`
+* `osmium_location_cache_create`
+* `osmium_location_cache_use`
+* `osmium_dump_internal`
+* `osmium_index_lookup`
 
 ## License
 
diff --git a/examples/osmium_area_test.cpp b/examples/osmium_area_test.cpp
index 0303374..0c849a7 100644
--- a/examples/osmium_area_test.cpp
+++ b/examples/osmium_area_test.cpp
@@ -106,7 +106,7 @@ int main(int argc, char* argv[]) {
 
     // Read options from command line.
     while (true) {
-        int c = getopt_long(argc, argv, "hwo", long_options, 0);
+        const int c = getopt_long(argc, argv, "hwo", long_options, 0);
         if (c == -1) {
             break;
         }
@@ -126,7 +126,7 @@ int main(int argc, char* argv[]) {
         }
     }
 
-    int remaining_args = argc - optind;
+    const int remaining_args = argc - optind;
     if (remaining_args != 1) {
         std::cerr << "Usage: " << argv[0] << " [OPTIONS] OSMFILE\n";
         std::exit(1);
diff --git a/examples/osmium_change_tags.cpp b/examples/osmium_change_tags.cpp
new file mode 100644
index 0000000..a7c1904
--- /dev/null
+++ b/examples/osmium_change_tags.cpp
@@ -0,0 +1,203 @@
+/*
+
+  EXAMPLE osmium_change_tags
+
+  An example how tags in OSM files can be removed or changed. Removes
+  "created_by" tags and changes tag "landuse=forest" into "natural_wood".
+
+  DEMONSTRATES USE OF:
+  * file input and output
+  * Osmium buffers
+  * your own handler
+  * access to tags
+  * using builders to write data
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+  * osmium_pub_names
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cstdlib>   // for std::exit
+#include <cstring>   // for std::strcmp
+#include <exception> // for std::exception
+#include <iostream>  // for std::cout, std::cerr
+#include <string>    // for std::string
+#include <utility>   // for std::move
+
+// Allow any format of input files (XML, PBF, ...)
+#include <osmium/io/any_input.hpp>
+
+// Allow any format of output files (XML, PBF, ...)
+#include <osmium/io/any_output.hpp>
+
+// We want to use the builder interface
+#include <osmium/builder/osm_object_builder.hpp>
+
+// We want to use the handler interface
+#include <osmium/handler.hpp>
+
+// For osmium::apply()
+#include <osmium/visitor.hpp>
+
+// The functions in this class will be called for each object in the input
+// and will write a (changed) copy of those objects to the given buffer.
+class RewriteHandler : public osmium::handler::Handler {
+
+    osmium::memory::Buffer& m_buffer;
+
+    // Copy attributes common to all OSM objects (nodes, ways, and relations).
+    template <typename T>
+    void copy_attributes(T& builder, const osmium::OSMObject& object) {
+        // The setter functions on the builder object all return the same
+        // builder object so they can be chained.
+        builder.set_id(object.id())
+            .set_version(object.version())
+            .set_changeset(object.changeset())
+            .set_timestamp(object.timestamp())
+            .set_uid(object.uid())
+            .set_user(object.user());
+    }
+
+    // Copy all tags with two changes:
+    // * Do not copy "created_by" tags
+    // * Change "landuse=forest" into "natural=wood"
+    void copy_tags(osmium::builder::Builder& parent, const osmium::TagList& tags) {
+
+        // The TagListBuilder is used to create a list of tags. The parameter
+        // to create it is a reference to the builder of the object that
+        // should have those tags.
+        osmium::builder::TagListBuilder builder{parent};
+
+        // Iterate over all tags and build new tags using the new builder
+        // based on the old ones.
+        for (const auto& tag : tags) {
+            if (std::strcmp(tag.key(), "created_by")) {
+                if (!std::strcmp(tag.key(), "landuse") && !std::strcmp(tag.value(), "forest")) {
+                    // add_tag() can be called with key and value C strings
+                    builder.add_tag("natural", "wood");
+                } else {
+                    // add_tag() can also be called with an osmium::Tag
+                    builder.add_tag(tag);
+                }
+            }
+        }
+    }
+
+public:
+
+    // Constructor. New data will be added to the given buffer.
+    RewriteHandler(osmium::memory::Buffer& buffer) :
+        m_buffer(buffer) {
+    }
+
+    // The node handler is called for each node in the input data.
+    void node(const osmium::Node& node) {
+        // Open a new scope, because the NodeBuilder we are creating has to
+        // be destructed, before we can call commit() below.
+        {
+            // To create a node, we need a NodeBuilder object. It will create
+            // the node in the given buffer.
+            osmium::builder::NodeBuilder builder{m_buffer};
+
+            // Copy common object attributes over to the new node.
+            copy_attributes(builder, node);
+
+            // Copy the location over to the new node.
+            builder.set_location(node.location());
+
+            // Copy (changed) tags.
+            copy_tags(builder, node.tags());
+        }
+
+        // Once the object is written to the buffer completely, we have to call
+        // commit().
+        m_buffer.commit();
+    }
+
+    // The way handler is called for each node in the input data.
+    void way(const osmium::Way& way) {
+        {
+            osmium::builder::WayBuilder builder{m_buffer};
+            copy_attributes(builder, way);
+            copy_tags(builder, way.tags());
+
+            // Copy the node list over to the new way.
+            builder.add_item(way.nodes());
+        }
+        m_buffer.commit();
+    }
+
+    // The relation handler is called for each node in the input data.
+    void relation(const osmium::Relation& relation) {
+        {
+            osmium::builder::RelationBuilder builder{m_buffer};
+            copy_attributes(builder, relation);
+            copy_tags(builder, relation.tags());
+
+            // Copy the relation member list over to the new way.
+            builder.add_item(relation.members());
+        }
+        m_buffer.commit();
+    }
+
+}; // class RewriteHandler
+
+int main(int argc, char* argv[]) {
+    if (argc != 3) {
+        std::cerr << "Usage: " << argv[0] << " INFILE OUTFILE\n";
+        std::exit(1);
+    }
+
+    // Get input and output file names from command line.
+    std::string input_file_name{argv[1]};
+    std::string output_file_name{argv[2]};
+
+    try {
+        // Initialize Reader
+        osmium::io::Reader reader{input_file_name};
+
+        // Get header from input file and change the "generator" setting to
+        // ourselves.
+        osmium::io::Header header = reader.header();
+        header.set("generator", "osmium_change_tags");
+
+        // Initialize Writer using the header from above and tell it that it
+        // is allowed to overwrite a possibly existing file.
+        osmium::io::Writer writer{output_file_name, header, osmium::io::overwrite::allow};
+
+        // Read in buffers with OSM objects until there are no more.
+        while (osmium::memory::Buffer input_buffer = reader.read()) {
+            // Create an empty buffer with the same size as the input buffer.
+            // We'll copy the changed data into output buffer, the changes
+            // are small, so the output buffer needs to be about the same size.
+            // In case it has to be bigger, we allow it to grow automatically
+            // by adding the auto_grow::yes parameter.
+            osmium::memory::Buffer output_buffer{input_buffer.committed(), osmium::memory::Buffer::auto_grow::yes};
+
+            // Construct a handler as defined above and feed the input buffer
+            // to it.
+            RewriteHandler handler{output_buffer};
+            osmium::apply(input_buffer, handler);
+
+            // Write out the contents of the output buffer.
+            writer(std::move(output_buffer));
+        }
+
+        // Explicitly close the writer and reader. Will throw an exception if
+        // there is a problem. If you wait for the destructor to close the writer
+        // and reader, you will not notice the problem, because destructors must
+        // not throw.
+        writer.close();
+        reader.close();
+    } catch (const std::exception& e) {
+        // All exceptions used by the Osmium library derive from std::exception.
+        std::cerr << e.what() << "\n";
+        std::exit(1);
+    }
+}
+
diff --git a/examples/osmium_convert.cpp b/examples/osmium_convert.cpp
index 48a0823..0ced82c 100644
--- a/examples/osmium_convert.cpp
+++ b/examples/osmium_convert.cpp
@@ -67,7 +67,7 @@ int main(int argc, char* argv[]) {
 
     // Read options from command line.
     while (true) {
-        int c = getopt_long(argc, argv, "dhf:t:", long_options, 0);
+        const int c = getopt_long(argc, argv, "dhf:t:", long_options, 0);
         if (c == -1) {
             break;
         }
@@ -87,7 +87,7 @@ int main(int argc, char* argv[]) {
         }
     }
 
-    int remaining_args = argc - optind;
+    const int remaining_args = argc - optind;
     if (remaining_args > 2) {
         std::cerr << "Usage: " << argv[0] << " [OPTIONS] [INFILE [OUTFILE]]\n";
         std::exit(1);
@@ -124,13 +124,13 @@ int main(int argc, char* argv[]) {
         osmium::io::Reader reader{input_file};
 
         // Get header from input file and change the "generator" setting to
-        // outselves.
+        // ourselves.
         osmium::io::Header header = reader.header();
         header.set("generator", "osmium_convert");
 
         // Initialize Writer using the header from above and tell it that it
         // is allowed to overwrite a possibly existing file.
-        osmium::io::Writer writer(output_file, header, osmium::io::overwrite::allow);
+        osmium::io::Writer writer{output_file, header, osmium::io::overwrite::allow};
 
         // Copy the contents from the input to the output file one buffer at
         // a time. This is much easier and faster than copying each object
diff --git a/examples/osmium_create_pois.cpp b/examples/osmium_create_pois.cpp
new file mode 100644
index 0000000..ff79cbb
--- /dev/null
+++ b/examples/osmium_create_pois.cpp
@@ -0,0 +1,96 @@
+/*
+
+  EXAMPLE osmium_create_pois
+
+  Showing how to create nodes for points of interest out of thin air.
+
+  DEMONSTRATES USE OF:
+  * file output
+  * Osmium buffers
+  * using builders to write data
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+  * osmium_pub_names
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cstdlib>   // for std::exit
+#include <cstring>   // for std::strcmp
+#include <ctime>     // for std::time
+#include <exception> // for std::exception
+#include <iostream>  // for std::cout, std::cerr
+#include <string>    // for std::string
+#include <utility>   // for std::move
+
+// Allow any format of output files (XML, PBF, ...)
+#include <osmium/io/any_output.hpp>
+
+// We want to use the builder interface
+#include <osmium/builder/osm_object_builder.hpp>
+#include <osmium/builder/attr.hpp>
+
+// Declare this to use the functions starting with the underscore (_) below.
+using namespace osmium::builder::attr;
+
+int main(int argc, char* argv[]) {
+    if (argc != 2) {
+        std::cerr << "Usage: " << argv[0] << " OUTFILE\n";
+        std::exit(1);
+    }
+
+    // Get output file name from command line.
+    std::string output_file_name{argv[1]};
+
+    try {
+        // Create a buffer where all objects will live. Use a sensible initial
+        // buffer size and set the buffer to automatically grow if needed.
+        const size_t initial_buffer_size = 10000;
+        osmium::memory::Buffer buffer{initial_buffer_size, osmium::memory::Buffer::auto_grow::yes};
+
+        // Add nodes to the buffer. This is, of course, only an example.
+        // You can set any of the attributes and more tags, etc. Ways and
+        // relations can be added in a similar way.
+        osmium::builder::add_node(buffer,
+            _id(-1),
+            _version(1),
+            _timestamp(std::time(nullptr)),
+            _location(osmium::Location{1.23, 3.45}),
+            _tag("amenity", "post_box")
+        );
+
+        osmium::builder::add_node(buffer,
+            _id(-2),
+            _version(1),
+            _timestamp(std::time(nullptr)),
+            _location(1.24, 3.46),
+            _tags({{"amenity", "restaurant"},
+                   {"name", "Chez OSM"}})
+        );
+
+        // Create header and set generator.
+        osmium::io::Header header;
+        header.set("generator", "osmium_create_pois");
+
+        // Initialize Writer using the header from above and tell it that it
+        // is allowed to overwrite a possibly existing file.
+        osmium::io::Writer writer{output_file_name, header, osmium::io::overwrite::allow};
+
+        // Write out the contents of the output buffer.
+        writer(std::move(buffer));
+
+        // Explicitly close the writer. Will throw an exception if there is
+        // a problem. If you wait for the destructor to close the writer, you
+        // will not notice the problem, because destructors must not throw.
+        writer.close();
+    } catch (const std::exception& e) {
+        // All exceptions used by the Osmium library derive from std::exception.
+        std::cerr << e.what() << "\n";
+        std::exit(1);
+    }
+}
+
diff --git a/examples/osmium_dump_internal.cpp b/examples/osmium_dump_internal.cpp
new file mode 100644
index 0000000..dbc50db
--- /dev/null
+++ b/examples/osmium_dump_internal.cpp
@@ -0,0 +1,179 @@
+/*
+
+  EXAMPLE osmium_dump_internal
+
+  Reads an OSM file and dumps the internal datastructure to disk including
+  indexes to find objects and object relations.
+
+  Note that this example programm will only work with small and medium sized
+  OSM file, not with the planet.
+
+  You can use the osmium_index example program to inspect the indexes.
+
+  DEMONSTRATES USE OF:
+  * file input
+  * indexes and maps
+  * use of the DiskStore handler
+  * use of the ObjectRelations handler
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+  * osmium_road_length
+  * osmium_location_cache_create
+  * osmium_location_cache_use
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cerrno>      // for errno
+#include <cstring>     // for std::strerror
+#include <cstdlib>     // for std::exit
+#include <iostream>    // for std::cout, std::cerr
+#include <string>      // for std::string
+#include <sys/stat.h>  // for open
+#include <sys/types.h> // for open
+
+#ifdef _MSC_VER
+# include <direct.h>
+#endif
+
+// Allow any format of input files (XML, PBF, ...)
+#include <osmium/io/any_input.hpp>
+
+// The DiskStore handler
+#include <osmium/handler/disk_store.hpp>
+
+// The ObjectRelations handler
+#include <osmium/handler/object_relations.hpp>
+
+// The indexes
+#include <osmium/index/map/sparse_mem_array.hpp>
+#include <osmium/index/multimap/sparse_mem_array.hpp>
+
+using offset_index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, size_t>;
+using map_type = osmium::index::multimap::SparseMemArray<osmium::unsigned_object_id_type, osmium::unsigned_object_id_type>;
+
+/**
+ * Small class wrapping index files, basically making sure errors are handled
+ * and the files are closed on destruction.
+ */
+class IndexFile {
+
+    int m_fd;
+
+public:
+
+    IndexFile(const std::string& filename) :
+        m_fd(::open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666)) {
+        if (m_fd < 0) {
+            std::cerr << "Can't open index file '" << filename << "': " << std::strerror(errno) << "\n";
+            std::exit(2);
+        }
+    }
+
+    ~IndexFile() {
+        if (m_fd >= 0) {
+            close(m_fd);
+        }
+    }
+
+    int fd() const noexcept {
+        return m_fd;
+    }
+
+}; // class IndexFile
+
+int main(int argc, char* argv[]) {
+    if (argc != 3) {
+        std::cerr << "Usage: " << argv[0] << " OSMFILE DIR\n";
+        std::exit(2);
+    }
+
+    const std::string input_file_name{argv[1]};
+    const std::string output_dir{argv[2]};
+
+    // Create output directory. Ignore the error if it already exists.
+#ifndef _WIN32
+    const int result = ::mkdir(output_dir.c_str(), 0777);
+#else
+    const int result = mkdir(output_dir.c_str());
+#endif
+    if (result == -1 && errno != EEXIST) {
+        std::cerr << "Problem creating directory '" << output_dir << "': " << std::strerror(errno) << "\n";
+        std::exit(2);
+    }
+
+    // Create the output file which will contain our serialized OSM data
+    const std::string data_file{output_dir + "/data.osm.ser"};
+    const int data_fd = ::open(data_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+    if (data_fd < 0) {
+        std::cerr << "Can't open data file '" << data_file << "': " << std::strerror(errno) << "\n";
+        std::exit(2);
+    }
+
+    // These indexes store the offset in the data file where each node, way,
+    // or relation is stored.
+    offset_index_type node_index;
+    offset_index_type way_index;
+    offset_index_type relation_index;
+
+    // This handler will dump the internal data to disk using the given file
+    // descriptor while updating the indexes.
+    osmium::handler::DiskStore disk_store_handler{data_fd, node_index, way_index, relation_index};
+
+    // These indexes store the mapping from node id to the ids of the ways
+    // containing this node, and from node/way/relation ids to the ids of the
+    // relations containing those objects.
+    map_type map_node2way;
+    map_type map_node2relation;
+    map_type map_way2relation;
+    map_type map_relation2relation;
+
+    // This handler will update the map indexes.
+    osmium::handler::ObjectRelations object_relations_handler{map_node2way, map_node2relation, map_way2relation, map_relation2relation};
+
+    // Read OSM data buffer by buffer.
+    osmium::io::Reader reader{input_file_name};
+
+    while (osmium::memory::Buffer buffer = reader.read()) {
+        // Write buffer to disk and update indexes.
+        disk_store_handler(buffer);
+
+        // Update object relation index maps.
+        osmium::apply(buffer, object_relations_handler);
+    }
+
+    reader.close();
+
+    // Write out node, way, and relation offset indexes to disk.
+    IndexFile nodes_idx{output_dir + "/nodes.idx"};
+    node_index.dump_as_list(nodes_idx.fd());
+
+    IndexFile ways_idx{output_dir + "/ways.idx"};
+    way_index.dump_as_list(ways_idx.fd());
+
+    IndexFile relations_idx{output_dir + "/relations.idx"};
+    relation_index.dump_as_list(relations_idx.fd());
+
+    // Sort the maps (so later binary search will work on them) and write
+    // them to disk.
+    map_node2way.sort();
+    IndexFile node2way_idx{output_dir + "/node2way.map"};
+    map_node2way.dump_as_list(node2way_idx.fd());
+
+    map_node2relation.sort();
+    IndexFile node2relation_idx{output_dir + "/node2rel.map"};
+    map_node2relation.dump_as_list(node2relation_idx.fd());
+
+    map_way2relation.sort();
+    IndexFile way2relation_idx{output_dir + "/way2rel.map"};
+    map_way2relation.dump_as_list(way2relation_idx.fd());
+
+    map_relation2relation.sort();
+    IndexFile relation2relation_idx{output_dir + "/rel2rel.map"};
+    map_relation2relation.dump_as_list(relation2relation_idx.fd());
+}
+
diff --git a/examples/osmium_filter_discussions.cpp b/examples/osmium_filter_discussions.cpp
index 6334981..c2a94e5 100644
--- a/examples/osmium_filter_discussions.cpp
+++ b/examples/osmium_filter_discussions.cpp
@@ -67,7 +67,7 @@ int main(int argc, char* argv[]) {
     // file for the output file. This will copy over some header information.
     // The last parameter will tell the writer that it is allowed to overwrite
     // an existing file. Without it, it will refuse to do so.
-    osmium::io::Writer writer(output_file, header, osmium::io::overwrite::allow);
+    osmium::io::Writer writer{output_file, header, osmium::io::overwrite::allow};
 
     // Create range of input iterators that will iterator over all changesets
     // delivered from input file through the "reader".
diff --git a/examples/osmium_index.cpp b/examples/osmium_index.cpp
deleted file mode 100644
index 478b6c6..0000000
--- a/examples/osmium_index.cpp
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
-
-  Example program to look at Osmium indexes on disk.
-
-  The code in this example file is released into the Public Domain.
-
-*/
-
-#include <fcntl.h>
-#include <iomanip>
-#include <iostream>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <getopt.h>
-
-#include <osmium/index/map/dense_file_array.hpp>
-#include <osmium/index/map/sparse_file_array.hpp>
-#include <osmium/osm/location.hpp>
-#include <osmium/osm/types.hpp>
-
-template <typename TKey, typename TValue>
-class IndexSearch {
-
-    typedef typename osmium::index::map::DenseFileArray<TKey, TValue> dense_index_type;
-    typedef typename osmium::index::map::SparseFileArray<TKey, TValue> sparse_index_type;
-
-    int m_fd;
-    bool m_dense_format;
-
-    void dump_dense() {
-        dense_index_type index(m_fd);
-
-        for (std::size_t i = 0; i < index.size(); ++i) {
-            if (index.get(i) != TValue()) {
-                std::cout << i << " " << index.get(i) << "\n";
-            }
-        }
-    }
-
-    void dump_sparse() {
-        sparse_index_type index(m_fd);
-
-        for (auto& element : index) {
-            std::cout << element.first << " " << element.second << "\n";
-        }
-    }
-
-    bool search_dense(TKey key) {
-        dense_index_type index(m_fd);
-
-        try {
-            TValue value = index.get(key);
-            std::cout << key << " " << value << "\n";
-        } catch (...) {
-            std::cout << key << " not found\n";
-            return false;
-        }
-
-        return true;
-    }
-
-    bool search_sparse(TKey key) {
-        typedef typename sparse_index_type::element_type element_type;
-        sparse_index_type index(m_fd);
-
-        element_type elem {key, TValue()};
-        auto positions = std::equal_range(index.begin(), index.end(), elem, [](const element_type& lhs, const element_type& rhs) {
-            return lhs.first < rhs.first;
-        });
-        if (positions.first == positions.second) {
-            std::cout << key << " not found\n";
-            return false;
-        }
-
-        for (auto& it = positions.first; it != positions.second; ++it) {
-            std::cout << it->first << " " << it->second << "\n";
-        }
-
-        return true;
-    }
-
-public:
-
-    IndexSearch(int fd, bool dense_format) :
-        m_fd(fd),
-        m_dense_format(dense_format) {
-    }
-
-    void dump() {
-        if (m_dense_format) {
-            dump_dense();
-        } else {
-            dump_sparse();
-        }
-    }
-
-    bool search(TKey key) {
-        if (m_dense_format) {
-            return search_dense(key);
-        } else {
-            return search_sparse(key);
-        }
-    }
-
-    bool search(const std::vector<TKey>& keys) {
-        bool found_all = true;
-
-        for (const auto key : keys) {
-            if (!search(key)) {
-                found_all = false;
-            }
-        }
-
-        return found_all;
-    }
-
-}; // class IndexSearch
-
-enum return_code : int {
-    okay      = 0,
-    not_found = 1,
-    error     = 2,
-    fatal     = 3
-};
-
-class Options {
-
-    std::vector<osmium::unsigned_object_id_type> m_ids;
-    std::string m_type;
-    std::string m_filename;
-    bool m_dump = false;
-    bool m_array_format = false;
-    bool m_list_format = false;
-
-    void print_help() {
-        std::cout << "Usage: osmium_index [OPTIONS]\n\n"
-                  << "-h, --help        Print this help message\n"
-                  << "-a, --array=FILE  Read given index file in array format\n"
-                  << "-l, --list=FILE   Read given index file in list format\n"
-                  << "-d, --dump        Dump contents of index file to STDOUT\n"
-                  << "-s, --search=ID   Search for given id (Option can appear multiple times)\n"
-                  << "-t, --type=TYPE   Type of value ('location' or 'offset')\n"
-        ;
-    }
-
-public:
-
-    Options(int argc, char* argv[]) {
-        static struct option long_options[] = {
-            {"array",  required_argument, 0, 'a'},
-            {"dump",         no_argument, 0, 'd'},
-            {"help",         no_argument, 0, 'h'},
-            {"list",   required_argument, 0, 'l'},
-            {"search", required_argument, 0, 's'},
-            {"type",   required_argument, 0, 't'},
-            {0, 0, 0, 0}
-        };
-
-        while (true) {
-            int c = getopt_long(argc, argv, "a:dhl:s:t:", long_options, 0);
-            if (c == -1) {
-                break;
-            }
-
-            switch (c) {
-                case 'a':
-                    m_array_format = true;
-                    m_filename = optarg;
-                    break;
-                case 'd':
-                    m_dump = true;
-                    break;
-                case 'h':
-                    print_help();
-                    std::exit(return_code::okay);
-                case 'l':
-                    m_list_format = true;
-                    m_filename = optarg;
-                    break;
-                case 's':
-                    m_ids.push_back(std::atoll(optarg));
-                    break;
-                case 't':
-                    m_type = optarg;
-                    if (m_type != "location" && m_type != "offset") {
-                        std::cerr << "Unknown type '" << m_type << "'. Must be 'location' or 'offset'.\n";
-                        std::exit(return_code::fatal);
-                    }
-                    break;
-                default:
-                    std::exit(return_code::fatal);
-            }
-        }
-
-        if (m_array_format == m_list_format) {
-            std::cerr << "Need option --array or --list, but not both\n";
-            std::exit(return_code::fatal);
-        }
-
-        if (m_type.empty()) {
-            std::cerr << "Need --type argument.\n";
-            std::exit(return_code::fatal);
-        }
-
-    }
-
-    const std::string& filename() const noexcept {
-        return m_filename;
-    }
-
-    bool dense_format() const noexcept {
-        return m_array_format;
-    }
-
-    bool do_dump() const noexcept {
-        return m_dump;
-    }
-
-    const std::vector<osmium::unsigned_object_id_type>& search_keys() const noexcept {
-        return m_ids;
-    }
-
-    bool type_is(const char* type) const noexcept {
-        return m_type == type;
-    }
-
-}; // class Options
-
-int main(int argc, char* argv[]) {
-    std::ios_base::sync_with_stdio(false);
-
-    Options options(argc, argv);
-
-    std::cout << std::fixed << std::setprecision(7);
-    int fd = open(options.filename().c_str(), O_RDWR);
-
-    bool result_okay = true;
-
-    if (options.type_is("location")) {
-        IndexSearch<osmium::unsigned_object_id_type, osmium::Location> is(fd, options.dense_format());
-
-        if (options.do_dump()) {
-            is.dump();
-        } else {
-            result_okay = is.search(options.search_keys());
-        }
-    } else {
-        IndexSearch<osmium::unsigned_object_id_type, size_t> is(fd, options.dense_format());
-
-        if (options.do_dump()) {
-            is.dump();
-        } else {
-            result_okay = is.search(options.search_keys());
-        }
-    }
-
-    std::exit(result_okay ? return_code::okay : return_code::not_found);
-}
-
diff --git a/examples/osmium_index_lookup.cpp b/examples/osmium_index_lookup.cpp
new file mode 100644
index 0000000..01d7e36
--- /dev/null
+++ b/examples/osmium_index_lookup.cpp
@@ -0,0 +1,333 @@
+/*
+
+  EXAMPLE osmium_index
+
+  Example program to look at Osmium indexes on disk.
+
+  You can use the osmium_dump_internal example program to create the offset
+  indexes or osmium_location_cache_create to create a node location index.
+
+  DEMONSTRATES USE OF:
+  * access to indexes on disk
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+  * osmium_road_length
+  * osmium_location_cache_create
+  * osmium_location_cache_use
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <algorithm>   // for std::all_of, std::equal_range
+#include <cstdlib>     // for std::exit
+#include <fcntl.h>     // for open
+#include <getopt.h>    // for getopt_long
+#include <iostream>    // for std::cout, std::cerr
+#include <memory>      // for std::unique_ptr
+#include <string>      // for std::string
+#include <sys/stat.h>  // for open
+#include <sys/types.h> // for open
+#include <vector>      // for std::vector
+
+// Disk-based indexes
+#include <osmium/index/map/dense_file_array.hpp>
+#include <osmium/index/map/sparse_file_array.hpp>
+
+// osmium::Location
+#include <osmium/osm/location.hpp>
+
+// Basic Osmium types
+#include <osmium/osm/types.hpp>
+
+// Virtual class for disk index access. If offers functions to dump the
+// indexes and to search for ids in the index.
+template <typename TValue>
+class IndexAccess {
+
+    int m_fd;
+
+public:
+
+    IndexAccess(int fd) :
+        m_fd(fd) {
+    }
+
+    int fd() const noexcept {
+        return m_fd;
+    }
+
+    virtual ~IndexAccess() = default;
+
+    virtual void dump() const = 0;
+
+    virtual bool search(const osmium::unsigned_object_id_type& key) const = 0;
+
+    bool search(const std::vector<osmium::unsigned_object_id_type>& keys) const {
+        return std::all_of(keys.cbegin(), keys.cend(), [this](const osmium::unsigned_object_id_type& key) {
+            return search(key);
+        });
+    }
+
+}; // class IndexAccess
+
+// Implementation of IndexAccess for dense indexes usually used for very large
+// extracts or the planet.
+template <typename TValue>
+class IndexAccessDense : public IndexAccess<TValue> {
+
+    using index_type  = typename osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, TValue>;
+
+public:
+
+    IndexAccessDense(int fd) :
+        IndexAccess<TValue>(fd) {
+    }
+
+    void dump() const override {
+        index_type index{this->fd()};
+
+        for (std::size_t i = 0; i < index.size(); ++i) {
+            if (index.get(i) != TValue{}) {
+                std::cout << i << " " << index.get(i) << "\n";
+            }
+        }
+    }
+
+    bool search(const osmium::unsigned_object_id_type& key) const override {
+        index_type index{this->fd()};
+
+        try {
+            TValue value = index.get(key);
+            std::cout << key << " " << value << "\n";
+        } catch (...) {
+            std::cout << key << " not found\n";
+            return false;
+        }
+
+        return true;
+    }
+
+}; // class IndexAccessDense
+
+// Implementation of IndexAccess for sparse indexes usually used for small or
+// medium sized extracts or for "multimap" type indexes.
+template <typename TValue>
+class IndexAccessSparse : public IndexAccess<TValue> {
+
+    using index_type = typename osmium::index::map::SparseFileArray<osmium::unsigned_object_id_type, TValue>;
+
+public:
+
+    IndexAccessSparse(int fd) :
+        IndexAccess<TValue>(fd) {
+    }
+
+    void dump() const override {
+        index_type index{this->fd()};
+
+        for (const auto& element : index) {
+            std::cout << element.first << " " << element.second << "\n";
+        }
+    }
+
+    bool search(const osmium::unsigned_object_id_type& key) const override {
+        using element_type = typename index_type::element_type;
+        index_type index{this->fd()};
+
+        element_type elem{key, TValue{}};
+        const auto positions = std::equal_range(index.begin(),
+                                                index.end(),
+                                                elem,
+                                                [](const element_type& lhs,
+                                                   const element_type& rhs) {
+            return lhs.first < rhs.first;
+        });
+        if (positions.first == positions.second) {
+            std::cout << key << " not found\n";
+            return false;
+        }
+
+        for (auto it = positions.first; it != positions.second; ++it) {
+            std::cout << it->first << " " << it->second << "\n";
+        }
+
+        return true;
+    }
+
+}; // class IndexAccessSparse
+
+// This class contains the code to parse the command line arguments, check
+// them and present the results to the rest of the program in an easy-to-use
+// way.
+class Options {
+
+    std::vector<osmium::unsigned_object_id_type> m_ids;
+    std::string m_type;
+    std::string m_filename;
+    bool m_dump = false;
+    bool m_array_format = false;
+    bool m_list_format = false;
+
+    void print_help() {
+        std::cout << "Usage: osmium_index_lookup [OPTIONS]\n\n"
+                  << "-h, --help        Print this help message\n"
+                  << "-a, --array=FILE  Read given index file in array format\n"
+                  << "-l, --list=FILE   Read given index file in list format\n"
+                  << "-d, --dump        Dump contents of index file to STDOUT\n"
+                  << "-s, --search=ID   Search for given id (Option can appear multiple times)\n"
+                  << "-t, --type=TYPE   Type of value ('location', 'id', or 'offset')\n"
+        ;
+    }
+
+public:
+
+    Options(int argc, char* argv[]) {
+        if (argc == 1) {
+            print_help();
+            std::exit(1);
+        }
+
+        static struct option long_options[] = {
+            {"array",  required_argument, 0, 'a'},
+            {"dump",         no_argument, 0, 'd'},
+            {"help",         no_argument, 0, 'h'},
+            {"list",   required_argument, 0, 'l'},
+            {"search", required_argument, 0, 's'},
+            {"type",   required_argument, 0, 't'},
+            {0, 0, 0, 0}
+        };
+
+        while (true) {
+            const int c = getopt_long(argc, argv, "a:dhl:s:t:", long_options, 0);
+            if (c == -1) {
+                break;
+            }
+
+            switch (c) {
+                case 'a':
+                    m_array_format = true;
+                    m_filename = optarg;
+                    break;
+                case 'd':
+                    m_dump = true;
+                    break;
+                case 'h':
+                    print_help();
+                    std::exit(0);
+                case 'l':
+                    m_list_format = true;
+                    m_filename = optarg;
+                    break;
+                case 's':
+                    m_ids.push_back(std::atoll(optarg));
+                    break;
+                case 't':
+                    m_type = optarg;
+                    if (m_type != "location" && m_type != "id" && m_type != "offset") {
+                        std::cerr << "Unknown type '" << m_type
+                                  << "'. Must be 'location', 'id', or 'offset'.\n";
+                        std::exit(2);
+                    }
+                    break;
+                default:
+                    std::exit(2);
+            }
+        }
+
+        if (m_array_format == m_list_format) {
+            std::cerr << "Need option --array or --list, but not both\n";
+            std::exit(2);
+        }
+
+        if (m_dump && !m_ids.empty()) {
+            std::cerr << "Need option --dump or --search, but not both\n";
+            std::exit(2);
+        }
+
+        if (m_type.empty()) {
+            std::cerr << "Need --type argument.\n";
+            std::exit(2);
+        }
+
+    }
+
+    const char* filename() const noexcept {
+        return m_filename.c_str();
+    }
+
+    bool dense_format() const noexcept {
+        return m_array_format;
+    }
+
+    bool do_dump() const noexcept {
+        return m_dump;
+    }
+
+    const std::vector<osmium::unsigned_object_id_type>& search_keys() const noexcept {
+        return m_ids;
+    }
+
+    bool type_is(const char* type) const noexcept {
+        return m_type == type;
+    }
+
+}; // class Options
+
+
+// Factory function to create the right IndexAccess-derived class.
+template <typename TValue>
+std::unique_ptr<IndexAccess<TValue>> create(bool dense, int fd) {
+    std::unique_ptr<IndexAccess<TValue>> ptr;
+
+    if (dense) {
+        ptr.reset(new IndexAccessDense<TValue>{fd});
+    } else {
+        ptr.reset(new IndexAccessSparse<TValue>{fd});
+    }
+
+    return ptr;
+}
+
+// Do the actual work: Either dump the index or search in the index.
+template <typename TValue>
+int run(const IndexAccess<TValue>& index, const Options& options) {
+    if (options.do_dump()) {
+        index.dump();
+        return 0;
+    } else {
+        return index.search(options.search_keys()) ? 0 : 1;
+    }
+}
+
+int main(int argc, char* argv[]) {
+    // Parse command line options.
+    Options options{argc, argv};
+
+    // Open the index file.
+    const int fd = open(options.filename(), O_RDWR);
+    if (fd < 0) {
+        std::cerr << "Can not open file '" << options.filename()
+                  << "': " << std::strerror(errno) << '\n';
+        std::exit(2);
+    }
+
+    // Depending on the type of index, we have different implementations.
+    if (options.type_is("location")) {
+        // index id -> location
+        const auto index = create<osmium::Location>(options.dense_format(), fd);
+        return run(*index, options);
+    } else if (options.type_is("id")) {
+        // index id -> id
+        const auto index = create<osmium::unsigned_object_id_type>(options.dense_format(), fd);
+        return run(*index, options);
+    } else {
+        // index id -> offset
+        const auto index = create<std::size_t>(options.dense_format(), fd);
+        return run(*index, options);
+    }
+}
+
diff --git a/examples/osmium_pub_names.cpp b/examples/osmium_pub_names.cpp
old mode 100755
new mode 100644
diff --git a/examples/osmium_road_length.cpp b/examples/osmium_road_length.cpp
old mode 100755
new mode 100644
diff --git a/examples/osmium_serdump.cpp b/examples/osmium_serdump.cpp
deleted file mode 100644
index 81a6d0c..0000000
--- a/examples/osmium_serdump.cpp
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
-
-  This is a small tool to dump the contents of the input file
-  in serialized format to stdout.
-
-  The code in this example file is released into the Public Domain.
-
-*/
-
-#include <cerrno>
-#include <cstring>
-#include <getopt.h>
-#include <iostream>
-#include <string>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#ifdef _MSC_VER
-# include <direct.h>
-#endif
-
-#include <osmium/io/any_input.hpp>
-#include <osmium/handler/disk_store.hpp>
-#include <osmium/handler/object_relations.hpp>
-
-#include <osmium/index/map/sparse_mem_array.hpp>
-#include <osmium/index/multimap/sparse_mem_multimap.hpp>
-#include <osmium/index/multimap/sparse_mem_array.hpp>
-#include <osmium/index/multimap/hybrid.hpp>
-
-// ==============================================================================
-// Choose the following depending on the size of the input OSM files:
-// ==============================================================================
-// for smaller OSM files (extracts)
-typedef osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, size_t> offset_index_type;
-//typedef osmium::index::map::SparseMapMmap<osmium::unsigned_object_id_type, size_t> offset_index_type;
-//typedef osmium::index::map::SparseMapFile<osmium::unsigned_object_id_type, size_t> offset_index_type;
-
-typedef osmium::index::multimap::SparseMemArray<osmium::unsigned_object_id_type, osmium::unsigned_object_id_type> map_type;
-//typedef osmium::index::multimap::SparseMemMultimap<osmium::unsigned_object_id_type, osmium::unsigned_object_id_type> map_type;
-//typedef osmium::index::multimap::Hybrid<osmium::unsigned_object_id_type, osmium::unsigned_object_id_type> map_type;
-
-// ==============================================================================
-// for very large OSM files (planet)
-//typedef osmium::index::map::DenseMmapArray<osmium::unsigned_object_id_type, size_t> offset_index_type;
-// ==============================================================================
-
-void print_help() {
-    std::cout << "osmium_serdump OSMFILE DIR\n" \
-              << "Serialize content of OSMFILE into data file in DIR.\n" \
-              << "\nOptions:\n" \
-              << "  -h, --help       This help message\n";
-}
-
-int main(int argc, char* argv[]) {
-    std::ios_base::sync_with_stdio(false);
-
-    static struct option long_options[] = {
-        {"help",      no_argument, 0, 'h'},
-        {0, 0, 0, 0}
-    };
-
-    while (true) {
-        int c = getopt_long(argc, argv, "h", long_options, 0);
-        if (c == -1) {
-            break;
-        }
-
-        switch (c) {
-            case 'h':
-                print_help();
-                std::exit(0);
-            default:
-                std::exit(2);
-        }
-    }
-
-    int remaining_args = argc - optind;
-
-    if (remaining_args != 2) {
-        std::cerr << "Usage: " << argv[0] << " OSMFILE DIR\n";
-        std::exit(2);
-    }
-
-    std::string dir(argv[optind+1]);
-#ifndef _WIN32
-    int result = ::mkdir(dir.c_str(), 0777);
-#else
-    int result = mkdir(dir.c_str());
-#endif
-    if (result == -1 && errno != EEXIST) {
-        std::cerr << "Problem creating directory '" << dir << "': " << strerror(errno) << "\n";
-        std::exit(2);
-    }
-
-    std::string data_file(dir + "/data.osm.ser");
-    int data_fd = ::open(data_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-    if (data_fd < 0) {
-        std::cerr << "Can't open data file '" << data_file << "': " << strerror(errno) << "\n";
-        std::exit(2);
-    }
-
-    offset_index_type node_index;
-    offset_index_type way_index;
-    offset_index_type relation_index;
-
-    osmium::handler::DiskStore disk_store_handler(data_fd, node_index, way_index, relation_index);
-
-    map_type map_node2way;
-    map_type map_node2relation;
-    map_type map_way2relation;
-    map_type map_relation2relation;
-
-    osmium::handler::ObjectRelations object_relations_handler(map_node2way, map_node2relation, map_way2relation, map_relation2relation);
-
-    osmium::io::Reader reader(argv[1]);
-
-    while (osmium::memory::Buffer buffer = reader.read()) {
-        disk_store_handler(buffer); // XXX
-        osmium::apply(buffer, object_relations_handler);
-    }
-
-    reader.close();
-
-    {
-        std::string index_file(dir + "/nodes.idx");
-        int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-        if (fd < 0) {
-            std::cerr << "Can't open nodes index file '" << index_file << "': " << strerror(errno) << "\n";
-            std::exit(2);
-        }
-        node_index.dump_as_list(fd);
-        close(fd);
-    }
-
-    {
-        std::string index_file(dir + "/ways.idx");
-        int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-        if (fd < 0) {
-            std::cerr << "Can't open ways index file '" << index_file << "': " << strerror(errno) << "\n";
-            std::exit(2);
-        }
-        way_index.dump_as_list(fd);
-        close(fd);
-    }
-
-    {
-        std::string index_file(dir + "/relations.idx");
-        int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-        if (fd < 0) {
-            std::cerr << "Can't open relations index file '" << index_file << "': " << strerror(errno) << "\n";
-            std::exit(2);
-        }
-        relation_index.dump_as_list(fd);
-        close(fd);
-    }
-
-    {
-        map_node2way.sort();
-        std::string index_file(dir + "/node2way.map");
-        int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-        if (fd < 0) {
-            std::cerr << "Can't open node->way map file '" << index_file << "': " << strerror(errno) << "\n";
-            std::exit(2);
-        }
-        map_node2way.dump_as_list(fd);
-        close(fd);
-    }
-
-    {
-        map_node2relation.sort();
-        std::string index_file(dir + "/node2rel.map");
-        int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-        if (fd < 0) {
-            std::cerr << "Can't open node->rel map file '" << index_file << "': " << strerror(errno) << "\n";
-            std::exit(2);
-        }
-        map_node2relation.dump_as_list(fd);
-        close(fd);
-    }
-
-    {
-        map_way2relation.sort();
-        std::string index_file(dir + "/way2rel.map");
-        int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-        if (fd < 0) {
-            std::cerr << "Can't open way->rel map file '" << index_file << "': " << strerror(errno) << "\n";
-            std::exit(2);
-        }
-        map_way2relation.dump_as_list(fd);
-        close(fd);
-    }
-
-    {
-        map_relation2relation.sort();
-        std::string index_file(dir + "/rel2rel.map");
-        int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-        if (fd < 0) {
-            std::cerr << "Can't open rel->rel map file '" << index_file << "': " << strerror(errno) << "\n";
-            std::exit(2);
-        }
-        map_relation2relation.dump_as_list(fd);
-        close(fd);
-    }
-}
-
diff --git a/include/osmium/area/assembler.hpp b/include/osmium/area/assembler.hpp
index d5bf8d8..092f4b4 100644
--- a/include/osmium/area/assembler.hpp
+++ b/include/osmium/area/assembler.hpp
@@ -193,12 +193,12 @@ namespace osmium {
 
             }; // struct location_to_ring_map
 
-            inline bool operator==(const location_to_ring_map& a, const location_to_ring_map& b) noexcept {
-                return a.location == b.location;
+            inline bool operator==(const location_to_ring_map& lhs, const location_to_ring_map& rhs) noexcept {
+                return lhs.location == rhs.location;
             }
 
-            inline bool operator<(const location_to_ring_map& a, const location_to_ring_map& b) noexcept {
-                return a.location < b.location;
+            inline bool operator<(const location_to_ring_map& lhs, const location_to_ring_map& rhs) noexcept {
+                return lhs.location < rhs.location;
             }
 
         } // namespace detail
@@ -288,7 +288,7 @@ namespace osmium {
             }
 
             void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Way& way) const {
-                builder.add_item(&way.tags());
+                builder.add_item(way.tags());
             }
 
             void add_common_tags(osmium::builder::TagListBuilder& tl_builder, std::set<const osmium::Way*>& ways) const {
@@ -333,7 +333,7 @@ namespace osmium {
             }
 
             static void copy_tags_without_type(osmium::builder::AreaBuilder& builder, const osmium::TagList& tags) {
-                osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder);
+                osmium::builder::TagListBuilder tl_builder{builder};
                 for (const osmium::Tag& tag : tags) {
                     if (std::strcmp(tag.key(), "type")) {
                         tl_builder.add_tag(tag.key(), tag.value());
@@ -354,7 +354,7 @@ namespace osmium {
                     }
 
                     if (m_config.keep_type_tag) {
-                        builder.add_item(&relation.tags());
+                        builder.add_item(relation.tags());
                     } else {
                         copy_tags_without_type(builder, relation.tags());
                     }
@@ -373,12 +373,12 @@ namespace osmium {
                         if (debug()) {
                             std::cerr << "      only one outer way\n";
                         }
-                        builder.add_item(&(*ways.cbegin())->tags());
+                        builder.add_item((*ways.cbegin())->tags());
                     } else {
                         if (debug()) {
                             std::cerr << "      multiple outer ways, get common tags\n";
                         }
-                        osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder);
+                        osmium::builder::TagListBuilder tl_builder{builder};
                         add_common_tags(tl_builder, ways);
                     }
                 }
@@ -386,7 +386,7 @@ namespace osmium {
 
             template <typename TBuilder>
             static void build_ring_from_proto_ring(osmium::builder::AreaBuilder& builder, const detail::ProtoRing& ring) {
-                TBuilder ring_builder(builder.buffer(), &builder);
+                TBuilder ring_builder{builder};
                 ring_builder.add_node_ref(ring.get_node_ref_start());
                 for (const auto& segment : ring.segments()) {
                     ring_builder.add_node_ref(segment->stop());
@@ -458,8 +458,8 @@ namespace osmium {
             }
 
             detail::NodeRefSegment* get_next_segment(const osmium::Location& location) {
-                auto it = std::lower_bound(m_locations.begin(), m_locations.end(), slocation{}, [this, &location](const slocation& a, const slocation& b) {
-                    return a.location(m_segment_list, location) < b.location(m_segment_list, location);
+                auto it = std::lower_bound(m_locations.begin(), m_locations.end(), slocation{}, [this, &location](const slocation& lhs, const slocation& rhs) {
+                    return lhs.location(m_segment_list, location) < rhs.location(m_segment_list, location);
                 });
 
                 assert(it != m_locations.end());
@@ -744,8 +744,8 @@ namespace osmium {
                     m_locations.emplace_back(n, true);
                 }
 
-                std::stable_sort(m_locations.begin(), m_locations.end(), [this](const slocation& a, const slocation& b) {
-                    return a.location(m_segment_list) < b.location(m_segment_list);
+                std::stable_sort(m_locations.begin(), m_locations.end(), [this](const slocation& lhs, const slocation& rhs) {
+                    return lhs.location(m_segment_list) < rhs.location(m_segment_list);
                 });
             }
 
@@ -1015,8 +1015,8 @@ namespace osmium {
 
                 std::vector<location_to_ring_map> xrings = create_location_to_ring_map(open_ring_its);
 
-                const auto ring_min = std::min_element(xrings.begin(), xrings.end(), [](const location_to_ring_map& a, const location_to_ring_map& b) {
-                    return a.ring().min_segment() < b.ring().min_segment();
+                const auto ring_min = std::min_element(xrings.begin(), xrings.end(), [](const location_to_ring_map& lhs, const location_to_ring_map& rhs) {
+                    return lhs.ring().min_segment() < rhs.ring().min_segment();
                 });
 
                 find_inner_outer_complex();
@@ -1068,11 +1068,11 @@ namespace osmium {
 
                 // Find the candidate with the smallest/largest area
                 const auto chosen_cand = ring_min_is_outer ?
-                     std::min_element(candidates.cbegin(), candidates.cend(), [](const candidate& a, const candidate& b) {
-                        return std::abs(a.sum) < std::abs(b.sum);
+                     std::min_element(candidates.cbegin(), candidates.cend(), [](const candidate& lhs, const candidate& rhs) {
+                        return std::abs(lhs.sum) < std::abs(rhs.sum);
                      }) :
-                     std::max_element(candidates.cbegin(), candidates.cend(), [](const candidate& a, const candidate& b) {
-                        return std::abs(a.sum) < std::abs(b.sum);
+                     std::max_element(candidates.cbegin(), candidates.cend(), [](const candidate& lhs, const candidate& rhs) {
+                        return std::abs(lhs.sum) < std::abs(rhs.sum);
                      });
 
                 if (debug()) {
@@ -1103,8 +1103,8 @@ namespace osmium {
                     const auto locs = make_range(std::equal_range(m_locations.begin(),
                                                                   m_locations.end(),
                                                                   slocation{},
-                                                                  [this, &location](const slocation& a, const slocation& b) {
-                        return a.location(m_segment_list, location) < b.location(m_segment_list, location);
+                                                                  [this, &location](const slocation& lhs, const slocation& rhs) {
+                        return lhs.location(m_segment_list, location) < rhs.location(m_segment_list, location);
                     }));
                     for (auto& loc : locs) {
                         if (!m_segment_list[loc.item].is_done()) {
@@ -1267,8 +1267,8 @@ namespace osmium {
                     }
                     for (const auto& location : m_split_locations) {
                         if (m_config.problem_reporter) {
-                            auto it = std::lower_bound(m_locations.cbegin(), m_locations.cend(), slocation{}, [this, &location](const slocation& a, const slocation& b) {
-                                return a.location(m_segment_list, location) < b.location(m_segment_list, location);
+                            auto it = std::lower_bound(m_locations.cbegin(), m_locations.cend(), slocation{}, [this, &location](const slocation& lhs, const slocation& rhs) {
+                                return lhs.location(m_segment_list, location) < rhs.location(m_segment_list, location);
                             });
                             assert(it != m_locations.cend());
                             const osmium::object_id_type id = it->node_ref(m_segment_list).ref();
@@ -1362,7 +1362,7 @@ namespace osmium {
 #endif
 
             bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Way& way) {
-                osmium::builder::AreaBuilder builder(out_buffer);
+                osmium::builder::AreaBuilder builder{out_buffer};
                 builder.initialize_from_object(way);
 
                 const bool area_okay = create_rings();
@@ -1382,7 +1382,7 @@ namespace osmium {
 
             bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Relation& relation, const std::vector<const osmium::Way*>& members) {
                 m_num_members = members.size();
-                osmium::builder::AreaBuilder builder(out_buffer);
+                osmium::builder::AreaBuilder builder{out_buffer};
                 builder.initialize_from_object(relation);
 
                 const bool area_okay = create_rings();
diff --git a/include/osmium/area/detail/node_ref_segment.hpp b/include/osmium/area/detail/node_ref_segment.hpp
index b131a43..1c03d3e 100644
--- a/include/osmium/area/detail/node_ref_segment.hpp
+++ b/include/osmium/area/detail/node_ref_segment.hpp
@@ -379,8 +379,8 @@ namespace osmium {
                     sl[2] = {1, s2.first().location() };
                     sl[3] = {1, s2.second().location()};
 
-                    std::sort(sl, sl+4, [](const seg_loc& a, const seg_loc& b) {
-                        return a.location < b.location;
+                    std::sort(sl, sl+4, [](const seg_loc& lhs, const seg_loc& rhs) {
+                        return lhs.location < rhs.location;
                     });
 
                     if (sl[1].location == sl[2].location) {
diff --git a/include/osmium/area/detail/segment_list.hpp b/include/osmium/area/detail/segment_list.hpp
index a4361e0..97d512a 100644
--- a/include/osmium/area/detail/segment_list.hpp
+++ b/include/osmium/area/detail/segment_list.hpp
@@ -101,7 +101,7 @@ namespace osmium {
                  * Calculate the number of segments in all the ways together.
                  */
                 static size_t get_num_segments(const std::vector<const osmium::Way*>& members) noexcept {
-                    return std::accumulate(members.cbegin(), members.cend(), 0, [](size_t sum, const osmium::Way* way) {
+                    return std::accumulate(members.cbegin(), members.cend(), static_cast<size_t>(0), [](size_t sum, const osmium::Way* way) {
                         if (way->nodes().empty()) {
                             return sum;
                         } else {
diff --git a/include/osmium/area/detail/vector.hpp b/include/osmium/area/detail/vector.hpp
index 44983cc..fae1280 100644
--- a/include/osmium/area/detail/vector.hpp
+++ b/include/osmium/area/detail/vector.hpp
@@ -73,18 +73,18 @@ namespace osmium {
             }; // struct vec
 
             // addition
-            constexpr inline vec operator+(const vec& a, const vec& b) noexcept {
-                return vec{a.x + b.x, a.y + b.y};
+            constexpr inline vec operator+(const vec& lhs, const vec& rhs) noexcept {
+                return vec{lhs.x + rhs.x, lhs.y + rhs.y};
             }
 
             // subtraction
-            constexpr inline vec operator-(const vec& a, const vec& b) noexcept {
-                return vec{a.x - b.x, a.y - b.y};
+            constexpr inline vec operator-(const vec& lhs, const vec& rhs) noexcept {
+                return vec{lhs.x - rhs.x, lhs.y - rhs.y};
             }
 
             // cross product
-            constexpr inline int64_t operator*(const vec& a, const vec& b) noexcept {
-                return a.x * b.y - a.y * b.x;
+            constexpr inline int64_t operator*(const vec& lhs, const vec& rhs) noexcept {
+                return lhs.x * rhs.y - lhs.y * rhs.x;
             }
 
             // scale vector
@@ -98,13 +98,13 @@ namespace osmium {
             }
 
             // equality
-            constexpr inline bool operator==(const vec& a, const vec& b) noexcept {
-                return a.x == b.x && a.y == b.y;
+            constexpr inline bool operator==(const vec& lhs, const vec& rhs) noexcept {
+                return lhs.x == rhs.x && lhs.y == rhs.y;
             }
 
             // inequality
-            constexpr inline bool operator!=(const vec& a, const vec& b) noexcept {
-                return !(a == b);
+            constexpr inline bool operator!=(const vec& lhs, const vec& rhs) noexcept {
+                return !(lhs == rhs);
             }
 
             template <typename TChar, typename TTraits>
diff --git a/include/osmium/builder/attr.hpp b/include/osmium/builder/attr.hpp
index 2a5b690..8e0b4a3 100644
--- a/include/osmium/builder/attr.hpp
+++ b/include/osmium/builder/attr.hpp
@@ -617,7 +617,7 @@ namespace osmium {
 
             template <typename TBuilder, typename... TArgs>
             inline void add_user(TBuilder& builder, const TArgs&... args) {
-                builder.add_user(get_user(args...));
+                builder.set_user(get_user(args...));
             }
 
             // ==============================================================
@@ -761,11 +761,13 @@ namespace osmium {
             static_assert(sizeof...(args) > 0, "add_node() must have buffer and at least one additional argument");
             static_assert(detail::are_all_handled_by<detail::any_node_handlers, TArgs...>::value, "Attribute not allowed in add_node()");
 
-            NodeBuilder builder(buffer);
+            {
+                NodeBuilder builder(buffer);
 
-            detail::add_basic<detail::node_handler>(builder, args...);
-            detail::add_user(builder, args...);
-            detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
+                detail::add_basic<detail::node_handler>(builder, args...);
+                detail::add_user(builder, args...);
+                detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
+            }
 
             return buffer.commit();
         }
@@ -782,12 +784,14 @@ namespace osmium {
             static_assert(sizeof...(args) > 0, "add_way() must have buffer and at least one additional argument");
             static_assert(detail::are_all_handled_by<detail::any_way_handlers, TArgs...>::value, "Attribute not allowed in add_way()");
 
-            WayBuilder builder(buffer);
+            {
+                WayBuilder builder(buffer);
 
-            detail::add_basic<detail::object_handler>(builder, args...);
-            detail::add_user(builder, args...);
-            detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
-            detail::add_list<WayNodeListBuilder, detail::nodes_handler>(builder, args...);
+                detail::add_basic<detail::object_handler>(builder, args...);
+                detail::add_user(builder, args...);
+                detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
+                detail::add_list<WayNodeListBuilder, detail::nodes_handler>(builder, args...);
+            }
 
             return buffer.commit();
         }
@@ -804,12 +808,14 @@ namespace osmium {
             static_assert(sizeof...(args) > 0, "add_relation() must have buffer and at least one additional argument");
             static_assert(detail::are_all_handled_by<detail::any_relation_handlers, TArgs...>::value, "Attribute not allowed in add_relation()");
 
-            RelationBuilder builder(buffer);
+            {
+                RelationBuilder builder(buffer);
 
-            detail::add_basic<detail::object_handler>(builder, args...);
-            detail::add_user(builder, args...);
-            detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
-            detail::add_list<RelationMemberListBuilder, detail::members_handler>(builder, args...);
+                detail::add_basic<detail::object_handler>(builder, args...);
+                detail::add_user(builder, args...);
+                detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
+                detail::add_list<RelationMemberListBuilder, detail::members_handler>(builder, args...);
+            }
 
             return buffer.commit();
         }
@@ -826,12 +832,14 @@ namespace osmium {
             static_assert(sizeof...(args) > 0, "add_changeset() must have buffer and at least one additional argument");
             static_assert(detail::are_all_handled_by<detail::any_changeset_handlers, TArgs...>::value, "Attribute not allowed in add_changeset()");
 
-            ChangesetBuilder builder(buffer);
+            {
+                ChangesetBuilder builder(buffer);
 
-            detail::add_basic<detail::changeset_handler>(builder, args...);
-            detail::add_user(builder, args...);
-            detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
-            detail::add_list<ChangesetDiscussionBuilder, detail::discussion_handler>(builder, args...);
+                detail::add_basic<detail::changeset_handler>(builder, args...);
+                detail::add_user(builder, args...);
+                detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
+                detail::add_list<ChangesetDiscussionBuilder, detail::discussion_handler>(builder, args...);
+            }
 
             return buffer.commit();
         }
@@ -848,15 +856,17 @@ namespace osmium {
             static_assert(sizeof...(args) > 0, "add_area() must have buffer and at least one additional argument");
             static_assert(detail::are_all_handled_by<detail::any_area_handlers, TArgs...>::value, "Attribute not allowed in add_area()");
 
-            AreaBuilder builder(buffer);
+            {
+                AreaBuilder builder(buffer);
 
-            detail::add_basic<detail::object_handler>(builder, args...);
-            detail::add_user(builder, args...);
-            detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
+                detail::add_basic<detail::object_handler>(builder, args...);
+                detail::add_user(builder, args...);
+                detail::add_list<TagListBuilder, detail::tags_handler>(builder, args...);
 
-            (void)std::initializer_list<int>{
-                (detail::ring_handler::set_value(builder, args), 0)...
-            };
+                (void)std::initializer_list<int>{
+                    (detail::ring_handler::set_value(builder, args), 0)...
+                };
+            }
 
             return buffer.commit();
         }
diff --git a/include/osmium/builder/builder.hpp b/include/osmium/builder/builder.hpp
index 1b274ad..044da1e 100644
--- a/include/osmium/builder/builder.hpp
+++ b/include/osmium/builder/builder.hpp
@@ -45,6 +45,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/memory/item.hpp>
 #include <osmium/osm/types.hpp>
 #include <osmium/util/cast.hpp>
+#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
@@ -53,6 +54,10 @@ namespace osmium {
      */
     namespace builder {
 
+        /**
+         * Parent class for individual builder classes. Instantiate one of
+         * its derived classes.
+         */
         class Builder {
 
             osmium::memory::Buffer& m_buffer;
@@ -71,20 +76,34 @@ namespace osmium {
                 m_buffer(buffer),
                 m_parent(parent),
                 m_item_offset(buffer.written()) {
-                m_buffer.reserve_space(size);
+                reserve_space(size);
                 assert(buffer.is_aligned());
                 if (m_parent) {
+                    assert(m_buffer.builder_count() == 1 && "Only one sub-builder can be open at any time.");
                     m_parent->add_size(size);
+                } else {
+                    assert(m_buffer.builder_count() == 0 && "Only one builder can be open at any time.");
                 }
+#ifndef NDEBUG
+                m_buffer.increment_builder_count();
+#endif
             }
 
+#ifdef NDEBUG
             ~Builder() = default;
+#else
+            ~Builder() noexcept {
+                m_buffer.decrement_builder_count();
+            }
+#endif
 
             osmium::memory::Item& item() const {
                 return *reinterpret_cast<osmium::memory::Item*>(m_buffer.data() + m_item_offset);
             }
 
-        public:
+            unsigned char* reserve_space(size_t size) {
+                return m_buffer.reserve_space(size);
+            }
 
             /**
              * Add padding to buffer (if needed) to align data properly.
@@ -102,7 +121,7 @@ namespace osmium {
             void add_padding(bool self = false) {
                 const auto padding = osmium::memory::align_bytes - (size() % osmium::memory::align_bytes);
                 if (padding != osmium::memory::align_bytes) {
-                    std::fill_n(m_buffer.reserve_space(padding), padding, 0);
+                    std::fill_n(reserve_space(padding), padding, 0);
                     if (self) {
                         add_size(padding);
                     } else if (m_parent) {
@@ -123,12 +142,6 @@ namespace osmium {
                 return item().byte_size();
             }
 
-            void add_item(const osmium::memory::Item* item) {
-                unsigned char* target = m_buffer.reserve_space(item->padded_size());
-                std::copy_n(reinterpret_cast<const unsigned char*>(item), item->padded_size(), target);
-                add_size(item->padded_size());
-            }
-
             /**
              * Reserve space for an object of class T in buffer and return
              * pointer to it.
@@ -136,7 +149,7 @@ namespace osmium {
             template <typename T>
             T* reserve_space_for() {
                 assert(m_buffer.is_aligned());
-                return reinterpret_cast<T*>(m_buffer.reserve_space(sizeof(T)));
+                return reinterpret_cast<T*>(reserve_space(sizeof(T)));
             }
 
             /**
@@ -149,7 +162,7 @@ namespace osmium {
              * @returns The number of bytes appended (length).
              */
             osmium::memory::item_size_type append(const char* data, const osmium::memory::item_size_type length) {
-                unsigned char* target = m_buffer.reserve_space(length);
+                unsigned char* target = reserve_space(length);
                 std::copy_n(reinterpret_cast<const unsigned char*>(data), length, target);
                 return length;
             }
@@ -170,64 +183,36 @@ namespace osmium {
              * @returns The number of bytes appended (always 1).
              */
             osmium::memory::item_size_type append_zero() {
-                *m_buffer.reserve_space(1) = '\0';
+                *reserve_space(1) = '\0';
                 return 1;
             }
 
+        public:
+
             /// Return the buffer this builder is using.
             osmium::memory::Buffer& buffer() noexcept {
                 return m_buffer;
             }
 
-        }; // class Builder
-
-        template <typename TItem>
-        class ObjectBuilder : public Builder {
-
-            static_assert(std::is_base_of<osmium::memory::Item, TItem>::value, "ObjectBuilder can only build objects derived from osmium::memory::Item");
-
-        public:
-
-            explicit ObjectBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                Builder(buffer, parent, sizeof(TItem)) {
-                new (&item()) TItem();
-            }
-
-            TItem& object() noexcept {
-                return static_cast<TItem&>(item());
-            }
-
-            /**
-             * Add user name to buffer.
-             *
-             * @param user Pointer to user name.
-             * @param length Length of user name (without \0 termination).
-             */
-            void add_user(const char* user, const string_size_type length) {
-                object().set_user_size(length + 1);
-                add_size(append(user, length) + append_zero());
-                add_padding(true);
-            }
-
             /**
-             * Add user name to buffer.
-             *
-             * @param user Pointer to \0-terminated user name.
+             * Add a subitem to the object being built. This can be something
+             * like a TagList or RelationMemberList.
              */
-            void add_user(const char* user) {
-                add_user(user, static_cast_with_assert<string_size_type>(std::strlen(user)));
+            void add_item(const osmium::memory::Item& item) {
+                m_buffer.add_item(item);
+                add_size(item.padded_size());
             }
 
             /**
-             * Add user name to buffer.
-             *
-             * @param user User name.
+             * @deprecated Use the version of add_item() taking a
+             *             reference instead.
              */
-            void add_user(const std::string& user) {
-                add_user(user.data(), static_cast_with_assert<string_size_type>(user.size()));
+            OSMIUM_DEPRECATED void add_item(const osmium::memory::Item* item) {
+                assert(item);
+                add_item(*item);
             }
 
-        }; // class ObjectBuilder
+        }; // class Builder
 
     } // namespace builder
 
diff --git a/include/osmium/builder/osm_object_builder.hpp b/include/osmium/builder/osm_object_builder.hpp
index e7a8298..b1c7220 100644
--- a/include/osmium/builder/osm_object_builder.hpp
+++ b/include/osmium/builder/osm_object_builder.hpp
@@ -45,6 +45,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/builder/builder.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
+#include <osmium/osm/node.hpp>
 #include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/object.hpp>
 #include <osmium/osm/tag.hpp>
@@ -55,6 +56,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/timestamp.hpp>
 #include <osmium/osm/way.hpp>
+#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
@@ -66,12 +68,18 @@ namespace osmium {
 
     namespace builder {
 
-        class TagListBuilder : public ObjectBuilder<TagList> {
+        class TagListBuilder : public Builder {
 
         public:
 
             explicit TagListBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                ObjectBuilder<TagList>(buffer, parent) {
+                Builder(buffer, parent, sizeof(TagList)) {
+                new (&item()) TagList();
+            }
+
+            explicit TagListBuilder(Builder& parent) :
+                Builder(parent.buffer(), &parent, sizeof(TagList)) {
+                new (&item()) TagList();
             }
 
             ~TagListBuilder() {
@@ -169,21 +177,27 @@ namespace osmium {
         }; // class TagListBuilder
 
         template <typename T>
-        class NodeRefListBuilder : public ObjectBuilder<T> {
+        class NodeRefListBuilder : public Builder {
 
         public:
 
             explicit NodeRefListBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                ObjectBuilder<T>(buffer, parent) {
+                Builder(buffer, parent, sizeof(T)) {
+                new (&item()) T();
+            }
+
+            explicit NodeRefListBuilder(Builder& parent) :
+                Builder(parent.buffer(), &parent, sizeof(T)) {
+                new (&item()) T();
             }
 
             ~NodeRefListBuilder() {
-                static_cast<Builder*>(this)->add_padding();
+                add_padding();
             }
 
             void add_node_ref(const NodeRef& node_ref) {
-                new (static_cast<Builder*>(this)->reserve_space_for<osmium::NodeRef>()) osmium::NodeRef(node_ref);
-                static_cast<Builder*>(this)->add_size(sizeof(osmium::NodeRef));
+                new (reserve_space_for<osmium::NodeRef>()) osmium::NodeRef(node_ref);
+                add_size(sizeof(osmium::NodeRef));
             }
 
             void add_node_ref(const object_id_type ref, const osmium::Location& location = Location{}) {
@@ -196,7 +210,7 @@ namespace osmium {
         using OuterRingBuilder   = NodeRefListBuilder<OuterRing>;
         using InnerRingBuilder   = NodeRefListBuilder<InnerRing>;
 
-        class RelationMemberListBuilder : public ObjectBuilder<RelationMemberList> {
+        class RelationMemberListBuilder : public Builder {
 
             /**
              * Add role to buffer.
@@ -219,7 +233,13 @@ namespace osmium {
         public:
 
             explicit RelationMemberListBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                ObjectBuilder<RelationMemberList>(buffer, parent) {
+                Builder(buffer, parent, sizeof(RelationMemberList)) {
+                new (&item()) RelationMemberList();
+            }
+
+            explicit RelationMemberListBuilder(Builder& parent) :
+                Builder(parent.buffer(), &parent, sizeof(RelationMemberList)) {
+                new (&item()) RelationMemberList();
             }
 
             ~RelationMemberListBuilder() {
@@ -245,7 +265,7 @@ namespace osmium {
                 add_size(sizeof(RelationMember));
                 add_role(*member, role, role_length);
                 if (full_member) {
-                    add_item(full_member);
+                    add_item(*full_member);
                 }
             }
 
@@ -281,7 +301,7 @@ namespace osmium {
 
         }; // class RelationMemberListBuilder
 
-        class ChangesetDiscussionBuilder : public ObjectBuilder<ChangesetDiscussion> {
+        class ChangesetDiscussionBuilder : public Builder {
 
             osmium::ChangesetComment* m_comment = nullptr;
 
@@ -309,7 +329,13 @@ namespace osmium {
         public:
 
             explicit ChangesetDiscussionBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                ObjectBuilder<ChangesetDiscussion>(buffer, parent) {
+                Builder(buffer, parent, sizeof(ChangesetDiscussion)) {
+                new (&item()) ChangesetDiscussion();
+            }
+
+            explicit ChangesetDiscussionBuilder(Builder& parent) :
+                Builder(parent.buffer(), &parent, sizeof(ChangesetDiscussion)) {
+                new (&item()) ChangesetDiscussion();
             }
 
             ~ChangesetDiscussionBuilder() {
@@ -339,19 +365,101 @@ namespace osmium {
 
         }; // class ChangesetDiscussionBuilder
 
-        template <typename T>
-        class OSMObjectBuilder : public ObjectBuilder<T> {
+#define OSMIUM_FORWARD(setter) \
+    template <typename... TArgs> \
+    type& setter(TArgs&&... args) { \
+        object().setter(std::forward<TArgs>(args)...); \
+        return static_cast<type&>(*this); \
+    }
+
+        template <typename TDerived, typename T>
+        class OSMObjectBuilder : public Builder {
+
+            using type = TDerived;
+
+            constexpr static const size_t min_size_for_user = osmium::memory::padded_length(sizeof(string_size_type) + 1);
 
         public:
 
             explicit OSMObjectBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                ObjectBuilder<T>(buffer, parent) {
-                static_cast<Builder*>(this)->reserve_space_for<string_size_type>();
-                static_cast<Builder*>(this)->add_size(sizeof(string_size_type));
+                Builder(buffer, parent, sizeof(T) + min_size_for_user) {
+                new (&item()) T();
+                add_size(min_size_for_user);
+                std::fill_n(object().data() + sizeof(T), min_size_for_user, 0);
+                object().set_user_size(1);
+            }
+
+            /**
+             * Get a reference to the object buing built.
+             *
+             * Note that this reference will be invalidated by every action
+             * on the builder that might make the buffer grow. This includes
+             * calls to set_user() and any time a new sub-builder is created.
+             */
+            T& object() noexcept {
+                return static_cast<T&>(item());
+            }
+
+            /**
+             * Set user name.
+             *
+             * @param user Pointer to user name.
+             * @param length Length of user name (without \0 termination).
+             */
+            TDerived& set_user(const char* user, const string_size_type length) {
+                const auto size_of_object = sizeof(T) + sizeof(string_size_type);
+                assert(object().user_size() == 1 && (size() <= size_of_object + osmium::memory::padded_length(1))
+                       && "set_user() must be called at most once and before any sub-builders");
+                const auto available_space = min_size_for_user - sizeof(string_size_type) - 1;
+                if (length > available_space) {
+                    const auto space_needed = osmium::memory::padded_length(length - available_space);
+                    reserve_space(space_needed);
+                    add_size(static_cast<uint32_t>(space_needed));
+                }
+                std::copy_n(user, length, object().data() + size_of_object);
+                std::fill_n(object().data() + size_of_object + length, osmium::memory::padded_length(length + 1) - length, 0);
+                object().set_user_size(length + 1);
+
+                return static_cast<TDerived&>(*this);
             }
 
+            /**
+             * Set user name.
+             *
+             * @param user Pointer to \0-terminated user name.
+             */
+            TDerived& set_user(const char* user) {
+                return set_user(user, static_cast_with_assert<string_size_type>(std::strlen(user)));
+            }
+
+            /**
+             * Set user name.
+             *
+             * @param user User name.
+             */
+            TDerived& set_user(const std::string& user) {
+                return set_user(user.data(), static_cast_with_assert<string_size_type>(user.size()));
+            }
+
+            /// @deprecated Use set_user(...) instead.
+            template <typename... TArgs>
+            OSMIUM_DEPRECATED void add_user(TArgs&&... args) {
+                set_user(std::forward<TArgs>(args)...);
+            }
+
+            OSMIUM_FORWARD(set_id)
+            OSMIUM_FORWARD(set_visible)
+            OSMIUM_FORWARD(set_deleted)
+            OSMIUM_FORWARD(set_version)
+            OSMIUM_FORWARD(set_changeset)
+            OSMIUM_FORWARD(set_uid)
+            OSMIUM_FORWARD(set_uid_from_signed)
+            OSMIUM_FORWARD(set_timestamp)
+            OSMIUM_FORWARD(set_attribute)
+            OSMIUM_FORWARD(set_removed)
+
             void add_tags(const std::initializer_list<std::pair<const char*, const char*>>& tags) {
-                osmium::builder::TagListBuilder tl_builder(static_cast<Builder*>(this)->buffer(), this);
+                osmium::builder::TagListBuilder tl_builder{buffer(), this};
                 for (const auto& p : tags) {
                     tl_builder.add_tag(p.first, p.second);
                 }
@@ -359,19 +467,40 @@ namespace osmium {
 
         }; // class OSMObjectBuilder
 
-        using NodeBuilder     = OSMObjectBuilder<osmium::Node>;
-        using RelationBuilder = OSMObjectBuilder<osmium::Relation>;
+        class NodeBuilder : public OSMObjectBuilder<NodeBuilder, Node> {
 
-        class WayBuilder : public OSMObjectBuilder<osmium::Way> {
+            using type = NodeBuilder;
+
+        public:
+
+            explicit NodeBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
+                OSMObjectBuilder<NodeBuilder, Node>(buffer, parent) {
+            }
+
+            explicit NodeBuilder(Builder& parent) :
+                OSMObjectBuilder<NodeBuilder, Node>(parent.buffer(), &parent) {
+            }
+
+            OSMIUM_FORWARD(set_location)
+
+        }; // class NodeBuilder
+
+        class WayBuilder : public OSMObjectBuilder<WayBuilder, Way> {
+
+            using type = WayBuilder;
 
         public:
 
             explicit WayBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                OSMObjectBuilder<osmium::Way>(buffer, parent) {
+                OSMObjectBuilder<WayBuilder, Way>(buffer, parent) {
+            }
+
+            explicit WayBuilder(Builder& parent) :
+                OSMObjectBuilder<WayBuilder, Way>(parent.buffer(), &parent) {
             }
 
             void add_node_refs(const std::initializer_list<osmium::NodeRef>& nodes) {
-                osmium::builder::WayNodeListBuilder builder(buffer(), this);
+                osmium::builder::WayNodeListBuilder builder{buffer(), this};
                 for (const auto& node_ref : nodes) {
                     builder.add_node_ref(node_ref);
                 }
@@ -379,32 +508,147 @@ namespace osmium {
 
         }; // class WayBuilder
 
-        class AreaBuilder : public OSMObjectBuilder<osmium::Area> {
+        class RelationBuilder : public OSMObjectBuilder<RelationBuilder, Relation> {
+
+            using type = RelationBuilder;
+
+        public:
+
+            explicit RelationBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
+                OSMObjectBuilder<RelationBuilder, Relation>(buffer, parent) {
+            }
+
+            explicit RelationBuilder(Builder& parent) :
+                OSMObjectBuilder<RelationBuilder, Relation>(parent.buffer(), &parent) {
+            }
+
+        }; // class RelationBuilder
+
+        class AreaBuilder : public OSMObjectBuilder<AreaBuilder, Area> {
+
+            using type = AreaBuilder;
 
         public:
 
             explicit AreaBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
-                OSMObjectBuilder<osmium::Area>(buffer, parent) {
+                OSMObjectBuilder<AreaBuilder, Area>(buffer, parent) {
+            }
+
+            explicit AreaBuilder(Builder& parent) :
+                OSMObjectBuilder<AreaBuilder, Area>(parent.buffer(), &parent) {
             }
 
             /**
              * Initialize area attributes from the attributes of the given object.
              */
             void initialize_from_object(const osmium::OSMObject& source) {
-                osmium::Area& area = object();
-                area.set_id(osmium::object_id_to_area_id(source.id(), source.type()));
-                area.set_version(source.version());
-                area.set_changeset(source.changeset());
-                area.set_timestamp(source.timestamp());
-                area.set_visible(source.visible());
-                area.set_uid(source.uid());
-
-                add_user(source.user());
+                set_id(osmium::object_id_to_area_id(source.id(), source.type()));
+                set_version(source.version());
+                set_changeset(source.changeset());
+                set_timestamp(source.timestamp());
+                set_visible(source.visible());
+                set_uid(source.uid());
+                set_user(source.user());
             }
 
         }; // class AreaBuilder
 
-        using ChangesetBuilder = ObjectBuilder<osmium::Changeset>;
+        class ChangesetBuilder : public Builder {
+
+            using type = ChangesetBuilder;
+
+            constexpr static const size_t min_size_for_user = osmium::memory::padded_length(1);
+
+        public:
+
+            explicit ChangesetBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
+                Builder(buffer, parent, sizeof(Changeset) + min_size_for_user) {
+                new (&item()) Changeset();
+                add_size(min_size_for_user);
+                std::fill_n(object().data() + sizeof(Changeset), min_size_for_user, 0);
+                object().set_user_size(1);
+            }
+
+            /**
+             * Get a reference to the changeset buing built.
+             *
+             * Note that this reference will be invalidated by every action
+             * on the builder that might make the buffer grow. This includes
+             * calls to set_user() and any time a new sub-builder is created.
+             */
+            Changeset& object() noexcept {
+                return static_cast<Changeset&>(item());
+            }
+
+            OSMIUM_FORWARD(set_id)
+            OSMIUM_FORWARD(set_uid)
+            OSMIUM_FORWARD(set_uid_from_signed)
+            OSMIUM_FORWARD(set_created_at)
+            OSMIUM_FORWARD(set_closed_at)
+            OSMIUM_FORWARD(set_num_changes)
+            OSMIUM_FORWARD(set_num_comments)
+            OSMIUM_FORWARD(set_attribute)
+            OSMIUM_FORWARD(set_removed)
+
+            // @deprecated Use set_bounds() instead.
+            OSMIUM_DEPRECATED osmium::Box& bounds() noexcept {
+                return object().bounds();
+            }
+
+            ChangesetBuilder& set_bounds(const osmium::Box& box) noexcept {
+                object().bounds() = box;
+                return *this;
+            }
+
+            /**
+             * Set user name.
+             *
+             * @param user Pointer to user name.
+             * @param length Length of user name (without \0 termination).
+             */
+            ChangesetBuilder& set_user(const char* user, const string_size_type length) {
+                assert(object().user_size() == 1 && (size() <= sizeof(Changeset) + osmium::memory::padded_length(1))
+                       && "set_user() must be called at most once and before any sub-builders");
+                const auto available_space = min_size_for_user - 1;
+                if (length > available_space) {
+                    const auto space_needed = osmium::memory::padded_length(length - available_space);
+                    reserve_space(space_needed);
+                    add_size(static_cast<uint32_t>(space_needed));
+                }
+                std::copy_n(user, length, object().data() + sizeof(Changeset));
+                std::fill_n(object().data() + sizeof(Changeset) + length, osmium::memory::padded_length(length + 1) - length, 0);
+                object().set_user_size(length + 1);
+
+                return *this;
+            }
+
+            /**
+             * Set user name.
+             *
+             * @param user Pointer to \0-terminated user name.
+             */
+            ChangesetBuilder& set_user(const char* user) {
+                return set_user(user, static_cast_with_assert<string_size_type>(std::strlen(user)));
+            }
+
+            /**
+             * Set user name.
+             *
+             * @param user User name.
+             */
+            ChangesetBuilder& set_user(const std::string& user) {
+                return set_user(user.data(), static_cast_with_assert<string_size_type>(user.size()));
+            }
+
+            /// @deprecated Use set_user(...) instead.
+            template <typename... TArgs>
+            OSMIUM_DEPRECATED void add_user(TArgs&&... args) {
+                set_user(std::forward<TArgs>(args)...);
+            }
+
+        }; // class ChangesetBuilder
+
+#undef OSMIUM_FORWARD
 
     } // namespace builder
 
diff --git a/include/osmium/geom/factory.hpp b/include/osmium/geom/factory.hpp
index 14c51df..f2db402 100644
--- a/include/osmium/geom/factory.hpp
+++ b/include/osmium/geom/factory.hpp
@@ -95,7 +95,7 @@ namespace osmium {
             return m_message.c_str();
         }
 
-    }; // struct geometry_error
+    }; // class geometry_error
 
     /**
      * @brief Everything related to geometry handling.
diff --git a/include/osmium/geom/geos.hpp b/include/osmium/geom/geos.hpp
index f406076..d59e7ce 100644
--- a/include/osmium/geom/geos.hpp
+++ b/include/osmium/geom/geos.hpp
@@ -33,12 +33,22 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <geos/version.h>
+#if defined(GEOS_VERSION_MAJOR) && defined(GEOS_VERSION_MINOR) && (GEOS_VERSION_MAJOR < 3 || (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR <= 5))
+
+#define OSMIUM_WITH_GEOS
+
 /**
  * @file
  *
- * This file contains code for conversion of OSM geometries into GDAL
+ * This file contains code for conversion of OSM geometries into GEOS
  * geometries.
  *
+ * Note that everything in this file is deprecated and only works up to
+ * GEOS 3.5. It uses the GEOS C++ API which the GEOS project does not consider
+ * to be a stable, external API. We probably should have used the GEOS C API
+ * instead.
+ *
  * @attention If you include this file, you'll need to link with `libgeos`.
  */
 
@@ -88,6 +98,7 @@ namespace osmium {
 
         namespace detail {
 
+            /// @deprecated
             class GEOSFactoryImpl {
 
                 std::unique_ptr<const geos::geom::PrecisionModel> m_precision_model;
@@ -245,6 +256,7 @@ namespace osmium {
 
         } // namespace detail
 
+        /// @deprecated
         template <typename TProjection = IdentityProjection>
         using GEOSFactory = GeometryFactory<osmium::geom::detail::GEOSFactoryImpl, TProjection>;
 
@@ -254,4 +266,6 @@ namespace osmium {
 
 #undef THROW
 
+#endif
+
 #endif // OSMIUM_GEOM_GEOS_HPP
diff --git a/include/osmium/geom/relations.hpp b/include/osmium/geom/relations.hpp
index 76d452e..5e6e773 100644
--- a/include/osmium/geom/relations.hpp
+++ b/include/osmium/geom/relations.hpp
@@ -43,11 +43,11 @@ namespace osmium {
         /**
          * Check whether one geometry contains another.
          */
-        inline bool contains(const osmium::Box& a, const osmium::Box& b) {
-            return ((a.bottom_left().x() >= b.bottom_left().x()) &&
-                    (a.top_right().x()   <= b.top_right().x())   &&
-                    (a.bottom_left().y() >= b.bottom_left().y()) &&
-                    (a.top_right().y()   <= b.top_right().y()));
+        inline bool contains(const osmium::Box& lhs, const osmium::Box& rhs) {
+            return ((lhs.bottom_left().x() >= rhs.bottom_left().x()) &&
+                    (lhs.top_right().x()   <= rhs.top_right().x())   &&
+                    (lhs.bottom_left().y() >= rhs.bottom_left().y()) &&
+                    (lhs.top_right().y()   <= rhs.top_right().y()));
         }
 
     } // namespace geom
diff --git a/include/osmium/geom/tile.hpp b/include/osmium/geom/tile.hpp
index 672ae54..2c33017 100644
--- a/include/osmium/geom/tile.hpp
+++ b/include/osmium/geom/tile.hpp
@@ -118,23 +118,23 @@ namespace osmium {
         }; // struct Tile
 
         /// Tiles are equal if all their attributes are equal.
-        inline bool operator==(const Tile& a, const Tile& b) {
-            return a.z == b.z && a.x == b.x && a.y == b.y;
+        inline bool operator==(const Tile& lhs, const Tile& rhs) {
+            return lhs.z == rhs.z && lhs.x == rhs.x && lhs.y == rhs.y;
         }
 
-        inline bool operator!=(const Tile& a, const Tile& b) {
-            return ! (a == b);
+        inline bool operator!=(const Tile& lhs, const Tile& rhs) {
+            return ! (lhs == rhs);
         }
 
         /**
          * This defines an arbitrary order on tiles for use in std::map etc.
          */
-        inline bool operator<(const Tile& a, const Tile& b) {
-            if (a.z < b.z) return true;
-            if (a.z > b.z) return false;
-            if (a.x < b.x) return true;
-            if (a.x > b.x) return false;
-            return a.y < b.y;
+        inline bool operator<(const Tile& lhs, const Tile& rhs) {
+            if (lhs.z < rhs.z) return true;
+            if (lhs.z > rhs.z) return false;
+            if (lhs.x < rhs.x) return true;
+            if (lhs.x > rhs.x) return false;
+            return lhs.y < rhs.y;
         }
 
     } // namespace geom
diff --git a/include/osmium/handler/disk_store.hpp b/include/osmium/handler/disk_store.hpp
index 4e95573..d112ac0 100644
--- a/include/osmium/handler/disk_store.hpp
+++ b/include/osmium/handler/disk_store.hpp
@@ -51,6 +51,9 @@ namespace osmium {
     namespace handler {
 
         /**
+         * Writes OSM data in the Osmium-internal serialized format to disk
+         * keeping track of object offsets in the indexes given to the
+         * constructor.
          *
          * Note: This handler will only work if either all object IDs are
          *       positive or all object IDs are negative.
@@ -95,10 +98,8 @@ namespace osmium {
                 m_offset += relation.byte_size();
             }
 
-            // XXX
             void operator()(const osmium::memory::Buffer& buffer) {
                 osmium::io::detail::reliable_write(m_data_fd, buffer.data(), buffer.committed());
-
                 osmium::apply(buffer.begin(), buffer.end(), *this);
             }
 
diff --git a/include/osmium/handler/node_locations_for_ways.hpp b/include/osmium/handler/node_locations_for_ways.hpp
index a490f9e..61c6de2 100644
--- a/include/osmium/handler/node_locations_for_ways.hpp
+++ b/include/osmium/handler/node_locations_for_ways.hpp
@@ -165,7 +165,7 @@ namespace osmium {
                     }
                 }
                 if (error && !m_ignore_errors) {
-                    throw osmium::not_found("location for one or more nodes not found in node location index");
+                    throw osmium::not_found{"location for one or more nodes not found in node location index"};
                 }
             }
 
diff --git a/include/osmium/handler/object_relations.hpp b/include/osmium/handler/object_relations.hpp
index 279365d..5f1956d 100644
--- a/include/osmium/handler/object_relations.hpp
+++ b/include/osmium/handler/object_relations.hpp
@@ -46,6 +46,8 @@ namespace osmium {
     namespace handler {
 
         /**
+         * This handler updates the indexes given to the constructor with
+         * the relations between objects.
          *
          * Note: This handler will only work if either all object IDs are
          *       positive or all object IDs are negative.
diff --git a/include/osmium/index/bool_vector.hpp b/include/osmium/index/bool_vector.hpp
index e6e190e..2ef80f9 100644
--- a/include/osmium/index/bool_vector.hpp
+++ b/include/osmium/index/bool_vector.hpp
@@ -33,50 +33,15 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <type_traits>
-#include <vector>
+#include <osmium/index/id_set.hpp>
 
 namespace osmium {
 
     namespace index {
 
-        /**
-         * Index storing one bit for each Id. The index automatically scales
-         * with the Ids stored. Default value is 'false'. Storage uses
-         * std::vector<bool> and needs a minimum of memory if the Ids are
-         * dense.
-         */
+        /// @deprecated Use osmium::index::IdSet instead.
         template <typename T>
-        class BoolVector {
-
-            static_assert(std::is_unsigned<T>::value, "Needs unsigned type");
-
-            std::vector<bool> m_bits;
-
-        public:
-
-            BoolVector() = default;
-
-            BoolVector(const BoolVector&) = default;
-            BoolVector(BoolVector&&) = default;
-            BoolVector& operator=(const BoolVector&) = default;
-            BoolVector& operator=(BoolVector&&) = default;
-
-            ~BoolVector() noexcept = default;
-
-            void set(T id, bool value = true) {
-                if (m_bits.size() <= id) {
-                    m_bits.resize(id + 1024 * 1024);
-                }
-
-                m_bits[id] = value;
-            }
-
-            bool get(T id) const {
-                return id < m_bits.size() && m_bits[id];
-            }
-
-        }; // class BoolVector
+        using BoolVector = IdSet<T>;
 
     } // namespace index
 
diff --git a/include/osmium/index/detail/vector_map.hpp b/include/osmium/index/detail/vector_map.hpp
index ac87c2f..9f4af1f 100644
--- a/include/osmium/index/detail/vector_map.hpp
+++ b/include/osmium/index/detail/vector_map.hpp
@@ -85,11 +85,11 @@ namespace osmium {
                     try {
                         const TValue& value = m_vector.at(id);
                         if (value == osmium::index::empty_value<TValue>()) {
-                            not_found_error(id);
+                            throw osmium::not_found{id};
                         }
                         return value;
                     } catch (const std::out_of_range&) {
-                        not_found_error(id);
+                        throw osmium::not_found{id};
                     }
                 }
 
@@ -180,7 +180,7 @@ namespace osmium {
                         return a.first < b.first;
                     });
                     if (result == m_vector.end() || result->first != id) {
-                        not_found_error(id);
+                        throw osmium::not_found{id};
                     } else {
                         return result->second;
                     }
diff --git a/include/osmium/index/id_set.hpp b/include/osmium/index/id_set.hpp
new file mode 100644
index 0000000..4a894a0
--- /dev/null
+++ b/include/osmium/index/id_set.hpp
@@ -0,0 +1,431 @@
+#ifndef OSMIUM_INDEX_ID_SET_HPP
+#define OSMIUM_INDEX_ID_SET_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2016 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <type_traits>
+#include <unordered_set>
+#include <vector>
+
+#include <osmium/osm/item_type.hpp>
+#include <osmium/osm/types.hpp>
+
+namespace osmium {
+
+    namespace index {
+
+        /**
+         * Virtual parent class for IdSets. Use one of the implementations
+         * provided.
+         */
+        template <typename T>
+        class IdSet {
+
+        public:
+
+            virtual ~IdSet() {
+            }
+
+            /**
+             * Add the given Id to the set.
+             */
+            virtual void set(T id) = 0;
+
+            /**
+             * Is the Id in the set?
+             */
+            virtual bool get(T id) const noexcept = 0;
+
+            /**
+             * Is the set empty?
+             */
+            virtual bool empty() const = 0;
+
+            /**
+             * Clear the set.
+             */
+            virtual void clear() = 0;
+
+        }; // class IdSet
+
+        template <typename T>
+        class IdSetDense;
+
+        /**
+         * Const_iterator for iterating over a IdSetDense.
+         */
+        template <typename T>
+        class IdSetDenseIterator {
+
+            const IdSetDense<T>* m_set;
+            T m_value;
+            T m_last;
+
+            void next() noexcept {
+                while (m_value != m_last && !m_set->get(m_value)) {
+                    const auto cid = IdSetDense<T>::chunk_id(m_value);
+                    assert(cid < m_set->m_data.size());
+                    if (!m_set->m_data[cid]) {
+                        m_value = (cid + 1) << (IdSetDense<T>::chunk_bits + 3);
+                    } else {
+                        const auto slot = m_set->m_data[cid][IdSetDense<T>::offset(m_value)];
+                        if (slot == 0) {
+                            m_value += 8;
+                            m_value &= ~0x7;
+                        } else {
+                            ++m_value;
+                        }
+                    }
+                }
+            }
+
+        public:
+
+            using iterator_category = std::forward_iterator_tag;
+            using value_type        = T;
+            using pointer           = value_type*;
+            using reference         = value_type&;
+
+            IdSetDenseIterator(const IdSetDense<T>* set, T value, T last) noexcept :
+                m_set(set),
+                m_value(value),
+                m_last(last) {
+                next();
+            }
+
+            IdSetDenseIterator<T>& operator++() noexcept {
+                if (m_value != m_last) {
+                    ++m_value;
+                    next();
+                }
+                return *this;
+            }
+
+            IdSetDenseIterator<T> operator++(int) noexcept {
+                IdSetDenseIterator<T> tmp(*this);
+                operator++();
+                return tmp;
+            }
+
+            bool operator==(const IdSetDenseIterator<T>& rhs) const noexcept {
+                return m_set == rhs.m_set && m_value == rhs.m_value;
+            }
+
+            bool operator!=(const IdSetDenseIterator<T>& rhs) const noexcept {
+                return ! (*this == rhs);
+            }
+
+            T operator*() const noexcept {
+                assert(m_value < m_last);
+                return m_value;
+            }
+
+        }; // class IdSetDenseIterator
+
+        /**
+         * A set of Ids of the given type. Internal storage is in chunks of
+         * arrays used as bit fields. Internally those chunks will be allocated
+         * as needed, so it works relatively efficiently with both smaller
+         * and larger Id sets. If it is not used, no memory is allocated at
+         * all.
+         */
+        template <typename T>
+        class IdSetDense : public IdSet<T> {
+
+            static_assert(std::is_unsigned<T>::value, "Needs unsigned type");
+            static_assert(sizeof(T) >= 4, "Needs at least 32bit type");
+
+            friend class IdSetDenseIterator<T>;
+
+            // This value is a compromise. For node Ids it could be bigger
+            // which would mean less (but larger) memory allocations. For
+            // relations Ids it could be smaller, because they would all fit
+            // into a smaller allocation.
+            constexpr static const size_t chunk_bits = 22;
+            constexpr static const size_t chunk_size = 1 << chunk_bits;
+
+            std::vector<std::unique_ptr<unsigned char[]>> m_data;
+            size_t m_size = 0;
+
+            static size_t chunk_id(T id) noexcept {
+                return id >> (chunk_bits + 3);
+            }
+
+            static size_t offset(T id) noexcept {
+                return (id >> 3) & ((1 << chunk_bits) - 1);
+            }
+
+            static unsigned char bitmask(T id) noexcept {
+                return 1 << (id & 0x7);
+            }
+
+            T last() const noexcept {
+                return m_data.size() * chunk_size * 8;
+            }
+
+            unsigned char& get_element(T id) {
+                const auto cid = chunk_id(id);
+                if (cid >= m_data.size()) {
+                    m_data.resize(cid + 1);
+                }
+
+                auto& chunk = m_data[cid];
+                if (!chunk) {
+                    chunk.reset(new unsigned char[chunk_size]);
+                    ::memset(chunk.get(), 0, chunk_size);
+                }
+
+                return chunk[offset(id)];
+            }
+
+        public:
+
+            using const_iterator = IdSetDenseIterator<T>;
+
+            IdSetDense() = default;
+
+            /**
+             * Add the Id to the set if it is not already in there.
+             *
+             * @param id The Id to set.
+             * @returns true if the Id was added, false if it was already set.
+             */
+            bool check_and_set(T id) {
+                auto& element = get_element(id);
+
+                if ((element & bitmask(id)) == 0) {
+                    element |= bitmask(id);
+                    ++m_size;
+                    return true;
+                }
+
+                return false;
+            }
+
+            /**
+             * Add the given Id to the set.
+             *
+             * @param id The Id to set.
+             */
+            void set(T id) override final {
+                (void)check_and_set(id);
+            }
+
+            /**
+             * Remove the given Id from the set.
+             *
+             * @param id The Id to set.
+             */
+            void unset(T id) {
+                auto& element = get_element(id);
+
+                if ((element & bitmask(id)) != 0) {
+                    element &= ~bitmask(id);
+                    --m_size;
+                }
+            }
+
+            /**
+             * Is the Id in the set?
+             *
+             * @param id The Id to check.
+             */
+            bool get(T id) const noexcept override final {
+                if (chunk_id(id) >= m_data.size()) {
+                    return false;
+                }
+                auto* r = m_data[chunk_id(id)].get();
+                if (!r) {
+                    return false;
+                }
+                return (r[offset(id)] & bitmask(id)) != 0;
+            }
+
+            /**
+             * Is the set empty?
+             */
+            bool empty() const noexcept override final {
+                return m_size == 0;
+            }
+
+            /**
+             * The number of Ids stored in the set.
+             */
+            size_t size() const noexcept {
+                return m_size;
+            }
+
+            /**
+             * Clear the set.
+             */
+            void clear() override final {
+                m_data.clear();
+                m_size = 0;
+            }
+
+            IdSetDenseIterator<T> begin() const {
+                return IdSetDenseIterator<T>{this, 0, last()};
+            }
+
+            IdSetDenseIterator<T> end() const {
+                return IdSetDenseIterator<T>{this, last(), last()};
+            }
+
+        }; // class IdSetDense
+
+        /**
+         * IdSet implementation for small Id sets. It writes the Ids
+         * into a vector and uses linear search.
+         */
+        template <typename T>
+        class IdSetSmall : public IdSet<T> {
+
+            std::vector<T> m_data;
+
+        public:
+
+            /**
+             * Add the given Id to the set.
+             */
+            void set(T id) override final {
+                m_data.push_back(id);
+            }
+
+            /**
+             * Is the Id in the set? Uses linear search.
+             *
+             * @param id The Id to check.
+             */
+            bool get(T id) const noexcept override final {
+                const auto it = std::find(m_data.cbegin(), m_data.cend(), id);
+                return it != m_data.cend();
+            }
+
+            /**
+             * Is the Id in the set? Uses a binary search. For larger sets
+             * this might be more efficient than calling get(), the set
+             * must be sorted.
+             *
+             * @param id The Id to check.
+             * @pre You must have called sort_unique() before calling this
+             *      or be sure there are no duplicates and the Ids have been
+             *      set in order.
+             */
+            bool get_binary_search(T id) const noexcept {
+                return std::binary_search(m_data.cbegin(), m_data.cend(), id);
+            }
+
+            /**
+             * Is the set empty?
+             */
+            bool empty() const noexcept override final {
+                return m_data.empty();
+            }
+
+            /**
+             * Clear the set.
+             */
+            void clear() override final {
+                m_data.clear();
+            }
+
+            /**
+             * Sort the internal vector and remove any duplicates. Call this
+             * before using size(), get_binary_search() or using an iterator.
+             */
+            void sort_unique() {
+                std::sort(m_data.begin(), m_data.end());
+                const auto last = std::unique(m_data.begin(), m_data.end());
+                m_data.erase(last, m_data.end());
+
+            }
+
+            /**
+             * The number of Ids stored in the set.
+             *
+             * @pre You must have called sort_unique() before calling this
+             *      or be sure there are no duplicates.
+             */
+            size_t size() const noexcept {
+                return m_data.size();
+            }
+
+            /// Iterator type. There is no non-const iterator.
+            using const_iterator = typename std::vector<T>::const_iterator;
+
+            const_iterator begin() const noexcept {
+                return m_data.cbegin();
+            }
+
+            const_iterator end() const noexcept {
+                return m_data.cend();
+            }
+
+            const_iterator cbegin() const noexcept {
+                return m_data.cbegin();
+            }
+
+            const_iterator cend() const noexcept {
+                return m_data.cend();
+            }
+
+        }; // class IdSetSmall
+
+        template <template<typename> class IdSetType>
+        class NWRIdSet {
+
+            using id_set_type = IdSetType<osmium::unsigned_object_id_type>;
+
+            id_set_type m_sets[3];
+
+        public:
+
+            id_set_type& operator()(osmium::item_type type) noexcept {
+                return m_sets[osmium::item_type_to_nwr_index(type)];
+            }
+
+            const id_set_type& operator()(osmium::item_type type) const noexcept {
+                return m_sets[osmium::item_type_to_nwr_index(type)];
+            }
+
+        }; // class NWRIdSet
+
+    } // namespace index
+
+} // namespace osmium
+
+#endif // OSMIUM_INDEX_ID_SET_HPP
diff --git a/include/osmium/index/index.hpp b/include/osmium/index/index.hpp
index c3deead..65871f2 100644
--- a/include/osmium/index/index.hpp
+++ b/include/osmium/index/index.hpp
@@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cstddef>
 #include <limits>
-#include <sstream>
 #include <stdexcept>
 #include <string>
 
@@ -57,6 +56,10 @@ namespace osmium {
             std::runtime_error(what) {
         }
 
+        explicit not_found(uint64_t id) :
+            std::runtime_error(std::string{"id "} + std::to_string(id) + " not found") {
+        }
+
     }; // struct not_found
 
     /**
@@ -64,13 +67,6 @@ namespace osmium {
      */
     namespace index {
 
-        template <typename TKey>
-        OSMIUM_NORETURN void not_found_error(TKey key) {
-            std::stringstream s;
-            s << "id " << key << " not found";
-            throw not_found(s.str());
-        }
-
         /**
          * Some of the index classes need an "empty" value that can
          * never appear in real data. This function must return this
diff --git a/include/osmium/index/map.hpp b/include/osmium/index/map.hpp
index 1d2d5aa..d26a4aa 100644
--- a/include/osmium/index/map.hpp
+++ b/include/osmium/index/map.hpp
@@ -48,6 +48,18 @@ DEALINGS IN THE SOFTWARE.
 
 namespace osmium {
 
+    struct map_factory_error : public std::runtime_error {
+
+        explicit map_factory_error(const char* message) :
+            std::runtime_error(message) {
+        }
+
+        explicit map_factory_error(const std::string& message) :
+            std::runtime_error(message) {
+        }
+
+    }; // struct map_factory_error
+
     namespace index {
 
         /**
@@ -148,14 +160,14 @@ namespace osmium {
                     // default implementation is empty
                 }
 
-                // This function could usually be const in derived classes,
+                // This function can usually be const in derived classes,
                 // but not always. It could, for instance, sort internal data.
                 // This is why it is not declared const here.
                 virtual void dump_as_list(const int /*fd*/) {
                     throw std::runtime_error("can't dump as list");
                 }
 
-                // This function could usually be const in derived classes,
+                // This function can usually be const in derived classes,
                 // but not always. It could, for instance, sort internal data.
                 // This is why it is not declared const here.
                 virtual void dump_as_array(const int /*fd*/) {
@@ -188,13 +200,6 @@ namespace osmium {
             MapFactory(MapFactory&&) = delete;
             MapFactory& operator=(MapFactory&&) = delete;
 
-            OSMIUM_NORETURN static void error(const std::string& map_type_name) {
-                std::string error_message {"Support for map type '"};
-                error_message += map_type_name;
-                error_message += "' not compiled into this binary.";
-                throw std::runtime_error(error_message);
-            }
-
         public:
 
             static MapFactory<id_type, value_type>& instance() {
@@ -226,7 +231,7 @@ namespace osmium {
                 std::vector<std::string> config = osmium::split_string(config_string, ',');
 
                 if (config.empty()) {
-                    throw std::runtime_error("Need non-empty map type name.");
+                    throw map_factory_error{"Need non-empty map type name"};
                 }
 
                 auto it = m_callbacks.find(config[0]);
@@ -234,7 +239,7 @@ namespace osmium {
                     return std::unique_ptr<map_type>((it->second)(config));
                 }
 
-                error(config[0]);
+                throw map_factory_error{std::string{"Support for map type '"} + config[0] + "' not compiled into this binary"};
             }
 
         }; // class MapFactory
diff --git a/include/osmium/index/map/dummy.hpp b/include/osmium/index/map/dummy.hpp
index 98d082b..529ad25 100644
--- a/include/osmium/index/map/dummy.hpp
+++ b/include/osmium/index/map/dummy.hpp
@@ -63,7 +63,7 @@ namespace osmium {
                 }
 
                 const TValue get(const TId id) const final {
-                    not_found_error(id);
+                    throw osmium::not_found{id};
                 }
 
                 size_t size() const final {
diff --git a/include/osmium/index/map/sparse_mem_map.hpp b/include/osmium/index/map/sparse_mem_map.hpp
index 1e3c58c..43fea36 100644
--- a/include/osmium/index/map/sparse_mem_map.hpp
+++ b/include/osmium/index/map/sparse_mem_map.hpp
@@ -79,7 +79,7 @@ namespace osmium {
                 const TValue get(const TId id) const final {
                     auto it = m_elements.find(id);
                     if (it == m_elements.end()) {
-                        not_found_error(id);
+                        throw osmium::not_found{id};
                     }
                     return it->second;
                 }
diff --git a/include/osmium/index/map/sparse_mem_table.hpp b/include/osmium/index/map/sparse_mem_table.hpp
index 241a64f..68f5797 100644
--- a/include/osmium/index/map/sparse_mem_table.hpp
+++ b/include/osmium/index/map/sparse_mem_table.hpp
@@ -99,10 +99,10 @@ namespace osmium {
 
                 const TValue get(const TId id) const final {
                     if (id >= m_elements.size()) {
-                        not_found_error(id);
+                        throw osmium::not_found{id};
                     }
                     if (m_elements[id] == osmium::index::empty_value<TValue>()) {
-                        not_found_error(id);
+                        throw osmium::not_found{id};
                     }
                     return m_elements[id];
                 }
diff --git a/include/osmium/io/compression.hpp b/include/osmium/io/compression.hpp
index e7f93bd..a38bba6 100644
--- a/include/osmium/io/compression.hpp
+++ b/include/osmium/io/compression.hpp
@@ -145,10 +145,11 @@ namespace osmium {
 
         private:
 
-            using compression_map_type = std::map<const osmium::io::file_compression,
-                                                  std::tuple<create_compressor_type,
-                                                             create_decompressor_type_fd,
-                                                             create_decompressor_type_buffer>>;
+            using callbacks_type = std::tuple<create_compressor_type,
+                                              create_decompressor_type_fd,
+                                              create_decompressor_type_buffer>;
+
+            using compression_map_type = std::map<const osmium::io::file_compression, callbacks_type>;
 
             compression_map_type m_callbacks;
 
@@ -160,11 +161,17 @@ namespace osmium {
             CompressionFactory(CompressionFactory&&) = delete;
             CompressionFactory& operator=(CompressionFactory&&) = delete;
 
-            OSMIUM_NORETURN void error(osmium::io::file_compression compression) {
-                std::string error_message {"Support for compression '"};
+            const callbacks_type& find_callbacks(osmium::io::file_compression compression) const {
+                const auto it = m_callbacks.find(compression);
+
+                if (it != m_callbacks.end()) {
+                    return it->second;
+                }
+
+                std::string error_message{"Support for compression '"};
                 error_message += as_string(compression);
-                error_message += "' not compiled into this binary.";
-                throw unsupported_file_format_error(error_message);
+                error_message += "' not compiled into this binary";
+                throw unsupported_file_format_error{error_message};
             }
 
         public:
@@ -189,36 +196,21 @@ namespace osmium {
             }
 
             template <typename... TArgs>
-            std::unique_ptr<osmium::io::Compressor> create_compressor(osmium::io::file_compression compression, TArgs&&... args) {
-                auto it = m_callbacks.find(compression);
-
-                if (it != m_callbacks.end()) {
-                    return std::unique_ptr<osmium::io::Compressor>(std::get<0>(it->second)(std::forward<TArgs>(args)...));
-                }
-
-                error(compression);
+            std::unique_ptr<osmium::io::Compressor> create_compressor(osmium::io::file_compression compression, TArgs&&... args) const {
+                const auto callbacks = find_callbacks(compression);
+                return std::unique_ptr<osmium::io::Compressor>(std::get<0>(callbacks)(std::forward<TArgs>(args)...));
             }
 
-            std::unique_ptr<osmium::io::Decompressor> create_decompressor(osmium::io::file_compression compression, int fd) {
-                auto it = m_callbacks.find(compression);
-
-                if (it != m_callbacks.end()) {
-                    auto p = std::unique_ptr<osmium::io::Decompressor>(std::get<1>(it->second)(fd));
-                    p->set_file_size(osmium::util::file_size(fd));
-                    return p;
-                }
-
-                error(compression);
+            std::unique_ptr<osmium::io::Decompressor> create_decompressor(osmium::io::file_compression compression, int fd) const {
+                const auto callbacks = find_callbacks(compression);
+                auto p = std::unique_ptr<osmium::io::Decompressor>(std::get<1>(callbacks)(fd));
+                p->set_file_size(osmium::util::file_size(fd));
+                return p;
             }
 
-            std::unique_ptr<osmium::io::Decompressor> create_decompressor(osmium::io::file_compression compression, const char* buffer, size_t size) {
-                auto it = m_callbacks.find(compression);
-
-                if (it != m_callbacks.end()) {
-                    return std::unique_ptr<osmium::io::Decompressor>(std::get<2>(it->second)(buffer, size));
-                }
-
-                error(compression);
+            std::unique_ptr<osmium::io::Decompressor> create_decompressor(osmium::io::file_compression compression, const char* buffer, size_t size) const {
+                const auto callbacks = find_callbacks(compression);
+                return std::unique_ptr<osmium::io::Decompressor>(std::get<2>(callbacks)(buffer, size));
             }
 
         }; // class CompressionFactory
diff --git a/include/osmium/io/detail/input_format.hpp b/include/osmium/io/detail/input_format.hpp
index 98081e3..67b05a8 100644
--- a/include/osmium/io/detail/input_format.hpp
+++ b/include/osmium/io/detail/input_format.hpp
@@ -55,12 +55,17 @@ namespace osmium {
 
         namespace detail {
 
+            struct reader_options {
+                osmium::osm_entity_bits::type read_which_entities = osm_entity_bits::all;
+                osmium::io::read_meta read_metadata = read_meta::yes;
+            };
+
             class Parser {
 
                 future_buffer_queue_type& m_output_queue;
                 std::promise<osmium::io::Header>& m_header_promise;
                 queue_wrapper<std::string> m_input_queue;
-                osmium::osm_entity_bits::type m_read_types;
+                reader_options m_options;
                 bool m_header_is_done;
 
             protected:
@@ -73,11 +78,15 @@ namespace osmium {
                     return m_input_queue.has_reached_end_of_data();
                 }
 
-                osmium::osm_entity_bits::type read_types() const {
-                    return m_read_types;
+                osmium::osm_entity_bits::type read_types() const noexcept {
+                    return m_options.read_which_entities;
+                }
+
+                osmium::io::read_meta read_metadata() const noexcept {
+                    return m_options.read_metadata;
                 }
 
-                bool header_is_done() const {
+                bool header_is_done() const noexcept {
                     return m_header_is_done;
                 }
 
@@ -111,11 +120,11 @@ namespace osmium {
                 Parser(future_string_queue_type& input_queue,
                        future_buffer_queue_type& output_queue,
                        std::promise<osmium::io::Header>& header_promise,
-                       osmium::osm_entity_bits::type read_types) :
+                       osmium::io::detail::reader_options options) :
                     m_output_queue(output_queue),
                     m_header_promise(header_promise),
                     m_input_queue(input_queue),
-                    m_read_types(read_types),
+                    m_options(options),
                     m_header_is_done(false) {
                 }
 
@@ -157,7 +166,7 @@ namespace osmium {
                 using create_parser_type = std::function<std::unique_ptr<Parser>(future_string_queue_type&,
                                                                                  future_buffer_queue_type&,
                                                                                  std::promise<osmium::io::Header>& header_promise,
-                                                                                 osmium::osm_entity_bits::type read_which_entities)>;
+                                                                                 osmium::io::detail::reader_options options)>;
 
             private:
 
diff --git a/include/osmium/io/detail/o5m_input_format.hpp b/include/osmium/io/detail/o5m_input_format.hpp
index d642879..de4595e 100644
--- a/include/osmium/io/detail/o5m_input_format.hpp
+++ b/include/osmium/io/detail/o5m_input_format.hpp
@@ -80,7 +80,7 @@ namespace osmium {
     struct o5m_error : public io_error {
 
         explicit o5m_error(const char* what) :
-            io_error(std::string("o5m format error: ") + what) {
+            io_error(std::string{"o5m format error: "} + what) {
         }
 
     }; // struct o5m_error
@@ -135,7 +135,7 @@ namespace osmium {
 
                 const char* get(uint64_t index) const {
                     if (m_table.empty() || index == 0 || index > number_of_entries) {
-                        throw o5m_error("reference to non-existing string in table");
+                        throw o5m_error{"reference to non-existing string in table"};
                     }
                     auto entry = (current_entry + number_of_entries - index) % number_of_entries;
                     return &m_table[entry * entry_size];
@@ -191,7 +191,7 @@ namespace osmium {
                     static const unsigned char header_magic[] = { 0xff, 0xe0, 0x04, 'o', '5' };
 
                     if (std::strncmp(reinterpret_cast<const char*>(header_magic), m_data, sizeof(header_magic))) {
-                        throw o5m_error("wrong header magic");
+                        throw o5m_error{"wrong header magic"};
                     }
 
                     m_data += sizeof(header_magic);
@@ -203,7 +203,7 @@ namespace osmium {
                     } else if (*m_data == 'c') {  // o5c change file
                         m_header.set_has_multiple_object_versions(true);
                     } else {
-                        throw o5m_error("wrong header magic");
+                        throw o5m_error{"wrong header magic"};
                     }
 
                     m_data++;
@@ -211,7 +211,7 @@ namespace osmium {
 
                 void check_file_format_version() {
                     if (*m_data != '2') {
-                        throw o5m_error("wrong header magic");
+                        throw o5m_error{"wrong header magic"};
                     }
 
                     m_data++;
@@ -219,7 +219,7 @@ namespace osmium {
 
                 void decode_header() {
                     if (! ensure_bytes_available(7)) { // overall length of header
-                        throw o5m_error("file too short (incomplete header info)");
+                        throw o5m_error{"file too short (incomplete header info)"};
                     }
 
                     check_header_magic();
@@ -260,7 +260,7 @@ namespace osmium {
                     if (**dataptr == 0x00) { // get inline string
                         (*dataptr)++;
                         if (*dataptr == end) {
-                            throw o5m_error("string format error");
+                            throw o5m_error{"string format error"};
                         }
                         return *dataptr;
                     } else { // get from reference table
@@ -277,7 +277,7 @@ namespace osmium {
                     auto uid = protozero::decode_varint(&data, end);
 
                     if (data == end) {
-                        throw o5m_error("missing user name");
+                        throw o5m_error{"missing user name"};
                     }
 
                     const char* user = ++data;
@@ -290,7 +290,7 @@ namespace osmium {
 
                     while (*data++) {
                         if (data == end) {
-                            throw o5m_error("no null byte in user name");
+                            throw o5m_error{"no null byte in user name"};
                         }
                     }
 
@@ -302,24 +302,24 @@ namespace osmium {
                     return std::make_pair(static_cast_with_assert<osmium::user_id_type>(uid), user);
                 }
 
-                void decode_tags(osmium::builder::Builder* builder, const char** dataptr, const char* const end) {
-                    osmium::builder::TagListBuilder tl_builder(m_buffer, builder);
+                void decode_tags(osmium::builder::Builder& parent, const char** dataptr, const char* const end) {
+                    osmium::builder::TagListBuilder builder{parent};
 
-                    while(*dataptr != end) {
+                    while (*dataptr != end) {
                         bool update_pointer = (**dataptr == 0x00);
                         const char* data = decode_string(dataptr, end);
                         const char* start = data;
 
                         while (*data++) {
                             if (data == end) {
-                                throw o5m_error("no null byte in tag key");
+                                throw o5m_error{"no null byte in tag key"};
                             }
                         }
 
                         const char* value = data;
                         while (*data++) {
                             if (data == end) {
-                                throw o5m_error("no null byte in tag value");
+                                throw o5m_error{"no null byte in tag value"};
                             }
                         }
 
@@ -328,7 +328,7 @@ namespace osmium {
                             *dataptr = data;
                         }
 
-                        tl_builder.add_tag(start, value);
+                        builder.add_tag(start, value);
                     }
                 }
 
@@ -357,50 +357,46 @@ namespace osmium {
                 }
 
                 void decode_node(const char* data, const char* const end) {
-                    osmium::builder::NodeBuilder builder(m_buffer);
-                    osmium::Node& node = builder.object();
+                    osmium::builder::NodeBuilder builder{m_buffer};
 
-                    node.set_id(m_delta_id.update(zvarint(&data, end)));
+                    builder.set_id(m_delta_id.update(zvarint(&data, end)));
 
-                    builder.add_user(decode_info(node, &data, end));
+                    builder.set_user(decode_info(builder.object(), &data, end));
 
                     if (data == end) {
                         // no location, object is deleted
-                        builder.object().set_visible(false);
-                        builder.object().set_location(osmium::Location{});
+                        builder.set_visible(false);
+                        builder.set_location(osmium::Location{});
                     } else {
                         auto lon = m_delta_lon.update(zvarint(&data, end));
                         auto lat = m_delta_lat.update(zvarint(&data, end));
-                        builder.object().set_location(osmium::Location{lon, lat});
+                        builder.set_location(osmium::Location{lon, lat});
 
                         if (data != end) {
-                            decode_tags(&builder, &data, end);
+                            decode_tags(builder, &data, end);
                         }
                     }
-
-                    m_buffer.commit();
                 }
 
                 void decode_way(const char* data, const char* const end) {
-                    osmium::builder::WayBuilder builder(m_buffer);
-                    osmium::Way& way = builder.object();
+                    osmium::builder::WayBuilder builder{m_buffer};
 
-                    way.set_id(m_delta_id.update(zvarint(&data, end)));
+                    builder.set_id(m_delta_id.update(zvarint(&data, end)));
 
-                    builder.add_user(decode_info(way, &data, end));
+                    builder.set_user(decode_info(builder.object(), &data, end));
 
                     if (data == end) {
                         // no reference section, object is deleted
-                        builder.object().set_visible(false);
+                        builder.set_visible(false);
                     } else {
                         auto reference_section_length = protozero::decode_varint(&data, end);
                         if (reference_section_length > 0) {
                             const char* const end_refs = data + reference_section_length;
                             if (end_refs > end) {
-                                throw o5m_error("way nodes ref section too long");
+                                throw o5m_error{"way nodes ref section too long"};
                             }
 
-                            osmium::builder::WayNodeListBuilder wn_builder(m_buffer, &builder);
+                            osmium::builder::WayNodeListBuilder wn_builder{builder};
 
                             while (data < end_refs) {
                                 wn_builder.add_node_ref(m_delta_way_node_id.update(zvarint(&data, end)));
@@ -408,16 +404,14 @@ namespace osmium {
                         }
 
                         if (data != end) {
-                            decode_tags(&builder, &data, end);
+                            decode_tags(builder, &data, end);
                         }
                     }
-
-                    m_buffer.commit();
                 }
 
                 osmium::item_type decode_member_type(char c) {
                     if (c < '0' || c > '2') {
-                        throw o5m_error("unknown member type");
+                        throw o5m_error{"unknown member type"};
                     }
                     return osmium::nwr_index_to_item_type(c - '0');
                 }
@@ -429,13 +423,13 @@ namespace osmium {
 
                     auto member_type = decode_member_type(*data++);
                     if (data == end) {
-                        throw o5m_error("missing role");
+                        throw o5m_error{"missing role"};
                     }
                     const char* role = data;
 
                     while (*data++) {
                         if (data == end) {
-                            throw o5m_error("no null byte in role");
+                            throw o5m_error{"no null byte in role"};
                         }
                     }
 
@@ -448,30 +442,29 @@ namespace osmium {
                 }
 
                 void decode_relation(const char* data, const char* const end) {
-                    osmium::builder::RelationBuilder builder(m_buffer);
-                    osmium::Relation& relation = builder.object();
+                    osmium::builder::RelationBuilder builder{m_buffer};
 
-                    relation.set_id(m_delta_id.update(zvarint(&data, end)));
+                    builder.set_id(m_delta_id.update(zvarint(&data, end)));
 
-                    builder.add_user(decode_info(relation, &data, end));
+                    builder.set_user(decode_info(builder.object(), &data, end));
 
                     if (data == end) {
                         // no reference section, object is deleted
-                        builder.object().set_visible(false);
+                        builder.set_visible(false);
                     } else {
                         auto reference_section_length = protozero::decode_varint(&data, end);
                         if (reference_section_length > 0) {
                             const char* const end_refs = data + reference_section_length;
                             if (end_refs > end) {
-                                throw o5m_error("relation format error");
+                                throw o5m_error{"relation format error"};
                             }
 
-                            osmium::builder::RelationMemberListBuilder rml_builder(m_buffer, &builder);
+                            osmium::builder::RelationMemberListBuilder rml_builder{builder};
 
                             while (data < end_refs) {
                                 auto delta_id = zvarint(&data, end);
                                 if (data == end) {
-                                    throw o5m_error("relation member format error");
+                                    throw o5m_error{"relation member format error"};
                                 }
                                 auto type_role = decode_role(&data, end);
                                 auto i = osmium::item_type_to_nwr_index(type_role.first);
@@ -481,11 +474,9 @@ namespace osmium {
                         }
 
                         if (data != end) {
-                            decode_tags(&builder, &data, end);
+                            decode_tags(builder, &data, end);
                         }
                     }
-
-                    m_buffer.commit();
                 }
 
                 void decode_bbox(const char* data, const char* const end) {
@@ -537,11 +528,11 @@ namespace osmium {
                             try {
                                 length = protozero::decode_varint(&m_data, m_end);
                             } catch (const protozero::end_of_buffer_exception&) {
-                                throw o5m_error("premature end of file");
+                                throw o5m_error{"premature end of file"};
                             }
 
                             if (! ensure_bytes_available(length)) {
-                                throw o5m_error("premature end of file");
+                                throw o5m_error{"premature end of file"};
                             }
 
                             switch (ds_type) {
@@ -549,18 +540,21 @@ namespace osmium {
                                     mark_header_as_done();
                                     if (read_types() & osmium::osm_entity_bits::node) {
                                         decode_node(m_data, m_data + length);
+                                        m_buffer.commit();
                                     }
                                     break;
                                 case dataset_type::way:
                                     mark_header_as_done();
                                     if (read_types() & osmium::osm_entity_bits::way) {
                                         decode_way(m_data, m_data + length);
+                                        m_buffer.commit();
                                     }
                                     break;
                                 case dataset_type::relation:
                                     mark_header_as_done();
                                     if (read_types() & osmium::osm_entity_bits::relation) {
                                         decode_relation(m_data, m_data + length);
+                                        m_buffer.commit();
                                     }
                                     break;
                                 case dataset_type::bounding_box:
@@ -598,8 +592,8 @@ namespace osmium {
                 O5mParser(future_string_queue_type& input_queue,
                           future_buffer_queue_type& output_queue,
                           std::promise<osmium::io::Header>& header_promise,
-                          osmium::osm_entity_bits::type read_types) :
-                    Parser(input_queue, output_queue, header_promise, read_types),
+                          osmium::io::detail::reader_options options) :
+                    Parser(input_queue, output_queue, header_promise, options),
                     m_header(),
                     m_buffer(buffer_size),
                     m_input(),
@@ -625,8 +619,8 @@ namespace osmium {
                 [](future_string_queue_type& input_queue,
                     future_buffer_queue_type& output_queue,
                     std::promise<osmium::io::Header>& header_promise,
-                    osmium::osm_entity_bits::type read_which_entities) {
-                    return std::unique_ptr<Parser>(new O5mParser(input_queue, output_queue, header_promise, read_which_entities));
+                    osmium::io::detail::reader_options options) {
+                    return std::unique_ptr<Parser>(new O5mParser(input_queue, output_queue, header_promise, options));
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/opl_input_format.hpp b/include/osmium/io/detail/opl_input_format.hpp
index 15a31f3..1bd8b07 100644
--- a/include/osmium/io/detail/opl_input_format.hpp
+++ b/include/osmium/io/detail/opl_input_format.hpp
@@ -82,8 +82,8 @@ namespace osmium {
                 OPLParser(future_string_queue_type& input_queue,
                           future_buffer_queue_type& output_queue,
                           std::promise<osmium::io::Header>& header_promise,
-                          osmium::osm_entity_bits::type read_types) :
-                    Parser(input_queue, output_queue, header_promise, read_types) {
+                          osmium::io::detail::reader_options options) :
+                    Parser(input_queue, output_queue, header_promise, options) {
                     set_header_value(osmium::io::Header{});
                 }
 
@@ -137,8 +137,8 @@ namespace osmium {
                 [](future_string_queue_type& input_queue,
                     future_buffer_queue_type& output_queue,
                     std::promise<osmium::io::Header>& header_promise,
-                    osmium::osm_entity_bits::type read_which_entities) {
-                    return std::unique_ptr<Parser>(new OPLParser(input_queue, output_queue, header_promise, read_which_entities));
+                    osmium::io::detail::reader_options options) {
+                    return std::unique_ptr<Parser>(new OPLParser(input_queue, output_queue, header_promise, options));
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/opl_parser_functions.hpp b/include/osmium/io/detail/opl_parser_functions.hpp
index 97c5927..ee35b36 100644
--- a/include/osmium/io/detail/opl_parser_functions.hpp
+++ b/include/osmium/io/detail/opl_parser_functions.hpp
@@ -365,9 +365,8 @@ namespace osmium {
 
             inline void opl_parse_node(const char** data, osmium::memory::Buffer& buffer) {
                 osmium::builder::NodeBuilder builder{buffer};
-                osmium::Node& node = builder.object();
 
-                node.set_id(opl_parse_id(data));
+                builder.set_id(opl_parse_id(data));
 
                 const char* tags_begin = nullptr;
 
@@ -382,19 +381,19 @@ namespace osmium {
                     ++(*data);
                     switch (c) {
                         case 'v':
-                            node.set_version(opl_parse_version(data));
+                            builder.set_version(opl_parse_version(data));
                             break;
                         case 'd':
-                            node.set_visible(opl_parse_visible(data));
+                            builder.set_visible(opl_parse_visible(data));
                             break;
                         case 'c':
-                            node.set_changeset(opl_parse_changeset_id(data));
+                            builder.set_changeset(opl_parse_changeset_id(data));
                             break;
                         case 't':
-                            node.set_timestamp(opl_parse_timestamp(data));
+                            builder.set_timestamp(opl_parse_timestamp(data));
                             break;
                         case 'i':
-                            node.set_uid(opl_parse_uid(data));
+                            builder.set_uid(opl_parse_uid(data));
                             break;
                         case 'u':
                             opl_parse_string(data, user);
@@ -422,23 +421,20 @@ namespace osmium {
                 }
 
                 if (location.valid()) {
-                    node.set_location(location);
+                    builder.set_location(location);
                 }
 
-                builder.add_user(user);
+                builder.set_user(user);
 
                 if (tags_begin) {
                     opl_parse_tags(tags_begin, buffer, &builder);
                 }
-
-                buffer.commit();
             }
 
             inline void opl_parse_way(const char** data, osmium::memory::Buffer& buffer) {
                 osmium::builder::WayBuilder builder{buffer};
-                osmium::Way& way = builder.object();
 
-                way.set_id(opl_parse_id(data));
+                builder.set_id(opl_parse_id(data));
 
                 const char* tags_begin = nullptr;
 
@@ -455,19 +451,19 @@ namespace osmium {
                     ++(*data);
                     switch (c) {
                         case 'v':
-                            way.set_version(opl_parse_version(data));
+                            builder.set_version(opl_parse_version(data));
                             break;
                         case 'd':
-                            way.set_visible(opl_parse_visible(data));
+                            builder.set_visible(opl_parse_visible(data));
                             break;
                         case 'c':
-                            way.set_changeset(opl_parse_changeset_id(data));
+                            builder.set_changeset(opl_parse_changeset_id(data));
                             break;
                         case 't':
-                            way.set_timestamp(opl_parse_timestamp(data));
+                            builder.set_timestamp(opl_parse_timestamp(data));
                             break;
                         case 'i':
-                            way.set_uid(opl_parse_uid(data));
+                            builder.set_uid(opl_parse_uid(data));
                             break;
                         case 'u':
                             opl_parse_string(data, user);
@@ -488,15 +484,13 @@ namespace osmium {
                     }
                 }
 
-                builder.add_user(user);
+                builder.set_user(user);
 
                 if (tags_begin) {
                     opl_parse_tags(tags_begin, buffer, &builder);
                 }
 
                 opl_parse_way_nodes(nodes_begin, nodes_end, buffer, &builder);
-
-                buffer.commit();
             }
 
             inline void opl_parse_relation_members(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::RelationBuilder* parent_builder = nullptr) {
@@ -536,9 +530,8 @@ namespace osmium {
 
             inline void opl_parse_relation(const char** data, osmium::memory::Buffer& buffer) {
                 osmium::builder::RelationBuilder builder{buffer};
-                osmium::Relation& relation = builder.object();
 
-                relation.set_id(opl_parse_id(data));
+                builder.set_id(opl_parse_id(data));
 
                 const char* tags_begin = nullptr;
 
@@ -555,19 +548,19 @@ namespace osmium {
                     ++(*data);
                     switch (c) {
                         case 'v':
-                            relation.set_version(opl_parse_version(data));
+                            builder.set_version(opl_parse_version(data));
                             break;
                         case 'd':
-                            relation.set_visible(opl_parse_visible(data));
+                            builder.set_visible(opl_parse_visible(data));
                             break;
                         case 'c':
-                            relation.set_changeset(opl_parse_changeset_id(data));
+                            builder.set_changeset(opl_parse_changeset_id(data));
                             break;
                         case 't':
-                            relation.set_timestamp(opl_parse_timestamp(data));
+                            builder.set_timestamp(opl_parse_timestamp(data));
                             break;
                         case 'i':
-                            relation.set_uid(opl_parse_uid(data));
+                            builder.set_uid(opl_parse_uid(data));
                             break;
                         case 'u':
                             opl_parse_string(data, user);
@@ -588,7 +581,7 @@ namespace osmium {
                     }
                 }
 
-                builder.add_user(user);
+                builder.set_user(user);
 
                 if (tags_begin) {
                     opl_parse_tags(tags_begin, buffer, &builder);
@@ -597,15 +590,12 @@ namespace osmium {
                 if (members_begin != members_end) {
                     opl_parse_relation_members(members_begin, members_end, buffer, &builder);
                 }
-
-                buffer.commit();
             }
 
             inline void opl_parse_changeset(const char** data, osmium::memory::Buffer& buffer) {
                 osmium::builder::ChangesetBuilder builder{buffer};
-                osmium::Changeset& changeset = builder.object();
 
-                changeset.set_id(opl_parse_changeset_id(data));
+                builder.set_id(opl_parse_changeset_id(data));
 
                 const char* tags_begin = nullptr;
 
@@ -621,19 +611,19 @@ namespace osmium {
                     ++(*data);
                     switch (c) {
                         case 'k':
-                            changeset.set_num_changes(opl_parse_int<osmium::num_changes_type>(data));
+                            builder.set_num_changes(opl_parse_int<osmium::num_changes_type>(data));
                             break;
                         case 's':
-                            changeset.set_created_at(opl_parse_timestamp(data));
+                            builder.set_created_at(opl_parse_timestamp(data));
                             break;
                         case 'e':
-                            changeset.set_closed_at(opl_parse_timestamp(data));
+                            builder.set_closed_at(opl_parse_timestamp(data));
                             break;
                         case 'd':
-                            changeset.set_num_comments(opl_parse_int<osmium::num_comments_type>(data));
+                            builder.set_num_comments(opl_parse_int<osmium::num_comments_type>(data));
                             break;
                         case 'i':
-                            changeset.set_uid(opl_parse_uid(data));
+                            builder.set_uid(opl_parse_uid(data));
                             break;
                         case 'u':
                             opl_parse_string(data, user);
@@ -672,17 +662,17 @@ namespace osmium {
                 }
 
                 if (location1.valid() && location2.valid()) {
-                    changeset.bounds().extend(location1);
-                    changeset.bounds().extend(location2);
+                    osmium::Box box;
+                    box.extend(location1);
+                    box.extend(location2);
+                    builder.set_bounds(box);
                 }
 
-                builder.add_user(user);
+                builder.set_user(user);
 
                 if (tags_begin) {
                     opl_parse_tags(tags_begin, buffer, &builder);
                 }
-
-                buffer.commit();
             }
 
             inline bool opl_parse_line(uint64_t line_count,
@@ -702,6 +692,7 @@ namespace osmium {
                             if (read_types & osmium::osm_entity_bits::node) {
                                 ++data;
                                 opl_parse_node(&data, buffer);
+                                buffer.commit();
                                 return true;
                             }
                             break;
@@ -709,6 +700,7 @@ namespace osmium {
                             if (read_types & osmium::osm_entity_bits::way) {
                                 ++data;
                                 opl_parse_way(&data, buffer);
+                                buffer.commit();
                                 return true;
                             }
                             break;
@@ -716,6 +708,7 @@ namespace osmium {
                             if (read_types & osmium::osm_entity_bits::relation) {
                                 ++data;
                                 opl_parse_relation(&data, buffer);
+                                buffer.commit();
                                 return true;
                             }
                             break;
@@ -723,6 +716,7 @@ namespace osmium {
                             if (read_types & osmium::osm_entity_bits::changeset) {
                                 ++data;
                                 opl_parse_changeset(&data, buffer);
+                                buffer.commit();
                                 return true;
                             }
                             break;
diff --git a/include/osmium/io/detail/pbf_decoder.hpp b/include/osmium/io/detail/pbf_decoder.hpp
index 5df191d..5164ce3 100644
--- a/include/osmium/io/detail/pbf_decoder.hpp
+++ b/include/osmium/io/detail/pbf_decoder.hpp
@@ -50,6 +50,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
 #include <osmium/io/detail/protobuf_tags.hpp>
 #include <osmium/io/detail/zlib.hpp>
+#include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/box.hpp>
@@ -94,6 +95,8 @@ namespace osmium {
 
                 osmium::memory::Buffer m_buffer { initial_buffer_size };
 
+                osmium::io::read_meta m_read_metadata;
+
                 void decode_stringtable(const data_view& data) {
                     if (!m_stringtable.empty()) {
                         throw osmium::pbf_error("more than one stringtable in pbf file");
@@ -143,13 +146,19 @@ namespace osmium {
                                 case OSMFormat::PrimitiveGroup::repeated_Node_nodes:
                                     if (m_read_types & osmium::osm_entity_bits::node) {
                                         decode_node(pbf_primitive_group.get_view());
+                                        m_buffer.commit();
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
                                     break;
                                 case OSMFormat::PrimitiveGroup::optional_DenseNodes_dense:
                                     if (m_read_types & osmium::osm_entity_bits::node) {
-                                        decode_dense_nodes(pbf_primitive_group.get_view());
+                                        if (m_read_metadata == osmium::io::read_meta::yes) {
+                                            decode_dense_nodes(pbf_primitive_group.get_view());
+                                        } else {
+                                            decode_dense_nodes_without_metadata(pbf_primitive_group.get_view());
+                                        }
+                                        m_buffer.commit();
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
@@ -157,6 +166,7 @@ namespace osmium {
                                 case OSMFormat::PrimitiveGroup::repeated_Way_ways:
                                     if (m_read_types & osmium::osm_entity_bits::way) {
                                         decode_way(pbf_primitive_group.get_view());
+                                        m_buffer.commit();
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
@@ -164,6 +174,7 @@ namespace osmium {
                                 case OSMFormat::PrimitiveGroup::repeated_Relation_relations:
                                     if (m_read_types & osmium::osm_entity_bits::relation) {
                                         decode_relation(pbf_primitive_group.get_view());
+                                        m_buffer.commit();
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
@@ -221,9 +232,9 @@ namespace osmium {
 
                 using kv_type = protozero::iterator_range<protozero::pbf_reader::const_uint32_iterator>;
 
-                void build_tag_list(osmium::builder::Builder& builder, const kv_type& keys, const kv_type& vals) {
+                void build_tag_list(osmium::builder::Builder& parent, const kv_type& keys, const kv_type& vals) {
                     if (!keys.empty()) {
-                        osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
+                        osmium::builder::TagListBuilder builder{parent};
                         auto kit = keys.begin();
                         auto vit = vals.begin();
                         while (kit != keys.end()) {
@@ -233,7 +244,7 @@ namespace osmium {
                             }
                             const auto& k = m_stringtable.at(*kit++);
                             const auto& v = m_stringtable.at(*vit++);
-                            tl_builder.add_tag(k.first, k.second, v.first, v.second);
+                            builder.add_tag(k.first, k.second, v.first, v.second);
                         }
                     }
                 }
@@ -243,7 +254,7 @@ namespace osmium {
                 }
 
                 void decode_node(const data_view& data) {
-                    osmium::builder::NodeBuilder builder(m_buffer);
+                    osmium::builder::NodeBuilder builder{m_buffer};
                     osmium::Node& node = builder.object();
 
                     kv_type keys;
@@ -266,7 +277,11 @@ namespace osmium {
                                 vals = pbf_node.get_packed_uint32();
                                 break;
                             case OSMFormat::Node::optional_Info_info:
-                                user = decode_info(pbf_node.get_view(), builder.object());
+                                if (m_read_metadata == osmium::io::read_meta::yes) {
+                                    user = decode_info(pbf_node.get_view(), builder.object());
+                                } else {
+                                    pbf_node.skip();
+                                }
                                 break;
                             case OSMFormat::Node::required_sint64_lat:
                                 lat = pbf_node.get_sint64();
@@ -290,15 +305,13 @@ namespace osmium {
                         ));
                     }
 
-                    builder.add_user(user.first, user.second);
+                    builder.set_user(user.first, user.second);
 
                     build_tag_list(builder, keys, vals);
-
-                    m_buffer.commit();
                 }
 
                 void decode_way(const data_view& data) {
-                    osmium::builder::WayBuilder builder(m_buffer);
+                    osmium::builder::WayBuilder builder{m_buffer};
 
                     kv_type keys;
                     kv_type vals;
@@ -321,7 +334,11 @@ namespace osmium {
                                 vals = pbf_way.get_packed_uint32();
                                 break;
                             case OSMFormat::Way::optional_Info_info:
-                                user = decode_info(pbf_way.get_view(), builder.object());
+                                if (m_read_metadata == osmium::io::read_meta::yes) {
+                                    user = decode_info(pbf_way.get_view(), builder.object());
+                                } else {
+                                    pbf_way.skip();
+                                }
                                 break;
                             case OSMFormat::Way::packed_sint64_refs:
                                 refs = pbf_way.get_packed_sint64();
@@ -337,10 +354,10 @@ namespace osmium {
                         }
                     }
 
-                    builder.add_user(user.first, user.second);
+                    builder.set_user(user.first, user.second);
 
                     if (!refs.empty()) {
-                        osmium::builder::WayNodeListBuilder wnl_builder(m_buffer, &builder);
+                        osmium::builder::WayNodeListBuilder wnl_builder{builder};
                         osmium::util::DeltaDecode<int64_t> ref;
                         if (lats.empty()) {
                             for (const auto& ref_value : refs) {
@@ -363,12 +380,10 @@ namespace osmium {
                     }
 
                     build_tag_list(builder, keys, vals);
-
-                    m_buffer.commit();
                 }
 
                 void decode_relation(const data_view& data) {
-                    osmium::builder::RelationBuilder builder(m_buffer);
+                    osmium::builder::RelationBuilder builder{m_buffer};
 
                     kv_type keys;
                     kv_type vals;
@@ -391,7 +406,11 @@ namespace osmium {
                                 vals = pbf_relation.get_packed_uint32();
                                 break;
                             case OSMFormat::Relation::optional_Info_info:
-                                user = decode_info(pbf_relation.get_view(), builder.object());
+                                if (m_read_metadata == osmium::io::read_meta::yes) {
+                                    user = decode_info(pbf_relation.get_view(), builder.object());
+                                } else {
+                                    pbf_relation.skip();
+                                }
                                 break;
                             case OSMFormat::Relation::packed_int32_roles_sid:
                                 roles = pbf_relation.get_packed_int32();
@@ -407,10 +426,10 @@ namespace osmium {
                         }
                     }
 
-                    builder.add_user(user.first, user.second);
+                    builder.set_user(user.first, user.second);
 
                     if (!refs.empty()) {
-                        osmium::builder::RelationMemberListBuilder rml_builder(m_buffer, &builder);
+                        osmium::builder::RelationMemberListBuilder rml_builder{builder};
                         osmium::util::DeltaDecode<int64_t> ref;
                         while (!roles.empty() && !refs.empty() && !types.empty()) {
                             const auto& r = m_stringtable.at(roles.front());
@@ -431,8 +450,84 @@ namespace osmium {
                     }
 
                     build_tag_list(builder, keys, vals);
+                }
+
+                void build_tag_list_from_dense_nodes(osmium::builder::NodeBuilder& builder, protozero::pbf_reader::const_int32_iterator& it, protozero::pbf_reader::const_int32_iterator last) {
+                    osmium::builder::TagListBuilder tl_builder{builder};
+                    while (it != last && *it != 0) {
+                        const auto& k = m_stringtable.at(*it++);
+                        if (it == last) {
+                            throw osmium::pbf_error("PBF format error"); // this is against the spec, keys/vals must come in pairs
+                        }
+                        const auto& v = m_stringtable.at(*it++);
+                        tl_builder.add_tag(k.first, k.second, v.first, v.second);
+                    }
+
+                    if (it != last) {
+                        ++it;
+                    }
+                }
+
+                void decode_dense_nodes_without_metadata(const data_view& data) {
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> ids;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lats;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lons;
+
+                    protozero::iterator_range<protozero::pbf_reader::const_int32_iterator>  tags;
+
+                    protozero::pbf_message<OSMFormat::DenseNodes> pbf_dense_nodes(data);
+                    while (pbf_dense_nodes.next()) {
+                        switch (pbf_dense_nodes.tag()) {
+                            case OSMFormat::DenseNodes::packed_sint64_id:
+                                ids = pbf_dense_nodes.get_packed_sint64();
+                                break;
+                            case OSMFormat::DenseNodes::packed_sint64_lat:
+                                lats = pbf_dense_nodes.get_packed_sint64();
+                                break;
+                            case OSMFormat::DenseNodes::packed_sint64_lon:
+                                lons = pbf_dense_nodes.get_packed_sint64();
+                                break;
+                            case OSMFormat::DenseNodes::packed_int32_keys_vals:
+                                tags = pbf_dense_nodes.get_packed_int32();
+                                break;
+                            default:
+                                pbf_dense_nodes.skip();
+                        }
+                    }
+
+                    osmium::util::DeltaDecode<int64_t> dense_id;
+                    osmium::util::DeltaDecode<int64_t> dense_latitude;
+                    osmium::util::DeltaDecode<int64_t> dense_longitude;
+
+                    auto tag_it = tags.begin();
+
+                    while (!ids.empty()) {
+                        if (lons.empty() ||
+                            lats.empty()) {
+                            // this is against the spec, must have same number of elements
+                            throw osmium::pbf_error("PBF format error");
+                        }
+
+                        osmium::builder::NodeBuilder builder{m_buffer};
+                        osmium::Node& node = builder.object();
+
+                        node.set_id(dense_id.update(ids.front()));
+                        ids.drop_front();
+
+                        const auto lon = dense_longitude.update(lons.front());
+                        lons.drop_front();
+                        const auto lat = dense_latitude.update(lats.front());
+                        lats.drop_front();
+                        builder.object().set_location(osmium::Location(
+                                convert_pbf_coordinate(lon),
+                                convert_pbf_coordinate(lat)
+                        ));
+
+                        if (tag_it != tags.end()) {
+                            build_tag_list_from_dense_nodes(builder, tag_it, tags.end());
+                        }
+                    }
 
-                    m_buffer.commit();
                 }
 
                 void decode_dense_nodes(const data_view& data) {
@@ -522,7 +617,7 @@ namespace osmium {
 
                         bool visible = true;
 
-                        osmium::builder::NodeBuilder builder(m_buffer);
+                        osmium::builder::NodeBuilder builder{m_buffer};
                         osmium::Node& node = builder.object();
 
                         node.set_id(dense_id.update(ids.front()));
@@ -569,9 +664,7 @@ namespace osmium {
 
                             const auto& u = m_stringtable.at(dense_user_sid.update(user_sids.front()));
                             user_sids.drop_front();
-                            builder.add_user(u.first, u.second);
-                        } else {
-                            builder.add_user("");
+                            builder.set_user(u.first, u.second);
                         }
 
                         // even if the node isn't visible, there's still a record
@@ -588,31 +681,18 @@ namespace osmium {
                         }
 
                         if (tag_it != tags.end()) {
-                            osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
-                            while (tag_it != tags.end() && *tag_it != 0) {
-                                const auto& k = m_stringtable.at(*tag_it++);
-                                if (tag_it == tags.end()) {
-                                    throw osmium::pbf_error("PBF format error"); // this is against the spec, keys/vals must come in pairs
-                                }
-                                const auto& v = m_stringtable.at(*tag_it++);
-                                tl_builder.add_tag(k.first, k.second, v.first, v.second);
-                            }
-
-                            if (tag_it != tags.end()) {
-                                ++tag_it;
-                            }
+                            build_tag_list_from_dense_nodes(builder, tag_it, tags.end());
                         }
-
-                        m_buffer.commit();
                     }
 
                 }
 
             public:
 
-                PBFPrimitiveBlockDecoder(const data_view& data, osmium::osm_entity_bits::type read_types) :
+                PBFPrimitiveBlockDecoder(const data_view& data, osmium::osm_entity_bits::type read_types, osmium::io::read_meta read_metadata) :
                     m_data(data),
-                    m_read_types(read_types) {
+                    m_read_types(read_types),
+                    m_read_metadata(read_metadata) {
                 }
 
                 PBFPrimitiveBlockDecoder(const PBFPrimitiveBlockDecoder&) = delete;
@@ -789,12 +869,14 @@ namespace osmium {
 
                 std::shared_ptr<std::string> m_input_buffer;
                 osmium::osm_entity_bits::type m_read_types;
+                osmium::io::read_meta m_read_metadata;
 
             public:
 
-                PBFDataBlobDecoder(std::string&& input_buffer, osmium::osm_entity_bits::type read_types) :
+                PBFDataBlobDecoder(std::string&& input_buffer, osmium::osm_entity_bits::type read_types, osmium::io::read_meta read_metadata) :
                     m_input_buffer(std::make_shared<std::string>(std::move(input_buffer))),
-                    m_read_types(read_types) {
+                    m_read_types(read_types),
+                    m_read_metadata(read_metadata) {
                 }
 
                 PBFDataBlobDecoder(const PBFDataBlobDecoder&) = default;
@@ -807,7 +889,7 @@ namespace osmium {
 
                 osmium::memory::Buffer operator()() {
                     std::string output;
-                    PBFPrimitiveBlockDecoder decoder(decode_blob(*m_input_buffer, output), m_read_types);
+                    PBFPrimitiveBlockDecoder decoder(decode_blob(*m_input_buffer, output), m_read_types, m_read_metadata);
                     return decoder();
                 }
 
diff --git a/include/osmium/io/detail/pbf_input_format.hpp b/include/osmium/io/detail/pbf_input_format.hpp
index 1253447..31e778a 100644
--- a/include/osmium/io/detail/pbf_input_format.hpp
+++ b/include/osmium/io/detail/pbf_input_format.hpp
@@ -180,7 +180,7 @@ namespace osmium {
                     while (const auto size = check_type_and_get_blob_size("OSMData")) {
                         std::string input_buffer = read_from_input_queue_with_check(size);
 
-                        PBFDataBlobDecoder data_blob_parser{ std::move(input_buffer), read_types() };
+                        PBFDataBlobDecoder data_blob_parser{std::move(input_buffer), read_types(), read_metadata()};
 
                         if (osmium::config::use_pool_threads_for_pbf_parsing()) {
                             send_to_output_queue(osmium::thread::Pool::instance().submit(std::move(data_blob_parser)));
@@ -195,8 +195,8 @@ namespace osmium {
                 PBFParser(future_string_queue_type& input_queue,
                           future_buffer_queue_type& output_queue,
                           std::promise<osmium::io::Header>& header_promise,
-                          osmium::osm_entity_bits::type read_types) :
-                    Parser(input_queue, output_queue, header_promise, read_types),
+                          osmium::io::detail::reader_options options) :
+                    Parser(input_queue, output_queue, header_promise, options),
                     m_input_buffer() {
                 }
 
@@ -221,8 +221,8 @@ namespace osmium {
                 [](future_string_queue_type& input_queue,
                     future_buffer_queue_type& output_queue,
                     std::promise<osmium::io::Header>& header_promise,
-                    osmium::osm_entity_bits::type read_which_entities) {
-                    return std::unique_ptr<Parser>(new PBFParser(input_queue, output_queue, header_promise, read_which_entities));
+                    osmium::io::detail::reader_options options) {
+                    return std::unique_ptr<Parser>(new PBFParser(input_queue, output_queue, header_promise, options));
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/queue_util.hpp b/include/osmium/io/detail/queue_util.hpp
index bc47020..021ea7d 100644
--- a/include/osmium/io/detail/queue_util.hpp
+++ b/include/osmium/io/detail/queue_util.hpp
@@ -33,10 +33,10 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <algorithm>
 #include <exception>
 #include <future>
 #include <string>
+#include <utility>
 
 #include <osmium/memory/buffer.hpp>
 #include <osmium/thread/queue.hpp>
@@ -63,13 +63,6 @@ namespace osmium {
              * This type of queue contains OSM file data in the form it is
              * stored on disk, ie encoded as XML, PBF, etc.
              * The "end of file" is marked by an empty string.
-             */
-            using string_queue_type = osmium::thread::Queue<std::string>;
-
-            /**
-             * This type of queue contains OSM file data in the form it is
-             * stored on disk, ie encoded as XML, PBF, etc.
-             * The "end of file" is marked by an empty string.
              * The strings are wrapped in a std::future so that they can also
              * transport exceptions. The future also helps with keeping the
              * data in order.
@@ -95,11 +88,11 @@ namespace osmium {
                 add_to_queue<T>(queue, T{});
             }
 
-            inline bool at_end_of_data(const std::string& data) {
+            inline bool at_end_of_data(const std::string& data) noexcept {
                 return data.empty();
             }
 
-            inline bool at_end_of_data(osmium::memory::Buffer& buffer) {
+            inline bool at_end_of_data(osmium::memory::Buffer& buffer) noexcept {
                 return !buffer;
             }
 
diff --git a/include/osmium/io/detail/xml_input_format.hpp b/include/osmium/io/detail/xml_input_format.hpp
index d8c57d8..242ef9b 100644
--- a/include/osmium/io/detail/xml_input_format.hpp
+++ b/include/osmium/io/detail/xml_input_format.hpp
@@ -80,8 +80,8 @@ namespace osmium {
         XML_Error error_code;
         std::string error_string;
 
-        explicit xml_error(XML_Parser parser) :
-            io_error(std::string("XML parsing error at line ")
+        explicit xml_error(const XML_Parser& parser) :
+            io_error(std::string{"XML parsing error at line "}
                     + std::to_string(XML_GetCurrentLineNumber(parser))
                     + ", column "
                     + std::to_string(XML_GetCurrentColumnNumber(parser))
@@ -117,7 +117,7 @@ namespace osmium {
         }
 
         explicit format_version_error(const char* v) :
-            io_error(std::string("Can not read file with version ") + v),
+            io_error(std::string{"Can not read file with version "} + v),
             version(v) {
         }
 
@@ -201,7 +201,7 @@ namespace osmium {
                     static void entity_declaration_handler(void*,
                             const XML_Char*, int, const XML_Char*, int, const XML_Char*,
                             const XML_Char*, const XML_Char*, const XML_Char*) {
-                        throw osmium::xml_error("XML entities are not supported");
+                        throw osmium::xml_error{"XML entities are not supported"};
                     }
 
                 public:
@@ -209,7 +209,7 @@ namespace osmium {
                     explicit ExpatXMLParser(T* callback_object) :
                         m_parser(XML_ParserCreate(nullptr)) {
                         if (!m_parser) {
-                            throw osmium::io_error("Internal error: Can not create parser");
+                            throw osmium::io_error{"Internal error: Can not create parser"};
                         }
                         XML_SetUserData(m_parser, callback_object);
                         XML_SetElementHandler(m_parser, start_element_wrapper, end_element_wrapper);
@@ -229,7 +229,7 @@ namespace osmium {
 
                     void operator()(const std::string& data, bool last) {
                         if (XML_Parse(m_parser, data.data(), static_cast_with_assert<int>(data.size()), last) == XML_STATUS_ERROR) {
-                            throw osmium::xml_error(m_parser);
+                            throw osmium::xml_error{m_parser};
                         }
                     }
 
@@ -271,37 +271,32 @@ namespace osmium {
                     return user;
                 }
 
-                void init_changeset(osmium::builder::ChangesetBuilder* builder, const XML_Char** attrs) {
-                    const char* user = "";
-                    osmium::Changeset& new_changeset = builder->object();
+                void init_changeset(osmium::builder::ChangesetBuilder& builder, const XML_Char** attrs) {
+                    osmium::Box box;
 
-                    osmium::Location min;
-                    osmium::Location max;
-                    check_attributes(attrs, [&min, &max, &user, &new_changeset](const XML_Char* name, const XML_Char* value) {
+                    check_attributes(attrs, [&builder, &box](const XML_Char* name, const XML_Char* value) {
                         if (!std::strcmp(name, "min_lon")) {
-                            min.set_lon(value);
+                            box.bottom_left().set_lon(value);
                         } else if (!std::strcmp(name, "min_lat")) {
-                            min.set_lat(value);
+                            box.bottom_left().set_lat(value);
                         } else if (!std::strcmp(name, "max_lon")) {
-                            max.set_lon(value);
+                            box.top_right().set_lon(value);
                         } else if (!std::strcmp(name, "max_lat")) {
-                            max.set_lat(value);
+                            box.top_right().set_lat(value);
                         } else if (!std::strcmp(name, "user")) {
-                            user = value;
+                            builder.set_user(value);
                         } else {
-                            new_changeset.set_attribute(name, value);
+                            builder.set_attribute(name, value);
                         }
                     });
 
-                    new_changeset.bounds().extend(min);
-                    new_changeset.bounds().extend(max);
-
-                    builder->add_user(user);
+                    builder.set_bounds(box);
                 }
 
-                void get_tag(osmium::builder::Builder* builder, const XML_Char** attrs) {
+                void get_tag(osmium::builder::Builder& builder, const XML_Char** attrs) {
                     const char* k = "";
                     const char* v = "";
+
                     check_attributes(attrs, [&k, &v](const XML_Char* name, const XML_Char* value) {
                         if (name[0] == 'k' && name[1] == 0) {
                             k = value;
@@ -309,8 +304,9 @@ namespace osmium {
                             v = value;
                         }
                     });
+
                     if (!m_tl_builder) {
-                        m_tl_builder = std::unique_ptr<osmium::builder::TagListBuilder>(new osmium::builder::TagListBuilder(m_buffer, builder));
+                        m_tl_builder.reset(new osmium::builder::TagListBuilder{builder});
                     }
                     m_tl_builder->add_tag(k, v);
                 }
@@ -330,17 +326,17 @@ namespace osmium {
                                     if (!std::strcmp(name, "version")) {
                                         m_header.set("version", value);
                                         if (std::strcmp(value, "0.6")) {
-                                            throw osmium::format_version_error(value);
+                                            throw osmium::format_version_error{value};
                                         }
                                     } else if (!std::strcmp(name, "generator")) {
                                         m_header.set("generator", value);
                                     }
                                 });
                                 if (m_header.get("version") == "") {
-                                    throw osmium::format_version_error();
+                                    throw osmium::format_version_error{};
                                 }
                             } else {
-                                throw osmium::xml_error(std::string("Unknown top-level element: ") + element);
+                                throw osmium::xml_error{std::string{"Unknown top-level element: "} + element};
                             }
                             m_context = context::top;
                             break;
@@ -349,8 +345,8 @@ namespace osmium {
                             if (!std::strcmp(element, "node")) {
                                 mark_header_as_done();
                                 if (read_types() & osmium::osm_entity_bits::node) {
-                                    m_node_builder = std::unique_ptr<osmium::builder::NodeBuilder>(new osmium::builder::NodeBuilder(m_buffer));
-                                    m_node_builder->add_user(init_object(m_node_builder->object(), attrs));
+                                    m_node_builder.reset(new osmium::builder::NodeBuilder{m_buffer});
+                                    m_node_builder->set_user(init_object(m_node_builder->object(), attrs));
                                     m_context = context::node;
                                 } else {
                                     m_context = context::ignored_node;
@@ -358,8 +354,8 @@ namespace osmium {
                             } else if (!std::strcmp(element, "way")) {
                                 mark_header_as_done();
                                 if (read_types() & osmium::osm_entity_bits::way) {
-                                    m_way_builder = std::unique_ptr<osmium::builder::WayBuilder>(new osmium::builder::WayBuilder(m_buffer));
-                                    m_way_builder->add_user(init_object(m_way_builder->object(), attrs));
+                                    m_way_builder.reset(new osmium::builder::WayBuilder{m_buffer});
+                                    m_way_builder->set_user(init_object(m_way_builder->object(), attrs));
                                     m_context = context::way;
                                 } else {
                                     m_context = context::ignored_way;
@@ -367,8 +363,8 @@ namespace osmium {
                             } else if (!std::strcmp(element, "relation")) {
                                 mark_header_as_done();
                                 if (read_types() & osmium::osm_entity_bits::relation) {
-                                    m_relation_builder = std::unique_ptr<osmium::builder::RelationBuilder>(new osmium::builder::RelationBuilder(m_buffer));
-                                    m_relation_builder->add_user(init_object(m_relation_builder->object(), attrs));
+                                    m_relation_builder.reset(new osmium::builder::RelationBuilder{m_buffer});
+                                    m_relation_builder->set_user(init_object(m_relation_builder->object(), attrs));
                                     m_context = context::relation;
                                 } else {
                                     m_context = context::ignored_relation;
@@ -376,8 +372,8 @@ namespace osmium {
                             } else if (!std::strcmp(element, "changeset")) {
                                 mark_header_as_done();
                                 if (read_types() & osmium::osm_entity_bits::changeset) {
-                                    m_changeset_builder = std::unique_ptr<osmium::builder::ChangesetBuilder>(new osmium::builder::ChangesetBuilder(m_buffer));
-                                    init_changeset(m_changeset_builder.get(), attrs);
+                                    m_changeset_builder.reset(new osmium::builder::ChangesetBuilder{m_buffer});
+                                    init_changeset(*m_changeset_builder, attrs);
                                     m_context = context::changeset;
                                 } else {
                                     m_context = context::ignored_changeset;
@@ -407,7 +403,7 @@ namespace osmium {
                             m_last_context = context::node;
                             m_context = context::in_object;
                             if (!std::strcmp(element, "tag")) {
-                                get_tag(m_node_builder.get(), attrs);
+                                get_tag(*m_node_builder, attrs);
                             }
                             break;
                         case context::way:
@@ -417,7 +413,7 @@ namespace osmium {
                                 m_tl_builder.reset();
 
                                 if (!m_wnl_builder) {
-                                    m_wnl_builder = std::unique_ptr<osmium::builder::WayNodeListBuilder>(new osmium::builder::WayNodeListBuilder(m_buffer, m_way_builder.get()));
+                                    m_wnl_builder.reset(new osmium::builder::WayNodeListBuilder{*m_way_builder});
                                 }
 
                                 NodeRef nr;
@@ -433,7 +429,7 @@ namespace osmium {
                                 m_wnl_builder->add_node_ref(nr);
                             } else if (!std::strcmp(element, "tag")) {
                                 m_wnl_builder.reset();
-                                get_tag(m_way_builder.get(), attrs);
+                                get_tag(*m_way_builder, attrs);
                             }
                             break;
                         case context::relation:
@@ -443,7 +439,7 @@ namespace osmium {
                                 m_tl_builder.reset();
 
                                 if (!m_rml_builder) {
-                                    m_rml_builder = std::unique_ptr<osmium::builder::RelationMemberListBuilder>(new osmium::builder::RelationMemberListBuilder(m_buffer, m_relation_builder.get()));
+                                    m_rml_builder.reset(new osmium::builder::RelationMemberListBuilder{*m_relation_builder});
                                 }
 
                                 item_type type = item_type::undefined;
@@ -459,15 +455,15 @@ namespace osmium {
                                     }
                                 });
                                 if (type != item_type::node && type != item_type::way && type != item_type::relation) {
-                                    throw osmium::xml_error("Unknown type on relation member");
+                                    throw osmium::xml_error{"Unknown type on relation member"};
                                 }
                                 if (ref == 0) {
-                                    throw osmium::xml_error("Missing ref on relation member");
+                                    throw osmium::xml_error{"Missing ref on relation member"};
                                 }
                                 m_rml_builder->add_member(type, ref, role);
                             } else if (!std::strcmp(element, "tag")) {
                                 m_rml_builder.reset();
-                                get_tag(m_relation_builder.get(), attrs);
+                                get_tag(*m_relation_builder, attrs);
                             }
                             break;
                         case context::changeset:
@@ -476,12 +472,12 @@ namespace osmium {
                                 m_context = context::discussion;
                                 m_tl_builder.reset();
                                 if (!m_changeset_discussion_builder) {
-                                    m_changeset_discussion_builder = std::unique_ptr<osmium::builder::ChangesetDiscussionBuilder>(new osmium::builder::ChangesetDiscussionBuilder(m_buffer, m_changeset_builder.get()));
+                                    m_changeset_discussion_builder.reset(new osmium::builder::ChangesetDiscussionBuilder{*m_changeset_builder});
                                 }
                             } else if (!std::strcmp(element, "tag")) {
                                 m_context = context::in_object;
                                 m_changeset_discussion_builder.reset();
-                                get_tag(m_changeset_builder.get(), attrs);
+                                get_tag(*m_changeset_builder, attrs);
                             }
                             break;
                         case context::discussion:
@@ -632,8 +628,8 @@ namespace osmium {
                 XMLParser(future_string_queue_type& input_queue,
                           future_buffer_queue_type& output_queue,
                           std::promise<osmium::io::Header>& header_promise,
-                          osmium::osm_entity_bits::type read_types) :
-                    Parser(input_queue, output_queue, header_promise, read_types),
+                          osmium::io::detail::reader_options options) :
+                    Parser(input_queue, output_queue, header_promise, options),
                     m_context(context::root),
                     m_last_context(context::root),
                     m_in_delete_section(false),
@@ -657,7 +653,7 @@ namespace osmium {
                     ExpatXMLParser<XMLParser> parser(this);
 
                     while (!input_done()) {
-                        std::string data = get_input();
+                        const std::string data{get_input()};
                         parser(data, input_done());
                         if (read_types() == osmium::osm_entity_bits::nothing && header_is_done()) {
                             break;
@@ -680,8 +676,8 @@ namespace osmium {
                 [](future_string_queue_type& input_queue,
                     future_buffer_queue_type& output_queue,
                     std::promise<osmium::io::Header>& header_promise,
-                    osmium::osm_entity_bits::type read_which_entities) {
-                    return std::unique_ptr<Parser>(new XMLParser(input_queue, output_queue, header_promise, read_which_entities));
+                    osmium::io::detail::reader_options options) {
+                    return std::unique_ptr<Parser>(new XMLParser{input_queue, output_queue, header_promise, options});
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/file_format.hpp b/include/osmium/io/file_format.hpp
index c447cb4..72b4abc 100644
--- a/include/osmium/io/file_format.hpp
+++ b/include/osmium/io/file_format.hpp
@@ -49,6 +49,11 @@ namespace osmium {
             debug   = 6
         };
 
+        enum class read_meta {
+            no  = 0,
+            yes = 1
+        };
+
 // avoid g++ false positive
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wreturn-type"
diff --git a/include/osmium/io/header.hpp b/include/osmium/io/header.hpp
index 55ff5c6..abd85f8 100644
--- a/include/osmium/io/header.hpp
+++ b/include/osmium/io/header.hpp
@@ -44,17 +44,37 @@ namespace osmium {
     namespace io {
 
         /**
-        * Meta information from the header of an OSM file.
-        */
+         * Meta information from the header of an OSM file.
+         *
+         * The header can contain any number of bounding boxes, although
+         * usually there is only a single one (or none). PBF files only
+         * allow a single bounding box, but XML files can have multiple ones,
+         * although it is unusual and the semantics are unclear, so it is
+         * discouraged to create files with multiple bounding boxes.
+         *
+         * The header contains a flag telling you whether this file can
+         * contain multiple versions of the same object. This is true for
+         * history files and for change files, but not for normal OSM data
+         * files. Not all OSM file formats can distinguish between those
+         * cases, so the flag might be wrong.
+         *
+         * In addition the header can contain any number of key-value pairs
+         * with additional information. Most often this is used to set the
+         * "generator", the program that generated the file. Depending on
+         * the file format some of these key-value pairs are handled
+         * specially. The the Options parent class for details on how to
+         * set and get those key-value pairs.
+         */
         class Header : public osmium::util::Options {
 
             /// Bounding boxes
             std::vector<osmium::Box> m_boxes;
 
             /**
-            * Are there possibly multiple versions of the same object in this stream of objects?
-            * This is true for history files and for change files, but not for normal OSM files.
-            */
+             * Are there possibly multiple versions of the same object in
+             * this stream of objects? This should be true for history files
+             * and for change files, but not for normal OSM data files.
+             */
             bool m_has_multiple_object_versions = false;
 
         public:
@@ -65,49 +85,76 @@ namespace osmium {
                 Options(values) {
             }
 
-            Header(const Header&) = default;
-            Header& operator=(const Header&) = default;
-
-            Header(Header&&) = default;
-            Header& operator=(Header&&) = default;
-
-            ~Header() = default;
-
+            /**
+             * Get the bounding boxes defined in the header.
+             */
             std::vector<osmium::Box>& boxes() noexcept {
                 return m_boxes;
             }
 
+            /**
+             * Get the bounding boxes defined in the header.
+             */
             const std::vector<osmium::Box>& boxes() const noexcept {
                 return m_boxes;
             }
 
+            /**
+             * Set all the bounding boxes in the header.
+             */
             Header& boxes(const std::vector<osmium::Box>& boxes) noexcept {
                 m_boxes = boxes;
                 return *this;
             }
 
+            /**
+             * Get the first (or only if there is only one) bounding box.
+             *
+             * Returns an empty, invalid box if there is none.
+             */
             osmium::Box box() const {
-                return m_boxes.empty() ? osmium::Box() : m_boxes.front();
+                return m_boxes.empty() ? osmium::Box{} : m_boxes.front();
             }
 
+            /**
+             * Join up all the bounding boxes in the header into one and return
+             * it. This method is what you probably want to use unless you want
+             * to handle the possibly multiple bounding boxes yourself.
+             *
+             * Returns an empty, invalid box if there is none.
+             */
             osmium::Box joined_boxes() const {
                 osmium::Box box;
                 for (const auto& b : m_boxes) {
-                    box.extend(b.bottom_left());
-                    box.extend(b.top_right());
+                    box.extend(b);
                 }
                 return box;
             }
 
+            /**
+             * Add the given bounding box to the list of bounding boxes in the
+             * header.
+             *
+             * @returns The header itself to allow chaining.
+             */
             Header& add_box(const osmium::Box& box) {
                 m_boxes.push_back(box);
                 return *this;
             }
 
+            /**
+             * Can this file contain multiple versions of the same object?
+             */
             bool has_multiple_object_versions() const noexcept {
                 return m_has_multiple_object_versions;
             }
 
+            /**
+             * Set the flag that tells us whether this file can contain
+             * multiple versions of the same object?
+             *
+             * @returns The header itself to allow chaining.
+             */
             Header& set_has_multiple_object_versions(bool value) noexcept {
                 m_has_multiple_object_versions = value;
                 return *this;
diff --git a/include/osmium/io/reader.hpp b/include/osmium/io/reader.hpp
index 12f97b8..89c8564 100644
--- a/include/osmium/io/reader.hpp
+++ b/include/osmium/io/reader.hpp
@@ -91,7 +91,6 @@ namespace osmium {
         class Reader {
 
             osmium::io::File m_file;
-            osmium::osm_entity_bits::type m_read_which_entities;
 
             enum class status {
                 okay   = 0, // normal reading
@@ -118,15 +117,25 @@ namespace osmium {
 
             size_t m_file_size;
 
+            osmium::io::detail::reader_options m_options;
+
+            void set_option(osmium::osm_entity_bits::type value) noexcept {
+                m_options.read_which_entities = value;
+            }
+
+            void set_option(osmium::io::read_meta value) noexcept {
+                m_options.read_metadata = value;
+            }
+
             // This function will run in a separate thread.
             static void parser_thread(const osmium::io::File& file,
                                       detail::future_string_queue_type& input_queue,
                                       detail::future_buffer_queue_type& osmdata_queue,
                                       std::promise<osmium::io::Header>&& header_promise,
-                                      osmium::osm_entity_bits::type read_which_entities) {
+                                      osmium::io::detail::reader_options options) {
                 std::promise<osmium::io::Header> promise = std::move(header_promise);
                 const auto creator = detail::ParserFactory::instance().get_creator_function(file);
-                const auto parser = creator(input_queue, osmdata_queue, promise, read_which_entities);
+                const auto parser = creator(input_queue, osmdata_queue, promise, options);
                 parser->parse();
             }
 
@@ -205,15 +214,28 @@ namespace osmium {
             /**
              * Create new Reader object.
              *
-             * @param file The file we want to open.
-             * @param read_which_entities Which OSM entities (nodes, ways, relations, and/or changesets)
-             *                            should be read from the input file. It can speed the read up
-             *                            significantly if objects that are not needed anyway are not
-             *                            parsed.
+             * @param file The file (contains name and format info) to open.
+             * @param args All further arguments are optional and can appear
+             *             in any order:
+             *
+             * * osmium::osm_entities::bits: Which OSM entities (nodes, ways,
+             *      relations, and/or changesets) should be read from the
+             *      input file. It can speed the read up significantly if
+             *      objects that are not needed anyway are not parsed.
+             *
+             * * osmium::io::read_meta: Read meta data or not. The default is
+             *      osmium::io::read_meta::yes which means that meta data
+             *      is read normally. If you set this to
+             *      osmium::io::read_meta::no, meta data (like version, uid,
+             *      etc.) is not read possibly speeding up the read. Not all
+             *      file formats use this setting.
+             *
+             * @throws osmium::io_error If there was an error.
+             * @throws std::system_error If the file could not be opened.
              */
-            explicit Reader(const osmium::io::File& file, osmium::osm_entity_bits::type read_which_entities = osmium::osm_entity_bits::all) :
+            template <typename... TArgs>
+            explicit Reader(const osmium::io::File& file, TArgs&&... args) :
                 m_file(file.check()),
-                m_read_which_entities(read_which_entities),
                 m_status(status::okay),
                 m_childpid(0),
                 m_input_queue(detail::get_input_queue_size(), "raw_input"),
@@ -227,17 +249,24 @@ namespace osmium {
                 m_header(),
                 m_thread(),
                 m_file_size(m_decompressor->file_size()) {
+
+                (void)std::initializer_list<int>{
+                    (set_option(args), 0)...
+                };
+
                 std::promise<osmium::io::Header> header_promise;
                 m_header_future = header_promise.get_future();
-                m_thread = osmium::thread::thread_handler{parser_thread, std::ref(m_file), std::ref(m_input_queue), std::ref(m_osmdata_queue), std::move(header_promise), read_which_entities};
+                m_thread = osmium::thread::thread_handler{parser_thread, std::ref(m_file), std::ref(m_input_queue), std::ref(m_osmdata_queue), std::move(header_promise), m_options};
             }
 
-            explicit Reader(const std::string& filename, osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all) :
-                Reader(osmium::io::File(filename), read_types) {
+            template <typename... TArgs>
+            explicit Reader(const std::string& filename, TArgs&&... args) :
+                Reader(osmium::io::File(filename), std::forward<TArgs>(args)...) {
             }
 
-            explicit Reader(const char* filename, osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all) :
-                Reader(osmium::io::File(filename), read_types) {
+            template <typename... TArgs>
+            explicit Reader(const char* filename, TArgs&&... args) :
+                Reader(osmium::io::File(filename), std::forward<TArgs>(args)...) {
             }
 
             Reader(const Reader&) = delete;
@@ -304,7 +333,7 @@ namespace osmium {
                 try {
                     if (m_header_future.valid()) {
                         m_header = m_header_future.get();
-                        if (m_read_which_entities == osmium::osm_entity_bits::nothing) {
+                        if (m_options.read_which_entities == osmium::osm_entity_bits::nothing) {
                             m_status = status::eof;
                         }
                     }
@@ -330,7 +359,7 @@ namespace osmium {
                 osmium::memory::Buffer buffer;
 
                 if (m_status != status::okay ||
-                    m_read_which_entities == osmium::osm_entity_bits::nothing) {
+                    m_options.read_which_entities == osmium::osm_entity_bits::nothing) {
                     throw io_error("Can not read from reader when in status 'closed', 'eof', or 'error'");
                 }
 
diff --git a/include/osmium/memory/buffer.hpp b/include/osmium/memory/buffer.hpp
index 8c246db..bcf0bd0 100644
--- a/include/osmium/memory/buffer.hpp
+++ b/include/osmium/memory/buffer.hpp
@@ -113,6 +113,9 @@ namespace osmium {
             size_t m_capacity;
             size_t m_written;
             size_t m_committed;
+#ifndef NDEBUG
+            uint8_t m_builder_count{0};
+#endif
             auto_grow m_auto_grow {auto_grow::no};
             std::function<void(Buffer&)> m_full;
 
@@ -216,13 +219,28 @@ namespace osmium {
 
             ~Buffer() = default;
 
+#ifndef NDEBUG
+            void increment_builder_count() noexcept {
+                ++m_builder_count;
+            }
+
+            void decrement_builder_count() noexcept {
+                assert(m_builder_count > 0);
+                --m_builder_count;
+            }
+
+            uint8_t builder_count() const noexcept {
+                return m_builder_count;
+            }
+#endif
+
             /**
              * Return a pointer to data inside the buffer.
              *
              * @pre The buffer must be valid.
              */
             unsigned char* data() const noexcept {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return m_data;
             }
 
@@ -258,7 +276,7 @@ namespace osmium {
              * @pre The buffer must be valid.
              */
             bool is_aligned() const noexcept {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return (m_written % align_bytes == 0) && (m_committed % align_bytes == 0);
             }
 
@@ -283,7 +301,7 @@ namespace osmium {
              * than the difference between committed() and capacity().
              */
             OSMIUM_DEPRECATED void set_full_callback(std::function<void(Buffer&)> full) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 m_full = full;
             }
 
@@ -292,7 +310,6 @@ namespace osmium {
              * This works only with internally memory-managed buffers.
              * If the given size is not larger than the current capacity,
              * nothing is done.
-             * Already written but not committed data is discarded.
              *
              * @pre The buffer must be valid.
              *
@@ -305,7 +322,7 @@ namespace osmium {
              * @throws std::bad_alloc if there isn't enough memory available.
              */
             void grow(size_t size) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 if (!m_memory) {
                     throw std::logic_error("Can't grow Buffer if it doesn't use internal memory management.");
                 }
@@ -325,15 +342,18 @@ namespace osmium {
             /**
              * Mark currently written bytes in the buffer as committed.
              *
-             * @pre The buffer must be valid and aligned properly (as indicated
+             * @pre The buffer must be valid.
+             * @pre The buffer must be aligned properly (as indicated
              *      by is_aligned().
+             * @pre No builder can be open on this buffer.
              *
              * @returns Number of committed bytes before this commit. Can be
              *          used as an offset into the buffer to get to the
              *          object being committed by this call.
              */
             size_t commit() {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
+                assert(m_builder_count == 0 && "Make sure there are no Builder objects still in scope");
                 assert(is_aligned());
 
                 const size_t offset = m_committed;
@@ -345,9 +365,11 @@ namespace osmium {
              * Roll back changes in buffer to last committed state.
              *
              * @pre The buffer must be valid.
+             * @pre No builder can be open on this buffer.
              */
             void rollback() {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
+                assert(m_builder_count == 0 && "Make sure there are no Builder objects still in scope");
                 m_written = m_committed;
             }
 
@@ -356,9 +378,12 @@ namespace osmium {
              *
              * No-op on an invalid buffer.
              *
+             * @pre No builder can be open on this buffer.
+             *
              * @returns Number of bytes in the buffer before it was cleared.
              */
             size_t clear() {
+                assert(m_builder_count == 0 && "Make sure there are no Builder objects still in scope");
                 const size_t committed = m_committed;
                 m_written = 0;
                 m_committed = 0;
@@ -377,7 +402,7 @@ namespace osmium {
              */
             template <typename T>
             T& get(const size_t offset) const {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return *reinterpret_cast<T*>(&m_data[offset]);
             }
 
@@ -415,7 +440,7 @@ namespace osmium {
              *         no callback defined and the buffer isn't auto-growing.
              */
             unsigned char* reserve_space(const size_t size) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 // try to flush the buffer empty first.
                 if (m_written + size > m_capacity && m_full) {
                     m_full(*this);
@@ -455,7 +480,7 @@ namespace osmium {
              */
             template <typename T>
             T& add_item(const T& item) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 unsigned char* target = reserve_space(item.padded_size());
                 std::copy_n(reinterpret_cast<const unsigned char*>(&item), item.padded_size(), target);
                 return *reinterpret_cast<T*>(target);
@@ -465,6 +490,7 @@ namespace osmium {
              * Add committed contents of the given buffer to this buffer.
              *
              * @pre The buffer must be valid.
+             * @pre No builder can be open on this buffer.
              *
              * Note that you have to eventually call commit() to actually
              * commit this data.
@@ -472,7 +498,9 @@ namespace osmium {
              * @param buffer The source of the copy. Must be valid.
              */
             void add_buffer(const Buffer& buffer) {
-                assert(m_data && buffer);
+                assert(m_data && "This must be a valid buffer");
+                assert(buffer && "Buffer parameter must be a valid buffer");
+                assert(m_builder_count == 0 && "Make sure there are no Builder objects still in scope");
                 unsigned char* target = reserve_space(buffer.committed());
                 std::copy_n(buffer.data(), buffer.committed(), target);
             }
@@ -482,11 +510,13 @@ namespace osmium {
              * you can use std::back_inserter.
              *
              * @pre The buffer must be valid.
+             * @pre No builder can be open on this buffer.
              *
              * @param item The item to be added.
              */
             void push_back(const osmium::memory::Item& item) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
+                assert(m_builder_count == 0 && "Make sure there are no Builder objects still in scope");
                 add_item(item);
                 commit();
             }
@@ -537,7 +567,7 @@ namespace osmium {
              */
             template <typename T>
             t_iterator<T> begin() {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return t_iterator<T>(m_data, m_data + m_committed);
             }
 
@@ -550,7 +580,7 @@ namespace osmium {
              * @returns Iterator to first OSMEntity in the buffer.
              */
             iterator begin() {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return iterator(m_data, m_data + m_committed);
             }
 
@@ -565,7 +595,7 @@ namespace osmium {
              */
             template <typename T>
             t_iterator<T> get_iterator(size_t offset) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return t_iterator<T>(m_data + offset, m_data + m_committed);
             }
 
@@ -579,7 +609,7 @@ namespace osmium {
              *          buffer.
              */
             iterator get_iterator(size_t offset) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return iterator(m_data + offset, m_data + m_committed);
             }
 
@@ -593,7 +623,7 @@ namespace osmium {
              */
             template <typename T>
             t_iterator<T> end() {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return t_iterator<T>(m_data + m_committed, m_data + m_committed);
             }
 
@@ -606,40 +636,40 @@ namespace osmium {
              * @returns End iterator.
              */
             iterator end() {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return iterator(m_data + m_committed, m_data + m_committed);
             }
 
             template <typename T>
             t_const_iterator<T> cbegin() const {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return t_const_iterator<T>(m_data, m_data + m_committed);
             }
 
             const_iterator cbegin() const {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return const_iterator(m_data, m_data + m_committed);
             }
 
             template <typename T>
             t_const_iterator<T> get_iterator(size_t offset) const {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return t_const_iterator<T>(m_data + offset, m_data + m_committed);
             }
 
             const_iterator get_iterator(size_t offset) const {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return const_iterator(m_data + offset, m_data + m_committed);
             }
 
             template <typename T>
             t_const_iterator<T> cend() const {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return t_const_iterator<T>(m_data + m_committed, m_data + m_committed);
             }
 
             const_iterator cend() const {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 return const_iterator(m_data + m_committed, m_data + m_committed);
             }
 
@@ -698,7 +728,7 @@ namespace osmium {
              */
             template <typename TCallbackClass>
             void purge_removed(TCallbackClass* callback) {
-                assert(m_data);
+                assert(m_data && "This must be a valid buffer");
                 if (begin() == end()) {
                     return;
                 }
diff --git a/include/osmium/memory/collection.hpp b/include/osmium/memory/collection.hpp
index 2a2c040..fb413ff 100644
--- a/include/osmium/memory/collection.hpp
+++ b/include/osmium/memory/collection.hpp
@@ -46,9 +46,9 @@ namespace osmium {
         template <typename TMember>
         class CollectionIterator {
 
-            // This data_type is either 'unsigned char*' or 'const unsigned char*' depending
-            // on whether TMember is const. This allows this class to be used as an iterator and
-            // as a const_iterator.
+            // This data_type is either 'unsigned char*' or 'const unsigned
+            // char*' depending on whether TMember is const. This allows this
+            // class to be used as an iterator and as a const_iterator.
             using data_type = typename std::conditional<std::is_const<TMember>::value, const unsigned char*, unsigned char*>::type;
 
             data_type m_data;
@@ -92,11 +92,11 @@ namespace osmium {
                 return m_data;
             }
 
-            TMember& operator*() const {
+            TMember& operator*() const noexcept {
                 return *reinterpret_cast<TMember*>(m_data);
             }
 
-            TMember* operator->() const {
+            TMember* operator->() const noexcept {
                 return reinterpret_cast<TMember*>(m_data);
             }
 
@@ -118,9 +118,12 @@ namespace osmium {
 
         public:
 
-            using iterator       = CollectionIterator<TMember>;
-            using const_iterator = CollectionIterator<const TMember>;
-            using value_type     = TMember;
+            using value_type      = TMember;
+            using reference       = TMember&;
+            using const_reference = const TMember&;
+            using iterator        = CollectionIterator<TMember>;
+            using const_iterator  = CollectionIterator<const TMember>;
+            using size_type       = size_t;
 
             static constexpr osmium::item_type itemtype = TCollectionItemType;
 
@@ -128,31 +131,45 @@ namespace osmium {
                 Item(sizeof(Collection<TMember, TCollectionItemType>), TCollectionItemType) {
             }
 
-            bool empty() const {
+            /**
+             * Does this collection contain any items?
+             *
+             * Complexity: Constant.
+             */
+            bool empty() const noexcept {
                 return sizeof(Collection<TMember, TCollectionItemType>) == byte_size();
             }
 
-            iterator begin() {
+            /**
+             * Returns the number of items in this collection.
+             *
+             * Complexity: Linear in the number of items.
+             */
+            size_type size() const noexcept {
+                return static_cast<size_type>(std::distance(begin(), end()));
+            }
+
+            iterator begin() noexcept {
                 return iterator(data() + sizeof(Collection<TMember, TCollectionItemType>));
             }
 
-            iterator end() {
+            iterator end() noexcept {
                 return iterator(data() + byte_size());
             }
 
-            const_iterator cbegin() const {
+            const_iterator cbegin() const noexcept {
                 return const_iterator(data() + sizeof(Collection<TMember, TCollectionItemType>));
             }
 
-            const_iterator cend() const {
+            const_iterator cend() const noexcept {
                 return const_iterator(data() + byte_size());
             }
 
-            const_iterator begin() const {
+            const_iterator begin() const noexcept {
                 return cbegin();
             }
 
-            const_iterator end() const {
+            const_iterator end() const noexcept {
                 return cend();
             }
 
diff --git a/include/osmium/memory/item.hpp b/include/osmium/memory/item.hpp
index 2df33c7..b72ca4d 100644
--- a/include/osmium/memory/item.hpp
+++ b/include/osmium/memory/item.hpp
@@ -59,9 +59,9 @@ namespace osmium {
         using item_size_type = uint32_t;
 
         // align datastructures to this many bytes
-        constexpr item_size_type align_bytes = 8;
+        constexpr const item_size_type align_bytes = 8;
 
-        inline std::size_t padded_length(std::size_t length) noexcept {
+        inline constexpr std::size_t padded_length(std::size_t length) noexcept {
             return (length + align_bytes - 1) & ~(align_bytes - 1);
         }
 
diff --git a/include/osmium/osm/area.hpp b/include/osmium/osm/area.hpp
index 490fbe9..d146e97 100644
--- a/include/osmium/osm/area.hpp
+++ b/include/osmium/osm/area.hpp
@@ -50,7 +50,8 @@ DEALINGS IN THE SOFTWARE.
 namespace osmium {
 
     namespace builder {
-        template <class T> class ObjectBuilder;
+        template <typename TDerived, typename T>
+        class OSMObjectBuilder;
     } // namespace builder
 
     /**
@@ -117,7 +118,8 @@ namespace osmium {
      */
     class Area : public OSMObject {
 
-        friend class osmium::builder::ObjectBuilder<osmium::Area>;
+        template <typename TDerived, typename T>
+        friend class osmium::builder::OSMObjectBuilder;
 
         Area() :
             OSMObject(sizeof(Area), osmium::item_type::area) {
@@ -130,6 +132,8 @@ namespace osmium {
         /**
          * Was this area created from a way? (In contrast to areas
          * created from a relation and their members.)
+         *
+         * Complexity: Constant.
          */
         bool from_way() const noexcept {
             return (positive_id() & 0x1) == 0;
@@ -137,6 +141,8 @@ namespace osmium {
 
         /**
          * Return the Id of the way or relation this area was created from.
+         *
+         * Complexity: Constant.
          */
         osmium::object_id_type orig_id() const noexcept {
             return osmium::area_id_to_object_id(id());
@@ -145,6 +151,8 @@ namespace osmium {
         /**
          * Count the number of outer and inner rings of this area.
          *
+         * Complexity: Linear in the number of rings.
+         *
          * @returns Pair (number outer rings, number inner rings)
          */
         std::pair<size_t, size_t> num_rings() const {
diff --git a/include/osmium/osm/changeset.hpp b/include/osmium/osm/changeset.hpp
index 8a503ca..828a2c2 100644
--- a/include/osmium/osm/changeset.hpp
+++ b/include/osmium/osm/changeset.hpp
@@ -51,7 +51,7 @@ namespace osmium {
 
     namespace builder {
         class ChangesetDiscussionBuilder;
-        template <typename T> class ObjectBuilder;
+        class ChangesetBuilder;
     } // namespace builder
 
     class Changeset;
@@ -129,20 +129,12 @@ namespace osmium {
 
     class ChangesetDiscussion : public osmium::memory::Collection<ChangesetComment, osmium::item_type::changeset_discussion> {
 
-        friend class osmium::builder::ObjectBuilder<osmium::Changeset>;
-
     public:
 
-        using size_type = size_t;
-
         ChangesetDiscussion() :
             osmium::memory::Collection<ChangesetComment, osmium::item_type::changeset_discussion>() {
         }
 
-        size_type size() const noexcept {
-            return static_cast<size_type>(std::distance(begin(), end()));
-        }
-
     }; // class ChangesetDiscussion
 
     static_assert(sizeof(ChangesetDiscussion) % osmium::memory::align_bytes == 0, "Class osmium::ChangesetDiscussion has wrong size to be aligned properly!");
@@ -156,7 +148,7 @@ namespace osmium {
      */
     class Changeset : public osmium::OSMEntity {
 
-        friend class osmium::builder::ObjectBuilder<osmium::Changeset>;
+        friend class osmium::builder::ChangesetBuilder;
 
         osmium::Box       m_bounds;
         osmium::Timestamp m_created_at;
@@ -173,10 +165,14 @@ namespace osmium {
             OSMEntity(sizeof(Changeset), osmium::item_type::changeset) {
         }
 
-        void set_user_size(string_size_type size) {
+        void set_user_size(string_size_type size) noexcept {
             m_user_size = size;
         }
 
+        string_size_type user_size() const noexcept {
+            return m_user_size;
+        }
+
         unsigned char* subitems_position() {
             return data() + osmium::memory::padded_length(sizeof(Changeset) + m_user_size);
         }
diff --git a/include/osmium/osm/crc.hpp b/include/osmium/osm/crc.hpp
index 2abeac4..bf057fd 100644
--- a/include/osmium/osm/crc.hpp
+++ b/include/osmium/osm/crc.hpp
@@ -100,15 +100,15 @@ namespace osmium {
             return m_crc;
         }
 
-        void update_bool(const bool value) {
+        void update_bool(const bool value) noexcept {
             m_crc.process_byte(value);
         }
 
-        void update_int8(const uint8_t value) {
+        void update_int8(const uint8_t value) noexcept {
             m_crc.process_byte(value);
         }
 
-        void update_int16(const uint16_t value) {
+        void update_int16(const uint16_t value) noexcept {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
             m_crc.process_bytes(&value, sizeof(uint16_t));
 #else
@@ -117,7 +117,7 @@ namespace osmium {
 #endif
         }
 
-        void update_int32(const uint32_t value) {
+        void update_int32(const uint32_t value) noexcept {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
             m_crc.process_bytes(&value, sizeof(uint32_t));
 #else
@@ -126,7 +126,7 @@ namespace osmium {
 #endif
         }
 
-        void update_int64(const uint64_t value) {
+        void update_int64(const uint64_t value) noexcept {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
             m_crc.process_bytes(&value, sizeof(uint64_t));
 #else
@@ -135,57 +135,57 @@ namespace osmium {
 #endif
         }
 
-        void update_string(const char* str) {
+        void update_string(const char* str) noexcept {
             while (*str) {
                 m_crc.process_byte(*str++);
             }
         }
 
-        void update(const Timestamp& timestamp) {
+        void update(const Timestamp& timestamp) noexcept {
             update_int32(uint32_t(timestamp));
         }
 
-        void update(const osmium::Location& location) {
+        void update(const osmium::Location& location) noexcept {
             update_int32(location.x());
             update_int32(location.y());
         }
 
-        void update(const osmium::Box& box) {
+        void update(const osmium::Box& box) noexcept {
             update(box.bottom_left());
             update(box.top_right());
         }
 
-        void update(const NodeRef& node_ref) {
+        void update(const NodeRef& node_ref) noexcept {
             update_int64(node_ref.ref());
             update(node_ref.location());
         }
 
-        void update(const NodeRefList& node_refs) {
+        void update(const NodeRefList& node_refs) noexcept {
             for (const NodeRef& node_ref : node_refs) {
                 update(node_ref);
             }
         }
 
-        void update(const TagList& tags) {
+        void update(const TagList& tags) noexcept {
             for (const Tag& tag : tags) {
                 update_string(tag.key());
                 update_string(tag.value());
             }
         }
 
-        void update(const osmium::RelationMember& member) {
+        void update(const osmium::RelationMember& member) noexcept {
             update_int64(member.ref());
             update_int16(uint16_t(member.type()));
             update_string(member.role());
         }
 
-        void update(const osmium::RelationMemberList& members) {
+        void update(const osmium::RelationMemberList& members) noexcept {
             for (const RelationMember& member : members) {
                 update(member);
             }
         }
 
-        void update(const osmium::OSMObject& object) {
+        void update(const osmium::OSMObject& object) noexcept {
             update_int64(object.id());
             update_bool(object.visible());
             update_int32(object.version());
@@ -195,22 +195,22 @@ namespace osmium {
             update(object.tags());
         }
 
-        void update(const osmium::Node& node) {
+        void update(const osmium::Node& node) noexcept {
             update(static_cast<const osmium::OSMObject&>(node));
             update(node.location());
         }
 
-        void update(const osmium::Way& way) {
+        void update(const osmium::Way& way) noexcept {
             update(static_cast<const osmium::OSMObject&>(way));
             update(way.nodes());
         }
 
-        void update(const osmium::Relation& relation) {
+        void update(const osmium::Relation& relation) noexcept {
             update(static_cast<const osmium::OSMObject&>(relation));
             update(relation.members());
         }
 
-        void update(const osmium::Area& area) {
+        void update(const osmium::Area& area) noexcept {
             update(static_cast<const osmium::OSMObject&>(area));
             for (const auto& subitem : area) {
                 if (subitem.type() == osmium::item_type::outer_ring ||
@@ -220,7 +220,7 @@ namespace osmium {
             }
         }
 
-        void update(const osmium::ChangesetDiscussion& discussion) {
+        void update(const osmium::ChangesetDiscussion& discussion) noexcept {
             for (const auto& comment : discussion) {
                 update(comment.date());
                 update_int32(comment.uid());
@@ -229,7 +229,7 @@ namespace osmium {
             }
         }
 
-        void update(const osmium::Changeset& changeset) {
+        void update(const osmium::Changeset& changeset) noexcept {
             update_int64(changeset.id());
             update(changeset.created_at());
             update(changeset.closed_at());
diff --git a/include/osmium/osm/entity_bits.hpp b/include/osmium/osm/entity_bits.hpp
index b8e9ddb..05afe3b 100644
--- a/include/osmium/osm/entity_bits.hpp
+++ b/include/osmium/osm/entity_bits.hpp
@@ -60,7 +60,9 @@ namespace osmium {
          * assert(! (entities & osmium::osm_entity_bits::changeset));
          * @endcode
          */
-        enum type : unsigned char {
+        enum type : unsigned char { // this should have been an enum class
+                                    // but now we can't change it any more
+                                    // without breaking lots of code
 
             nothing    = 0x00,
             node       = 0x01,
@@ -75,21 +77,21 @@ namespace osmium {
 
         }; // enum type
 
-        inline type operator|(const type lhs, const type rhs) noexcept {
-            return static_cast<type>(static_cast<int>(lhs) | static_cast<int> (rhs));
+        inline constexpr type operator|(const type lhs, const type rhs) noexcept {
+            return static_cast<type>(static_cast<int>(lhs) | static_cast<int>(rhs));
         }
 
-        inline type& operator|=(type& lhs, const type rhs) noexcept {
-            lhs = lhs | rhs;
-            return lhs;
+        inline constexpr type operator&(const type lhs, const type rhs) noexcept {
+            return static_cast<type>(static_cast<int>(lhs) & static_cast<int>(rhs));
         }
 
-        inline type operator&(const type lhs, const type rhs) noexcept {
-            return static_cast<type>(static_cast<int>(lhs) & static_cast<int> (rhs));
+        inline constexpr type operator~(const type value) noexcept {
+            return all & static_cast<type>(~static_cast<int>(value));
         }
 
-        inline type operator~(const type value) noexcept {
-            return static_cast<type>(~static_cast<int>(value));
+        inline type& operator|=(type& lhs, const type rhs) noexcept {
+            lhs = lhs | rhs;
+            return lhs;
         }
 
         inline type operator&=(type& lhs, const type rhs) noexcept {
@@ -104,7 +106,7 @@ namespace osmium {
          *      changeset.
          */
         inline type from_item_type(osmium::item_type item_type) noexcept {
-            auto ut = static_cast<std::underlying_type<osmium::item_type>::type>(item_type);
+            const auto ut = static_cast<std::underlying_type<osmium::item_type>::type>(item_type);
             assert(ut <= 0x05);
             if (ut == 0) {
                 return nothing;
diff --git a/include/osmium/osm/location.hpp b/include/osmium/osm/location.hpp
index c5da620..d208717 100644
--- a/include/osmium/osm/location.hpp
+++ b/include/osmium/osm/location.hpp
@@ -86,23 +86,31 @@ namespace osmium {
                 ++str;
             }
 
-            // there has to be at least one digit
-            if (*str >= '0' && *str <= '9') {
-                result = *str - '0';
-                ++str;
-            } else {
-                goto error;
-            }
+            if (*str != '.') {
+                // there has to be at least one digit
+                if (*str >= '0' && *str <= '9') {
+                    result = *str - '0';
+                    ++str;
+                } else {
+                    goto error;
+                }
 
-            // optional additional digits before decimal point
-            while (*str >= '0' && *str <= '9' && max_digits > 0) {
-                result = result * 10 + (*str - '0');
-                ++str;
-                --max_digits;
-            }
+                // optional additional digits before decimal point
+                while (*str >= '0' && *str <= '9' && max_digits > 0) {
+                    result = result * 10 + (*str - '0');
+                    ++str;
+                    --max_digits;
+                }
 
-            if (max_digits == 0) {
-                goto error;
+                if (max_digits == 0) {
+                    goto error;
+                }
+            } else {
+                // need at least one digit after decimal dot if there was no
+                // digit before decimal dot
+                if (*(str + 1) < '0' || *(str + 1) > '9') {
+                    goto error;
+                }
             }
 
             // optional decimal point
@@ -163,18 +171,20 @@ namespace osmium {
             }
 
             if (scale < 0) {
-                result = 0;
+                for (; scale < 0 && result > 0; ++scale) {
+                    result /= 10;
+                }
             } else {
                 for (; scale > 0; --scale) {
                     result *= 10;
                 }
+            }
 
-                result = (result + 5) / 10 * sign;
+            result = (result + 5) / 10 * sign;
 
-                if (result > std::numeric_limits<int32_t>::max() ||
-                    result < std::numeric_limits<int32_t>::min()) {
-                    goto error;
-                }
+            if (result > std::numeric_limits<int32_t>::max() ||
+                result < std::numeric_limits<int32_t>::min()) {
+                goto error;
             }
 
             *data = str;
diff --git a/include/osmium/osm/node.hpp b/include/osmium/osm/node.hpp
index 677ffc7..f3df5e9 100644
--- a/include/osmium/osm/node.hpp
+++ b/include/osmium/osm/node.hpp
@@ -41,12 +41,14 @@ DEALINGS IN THE SOFTWARE.
 namespace osmium {
 
     namespace builder {
-        template <typename T> class ObjectBuilder;
+        template <typename TDerived, typename T>
+        class OSMObjectBuilder;
     } // namespace builder
 
     class Node : public OSMObject {
 
-        friend class osmium::builder::ObjectBuilder<osmium::Node>;
+        template <typename TDerived, typename T>
+        friend class osmium::builder::OSMObjectBuilder;
 
         osmium::Location m_location;
 
@@ -62,7 +64,7 @@ namespace osmium {
             return m_location;
         }
 
-        Node& set_location(const osmium::Location& location) {
+        Node& set_location(const osmium::Location& location) noexcept {
             m_location = location;
             return *this;
         }
diff --git a/include/osmium/osm/node_ref_list.hpp b/include/osmium/osm/node_ref_list.hpp
index 6cfdf22..f430b63 100644
--- a/include/osmium/osm/node_ref_list.hpp
+++ b/include/osmium/osm/node_ref_list.hpp
@@ -52,12 +52,23 @@ namespace osmium {
 
     public:
 
+        using value_type             = NodeRef;
+        using reference              = NodeRef&;
+        using const_reference        = const NodeRef&;
+        using iterator               = NodeRef*;
+        using const_iterator         = const NodeRef*;
+        using const_reverse_iterator = std::reverse_iterator<const NodeRef*>;
+        using difference_type        = std::ptrdiff_t;
+        using size_type              = std::size_t;
+
         explicit NodeRefList(osmium::item_type itemtype) noexcept :
             osmium::memory::Item(sizeof(NodeRefList), itemtype) {
         }
 
         /**
          * Checks whether the collection is empty.
+         *
+         * Complexity: Constant.
          */
         bool empty() const noexcept {
             return sizeof(NodeRefList) == byte_size();
@@ -65,8 +76,10 @@ namespace osmium {
 
         /**
          * Returns the number of NodeRefs in the collection.
+         *
+         * Complexity: Constant.
          */
-        size_t size() const noexcept {
+        size_type size() const noexcept {
             const auto size_node_refs = byte_size() - sizeof(NodeRefList);
             assert(size_node_refs % sizeof(NodeRef) == 0);
             return size_node_refs / sizeof(NodeRef);
@@ -75,11 +88,13 @@ namespace osmium {
         /**
          * Access specified element.
          *
+         * Complexity: Constant.
+         *
          * @pre @code n < size() @endcode
          *
          * @param n Get the n-th element of the collection.
          */
-        const NodeRef& operator[](size_t n) const noexcept {
+        const NodeRef& operator[](size_type n) const noexcept {
             assert(n < size());
             const NodeRef* node_ref = &*(cbegin());
             return node_ref[n];
@@ -88,6 +103,8 @@ namespace osmium {
         /**
          * Access the first element.
          *
+         * Complexity: Constant.
+         *
          * @pre @code !empty() @endcode
          */
         const NodeRef& front() const noexcept {
@@ -98,6 +115,8 @@ namespace osmium {
         /**
          * Access the last element.
          *
+         * Complexity: Constant.
+         *
          * @pre @code !empty() @endcode
          */
         const NodeRef& back() const noexcept {
@@ -109,6 +128,8 @@ namespace osmium {
          * Checks whether the first and last node in the collection have the
          * same ID. The locations are not checked.
          *
+         * Complexity: Constant.
+         *
          * @pre @code !empty() @endcode
          */
         bool is_closed() const noexcept {
@@ -119,6 +140,8 @@ namespace osmium {
          * Checks whether the first and last node in the collection have the
          * same ID. The locations are not checked.
          *
+         * Complexity: Constant.
+         *
          * @pre @code !empty() @endcode
          */
         bool ends_have_same_id() const noexcept {
@@ -129,6 +152,8 @@ namespace osmium {
          * Checks whether the first and last node in the collection have the
          * same location. The IDs are not checked.
          *
+         * Complexity: Constant.
+         *
          * @pre @code !empty() @endcode
          * @pre @code front().location() && back().location() @endcode
          */
@@ -137,10 +162,6 @@ namespace osmium {
             return front().location() == back().location();
         }
 
-        using iterator = NodeRef*;
-        using const_iterator = const NodeRef*;
-        using const_reverse_iterator = std::reverse_iterator<const NodeRef*>;
-
         /// Returns an iterator to the beginning.
         iterator begin() noexcept {
             return iterator(data() + sizeof(NodeRefList));
diff --git a/include/osmium/osm/object.hpp b/include/osmium/osm/object.hpp
index caa6fbc..01fe249 100644
--- a/include/osmium/osm/object.hpp
+++ b/include/osmium/osm/object.hpp
@@ -52,11 +52,19 @@ DEALINGS IN THE SOFTWARE.
 
 namespace osmium {
 
+    namespace builder {
+        template <typename TDerived, typename T>
+        class OSMObjectBuilder;
+    } // namespace builder
+
     /**
      * OSMObject (Node, Way, Relation, or Area).
      */
     class OSMObject : public osmium::OSMEntity {
 
+        template <typename TDerived, typename T>
+        friend class osmium::builder::OSMObjectBuilder;
+
         object_id_type      m_id;
         bool                m_deleted : 1;
         object_version_type m_version : 31;
diff --git a/include/osmium/osm/relation.hpp b/include/osmium/osm/relation.hpp
index 2aa9caa..8c09680 100644
--- a/include/osmium/osm/relation.hpp
+++ b/include/osmium/osm/relation.hpp
@@ -43,11 +43,14 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/object.hpp>
 #include <osmium/osm/types.hpp>
+#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
     namespace builder {
-        template <typename> class ObjectBuilder;
+        template <typename TDerived, typename T>
+        class OSMObjectBuilder;
+
         class RelationMemberListBuilder;
     } // namespace builder
 
@@ -109,7 +112,8 @@ namespace osmium {
             return m_ref;
         }
 
-        RelationMember& ref(object_id_type ref) noexcept {
+        /// @deprecated Use set_ref() instead.
+        OSMIUM_DEPRECATED RelationMember& ref(object_id_type ref) noexcept {
             m_ref = ref;
             return *this;
         }
@@ -149,23 +153,18 @@ namespace osmium {
 
     public:
 
-        using size_type = size_t;
-
         RelationMemberList() :
             osmium::memory::Collection<RelationMember, osmium::item_type::relation_member_list>() {
         }
 
-        size_type size() const noexcept {
-            return static_cast<size_type>(std::distance(begin(), end()));
-        }
-
     }; // class RelationMemberList
 
     static_assert(sizeof(RelationMemberList) % osmium::memory::align_bytes == 0, "Class osmium::RelationMemberList has wrong size to be aligned properly!");
 
     class Relation : public OSMObject {
 
-        friend class osmium::builder::ObjectBuilder<osmium::Relation>;
+        template <typename TDerived, typename T>
+        friend class osmium::builder::OSMObjectBuilder;
 
         Relation() noexcept :
             OSMObject(sizeof(Relation), osmium::item_type::relation) {
diff --git a/include/osmium/osm/tag.hpp b/include/osmium/osm/tag.hpp
index cd2a913..e2537ce 100644
--- a/include/osmium/osm/tag.hpp
+++ b/include/osmium/osm/tag.hpp
@@ -86,12 +86,14 @@ namespace osmium {
 
     }; // class Tag
 
-    inline bool operator==(const Tag& a, const Tag& b) {
-        return !std::strcmp(a.key(), b.key()) && !std::strcmp(a.value(), b.value());
+    inline bool operator==(const Tag& lhs, const Tag& rhs) {
+        return !std::strcmp(lhs.key(), rhs.key()) &&
+               !std::strcmp(lhs.value(), rhs.value());
     }
 
-    inline bool operator<(const Tag& a, const Tag& b) {
-        return (!std::strcmp(a.key(), b.key()) && (std::strcmp(a.value(), b.value()) < 0)) || (std::strcmp(a.key(), b.key()) < 0);
+    inline bool operator<(const Tag& lhs, const Tag& rhs) {
+        const auto c = std::strcmp(lhs.key(), rhs.key());
+        return (c == 0 ? std::strcmp(lhs.value(), rhs.value()) : c) < 0;
     }
 
     /**
@@ -112,20 +114,11 @@ namespace osmium {
 
     public:
 
-        using size_type = size_t;
-
         TagList() :
             osmium::memory::Collection<Tag, osmium::item_type::tag_list>() {
         }
 
         /**
-         * Returns the number of tags in this tag list.
-         */
-        size_type size() const noexcept {
-            return static_cast<size_type>(std::distance(begin(), end()));
-        }
-
-        /**
          * Get tag value for the given tag key. If the key is not set, returns
          * the default_value.
          *
diff --git a/include/osmium/osm/way.hpp b/include/osmium/osm/way.hpp
index f6713fe..e4415ef 100644
--- a/include/osmium/osm/way.hpp
+++ b/include/osmium/osm/way.hpp
@@ -44,7 +44,8 @@ DEALINGS IN THE SOFTWARE.
 namespace osmium {
 
     namespace builder {
-        template <typename T> class ObjectBuilder;
+        template <typename TDerived, typename T>
+        class OSMObjectBuilder;
     } // namespace builder
 
     /**
@@ -66,7 +67,8 @@ namespace osmium {
 
     class Way : public OSMObject {
 
-        friend class osmium::builder::ObjectBuilder<osmium::Way>;
+        template <typename TDerived, typename T>
+        friend class osmium::builder::OSMObjectBuilder;
 
         Way() noexcept :
             OSMObject(sizeof(Way), osmium::item_type::way) {
diff --git a/include/osmium/relations/collector.hpp b/include/osmium/relations/collector.hpp
index b8455b4..53f6de9 100644
--- a/include/osmium/relations/collector.hpp
+++ b/include/osmium/relations/collector.hpp
@@ -40,6 +40,7 @@ DEALINGS IN THE SOFTWARE.
 #include <functional>
 #include <iomanip>
 #include <iostream>
+#include <utility>
 #include <vector>
 
 #include <osmium/osm/item_type.hpp>
@@ -353,7 +354,7 @@ namespace osmium {
                         member_meta(member.type()).emplace_back(member.ref(), m_relations.size(), n);
                         relation_meta.increment_need_members();
                     } else {
-                        member.ref(0); // set member id to zero to indicate we are not interested
+                        member.set_ref(0); // set member id to zero to indicate we are not interested
                     }
                     ++n;
                 }
@@ -494,12 +495,65 @@ namespace osmium {
                 return m_members_buffer;
             }
 
+            /**
+             * Is the given member available in the members buffer?
+             *
+             * If you also need the offset of the object, use
+             * get_availability_and_offset() instead, it is more efficient
+             * that way.
+             *
+             * @param type Item type
+             * @param id Object Id
+             * @returns True if the object is available, false otherwise.
+             */
+            bool is_available(osmium::item_type type, osmium::object_id_type id) {
+                const auto range = find_member_meta(type, id);
+                assert(!range.empty());
+                return range.begin()->is_available();
+            }
+
+            /**
+             * Get offset of a member in the members buffer.
+             *
+             * @pre The member must be available. If you are not sure, call
+             *      get_availability_and_offset() instead.
+             * @param type Item type
+             * @param id Object Id
+             * @returns The offset of the object in the members buffer.
+             */
             size_t get_offset(osmium::item_type type, osmium::object_id_type id) {
                 const auto range = find_member_meta(type, id);
                 assert(!range.empty());
+                assert(range.begin()->is_available());
                 return range.begin()->buffer_offset();
             }
 
+            /**
+             * Checks whether a member is available in the members buffer
+             * and returns its offset.
+             *
+             * If the member is not available, the boolean returned as the
+             * first element in the pair is false. In that case the offset
+             * in the second element is undefined.
+             *
+             * If the member is available, the boolean returned as the first
+             * element in the pair is true and the second element of the
+             * pair contains the offset into the members buffer.
+             *
+             * @param type Item type
+             * @param id Object Id
+             * @returns Pair of bool (showing availability) and the offset.
+             */
+            std::pair<bool, size_t> get_availability_and_offset(osmium::item_type type, osmium::object_id_type id) {
+                const auto range = find_member_meta(type, id);
+                assert(!range.empty());
+                if (range.begin()->is_available()) {
+                    return std::make_pair(true, range.begin()->buffer_offset());
+                } else {
+                    return std::make_pair(false, 0);
+                }
+            }
+
             template <typename TIter>
             void read_relations(TIter begin, TIter end) {
                 HandlerPass1 handler(*static_cast<TCollector*>(this));
@@ -525,7 +579,7 @@ namespace osmium {
             /**
              * Decide whether to purge removed members and then do it.
              *
-             * Currently the purging is done every thousand calls.
+             * Currently the purging is done every 10000 calls.
              * This could probably be improved upon.
              */
             void possibly_purge_removed_members() {
diff --git a/include/osmium/relations/detail/member_meta.hpp b/include/osmium/relations/detail/member_meta.hpp
index b28dca1..7624a60 100644
--- a/include/osmium/relations/detail/member_meta.hpp
+++ b/include/osmium/relations/detail/member_meta.hpp
@@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cstddef>
 #include <iosfwd>
+#include <limits>
 
 #include <osmium/osm/types.hpp>
 
@@ -71,24 +72,44 @@ namespace osmium {
 
             /**
              * Offset in the buffer where the object is stored.
+             *
+             * The default value is one that will never be valid, so it is
+             * easier to catch problems.
              */
-            size_t m_buffer_offset { 0 };
+            size_t m_buffer_offset = std::numeric_limits<size_t>::max();
 
+            /**
+             * Has this member been found in the input data.
+             */
+            bool m_available = false;
+
+            /**
+             * Marks this member as removed. It can not be used any more.
+             */
             bool m_removed = false;
 
         public:
 
             /**
-             * Create new MemberMeta. The variant with zeros for relation_pos and
-             * member_pos is used to create dummy MemberMeta that can be compared
-             * to the MemberMeta in the vectors using the equal_range algorithm.
+             * Create new MemberMeta.
              */
-            explicit MemberMeta(osmium::object_id_type member_id, size_t relation_pos=0, size_t member_pos=0) noexcept :
+            explicit MemberMeta(osmium::object_id_type member_id, size_t relation_pos, size_t member_pos) noexcept :
                 m_member_id(member_id),
                 m_relation_pos(relation_pos),
                 m_member_pos(member_pos) {
             }
 
+            /**
+             * Create new MemberMeta. This constructor is used to create
+             * dummy MemberMeta objects that can be compared to the
+             * MemberMetas in a vector using the equal_range algorithm.
+             */
+            explicit MemberMeta(osmium::object_id_type member_id) noexcept :
+                m_member_id(member_id),
+                m_relation_pos(0),
+                m_member_pos(0) {
+            }
+
             osmium::object_id_type member_id() const noexcept {
                 return m_member_id;
             }
@@ -107,6 +128,11 @@ namespace osmium {
 
             void set_buffer_offset(size_t offset) noexcept {
                 m_buffer_offset = offset;
+                m_available = true;
+            }
+
+            bool is_available() const noexcept {
+                return m_available;
             }
 
             bool removed() const noexcept {
@@ -124,8 +150,8 @@ namespace osmium {
          * Used to sort a vector of MemberMeta objects and to later find
          * them using binary search.
          */
-        inline bool operator<(const MemberMeta& a, const MemberMeta& b) noexcept {
-            return a.member_id() < b.member_id();
+        inline bool operator<(const MemberMeta& lhs, const MemberMeta& rhs) noexcept {
+            return lhs.member_id() < rhs.member_id();
         }
 
         template <typename TChar, typename TTraits>
diff --git a/include/osmium/thread/queue.hpp b/include/osmium/thread/queue.hpp
index 6f4f7b1..5ae9108 100644
--- a/include/osmium/thread/queue.hpp
+++ b/include/osmium/thread/queue.hpp
@@ -51,7 +51,7 @@ namespace osmium {
 
     namespace thread {
 
-        static const std::chrono::milliseconds full_queue_sleep_duration { 10 }; // XXX
+        static const std::chrono::milliseconds max_wait{10};
 
         /**
          *  A thread-safe queue.
@@ -70,9 +70,12 @@ namespace osmium {
 
             std::queue<T> m_queue;
 
-            /// Used to signal readers when data is available in the queue.
+            /// Used to signal consumers when data is available in the queue.
             std::condition_variable m_data_available;
 
+            /// Used to signal producers when queue is not full.
+            std::condition_variable m_space_available;
+
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
             /// The largest size the queue has been so far.
             size_t m_largest_size;
@@ -109,7 +112,8 @@ namespace osmium {
                 m_name(name),
                 m_mutex(),
                 m_queue(),
-                m_data_available()
+                m_data_available(),
+                m_space_available()
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
                 ,
                 m_largest_size(0),
@@ -123,13 +127,20 @@ namespace osmium {
 
             ~Queue() {
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
-                std::cerr << "queue '" << m_name << "' with max_size=" << m_max_size << " had largest size " << m_largest_size << " and was full " << m_full_counter << " times in " << m_push_counter << " push() calls and was empty " << m_empty_counter << " times in " << m_pop_counter << " pop() calls\n";
+                std::cerr << "queue '" << m_name
+                          << "' with max_size=" << m_max_size
+                          << " had largest size " << m_largest_size
+                          << " and was full " << m_full_counter
+                          << " times in " << m_push_counter
+                          << " push() calls and was empty " << m_empty_counter
+                          << " times in " << m_pop_counter
+                          << " pop() calls\n";
 #endif
             }
 
             /**
-             * Push an element onto the queue. If the queue has a max size, this
-             * call will block if the queue is full.
+             * Push an element onto the queue. If the queue has a max size,
+             * this call will block if the queue is full.
              */
             void push(T value) {
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
@@ -137,13 +148,16 @@ namespace osmium {
 #endif
                 if (m_max_size) {
                     while (size() >= m_max_size) {
-                        std::this_thread::sleep_for(full_queue_sleep_duration);
+                        std::unique_lock<std::mutex> lock{m_mutex};
+                        m_space_available.wait_for(lock, max_wait, [this] {
+                            return m_queue.size() < m_max_size;
+                        });
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
                         ++m_full_counter;
 #endif
                     }
                 }
-                std::lock_guard<std::mutex> lock(m_mutex);
+                std::lock_guard<std::mutex> lock{m_mutex};
                 m_queue.push(std::move(value));
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
                 if (m_largest_size < m_queue.size()) {
@@ -157,7 +171,7 @@ namespace osmium {
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
                 ++m_pop_counter;
 #endif
-                std::unique_lock<std::mutex> lock(m_mutex);
+                std::unique_lock<std::mutex> lock{m_mutex};
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
                 if (m_queue.empty()) {
                     ++m_empty_counter;
@@ -169,6 +183,10 @@ namespace osmium {
                 if (!m_queue.empty()) {
                     value = std::move(m_queue.front());
                     m_queue.pop();
+                    lock.unlock();
+                    if (m_max_size) {
+                        m_space_available.notify_one();
+                    }
                 }
             }
 
@@ -176,25 +194,30 @@ namespace osmium {
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
                 ++m_pop_counter;
 #endif
-                std::lock_guard<std::mutex> lock(m_mutex);
-                if (m_queue.empty()) {
+                {
+                    std::lock_guard<std::mutex> lock{m_mutex};
+                    if (m_queue.empty()) {
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
-                    ++m_empty_counter;
+                        ++m_empty_counter;
 #endif
-                    return false;
+                        return false;
+                    }
+                    value = std::move(m_queue.front());
+                    m_queue.pop();
+                }
+                if (m_max_size) {
+                    m_space_available.notify_one();
                 }
-                value = std::move(m_queue.front());
-                m_queue.pop();
                 return true;
             }
 
             bool empty() const {
-                std::lock_guard<std::mutex> lock(m_mutex);
+                std::lock_guard<std::mutex> lock{m_mutex};
                 return m_queue.empty();
             }
 
             size_t size() const {
-                std::lock_guard<std::mutex> lock(m_mutex);
+                std::lock_guard<std::mutex> lock{m_mutex};
                 return m_queue.size();
             }
 
diff --git a/include/osmium/util/progress_bar.hpp b/include/osmium/util/progress_bar.hpp
index 814aa2c..0e528fc 100644
--- a/include/osmium/util/progress_bar.hpp
+++ b/include/osmium/util/progress_bar.hpp
@@ -172,6 +172,18 @@ namespace osmium {
             }
         }
 
+        /**
+         * Removes the progress bar. Call this before doing any other output.
+         * The next time update() is called, the progress bar will be visible
+         * again.
+         */
+        void remove() {
+            if (m_enable) {
+                std::cerr << spc() << "         \r";
+                m_prev_percent = 100 + 1;
+            }
+        }
+
     }; // class ProgressBar
 
 } // namespace osmium
diff --git a/include/osmium/version.hpp b/include/osmium/version.hpp
index 6f3b0a3..09c5762 100644
--- a/include/osmium/version.hpp
+++ b/include/osmium/version.hpp
@@ -34,9 +34,9 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #define LIBOSMIUM_VERSION_MAJOR 2
-#define LIBOSMIUM_VERSION_MINOR 9
+#define LIBOSMIUM_VERSION_MINOR 10
 #define LIBOSMIUM_VERSION_PATCH 0
 
-#define LIBOSMIUM_VERSION_STRING "2.9.0"
+#define LIBOSMIUM_VERSION_STRING "2.10.0"
 
 #endif // OSMIUM_VERSION_HPP
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6230cde..051574e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -112,12 +112,6 @@ if(NOT Threads_FOUND)
     set(Threads_FOUND FALSE)
 endif()
 
-if(GEOS_FOUND AND PROJ_FOUND)
-    set(GEOS_AND_PROJ_FOUND TRUE)
-else()
-    set(GEOS_AND_PROJ_FOUND FALSE)
-endif()
-
 
 #-----------------------------------------------------------------------------
 #
@@ -127,45 +121,45 @@ endif()
 add_unit_test(area test_area_id)
 add_unit_test(area test_node_ref_segment)
 
-add_unit_test(basic test_area)
-add_unit_test(basic test_box)
-add_unit_test(basic test_changeset)
-add_unit_test(basic test_crc)
-add_unit_test(basic test_entity_bits)
-add_unit_test(basic test_location)
-add_unit_test(basic test_node)
-add_unit_test(basic test_node_ref)
-add_unit_test(basic test_object_comparisons)
-add_unit_test(basic test_relation)
-add_unit_test(basic test_timestamp)
-add_unit_test(basic test_types_from_string)
-add_unit_test(basic test_way)
-
-add_unit_test(buffer test_buffer_basics)
-add_unit_test(buffer test_buffer_node)
-add_unit_test(buffer test_buffer_purge)
+add_unit_test(osm test_area)
+add_unit_test(osm test_box)
+add_unit_test(osm test_changeset)
+add_unit_test(osm test_crc)
+add_unit_test(osm test_entity_bits)
+add_unit_test(osm test_location)
+add_unit_test(osm test_node)
+add_unit_test(osm test_node_ref)
+add_unit_test(osm test_object_comparisons)
+add_unit_test(osm test_relation)
+add_unit_test(osm test_timestamp)
+add_unit_test(osm test_types_from_string)
+add_unit_test(osm test_way)
+
+add_unit_test(memory test_buffer_basics)
+add_unit_test(memory test_buffer_node)
+add_unit_test(memory test_buffer_purge)
 
 add_unit_test(builder test_attr)
-
-add_unit_test(geom test_factory_with_projection
-    ENABLE_IF ${GEOS_AND_PROJ_FOUND}
-    LIBS ${GEOS_LIBRARY} ${PROJ_LIBRARY})
+add_unit_test(builder test_object_builder)
 
 add_unit_test(geom test_crs ENABLE_IF ${PROJ_FOUND} LIBS ${PROJ_LIBRARY})
 add_unit_test(geom test_exception)
+add_unit_test(geom test_factory_with_projection ENABLE_IF ${PROJ_FOUND} LIBS ${PROJ_LIBRARY})
 add_unit_test(geom test_geojson)
 add_unit_test(geom test_geos ENABLE_IF ${GEOS_FOUND} LIBS ${GEOS_LIBRARY})
-add_unit_test(geom test_geos_wkb ENABLE_IF ${GEOS_FOUND} LIBS ${GEOS_LIBRARY})
 add_unit_test(geom test_mercator)
 add_unit_test(geom test_ogr ENABLE_IF ${GDAL_FOUND} LIBS ${GDAL_LIBRARY})
+add_unit_test(geom test_ogr_wkb ENABLE_IF ${GDAL_FOUND} LIBS ${GDAL_LIBRARY})
 add_unit_test(geom test_projection ENABLE_IF ${PROJ_FOUND} LIBS ${PROJ_LIBRARY})
-add_unit_test(geom test_tile ENABLE_IF ${GEOS_FOUND})
+add_unit_test(geom test_tile)
 add_unit_test(geom test_wkb)
 add_unit_test(geom test_wkt)
 
+add_unit_test(index test_id_set)
 add_unit_test(index test_id_to_location ENABLE_IF ${SPARSEHASH_FOUND})
 add_unit_test(index test_file_based_index)
 
+add_unit_test(io test_compression_factory)
 add_unit_test(io test_bzip2 ENABLE_IF ${BZIP2_FOUND} LIBS ${BZIP2_LIBRARIES})
 add_unit_test(io test_file_formats)
 add_unit_test(io test_reader LIBS "${OSMIUM_XML_LIBRARIES};${OSMIUM_PBF_LIBRARIES}")
diff --git a/test/data-tests/testdata-xml.cpp b/test/data-tests/testdata-xml.cpp
index 0d2739a..eb984f8 100644
--- a/test/data-tests/testdata-xml.cpp
+++ b/test/data-tests/testdata-xml.cpp
@@ -83,7 +83,7 @@ header_buffer_type parse_xml(std::string input) {
     osmium::io::detail::add_to_queue(input_queue, std::move(input));
     osmium::io::detail::add_to_queue(input_queue, std::string{});
 
-    osmium::io::detail::XMLParser parser{input_queue, output_queue, header_promise, osmium::osm_entity_bits::all};
+    osmium::io::detail::XMLParser parser{input_queue, output_queue, header_promise, osmium::io::detail::reader_options{}};
     parser.parse();
 
     header_buffer_type result;
diff --git a/test/examples/CMakeLists.txt b/test/examples/CMakeLists.txt
new file mode 100644
index 0000000..90bf76e
--- /dev/null
+++ b/test/examples/CMakeLists.txt
@@ -0,0 +1,21 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake Config
+#
+#  Libosmium example tests
+#
+#-----------------------------------------------------------------------------
+
+message(STATUS "Configuring example tests")
+
+file(GLOB _dirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/t/*)
+
+foreach(_dir ${_dirs})
+    message(STATUS "  adding test: ${_dir}")
+    add_subdirectory("${_dir}")
+endforeach()
+
+message(STATUS "Configuring example tests - done")
+
+
+#-----------------------------------------------------------------------------
diff --git a/test/examples/t/pub_names/CMakeLists.txt b/test/examples/t/pub_names/CMakeLists.txt
new file mode 100644
index 0000000..9a68ae8
--- /dev/null
+++ b/test/examples/t/pub_names/CMakeLists.txt
@@ -0,0 +1,7 @@
+
+add_test(NAME examples_pub_names
+         COMMAND osmium_pub_names ${CMAKE_CURRENT_SOURCE_DIR}/pubs.osm)
+
+set_tests_properties(examples_pub_names PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Im Holze\n$")
+
diff --git a/test/examples/t/pub_names/pubs.osm b/test/examples/t/pub_names/pubs.osm
new file mode 100644
index 0000000..ee54226
--- /dev/null
+++ b/test/examples/t/pub_names/pubs.osm
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6">
+  <node id="167199652" version="3" timestamp="2010-12-27T13:15:02Z" uid="57645" user="KartoGrapHiti" changeset="6777507" lat="53.0526516" lon="8.8919477">
+    <tag k="amenity" v="pub"/>
+    <tag k="name" v="Im Holze"/>
+  </node>
+</osm>
diff --git a/test/examples/t/road_length/CMakeLists.txt b/test/examples/t/road_length/CMakeLists.txt
new file mode 100644
index 0000000..6323f07
--- /dev/null
+++ b/test/examples/t/road_length/CMakeLists.txt
@@ -0,0 +1,8 @@
+
+add_test(NAME examples_road_length
+         COMMAND osmium_road_length ${CMAKE_CURRENT_SOURCE_DIR}/road.osm)
+
+set_tests_properties(examples_road_length PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Length: 0\\.405.*km\n$"
+)
+
diff --git a/test/examples/t/road_length/road.osm b/test/examples/t/road_length/road.osm
new file mode 100644
index 0000000..934ef46
--- /dev/null
+++ b/test/examples/t/road_length/road.osm
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6" generator="CGImap 0.3.3 (31041 thorn-01.openstreetmap.org)" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
+ <node id="5599384" visible="true" version="5" changeset="829716" timestamp="2009-03-18T17:16:26Z" user="burts" uid="97529" lat="51.0271601" lon="13.7252197"/>
+ <node id="250384970" visible="true" version="3" changeset="855544" timestamp="2009-01-30T21:58:37Z" user="saftl" uid="7989" lat="51.0288568" lon="13.7248159">
+  <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="250996316" visible="true" version="5" changeset="838711" timestamp="2009-03-21T13:10:39Z" user="burts" uid="97529" lat="51.0274683" lon="13.7251464"/>
+ <node id="250996321" visible="true" version="3" changeset="855544" timestamp="2009-01-30T21:59:14Z" user="saftl" uid="7989" lat="51.0284283" lon="13.7249179">
+  <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="252587568" visible="true" version="3" changeset="855544" timestamp="2009-01-30T21:58:45Z" user="saftl" uid="7989" lat="51.0275477" lon="13.7251275">
+  <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="5599381" visible="true" version="6" changeset="15768585" timestamp="2013-04-18T01:20:26Z" user="Wolle DD" uid="1161559" lat="51.0307642" lon="13.7243263"/>
+ <node id="1122039499" visible="true" version="2" changeset="26063898" timestamp="2014-10-14T04:09:11Z" user="Seandebasti" uid="550560" lat="51.0285248" lon="13.7248970"/>
+ <node id="1122039521" visible="true" version="2" changeset="12753110" timestamp="2012-08-16T17:03:47Z" user="TEAM_CN_TUD" uid="716608" lat="51.0277456" lon="13.7250806"/>
+ <node id="5599382" visible="true" version="6" changeset="8191054" timestamp="2011-05-19T16:19:51Z" user="stw1701" uid="102899" lat="51.0297991" lon="13.7245892"/>
+ <node id="1299329303" visible="true" version="1" changeset="8242335" timestamp="2011-05-25T07:46:01Z" user="bigbug21" uid="15748" lat="51.0290875" lon="13.7247628"/>
+ <node id="1868844753" visible="true" version="2" changeset="13260925" timestamp="2012-09-26T15:26:31Z" user="TEAM_CN_TUD" uid="716608" lat="51.0289617" lon="13.7247917"/>
+ <node id="1868844765" visible="true" version="2" changeset="13260925" timestamp="2012-09-26T15:26:31Z" user="TEAM_CN_TUD" uid="716608" lat="51.0292872" lon="13.7247140"/>
+ <node id="1868844782" visible="true" version="2" changeset="13260925" timestamp="2012-09-26T15:26:31Z" user="TEAM_CN_TUD" uid="716608" lat="51.0295717" lon="13.7246429"/>
+ <node id="1922091528" visible="true" version="2" changeset="13260925" timestamp="2012-09-26T15:26:31Z" user="TEAM_CN_TUD" uid="716608" lat="51.0281700" lon="13.7249813"/>
+ <node id="1953249124" visible="true" version="1" changeset="13418280" timestamp="2012-10-08T20:18:35Z" user="TEAM_CN_TUD" uid="716608" lat="51.0292437" lon="13.7247246"/>
+ <node id="2015120752" visible="true" version="1" changeset="13883494" timestamp="2012-11-15T14:22:31Z" user="TEAM_CN_TUD" uid="716608" lat="51.0293536" lon="13.7246974"/>
+ <node id="2056871900" visible="true" version="1" changeset="14207491" timestamp="2012-12-09T00:03:11Z" user="bigbug21" uid="15748" lat="51.0305821" lon="13.7243895"/>
+ <node id="2458246647" visible="true" version="1" changeset="17836591" timestamp="2013-09-14T17:05:06Z" user="bigbug21" uid="15748" lat="51.0295979" lon="13.7246367"/>
+ <node id="250384969" visible="true" version="4" changeset="17857555" timestamp="2013-09-15T20:17:55Z" user="4b696d" uid="1420318" lat="51.0282021" lon="13.7249734"/>
+ <node id="3128723784" visible="true" version="1" changeset="26063898" timestamp="2014-10-14T04:09:07Z" user="Seandebasti" uid="550560" lat="51.0281018" lon="13.7249973"/>
+ <way id="4428564" visible="true" version="21" changeset="26063898" timestamp="2014-10-14T04:09:13Z" user="Seandebasti" uid="550560">
+  <nd ref="5599381"/>
+  <nd ref="2056871900"/>
+  <nd ref="5599382"/>
+  <nd ref="2458246647"/>
+  <nd ref="1868844782"/>
+  <nd ref="2015120752"/>
+  <nd ref="1868844765"/>
+  <nd ref="1953249124"/>
+  <nd ref="1299329303"/>
+  <nd ref="1868844753"/>
+  <nd ref="250384970"/>
+  <nd ref="1122039499"/>
+  <nd ref="250996321"/>
+  <nd ref="250384969"/>
+  <nd ref="1922091528"/>
+  <nd ref="3128723784"/>
+  <nd ref="1122039521"/>
+  <nd ref="252587568"/>
+  <nd ref="250996316"/>
+  <nd ref="5599384"/>
+  <tag k="highway" v="residential"/>
+  <tag k="lit" v="yes"/>
+  <tag k="maxspeed" v="30"/>
+  <tag k="name" v="Helmholtzstraße"/>
+  <tag k="postal_code" v="01069"/>
+  <tag k="sidewalk" v="both"/>
+  <tag k="smoothness" v="good"/>
+  <tag k="surface" v="asphalt"/>
+ </way>
+</osm>
\ No newline at end of file
diff --git a/test/include/catch.hpp b/test/include/catch.hpp
index 879fc5b..2e6fe8d 100644
--- a/test/include/catch.hpp
+++ b/test/include/catch.hpp
@@ -1,6 +1,6 @@
 /*
- *  Catch v1.5.6
- *  Generated: 2016-06-09 19:20:41.460328
+ *  Catch v1.5.8
+ *  Generated: 2016-10-26 12:07:30.938259
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
  *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
@@ -3223,10 +3223,11 @@ namespace Catch {
 
             bool matches( TestCaseInfo const& testCase ) const {
                 // All patterns in a filter must match for the filter to be a match
-                for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it )
+                for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) {
                     if( !(*it)->matches( testCase ) )
                         return false;
-                    return true;
+                }
+                return true;
             }
         };
 
@@ -4719,8 +4720,11 @@ namespace Catch {
         std::string line;
         while( std::getline( f, line ) ) {
             line = trim(line);
-            if( !line.empty() && !startsWith( line, "#" ) )
-                addTestOrTags( config, "\"" + line + "\"," );
+            if( !line.empty() && !startsWith( line, "#" ) ) {
+                if( !startsWith( line, "\"" ) )
+                    line = "\"" + line + "\"";
+                addTestOrTags( config, line + "," );
+            }
         }
     }
 
@@ -5368,7 +5372,10 @@ namespace Catch {
                 ++it ) {
             matchedTests++;
             TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
-            Catch::cout() << testCaseInfo.name << std::endl;
+            if( startsWith( testCaseInfo.name, "#" ) )
+               Catch::cout() << "\"" << testCaseInfo.name << "\"" << std::endl;
+            else
+               Catch::cout() << testCaseInfo.name << std::endl;
         }
         return matchedTests;
     }
@@ -6454,7 +6461,7 @@ namespace Catch {
 namespace Catch {
 
     struct RandomNumberGenerator {
-        typedef int result_type;
+        typedef std::ptrdiff_t result_type;
 
         result_type operator()( result_type n ) const { return std::rand() % n; }
 
@@ -7571,7 +7578,7 @@ namespace Catch {
         return os;
     }
 
-    Version libraryVersion( 1, 5, 6, "", 0 );
+    Version libraryVersion( 1, 5, 8, "", 0 );
 
 }
 
@@ -7802,8 +7809,11 @@ namespace Catch {
     bool contains( std::string const& s, std::string const& infix ) {
         return s.find( infix ) != std::string::npos;
     }
+    char toLowerCh(char c) {
+        return static_cast<char>( ::tolower( c ) );
+    }
     void toLowerInPlace( std::string& s ) {
-        std::transform( s.begin(), s.end(), s.begin(), ::tolower );
+        std::transform( s.begin(), s.end(), s.begin(), toLowerCh );
     }
     std::string toLower( std::string const& s ) {
         std::string lc = s;
@@ -8951,9 +8961,10 @@ namespace Catch {
                         break;
 
                     default:
-                        // Escape control chars - based on contribution by @espenalb in PR #465
+                        // Escape control chars - based on contribution by @espenalb in PR #465 and
+                        // by @mrpi PR #588
                         if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' )
-                            os << "&#x" << std::uppercase << std::hex << static_cast<int>( c );
+                            os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>( c ) << ';';
                         else
                             os << c;
                 }
@@ -9008,13 +9019,20 @@ namespace Catch {
         :   m_tagIsOpen( false ),
             m_needsNewline( false ),
             m_os( &Catch::cout() )
-        {}
+        {
+            // We encode control characters, which requires
+            // XML 1.1
+            // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+            *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
+        }
 
         XmlWriter( std::ostream& os )
         :   m_tagIsOpen( false ),
             m_needsNewline( false ),
             m_os( &os )
-        {}
+        {
+            *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
+        }
 
         ~XmlWriter() {
             while( !m_tags.empty() )
@@ -9181,7 +9199,7 @@ namespace Catch {
 
         virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
             StreamingReporterBase::testCaseStarting(testInfo);
-            m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
+            m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.name );
 
             if ( m_config->showDurations() == ShowDurations::Always )
                 m_testCaseTimer.start();
@@ -9243,7 +9261,7 @@ namespace Catch {
                         .writeText( assertionResult.getMessage() );
                     break;
                 case ResultWas::FatalErrorCondition:
-                    m_xml.scopedElement( "Fatal Error Condition" )
+                    m_xml.scopedElement( "FatalErrorCondition" )
                         .writeAttribute( "filename", assertionResult.getSourceInfo().file )
                         .writeAttribute( "line", assertionResult.getSourceInfo().line )
                         .writeText( assertionResult.getMessage() );
diff --git a/test/t/basic/test_entity_bits.cpp b/test/t/basic/test_entity_bits.cpp
deleted file mode 100644
index 13de94b..0000000
--- a/test/t/basic/test_entity_bits.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#include "catch.hpp"
-
-#include <osmium/osm/entity_bits.hpp>
-
-TEST_CASE("entity_bits") {
-
-    SECTION("can_be_set_and_checked") {
-        osmium::osm_entity_bits::type entities = osmium::osm_entity_bits::node | osmium::osm_entity_bits::way;
-        REQUIRE(entities == (osmium::osm_entity_bits::node | osmium::osm_entity_bits::way));
-
-        entities |= osmium::osm_entity_bits::relation;
-        REQUIRE((entities & osmium::osm_entity_bits::object));
-
-        entities |= osmium::osm_entity_bits::area;
-        REQUIRE(entities == osmium::osm_entity_bits::object);
-
-        REQUIRE(! (entities & osmium::osm_entity_bits::changeset));
-
-        entities &= osmium::osm_entity_bits::node;
-        REQUIRE((entities & osmium::osm_entity_bits::node));
-        REQUIRE(! (entities & osmium::osm_entity_bits::way));
-        REQUIRE(entities == osmium::osm_entity_bits::node);
-
-        REQUIRE(osmium::osm_entity_bits::nothing   == osmium::osm_entity_bits::from_item_type(osmium::item_type::undefined));
-        REQUIRE(osmium::osm_entity_bits::node      == osmium::osm_entity_bits::from_item_type(osmium::item_type::node));
-        REQUIRE(osmium::osm_entity_bits::way       == osmium::osm_entity_bits::from_item_type(osmium::item_type::way));
-        REQUIRE(osmium::osm_entity_bits::relation  == osmium::osm_entity_bits::from_item_type(osmium::item_type::relation));
-        REQUIRE(osmium::osm_entity_bits::changeset == osmium::osm_entity_bits::from_item_type(osmium::item_type::changeset));
-        REQUIRE(osmium::osm_entity_bits::area      == osmium::osm_entity_bits::from_item_type(osmium::item_type::area));
-    }
-
-}
diff --git a/test/t/builder/test_object_builder.cpp b/test/t/builder/test_object_builder.cpp
new file mode 100644
index 0000000..5b12564
--- /dev/null
+++ b/test/t/builder/test_object_builder.cpp
@@ -0,0 +1,444 @@
+
+#include "catch.hpp"
+
+#include <osmium/builder/osm_object_builder.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm.hpp>
+
+TEST_CASE("create objects using builder") {
+    osmium::memory::Buffer buffer{1024*10};
+    std::string user;
+
+    SECTION("complete node with tags") {
+        SECTION("user length 0") {
+            user = "";
+        }
+        SECTION("user length 1") {
+            user = "1";
+        }
+        SECTION("user length 2") {
+            user = "12";
+        }
+        SECTION("user length 3") {
+            user = "123";
+        }
+        SECTION("user length 4") {
+            user = "1234";
+        }
+        SECTION("user length 5") {
+            user = "12345";
+        }
+        SECTION("user length 6") {
+            user = "123456";
+        }
+        SECTION("user length 7") {
+            user = "1234567";
+        }
+        SECTION("user length 8") {
+            user = "12345678";
+        }
+        SECTION("user length 9") {
+            user = "123456789";
+        }
+        SECTION("user length 10") {
+            user = "1234567890";
+        }
+        SECTION("user length 11") {
+            user = "12345678901";
+        }
+        SECTION("user length 12") {
+            user = "123456789012";
+        }
+        SECTION("user length 13") {
+            user = "1234567890123";
+        }
+        SECTION("user length 14") {
+            user = "12345678901234";
+        }
+        SECTION("user length 15") {
+            user = "123456789012345";
+        }
+        SECTION("user length 16") {
+            user = "1234567890123456";
+        }
+        SECTION("user length 17") {
+            user = "12345678901234567";
+        }
+        SECTION("user length 18") {
+            user = "123456789012345678";
+        }
+
+        osmium::Location loc{1.2, 3.4};
+
+        {
+            osmium::builder::NodeBuilder builder{buffer};
+
+            builder.set_id(17)
+                .set_visible(true)
+                .set_version(1)
+                .set_changeset(123)
+                .set_uid(555)
+                .set_timestamp("2015-07-01T00:00:01Z")
+                .set_location(loc)
+                .set_user(user);
+
+            builder.add_tags({{"highway", "primary"}, {"oneway", "yes"}});
+        }
+
+        const auto& node = buffer.get<osmium::Node>(buffer.commit());
+
+        REQUIRE(node.id() == 17);
+        REQUIRE(node.version() == 1);
+        REQUIRE(node.changeset() == 123);
+        REQUIRE(node.uid() == 555);
+        REQUIRE(node.timestamp() == osmium::Timestamp{"2015-07-01T00:00:01Z"});
+        REQUIRE(node.location() == loc);
+
+        REQUIRE(user == node.user());
+
+        REQUIRE(node.tags().size() == 2);
+    }
+
+    SECTION("complete way with tags") {
+        SECTION("user length 0") {
+            user = "";
+        }
+        SECTION("user length 1") {
+            user = "1";
+        }
+        SECTION("user length 2") {
+            user = "12";
+        }
+        SECTION("user length 3") {
+            user = "123";
+        }
+        SECTION("user length 4") {
+            user = "1234";
+        }
+        SECTION("user length 5") {
+            user = "12345";
+        }
+        SECTION("user length 6") {
+            user = "123456";
+        }
+        SECTION("user length 7") {
+            user = "1234567";
+        }
+        SECTION("user length 8") {
+            user = "12345678";
+        }
+        SECTION("user length 9") {
+            user = "123456789";
+        }
+        SECTION("user length 10") {
+            user = "1234567890";
+        }
+        SECTION("user length 11") {
+            user = "12345678901";
+        }
+        SECTION("user length 12") {
+            user = "123456789012";
+        }
+        SECTION("user length 13") {
+            user = "1234567890123";
+        }
+        SECTION("user length 14") {
+            user = "12345678901234";
+        }
+        SECTION("user length 15") {
+            user = "123456789012345";
+        }
+        SECTION("user length 16") {
+            user = "1234567890123456";
+        }
+        SECTION("user length 17") {
+            user = "12345678901234567";
+        }
+        SECTION("user length 18") {
+            user = "123456789012345678";
+        }
+
+        {
+            osmium::builder::WayBuilder builder{buffer};
+
+            builder.set_id(17)
+                .set_visible(true)
+                .set_version(1)
+                .set_changeset(123)
+                .set_uid(555)
+                .set_timestamp("2015-07-01T00:00:01Z")
+                .set_user(user);
+
+            builder.add_tags({{"highway", "primary"}, {"oneway", "yes"}});
+        }
+
+        const auto& way = buffer.get<osmium::Way>(buffer.commit());
+
+        REQUIRE(way.id() == 17);
+        REQUIRE(way.version() == 1);
+        REQUIRE(way.changeset() == 123);
+        REQUIRE(way.uid() == 555);
+        REQUIRE(way.timestamp() == osmium::Timestamp{"2015-07-01T00:00:01Z"});
+
+        REQUIRE(user == way.user());
+
+        REQUIRE(way.tags().size() == 2);
+    }
+
+    SECTION("complete relation with tags") {
+        SECTION("user length 0") {
+            user = "";
+        }
+        SECTION("user length 1") {
+            user = "1";
+        }
+        SECTION("user length 2") {
+            user = "12";
+        }
+        SECTION("user length 3") {
+            user = "123";
+        }
+        SECTION("user length 4") {
+            user = "1234";
+        }
+        SECTION("user length 5") {
+            user = "12345";
+        }
+        SECTION("user length 6") {
+            user = "123456";
+        }
+        SECTION("user length 7") {
+            user = "1234567";
+        }
+        SECTION("user length 8") {
+            user = "12345678";
+        }
+        SECTION("user length 9") {
+            user = "123456789";
+        }
+        SECTION("user length 10") {
+            user = "1234567890";
+        }
+        SECTION("user length 11") {
+            user = "12345678901";
+        }
+        SECTION("user length 12") {
+            user = "123456789012";
+        }
+        SECTION("user length 13") {
+            user = "1234567890123";
+        }
+        SECTION("user length 14") {
+            user = "12345678901234";
+        }
+        SECTION("user length 15") {
+            user = "123456789012345";
+        }
+        SECTION("user length 16") {
+            user = "1234567890123456";
+        }
+        SECTION("user length 17") {
+            user = "12345678901234567";
+        }
+        SECTION("user length 18") {
+            user = "123456789012345678";
+        }
+
+        {
+            osmium::builder::RelationBuilder builder{buffer};
+
+            builder.set_id(17)
+                .set_visible(true)
+                .set_version(1)
+                .set_changeset(123)
+                .set_uid(555)
+                .set_timestamp("2015-07-01T00:00:01Z")
+                .set_user(user);
+
+            builder.add_tags({{"highway", "primary"}, {"oneway", "yes"}});
+        }
+
+        const auto& relation = buffer.get<osmium::Relation>(buffer.commit());
+
+        REQUIRE(relation.id() == 17);
+        REQUIRE(relation.version() == 1);
+        REQUIRE(relation.changeset() == 123);
+        REQUIRE(relation.uid() == 555);
+        REQUIRE(relation.timestamp() == osmium::Timestamp{"2015-07-01T00:00:01Z"});
+
+        REQUIRE(user == relation.user());
+
+        REQUIRE(relation.tags().size() == 2);
+    }
+
+    SECTION("complete changeset with tags") {
+        osmium::Location bl{-1.2, -3.4};
+        osmium::Location tr{1.2, 3.4};
+
+        SECTION("user length 0") {
+            user = "";
+        }
+        SECTION("user length 1") {
+            user = "1";
+        }
+        SECTION("user length 2") {
+            user = "12";
+        }
+        SECTION("user length 3") {
+            user = "123";
+        }
+        SECTION("user length 4") {
+            user = "1234";
+        }
+        SECTION("user length 5") {
+            user = "12345";
+        }
+        SECTION("user length 6") {
+            user = "123456";
+        }
+        SECTION("user length 7") {
+            user = "1234567";
+        }
+        SECTION("user length 8") {
+            user = "12345678";
+        }
+        SECTION("user length 9") {
+            user = "123456789";
+        }
+        SECTION("user length 10") {
+            user = "1234567890";
+        }
+        SECTION("user length 11") {
+            user = "12345678901";
+        }
+        SECTION("user length 12") {
+            user = "123456789012";
+        }
+        SECTION("user length 13") {
+            user = "1234567890123";
+        }
+        SECTION("user length 14") {
+            user = "12345678901234";
+        }
+        SECTION("user length 15") {
+            user = "123456789012345";
+        }
+        SECTION("user length 16") {
+            user = "1234567890123456";
+        }
+        SECTION("user length 17") {
+            user = "12345678901234567";
+        }
+        SECTION("user length 18") {
+            user = "123456789012345678";
+        }
+
+        {
+            osmium::builder::ChangesetBuilder builder{buffer};
+
+            builder.set_id(17)
+                .set_uid(222)
+                .set_created_at(osmium::Timestamp{"2016-07-03T01:23:45Z"})
+                .set_closed_at(osmium::Timestamp{"2016-07-03T01:23:48Z"})
+                .set_num_changes(3)
+                .set_num_comments(2)
+                .set_bounds(osmium::Box{bl, tr})
+                .set_user(user);
+        }
+
+        const auto& changeset = buffer.get<osmium::Changeset>(buffer.commit());
+
+        REQUIRE(changeset.id() == 17);
+        REQUIRE(changeset.uid() == 222);
+        REQUIRE(changeset.created_at() == osmium::Timestamp{"2016-07-03T01:23:45Z"});
+        REQUIRE(changeset.closed_at() == osmium::Timestamp{"2016-07-03T01:23:48Z"});
+        REQUIRE(changeset.num_changes() == 3);
+        REQUIRE(changeset.num_comments() == 2);
+
+        const auto& box = changeset.bounds();
+        REQUIRE(box.bottom_left() == bl);
+        REQUIRE(box.top_right() == tr);
+
+        REQUIRE(user == changeset.user());
+    }
+
+}
+
+TEST_CASE("no call to set_user on node") {
+    osmium::memory::Buffer buffer{1024*10};
+
+    {
+        osmium::builder::NodeBuilder builder{buffer};
+    }
+
+    const auto& node = buffer.get<osmium::Node>(buffer.commit());
+
+    REQUIRE(*node.user() == '\0');
+}
+
+TEST_CASE("set_user with length on node") {
+    osmium::memory::Buffer buffer{1024*10};
+    std::string user = "userx";
+
+    {
+        osmium::builder::NodeBuilder builder{buffer};
+        builder.set_user(user.c_str(), 4);
+    }
+
+    const auto& node = buffer.get<osmium::Node>(buffer.commit());
+
+    REQUIRE(std::string{"user"} == node.user());
+}
+
+TEST_CASE("no call to set_user on way") {
+    osmium::memory::Buffer buffer{1024*10};
+
+    {
+        osmium::builder::WayBuilder builder{buffer};
+    }
+
+    const auto& way = buffer.get<osmium::Way>(buffer.commit());
+
+    REQUIRE(*way.user() == '\0');
+}
+
+TEST_CASE("set_user with length on way") {
+    osmium::memory::Buffer buffer{1024*10};
+    std::string user = "userx";
+
+    {
+        osmium::builder::WayBuilder builder{buffer};
+        builder.set_user(user.c_str(), 4);
+    }
+
+    const auto& way = buffer.get<osmium::Way>(buffer.commit());
+
+    REQUIRE(std::string{"user"} == way.user());
+}
+
+TEST_CASE("no call to set_user on changeset") {
+    osmium::memory::Buffer buffer{1024*10};
+
+    {
+        osmium::builder::ChangesetBuilder builder{buffer};
+    }
+
+    const auto& changeset = buffer.get<osmium::Changeset>(buffer.commit());
+
+    REQUIRE(*changeset.user() == '\0');
+}
+
+TEST_CASE("set_user with length on changeset") {
+    osmium::memory::Buffer buffer{1024*10};
+    std::string user = "userx";
+
+    {
+        osmium::builder::ChangesetBuilder builder{buffer};
+        builder.set_user(user.c_str(), 4);
+    }
+
+    const auto& changeset = buffer.get<osmium::Changeset>(buffer.commit());
+
+    REQUIRE(std::string{"user"} == changeset.user());
+}
+
diff --git a/test/t/geom/helper.hpp b/test/t/geom/helper.hpp
deleted file mode 100644
index e0cefe6..0000000
--- a/test/t/geom/helper.hpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef TEST_GEOM_HELPER_HPP
-#define TEST_GEOM_HELPER_HPP
-
-#include <string>
-
-#include <geos/io/WKBWriter.h>
-
-inline std::string geos_to_wkb(const geos::geom::Geometry* geometry) {
-    std::stringstream ss;
-    geos::io::WKBWriter wkb_writer;
-    wkb_writer.writeHEX(*geometry, ss);
-    return ss.str();
-}
-
-#endif // TEST_GEOM_HELPER_HPP
diff --git a/test/t/geom/test_crs.cpp b/test/t/geom/test_crs.cpp
index c8fbc09..d8f7c36 100644
--- a/test/t/geom/test_crs.cpp
+++ b/test/t/geom/test_crs.cpp
@@ -5,12 +5,12 @@
 #include <osmium/geom/projection.hpp>
 
 TEST_CASE("CRS") {
-    osmium::geom::CRS wgs84{4326};
-    osmium::geom::CRS mercator{3857};
+    const osmium::geom::CRS wgs84{4326};
+    const osmium::geom::CRS mercator{3857};
 
-    osmium::geom::Coordinates c{osmium::geom::deg_to_rad(1.2), osmium::geom::deg_to_rad(3.4)};
-    auto ct = osmium::geom::transform(wgs84, mercator, c);
-    auto c2 = osmium::geom::transform(mercator, wgs84, ct);
+    const osmium::geom::Coordinates c{osmium::geom::deg_to_rad(1.2), osmium::geom::deg_to_rad(3.4)};
+    const auto ct = osmium::geom::transform(wgs84, mercator, c);
+    const auto c2 = osmium::geom::transform(mercator, wgs84, ct);
 
     REQUIRE(c.x == Approx(c2.x));
     REQUIRE(c.y == Approx(c2.y));
diff --git a/test/t/geom/test_exception.cpp b/test/t/geom/test_exception.cpp
index fe95043..4122d17 100644
--- a/test/t/geom/test_exception.cpp
+++ b/test/t/geom/test_exception.cpp
@@ -6,11 +6,9 @@
 
 TEST_CASE("Geometry exception") {
 
-    SECTION("geometry_error") {
-        osmium::geometry_error e("some error message", "node", 17);
-        REQUIRE(e.id() == 17);
-        REQUIRE(std::string(e.what()) == "some error message (node_id=17)");
-    }
+    osmium::geometry_error e{"some error message", "node", 17};
+    REQUIRE(e.id() == 17);
+    REQUIRE(std::string{e.what()} == "some error message (node_id=17)");
 
 }
 
diff --git a/test/t/geom/test_factory_with_projection.cpp b/test/t/geom/test_factory_with_projection.cpp
index 42fc864..08efc03 100644
--- a/test/t/geom/test_factory_with_projection.cpp
+++ b/test/t/geom/test_factory_with_projection.cpp
@@ -1,41 +1,21 @@
 #include "catch.hpp"
 
-#include <osmium/geom/geos.hpp>
 #include <osmium/geom/mercator_projection.hpp>
 #include <osmium/geom/projection.hpp>
 #include <osmium/geom/wkb.hpp>
 #include <osmium/geom/wkt.hpp>
 
-#include "helper.hpp"
+TEST_CASE("Projection using MercatorProjection class to WKT") {
+    osmium::geom::WKTFactory<osmium::geom::MercatorProjection> factory{2};
 
-TEST_CASE("Projection") {
-
-    SECTION("point_mercator") {
-        osmium::geom::WKTFactory<osmium::geom::MercatorProjection> factory(2);
-
-        std::string wkt {factory.create_point(osmium::Location(3.2, 4.2))};
-        REQUIRE(std::string {"POINT(356222.37 467961.14)"} == wkt);
-    }
-
-    SECTION("point_epsg_3857") {
-        osmium::geom::WKTFactory<osmium::geom::Projection> factory(osmium::geom::Projection(3857), 2);
-
-        std::string wkt {factory.create_point(osmium::Location(3.2, 4.2))};
-        REQUIRE(std::string {"POINT(356222.37 467961.14)"} == wkt);
-    }
-
-    SECTION("wkb_with_parameter") {
-        osmium::geom::WKBFactory<osmium::geom::Projection> wkb_factory(osmium::geom::Projection(3857), osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
-        osmium::geom::GEOSFactory<osmium::geom::Projection> geos_factory(osmium::geom::Projection(3857));
-
-        std::string wkb = wkb_factory.create_point(osmium::Location(3.2, 4.2));
-        std::unique_ptr<geos::geom::Point> geos_point = geos_factory.create_point(osmium::Location(3.2, 4.2));
-        REQUIRE(geos_to_wkb(geos_point.get()) == wkb);
-    }
+    const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
+    REQUIRE(wkt == "POINT(356222.37 467961.14)");
+}
 
-    SECTION("cleanup") {
-        // trying to make valgrind happy, but there is still a memory leak in proj library
-        pj_deallocate_grids();
-    }
+TEST_CASE("Projection using Projection class to WKT") {
+    osmium::geom::WKTFactory<osmium::geom::Projection> factory{osmium::geom::Projection{3857}, 2};
 
+    const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
+    REQUIRE(wkt == "POINT(356222.37 467961.14)");
 }
+
diff --git a/test/t/geom/test_geojson.cpp b/test/t/geom/test_geojson.cpp
index 725be7f..5724936 100644
--- a/test/t/geom/test_geojson.cpp
+++ b/test/t/geom/test_geojson.cpp
@@ -5,162 +5,143 @@
 #include "area_helper.hpp"
 #include "wnl_helper.hpp"
 
-TEST_CASE("GeoJSON_Geometry") {
-
-SECTION("point") {
+TEST_CASE("GeoJSON point geometry") {
     osmium::geom::GeoJSONFactory<> factory;
 
-    std::string json {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"{\"type\":\"Point\",\"coordinates\":[3.2,4.2]}"} == json);
-}
+    SECTION("point") {
+        const std::string json{factory.create_point(osmium::Location{3.2, 4.2})};
+        REQUIRE(std::string{"{\"type\":\"Point\",\"coordinates\":[3.2,4.2]}"} == json);
+    }
 
-SECTION("empty_point") {
-    osmium::geom::GeoJSONFactory<> factory;
+    SECTION("empty_point") {
+        REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), osmium::invalid_location);
+    }
 
-    REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
 }
 
-SECTION("linestring") {
+TEST_CASE("GeoJSON linestring geometry") {
     osmium::geom::GeoJSONFactory<> factory;
+    osmium::memory::Buffer buffer{1000};
 
-    osmium::memory::Buffer buffer(1000);
-    auto &wnl = create_test_wnl_okay(buffer);
-
-    {
-        std::string json {factory.create_linestring(wnl)};
+    SECTION("linestring, default") {
+        const auto& wnl = create_test_wnl_okay(buffer);
+        const std::string json{factory.create_linestring(wnl)};
         REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.2,4.2],[3.5,4.7],[3.6,4.9]]}"} == json);
     }
 
-    {
-        std::string json {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
+    SECTION("linestring, unique, backwards") {
+        const auto& wnl = create_test_wnl_okay(buffer);
+        const std::string json{factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
         REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.6,4.9],[3.5,4.7],[3.2,4.2]]}"} == json);
     }
 
-    {
-        std::string json {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+    SECTION("linestring, all") {
+        const auto& wnl = create_test_wnl_okay(buffer);
+        const std::string json{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
         REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.2,4.2],[3.5,4.7],[3.5,4.7],[3.6,4.9]]}"} == json);
     }
 
-    {
-        std::string json {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+    SECTION("linestring, all, backwards") {
+        const auto& wnl = create_test_wnl_okay(buffer);
+        const std::string json{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
         REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.6,4.9],[3.5,4.7],[3.5,4.7],[3.2,4.2]]}"} == json);
     }
-}
 
-SECTION("empty_linestring") {
-    osmium::geom::GeoJSONFactory<> factory;
+    SECTION("empty_linestring") {
+        const auto& wnl = create_test_wnl_empty(buffer);
 
-    osmium::memory::Buffer buffer(1000);
-    auto& wnl = create_test_wnl_empty(buffer);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error);
+    }
 
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error);
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error);
-}
+    SECTION("linestring with two same locations") {
+        const auto& wnl = create_test_wnl_same_location(buffer);
 
-SECTION("linestring_with_two_same_locations") {
-    osmium::geom::GeoJSONFactory<> factory;
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
 
-    osmium::memory::Buffer buffer(1000);
-    auto& wnl = create_test_wnl_same_location(buffer);
-
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+        {
+            const std::string json{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+            REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.5,4.7],[3.5,4.7]]}"} == json);
+        }
 
-    {
-        std::string json {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
-        REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.5,4.7],[3.5,4.7]]}"} == json);
+        {
+            const std::string json{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+            REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.5,4.7],[3.5,4.7]]}"} == json);
+        }
     }
 
-    {
-        std::string json {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
-        REQUIRE(std::string{"{\"type\":\"LineString\",\"coordinates\":[[3.5,4.7],[3.5,4.7]]}"} == json);
+    SECTION("linestring with undefined location") {
+        const auto& wnl = create_test_wnl_undefined_location(buffer);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
     }
-}
-
-SECTION("linestring_with_undefined_location") {
-    osmium::geom::GeoJSONFactory<> factory;
-
-    osmium::memory::Buffer buffer(1000);
-    auto& wnl = create_test_wnl_undefined_location(buffer);
 
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
 }
 
-SECTION("area_1outer_0inner") {
+TEST_CASE("GeoJSON area geometry") {
     osmium::geom::GeoJSONFactory<> factory;
+    osmium::memory::Buffer buffer{1000};
 
-    osmium::memory::Buffer buffer(1000);
-    const osmium::Area& area = create_test_area_1outer_0inner(buffer);
+    SECTION("area_1outer_0inner") {
+        const osmium::Area& area = create_test_area_1outer_0inner(buffer);
 
-    REQUIRE(!area.is_multipolygon());
-    REQUIRE(std::distance(area.cbegin(), area.cend()) == 2);
-    REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
+        REQUIRE(!area.is_multipolygon());
+        REQUIRE(std::distance(area.cbegin(), area.cend()) == 2);
+        REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
 
-    {
-        std::string json {factory.create_multipolygon(area)};
+        std::string json{factory.create_multipolygon(area)};
         REQUIRE(std::string{"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[3.2,4.2],[3.5,4.7],[3.6,4.9],[3.2,4.2]]]]}"} == json);
     }
-}
-
-SECTION("area_1outer_1inner") {
-    osmium::geom::GeoJSONFactory<> factory;
 
-    osmium::memory::Buffer buffer(1000);
-    const osmium::Area& area = create_test_area_1outer_1inner(buffer);
+    SECTION("area_1outer_1inner") {
+        const osmium::Area& area = create_test_area_1outer_1inner(buffer);
 
-    REQUIRE(!area.is_multipolygon());
-    REQUIRE(std::distance(area.cbegin(), area.cend()) == 3);
-    REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
-    REQUIRE(area.subitems<osmium::InnerRing>().size() == area.num_rings().second);
+        REQUIRE(!area.is_multipolygon());
+        REQUIRE(std::distance(area.cbegin(), area.cend()) == 3);
+        REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
+        REQUIRE(area.subitems<osmium::InnerRing>().size() == area.num_rings().second);
 
-    {
-        std::string json {factory.create_multipolygon(area)};
+        std::string json{factory.create_multipolygon(area)};
         REQUIRE(std::string{"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0.1,0.1],[9.1,0.1],[9.1,9.1],[0.1,9.1],[0.1,0.1]],[[1,1],[8,1],[8,8],[1,8],[1,1]]]]}"} == json);
     }
-}
-
-SECTION("area_2outer_2inner") {
-    osmium::geom::GeoJSONFactory<> factory;
 
-    osmium::memory::Buffer buffer(1000);
-    const osmium::Area& area = create_test_area_2outer_2inner(buffer);
-
-    REQUIRE(area.is_multipolygon());
-    REQUIRE(std::distance(area.cbegin(), area.cend()) == 5);
-    REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
-    REQUIRE(area.subitems<osmium::InnerRing>().size() == area.num_rings().second);
-
-    int outer_ring=0;
-    int inner_ring=0;
-    for (const auto& outer : area.outer_rings()) {
-        if (outer_ring == 0) {
-            REQUIRE(outer.front().ref() == 1);
-        } else if (outer_ring == 1) {
-            REQUIRE(outer.front().ref() == 100);
-        } else {
-            REQUIRE(false);
-        }
-        for (const auto& inner : area.inner_rings(outer)) {
-            if (outer_ring == 0 && inner_ring == 0) {
-                REQUIRE(inner.front().ref() == 5);
-            } else if (outer_ring == 0 && inner_ring == 1) {
-                REQUIRE(inner.front().ref() == 10);
+    SECTION("area_2outer_2inner") {
+        const osmium::Area& area = create_test_area_2outer_2inner(buffer);
+
+        REQUIRE(area.is_multipolygon());
+        REQUIRE(std::distance(area.cbegin(), area.cend()) == 5);
+        REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
+        REQUIRE(area.subitems<osmium::InnerRing>().size() == area.num_rings().second);
+
+        int outer_ring=0;
+        int inner_ring=0;
+        for (const auto& outer : area.outer_rings()) {
+            if (outer_ring == 0) {
+                REQUIRE(outer.front().ref() == 1);
+            } else if (outer_ring == 1) {
+                REQUIRE(outer.front().ref() == 100);
             } else {
                 REQUIRE(false);
             }
-            ++inner_ring;
+            for (const auto& inner : area.inner_rings(outer)) {
+                if (outer_ring == 0 && inner_ring == 0) {
+                    REQUIRE(inner.front().ref() == 5);
+                } else if (outer_ring == 0 && inner_ring == 1) {
+                    REQUIRE(inner.front().ref() == 10);
+                } else {
+                    REQUIRE(false);
+                }
+                ++inner_ring;
+            }
+            inner_ring = 0;
+            ++outer_ring;
         }
-        inner_ring = 0;
-        ++outer_ring;
-    }
 
-    {
-        std::string json {factory.create_multipolygon(area)};
+        std::string json{factory.create_multipolygon(area)};
         REQUIRE(std::string{"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0.1,0.1],[9.1,0.1],[9.1,9.1],[0.1,9.1],[0.1,0.1]],[[1,1],[4,1],[4,4],[1,4],[1,1]],[[5,5],[5,7],[7,7],[5,5]]],[[[10,10],[11,10],[11,11],[10,11],[10,10]]]]}"} == json);
     }
-}
 
 }
 
diff --git a/test/t/geom/test_geos.cpp b/test/t/geom/test_geos.cpp
index f74027c..8e7fac4 100644
--- a/test/t/geom/test_geos.cpp
+++ b/test/t/geom/test_geos.cpp
@@ -1,6 +1,10 @@
-#include "catch.hpp"
 
 #include <osmium/geom/geos.hpp>
+
+#ifdef OSMIUM_WITH_GEOS
+
+#include "catch.hpp"
+
 #include <osmium/geom/mercator_projection.hpp>
 
 #include "area_helper.hpp"
@@ -9,7 +13,7 @@
 TEST_CASE("GEOS geometry factory - create point") {
     osmium::geom::GEOSFactory<> factory;
 
-    std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
+    const std::unique_ptr<geos::geom::Point> point{factory.create_point(osmium::Location{3.2, 4.2})};
     REQUIRE(3.2 == point->getX());
     REQUIRE(4.2 == point->getY());
     REQUIRE(4326 == point->getSRID());
@@ -18,7 +22,7 @@ TEST_CASE("GEOS geometry factory - create point") {
 TEST_CASE("GEOS geometry factory - create point in web mercator") {
     osmium::geom::GEOSFactory<osmium::geom::MercatorProjection> factory;
 
-    std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
+    const std::unique_ptr<geos::geom::Point> point{factory.create_point(osmium::Location{3.2, 4.2})};
     REQUIRE(Approx(356222.3705384755l) == point->getX());
     REQUIRE(Approx(467961.143605213l) == point->getY());
     REQUIRE(3857 == point->getSRID());
@@ -26,9 +30,9 @@ TEST_CASE("GEOS geometry factory - create point in web mercator") {
 
 TEST_CASE("GEOS geometry factory - create point with externally created GEOS factory") {
     geos::geom::GeometryFactory geos_factory;
-    osmium::geom::GEOSFactory<> factory(geos_factory);
+    osmium::geom::GEOSFactory<> factory{geos_factory};
 
-    std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
+    const std::unique_ptr<geos::geom::Point> point{factory.create_point(osmium::Location{3.2, 4.2})};
     REQUIRE(3.2 == point->getX());
     REQUIRE(4.2 == point->getY());
     REQUIRE(0 == point->getSRID());
@@ -37,45 +41,45 @@ TEST_CASE("GEOS geometry factory - create point with externally created GEOS fac
 TEST_CASE("GEOS geometry factory - can not create from invalid location") {
     osmium::geom::GEOSFactory<> factory;
 
-    REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
+    REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), osmium::invalid_location);
 }
 
 TEST_CASE("GEOS geometry factory - create linestring") {
     osmium::geom::GEOSFactory<> factory;
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_okay(buffer);
+    osmium::memory::Buffer buffer{10000};
+    const auto& wnl = create_test_wnl_okay(buffer);
 
     SECTION("from way node list") {
-        std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl)};
+        const std::unique_ptr<geos::geom::LineString> linestring{factory.create_linestring(wnl)};
         REQUIRE(3 == linestring->getNumPoints());
 
-        std::unique_ptr<geos::geom::Point> p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
+        const auto p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
         REQUIRE(3.2 == p0->getX());
-        std::unique_ptr<geos::geom::Point> p2 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(2));
+        const auto p2 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(2));
         REQUIRE(3.6 == p2->getX());
     }
 
     SECTION("without duplicates and backwards") {
-        std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
+        const std::unique_ptr<geos::geom::LineString> linestring{factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
         REQUIRE(3 == linestring->getNumPoints());
-        std::unique_ptr<geos::geom::Point> p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
+        const auto p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
         REQUIRE(3.6 == p0->getX());
-        std::unique_ptr<geos::geom::Point> p2 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(2));
+        const auto p2 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(2));
         REQUIRE(3.2 == p2->getX());
     }
 
     SECTION("with duplicates") {
-        std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+        const std::unique_ptr<geos::geom::LineString> linestring{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
         REQUIRE(4 == linestring->getNumPoints());
-        std::unique_ptr<geos::geom::Point> p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
+        const auto p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
         REQUIRE(3.2 == p0->getX());
     }
 
     SECTION("with duplicates and backwards") {
-        std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+        const std::unique_ptr<geos::geom::LineString> linestring{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
         REQUIRE(4 == linestring->getNumPoints());
-        std::unique_ptr<geos::geom::Point> p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
+        const auto p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
         REQUIRE(3.6 == p0->getX());
     }
 }
@@ -83,10 +87,10 @@ TEST_CASE("GEOS geometry factory - create linestring") {
 TEST_CASE("GEOS geometry factory - create area with one outer and no inner rings") {
     osmium::geom::GEOSFactory<> factory;
 
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
     const osmium::Area& area = create_test_area_1outer_0inner(buffer);
 
-    std::unique_ptr<geos::geom::MultiPolygon> mp {factory.create_multipolygon(area)};
+    const std::unique_ptr<geos::geom::MultiPolygon> mp{factory.create_multipolygon(area)};
     REQUIRE(1 == mp->getNumGeometries());
 
     const geos::geom::Polygon* p0 = dynamic_cast<const geos::geom::Polygon*>(mp->getGeometryN(0));
@@ -96,17 +100,17 @@ TEST_CASE("GEOS geometry factory - create area with one outer and no inner rings
     const geos::geom::LineString* l0e = p0->getExteriorRing();
     REQUIRE(4 == l0e->getNumPoints());
 
-    std::unique_ptr<geos::geom::Point> l0e_p0 = std::unique_ptr<geos::geom::Point>(l0e->getPointN(1));
+    const auto l0e_p0 = std::unique_ptr<geos::geom::Point>(l0e->getPointN(1));
     REQUIRE(3.5 == l0e_p0->getX());
 }
 
 TEST_CASE("GEOS geometry factory - create area with one outer and one inner ring") {
     osmium::geom::GEOSFactory<> factory;
 
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
     const osmium::Area& area = create_test_area_1outer_1inner(buffer);
 
-    std::unique_ptr<geos::geom::MultiPolygon> mp {factory.create_multipolygon(area)};
+    const std::unique_ptr<geos::geom::MultiPolygon> mp{factory.create_multipolygon(area)};
     REQUIRE(1 == mp->getNumGeometries());
 
     const geos::geom::Polygon* p0 = dynamic_cast<const geos::geom::Polygon*>(mp->getGeometryN(0));
@@ -123,10 +127,10 @@ TEST_CASE("GEOS geometry factory - create area with one outer and one inner ring
 TEST_CASE("GEOS geometry factory - create area with two outer and two inner rings") {
     osmium::geom::GEOSFactory<> factory;
 
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
     const osmium::Area& area = create_test_area_2outer_2inner(buffer);
 
-    std::unique_ptr<geos::geom::MultiPolygon> mp {factory.create_multipolygon(area)};
+    const std::unique_ptr<geos::geom::MultiPolygon> mp{factory.create_multipolygon(area)};
     REQUIRE(2 == mp->getNumGeometries());
 
     const geos::geom::Polygon* p0 = dynamic_cast<const geos::geom::Polygon*>(mp->getGeometryN(0));
@@ -144,3 +148,5 @@ TEST_CASE("GEOS geometry factory - create area with two outer and two inner ring
     REQUIRE(5 == l1e->getNumPoints());
 }
 
+#endif
+
diff --git a/test/t/geom/test_geos_wkb.cpp b/test/t/geom/test_geos_wkb.cpp
deleted file mode 100644
index 1fca63b..0000000
--- a/test/t/geom/test_geos_wkb.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-#include "catch.hpp"
-
-#include <osmium/geom/geos.hpp>
-#include <osmium/geom/wkb.hpp>
-
-#include "helper.hpp"
-#include "area_helper.hpp"
-#include "wnl_helper.hpp"
-
-TEST_CASE("WKB_Geometry_with_GEOS") {
-
-SECTION("point") {
-    osmium::geom::WKBFactory<> wkb_factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
-    osmium::geom::GEOSFactory<> geos_factory;
-
-    std::string wkb {wkb_factory.create_point(osmium::Location(3.2, 4.2))};
-
-    std::unique_ptr<geos::geom::Point> geos_point = geos_factory.create_point(osmium::Location(3.2, 4.2));
-    REQUIRE(geos_to_wkb(geos_point.get()) == wkb);
-}
-
-
-SECTION("linestring") {
-    osmium::geom::WKBFactory<> wkb_factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
-    osmium::geom::GEOSFactory<> geos_factory;
-
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_okay(buffer);
-
-    {
-        std::string wkb = wkb_factory.create_linestring(wnl);
-        std::unique_ptr<geos::geom::LineString> geos = geos_factory.create_linestring(wnl);
-        REQUIRE(geos_to_wkb(geos.get()) == wkb);
-    }
-
-    {
-        std::string wkb = wkb_factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward);
-        std::unique_ptr<geos::geom::LineString> geos = geos_factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward);
-        REQUIRE(geos_to_wkb(geos.get()) == wkb);
-    }
-
-    {
-        std::string wkb = wkb_factory.create_linestring(wnl, osmium::geom::use_nodes::all);
-        std::unique_ptr<geos::geom::LineString> geos = geos_factory.create_linestring(wnl, osmium::geom::use_nodes::all);
-        REQUIRE(geos_to_wkb(geos.get()) == wkb);
-    }
-
-    {
-        std::string wkb = wkb_factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward);
-        std::unique_ptr<geos::geom::LineString> geos = geos_factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward);
-        REQUIRE(geos_to_wkb(geos.get()) == wkb);
-    }
-}
-
-SECTION("area_1outer_0inner") {
-    osmium::geom::WKBFactory<> wkb_factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
-    osmium::geom::GEOSFactory<> geos_factory;
-
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_1outer_0inner(buffer);
-
-    std::string wkb = wkb_factory.create_multipolygon(area);
-    std::unique_ptr<geos::geom::MultiPolygon> geos = geos_factory.create_multipolygon(area);
-    REQUIRE(geos_to_wkb(geos.get()) == wkb);
-}
-
-SECTION("area_1outer_1inner") {
-    osmium::geom::WKBFactory<> wkb_factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
-    osmium::geom::GEOSFactory<> geos_factory;
-
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_1outer_1inner(buffer);
-
-    std::string wkb = wkb_factory.create_multipolygon(area);
-    std::unique_ptr<geos::geom::MultiPolygon> geos = geos_factory.create_multipolygon(area);
-    REQUIRE(geos_to_wkb(geos.get()) == wkb);
-}
-
-SECTION("area_2outer_2inner") {
-    osmium::geom::WKBFactory<> wkb_factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
-    osmium::geom::GEOSFactory<> geos_factory;
-
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_2outer_2inner(buffer);
-
-    std::string wkb = wkb_factory.create_multipolygon(area);
-    std::unique_ptr<geos::geom::MultiPolygon> geos = geos_factory.create_multipolygon(area);
-    REQUIRE(geos_to_wkb(geos.get()) == wkb);
-}
-
-}
-
diff --git a/test/t/geom/test_ogr.cpp b/test/t/geom/test_ogr.cpp
index 3490c57..5e03082 100644
--- a/test/t/geom/test_ogr.cpp
+++ b/test/t/geom/test_ogr.cpp
@@ -5,121 +5,115 @@
 #include "area_helper.hpp"
 #include "wnl_helper.hpp"
 
-TEST_CASE("OGR_Geometry") {
-
-SECTION("point") {
+TEST_CASE("OGR point geometry") {
     osmium::geom::OGRFactory<> factory;
 
-    std::unique_ptr<OGRPoint> point {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(3.2 == point->getX());
-    REQUIRE(4.2 == point->getY());
-}
+    SECTION("point") {
+        std::unique_ptr<OGRPoint> point{factory.create_point(osmium::Location{3.2, 4.2})};
+        REQUIRE(3.2 == point->getX());
+        REQUIRE(4.2 == point->getY());
+    }
 
-SECTION("empty_point") {
-    osmium::geom::OGRFactory<> factory;
+    SECTION("empty_point") {
+        REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
+    }
 
-    REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
 }
 
-SECTION("linestring") {
+TEST_CASE("OGR linestring geometry") {
     osmium::geom::OGRFactory<> factory;
+    osmium::memory::Buffer buffer{10000};
+    const auto& wnl = create_test_wnl_okay(buffer);
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_okay(buffer);
-
-    {
-        std::unique_ptr<OGRLineString> linestring {factory.create_linestring(wnl)};
+    SECTION("linestring, default") {
+        std::unique_ptr<OGRLineString> linestring{factory.create_linestring(wnl)};
         REQUIRE(3 == linestring->getNumPoints());
 
         REQUIRE(3.2 == linestring->getX(0));
         REQUIRE(3.6 == linestring->getX(2));
     }
 
-    {
-        std::unique_ptr<OGRLineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
+    SECTION("linestring, unique nodes, backwards") {
+        std::unique_ptr<OGRLineString> linestring{factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
         REQUIRE(3 == linestring->getNumPoints());
 
         REQUIRE(3.6 == linestring->getX(0));
         REQUIRE(3.2 == linestring->getX(2));
     }
 
-    {
-        std::unique_ptr<OGRLineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+    SECTION("linestring, all nodes") {
+        std::unique_ptr<OGRLineString> linestring{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
         REQUIRE(4 == linestring->getNumPoints());
 
         REQUIRE(3.2 == linestring->getX(0));
     }
 
-    {
-        std::unique_ptr<OGRLineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+    SECTION("linestring, all nodes, backwards") {
+        std::unique_ptr<OGRLineString> linestring{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
         REQUIRE(4 == linestring->getNumPoints());
 
         REQUIRE(3.6 == linestring->getX(0));
     }
+
 }
 
-SECTION("area_1outer_0inner") {
+TEST_CASE("OGR area geometry") {
     osmium::geom::OGRFactory<> factory;
+    osmium::memory::Buffer buffer{10000};
 
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_1outer_0inner(buffer);
+    SECTION("area_1outer_0inner") {
+        const osmium::Area& area = create_test_area_1outer_0inner(buffer);
 
-    std::unique_ptr<OGRMultiPolygon> mp {factory.create_multipolygon(area)};
-    REQUIRE(1 == mp->getNumGeometries());
+        std::unique_ptr<OGRMultiPolygon> mp {factory.create_multipolygon(area)};
+        REQUIRE(1 == mp->getNumGeometries());
 
-    const OGRPolygon* p0 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(0));
-    REQUIRE(p0);
-    REQUIRE(0 == p0->getNumInteriorRings());
+        const OGRPolygon* p0 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(0));
+        REQUIRE(p0);
+        REQUIRE(0 == p0->getNumInteriorRings());
 
-    const OGRLineString* l0e = p0->getExteriorRing();
-    REQUIRE(4 == l0e->getNumPoints());
-
-    REQUIRE(3.5 == l0e->getX(1));
-}
+        const OGRLineString* l0e = p0->getExteriorRing();
+        REQUIRE(4 == l0e->getNumPoints());
 
-SECTION("area_1outer_1inner") {
-    osmium::geom::OGRFactory<> factory;
-
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_1outer_1inner(buffer);
+        REQUIRE(3.5 == l0e->getX(1));
+    }
 
-    std::unique_ptr<OGRMultiPolygon> mp {factory.create_multipolygon(area)};
-    REQUIRE(1 == mp->getNumGeometries());
+    SECTION("area_1outer_1inner") {
+        const osmium::Area& area = create_test_area_1outer_1inner(buffer);
 
-    const OGRPolygon* p0 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(0));
-    REQUIRE(p0);
-    REQUIRE(1 == p0->getNumInteriorRings());
+        std::unique_ptr<OGRMultiPolygon> mp {factory.create_multipolygon(area)};
+        REQUIRE(1 == mp->getNumGeometries());
 
-    const OGRLineString* l0e = p0->getExteriorRing();
-    REQUIRE(5 == l0e->getNumPoints());
+        const OGRPolygon* p0 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(0));
+        REQUIRE(p0);
+        REQUIRE(1 == p0->getNumInteriorRings());
 
-    const OGRLineString* l0i0 = p0->getInteriorRing(0);
-    REQUIRE(5 == l0i0->getNumPoints());
-}
+        const OGRLineString* l0e = p0->getExteriorRing();
+        REQUIRE(5 == l0e->getNumPoints());
 
-SECTION("area_2outer_2inner") {
-    osmium::geom::OGRFactory<> factory;
+        const OGRLineString* l0i0 = p0->getInteriorRing(0);
+        REQUIRE(5 == l0i0->getNumPoints());
+    }
 
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_2outer_2inner(buffer);
+    SECTION("area_2outer_2inner") {
+        const osmium::Area& area = create_test_area_2outer_2inner(buffer);
 
-    std::unique_ptr<OGRMultiPolygon> mp {factory.create_multipolygon(area)};
-    REQUIRE(2 == mp->getNumGeometries());
+        std::unique_ptr<OGRMultiPolygon> mp {factory.create_multipolygon(area)};
+        REQUIRE(2 == mp->getNumGeometries());
 
-    const OGRPolygon* p0 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(0));
-    REQUIRE(p0);
-    REQUIRE(2 == p0->getNumInteriorRings());
+        const OGRPolygon* p0 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(0));
+        REQUIRE(p0);
+        REQUIRE(2 == p0->getNumInteriorRings());
 
-    const OGRLineString* l0e = p0->getExteriorRing();
-    REQUIRE(5 == l0e->getNumPoints());
+        const OGRLineString* l0e = p0->getExteriorRing();
+        REQUIRE(5 == l0e->getNumPoints());
 
-    const OGRPolygon* p1 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(1));
-    REQUIRE(p1);
-    REQUIRE(0 == p1->getNumInteriorRings());
+        const OGRPolygon* p1 = dynamic_cast<const OGRPolygon*>(mp->getGeometryRef(1));
+        REQUIRE(p1);
+        REQUIRE(0 == p1->getNumInteriorRings());
 
-    const OGRLineString* l1e = p1->getExteriorRing();
-    REQUIRE(5 == l1e->getNumPoints());
-}
+        const OGRLineString* l1e = p1->getExteriorRing();
+        REQUIRE(5 == l1e->getNumPoints());
+    }
 
 }
 
diff --git a/test/t/geom/test_ogr_wkb.cpp b/test/t/geom/test_ogr_wkb.cpp
new file mode 100644
index 0000000..548d143
--- /dev/null
+++ b/test/t/geom/test_ogr_wkb.cpp
@@ -0,0 +1,100 @@
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+#include "catch.hpp"
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include <osmium/geom/ogr.hpp>
+#include <osmium/geom/wkb.hpp>
+
+#include "area_helper.hpp"
+#include "wnl_helper.hpp"
+
+std::string to_wkb(const OGRGeometry* geometry) {
+    std::string buffer;
+    buffer.resize(geometry->WkbSize());
+
+    geometry->exportToWkb(wkbNDR, const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(buffer.data())));
+
+    return buffer;
+}
+
+TEST_CASE("compare WKB point against GDAL/OGR") {
+    osmium::geom::WKBFactory<> wkb_factory{osmium::geom::wkb_type::wkb};
+    osmium::geom::OGRFactory<> ogr_factory;
+
+    osmium::Location loc{3.2, 4.2};
+    const std::string wkb{wkb_factory.create_point(loc)};
+    const std::unique_ptr<OGRPoint> geometry = ogr_factory.create_point(loc);
+    REQUIRE(to_wkb(geometry.get()) == wkb);
+}
+
+TEST_CASE("compare WKB linestring against GDAL/OGR") {
+    osmium::geom::WKBFactory<> wkb_factory{osmium::geom::wkb_type::wkb};
+    osmium::geom::OGRFactory<> ogr_factory;
+    osmium::memory::Buffer buffer{10000};
+
+    const auto& wnl = create_test_wnl_okay(buffer);
+
+    SECTION("linestring") {
+        const std::string wkb{wkb_factory.create_linestring(wnl)};
+        const std::unique_ptr<OGRLineString> geometry = ogr_factory.create_linestring(wnl);
+        REQUIRE(to_wkb(geometry.get()) == wkb);
+    }
+
+    SECTION("linestring, unique nodes, backwards") {
+        const std::string wkb{wkb_factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
+        const std::unique_ptr<OGRLineString> geometry = ogr_factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward);
+        REQUIRE(to_wkb(geometry.get()) == wkb);
+    }
+
+    SECTION("linestring, all nodes, forwards") {
+        const std::string wkb{wkb_factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+        const std::unique_ptr<OGRLineString> geometry = ogr_factory.create_linestring(wnl, osmium::geom::use_nodes::all);
+        REQUIRE(to_wkb(geometry.get()) == wkb);
+    }
+
+    SECTION("linestring, all nodes, backwards") {
+        const std::string wkb{wkb_factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+        const std::unique_ptr<OGRLineString> geometry = ogr_factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward);
+        REQUIRE(to_wkb(geometry.get()) == wkb);
+    }
+
+}
+
+TEST_CASE("compare WKB area against GDAL/OGR") {
+    osmium::geom::WKBFactory<> wkb_factory{osmium::geom::wkb_type::wkb};
+    osmium::geom::OGRFactory<> ogr_factory;
+    osmium::memory::Buffer buffer{10000};
+
+    SECTION("area_1outer_0inner") {
+        const osmium::Area& area = create_test_area_1outer_0inner(buffer);
+
+        const std::string wkb{wkb_factory.create_multipolygon(area)};
+        const std::unique_ptr<OGRMultiPolygon> geometry = ogr_factory.create_multipolygon(area);
+        REQUIRE(to_wkb(geometry.get()) == wkb);
+    }
+
+    SECTION("area_1outer_1inner") {
+        const osmium::Area& area = create_test_area_1outer_1inner(buffer);
+
+        const std::string wkb{wkb_factory.create_multipolygon(area)};
+        const std::unique_ptr<OGRMultiPolygon> geometry = ogr_factory.create_multipolygon(area);
+        REQUIRE(to_wkb(geometry.get()) == wkb);
+    }
+
+    SECTION("area_2outer_2inner") {
+        const osmium::Area& area = create_test_area_2outer_2inner(buffer);
+
+        const std::string wkb{wkb_factory.create_multipolygon(area)};
+        const std::unique_ptr<OGRMultiPolygon> geometry = ogr_factory.create_multipolygon(area);
+        REQUIRE(to_wkb(geometry.get()) == wkb);
+    }
+
+}
+
+#endif
+
diff --git a/test/t/geom/test_projection.cpp b/test/t/geom/test_projection.cpp
index 5885410..14df3bf 100644
--- a/test/t/geom/test_projection.cpp
+++ b/test/t/geom/test_projection.cpp
@@ -6,144 +6,121 @@
 #include <osmium/geom/mercator_projection.hpp>
 #include <osmium/geom/projection.hpp>
 
-TEST_CASE("Projection") {
-
-SECTION("identity_projection") {
+TEST_CASE("Indentity Projection") {
     osmium::geom::IdentityProjection projection;
     REQUIRE(4326 == projection.epsg());
     REQUIRE("+proj=longlat +datum=WGS84 +no_defs" == projection.proj_string());
 }
 
-SECTION("project_location_4326") {
-    osmium::geom::Projection projection(4326);
+TEST_CASE("Projection 4326") {
+    osmium::geom::Projection projection{4326};
     REQUIRE(4326 == projection.epsg());
     REQUIRE("+init=epsg:4326" == projection.proj_string());
 
-    const osmium::Location loc(1.0, 2.0);
-    const osmium::geom::Coordinates c {1.0, 2.0};
+    const osmium::Location loc{1.0, 2.0};
+    const osmium::geom::Coordinates c{1.0, 2.0};
     REQUIRE(c == projection(loc));
 }
 
-SECTION("project_location_4326_string") {
-    osmium::geom::Projection projection("+init=epsg:4326");
+TEST_CASE("Projection 4326 from init string") {
+    osmium::geom::Projection projection{"+init=epsg:4326"};
     REQUIRE(-1 == projection.epsg());
     REQUIRE("+init=epsg:4326" == projection.proj_string());
 
-    const osmium::Location loc(1.0, 2.0);
-    const osmium::geom::Coordinates c {1.0, 2.0};
+    const osmium::Location loc{1.0, 2.0};
+    const osmium::geom::Coordinates c{1.0, 2.0};
     REQUIRE(c == projection(loc));
 }
 
-SECTION("unknown_projection_string") {
-    REQUIRE_THROWS_AS(osmium::geom::Projection projection("abc"), osmium::projection_error);
+TEST_CASE("Creating projection from unknown init string") {
+    REQUIRE_THROWS_AS(osmium::geom::Projection projection{"abc"}, osmium::projection_error);
 }
 
-SECTION("unknown_epsg_code") {
-    REQUIRE_THROWS_AS(osmium::geom::Projection projection(9999999), osmium::projection_error);
+TEST_CASE("Creating projection from unknown EPSG code") {
+    REQUIRE_THROWS_AS(osmium::geom::Projection projection{9999999}, osmium::projection_error);
 }
 
-SECTION("project_location_3857") {
-    osmium::geom::Projection projection(3857);
+TEST_CASE("Projection 3857") {
+    osmium::geom::Projection projection{3857};
     REQUIRE(3857 == projection.epsg());
     REQUIRE("+init=epsg:3857" == projection.proj_string());
 
-    {
-        const osmium::Location loc(0.0, 0.0);
-        const osmium::geom::Coordinates c {0.0, 0.0};
+    SECTION("Zero coordinates") {
+        const osmium::Location loc{0.0, 0.0};
+        const osmium::geom::Coordinates c{0.0, 0.0};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
-    {
-        const osmium::Location loc(180.0, 0.0);
-        const osmium::geom::Coordinates c {20037508.34, 0.0};
+
+    SECTION("Max longitude") {
+        const osmium::Location loc{180.0, 0.0};
+        const osmium::geom::Coordinates c{20037508.34, 0.0};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
-    {
-        const osmium::Location loc(180.0, 0.0);
-        const osmium::geom::Coordinates c {20037508.34, 0.0};
+
+    SECTION("Min longitude") {
+        const osmium::Location loc{-180.0, 0.0};
+        const osmium::geom::Coordinates c{-20037508.34, 0.0};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
-    {
-        const osmium::Location loc(0.0, 85.0511288);
-        const osmium::geom::Coordinates c {0.0, 20037508.34};
+
+    SECTION("Max latitude") {
+        const osmium::Location loc{0.0, 85.0511288};
+        const osmium::geom::Coordinates c{0.0, 20037508.34};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
 }
 
-SECTION("project_location_mercator") {
+TEST_CASE("MercatorProjection") {
     osmium::geom::MercatorProjection projection;
 
-    {
-        const osmium::Location loc(0.0, 0.0);
-        const osmium::geom::Coordinates c {0.0, 0.0};
+    SECTION("Zero coordinates") {
+        const osmium::Location loc{0.0, 0.0};
+        const osmium::geom::Coordinates c{0.0, 0.0};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
-    {
-        const osmium::Location loc(180.0, 0.0);
-        const osmium::geom::Coordinates c {20037508.34, 0.0};
+
+    SECTION("Max longitude") {
+        const osmium::Location loc{180.0, 0.0};
+        const osmium::geom::Coordinates c{20037508.34, 0.0};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
-    {
-        const osmium::Location loc(180.0, 0.0);
-        const osmium::geom::Coordinates c {20037508.34, 0.0};
+
+    SECTION("Min longitude") {
+        const osmium::Location loc{-180.0, 0.0};
+        const osmium::geom::Coordinates c{-20037508.34, 0.0};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
-    {
-        const osmium::Location loc(0.0, 85.0511288);
-        const osmium::geom::Coordinates c {0.0, 20037508.34};
+
+    SECTION("Max latitude") {
+        const osmium::Location loc{0.0, 85.0511288};
+        const osmium::geom::Coordinates c{0.0, 20037508.34};
         REQUIRE(projection(loc).x == Approx(c.x).epsilon(0.1));
         REQUIRE(projection(loc).y == Approx(c.y).epsilon(0.1));
     }
 }
 
-SECTION("compare_mercators") {
+TEST_CASE("Compare mercator implementations") {
     osmium::geom::MercatorProjection projection_merc;
-    osmium::geom::Projection projection_3857(3857);
-    REQUIRE(3857 == projection_3857.epsg());
-    REQUIRE("+init=epsg:3857" == projection_3857.proj_string());
-
-    {
-        const osmium::Location loc(4.2, 27.3);
-        REQUIRE(projection_merc(loc).x == Approx(projection_3857(loc).x).epsilon(0.1));
-        REQUIRE(projection_merc(loc).y == Approx(projection_3857(loc).y).epsilon(0.1));
-    }
-    {
-        const osmium::Location loc(160.789, -42.42);
-        REQUIRE(projection_merc(loc).x == Approx(projection_3857(loc).x).epsilon(0.1));
-        REQUIRE(projection_merc(loc).y == Approx(projection_3857(loc).y).epsilon(0.1));
-    }
-    {
-        const osmium::Location loc(-0.001, 0.001);
-        REQUIRE(projection_merc(loc).x == Approx(projection_3857(loc).x).epsilon(0.1));
-        REQUIRE(projection_merc(loc).y == Approx(projection_3857(loc).y).epsilon(0.1));
+    osmium::geom::Projection projection_3857{3857};
+
+    SECTION("random coordinates") {
+        std::random_device rd;
+        std::mt19937 gen{rd()};
+        std::uniform_real_distribution<> dis_x{-180.0, 180.0};
+        std::uniform_real_distribution<> dis_y{-90.0, 90.0};
+
+        for (int n = 0; n < 10000; ++n) {
+            const osmium::Location loc{dis_x(gen), dis_y(gen)};
+            REQUIRE(projection_merc(loc).x == Approx(projection_3857(loc).x).epsilon(0.1));
+            REQUIRE(projection_merc(loc).y == Approx(projection_3857(loc).y).epsilon(0.1));
+        }
     }
-    {
-        const osmium::Location loc(-85.2, -85.2);
-        REQUIRE(projection_merc(loc).x == Approx(projection_3857(loc).x).epsilon(0.1));
-        REQUIRE(projection_merc(loc).y == Approx(projection_3857(loc).y).epsilon(0.1));
-    }
-}
-
-SECTION("compare_mercators") {
-    osmium::geom::MercatorProjection projection_merc;
-    osmium::geom::Projection projection_3857(3857);
-
-    std::random_device rd;
-    std::mt19937 gen(rd());
-    std::uniform_real_distribution<> dis_x(-180.0, 180.0);
-    std::uniform_real_distribution<> dis_y(-90.0, 90.0);
-
-    for (int n = 0; n < 100000; ++n) {
-        const osmium::Location loc(dis_x(gen), dis_y(gen));
-        REQUIRE(projection_merc(loc).x == Approx(projection_3857(loc).x).epsilon(0.1));
-        REQUIRE(projection_merc(loc).y == Approx(projection_3857(loc).y).epsilon(0.1));
-    }
-}
 
 }
diff --git a/test/t/geom/test_tile.cpp b/test/t/geom/test_tile.cpp
index 5454fed..953fcc7 100644
--- a/test/t/geom/test_tile.cpp
+++ b/test/t/geom/test_tile.cpp
@@ -4,8 +4,6 @@
 
 #include <osmium/geom/tile.hpp>
 
-#include "helper.hpp"
-
 #include "test_tile_data.hpp"
 
 TEST_CASE("Tile from x0.0 y0.0 at zoom 0") {
diff --git a/test/t/geom/test_wkb.cpp b/test/t/geom/test_wkb.cpp
index d4d9228..66dd42e 100644
--- a/test/t/geom/test_wkb.cpp
+++ b/test/t/geom/test_wkb.cpp
@@ -10,28 +10,28 @@ TEST_CASE("WKB geometry factory (byte-order-dependant), points") {
     const osmium::Location loc{3.2, 4.2};
 
     SECTION("point") {
-        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex};
 
         const std::string wkb{factory.create_point(loc)};
         REQUIRE(wkb == "01010000009A99999999990940CDCCCCCCCCCC1040");
     }
 
     SECTION("point in web mercator") {
-        osmium::geom::WKBFactory<osmium::geom::MercatorProjection> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<osmium::geom::MercatorProjection> factory{osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex};
 
         const std::string wkb{factory.create_point(loc)};
         REQUIRE(wkb == "010100000028706E7BF9BD1541B03E0D93E48F1C41");
     }
 
     SECTION("point in ewkb") {
-        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex};
 
         const std::string wkb{factory.create_point(loc)};
         REQUIRE(wkb == "0101000020E61000009A99999999990940CDCCCCCCCCCC1040");
     }
 
     SECTION("point in ewkb in web mercator") {
-        osmium::geom::WKBFactory<osmium::geom::MercatorProjection> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<osmium::geom::MercatorProjection> factory{osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex};
 
         const std::string wkb{factory.create_point(loc)};
         REQUIRE(wkb == "0101000020110F000028706E7BF9BD1541B03E0D93E48F1C41");
@@ -44,7 +44,7 @@ TEST_CASE("WKB geometry factory (byte-order-dependant)") {
     osmium::memory::Buffer buffer{10000};
 
     SECTION("linestring") {
-        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex};
         const auto& wnl = create_test_wnl_okay(buffer);
 
         {
@@ -69,7 +69,7 @@ TEST_CASE("WKB geometry factory (byte-order-dependant)") {
     }
 
     SECTION("linestring as ewkb") {
-        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex};
 
         const auto& wnl = create_test_wnl_okay(buffer);
 
@@ -78,7 +78,7 @@ TEST_CASE("WKB geometry factory (byte-order-dependant)") {
     }
 
     SECTION("linestring with two same locations") {
-        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex};
 
         const auto& wnl = create_test_wnl_same_location(buffer);
 
@@ -102,7 +102,7 @@ TEST_CASE("WKB geometry factory (byte-order-dependant)") {
     }
 
     SECTION("linestring with undefined location") {
-        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+        osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex};
 
         const auto& wnl = create_test_wnl_undefined_location(buffer);
 
@@ -115,7 +115,7 @@ TEST_CASE("WKB geometry factory (byte-order-dependant)") {
 
 TEST_CASE("WKB geometry (byte-order-independent)") {
 
-    osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+    osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex};
 
     SECTION("empty point") {
         REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), osmium::invalid_location);
diff --git a/test/t/geom/wnl_helper.hpp b/test/t/geom/wnl_helper.hpp
index 91ac114..68de4c3 100644
--- a/test/t/geom/wnl_helper.hpp
+++ b/test/t/geom/wnl_helper.hpp
@@ -6,7 +6,7 @@
 using namespace osmium::builder::attr;
 
 inline const osmium::WayNodeList& create_test_wnl_okay(osmium::memory::Buffer& buffer) {
-    auto pos = osmium::builder::add_way_node_list(buffer, _nodes({
+    const auto pos = osmium::builder::add_way_node_list(buffer, _nodes({
         {1, {3.2, 4.2}},
         {3, {3.5, 4.7}},
         {4, {3.5, 4.7}},
@@ -25,7 +25,7 @@ inline const osmium::WayNodeList& create_test_wnl_empty(osmium::memory::Buffer&
 }
 
 inline const osmium::WayNodeList& create_test_wnl_same_location(osmium::memory::Buffer& buffer) {
-    auto pos = osmium::builder::add_way_node_list(buffer, _nodes({
+    const auto pos = osmium::builder::add_way_node_list(buffer, _nodes({
         {1, {3.5, 4.7}},
         {2, {3.5, 4.7}}
     }));
@@ -34,7 +34,7 @@ inline const osmium::WayNodeList& create_test_wnl_same_location(osmium::memory::
 }
 
 inline const osmium::WayNodeList& create_test_wnl_undefined_location(osmium::memory::Buffer& buffer) {
-    auto pos = osmium::builder::add_way_node_list(buffer, _nodes({
+    const auto pos = osmium::builder::add_way_node_list(buffer, _nodes({
         {1, {3.5, 4.7}},
         {2, osmium::Location()}
     }));
diff --git a/test/t/index/test_id_set.cpp b/test/t/index/test_id_set.cpp
new file mode 100644
index 0000000..4c24447
--- /dev/null
+++ b/test/t/index/test_id_set.cpp
@@ -0,0 +1,166 @@
+
+#include "catch.hpp"
+
+#include <osmium/index/id_set.hpp>
+#include <osmium/osm/types.hpp>
+
+TEST_CASE("Basic functionality of IdSetDense") {
+    osmium::index::IdSetDense<osmium::unsigned_object_id_type> s;
+
+    REQUIRE_FALSE(s.get(17));
+    REQUIRE_FALSE(s.get(28));
+    REQUIRE(s.empty());
+    REQUIRE(s.size() == 0);
+
+    s.set(17);
+    REQUIRE(s.get(17));
+    REQUIRE_FALSE(s.get(28));
+    REQUIRE_FALSE(s.empty());
+    REQUIRE(s.size() == 1);
+
+    s.set(28);
+    REQUIRE(s.get(17));
+    REQUIRE(s.get(28));
+    REQUIRE_FALSE(s.empty());
+    REQUIRE(s.size() == 2);
+
+    s.set(17);
+    REQUIRE(s.get(17));
+    REQUIRE(s.size() == 2);
+
+    REQUIRE_FALSE(s.check_and_set(17));
+    REQUIRE(s.get(17));
+    REQUIRE(s.size() == 2);
+
+    s.unset(17);
+    REQUIRE_FALSE(s.get(17));
+    REQUIRE(s.size() == 1);
+
+    REQUIRE(s.check_and_set(32));
+    REQUIRE(s.get(32));
+    REQUIRE(s.size() == 2);
+
+    s.clear();
+    REQUIRE(s.empty());
+    REQUIRE(s.size() == 0);
+}
+
+TEST_CASE("Iterating over IdSetDense") {
+    osmium::index::IdSetDense<osmium::unsigned_object_id_type> s;
+    s.set(7);
+    s.set(35);
+    s.set(35);
+    s.set(20);
+    s.set(1LL << 33);
+    s.set(21);
+    s.set((1LL << 27) + 13);
+
+    REQUIRE(s.size() == 6);
+
+    auto it = s.begin();
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 7);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 20);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 21);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 35);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == (1LL << 27) + 13);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 1LL << 33);
+    ++it;
+    REQUIRE(it == s.end());
+}
+
+TEST_CASE("Test with larger Ids") {
+    osmium::index::IdSetDense<osmium::unsigned_object_id_type> s;
+
+    const osmium::unsigned_object_id_type start = 25;
+    const osmium::unsigned_object_id_type end = 100000000;
+    const osmium::unsigned_object_id_type step = 123456;
+
+    for (osmium::unsigned_object_id_type i = start; i < end; i += step) {
+        s.set(i);
+    }
+
+    for (osmium::unsigned_object_id_type i = start; i < end; i += step) {
+        REQUIRE(s.get(i));
+        REQUIRE_FALSE(s.get(i + 1));
+    }
+}
+
+TEST_CASE("Large gap") {
+    osmium::index::IdSetDense<osmium::unsigned_object_id_type> s;
+
+    s.set(3);
+    s.set(1 << 30);
+
+    REQUIRE(s.get(1 << 30));
+    REQUIRE_FALSE(s.get(1 << 29));
+}
+
+TEST_CASE("Basic functionality of IdSetSmall") {
+    osmium::index::IdSetSmall<osmium::unsigned_object_id_type> s;
+
+    REQUIRE_FALSE(s.get(17));
+    REQUIRE_FALSE(s.get(28));
+    REQUIRE(s.empty());
+
+    s.set(17);
+    REQUIRE(s.get(17));
+    REQUIRE_FALSE(s.get(28));
+    REQUIRE_FALSE(s.empty());
+
+    s.set(28);
+    REQUIRE(s.get(17));
+    REQUIRE(s.get(28));
+    REQUIRE_FALSE(s.empty());
+
+    s.clear();
+    REQUIRE(s.empty());
+}
+
+TEST_CASE("Iterating over IdSetSmall") {
+    osmium::index::IdSetSmall<osmium::unsigned_object_id_type> s;
+    s.set(7);
+    s.set(35);
+    s.set(35);
+    s.set(20);
+    s.set(1LL << 33);
+    s.set(21);
+    s.set((1LL << 27) + 13);
+
+    // needs to be called before size() and iterator will work properly
+    s.sort_unique();
+
+    REQUIRE(s.size() == 6);
+
+    auto it = s.begin();
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 7);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 20);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 21);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 35);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == (1LL << 27) + 13);
+    ++it;
+    REQUIRE(it != s.end());
+    REQUIRE(*it == 1LL << 33);
+    ++it;
+    REQUIRE(it == s.end());
+}
+
diff --git a/test/t/index/test_id_to_location.cpp b/test/t/index/test_id_to_location.cpp
index 810ef3b..36fc074 100644
--- a/test/t/index/test_id_to_location.cpp
+++ b/test/t/index/test_id_to_location.cpp
@@ -15,12 +15,14 @@
 
 #include <osmium/index/node_locations_map.hpp>
 
+static_assert(osmium::index::empty_value<osmium::Location>() == osmium::Location{}, "Empty value for location is wrong");
+
 template <typename TIndex>
 void test_func_all(TIndex& index) {
-    osmium::unsigned_object_id_type id1 = 12;
-    osmium::unsigned_object_id_type id2 = 3;
-    osmium::Location loc1{1.2, 4.5};
-    osmium::Location loc2{3.5, -7.2};
+    const osmium::unsigned_object_id_type id1 = 12;
+    const osmium::unsigned_object_id_type id2 = 3;
+    const osmium::Location loc1{1.2, 4.5};
+    const osmium::Location loc2{3.5, -7.2};
 
     REQUIRE_THROWS_AS(index.get(id1), osmium::not_found);
 
@@ -33,14 +35,16 @@ void test_func_all(TIndex& index) {
     REQUIRE_THROWS_AS(index.get(1), osmium::not_found);
     REQUIRE_THROWS_AS(index.get(5), osmium::not_found);
     REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+    REQUIRE_THROWS_WITH(index.get(0), "id 0 not found");
+    REQUIRE_THROWS_WITH(index.get(1), "id 1 not found");
 }
 
 template <typename TIndex>
 void test_func_real(TIndex& index) {
-    osmium::unsigned_object_id_type id1 = 12;
-    osmium::unsigned_object_id_type id2 = 3;
-    osmium::Location loc1{1.2, 4.5};
-    osmium::Location loc2{3.5, -7.2};
+    const osmium::unsigned_object_id_type id1 = 12;
+    const osmium::unsigned_object_id_type id2 = 3;
+    const osmium::Location loc1{1.2, 4.5};
+    const osmium::Location loc2{3.5, -7.2};
 
     index.set(id1, loc1);
     index.set(id2, loc2);
@@ -66,115 +70,116 @@ void test_func_real(TIndex& index) {
     REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
 }
 
-TEST_CASE("IdToLocation") {
-
-    SECTION("Dummy") {
-        using index_type = osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location>;
+TEST_CASE("Map Id to location: Dummy") {
+    using index_type = osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location>;
 
-        index_type index1;
+    index_type index1;
 
-        REQUIRE(0 == index1.size());
-        REQUIRE(0 == index1.used_memory());
+    REQUIRE(0 == index1.size());
+    REQUIRE(0 == index1.used_memory());
 
-        test_func_all<index_type>(index1);
+    test_func_all<index_type>(index1);
 
-        REQUIRE(0 == index1.size());
-        REQUIRE(0 == index1.used_memory());
-    }
+    REQUIRE(0 == index1.size());
+    REQUIRE(0 == index1.used_memory());
+}
 
-    SECTION("DenseMemArray") {
-        using index_type = osmium::index::map::DenseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+TEST_CASE("Map Id to location: DenseMemArray") {
+    using index_type = osmium::index::map::DenseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
 
-        index_type index1;
-        index1.reserve(1000);
-        test_func_all<index_type>(index1);
+    index_type index1;
+    index1.reserve(1000);
+    test_func_all<index_type>(index1);
 
-        index_type index2;
-        index2.reserve(1000);
-        test_func_real<index_type>(index2);
-    }
+    index_type index2;
+    index2.reserve(1000);
+    test_func_real<index_type>(index2);
+}
 
 #ifdef __linux__
-    SECTION("DenseMmapArray") {
-        using index_type = osmium::index::map::DenseMmapArray<osmium::unsigned_object_id_type, osmium::Location>;
+TEST_CASE("Map Id to location: DenseMmapArray") {
+    using index_type = osmium::index::map::DenseMmapArray<osmium::unsigned_object_id_type, osmium::Location>;
 
-        index_type index1;
-        test_func_all<index_type>(index1);
+    index_type index1;
+    test_func_all<index_type>(index1);
 
-        index_type index2;
-        test_func_real<index_type>(index2);
-    }
+    index_type index2;
+    test_func_real<index_type>(index2);
+}
 #else
 # pragma message("not running 'DenseMapMmap' test case on this machine")
 #endif
 
-    SECTION("DenseFileArray") {
-        using index_type = osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location>;
+TEST_CASE("Map Id to location: DenseFileArray") {
+    using index_type = osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location>;
 
-        index_type index1;
-        test_func_all<index_type>(index1);
+    index_type index1;
+    test_func_all<index_type>(index1);
 
-        index_type index2;
-        test_func_real<index_type>(index2);
-    }
+    index_type index2;
+    test_func_real<index_type>(index2);
+}
 
 #ifdef OSMIUM_WITH_SPARSEHASH
 
-    SECTION("SparseMemTable") {
-        using index_type = osmium::index::map::SparseMemTable<osmium::unsigned_object_id_type, osmium::Location>;
+TEST_CASE("Map Id to location: SparseMemTable") {
+    using index_type = osmium::index::map::SparseMemTable<osmium::unsigned_object_id_type, osmium::Location>;
 
-        index_type index1;
-        test_func_all<index_type>(index1);
+    index_type index1;
+    test_func_all<index_type>(index1);
 
-        index_type index2;
-        test_func_real<index_type>(index2);
-    }
+    index_type index2;
+    test_func_real<index_type>(index2);
+}
 
 #endif
 
-    SECTION("SparseMemMap") {
-        using index_type = osmium::index::map::SparseMemMap<osmium::unsigned_object_id_type, osmium::Location>;
+TEST_CASE("Map Id to location: SparseMemMap") {
+    using index_type = osmium::index::map::SparseMemMap<osmium::unsigned_object_id_type, osmium::Location>;
 
-        index_type index1;
-        test_func_all<index_type>(index1);
+    index_type index1;
+    test_func_all<index_type>(index1);
 
-        index_type index2;
-        test_func_real<index_type>(index2);
-    }
+    index_type index2;
+    test_func_real<index_type>(index2);
+}
 
-    SECTION("SparseMemArray") {
-        using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+TEST_CASE("Map Id to location: SparseMemArray") {
+    using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
 
-        index_type index1;
+    index_type index1;
 
-        REQUIRE(0 == index1.size());
-        REQUIRE(0 == index1.used_memory());
+    REQUIRE(0 == index1.size());
+    REQUIRE(0 == index1.used_memory());
 
-        test_func_all<index_type>(index1);
+    test_func_all<index_type>(index1);
 
-        REQUIRE(2 == index1.size());
+    REQUIRE(2 == index1.size());
 
-        index_type index2;
-        test_func_real<index_type>(index2);
-    }
+    index_type index2;
+    test_func_real<index_type>(index2);
+}
 
-    SECTION("Dynamic map choice") {
-        using map_type = osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>;
-        const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
+TEST_CASE("Map Id to location: Dynamic map choice") {
+    using map_type = osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>;
+    const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
 
-        const std::vector<std::string> map_type_names = map_factory.map_types();
-        REQUIRE(map_type_names.size() >= 5);
+    const std::vector<std::string> map_type_names = map_factory.map_types();
+    REQUIRE(map_type_names.size() >= 5);
 
-        for (const auto& map_type_name : map_type_names) {
-            std::unique_ptr<map_type> index1 = map_factory.create_map(map_type_name);
-            index1->reserve(1000);
-            test_func_all<map_type>(*index1);
+    REQUIRE_THROWS_AS(map_factory.create_map(""), osmium::map_factory_error);
+    REQUIRE_THROWS_AS(map_factory.create_map("does not exist"), osmium::map_factory_error);
+    REQUIRE_THROWS_WITH(map_factory.create_map(""), "Need non-empty map type name");
+    REQUIRE_THROWS_WITH(map_factory.create_map("does not exist"), "Support for map type 'does not exist' not compiled into this binary");
 
-            std::unique_ptr<map_type> index2 = map_factory.create_map(map_type_name);
-            index2->reserve(1000);
-            test_func_real<map_type>(*index2);
-        }
-    }
+    for (const auto& map_type_name : map_type_names) {
+        std::unique_ptr<map_type> index1 = map_factory.create_map(map_type_name);
+        index1->reserve(1000);
+        test_func_all<map_type>(*index1);
 
+        std::unique_ptr<map_type> index2 = map_factory.create_map(map_type_name);
+        index2->reserve(1000);
+        test_func_real<map_type>(*index2);
+    }
 }
 
diff --git a/test/t/io/test_compression_factory.cpp b/test/t/io/test_compression_factory.cpp
new file mode 100644
index 0000000..5428d82
--- /dev/null
+++ b/test/t/io/test_compression_factory.cpp
@@ -0,0 +1,27 @@
+
+#include "catch.hpp"
+
+#include <osmium/io/compression.hpp>
+
+TEST_CASE("Compression factory") {
+    const auto& factory = osmium::io::CompressionFactory::instance();
+
+    SECTION("compressor") {
+        REQUIRE(factory.create_compressor(osmium::io::file_compression::none, -1, osmium::io::fsync::no));
+    }
+
+    SECTION("decompressor") {
+        REQUIRE(factory.create_decompressor(osmium::io::file_compression::none, nullptr, 0));
+    }
+
+    SECTION("fail on undefined compression") {
+        REQUIRE_THROWS_AS({
+            factory.create_compressor(osmium::io::file_compression::gzip, -1, osmium::io::fsync::no);
+        }, osmium::unsupported_file_format_error);
+        REQUIRE_THROWS_WITH({
+            factory.create_compressor(osmium::io::file_compression::gzip, -1, osmium::io::fsync::no);
+        }, "Support for compression 'gzip' not compiled into this binary");
+    }
+
+}
+
diff --git a/test/t/io/test_reader_with_mock_parser.cpp b/test/t/io/test_reader_with_mock_parser.cpp
index c5c9975..b1076cc 100644
--- a/test/t/io/test_reader_with_mock_parser.cpp
+++ b/test/t/io/test_reader_with_mock_parser.cpp
@@ -24,9 +24,9 @@ public:
     MockParser(osmium::io::detail::future_string_queue_type& input_queue,
                osmium::io::detail::future_buffer_queue_type& output_queue,
                std::promise<osmium::io::Header>& header_promise,
-               osmium::osm_entity_bits::type read_types,
+               osmium::io::detail::reader_options options,
                const std::string& fail_in) :
-        Parser(input_queue, output_queue, header_promise, read_types),
+        Parser(input_queue, output_queue, header_promise, options),
         m_fail_in(fail_in) {
     }
 
@@ -59,8 +59,8 @@ TEST_CASE("Test Reader using MockParser") {
         [&](osmium::io::detail::future_string_queue_type& input_queue,
             osmium::io::detail::future_buffer_queue_type& output_queue,
             std::promise<osmium::io::Header>& header_promise,
-            osmium::osm_entity_bits::type read_which_entities) {
-        return std::unique_ptr<osmium::io::detail::Parser>(new MockParser(input_queue, output_queue, header_promise, read_which_entities, fail_in));
+            osmium::io::detail::reader_options options) {
+        return std::unique_ptr<osmium::io::detail::Parser>(new MockParser(input_queue, output_queue, header_promise, options, fail_in));
     });
 
     SECTION("no failure") {
diff --git a/test/t/buffer/test_buffer_basics.cpp b/test/t/memory/test_buffer_basics.cpp
similarity index 100%
rename from test/t/buffer/test_buffer_basics.cpp
rename to test/t/memory/test_buffer_basics.cpp
diff --git a/test/t/buffer/test_buffer_node.cpp b/test/t/memory/test_buffer_node.cpp
similarity index 50%
rename from test/t/buffer/test_buffer_node.cpp
rename to test/t/memory/test_buffer_node.cpp
index ba2431b..040cbb8 100644
--- a/test/t/buffer/test_buffer_node.cpp
+++ b/test/t/memory/test_buffer_node.cpp
@@ -3,7 +3,7 @@
 #include <osmium/builder/osm_object_builder.hpp>
 #include <osmium/osm/node.hpp>
 
-void check_node_1(osmium::Node& node) {
+void check_node_1(const osmium::Node& node) {
     REQUIRE(1 == node.id());
     REQUIRE(3 == node.version());
     REQUIRE(true == node.visible());
@@ -11,9 +11,9 @@ void check_node_1(osmium::Node& node) {
     REQUIRE(21 == node.uid());
     REQUIRE(123 == uint32_t(node.timestamp()));
     REQUIRE(osmium::Location(3.5, 4.7) == node.location());
-    REQUIRE(std::string("testuser") == node.user());
+    REQUIRE(std::string{"testuser"} == node.user());
 
-    for (osmium::memory::Item& item : node) {
+    for (const osmium::memory::Item& item : node) {
         REQUIRE(osmium::item_type::tag_list == item.type());
     }
 
@@ -22,7 +22,7 @@ void check_node_1(osmium::Node& node) {
     REQUIRE(0 == std::distance(node.tags().begin(), node.tags().end()));
 }
 
-void check_node_2(osmium::Node& node) {
+void check_node_2(const osmium::Node& node) {
     REQUIRE(2 == node.id());
     REQUIRE(3 == node.version());
     REQUIRE(true == node.visible());
@@ -30,9 +30,9 @@ void check_node_2(osmium::Node& node) {
     REQUIRE(21 == node.uid());
     REQUIRE(123 == uint32_t(node.timestamp()));
     REQUIRE(osmium::Location(3.5, 4.7) == node.location());
-    REQUIRE(std::string("testuser") == node.user());
+    REQUIRE(std::string{"testuser"} == node.user());
 
-    for (osmium::memory::Item& item : node) {
+    for (const osmium::memory::Item& item : node) {
         REQUIRE(osmium::item_type::tag_list == item.type());
     }
 
@@ -61,60 +61,52 @@ TEST_CASE("Node in Buffer") {
     constexpr size_t buffer_size = 10000;
     unsigned char data[buffer_size];
 
-    osmium::memory::Buffer buffer(data, buffer_size, 0);
+    osmium::memory::Buffer buffer{data, buffer_size, 0};
 
     SECTION("Add node to buffer") {
 
         {
             // add node 1
-            osmium::builder::NodeBuilder node_builder(buffer);
-            osmium::Node& node = node_builder.object();
-            REQUIRE(osmium::item_type::node == node.type());
-
-            node.set_id(1);
-            node.set_version(3);
-            node.set_visible(true);
-            node.set_changeset(333);
-            node.set_uid(21);
-            node.set_timestamp(123);
-            node.set_location(osmium::Location(3.5, 4.7));
-
-            node_builder.add_user("testuser");
-
-            buffer.commit();
+            osmium::builder::NodeBuilder node_builder{buffer};
+
+            node_builder.set_id(1)
+                .set_version(3)
+                .set_visible(true)
+                .set_changeset(333)
+                .set_uid(21)
+                .set_timestamp(123)
+                .set_location(osmium::Location{3.5, 4.7})
+                .set_user("testuser");
         }
 
+        buffer.commit();
+
         {
             // add node 2
-            osmium::builder::NodeBuilder node_builder(buffer);
-            osmium::Node& node = node_builder.object();
-            REQUIRE(osmium::item_type::node == node.type());
-
-            node.set_id(2);
-            node.set_version(3);
-            node.set_visible(true);
-            node.set_changeset(333);
-            node.set_uid(21);
-            node.set_timestamp(123);
-            node.set_location(osmium::Location(3.5, 4.7));
-
-            node_builder.add_user("testuser");
-
-            {
-                osmium::builder::TagListBuilder tag_builder(buffer, &node_builder);
-                tag_builder.add_tag("amenity", "bank");
-                tag_builder.add_tag("name", "OSM Savings");
-            }
-
-            buffer.commit();
+            osmium::builder::NodeBuilder node_builder{buffer};
+
+            node_builder.set_id(2)
+                .set_version(3)
+                .set_visible(true)
+                .set_changeset(333)
+                .set_uid(21)
+                .set_timestamp(123)
+                .set_location(osmium::Location{3.5, 4.7})
+                .set_user("testuser");
+
+            osmium::builder::TagListBuilder tag_builder{node_builder};
+            tag_builder.add_tag("amenity", "bank");
+            tag_builder.add_tag("name", "OSM Savings");
         }
 
+        buffer.commit();
+
         REQUIRE(2 == std::distance(buffer.begin(), buffer.end()));
         int item_no = 0;
-        for (osmium::memory::Item& item : buffer) {
+        for (const osmium::memory::Item& item : buffer) {
             REQUIRE(osmium::item_type::node == item.type());
 
-            osmium::Node& node = static_cast<osmium::Node&>(item);
+            const osmium::Node& node = static_cast<const osmium::Node&>(item);
 
             switch (item_no) {
                 case 0:
@@ -137,24 +129,21 @@ TEST_CASE("Node in Buffer") {
 
         {
             // add node 1
-            osmium::builder::NodeBuilder node_builder(buffer);
-            osmium::Node& node = node_builder.object();
-            REQUIRE(osmium::item_type::node == node.type());
-
-            node.set_id(1);
-            node.set_version(3);
-            node.set_visible(true);
-            node.set_changeset(333);
-            node.set_uid(21);
-            node.set_timestamp(123);
-            node.set_location(osmium::Location(3.5, 4.7));
-
-            node_builder.add_user("testuser");
-
-            buffer.commit();
+            osmium::builder::NodeBuilder node_builder{buffer};
+
+            node_builder.set_id(1)
+                .set_version(3)
+                .set_visible(true)
+                .set_changeset(333)
+                .set_uid(21)
+                .set_timestamp(123)
+                .set_location(osmium::Location{3.5, 4.7})
+                .set_user("testuser");
         }
 
-        osmium::memory::Buffer buffer2(buffer_size, osmium::memory::Buffer::auto_grow::yes);
+        buffer.commit();
+
+        osmium::memory::Buffer buffer2{buffer_size, osmium::memory::Buffer::auto_grow::yes};
 
         buffer2.add_buffer(buffer);
         buffer2.commit();
@@ -169,24 +158,21 @@ TEST_CASE("Node in Buffer") {
 
         {
             // add node 1
-            osmium::builder::NodeBuilder node_builder(buffer);
-            osmium::Node& node = node_builder.object();
-            REQUIRE(osmium::item_type::node == node.type());
-
-            node.set_id(1);
-            node.set_version(3);
-            node.set_visible(true);
-            node.set_changeset(333);
-            node.set_uid(21);
-            node.set_timestamp(123);
-            node.set_location(osmium::Location(3.5, 4.7));
-
-            node_builder.add_user("testuser");
-
-            buffer.commit();
+            osmium::builder::NodeBuilder node_builder{buffer};
+
+            node_builder.set_id(1)
+                .set_version(3)
+                .set_visible(true)
+                .set_changeset(333)
+                .set_uid(21)
+                .set_timestamp(123)
+                .set_location(osmium::Location{3.5, 4.7})
+                .set_user("testuser");
         }
 
-        osmium::memory::Buffer buffer2(buffer_size, osmium::memory::Buffer::auto_grow::yes);
+        buffer.commit();
+
+        osmium::memory::Buffer buffer2{buffer_size, osmium::memory::Buffer::auto_grow::yes};
 
         std::copy(buffer.begin(), buffer.end(), std::back_inserter(buffer2));
 
diff --git a/test/t/buffer/test_buffer_purge.cpp b/test/t/memory/test_buffer_purge.cpp
similarity index 60%
rename from test/t/buffer/test_buffer_purge.cpp
rename to test/t/memory/test_buffer_purge.cpp
index a72db1b..e61e08d 100644
--- a/test/t/buffer/test_buffer_purge.cpp
+++ b/test/t/memory/test_buffer_purge.cpp
@@ -17,9 +17,9 @@ struct CallbackClass {
 TEST_CASE("Purge data from buffer") {
 
     constexpr size_t buffer_size = 10000;
+    osmium::memory::Buffer buffer{buffer_size};
 
     SECTION("purge empty buffer") {
-        osmium::memory::Buffer buffer(buffer_size);
         REQUIRE(std::distance(buffer.begin(), buffer.end()) == 0);
 
         CallbackClass callback;
@@ -30,15 +30,13 @@ TEST_CASE("Purge data from buffer") {
     }
 
     SECTION("purge buffer with one object but nothing to delete") {
-        osmium::memory::Buffer buffer(buffer_size);
-
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser");
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser");
         }
         buffer.commit();
         REQUIRE(std::distance(buffer.begin(), buffer.end()) == 1);
-        size_t committed = buffer.committed();
+        const size_t committed = buffer.committed();
 
         CallbackClass callback;
         buffer.purge_removed(&callback);
@@ -49,12 +47,10 @@ TEST_CASE("Purge data from buffer") {
     }
 
     SECTION("purge buffer with one object which gets deleted") {
-        osmium::memory::Buffer buffer(buffer_size);
-
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser");
-            node_builder.object().set_removed(true);
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser");
+            node_builder.set_removed(true);
         }
         buffer.commit();
         REQUIRE(std::distance(buffer.begin(), buffer.end()) == 1);
@@ -68,21 +64,19 @@ TEST_CASE("Purge data from buffer") {
     }
 
     SECTION("purge buffer with two objects, first gets deleted") {
-        osmium::memory::Buffer buffer(buffer_size);
-
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser");
-            node_builder.object().set_removed(true);
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser");
+            node_builder.set_removed(true);
         }
         buffer.commit();
-        size_t size1 = buffer.committed();
+        const size_t size1 = buffer.committed();
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser");
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser");
         }
         buffer.commit();
-        size_t size2 = buffer.committed() - size1;
+        const size_t size2 = buffer.committed() - size1;
         REQUIRE(std::distance(buffer.begin(), buffer.end()) == 2);
 
         CallbackClass callback;
@@ -94,18 +88,16 @@ TEST_CASE("Purge data from buffer") {
     }
 
     SECTION("purge buffer with two objects, second gets deleted") {
-        osmium::memory::Buffer buffer(buffer_size);
-
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser_longer_name");
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser_longer_name");
         }
         buffer.commit();
         size_t size1 = buffer.committed();
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser");
-            node_builder.object().set_removed(true);
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser");
+            node_builder.set_removed(true);
         }
         buffer.commit();
 
@@ -120,24 +112,22 @@ TEST_CASE("Purge data from buffer") {
     }
 
     SECTION("purge buffer with three objects, middle one gets deleted") {
-        osmium::memory::Buffer buffer(buffer_size);
-
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser_longer_name");
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser_longer_name");
         }
         buffer.commit();
 
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser");
-            node_builder.object().set_removed(true);
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser");
+            node_builder.set_removed(true);
         }
         buffer.commit();
 
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("sn");
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("sn");
         }
         buffer.commit();
 
@@ -151,26 +141,24 @@ TEST_CASE("Purge data from buffer") {
     }
 
     SECTION("purge buffer with three objects, all get deleted") {
-        osmium::memory::Buffer buffer(buffer_size);
-
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser_longer_name");
-            node_builder.object().set_removed(true);
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser_longer_name");
+            node_builder.set_removed(true);
         }
         buffer.commit();
 
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("testuser");
-            node_builder.object().set_removed(true);
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("testuser");
+            node_builder.set_removed(true);
         }
         buffer.commit();
 
         {
-            osmium::builder::NodeBuilder node_builder(buffer);
-            node_builder.add_user("sn");
-            node_builder.object().set_removed(true);
+            osmium::builder::NodeBuilder node_builder{buffer};
+            node_builder.set_user("sn");
+            node_builder.set_removed(true);
         }
         buffer.commit();
 
diff --git a/test/t/basic/test_area.cpp b/test/t/osm/test_area.cpp
similarity index 100%
rename from test/t/basic/test_area.cpp
rename to test/t/osm/test_area.cpp
diff --git a/test/t/basic/test_box.cpp b/test/t/osm/test_box.cpp
similarity index 100%
rename from test/t/basic/test_box.cpp
rename to test/t/osm/test_box.cpp
diff --git a/test/t/basic/test_changeset.cpp b/test/t/osm/test_changeset.cpp
similarity index 73%
rename from test/t/basic/test_changeset.cpp
rename to test/t/osm/test_changeset.cpp
index 36c4778..9be8fbb 100644
--- a/test/t/basic/test_changeset.cpp
+++ b/test/t/osm/test_changeset.cpp
@@ -9,7 +9,7 @@
 using namespace osmium::builder::attr;
 
 TEST_CASE("Build changeset") {
-    osmium::memory::Buffer buffer(10 * 1000);
+    osmium::memory::Buffer buffer{10 * 1000};
 
     osmium::builder::add_changeset(buffer,
         _cid(42),
@@ -88,58 +88,57 @@ TEST_CASE("Build changeset") {
 }
 
 TEST_CASE("Create changeset without helper") {
-    osmium::memory::Buffer buffer(10 * 1000);
-    osmium::builder::ChangesetBuilder builder(buffer);
-
-    osmium::Changeset& cs1 = builder.object();
-    cs1.set_id(42)
-       .set_created_at(100)
-       .set_closed_at(200)
-       .set_num_changes(7)
-       .set_num_comments(2)
-       .set_uid(9);
-
-    builder.add_user("user");
+    osmium::memory::Buffer buffer{10 * 1000};
     {
-        osmium::builder::TagListBuilder tl_builder(buffer, &builder);
-        tl_builder.add_tag("key1", "val1");
-        tl_builder.add_tag("key2", "val2");
-    }
-
-    {
-        osmium::builder::ChangesetDiscussionBuilder disc_builder(buffer, &builder);
+        osmium::builder::ChangesetBuilder builder{buffer};
+
+        builder.set_id(42)
+            .set_created_at(100)
+            .set_closed_at(200)
+            .set_num_changes(7)
+            .set_num_comments(2)
+            .set_uid(9)
+            .set_user("user");
+
+        {
+            osmium::builder::TagListBuilder tl_builder{builder};
+            tl_builder.add_tag("key1", "val1");
+            tl_builder.add_tag("key2", "val2");
+        }
+
+        osmium::builder::ChangesetDiscussionBuilder disc_builder{builder};
         disc_builder.add_comment(osmium::Timestamp(300), 10, "user2");
         disc_builder.add_comment_text("foo");
         disc_builder.add_comment(osmium::Timestamp(400), 9, "user");
         disc_builder.add_comment_text("bar");
     }
 
-    buffer.commit();
+    const auto& cs = buffer.get<osmium::Changeset>(buffer.commit());
 
-    REQUIRE(42 == cs1.id());
-    REQUIRE(9 == cs1.uid());
-    REQUIRE(7 == cs1.num_changes());
-    REQUIRE(2 == cs1.num_comments());
-    REQUIRE(true == cs1.closed());
-    REQUIRE(osmium::Timestamp(100) == cs1.created_at());
-    REQUIRE(osmium::Timestamp(200) == cs1.closed_at());
-    REQUIRE(2 == cs1.tags().size());
-    REQUIRE(std::string("user") == cs1.user());
+    REQUIRE(42 == cs.id());
+    REQUIRE(9 == cs.uid());
+    REQUIRE(7 == cs.num_changes());
+    REQUIRE(2 == cs.num_comments());
+    REQUIRE(true == cs.closed());
+    REQUIRE(osmium::Timestamp(100) == cs.created_at());
+    REQUIRE(osmium::Timestamp(200) == cs.closed_at());
+    REQUIRE(2 == cs.tags().size());
+    REQUIRE(std::string("user") == cs.user());
 
-    auto cit = cs1.discussion().begin();
+    auto cit = cs.discussion().begin();
 
-    REQUIRE(cit != cs1.discussion().end());
+    REQUIRE(cit != cs.discussion().end());
     REQUIRE(cit->date() == osmium::Timestamp(300));
     REQUIRE(cit->uid() == 10);
     REQUIRE(std::string("user2") == cit->user());
     REQUIRE(std::string("foo") == cit->text());
 
-    REQUIRE(++cit != cs1.discussion().end());
+    REQUIRE(++cit != cs.discussion().end());
     REQUIRE(cit->date() == osmium::Timestamp(400));
     REQUIRE(cit->uid() == 9);
     REQUIRE(std::string("user") == cit->user());
     REQUIRE(std::string("bar") == cit->text());
 
-    REQUIRE(++cit == cs1.discussion().end());
+    REQUIRE(++cit == cs.discussion().end());
 }
 
diff --git a/test/t/basic/test_crc.cpp b/test/t/osm/test_crc.cpp
similarity index 100%
rename from test/t/basic/test_crc.cpp
rename to test/t/osm/test_crc.cpp
diff --git a/test/t/osm/test_entity_bits.cpp b/test/t/osm/test_entity_bits.cpp
new file mode 100644
index 0000000..a124fa3
--- /dev/null
+++ b/test/t/osm/test_entity_bits.cpp
@@ -0,0 +1,62 @@
+#include "catch.hpp"
+
+#include <osmium/osm/entity_bits.hpp>
+
+static_assert((osmium::osm_entity_bits::node
+              |osmium::osm_entity_bits::way
+              |osmium::osm_entity_bits::relation)
+              == osmium::osm_entity_bits::nwr, "entity_bits nwr failed");
+
+static_assert((osmium::osm_entity_bits::node
+              |osmium::osm_entity_bits::way
+              |osmium::osm_entity_bits::relation
+              |osmium::osm_entity_bits::area)
+              == osmium::osm_entity_bits::nwra, "entity_bits nwra failed");
+
+static_assert((osmium::osm_entity_bits::nwra
+              |osmium::osm_entity_bits::changeset)
+              == osmium::osm_entity_bits::all, "entity_bits all failed");
+
+static_assert((osmium::osm_entity_bits::all
+              &osmium::osm_entity_bits::node)
+              == osmium::osm_entity_bits::node, "entity_bits node failed");
+
+static_assert((~osmium::osm_entity_bits::all) == osmium::osm_entity_bits::nothing, "entity_bits nothing is the inverse of all");
+static_assert((~osmium::osm_entity_bits::nothing) == osmium::osm_entity_bits::all, "entity_bits all is the inverse of nothing");
+static_assert((~osmium::osm_entity_bits::changeset) == osmium::osm_entity_bits::nwra, "entity_bits nwra is the inverse of changeset");
+
+TEST_CASE("Bitwise 'and' and 'or' on entity bits") {
+    osmium::osm_entity_bits::type entities = osmium::osm_entity_bits::node | osmium::osm_entity_bits::way;
+    REQUIRE(entities == (osmium::osm_entity_bits::node | osmium::osm_entity_bits::way));
+
+    entities |= osmium::osm_entity_bits::relation;
+    REQUIRE((entities & osmium::osm_entity_bits::object));
+
+    entities |= osmium::osm_entity_bits::area;
+    REQUIRE(entities == osmium::osm_entity_bits::object);
+
+    REQUIRE_FALSE((entities & osmium::osm_entity_bits::changeset));
+
+    entities &= osmium::osm_entity_bits::node;
+    REQUIRE((entities & osmium::osm_entity_bits::node));
+    REQUIRE_FALSE((entities & osmium::osm_entity_bits::way));
+    REQUIRE(entities == osmium::osm_entity_bits::node);
+}
+
+TEST_CASE("Bitwise 'not' on entity bits") {
+    REQUIRE(~osmium::osm_entity_bits::all == osmium::osm_entity_bits::nothing);
+    REQUIRE(~osmium::osm_entity_bits::nothing == osmium::osm_entity_bits::all);
+    REQUIRE(~osmium::osm_entity_bits::node == (osmium::osm_entity_bits::way | osmium::osm_entity_bits::relation | osmium::osm_entity_bits::area | osmium::osm_entity_bits::changeset));
+    REQUIRE(~osmium::osm_entity_bits::nwr == (osmium::osm_entity_bits::area | osmium::osm_entity_bits::changeset));
+    REQUIRE(~osmium::osm_entity_bits::nwra == osmium::osm_entity_bits::changeset);
+}
+
+TEST_CASE("Converting item types to entity bits") {
+    REQUIRE(osmium::osm_entity_bits::nothing   == osmium::osm_entity_bits::from_item_type(osmium::item_type::undefined));
+    REQUIRE(osmium::osm_entity_bits::node      == osmium::osm_entity_bits::from_item_type(osmium::item_type::node));
+    REQUIRE(osmium::osm_entity_bits::way       == osmium::osm_entity_bits::from_item_type(osmium::item_type::way));
+    REQUIRE(osmium::osm_entity_bits::relation  == osmium::osm_entity_bits::from_item_type(osmium::item_type::relation));
+    REQUIRE(osmium::osm_entity_bits::changeset == osmium::osm_entity_bits::from_item_type(osmium::item_type::changeset));
+    REQUIRE(osmium::osm_entity_bits::area      == osmium::osm_entity_bits::from_item_type(osmium::item_type::area));
+}
+
diff --git a/test/t/basic/test_location.cpp b/test/t/osm/test_location.cpp
similarity index 78%
rename from test/t/basic/test_location.cpp
rename to test/t/osm/test_location.cpp
index dc5b378..7abf779 100644
--- a/test/t/basic/test_location.cpp
+++ b/test/t/osm/test_location.cpp
@@ -166,41 +166,44 @@ TEST_CASE("Location hash") {
     }
 }
 
-#define CR(s, v, r) { \
-                const char* strm = "-" s; \
-                const char* strp = strm + 1; \
-                REQUIRE(std::atof(strp) == Approx( v / 10000000.0)); \
-                REQUIRE(std::atof(strm) == Approx(-v / 10000000.0)); \
-                const char** data = &strp; \
-                REQUIRE(osmium::detail::string_to_location_coordinate(data) == v); \
-                REQUIRE(std::string{*data} == r); \
-                data = &strm; \
-                REQUIRE(osmium::detail::string_to_location_coordinate(data) == -v); \
-                REQUIRE(std::string{*data} == r); \
-                }
-
-#define C(s, v) CR(s, v, "")
-
-#define F(s) { \
-             const char* strm = "-" s; \
-             const char* strp = strm + 1; \
-             const char** data = &strp; \
-             REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location); \
-             data = &strm; \
-             REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location); \
-             }
+void C(const char* s, long v, const char* r = "") {
+    std::string strm{"-"};
+    strm += s;
+    REQUIRE(std::atof(strm.c_str() + 1) == Approx( v / 10000000.0));
+    REQUIRE(std::atof(strm.c_str()    ) == Approx(-v / 10000000.0));
+    const char* x = strm.c_str() + 1;
+    const char** data = &x;
+    REQUIRE(osmium::detail::string_to_location_coordinate(data) == v);
+    REQUIRE(std::string{*data} == r);
+    x = strm.c_str();
+    data = &x;
+    REQUIRE(osmium::detail::string_to_location_coordinate(data) == -v);
+    REQUIRE(std::string{*data} == r);
+}
+
+void F(const char* s) {
+    std::string strm{"-"};
+    strm += s;
+    const char* x = strm.c_str();
+    const char** data = &x;
+    REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location);
+    ++x;
+    data = &x;
+    REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location);
+}
 
 TEST_CASE("Parsing coordinates from strings") {
     F("x");
     F(".");
+    F(".e2");
     F("--");
     F("");
     F(" ");
     F(" 123");
 
-    CR("123 ", 1230000000, " ");
-    CR("123x", 1230000000, "x");
-    CR("1.2x",   12000000, "x");
+    C("123 ", 1230000000, " ");
+    C("123x", 1230000000, "x");
+    C("1.2x",   12000000, "x");
 
     C("0",              0);
 
@@ -223,14 +226,19 @@ TEST_CASE("Parsing coordinates from strings") {
     F("1234");
     F("1234.");
     F("12345678901234567890");
+    F("1.1234568111111111111111111111111111111");
+    F("112.34568111111111111111111111111111111");
 
     C("0.",             0);
+    C(".0",             0);
     C("0.0",            0);
     C("1.",      10000000);
     C("1.0",     10000000);
     C("1.2",     12000000);
     C("0.1",      1000000);
+    C(".1",       1000000);
     C("0.01",      100000);
+    C(".01",       100000);
     C("0.001",      10000);
     C("0.0001",      1000);
     C("0.00001",      100);
@@ -251,6 +259,24 @@ TEST_CASE("Parsing coordinates from strings") {
     C("179.99999999", 1800000000);
     C("200.123",      2001230000);
 
+    C("8.109E-4" , 8109);
+    C("8.1090E-4" , 8109);
+    C("8.10909E-4" , 8109);
+    C("8.109095E-4" , 8109);
+    C("8.1090959E-4" , 8109);
+    C("8.10909598E-4" , 8109);
+    C("8.109095988E-4" , 8109);
+    C("8.1090959887E-4" , 8109);
+    C("8.10909598870E-4" , 8109);
+    C("8.109095988709E-4" , 8109);
+    C("8.1090959887098E-4" , 8109);
+    C("8.10909598870983E-4" , 8109);
+    C("8.109095988709837E-4" , 8109);
+    C("81.09095988709837E-4" , 81091);
+    C("810.9095988709837E-4" , 810910);
+    C(".8109095988709837E-4" , 811);
+    C(".08109095988709837E-4" , 81);
+
     C("1e2",   1000000000);
     C("1e1",    100000000);
     C("1e0",     10000000);
@@ -263,8 +289,10 @@ TEST_CASE("Parsing coordinates from strings") {
     C("1e-7",           1);
 
     C("1.0e2",   1000000000);
+    C("1.e2",    1000000000);
     C("1.1e1",    110000000);
     C("0.1e1",     10000000);
+    C(".1e1",      10000000);
     C("1.2e0",     12000000);
     C("1.9e-1",     1900000);
     C("2.0e-2",      200000);
@@ -291,32 +319,35 @@ TEST_CASE("Parsing coordinates from strings") {
     F("5.0e2");
     F("3e2");
     F("1e");
+    F("1e-");
+    F("1e1234567");
     F("0.5e");
     F("1e10");
 
-    CR("1e2 ",   1000000000, " ");
-    CR("1.1e2 ", 1100000000, " ");
-    CR("1.1e2x", 1100000000, "x");
-    CR("1.1e2:", 1100000000, ":");
+    C("1e2 ",   1000000000, " ");
+    C("1.1e2 ", 1100000000, " ");
+    C("1.1e2x", 1100000000, "x");
+    C("1.1e2:", 1100000000, ":");
 }
 
-#undef C
-#undef CR
-#undef F
-
-#define CW(v, s) buffer.clear(); \
-                 osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), v); \
-                 CHECK(buffer == s); \
-                 buffer.clear(); \
-                 osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), -v); \
-                 CHECK(buffer == "-" s);
+TEST_CASE("Writing zero coordinate into string") {
+    std::string buffer;
+    osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), 0);
+    REQUIRE(buffer == "0");
+}
 
-TEST_CASE("Writing coordinates into string") {
+void CW(long v, const char* s) {
     std::string buffer;
 
-    osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), 0);
-    CHECK(buffer == "0");
+    osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), v);
+    REQUIRE(buffer == s);
+    buffer.clear();
+    osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), -v);
+    REQUIRE(buffer[0] == '-');
+    REQUIRE_FALSE(std::strcmp(buffer.c_str() + 1, s));
+}
 
+TEST_CASE("Writing coordinate into string") {
     CW(  10000000, "1");
     CW(  90000000, "9");
     CW( 100000000, "10");
@@ -338,8 +369,6 @@ TEST_CASE("Writing coordinates into string") {
     CW(1799999999, "179.9999999");
 }
 
-#undef CW
-
 TEST_CASE("set lon/lat from string") {
     osmium::Location loc;
     loc.set_lon("1.2");
diff --git a/test/t/basic/test_node.cpp b/test/t/osm/test_node.cpp
similarity index 100%
rename from test/t/basic/test_node.cpp
rename to test/t/osm/test_node.cpp
diff --git a/test/t/basic/test_node_ref.cpp b/test/t/osm/test_node_ref.cpp
similarity index 100%
rename from test/t/basic/test_node_ref.cpp
rename to test/t/osm/test_node_ref.cpp
diff --git a/test/t/basic/test_object_comparisons.cpp b/test/t/osm/test_object_comparisons.cpp
similarity index 100%
rename from test/t/basic/test_object_comparisons.cpp
rename to test/t/osm/test_object_comparisons.cpp
diff --git a/test/t/basic/test_relation.cpp b/test/t/osm/test_relation.cpp
similarity index 100%
rename from test/t/basic/test_relation.cpp
rename to test/t/osm/test_relation.cpp
diff --git a/test/t/basic/test_timestamp.cpp b/test/t/osm/test_timestamp.cpp
similarity index 100%
rename from test/t/basic/test_timestamp.cpp
rename to test/t/osm/test_timestamp.cpp
diff --git a/test/t/basic/test_types_from_string.cpp b/test/t/osm/test_types_from_string.cpp
similarity index 100%
rename from test/t/basic/test_types_from_string.cpp
rename to test/t/osm/test_types_from_string.cpp
diff --git a/test/t/basic/test_way.cpp b/test/t/osm/test_way.cpp
similarity index 98%
rename from test/t/basic/test_way.cpp
rename to test/t/osm/test_way.cpp
index 005ef30..21258a4 100644
--- a/test/t/basic/test_way.cpp
+++ b/test/t/osm/test_way.cpp
@@ -68,7 +68,7 @@ TEST_CASE("build way with helpers") {
 
     {
         osmium::builder::WayBuilder builder(buffer);
-        builder.add_user("username");
+        builder.set_user("username");
         builder.add_tags({
             {"amenity", "restaurant"},
             {"name", "Zum goldenen Schwanen"}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/libosmium.git



More information about the Pkg-grass-devel mailing list