[libosmium] 01/06: Imported Upstream version 2.9.0

Bas Couwenberg sebastic at debian.org
Thu Sep 15 14:03:14 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 6928a9c709db4ac9fdd95e4ae7ba634283a7a27b
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Sep 15 15:21:20 2016 +0200

    Imported Upstream version 2.9.0
---
 .gitignore                                         |    2 +
 .ycm_extra_conf.py                                 |    5 +
 CHANGELOG.md                                       |   36 +-
 CMakeLists.txt                                     |    2 +-
 CONTRIBUTING.md                                    |  147 +--
 CONTRIBUTING.md => NOTES_FOR_DEVELOPERS.md         |   12 +-
 appveyor.yml                                       |   83 +-
 benchmarks/osmium_benchmark_count.cpp              |    8 +-
 benchmarks/osmium_benchmark_count_tag.cpp          |    8 +-
 benchmarks/osmium_benchmark_index_map.cpp          |   16 +-
 .../osmium_benchmark_static_vs_dynamic_index.cpp   |   58 +-
 benchmarks/osmium_benchmark_write_pbf.cpp          |   17 +-
 build-appveyor.bat                                 |  123 +++
 build-local.bat                                    |   43 +
 cmake/iwyu.sh                                      |    5 +
 examples/CMakeLists.txt                            |    8 +-
 examples/README.md                                 |   32 +
 examples/osmium_area_test.cpp                      |  131 ++-
 examples/osmium_convert.cpp                        |   92 +-
 examples/osmium_count.cpp                          |   69 +-
 examples/osmium_create_node_cache.cpp              |   55 -
 examples/osmium_debug.cpp                          |   46 +-
 examples/osmium_filter_discussions.cpp             |   48 +-
 examples/osmium_index.cpp                          |   22 +-
 examples/osmium_location_cache_create.cpp          |   87 ++
 examples/osmium_location_cache_use.cpp             |  101 ++
 examples/osmium_pub_names.cpp                      |   89 ++
 examples/osmium_read.cpp                           |   26 +-
 examples/osmium_read_with_progress.cpp             |   56 +
 examples/osmium_road_length.cpp                    |   92 ++
 examples/osmium_serdump.cpp                        |   24 +-
 examples/osmium_tiles.cpp                          |   72 ++
 examples/osmium_use_node_cache.cpp                 |   68 --
 include/osmium/area/assembler.hpp                  |   46 +-
 include/osmium/area/detail/node_ref_segment.hpp    |   17 +-
 include/osmium/area/detail/proto_ring.hpp          |    5 +-
 include/osmium/area/detail/segment_list.hpp        |   23 +-
 include/osmium/area/detail/vector.hpp              |    1 +
 include/osmium/area/multipolygon_collector.hpp     |    7 +-
 include/osmium/area/problem_reporter.hpp           |    2 +
 include/osmium/area/problem_reporter_exception.hpp |    1 +
 include/osmium/area/problem_reporter_ogr.hpp       |    8 +-
 include/osmium/builder/attr.hpp                    |   37 +-
 include/osmium/builder/builder.hpp                 |    3 +-
 include/osmium/builder/builder_helper.hpp          |    8 +-
 include/osmium/builder/osm_object_builder.hpp      |   10 +-
 include/osmium/diff_iterator.hpp                   |    2 +
 include/osmium/dynamic_handler.hpp                 |    7 +-
 include/osmium/experimental/flex_reader.hpp        |    3 +-
 include/osmium/geom/factory.hpp                    |    8 +-
 include/osmium/geom/geojson.hpp                    |    1 +
 include/osmium/geom/geos.hpp                       |   32 +-
 include/osmium/geom/haversine.hpp                  |    2 +-
 include/osmium/geom/ogr.hpp                        |    2 +-
 include/osmium/geom/projection.hpp                 |   22 +-
 include/osmium/geom/rapid_geojson.hpp              |    2 +
 include/osmium/geom/tile.hpp                       |   45 +-
 include/osmium/geom/util.hpp                       |    4 +-
 include/osmium/geom/wkb.hpp                        |    8 +-
 include/osmium/geom/wkt.hpp                        |    3 -
 include/osmium/handler.hpp                         |   56 +-
 include/osmium/handler/check_order.hpp             |    4 +-
 include/osmium/handler/node_locations_for_ways.hpp |    2 +-
 include/osmium/index/detail/create_map_with_fd.hpp |    4 +-
 include/osmium/index/detail/mmap_vector_file.hpp   |    4 +-
 include/osmium/index/detail/vector_map.hpp         |    2 +-
 include/osmium/index/detail/vector_multimap.hpp    |    2 +-
 include/osmium/index/index.hpp                     |    4 +-
 include/osmium/index/map.hpp                       |    2 +-
 include/osmium/index/map/sparse_mem_map.hpp        |    1 -
 include/osmium/io/any_input.hpp                    |    3 +-
 include/osmium/io/bzip2_compression.hpp            |   10 +-
 include/osmium/io/compression.hpp                  |   31 +-
 include/osmium/io/detail/debug_output_format.hpp   |  119 ++-
 include/osmium/io/detail/input_format.hpp          |    2 +-
 include/osmium/io/detail/o5m_input_format.hpp      |   13 +-
 include/osmium/io/detail/opl_input_format.hpp      |  156 +++
 include/osmium/io/detail/opl_output_format.hpp     |   25 +-
 include/osmium/io/detail/opl_parser_functions.hpp  |  747 ++++++++++++++
 include/osmium/io/detail/output_format.hpp         |    4 +-
 include/osmium/io/detail/pbf_decoder.hpp           |   36 +-
 include/osmium/io/detail/pbf_input_format.hpp      |   15 +-
 include/osmium/io/detail/pbf_output_format.hpp     |   14 +-
 include/osmium/io/detail/read_write.hpp            |    7 +-
 include/osmium/io/detail/string_table.hpp          |    9 +-
 include/osmium/io/detail/string_util.hpp           |   27 +-
 include/osmium/io/detail/write_thread.hpp          |    1 +
 include/osmium/io/detail/xml_input_format.hpp      |   11 +-
 include/osmium/io/detail/xml_output_format.hpp     |   11 +-
 include/osmium/io/detail/zlib.hpp                  |    6 +-
 include/osmium/io/file.hpp                         |    6 +-
 include/osmium/io/gzip_compression.hpp             |   12 +-
 include/osmium/io/input_iterator.hpp               |    1 -
 include/osmium/{version.hpp => io/opl_input.hpp}   |   18 +-
 include/osmium/io/output_iterator.hpp              |    3 -
 include/osmium/io/overwrite.hpp                    |    2 +-
 include/osmium/io/reader.hpp                       |   43 +-
 include/osmium/io/writer.hpp                       |   12 +-
 include/osmium/memory/item.hpp                     |   34 +-
 include/osmium/memory/item_iterator.hpp            |   15 +-
 include/osmium/object_pointer_collection.hpp       |    2 -
 include/osmium/{tags/taglist.hpp => opl.hpp}       |   50 +-
 include/osmium/osm.hpp                             |   15 +-
 include/osmium/osm/area.hpp                        |    2 +
 include/osmium/osm/box.hpp                         |    1 -
 include/osmium/osm/changeset.hpp                   |    7 +
 include/osmium/osm/crc.hpp                         |   28 +-
 include/osmium/osm/diff_object.hpp                 |    5 +-
 include/osmium/osm/location.hpp                    |  156 +--
 include/osmium/osm/node_ref_list.hpp               |    3 +-
 include/osmium/osm/object.hpp                      |   23 +-
 include/osmium/osm/object_comparisons.hpp          |    3 +
 include/osmium/osm/relation.hpp                    |    2 +-
 include/osmium/osm/segment.hpp                     |    1 -
 include/osmium/osm/tag.hpp                         |    1 -
 include/osmium/osm/timestamp.hpp                   |   83 +-
 include/osmium/osm/types_from_string.hpp           |    3 +-
 include/osmium/osm/way.hpp                         |    5 +-
 include/osmium/relations/collector.hpp             |   14 +-
 include/osmium/relations/detail/member_meta.hpp    |    2 -
 include/osmium/tags/filter.hpp                     |    1 +
 include/osmium/tags/taglist.hpp                    |    2 +-
 include/osmium/thread/pool.hpp                     |    4 +-
 include/osmium/thread/queue.hpp                    |    4 +-
 include/osmium/thread/util.hpp                     |    2 +
 include/osmium/util/delta.hpp                      |    3 +-
 include/osmium/util/double.hpp                     |    3 +-
 include/osmium/util/file.hpp                       |   82 +-
 include/osmium/util/iterator.hpp                   |    3 +-
 include/osmium/util/memory_mapping.hpp             |    9 +-
 include/osmium/util/options.hpp                    |    9 +-
 include/osmium/util/progress_bar.hpp               |  179 ++++
 include/osmium/util/string.hpp                     |    2 +-
 include/osmium/util/verbose_output.hpp             |    8 +-
 include/osmium/version.hpp                         |    4 +-
 include/protozero/iterators.hpp                    |   10 +-
 include/protozero/pbf_writer.hpp                   |    8 +-
 include/protozero/version.hpp                      |    4 +-
 osmium.imp                                         |    9 +-
 test/CMakeLists.txt                                |    2 +
 test/data-tests/include/common.hpp                 |    6 +-
 test/data-tests/testdata-multipolygon.cpp          |   56 +-
 test/data-tests/testdata-overview.cpp              |   23 +-
 test/data-tests/testdata-testcases.cpp             |    6 +-
 test/data-tests/testdata-xml.cpp                   |   90 +-
 test/t/basic/test_area.cpp                         |   78 ++
 test/t/basic/test_location.cpp                     |   93 +-
 test/t/basic/test_node.cpp                         |   68 +-
 test/t/basic/test_timestamp.cpp                    |   44 +
 test/t/geom/test_tile.cpp                          |  140 +--
 test/t/geom/test_wkb.cpp                           |  167 +--
 test/t/geom/test_wkt.cpp                           |  188 ++--
 test/t/index/test_id_to_location.cpp               |   26 +-
 test/t/io/test_opl_parser.cpp                      | 1075 ++++++++++++++++++++
 test/t/io/test_reader_with_mock_decompression.cpp  |   10 +-
 test/t/io/test_reader_with_mock_parser.cpp         |    6 +-
 test/t/io/test_writer_with_mock_compression.cpp    |    2 +-
 test/t/tags/test_filter.cpp                        |    6 +-
 test/t/util/test_cast_with_assert.cpp              |    2 +-
 159 files changed, 4938 insertions(+), 1368 deletions(-)

diff --git a/.gitignore b/.gitignore
index 5013903..79e5e65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 *.swp
 .ycm_extra_conf.pyc
+/build
+/libosmium-deps
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
index 2b87306..6fc1937 100644
--- a/.ycm_extra_conf.py
+++ b/.ycm_extra_conf.py
@@ -29,6 +29,11 @@ flags = [
 '-x',
 'c++',
 
+# workaround for https://github.com/Valloric/YouCompleteMe/issues/303
+# also see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=800618
+'-isystem',
+'/usr/lib/ycmd/clang_includes/',
+
 # libosmium include dirs
 '-I%s/include' % basedir,
 '-I%s/test/include' % basedir,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb192ab..0d132ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,39 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 
+## [2.9.0] - 2016-09-15
+
+### Added
+
+- Support for reading OPL files.
+- For diff output OSM objects in buffers can be marked as only in one or the
+  other file. The OPL and debug output formats support diff output based on
+  this.
+- Add documentation and range checks to `Tile` struct.
+- More documentation.
+- More examples and more extensive comments on examples.
+- Support for a progress report in `osmium::io::Reader()` and a `ProgressBar`
+  utility class to use it.
+- New `OSMObject::set_timestamp(const char*)` function.
+
+### Changed
+
+- Parse coordinates in scientific notations ourselves.
+- Updated included protozero version to 1.4.2.
+- Lots of one-argument constructors are now explicit.
+- Timestamp parser now uses our own implementation instead of strptime.
+  This is faster and independant of locale settings.
+- More cases of invalid areas with duplicate segments are reported as
+  errors.
+
+### Fixed
+
+- Fixed a problem limiting cache file sizes on Windows to 32 bit.
+- Fixed includes.
+- Exception messages for invalid areas do not report "area contains no rings"
+  any more, but "invalid area".
+
+
 ## [2.8.0] - 2016-08-04
 
 ### Added
@@ -377,7 +410,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.8.0...HEAD
+[unreleased]: https://github.com/osmcode/libosmium/compare/v2.9.0...HEAD
+[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
 [2.7.1]: https://github.com/osmcode/libosmium/compare/v2.7.0...v2.7.1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c82cdd0..095137b 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 8)
+set(LIBOSMIUM_VERSION_MINOR 9)
 set(LIBOSMIUM_VERSION_PATCH 0)
 
 set(LIBOSMIUM_VERSION
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1064b94..a054f36 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,143 +1,12 @@
 
-# Notes for Developers
+Some rules for contributing to this project:
 
-Read this if you want to contribute to Libosmium.
+* Please open a separate issue for each problem, question, or comment you have.
+  Do not re-use existing issues for other topics, even if they are similar. This
+  keeps issues small and manageable and makes it much easier to follow through
+  and make sure each problem is taken care of.
 
-
-## Versioning
-
-Osmium is currently considered in beta and doesn't use versioning yet. Proper
-versions will be introduced as soon as it is somewhat stable.
-
-
-## Namespace
-
-All Osmium code MUST be in the `osmium` namespace or one of its sub-namespaces.
-
-
-## Include-Only
-
-Osmium is a include-only library. You can't compile the library itself. There
-is no libosmium.so.
-
-One drawback ist that you can't have static data in classes, because there
-is no place to put this data.
-
-All free functions must be declared `inline`.
-
-
-## Coding Conventions
-
-These coding conventions have been changing over time and some code is still
-different.
-
-* All include files have `#ifdef` guards around them, macros are the path name
-  in all uppercase where the slashes (`/`) have been changed to underscore (`_`).
-* Class names begin with uppercase chars and use CamelCase. Smaller helper
-  classes are usually defined as struct and have lowercase names.
-* Macros (and only macros) are all uppercase. Use macros sparingly, usually
-  a simple (maybe constexpr) inline function is better. Undef macros after use
-  if possible.
-* Macros should only be used for controlling which parts of the code should be
-  included when compiling or to avoid major code repetitions.
-* Variables, attributes, and function names are lowercase with
-  `underscores_between_words`.
-* Class attribute names start with `m_` (member).
-* Use `descriptive_variable_names`, exceptions are well-established conventions
-  like `i` for a loop variable. Iterators are usually called `it`.
-* Declare variables where they are first used (C++ style), not at the beginning
-  of a function (old C style).
-* Names from external namespaces (even `std`) are always mentioned explicitly.
-  Do not use `using` (except for `std::swap`). This way we can't even by
-  accident pollute the namespace of the code using Osmium.
-* Always use the standard swap idiom: `using std::swap; swap(foo, bar);`.
-* `#include` directives appear in three "blocks" after the copyright notice.
-  The blocks are separated by blank lines. First block contains `#include`s for
-  standard C/C++ includes, second block for any external libs used, third
-  block for osmium internal includes. Within each block `#include`s are usually
-  sorted by path name. All `#include`s use `<>` syntax not `""`.
-* Names not to be used from outside the library should be in a namespace
-  called `detail` under the namespace where they would otherwise appear. If
-  whole include files are never meant to be included from outside they should
-  be in a subdirectory called `detail`.
-* All files have suffix `.hpp`.
-* Closing } of all classes and namespaces should have a trailing comment
-  with the name of the class/namespace.
-* All constructors with one (or more arguments if they have a default) should
-  be declared "explicit" unless there is a reason for them not to be. Document
-  that reason.
-* If a class has any of the special methods (copy/move constructor/assigment,
-  destructor) it should have all of them, possibly marking them as default or
-  deleted.
-* Typedefs have `names_like_this_type` which end in `_type`. Typedefs should
-  use the new `using foo_type = bar` syntax instead of the old
-  `typedef bar foo_type`.
-* Template parameters are single uppercase letters or start with uppercase `T`
-  and use CamelCase.
-* Always use `typename` in templates, not `class`: `template <typename T>`.
-* The ellipsis in variadic template never has a space to the left of it and
-  always has a space to the right: `template <typename... TArgs>` etc.
-
-Keep to the indentation and other styles used in the code. Use `make indent`
-in the toplevel directory to fix indentation and styling. It calls `astyle`
-with the right parameters. This program is in the `astyle` Debian package.
-
-
-## C++11
-
-Osmium uses C++11 and you can use its features such as auto, lambdas,
-threading, etc. There are a few features we do not use, because even modern
-compilers don't support them yet. This list might change as we get more data
-about which compilers support which feature and what operating system versions
-or distributions have which versions of these compilers installed.
-
-GCC 4.6   - too old, not supported (Ubuntu 12.04 LTS)
-GCC 4.7.2 - can probably not be supported (Debian wheezy)
-GCC 4.7.3 - probably works
-GCC 4.8   - works and is supported from here on
-clang 3.0 - too old, not supported (Debian wheezy, Ubuntu 12.04 LTS)
-clang 3.2 - probably works
-clang 3.5 - works and is supported from here on
-
-Use `include/osmium/util/compatibility.hpp` if there are compatibility problems
-between compilers due to different C++11 support.
-
-
-## Checking your code
-
-The Osmium makefiles use pretty draconian warning options for the compiler.
-This is good. Code MUST never produce any warnings, even with those settings.
-If absolutely necessary pragmas can be used to disable certain warnings in
-specific areas of the code.
-
-If the static code checker `cppcheck` is installed, the CMake configuration
-will add a new build target `cppcheck` that will check all `.cpp` and `.hpp`
-files. Cppcheck finds some bugs that gcc/clang doesn't. But take the result
-with a grain of salt, it also sometimes produces wrong warnings.
-
-Set `BUILD_HEADERS=ON` in the CMake config to enable compiling all include
-files on their own to check whether dependencies are all okay. All include
-files MUST include all other include files they depend on.
-
-Call `cmake/iwyu.sh` to check for proper includes and forward declarations.
-This uses the clang-based `include-what-you-use` program. Note that it does
-produce some false reports and crashes often. The `osmium.imp` file can be
-used to define mappings for iwyu. See the IWYU tool at
-<http://code.google.com/p/include-what-you-use/>.
-
-
-## Testing
-
-There are a unit tests using the Catch Unit Test Framework in the `test`
-directory and some data tests in `test/osm-testdata`. They are built by the
-default cmake config. Run `ctest` to run them. Many more tests are needed.
-
-
-## Documenting the code
-
-All namespaces, classes, functions, attributes, etc. should be documented.
-
-Osmium uses the Doxygen (www.doxygen.org) source code documentation system.
-If it is installed, the CMake configuration will add a new build target, so
-you can build it with `make doc`.
+* We'd love for you to send pull requests for fixes you have made or new features
+  you have added. Please read the [notes for developers](NOTES_FOR_DEVELOPERS.md)
+  beforehand which contain some coding guidelines.
 
diff --git a/CONTRIBUTING.md b/NOTES_FOR_DEVELOPERS.md
similarity index 97%
copy from CONTRIBUTING.md
copy to NOTES_FOR_DEVELOPERS.md
index 1064b94..3c1f73b 100644
--- a/CONTRIBUTING.md
+++ b/NOTES_FOR_DEVELOPERS.md
@@ -4,12 +4,6 @@
 Read this if you want to contribute to Libosmium.
 
 
-## Versioning
-
-Osmium is currently considered in beta and doesn't use versioning yet. Proper
-versions will be introduced as soon as it is somewhat stable.
-
-
 ## Namespace
 
 All Osmium code MUST be in the `osmium` namespace or one of its sub-namespaces.
@@ -103,6 +97,12 @@ Use `include/osmium/util/compatibility.hpp` if there are compatibility problems
 between compilers due to different C++11 support.
 
 
+## Operating systems
+
+Usually all code must work on Linux, OSX, and Windows. Execptions are allowed
+for some minor functionality, but please discuss this first.
+
+
 ## Checking your code
 
 The Osmium makefiles use pretty draconian warning options for the compiler.
diff --git a/appveyor.yml b/appveyor.yml
index e33b08c..7e6060a 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -22,86 +22,5 @@ clone_folder: c:\projects\libosmium
 
 platform: x64
 
-install:
-  # show all available env vars
-  - set
-  - echo cmake on AppVeyor
-  - cmake -version
-  - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
-  - set PATH=c:\projects\libosmium\cmake-3.1.0-win32-x86\bin;%PATH%
-  - set LODEPSDIR=c:\projects\libosmium\libosmium-deps
-  - set PROJ_LIB=%LODEPSDIR%\proj\share
-  - set GDAL_DATA=%LODEPSDIR%\gdal\data
-  #geos.dll
-  - set PATH=%LODEPSDIR%\geos\lib;%PATH%
-  #gdal.dll
-  - set PATH=%LODEPSDIR%\gdal\lib;%PATH%
-  #libexpat.dll
-  - set PATH=%LODEPSDIR%\expat\lib;%PATH%
-  #libtiff.dll
-  - set PATH=%LODEPSDIR%\libtiff\lib;%PATH%
-  #jpeg.dll
-  - set PATH=%LODEPSDIR%\jpeg\lib;%PATH%
-  #zlibwapi.dll
-  - set PATH=%LODEPSDIR%\zlib\lib;%PATH%
-  #convert backslashes in bzip2 path to forward slashes
-  #cmake cannot find it otherwise
-  - set LIBBZIP2=%LODEPSDIR%\bzip2\lib\libbz2.lib
-  - set LIBBZIP2=%LIBBZIP2:\=/%
-  - ps: Start-FileDownload https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/cmake-3.1.0-win32-x86.7z -FileName cm.7z
-  - ps: Start-FileDownload https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/libosmium-deps-win-14.0-x64.7z -FileName lodeps.7z
-  - 7z x cm.7z | %windir%\system32\find "ing archive"
-  - 7z x lodeps.7z | %windir%\system32\find "ing archive"
-  - echo %LODEPSDIR%
-  - dir %LODEPSDIR%
-  - echo our own cmake
-  - cmake -version
-  - cd c:\projects
-  - git clone --depth 1 https://github.com/osmcode/osm-testdata.git
-
 build_script:
-  - cd c:\projects\libosmium
-  - mkdir build
-  - cd build
-  - echo %config%
-  # This will produce lots of LNK4099 warnings which can be ignored.
-  # Unfortunately they can't be disabled, see
-  # http://stackoverflow.com/questions/661606/visual-c-how-to-disable-specific-linker-warnings
-  - cmake -LA -G "Visual Studio 14 Win64"
-    -DOsmium_DEBUG=TRUE
-    -DCMAKE_BUILD_TYPE=%config%
-    -DBUILD_HEADERS=OFF
-    -DBOOST_ROOT=%LODEPSDIR%\boost
-    -DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib
-    -DBZIP2_LIBRARY_RELEASE=%LIBBZIP2%
-    -DCMAKE_PREFIX_PATH=%LODEPSDIR%\zlib;%LODEPSDIR%\expat;%LODEPSDIR%\bzip2;%LODEPSDIR%\geos;%LODEPSDIR%\gdal;%LODEPSDIR%\proj;%LODEPSDIR%\sparsehash;%LODEPSDIR%\wingetopt
-    ..
-  - msbuild libosmium.sln /p:Configuration=%config% /toolsversion:14.0 /p:Platform=x64 /p:PlatformToolset=v140
-  #- cmake .. -LA -G "NMake Makefiles"
-  #  -DOsmium_DEBUG=TRUE
-  #  -DCMAKE_BUILD_TYPE=%config%
-  #  -DBOOST_ROOT=%LODEPSDIR%\boost
-  #  -DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib
-  #  -DZLIB_INCLUDE_DIR=%LODEPSDIR%\zlib\include
-  #  -DEXPAT_LIBRARY=%LODEPSDIR%\expat\lib\libexpat.lib
-  #  -DEXPAT_INCLUDE_DIR=%LODEPSDIR%\expat\include
-  #  -DBZIP2_LIBRARIES=%LIBBZIP2%
-  #  -DBZIP2_INCLUDE_DIR=%LODEPSDIR%\bzip2\include
-  #  -DGDAL_LIBRARY=%LODEPSDIR%\gdal\lib\gdal_i.lib
-  #  -DGDAL_INCLUDE_DIR=%LODEPSDIR%\gdal\include
-  #  -DGEOS_LIBRARY=%LODEPSDIR%\geos\lib\geos.lib
-  #  -DGEOS_INCLUDE_DIR=%LODEPSDIR%\geos\include
-  #  -DPROJ_LIBRARY=%LODEPSDIR%\proj\lib\proj.lib
-  #  -DPROJ_INCLUDE_DIR=%LODEPSDIR%\proj\include
-  #  -DSPARSEHASH_INCLUDE_DIR=%LODEPSDIR%\sparsehash\include
-  #  -DGETOPT_LIBRARY=%LODEPSDIR%\wingetopt\lib\wingetopt.lib
-  #  -DGETOPT_INCLUDE_DIR=%LODEPSDIR%\wingetopt\include
-  #- nmake
-
-test_script:
-  # "-E testdata-overview" exempts one test we know fails on Appveyor
-  #    because we currently don't have spatialite support.
-  - ctest --output-on-failure
-    -C %config%
-    -E testdata-overview
-
+  - build-appveyor.bat
diff --git a/benchmarks/osmium_benchmark_count.cpp b/benchmarks/osmium_benchmark_count.cpp
index d50c53d..1a16275 100644
--- a/benchmarks/osmium_benchmark_count.cpp
+++ b/benchmarks/osmium_benchmark_count.cpp
@@ -5,7 +5,9 @@
 */
 
 #include <cstdint>
+#include <cstdlib>
 #include <iostream>
+#include <string>
 
 #include <osmium/io/any_input.hpp>
 #include <osmium/handler.hpp>
@@ -35,12 +37,12 @@ struct CountHandler : public osmium::handler::Handler {
 int main(int argc, char* argv[]) {
     if (argc != 2) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    std::string input_filename = argv[1];
+    const std::string input_filename{argv[1]};
 
-    osmium::io::Reader reader(input_filename);
+    osmium::io::Reader reader{input_filename};
 
     CountHandler handler;
     osmium::apply(reader, handler);
diff --git a/benchmarks/osmium_benchmark_count_tag.cpp b/benchmarks/osmium_benchmark_count_tag.cpp
index 8fa696a..6062ecc 100644
--- a/benchmarks/osmium_benchmark_count_tag.cpp
+++ b/benchmarks/osmium_benchmark_count_tag.cpp
@@ -5,7 +5,9 @@
 */
 
 #include <cstdint>
+#include <cstdlib>
 #include <iostream>
+#include <string>
 
 #include <osmium/io/any_input.hpp>
 #include <osmium/handler.hpp>
@@ -38,12 +40,12 @@ struct CountHandler : public osmium::handler::Handler {
 int main(int argc, char* argv[]) {
     if (argc != 2) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    std::string input_filename = argv[1];
+    const std::string input_filename{argv[1]};
 
-    osmium::io::Reader reader(input_filename);
+    osmium::io::Reader reader{input_filename};
 
     CountHandler handler;
     osmium::apply(reader, handler);
diff --git a/benchmarks/osmium_benchmark_index_map.cpp b/benchmarks/osmium_benchmark_index_map.cpp
index 0257826..2cf550c 100644
--- a/benchmarks/osmium_benchmark_index_map.cpp
+++ b/benchmarks/osmium_benchmark_index_map.cpp
@@ -4,7 +4,9 @@
 
 */
 
+#include <cstdlib>
 #include <iostream>
+#include <string>
 
 #include <osmium/index/map/all.hpp>
 #include <osmium/handler/node_locations_for_ways.hpp>
@@ -13,24 +15,24 @@
 #include <osmium/io/any_input.hpp>
 #include <osmium/handler.hpp>
 
-typedef osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location> index_type;
+using index_type = osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>;
 
-typedef osmium::handler::NodeLocationsForWays<index_type> location_handler_type;
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
 
 int main(int argc, char* argv[]) {
     if (argc != 3) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE FORMAT\n";
-        exit(1);
+        std::exit(1);
     }
 
-    std::string input_filename = argv[1];
-    std::string location_store = argv[2];
+    const std::string input_filename{argv[1]};
+    const std::string location_store{argv[2]};
 
-    osmium::io::Reader reader(input_filename);
+    osmium::io::Reader reader{input_filename};
 
     const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
     std::unique_ptr<index_type> index = map_factory.create_map(location_store);
-    location_handler_type location_handler(*index);
+    location_handler_type location_handler{*index};
     location_handler.ignore_errors();
 
     osmium::apply(reader, location_handler);
diff --git a/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp b/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp
index 66e2a0b..e9f950c 100644
--- a/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp
+++ b/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp
@@ -19,8 +19,10 @@
 #include <algorithm>
 #include <chrono>
 #include <cmath>
+#include <cstdlib>
 #include <iostream>
 #include <limits>
+#include <string>
 
 #include <osmium/index/map/all.hpp>
 #include <osmium/handler/node_locations_for_ways.hpp>
@@ -29,23 +31,23 @@
 #include <osmium/io/any_input.hpp>
 #include <osmium/handler.hpp>
 
-typedef osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location> static_index_type;
-const std::string location_store="sparse_mem_array";
+using static_index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+const std::string location_store{"sparse_mem_array"};
 
-typedef osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location> dynamic_index_type;
+using dynamic_index_type = osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>;
 
-typedef osmium::handler::NodeLocationsForWays<static_index_type> static_location_handler_type;
-typedef osmium::handler::NodeLocationsForWays<dynamic_index_type> dynamic_location_handler_type;
+using static_location_handler_type = osmium::handler::NodeLocationsForWays<static_index_type>;
+using dynamic_location_handler_type = osmium::handler::NodeLocationsForWays<dynamic_index_type>;
 
 int main(int argc, char* argv[]) {
     if (argc != 2) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    std::string input_filename = argv[1];
+    const std::string input_filename{argv[1]};
 
-    osmium::memory::Buffer buffer = osmium::io::read_file(input_filename);
+    osmium::memory::Buffer buffer{osmium::io::read_file(input_filename)};
 
     const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
 
@@ -67,20 +69,20 @@ int main(int argc, char* argv[]) {
 
         {
             // static index
-            osmium::memory::Buffer tmp_buffer(buffer.committed());
+            osmium::memory::Buffer tmp_buffer{buffer.committed()};
             for (const auto& item : buffer) {
                 tmp_buffer.add_item(item);
                 tmp_buffer.commit();
             }
 
             static_index_type static_index;
-            static_location_handler_type static_location_handler(static_index);
+            static_location_handler_type static_location_handler{static_index};
 
-            auto start = std::chrono::steady_clock::now();
+            const auto start = std::chrono::steady_clock::now();
             osmium::apply(tmp_buffer, static_location_handler);
-            auto end = std::chrono::steady_clock::now();
+            const auto end = std::chrono::steady_clock::now();
 
-            double duration = std::chrono::duration<double, std::milli>(end-start).count();
+            const double duration = std::chrono::duration<double, std::milli>(end-start).count();
 
             if (duration < static_min) static_min = duration;
             if (duration > static_max) static_max = duration;
@@ -89,21 +91,21 @@ int main(int argc, char* argv[]) {
 
         {
             // dynamic index
-            osmium::memory::Buffer tmp_buffer(buffer.committed());
+            osmium::memory::Buffer tmp_buffer{buffer.committed()};
             for (const auto& item : buffer) {
                 tmp_buffer.add_item(item);
                 tmp_buffer.commit();
             }
 
             std::unique_ptr<dynamic_index_type> index = map_factory.create_map(location_store);
-            dynamic_location_handler_type dynamic_location_handler(*index);
+            dynamic_location_handler_type dynamic_location_handler{*index};
             dynamic_location_handler.ignore_errors();
 
-            auto start = std::chrono::steady_clock::now();
+            const auto start = std::chrono::steady_clock::now();
             osmium::apply(tmp_buffer, dynamic_location_handler);
-            auto end = std::chrono::steady_clock::now();
+            const auto end = std::chrono::steady_clock::now();
 
-            double duration = std::chrono::duration<double, std::milli>(end-start).count();
+            const double duration = std::chrono::duration<double, std::milli>(end-start).count();
 
             if (duration < dynamic_min) dynamic_min = duration;
             if (duration > dynamic_max) dynamic_max = duration;
@@ -111,21 +113,21 @@ int main(int argc, char* argv[]) {
         }
     }
 
-    double static_avg = static_sum/runs;
-    double dynamic_avg = dynamic_sum/runs;
+    const double static_avg = static_sum/runs;
+    const double dynamic_avg = dynamic_sum/runs;
 
     std::cout << "static  min=" << static_min << "ms avg=" << static_avg << "ms max=" << static_max << "ms\n";
     std::cout << "dynamic min=" << dynamic_min << "ms avg=" << dynamic_avg << "ms max=" << dynamic_max << "ms\n";
 
-    double rfactor = 100.0;
-    double diff_min = std::round((dynamic_min - static_min) * rfactor) / rfactor;
-    double diff_avg = std::round((dynamic_avg - static_avg) * rfactor) / rfactor;
-    double diff_max = std::round((dynamic_max - static_max) * rfactor) / rfactor;
+    const double rfactor = 100.0;
+    const double diff_min = std::round((dynamic_min - static_min) * rfactor) / rfactor;
+    const double diff_avg = std::round((dynamic_avg - static_avg) * rfactor) / rfactor;
+    const double diff_max = std::round((dynamic_max - static_max) * rfactor) / rfactor;
 
-    double prfactor = 10.0;
-    double percent_min = std::round((100.0 * diff_min / static_min) * prfactor) / prfactor;
-    double percent_avg = std::round((100.0 * diff_avg / static_avg) * prfactor) / prfactor;
-    double percent_max = std::round((100.0 * diff_max / static_max) * prfactor) / prfactor;
+    const double prfactor = 10.0;
+    const double percent_min = std::round((100.0 * diff_min / static_min) * prfactor) / prfactor;
+    const double percent_avg = std::round((100.0 * diff_avg / static_avg) * prfactor) / prfactor;
+    const double percent_max = std::round((100.0 * diff_max / static_max) * prfactor) / prfactor;
 
     std::cout << "difference:";
     std::cout << " min=" << diff_min << "ms (" << percent_min << "%)";
diff --git a/benchmarks/osmium_benchmark_write_pbf.cpp b/benchmarks/osmium_benchmark_write_pbf.cpp
index 869f3a8..632dd26 100644
--- a/benchmarks/osmium_benchmark_write_pbf.cpp
+++ b/benchmarks/osmium_benchmark_write_pbf.cpp
@@ -4,8 +4,9 @@
 
 */
 
-#include <cstdint>
-#include <vector>
+#include <cstdlib>
+#include <iostream>
+#include <string>
 
 #include <osmium/io/any_input.hpp>
 #include <osmium/io/any_output.hpp>
@@ -13,16 +14,16 @@
 int main(int argc, char* argv[]) {
     if (argc != 3) {
         std::cerr << "Usage: " << argv[0] << " INPUT-FILE OUTPUT-FILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    std::string input_filename = argv[1];
-    std::string output_filename = argv[2];
+    std::string input_filename{argv[1]};
+    std::string output_filename{argv[2]};
 
-    osmium::io::Reader reader(input_filename);
-    osmium::io::File output_file(output_filename, "pbf");
+    osmium::io::Reader reader{input_filename};
+    osmium::io::File output_file{output_filename, "pbf"};
     osmium::io::Header header;
-    osmium::io::Writer writer(output_file, header, osmium::io::overwrite::allow);
+    osmium::io::Writer writer{output_file, header, osmium::io::overwrite::allow};
 
     while (osmium::memory::Buffer buffer = reader.read()) {
         writer(std::move(buffer));
diff --git a/build-appveyor.bat b/build-appveyor.bat
new file mode 100644
index 0000000..27d387c
--- /dev/null
+++ b/build-appveyor.bat
@@ -0,0 +1,123 @@
+ at ECHO OFF
+SETLOCAL
+SET EL=0
+
+ECHO ~~~~~~ %~f0 ~~~~~~
+
+SET CUSTOM_CMAKE=cmake-3.6.2-win64-x64
+::show all available env vars
+SET
+ECHO cmake on AppVeyor
+cmake -version
+
+ECHO activating VS cmd prompt && CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+SET lodir=%CD%
+SET PATH=%lodir%\%CUSTOM_CMAKE%\bin;%PATH%
+SET LODEPSDIR=%lodir%\libosmium-deps
+SET PROJ_LIB=%LODEPSDIR%\proj\share
+SET GDAL_DATA=%LODEPSDIR%\gdal\data
+::gdal.dll
+SET PATH=%LODEPSDIR%\gdal\lib;%PATH%
+::geos.dll
+SET PATH=%LODEPSDIR%\geos\lib;%PATH%
+::libtiff.dll
+SET PATH=%LODEPSDIR%\libtiff\lib;%PATH%
+::jpeg.dll
+SET PATH=%LODEPSDIR%\jpeg\lib;%PATH%
+::libexpat.dll
+SET PATH=%LODEPSDIR%\expat\lib;%PATH%
+::zlibwapi.dll
+SET PATH=%LODEPSDIR%\zlib\lib;%PATH%
+::convert backslashes in bzip2 path to forward slashes
+::cmake cannot find it otherwise
+SET LIBBZIP2=%LODEPSDIR%\bzip2\lib\libbz2.lib
+SET LIBBZIP2=%LIBBZIP2:\=/%
+
+IF NOT EXIST cm.7z ECHO downloading cmake %CUSTOM_CMAKE% ... && powershell Invoke-WebRequest https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/%CUSTOM_CMAKE%.7z -OutFile cm.7z
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+IF NOT EXIST lodeps.7z ECHO downloading binary dependencies... && powershell Invoke-WebRequest https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/libosmium-deps-win-14.0-x64.7z -OutFile lodeps.7z
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+IF NOT EXIST %CUSTOM_CMAKE% ECHO extracting cmake... && 7z x cm.7z | %windir%\system32\find "ing archive"
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+IF NOT EXIST %LODEPSDIR% ECHO extracting binary dependencies... && 7z x lodeps.7z | %windir%\system32\find "ing archive"
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+ECHO %LODEPSDIR%
+DIR %LODEPSDIR%
+::TREE %LODEPSDIR%
+
+::powershell (Get-ChildItem $env:LODEPSDIR\boost\lib -Filter *boost*.dll)[0].BaseName.split('_')[-1]
+FOR /F "tokens=1 usebackq" %%i in (`powershell ^(Get-ChildItem %LODEPSDIR%\boost\lib -Filter *boost*.dll^)[0].BaseName.split^('_'^)[-1]`) DO SET BOOST_VERSION=%%i
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+ECHO BOOST_VERSION^: %BOOST_VERSION%
+
+ECHO our own cmake
+cmake -version
+
+CD %lodir%\..
+
+IF NOT EXIST osm-testdata ECHO cloning osm-testdata && git clone --depth 1 https://github.com/osmcode/osm-testdata.git
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+CD osm-testdata
+git fetch
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+git pull
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+CD %lodir%
+IF EXIST build ECHO deleting build dir... && RD /Q /S build
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+MKDIR build
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+CD build
+ECHO config^: %config%
+
+::This will produce lots of LNK4099 warnings which can be ignored.
+::Unfortunately they can't be disabled, see
+::http://stackoverflow.com/questions/661606/visual-c-how-to-disable-specific-linker-warnings
+SET CMAKE_CMD=cmake .. ^
+-LA -G "Visual Studio 14 Win64" ^
+-DOsmium_DEBUG=TRUE ^
+-DCMAKE_BUILD_TYPE=%config% ^
+-DBUILD_HEADERS=OFF ^
+-DBOOST_ROOT=%LODEPSDIR%\boost ^
+-DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib ^
+-DBZIP2_LIBRARY_RELEASE=%LIBBZIP2% ^
+-DCMAKE_PREFIX_PATH=%LODEPSDIR%\zlib;%LODEPSDIR%\expat;%LODEPSDIR%\bzip2;%LODEPSDIR%\geos;%LODEPSDIR%\gdal;%LODEPSDIR%\proj;%LODEPSDIR%\sparsehash;%LODEPSDIR%\wingetopt
+
+ECHO calling^: %CMAKE_CMD%
+%CMAKE_CMD%
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+msbuild libosmium.sln ^
+/p:Configuration=%config% ^
+/toolsversion:14.0 ^
+/p:Platform=x64 ^
+/p:PlatformToolset=v140
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+ctest --output-on-failure ^
+-C %config% ^
+-E testdata-overview
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+
+GOTO DONE
+
+:ERROR
+ECHO ~~~~~~ ERROR %~f0 ~~~~~~
+SET EL=%ERRORLEVEL%
+
+:DONE
+IF %EL% NEQ 0 ECHO. && ECHO !!! ERRORLEVEL^: %EL% !!! && ECHO.
+ECHO ~~~~~~ DONE %~f0 ~~~~~~
+
+EXIT /b %EL%
diff --git a/build-local.bat b/build-local.bat
new file mode 100644
index 0000000..202d0b2
--- /dev/null
+++ b/build-local.bat
@@ -0,0 +1,43 @@
+ at ECHO OFF
+SETLOCAL
+SET EL=0
+
+ECHO ~~~~~~ %~f0 ~~~~~~
+
+ECHO.
+ECHO build-local ["config=Dev"]
+ECHO default config^: RelWithDebInfo
+ECHO.
+
+SET platform=x64
+SET config=RelWithDebInfo
+
+:: OVERRIDE PARAMETERS >>>>>>>>
+:NEXT-ARG
+
+IF '%1'=='' GOTO ARGS-DONE
+ECHO setting %1
+SET %1
+SHIFT
+GOTO NEXT-ARG
+
+:ARGS-DONE
+::<<<<< OVERRIDE PARAMETERS
+
+WHERE 7z
+IF %ERRORLEVEL% NEQ 0 ECHO 7zip not on PATH && GOTO ERROR
+
+CALL build-appveyor.bat
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+
+GOTO DONE
+
+:ERROR
+ECHO ~~~~~~ ERROR %~f0 ~~~~~~
+SET EL=%ERRORLEVEL%
+
+:DONE
+IF %EL% NEQ 0 ECHO. && ECHO !!! ERRORLEVEL^: %EL% !!! && ECHO.
+ECHO ~~~~~~ DONE %~f0 ~~~~~~
+
+EXIT /b %EL%
diff --git a/cmake/iwyu.sh b/cmake/iwyu.sh
index ceea106..b386159 100755
--- a/cmake/iwyu.sh
+++ b/cmake/iwyu.sh
@@ -6,6 +6,11 @@
 # TODO: This script should be integrated with cmake in some way...
 #
 
+# If these are set, the wrong compiler is used by iwyu and there will be
+# errors about missing includes.
+unset CC
+unset CXX
+
 cmdline="iwyu -Xiwyu --mapping_file=osmium.imp -std=c++11 -I include"
 
 log=build/iwyu.log
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index eff3363..b47cfdc 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -12,13 +12,17 @@ set(EXAMPLES
     area_test
     convert
     count
-    create_node_cache
     debug
     filter_discussions
     index
+    location_cache_create
+    location_cache_use
+    pub_names
     read
+    read_with_progress
+    road_length
     serdump
-    use_node_cache
+    tiles
     CACHE STRING "Example programs"
 )
 
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..e291032
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,32 @@
+
+# Osmium example programs
+
+The programs in this directory are intended as examples for developers. They
+contain extensive comments explaining what's going on. Note that the examples
+only cover a small part of what Osmium can do, you should also read the
+documentation and API documentation.
+
+All programs can be run without arguments and they will tell you how to run
+them.
+
+## Very simple examples
+
+* `osmium_read`
+* `osmium_count`
+* `osmium_debug`
+* `osmium_tiles`
+
+## Still reasonably simple examples
+
+* `osmium_filter_discussions`
+* `osmium_convert`
+
+## More advanced examples
+
+* `osmium_area_test`
+
+## License
+
+The code in these example files is released into the Public Domain. Feel free
+to copy the code and build on it.
+
diff --git a/examples/osmium_area_test.cpp b/examples/osmium_area_test.cpp
index e9b7a18..0303374 100644
--- a/examples/osmium_area_test.cpp
+++ b/examples/osmium_area_test.cpp
@@ -1,48 +1,81 @@
 /*
 
-  This is an example tool that creates multipolygons from OSM data
-  and dumps them to stdout.
-
+  EXAMPLE osmium_area_test
+
+  Create multipolygons from OSM data and dump them to stdout in one of two
+  formats: WKT or using the built-in Dump format.
+
+  DEMONSTRATES USE OF:
+  * file input
+  * location indexes and the NodeLocationsForWays handler
+  * the MultipolygonCollector and Assembler to assemble areas (multipolygons)
+  * your own handler that works with areas (multipolygons)
+  * the WKTFactory to write geometries in WKT format
+  * the Dump handler
+  * the DynamicHandler
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+  * osmium_debug
+
+  LICENSE
   The code in this example file is released into the Public Domain.
 
 */
 
-#include <iostream>
-
-#include <getopt.h>
+#include <cstdlib>  // for std::exit
+#include <getopt.h> // for getopt_long
+#include <iostream> // for std::cout, std::cerr
 
+// For assembling multipolygons
 #include <osmium/area/assembler.hpp>
 #include <osmium/area/multipolygon_collector.hpp>
+
+// For the DynamicHandler class
 #include <osmium/dynamic_handler.hpp>
+
+// For the WKT factory
 #include <osmium/geom/wkt.hpp>
+
+// For the Dump handler
 #include <osmium/handler/dump.hpp>
+
+// For the NodeLocationForWays handler
 #include <osmium/handler/node_locations_for_ways.hpp>
-#include <osmium/index/map/dummy.hpp>
-#include <osmium/index/map/sparse_mem_array.hpp>
+
+// Allow any format of input files (XML, PBF, ...)
 #include <osmium/io/any_input.hpp>
+
+// For osmium::apply()
 #include <osmium/visitor.hpp>
 
-typedef osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location> index_neg_type;
-typedef osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location> index_pos_type;
-typedef osmium::handler::NodeLocationsForWays<index_pos_type, index_neg_type> location_handler_type;
+// For the location index. There are different types of indexes available.
+// This will work for small and medium sized input files.
+#include <osmium/index/map/sparse_mem_array.hpp>
 
-class WKTDump : public osmium::handler::Handler {
+// The type of index used. This must match the include file above
+using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
 
-    osmium::geom::WKTFactory<> m_factory ;
+// The location handler always depends on the index type
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
 
-    std::ostream& m_out;
+// This handler writes all area geometries out in WKT (Well Known Text) format.
+class WKTDump : public osmium::handler::Handler {
 
-public:
+    // This factory is used to create a geometry in WKT format from OSM
+    // objects. The template parameter is empty here, because we output WGS84
+    // coordinates, but could be used for a projection.
+    osmium::geom::WKTFactory<> m_factory;
 
-    WKTDump(std::ostream& out) :
-        m_out(out) {
-    }
+public:
 
+    // This callback is called by osmium::apply for each area in the data.
     void area(const osmium::Area& area) {
         try {
-            m_out << m_factory.create_multipolygon(area) << "\n";
-        } catch (osmium::geometry_error& e) {
-            m_out << "GEOMETRY ERROR: " << e.what() << "\n";
+            std::cout << m_factory.create_multipolygon(area) << "\n";
+        } catch (const osmium::geometry_error& e) {
+            std::cout << "GEOMETRY ERROR: " << e.what() << "\n";
         }
     }
 
@@ -65,8 +98,13 @@ int main(int argc, char* argv[]) {
         {0, 0, 0, 0}
     };
 
+    // Initialize an empty DynamicHandler. Later it will be associated
+    // with one of the handlers. You can think of the DynamicHandler as
+    // a kind of "variant handler" or a "pointer handler" pointing to the
+    // real handler.
     osmium::handler::DynamicHandler handler;
 
+    // Read options from command line.
     while (true) {
         int c = getopt_long(argc, argv, "hwo", long_options, 0);
         if (c == -1) {
@@ -76,54 +114,81 @@ int main(int argc, char* argv[]) {
         switch (c) {
             case 'h':
                 print_help();
-                exit(0);
+                std::exit(0);
             case 'w':
-                handler.set<WKTDump>(std::cout);
+                handler.set<WKTDump>();
                 break;
             case 'o':
                 handler.set<osmium::handler::Dump>(std::cout);
                 break;
             default:
-                exit(1);
+                std::exit(1);
         }
     }
 
     int remaining_args = argc - optind;
     if (remaining_args != 1) {
         std::cerr << "Usage: " << argv[0] << " [OPTIONS] OSMFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    osmium::io::File infile(argv[optind]);
+    osmium::io::File input_file{argv[optind]};
 
+    // Configuration for the multipolygon assembler. Here the default settings
+    // are used, but you could change multiple settings.
     osmium::area::Assembler::config_type assembler_config;
-    osmium::area::MultipolygonCollector<osmium::area::Assembler> collector(assembler_config);
 
+    // Initialize the MultipolygonCollector. Its job is to collect all
+    // relations and member ways needed for each area. It then calls an
+    // instance of the osmium::area::Assembler class (with the given config)
+    // to actually assemble one area.
+    osmium::area::MultipolygonCollector<osmium::area::Assembler> collector{assembler_config};
+
+    // We read the input file twice. In the first pass, only relations are
+    // read and fed into the multipolygon collector.
     std::cerr << "Pass 1...\n";
-    osmium::io::Reader reader1(infile, osmium::osm_entity_bits::relation);
+    osmium::io::Reader reader1{input_file, osmium::osm_entity_bits::relation};
     collector.read_relations(reader1);
     reader1.close();
     std::cerr << "Pass 1 done\n";
 
+    // Output the amount of main memory used so far. All multipolygon relations
+    // are in memory now.
     std::cerr << "Memory:\n";
     collector.used_memory();
 
-    index_pos_type index_pos;
-    index_neg_type index_neg;
-    location_handler_type location_handler(index_pos, index_neg);
-    location_handler.ignore_errors(); // XXX
+    // The index storing all node locations.
+    index_type index;
+
+    // The handler that stores all node locations in the index and adds them
+    // to the ways.
+    location_handler_type location_handler{index};
+
+    // If a location is not available in the index, we ignore it. It might
+    // not be needed (if it is not part of a multipolygon relation), so why
+    // create an error?
+    location_handler.ignore_errors();
 
+    // On the second pass we read all objects and run them first through the
+    // node location handler and then the multipolygon collector. The collector
+    // will put the areas it has created into the "buffer" which are then
+    // fed through our "handler".
     std::cerr << "Pass 2...\n";
-    osmium::io::Reader reader2(infile);
+    osmium::io::Reader reader2{input_file};
     osmium::apply(reader2, location_handler, collector.handler([&handler](osmium::memory::Buffer&& buffer) {
         osmium::apply(buffer, handler);
     }));
     reader2.close();
     std::cerr << "Pass 2 done\n";
 
+    // Output the amount of main memory used so far. All complete multipolygon
+    // relations have been cleaned up.
     std::cerr << "Memory:\n";
     collector.used_memory();
 
+    // If there were multipolgyon relations in the input, but some of their
+    // members are not in the input file (which often happens for extracts)
+    // this will write the IDs of the incomplete relations to stderr.
     std::vector<const osmium::Relation*> incomplete_relations = collector.get_incomplete_relations();
     if (!incomplete_relations.empty()) {
         std::cerr << "Warning! Some member ways missing for these multipolygon relations:";
diff --git a/examples/osmium_convert.cpp b/examples/osmium_convert.cpp
index 4f2ba33..48a0823 100644
--- a/examples/osmium_convert.cpp
+++ b/examples/osmium_convert.cpp
@@ -1,16 +1,32 @@
 /*
 
+  EXAMPLE osmium_convert
+
   Convert OSM files from one format into another.
 
+  DEMONSTRATES USE OF:
+  * file input and output
+  * file types
+  * Osmium buffers
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+
+  LICENSE
   The code in this example file is released into the Public Domain.
 
 */
 
-#include <iostream>
-#include <getopt.h>
+#include <cstdlib>   // for std::exit
+#include <exception> // for std::exception
+#include <getopt.h>  // for getopt_long
+#include <iostream>  // for std::cout, std::cerr
+#include <string>    // for std::string
 
+// 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>
 
 void print_help() {
@@ -43,9 +59,13 @@ int main(int argc, char* argv[]) {
         {0, 0, 0, 0}
     };
 
+    // Input and output format are empty by default. Later this will mean that
+    // the format should be taken from the input and output file suffix,
+    // respectively.
     std::string input_format;
     std::string output_format;
 
+    // Read options from command line.
     while (true) {
         int c = getopt_long(argc, argv, "dhf:t:", long_options, 0);
         if (c == -1) {
@@ -55,7 +75,7 @@ int main(int argc, char* argv[]) {
         switch (c) {
             case 'h':
                 print_help();
-                exit(0);
+                std::exit(0);
             case 'f':
                 input_format = optarg;
                 break;
@@ -63,49 +83,73 @@ int main(int argc, char* argv[]) {
                 output_format = optarg;
                 break;
             default:
-                exit(1);
+                std::exit(1);
         }
     }
 
-    std::string input;
-    std::string output;
     int remaining_args = argc - optind;
     if (remaining_args > 2) {
-        std::cerr << "Usage: " << argv[0] << " [OPTIONS] [INFILE [OUTFILE]]" << std::endl;
-        exit(1);
-    } else if (remaining_args == 2) {
-        input =  argv[optind];
-        output = argv[optind+1];
-    } else if (remaining_args == 1) {
-        input =  argv[optind];
+        std::cerr << "Usage: " << argv[0] << " [OPTIONS] [INFILE [OUTFILE]]\n";
+        std::exit(1);
     }
 
-    osmium::io::File infile(input, input_format);
+    // Get input file name from command line.
+    std::string input_file_name;
+    if (remaining_args >= 1) {
+        input_file_name = argv[optind];
+    }
 
-    osmium::io::File outfile(output, output_format);
+    // Get output file name from command line.
+    std::string output_file_name;
+    if (remaining_args == 2) {
+        output_file_name = argv[optind+1];
+    }
 
-    if (infile.has_multiple_object_versions() && !outfile.has_multiple_object_versions()) {
+    // This declares the input and output files using either the suffix of
+    // the file names or the format in the 2nd argument. It does not yet open
+    // the files.
+    osmium::io::File input_file{input_file_name, input_format};
+    osmium::io::File output_file{output_file_name, output_format};
+
+    // Input and output files can be OSM data files (without history) or
+    // OSM history files. History files are detected if they use the '.osh'
+    // file suffix.
+    if (  input_file.has_multiple_object_versions() &&
+        !output_file.has_multiple_object_versions()) {
         std::cerr << "Warning! You are converting from an OSM file with (potentially) several versions of the same object to one that is not marked as such.\n";
     }
 
-    int exit_code = 0;
-
     try {
-        osmium::io::Reader reader(infile);
+        // Initialize Reader
+        osmium::io::Reader reader{input_file};
+
+        // Get header from input file and change the "generator" setting to
+        // outselves.
         osmium::io::Header header = reader.header();
         header.set("generator", "osmium_convert");
 
-        osmium::io::Writer writer(outfile, header, osmium::io::overwrite::allow);
+        // 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);
+
+        // 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
+        // in the file. Buffers are moved around, so there is no cost for
+        // copying in memory.
         while (osmium::memory::Buffer buffer = reader.read()) {
             writer(std::move(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 (std::exception& e) {
+    } catch (const std::exception& e) {
+        // All exceptions used by the Osmium library derive from std::exception.
         std::cerr << e.what() << "\n";
-        exit_code = 1;
+        std::exit(1);
     }
-
-    return exit_code;
 }
 
diff --git a/examples/osmium_count.cpp b/examples/osmium_count.cpp
index baea153..7de1b6b 100644
--- a/examples/osmium_count.cpp
+++ b/examples/osmium_count.cpp
@@ -1,56 +1,95 @@
 /*
 
-  This is a small tool that counts the number of nodes, ways, and relations in
-  the input file.
+  EXAMPLE osmium_count
 
+  Counts the number of nodes, ways, and relations in the input file.
+
+  DEMONSTRATES USE OF:
+  * OSM file input
+  * your own handler
+  * the memory usage utility class
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+
+  LICENSE
   The code in this example file is released into the Public Domain.
 
 */
 
-#include <cstdint>
-#include <iostream>
+#include <cstdint>  // for std::uint64_t
+#include <cstdlib>  // for std::exit
+#include <iostream> // for std::cout, std::cerr
 
+// Allow any format of input files (XML, PBF, ...)
 #include <osmium/io/any_input.hpp>
+
+// We want to use the handler interface
 #include <osmium/handler.hpp>
+
+// Utility class gives us access to memory usage information
+#include <osmium/util/memory.hpp>
+
+// For osmium::apply()
 #include <osmium/visitor.hpp>
 
+// Handler derive from the osmium::handler::Handler base class. Usually you
+// overwrite functions node(), way(), and relation(). Other functions are
+// available, too. Read the API documentation for details.
 struct CountHandler : public osmium::handler::Handler {
 
-    uint64_t nodes = 0;
-    uint64_t ways = 0;
-    uint64_t relations = 0;
+    std::uint64_t nodes     = 0;
+    std::uint64_t ways      = 0;
+    std::uint64_t relations = 0;
 
-    void node(osmium::Node&) {
+    // This callback is called by osmium::apply for each node in the data.
+    void node(const osmium::Node&) noexcept {
         ++nodes;
     }
 
-    void way(osmium::Way&) {
+    // This callback is called by osmium::apply for each way in the data.
+    void way(const osmium::Way&) noexcept {
         ++ways;
     }
 
-    void relation(osmium::Relation&) {
+    // This callback is called by osmium::apply for each relation in the data.
+    void relation(const osmium::Relation&) noexcept {
         ++relations;
     }
 
-};
+}; // struct CountHandler
 
 
 int main(int argc, char* argv[]) {
-
     if (argc != 2) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    osmium::io::File infile(argv[1]);
-    osmium::io::Reader reader(infile);
+    // The Reader is initialized here with an osmium::io::File, but could
+    // also be directly initialized with a file name.
+    osmium::io::File input_file{argv[1]};
+    osmium::io::Reader reader{input_file};
 
+    // Create an instance of our own CountHandler and push the data from the
+    // input file through it.
     CountHandler handler;
     osmium::apply(reader, handler);
+
+    // You do not have to close the Reader explicitly, but because the
+    // destructor can't throw, you will not see any errors otherwise.
     reader.close();
 
     std::cout << "Nodes: "     << handler.nodes << "\n";
     std::cout << "Ways: "      << handler.ways << "\n";
     std::cout << "Relations: " << handler.relations << "\n";
+
+    // Because of the huge amount of OSM data, some Osmium-based programs
+    // (though not this one) can use huge amounts of data. So checking actual
+    // memore usage is often useful and can be done easily with this class.
+    // (Currently only works on Linux, not OSX and Windows.)
+    osmium::MemoryUsage memory;
+
+    std::cout << "\nMemory used: " << memory.peak() << " MBytes\n";
 }
 
diff --git a/examples/osmium_create_node_cache.cpp b/examples/osmium_create_node_cache.cpp
deleted file mode 100644
index 359fa19..0000000
--- a/examples/osmium_create_node_cache.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-
-  This reads an OSM file and writes out the node locations to a cache
-  file.
-
-  The code in this example file is released into the Public Domain.
-
-*/
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <iostream>
-
-#include <osmium/io/any_input.hpp>
-
-#include <osmium/index/map/dummy.hpp>
-#include <osmium/index/map/dense_mmap_array.hpp>
-#include <osmium/index/map/dense_file_array.hpp>
-
-#include <osmium/handler/node_locations_for_ways.hpp>
-#include <osmium/visitor.hpp>
-
-typedef osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location> index_neg_type;
-//typedef osmium::index::map::DenseMmapArray<osmium::unsigned_object_id_type, osmium::Location> index_pos_type;
-typedef osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location> index_pos_type;
-
-typedef osmium::handler::NodeLocationsForWays<index_pos_type, index_neg_type> location_handler_type;
-
-int main(int argc, char* argv[]) {
-    if (argc != 3) {
-        std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n";
-        return 1;
-    }
-
-    std::string input_filename(argv[1]);
-    osmium::io::Reader reader(input_filename, osmium::osm_entity_bits::node);
-
-    int fd = open(argv[2], O_RDWR | O_CREAT, 0666);
-    if (fd == -1) {
-        std::cerr << "Can not open node cache file '" << argv[2] << "': " << strerror(errno) << "\n";
-        return 1;
-    }
-
-    index_pos_type index_pos {fd};
-    index_neg_type index_neg;
-    location_handler_type location_handler(index_pos, index_neg);
-    location_handler.ignore_errors();
-
-    osmium::apply(reader, location_handler);
-    reader.close();
-
-    return 0;
-}
-
diff --git a/examples/osmium_debug.cpp b/examples/osmium_debug.cpp
index 365fc72..8133491 100644
--- a/examples/osmium_debug.cpp
+++ b/examples/osmium_debug.cpp
@@ -1,27 +1,46 @@
 /*
 
-  This is a small tool to dump the contents of the input file.
+  EXAMPLE osmium_debug
 
+  Dump the contents of the input file in a debug format.
+
+  DEMONSTRATES USE OF:
+  * file input reading only some types
+  * the dump handler
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+
+  LICENSE
   The code in this example file is released into the Public Domain.
 
 */
 
-#include <iostream>
+#include <cstdlib>  // for std::exit
+#include <iostream> // for std::cout, std::cerr
+#include <string>   // for std::string
 
+// The Dump handler
 #include <osmium/handler/dump.hpp>
+
+// Allow any format of input files (XML, PBF, ...)
 #include <osmium/io/any_input.hpp>
 
 int main(int argc, char* argv[]) {
+    // Speed up output (not Osmium-specific)
     std::ios_base::sync_with_stdio(false);
 
     if (argc < 2 || argc > 3) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE [TYPES]\n";
         std::cerr << "TYPES can be any combination of 'n', 'w', 'r', and 'c' to indicate what types of OSM entities you want (default: all).\n";
-        exit(1);
+        std::exit(1);
     }
 
+    // Default is all entity types: nodes, ways, relations, and changesets
     osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all;
 
+    // Get entity types from command line if there is a 2nd argument.
     if (argc == 3) {
         read_types = osmium::osm_entity_bits::nothing;
         std::string types = argv[2];
@@ -31,20 +50,27 @@ int main(int argc, char* argv[]) {
         if (types.find('c') != std::string::npos) read_types |= osmium::osm_entity_bits::changeset;
     }
 
-    osmium::io::Reader reader(argv[1], read_types);
-    osmium::io::Header header = reader.header();
+    // Initialize Reader with file name and the types of entities we want to
+    // read.
+    osmium::io::Reader reader{argv[1], read_types};
 
+    // The file header can contain metadata such as the program that generated
+    // the file and the bounding box of the data.
+    osmium::io::Header header = reader.header();
     std::cout << "HEADER:\n  generator=" << header.get("generator") << "\n";
 
-    for (auto& bbox : header.boxes()) {
+    for (const auto& bbox : header.boxes()) {
         std::cout << "  bbox=" << bbox << "\n";
     }
 
-    osmium::handler::Dump dump(std::cout);
-    while (osmium::memory::Buffer buffer = reader.read()) {
-        osmium::apply(buffer, dump);
-    }
+    // Initialize Dump handler.
+    osmium::handler::Dump dump{std::cout};
+
+    // Read from input and send everything to Dump handler.
+    osmium::apply(reader, dump);
 
+    // You do not have to close the Reader explicitly, but because the
+    // destructor can't throw, you will not see any errors otherwise.
     reader.close();
 }
 
diff --git a/examples/osmium_filter_discussions.cpp b/examples/osmium_filter_discussions.cpp
index bba25b7..6334981 100644
--- a/examples/osmium_filter_discussions.cpp
+++ b/examples/osmium_filter_discussions.cpp
@@ -1,53 +1,73 @@
 /*
 
+  EXAMPLE osmium_filter_discussions
+
   Read OSM changesets with discussions from a changeset dump like the one
   you get from http://planet.osm.org/planet/discussions-latest.osm.bz2
   and write out only those changesets which have discussions (ie comments).
 
+  DEMONSTRATES USE OF:
+  * file input and output
+  * setting file formats using the osmium::io::File class
+  * OSM file headers
+  * input and output iterators
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+
+  LICENSE
   The code in this example file is released into the Public Domain.
 
 */
 
 #include <algorithm> // for std::copy_if
-#include <iostream> // for std::cout, std::cerr
+#include <cstdlib>   // for std::exit
+#include <iostream>  // for std::cout, std::cerr
 
-// we want to read OSM files in XML format
-// (other formats don't support full changesets, so only XML is needed here)
+// We want to read OSM files in XML format
+// (other formats don't support full changesets, so only XML is needed here).
 #include <osmium/io/xml_input.hpp>
-#include <osmium/io/input_iterator.hpp>
 
-// we want to write OSM files in XML format
+// We want to write OSM files in XML format.
 #include <osmium/io/xml_output.hpp>
+
+// We want to use input and output iterators for easy integration with the
+// algorithms of the standard library.
+#include <osmium/io/input_iterator.hpp>
 #include <osmium/io/output_iterator.hpp>
 
-// we want to support any compressioon (.gz2 and .bz2)
+// We want to support any compression (none, gzip, and bzip2).
 #include <osmium/io/any_compression.hpp>
 
 int main(int argc, char* argv[]) {
     if (argc != 3) {
         std::cout << "Usage: " << argv[0] << " INFILE OUTFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    // The input file, deduce file format from file suffix
-    osmium::io::File infile(argv[1]);
+    // The input file, deduce file format from file suffix.
+    osmium::io::File input_file{argv[1]};
 
-    // The output file, force class XML OSM file format
-    osmium::io::File outfile(argv[2], "osm");
+    // The output file, force XML OSM file format.
+    osmium::io::File output_file{argv[2], "osm"};
 
     // Initialize Reader for the input file.
     // Read only changesets (will ignore nodes, ways, and
     // relations if there are any).
-    osmium::io::Reader reader(infile, osmium::osm_entity_bits::changeset);
+    osmium::io::Reader reader{input_file, osmium::osm_entity_bits::changeset};
 
-    // Get the header from the input file
+    // Get the header from the input file.
     osmium::io::Header header = reader.header();
 
+    // Set the "generator" on the header to ourselves.
+    header.set("generator", "osmium_filter_discussions");
+
     // Initialize writer for the output file. Use the header from the input
     // 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(outfile, 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
index 8d5c5d9..478b6c6 100644
--- a/examples/osmium_index.cpp
+++ b/examples/osmium_index.cpp
@@ -31,7 +31,7 @@ class IndexSearch {
     void dump_dense() {
         dense_index_type index(m_fd);
 
-        for (size_t i = 0; i < index.size(); ++i) {
+        for (std::size_t i = 0; i < index.size(); ++i) {
             if (index.get(i) != TValue()) {
                 std::cout << i << " " << index.get(i) << "\n";
             }
@@ -51,9 +51,9 @@ class IndexSearch {
 
         try {
             TValue value = index.get(key);
-            std::cout << key << " " << value << std::endl;
+            std::cout << key << " " << value << "\n";
         } catch (...) {
-            std::cout << key << " not found" << std::endl;
+            std::cout << key << " not found\n";
             return false;
         }
 
@@ -69,7 +69,7 @@ class IndexSearch {
             return lhs.first < rhs.first;
         });
         if (positions.first == positions.second) {
-            std::cout << key << " not found" << std::endl;
+            std::cout << key << " not found\n";
             return false;
         }
 
@@ -103,7 +103,7 @@ public:
         }
     }
 
-    bool search(std::vector<TKey> keys) {
+    bool search(const std::vector<TKey>& keys) {
         bool found_all = true;
 
         for (const auto key : keys) {
@@ -173,7 +173,7 @@ public:
                     break;
                 case 'h':
                     print_help();
-                    exit(return_code::okay);
+                    std::exit(return_code::okay);
                 case 'l':
                     m_list_format = true;
                     m_filename = optarg;
@@ -185,22 +185,22 @@ public:
                     m_type = optarg;
                     if (m_type != "location" && m_type != "offset") {
                         std::cerr << "Unknown type '" << m_type << "'. Must be 'location' or 'offset'.\n";
-                        exit(return_code::fatal);
+                        std::exit(return_code::fatal);
                     }
                     break;
                 default:
-                    exit(return_code::fatal);
+                    std::exit(return_code::fatal);
             }
         }
 
         if (m_array_format == m_list_format) {
             std::cerr << "Need option --array or --list, but not both\n";
-            exit(return_code::fatal);
+            std::exit(return_code::fatal);
         }
 
         if (m_type.empty()) {
             std::cerr << "Need --type argument.\n";
-            exit(return_code::fatal);
+            std::exit(return_code::fatal);
         }
 
     }
@@ -255,6 +255,6 @@ int main(int argc, char* argv[]) {
         }
     }
 
-    exit(result_okay ? return_code::okay : return_code::not_found);
+    std::exit(result_okay ? return_code::okay : return_code::not_found);
 }
 
diff --git a/examples/osmium_location_cache_create.cpp b/examples/osmium_location_cache_create.cpp
new file mode 100644
index 0000000..9de41d1
--- /dev/null
+++ b/examples/osmium_location_cache_create.cpp
@@ -0,0 +1,87 @@
+/*
+
+  EXAMPLE osmium_location_cache_create
+
+  Reads nodes from an OSM file and writes out their locations to a cache
+  file. The cache file can then be read with osmium_location_cache_use.
+
+  Warning: The locations cache file will get huge (>32GB) if you are using
+           the DenseFileArray index even if the input file is small, because
+           it depends on the *largest* node ID, not the number of nodes.
+
+  DEMONSTRATES USE OF:
+  * file input
+  * location indexes and the NodeLocationsForWays handler
+  * location indexes on disk
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+  * osmium_road_length
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cerrno>      // for errno
+#include <cstdlib>     // for std::exit
+#include <cstring>     // for strerror
+#include <fcntl.h>     // for open
+#include <iostream>    // for std::cout, std::cerr
+#include <string>      // for std::string
+#include <sys/stat.h>  // for open
+#include <sys/types.h> // for open
+
+// Allow any format of input files (XML, PBF, ...)
+#include <osmium/io/any_input.hpp>
+
+// For the location index. There are different types of index implementation
+// available. These implementations put the index on disk. See below.
+#include <osmium/index/map/sparse_file_array.hpp>
+#include <osmium/index/map/dense_file_array.hpp>
+
+// For the NodeLocationForWays handler
+#include <osmium/handler/node_locations_for_ways.hpp>
+
+// For osmium::apply()
+#include <osmium/visitor.hpp>
+
+// Chose one of these two. "sparse" is best used for small and medium extracts,
+// the "dense" index for large extracts or the whole planet.
+using index_type = osmium::index::map::SparseFileArray<osmium::unsigned_object_id_type, osmium::Location>;
+//using index_type = osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location>;
+
+// The location handler always depends on the index type
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
+
+int main(int argc, char* argv[]) {
+    if (argc != 3) {
+        std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n";
+        std::exit(1);
+    }
+
+    const std::string input_filename{argv[1]};
+    const std::string cache_filename{argv[2]};
+
+    // Construct Reader reading only nodes
+    osmium::io::Reader reader{input_filename, osmium::osm_entity_bits::node};
+
+    // Initialize location index on disk creating a new file.
+    const int fd = open(cache_filename.c_str(), O_RDWR | O_CREAT, 0666);
+    if (fd == -1) {
+        std::cerr << "Can not open location cache file '" << cache_filename << "': " << std::strerror(errno) << "\n";
+        std::exit(1);
+    }
+    index_type index{fd};
+
+    // The handler that stores all node locations in the index.
+    location_handler_type location_handler{index};
+
+    // Feed all nodes through the location handler.
+    osmium::apply(reader, location_handler);
+
+    // Explicitly close input so we get notified of any errors.
+    reader.close();
+}
+
diff --git a/examples/osmium_location_cache_use.cpp b/examples/osmium_location_cache_use.cpp
new file mode 100644
index 0000000..f5db0be
--- /dev/null
+++ b/examples/osmium_location_cache_use.cpp
@@ -0,0 +1,101 @@
+/*
+
+  EXAMPLE osmium_location_cache_use
+
+  This reads ways from an OSM file and writes out the way node locations
+  it got from a location cache generated with osmium_location_cache_create.
+
+  Warning: The locations cache file will get huge (>32GB) if you are using
+           the DenseFileArray index even if the input file is small, because
+           it depends on the *largest* node ID, not the number of nodes.
+
+  DEMONSTRATES USE OF:
+  * file input
+  * location indexes and the NodeLocationsForWays handler
+  * location indexes on disk
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+  * osmium_road_length
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cerrno>      // for errno
+#include <cstdlib>     // for std::exit
+#include <cstring>     // for strerror
+#include <fcntl.h>     // for open
+#include <iostream>    // for std::cout, std::cerr
+#include <string>      // for std::string
+#include <sys/stat.h>  // for open
+#include <sys/types.h> // for open
+
+// Allow any format of input files (XML, PBF, ...)
+#include <osmium/io/any_input.hpp>
+
+// For the location index. There are different types of index implementation
+// available. These implementations put the index on disk. See below.
+#include <osmium/index/map/dense_file_array.hpp>
+#include <osmium/index/map/sparse_file_array.hpp>
+
+// For the NodeLocationForWays handler
+#include <osmium/handler/node_locations_for_ways.hpp>
+
+// For osmium::apply()
+#include <osmium/visitor.hpp>
+
+// Chose one of these two. "sparse" is best used for small and medium extracts,
+// the "dense" index for large extracts or the whole planet.
+using index_type = osmium::index::map::SparseFileArray<osmium::unsigned_object_id_type, osmium::Location>;
+//using index_type = osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location>;
+
+// The location handler always depends on the index type
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
+
+// This handler only implements the way() function which prints out the way
+// ID and all nodes IDs and locations in those ways.
+struct MyHandler : public osmium::handler::Handler {
+
+    void way(const osmium::Way& way) {
+        std::cout << "way " << way.id() << "\n";
+        for (const auto& nr : way.nodes()) {
+            std::cout << "  node " << nr.ref() << " " << nr.location() << "\n";
+        }
+    }
+
+}; // struct MyHandler
+
+int main(int argc, char* argv[]) {
+    if (argc != 3) {
+        std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n";
+        std::exit(1);
+    }
+
+    const std::string input_filename{argv[1]};
+    const std::string cache_filename{argv[2]};
+
+    // Construct Reader reading only ways
+    osmium::io::Reader reader{input_filename, osmium::osm_entity_bits::way};
+
+    // Initialize location index on disk using an existing file
+    const int fd = open(cache_filename.c_str(), O_RDWR);
+    if (fd == -1) {
+        std::cerr << "Can not open location cache file '" << cache_filename << "': " << std::strerror(errno) << "\n";
+        return 1;
+    }
+    index_type index{fd};
+
+    // The handler that adds node locations from the index to the ways.
+    location_handler_type location_handler{index};
+
+    // Feed all ways through the location handler and then our own handler.
+    MyHandler handler;
+    osmium::apply(reader, location_handler, handler);
+
+    // Explicitly close input so we get notified of any errors.
+    reader.close();
+}
+
diff --git a/examples/osmium_pub_names.cpp b/examples/osmium_pub_names.cpp
new file mode 100755
index 0000000..dbc37c3
--- /dev/null
+++ b/examples/osmium_pub_names.cpp
@@ -0,0 +1,89 @@
+/*
+
+  EXAMPLE osmium_pub_names
+
+  Show the names and addresses of all pubs found in an OSM file.
+
+  DEMONSTRATES USE OF:
+  * file input
+  * your own handler
+  * access to tags
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+  * osmium_count
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cstdlib>  // for std::exit
+#include <cstring>  // for std::strncmp
+#include <iostream> // for std::cout, std::cerr
+
+// Allow any format of input files (XML, PBF, ...)
+#include <osmium/io/any_input.hpp>
+
+// We want to use the handler interface
+#include <osmium/handler.hpp>
+
+// For osmium::apply()
+#include <osmium/visitor.hpp>
+
+class NamesHandler : public osmium::handler::Handler {
+
+    void output_pubs(const osmium::OSMObject& object) {
+        const osmium::TagList& tags = object.tags();
+        if (tags.has_tag("amenity", "pub")) {
+
+            // Print name of the pub if it is set.
+            const char* name = tags["name"];
+            if (name) {
+                std::cout << name << "\n";
+            } else {
+                std::cout << "pub with unknown name\n";
+            }
+
+            // Iterate over all tags finding those which start with "addr:"
+            // and print them.
+            for (const osmium::Tag& tag : tags) {
+                if (!std::strncmp(tag.key(), "addr:", 5)) {
+                    std::cout << "  " << tag.key() << ": " << tag.value() << "\n";
+                }
+            }
+        }
+    }
+
+public:
+
+    // Nodes can be tagged amenity=pub.
+    void node(const osmium::Node& node) {
+        output_pubs(node);
+    }
+
+    // Ways can be tagged amenity=pub, too (typically buildings).
+    void way(const osmium::Way& way) {
+        output_pubs(way);
+    }
+
+}; // class NamesHandler
+
+int main(int argc, char* argv[]) {
+    if (argc != 2) {
+        std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
+        std::exit(1);
+    }
+
+    // Construct the handler defined above
+    NamesHandler names_handler;
+
+    // Initialize the reader with the filename from the command line and
+    // tell it to only read nodes and ways. We are ignoring multipolygon
+    // relations in this simple example.
+    osmium::io::Reader reader{argv[1], osmium::osm_entity_bits::node | osmium::osm_entity_bits::way};
+
+    // Apply input data to our own handler
+    osmium::apply(reader, names_handler);
+}
+
diff --git a/examples/osmium_read.cpp b/examples/osmium_read.cpp
index 6536006..9f391c8 100644
--- a/examples/osmium_read.cpp
+++ b/examples/osmium_read.cpp
@@ -1,30 +1,42 @@
 /*
 
-  This is a small tool that reads and discards the contents of the input file.
-  (Used for timing.)
+  EXAMPLE osmium_read
 
+  Reads and discards the contents of the input file.
+  (It can be used for timing.)
+
+  DEMONSTRATES USE OF:
+  * file input
+
+  LICENSE
   The code in this example file is released into the Public Domain.
 
 */
 
-#include <iostream>
+#include <cstdlib>  // for std::exit
+#include <iostream> // for std::cerr
 
+// Allow any format of input files (XML, PBF, ...)
 #include <osmium/io/any_input.hpp>
 
 int main(int argc, char* argv[]) {
-
     if (argc != 2) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    osmium::io::File infile(argv[1]);
-    osmium::io::Reader reader(infile);
+    // The Reader is initialized here with an osmium::io::File, but could
+    // also be directly initialized with a file name.
+    osmium::io::File input_file{argv[1]};
+    osmium::io::Reader reader{input_file};
 
+    // OSM data comes in buffers, read until there are no more.
     while (osmium::memory::Buffer buffer = reader.read()) {
         // do nothing
     }
 
+    // You do not have to close the Reader explicitly, but because the
+    // destructor can't throw, you will not see any errors otherwise.
     reader.close();
 }
 
diff --git a/examples/osmium_read_with_progress.cpp b/examples/osmium_read_with_progress.cpp
new file mode 100644
index 0000000..81437a6
--- /dev/null
+++ b/examples/osmium_read_with_progress.cpp
@@ -0,0 +1,56 @@
+/*
+
+  EXAMPLE osmium_read_with_progress
+
+  Reads the contents of the input file showing a progress bar.
+
+  DEMONSTRATES USE OF:
+  * file input
+  * ProgressBar utility function
+
+  SIMPLER EXAMPLES you might want to understand first:
+  * osmium_read
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cstdlib>  // for std::exit
+#include <iostream> // for std::cerr
+
+// Allow any format of input files (XML, PBF, ...)
+#include <osmium/io/any_input.hpp>
+
+// Get access to isatty utility function and progress bar utility class.
+#include <osmium/util/file.hpp>
+#include <osmium/util/progress_bar.hpp>
+
+int main(int argc, char* argv[]) {
+    if (argc != 2) {
+        std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
+        std::exit(1);
+    }
+
+    // The Reader is initialized here with an osmium::io::File, but could
+    // also be directly initialized with a file name.
+    osmium::io::File input_file{argv[1]};
+    osmium::io::Reader reader{input_file};
+
+    // Initialize progress bar, enable it only if STDERR is a TTY.
+    osmium::ProgressBar progress{reader.file_size(), osmium::util::isatty(2)};
+
+    // OSM data comes in buffers, read until there are no more.
+    while (osmium::memory::Buffer buffer = reader.read()) {
+        // Update progress bar for each buffer.
+        progress.update(reader.offset());
+    }
+
+    // Progress bar is done.
+    progress.done();
+
+    // You do not have to close the Reader explicitly, but because the
+    // destructor can't throw, you will not see any errors otherwise.
+    reader.close();
+}
+
diff --git a/examples/osmium_road_length.cpp b/examples/osmium_road_length.cpp
new file mode 100755
index 0000000..2e1be90
--- /dev/null
+++ b/examples/osmium_road_length.cpp
@@ -0,0 +1,92 @@
+/*
+
+  EXAMPLE osmium_road_length
+
+  Calculate the length of the road network (everything tagged `highway=*`)
+  from the given OSM file.
+
+  DEMONSTRATES USE OF:
+  * file input
+  * location indexes and the NodeLocationsForWays handler
+  * length calculation on the earth using the haversine function
+
+  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 <iostream> // for std::cout, std::cerr
+
+// Allow any format of input files (XML, PBF, ...)
+#include <osmium/io/any_input.hpp>
+
+// For the osmium::geom::haversine::distance() function
+#include <osmium/geom/haversine.hpp>
+
+// For osmium::apply()
+#include <osmium/visitor.hpp>
+
+// For the location index. There are different types of indexes available.
+// This will work for small and medium sized input files.
+#include <osmium/index/map/sparse_mem_array.hpp>
+
+// For the NodeLocationForWays handler
+#include <osmium/handler/node_locations_for_ways.hpp>
+
+// The type of index used. This must match the include file above
+using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+
+// The location handler always depends on the index type
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
+
+// This handler only implements the way() function, we are not interested in
+// any other objects.
+struct RoadLengthHandler : public osmium::handler::Handler {
+
+    double length = 0;
+
+    // If the way has a "highway" tag, find its length and add it to the
+    // overall length.
+    void way(const osmium::Way& way) {
+        const char* highway = way.tags()["highway"];
+        if (highway) {
+            length += osmium::geom::haversine::distance(way.nodes());
+        }
+    }
+
+}; // struct RoadLengthHandler
+
+int main(int argc, char* argv[]) {
+    if (argc != 2) {
+        std::cerr << "Usage: " << argv[0] << " OSMFILE\n";
+        std::exit(1);
+    }
+
+    // Initialize the reader with the filename from the command line and
+    // tell it to only read nodes and ways.
+    osmium::io::Reader reader{argv[1], osmium::osm_entity_bits::node | osmium::osm_entity_bits::way};
+
+    // The index to hold node locations.
+    index_type index;
+
+    // The location handler will add the node locations to the index and then
+    // to the ways
+    location_handler_type location_handler{index};
+
+    // Our handler defined above
+    RoadLengthHandler road_length_handler;
+
+    // Apply input data to first the location handler and then our own handler
+    osmium::apply(reader, location_handler, road_length_handler);
+
+    // Output the length. The haversine function calculates it in meters,
+    // so we first devide by 1000 to get kilometers.
+    std::cout << "Length: " << road_length_handler.length / 1000 << " km\n";
+}
+
diff --git a/examples/osmium_serdump.cpp b/examples/osmium_serdump.cpp
index 9ab26e4..81a6d0c 100644
--- a/examples/osmium_serdump.cpp
+++ b/examples/osmium_serdump.cpp
@@ -69,9 +69,9 @@ int main(int argc, char* argv[]) {
         switch (c) {
             case 'h':
                 print_help();
-                exit(0);
+                std::exit(0);
             default:
-                exit(2);
+                std::exit(2);
         }
     }
 
@@ -79,7 +79,7 @@ int main(int argc, char* argv[]) {
 
     if (remaining_args != 2) {
         std::cerr << "Usage: " << argv[0] << " OSMFILE DIR\n";
-        exit(2);
+        std::exit(2);
     }
 
     std::string dir(argv[optind+1]);
@@ -90,14 +90,14 @@ int main(int argc, char* argv[]) {
 #endif
     if (result == -1 && errno != EEXIST) {
         std::cerr << "Problem creating directory '" << dir << "': " << strerror(errno) << "\n";
-        exit(2);
+        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";
-        exit(2);
+        std::exit(2);
     }
 
     offset_index_type node_index;
@@ -127,7 +127,7 @@ int main(int argc, char* argv[]) {
         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";
-            exit(2);
+            std::exit(2);
         }
         node_index.dump_as_list(fd);
         close(fd);
@@ -138,7 +138,7 @@ int main(int argc, char* argv[]) {
         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";
-            exit(2);
+            std::exit(2);
         }
         way_index.dump_as_list(fd);
         close(fd);
@@ -149,7 +149,7 @@ int main(int argc, char* argv[]) {
         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";
-            exit(2);
+            std::exit(2);
         }
         relation_index.dump_as_list(fd);
         close(fd);
@@ -161,7 +161,7 @@ int main(int argc, char* argv[]) {
         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";
-            exit(2);
+            std::exit(2);
         }
         map_node2way.dump_as_list(fd);
         close(fd);
@@ -173,7 +173,7 @@ int main(int argc, char* argv[]) {
         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";
-            exit(2);
+            std::exit(2);
         }
         map_node2relation.dump_as_list(fd);
         close(fd);
@@ -185,7 +185,7 @@ int main(int argc, char* argv[]) {
         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";
-            exit(2);
+            std::exit(2);
         }
         map_way2relation.dump_as_list(fd);
         close(fd);
@@ -197,7 +197,7 @@ int main(int argc, char* argv[]) {
         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";
-            exit(2);
+            std::exit(2);
         }
         map_relation2relation.dump_as_list(fd);
         close(fd);
diff --git a/examples/osmium_tiles.cpp b/examples/osmium_tiles.cpp
new file mode 100644
index 0000000..7dbeb3e
--- /dev/null
+++ b/examples/osmium_tiles.cpp
@@ -0,0 +1,72 @@
+/*
+
+  EXAMPLE osmium_tiles
+
+  Convert WGS84 longitude and latitude to Mercator coordinates and tile
+  coordinates.
+
+  DEMONSTRATES USE OF:
+  * the Location and Coordinates classes
+  * the Mercator projection function
+  * the Tile class
+
+  LICENSE
+  The code in this example file is released into the Public Domain.
+
+*/
+
+#include <cstdlib>  // for std::exit, std::atoi, std::atof
+#include <iostream> // for std::cout, std::cerr
+
+// The Location contains a longitude and latitude and is usually used inside
+// a node to store its location in the world.
+#include <osmium/osm/location.hpp>
+
+// Needed for the Mercator projection function. Osmium supports the Mercator
+// projection out of the box, or pretty much any projection using the Proj.4
+// library (with the osmium::geom::Projection class).
+#include <osmium/geom/mercator_projection.hpp>
+
+// The Tile class handles tile coordinates and zoom levels.
+#include <osmium/geom/tile.hpp>
+
+int main(int argc, char* argv[]) {
+    if (argc != 4) {
+        std::cerr << "Usage: " << argv[0] << " ZOOM LON LAT\n";
+        std::exit(1);
+    }
+
+    const int zoom = std::atoi(argv[1]);
+
+    if (zoom < 0 || zoom > 30) {
+        std::cerr << "ERROR: Zoom must be between 0 and 30\n";
+        std::exit(1);
+    }
+
+    const double lon = std::atof(argv[2]);
+    const double lat = std::atof(argv[3]);
+
+    // Create location from WGS84 coordinates. In Osmium the order of
+    // coordinate values is always x/longitude first, then y/latitude.
+    const osmium::Location location{lon, lat};
+
+    std::cout << "WGS84:    lon=" << lon << " lat=" << lat << "\n";
+
+    // A location can store some invalid locations, ie locations outside the
+    // -180 to 180 and -90 to 90 degree range. This function checks for that.
+    if (!location.valid()) {
+        std::cerr << "ERROR: Location is invalid\n";
+        std::exit(1);
+    }
+
+    // Project the coordinates using a helper function. You can also use the
+    // osmium::geom::MercatorProjection class.
+    const osmium::geom::Coordinates c = osmium::geom::lonlat_to_mercator(location);
+    std::cout << "Mercator: x=" << c.x << " y=" << c.y << "\n";
+
+    // Create a tile at this location. This will also internally use the
+    // Mercator projection and then calculate the tile coordinates.
+    const osmium::geom::Tile tile{uint32_t(zoom), location};
+    std::cout << "Tile:     zoom=" << tile.z << " x=" << tile.x << " y=" << tile.y << "\n";
+}
+
diff --git a/examples/osmium_use_node_cache.cpp b/examples/osmium_use_node_cache.cpp
deleted file mode 100644
index cfee6df..0000000
--- a/examples/osmium_use_node_cache.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-
-  This reads ways from an OSM file and writes out the node locations
-  it got from a node cache generated with osmium_create_node_cache.
-
-  The code in this example file is released into the Public Domain.
-
-*/
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <iostream>
-
-#include <osmium/io/any_input.hpp>
-
-#include <osmium/index/map/dummy.hpp>
-#include <osmium/index/map/dense_file_array.hpp>
-#include <osmium/index/map/dense_mmap_array.hpp>
-
-#include <osmium/handler/node_locations_for_ways.hpp>
-#include <osmium/visitor.hpp>
-
-typedef osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location> index_neg_type;
-//typedef osmium::index::map::DenseMmapArray<osmium::unsigned_object_id_type, osmium::Location> index_pos_type;
-typedef osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location> index_pos_type;
-
-typedef osmium::handler::NodeLocationsForWays<index_pos_type, index_neg_type> location_handler_type;
-
-class MyHandler : public osmium::handler::Handler {
-
-public:
-
-    void way(osmium::Way& way) {
-        for (auto& nr : way.nodes()) {
-            std::cout << nr << "\n";
-        }
-    }
-
-}; // class MyHandler
-
-int main(int argc, char* argv[]) {
-    if (argc != 3) {
-        std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n";
-        return 1;
-    }
-
-    std::string input_filename(argv[1]);
-    osmium::io::Reader reader(input_filename, osmium::osm_entity_bits::way);
-
-    int fd = open(argv[2], O_RDWR);
-    if (fd == -1) {
-        std::cerr << "Can not open node cache file '" << argv[2] << "': " << strerror(errno) << "\n";
-        return 1;
-    }
-
-    index_pos_type index_pos {fd};
-    index_neg_type index_neg;
-    location_handler_type location_handler(index_pos, index_neg);
-    location_handler.ignore_errors();
-
-    MyHandler handler;
-    osmium::apply(reader, location_handler, handler);
-    reader.close();
-
-    return 0;
-}
-
diff --git a/include/osmium/area/assembler.hpp b/include/osmium/area/assembler.hpp
index 9095302..d5bf8d8 100644
--- a/include/osmium/area/assembler.hpp
+++ b/include/osmium/area/assembler.hpp
@@ -35,6 +35,8 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
+#include <cstdint>
+#include <cstdlib>
 #include <cstring>
 #include <iostream>
 #include <iterator>
@@ -42,16 +44,21 @@ DEALINGS IN THE SOFTWARE.
 #include <set>
 #include <string>
 #include <map>
-#include <numeric>
 #include <unordered_map>
 #include <unordered_set>
+#include <utility>
 #include <vector>
 
 #include <osmium/builder/osm_object_builder.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/area.hpp>
+#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
+#include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
+#include <osmium/osm/tag.hpp>
+#include <osmium/osm/types.hpp>
+#include <osmium/osm/way.hpp>
 #include <osmium/tags/filter.hpp>
 #include <osmium/util/compatibility.hpp>
 #include <osmium/util/iterator.hpp>
@@ -259,6 +266,9 @@ namespace osmium {
             // Statistics
             area_stats m_stats;
 
+            // The number of members the multipolygon relation has
+            size_t m_num_members = 0;
+
             bool debug() const noexcept {
                 return m_config.debug_level > 1;
             }
@@ -582,7 +592,7 @@ namespace osmium {
                                 if (debug()) {
                                     std::cerr << "        Segment belongs to outer ring\n";
                                 }
-                                int32_t y = int32_t(ay + (by - ay) * (lx - ax) / (bx - ax));
+                                const int32_t y = int32_t(ay + (by - ay) * (lx - ax) / (bx - ax));
                                 outer_rings.emplace_back(y, segment->ring());
                             }
                         }
@@ -819,7 +829,7 @@ namespace osmium {
             }
 
             void create_rings_simple_case() {
-                uint32_t count_remaining = m_segment_list.size();
+                auto count_remaining = m_segment_list.size();
                 for (slocation& sl : m_locations) {
                     const detail::NodeRefSegment& segment = m_segment_list[sl.item];
                     if (!segment.is_done()) {
@@ -1005,7 +1015,7 @@ namespace osmium {
 
                 std::vector<location_to_ring_map> xrings = create_location_to_ring_map(open_ring_its);
 
-                auto ring_min = std::min_element(xrings.begin(), xrings.end(), [](const location_to_ring_map& a, const location_to_ring_map& b) {
+                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();
                 });
 
@@ -1057,7 +1067,7 @@ namespace osmium {
                 }
 
                 // Find the candidate with the smallest/largest area
-                auto chosen_cand = ring_min_is_outer ?
+                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);
                      }) :
@@ -1088,7 +1098,7 @@ namespace osmium {
 
             bool create_rings_complex_case() {
                 // First create all the (partial) rings starting at the split locations
-                uint32_t count_remaining = m_segment_list.size();
+                auto count_remaining = m_segment_list.size();
                 for (const osmium::Location& location : m_split_locations) {
                     const auto locs = make_range(std::equal_range(m_locations.begin(),
                                                                   m_locations.end(),
@@ -1162,6 +1172,20 @@ namespace osmium {
             }
 
             /**
+             * Checks if any ways were completely removed in the
+             * erase_duplicate_segments step.
+             */
+            bool ways_were_lost() {
+                std::unordered_set<const osmium::Way*> ways_in_segments;
+
+                for (const auto& segment : m_segment_list) {
+                    ways_in_segments.insert(segment.way());
+                }
+
+                return ways_in_segments.size() < m_num_members;
+            }
+
+            /**
              * Create rings from segments.
              */
             bool create_rings() {
@@ -1189,6 +1213,15 @@ namespace osmium {
                     return false;
                 }
 
+                // If one or more complete ways was removed because of
+                // duplicate segments, this isn't a valid area.
+                if (ways_were_lost()) {
+                    if (debug()) {
+                        std::cerr << "  Complete ways removed because of duplicate segments\n";
+                    }
+                    return false;
+                }
+
                 if (m_config.debug_level >= 3) {
                     std::cerr << "Sorted de-duplicated segment list:\n";
                     for (const auto& s : m_segment_list) {
@@ -1348,6 +1381,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);
                 builder.initialize_from_object(relation);
 
diff --git a/include/osmium/area/detail/node_ref_segment.hpp b/include/osmium/area/detail/node_ref_segment.hpp
index bb97520..b131a43 100644
--- a/include/osmium/area/detail/node_ref_segment.hpp
+++ b/include/osmium/area/detail/node_ref_segment.hpp
@@ -36,7 +36,6 @@ DEALINGS IN THE SOFTWARE.
 #include <algorithm>
 #include <cassert>
 #include <cstdint>
-#include <cstring>
 #include <iosfwd>
 #include <utility>
 
@@ -242,19 +241,19 @@ namespace osmium {
              */
             inline bool operator<(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept {
                 if (lhs.first().location() == rhs.first().location()) {
-                    vec p0{lhs.first().location()};
-                    vec p1{lhs.second().location()};
-                    vec q0{rhs.first().location()};
-                    vec q1{rhs.second().location()};
-                    vec p = p1 - p0;
-                    vec q = q1 - q0;
+                    const vec p0{lhs.first().location()};
+                    const vec p1{lhs.second().location()};
+                    const vec q0{rhs.first().location()};
+                    const vec q1{rhs.second().location()};
+                    const vec p = p1 - p0;
+                    const vec q = q1 - q0;
 
                     if (p.x == 0 && q.x == 0) {
                         return p.y < q.y;
                     }
 
-                    auto a = p.y * q.x;
-                    auto b = q.y * p.x;
+                    const auto a = p.y * q.x;
+                    const auto b = q.y * p.x;
                     if (a == b) {
                         return p.x < q.x;
                     }
diff --git a/include/osmium/area/detail/proto_ring.hpp b/include/osmium/area/detail/proto_ring.hpp
index c71a5d3..bce6817 100644
--- a/include/osmium/area/detail/proto_ring.hpp
+++ b/include/osmium/area/detail/proto_ring.hpp
@@ -34,10 +34,9 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <algorithm>
+#include <cassert>
 #include <cstdint>
-#include <cstdlib>
 #include <iostream>
-#include <iterator>
 #include <set>
 #include <vector>
 
@@ -47,6 +46,8 @@ DEALINGS IN THE SOFTWARE.
 
 namespace osmium {
 
+    class Way;
+
     namespace area {
 
         namespace detail {
diff --git a/include/osmium/area/detail/segment_list.hpp b/include/osmium/area/detail/segment_list.hpp
index d7c5927..a4361e0 100644
--- a/include/osmium/area/detail/segment_list.hpp
+++ b/include/osmium/area/detail/segment_list.hpp
@@ -35,13 +35,16 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
+#include <cstdint>
+#include <cstring>
 #include <iostream>
+#include <iterator>
 #include <numeric>
 #include <vector>
 
-#include <osmium/area/problem_reporter.hpp>
 #include <osmium/area/detail/node_ref_segment.hpp>
-#include <osmium/memory/buffer.hpp>
+#include <osmium/area/problem_reporter.hpp>
+#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
@@ -60,7 +63,7 @@ namespace osmium {
              * non-way members in the relation.
              */
             template <typename F>
-            inline void for_each_member(const osmium::Relation& relation, const std::vector<const osmium::Way*> ways, F&& func) {
+            inline void for_each_member(const osmium::Relation& relation, const std::vector<const osmium::Way*>& ways, F&& func) {
                 auto way_it = ways.cbegin();
                 for (const osmium::RelationMember& member : relation.members()) {
                     if (member.type() == osmium::item_type::way) {
@@ -225,7 +228,7 @@ namespace osmium {
                 uint32_t extract_segments_from_ways(osmium::area::ProblemReporter* problem_reporter, const osmium::Relation& relation, const std::vector<const osmium::Way*>& members) {
                     assert(relation.members().size() >= members.size());
 
-                    size_t num_segments = get_num_segments(members);
+                    const size_t num_segments = get_num_segments(members);
                     if (problem_reporter) {
                         problem_reporter->set_nodes(num_segments);
                     }
@@ -258,11 +261,13 @@ namespace osmium {
                         }
 
                         // Only count and report duplicate segments if they
-                        // belong to the same way. Those cases are definitely
-                        // wrong. If the duplicate segments belong to
-                        // different ways, they could be touching inner rings
-                        // which are perfectly okay.
-                        if (it->way() == std::next(it)->way()) {
+                        // belong to the same way or if they don't both have
+                        // the role "inner". Those cases are definitely wrong.
+                        // If the duplicate segments belong to different
+                        // "inner" ways, they could be touching inner rings
+                        // which are perfectly okay. Note that for this check
+                        // the role has to be correct in the member data.
+                        if (it->way() == std::next(it)->way() || !it->role_inner() || !std::next(it)->role_inner()) {
                             ++duplicate_segments;
                             if (problem_reporter) {
                                 problem_reporter->report_duplicate_segment(it->first(), it->second());
diff --git a/include/osmium/area/detail/vector.hpp b/include/osmium/area/detail/vector.hpp
index 283801a..44983cc 100644
--- a/include/osmium/area/detail/vector.hpp
+++ b/include/osmium/area/detail/vector.hpp
@@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cstdint>
+#include <iosfwd>
 
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node_ref.hpp>
diff --git a/include/osmium/area/multipolygon_collector.hpp b/include/osmium/area/multipolygon_collector.hpp
index 0f5e4d7..8b37052 100644
--- a/include/osmium/area/multipolygon_collector.hpp
+++ b/include/osmium/area/multipolygon_collector.hpp
@@ -47,7 +47,6 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/osm/tag.hpp>
 #include <osmium/osm/way.hpp>
 #include <osmium/relations/collector.hpp>
-#include <osmium/relations/detail/member_meta.hpp>
 
 namespace osmium {
 
@@ -164,7 +163,7 @@ namespace osmium {
                         m_stats += assembler.stats();
                         possibly_flush_output_buffer();
                     }
-                } catch (osmium::invalid_location&) {
+                } catch (const osmium::invalid_location&) {
                     // XXX ignore
                 }
             }
@@ -176,7 +175,7 @@ namespace osmium {
                 std::vector<const osmium::Way*> ways;
                 for (const auto& member : relation.members()) {
                     if (member.ref() != 0) {
-                        size_t offset = this->get_offset(member.type(), member.ref());
+                        const size_t offset = this->get_offset(member.type(), member.ref());
                         ways.push_back(&buffer.get<const osmium::Way>(offset));
                     }
                 }
@@ -186,7 +185,7 @@ namespace osmium {
                     assembler(relation, ways, m_output_buffer);
                     m_stats += assembler.stats();
                     possibly_flush_output_buffer();
-                } catch (osmium::invalid_location&) {
+                } catch (const osmium::invalid_location&) {
                     // XXX ignore
                 }
             }
diff --git a/include/osmium/area/problem_reporter.hpp b/include/osmium/area/problem_reporter.hpp
index 13fb096..6c56231 100644
--- a/include/osmium/area/problem_reporter.hpp
+++ b/include/osmium/area/problem_reporter.hpp
@@ -33,6 +33,8 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstddef>
+
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/types.hpp>
diff --git a/include/osmium/area/problem_reporter_exception.hpp b/include/osmium/area/problem_reporter_exception.hpp
index 8acd181..009a5f4 100644
--- a/include/osmium/area/problem_reporter_exception.hpp
+++ b/include/osmium/area/problem_reporter_exception.hpp
@@ -43,6 +43,7 @@ DEALINGS IN THE SOFTWARE.
 namespace osmium {
 
     class NodeRef;
+    class Way;
 
     namespace area {
 
diff --git a/include/osmium/area/problem_reporter_ogr.hpp b/include/osmium/area/problem_reporter_ogr.hpp
index b7704a7..0889a58 100644
--- a/include/osmium/area/problem_reporter_ogr.hpp
+++ b/include/osmium/area/problem_reporter_ogr.hpp
@@ -42,7 +42,6 @@ DEALINGS IN THE SOFTWARE.
  * @attention If you include this file, you'll need to link with `libgdal`.
  */
 
-#include <cstdint>
 #include <memory>
 
 #include <gdalcpp.hpp>
@@ -50,6 +49,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/area/problem_reporter.hpp>
 #include <osmium/geom/factory.hpp>
 #include <osmium/geom/ogr.hpp>
+#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/node_ref_list.hpp>
@@ -181,7 +181,7 @@ namespace osmium {
                     feature.set_field("id2", 0);
                     feature.set_field("problem", "way_in_multiple_rings");
                     feature.add_to_layer();
-                } catch (osmium::geometry_error& e) {
+                } catch (const osmium::geometry_error&) {
                     // XXX
                 }
             }
@@ -197,7 +197,7 @@ namespace osmium {
                     feature.set_field("id2", 0);
                     feature.set_field("problem", "inner_with_same_tags");
                     feature.add_to_layer();
-                } catch (osmium::geometry_error& e) {
+                } catch (const osmium::geometry_error&) {
                     // XXX
                 }
             }
@@ -216,7 +216,7 @@ namespace osmium {
                     set_object(feature);
                     feature.set_field("way_id", int32_t(way.id()));
                     feature.add_to_layer();
-                } catch (osmium::geometry_error& e) {
+                } catch (const osmium::geometry_error&) {
                     // XXX
                 }
             }
diff --git a/include/osmium/builder/attr.hpp b/include/osmium/builder/attr.hpp
index d9831c2..2a5b690 100644
--- a/include/osmium/builder/attr.hpp
+++ b/include/osmium/builder/attr.hpp
@@ -46,8 +46,15 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/builder/builder.hpp>
 #include <osmium/builder/osm_object_builder.hpp>
 #include <osmium/memory/buffer.hpp>
+#include <osmium/osm/changeset.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/relation.hpp>
+#include <osmium/osm/timestamp.hpp>
 #include <osmium/osm/types.hpp>
-#include <osmium/osm.hpp>
 
 namespace osmium {
 
@@ -261,6 +268,34 @@ namespace osmium {
 
             }; // class member_type
 
+            class member_type_string {
+
+                osmium::item_type      m_type;
+                osmium::object_id_type m_ref;
+                std::string            m_role;
+
+            public:
+
+                member_type_string(osmium::item_type type, osmium::object_id_type ref, std::string&& role) :
+                    m_type(type),
+                    m_ref(ref),
+                    m_role(std::move(role)) {
+                }
+
+                osmium::item_type type() const noexcept {
+                    return m_type;
+                }
+
+                osmium::object_id_type ref() const noexcept {
+                    return m_ref;
+                }
+
+                const char* role() const noexcept {
+                    return m_role.c_str();
+                }
+
+            }; // class member_type_string
+
             class comment_type {
 
                 osmium::Timestamp    m_date;
diff --git a/include/osmium/builder/builder.hpp b/include/osmium/builder/builder.hpp
index 869fe44..1b274ad 100644
--- a/include/osmium/builder/builder.hpp
+++ b/include/osmium/builder/builder.hpp
@@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
-#include <cstddef>
 #include <cstdint>
 #include <cstring>
 #include <new>
@@ -101,7 +100,7 @@ namespace osmium {
              *
              */
             void add_padding(bool self = false) {
-                auto padding = osmium::memory::align_bytes - (size() % osmium::memory::align_bytes);
+                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);
                     if (self) {
diff --git a/include/osmium/builder/builder_helper.hpp b/include/osmium/builder/builder_helper.hpp
index e1b7c52..5e0f218 100644
--- a/include/osmium/builder/builder_helper.hpp
+++ b/include/osmium/builder/builder_helper.hpp
@@ -56,7 +56,7 @@ namespace osmium {
          * Use osmium::builder::add_way_node_list() instead.
          */
         OSMIUM_DEPRECATED inline const osmium::WayNodeList& build_way_node_list(osmium::memory::Buffer& buffer, const std::initializer_list<osmium::NodeRef>& nodes) {
-            size_t pos = buffer.committed();
+            const size_t pos = buffer.committed();
             {
                 osmium::builder::WayNodeListBuilder wnl_builder(buffer);
                 for (const auto& node_ref : nodes) {
@@ -72,7 +72,7 @@ namespace osmium {
          * Use osmium::builder::add_tag_list() instead.
          */
         inline const osmium::TagList& build_tag_list(osmium::memory::Buffer& buffer, const std::initializer_list<std::pair<const char*, const char*>>& tags) {
-            size_t pos = buffer.committed();
+            const size_t pos = buffer.committed();
             {
                 osmium::builder::TagListBuilder tl_builder(buffer);
                 for (const auto& p : tags) {
@@ -88,7 +88,7 @@ namespace osmium {
          * Use osmium::builder::add_tag_list() instead.
          */
         inline const osmium::TagList& build_tag_list_from_map(osmium::memory::Buffer& buffer, const std::map<const char*, const char*>& tags) {
-            size_t pos = buffer.committed();
+            const size_t pos = buffer.committed();
             {
                 osmium::builder::TagListBuilder tl_builder(buffer);
                 for (const auto& p : tags) {
@@ -104,7 +104,7 @@ namespace osmium {
          * Use osmium::builder::add_tag_list() instead.
          */
         inline const osmium::TagList& build_tag_list_from_func(osmium::memory::Buffer& buffer, std::function<void(osmium::builder::TagListBuilder&)> func) {
-            size_t pos = buffer.committed();
+            const size_t pos = buffer.committed();
             {
                 osmium::builder::TagListBuilder tl_builder(buffer);
                 func(tl_builder);
diff --git a/include/osmium/builder/osm_object_builder.hpp b/include/osmium/builder/osm_object_builder.hpp
index 2ab0e7f..e7a8298 100644
--- a/include/osmium/builder/osm_object_builder.hpp
+++ b/include/osmium/builder/osm_object_builder.hpp
@@ -34,7 +34,6 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
-#include <cstddef>
 #include <cstring>
 #include <initializer_list>
 #include <limits>
@@ -44,16 +43,23 @@ DEALINGS IN THE SOFTWARE.
 #include <utility>
 
 #include <osmium/builder/builder.hpp>
-#include <osmium/osm.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/object.hpp>
 #include <osmium/osm/tag.hpp>
 #include <osmium/osm/types.hpp>
+#include <osmium/memory/item.hpp>
+#include <osmium/osm/area.hpp>
+#include <osmium/osm/changeset.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/timestamp.hpp>
+#include <osmium/osm/way.hpp>
 
 namespace osmium {
 
+    class Node;
+
     namespace memory {
         class Buffer;
     } // namespace memory
diff --git a/include/osmium/diff_iterator.hpp b/include/osmium/diff_iterator.hpp
index 0814fef..fc92b36 100644
--- a/include/osmium/diff_iterator.hpp
+++ b/include/osmium/diff_iterator.hpp
@@ -34,8 +34,10 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
+#include <cstddef>
 #include <iterator>
 #include <type_traits>
+#include <utility>
 
 #include <osmium/osm/diff_object.hpp>
 
diff --git a/include/osmium/dynamic_handler.hpp b/include/osmium/dynamic_handler.hpp
index b250b57..01d1554 100644
--- a/include/osmium/dynamic_handler.hpp
+++ b/include/osmium/dynamic_handler.hpp
@@ -36,11 +36,16 @@ DEALINGS IN THE SOFTWARE.
 #include <memory>
 #include <utility>
 
-#include <osmium/fwd.hpp>
 #include <osmium/handler.hpp>
 
 namespace osmium {
 
+    class Node;
+    class Way;
+    class Relation;
+    class Area;
+    class Changeset;
+
     namespace handler {
 
         namespace detail {
diff --git a/include/osmium/experimental/flex_reader.hpp b/include/osmium/experimental/flex_reader.hpp
index 8059ea3..0a2a668 100644
--- a/include/osmium/experimental/flex_reader.hpp
+++ b/include/osmium/experimental/flex_reader.hpp
@@ -34,11 +34,12 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <osmium/area/assembler.hpp>
 #include <osmium/area/multipolygon_collector.hpp>
-#include <osmium/handler/node_locations_for_ways.hpp>
+#include <osmium/handler/node_locations_for_ways.hpp> // IWYU pragma: keep
 #include <osmium/io/file.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/io/reader.hpp>
diff --git a/include/osmium/geom/factory.hpp b/include/osmium/geom/factory.hpp
index f03eec5..14c51df 100644
--- a/include/osmium/geom/factory.hpp
+++ b/include/osmium/geom/factory.hpp
@@ -46,6 +46,8 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node.hpp>
 #include <osmium/osm/node_ref.hpp>
+#include <osmium/osm/node_ref_list.hpp>
+#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
 
 namespace osmium {
@@ -281,7 +283,7 @@ namespace osmium {
                 }
 
                 if (num_points < 2) {
-                    throw osmium::geometry_error("need at least two points for linestring");
+                    throw osmium::geometry_error{"need at least two points for linestring"};
                 }
 
                 return linestring_finish(num_points);
@@ -355,7 +357,7 @@ namespace osmium {
                 }
 
                 if (num_points < 4) {
-                    throw osmium::geometry_error("need at least four points for polygon");
+                    throw osmium::geometry_error{"need at least four points for polygon"};
                 }
 
                 return polygon_finish(num_points);
@@ -401,7 +403,7 @@ namespace osmium {
 
                     // if there are no rings, this area is invalid
                     if (num_rings == 0) {
-                        throw osmium::geometry_error("area contains no rings");
+                        throw osmium::geometry_error{"invalid area"};
                     }
 
                     m_impl.multipolygon_polygon_finish();
diff --git a/include/osmium/geom/geojson.hpp b/include/osmium/geom/geojson.hpp
index ffa9440..e9f722f 100644
--- a/include/osmium/geom/geojson.hpp
+++ b/include/osmium/geom/geojson.hpp
@@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
+#include <cstddef>
 #include <string>
 #include <utility>
 
diff --git a/include/osmium/geom/geos.hpp b/include/osmium/geom/geos.hpp
index 4a097e9..f406076 100644
--- a/include/osmium/geom/geos.hpp
+++ b/include/osmium/geom/geos.hpp
@@ -42,9 +42,14 @@ DEALINGS IN THE SOFTWARE.
  * @attention If you include this file, you'll need to link with `libgeos`.
  */
 
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <iterator>
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include <geos/geom/Coordinate.h>
 #include <geos/geom/CoordinateSequence.h>
@@ -65,6 +70,7 @@ DEALINGS IN THE SOFTWARE.
 #ifdef _MSC_VER
 # define THROW throw
 #else
+# include <exception>
 # define THROW std::throw_with_nested
 #endif
 
@@ -72,8 +78,8 @@ namespace osmium {
 
     struct geos_geometry_error : public geometry_error {
 
-        geos_geometry_error(const char* message) :
-            geometry_error(std::string("geometry creation failed in GEOS library: ") + message) {
+        explicit geos_geometry_error(const char* message) :
+            geometry_error(std::string{"geometry creation failed in GEOS library: "} + message) {
         }
 
     }; // struct geos_geometry_error
@@ -127,7 +133,7 @@ namespace osmium {
                 point_type make_point(const osmium::geom::Coordinates& xy) const {
                     try {
                         return point_type(m_geos_factory->createPoint(geos::geom::Coordinate(xy.x, xy.y)));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -137,7 +143,7 @@ namespace osmium {
                 void linestring_start() {
                     try {
                         m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -145,7 +151,7 @@ namespace osmium {
                 void linestring_add_location(const osmium::geom::Coordinates& xy) {
                     try {
                         m_coordinate_sequence->add(geos::geom::Coordinate(xy.x, xy.y));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -153,7 +159,7 @@ namespace osmium {
                 linestring_type linestring_finish(size_t /* num_points */) {
                     try {
                         return linestring_type(m_geos_factory->createLineString(m_coordinate_sequence.release()));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -177,7 +183,7 @@ namespace osmium {
                         });
                         m_polygons.emplace_back(m_geos_factory->createPolygon(m_rings[0].release(), inner_rings));
                         m_rings.clear();
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -185,7 +191,7 @@ namespace osmium {
                 void multipolygon_outer_ring_start() {
                     try {
                         m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -193,7 +199,7 @@ namespace osmium {
                 void multipolygon_outer_ring_finish() {
                     try {
                         m_rings.emplace_back(m_geos_factory->createLinearRing(m_coordinate_sequence.release()));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -201,7 +207,7 @@ namespace osmium {
                 void multipolygon_inner_ring_start() {
                     try {
                         m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -209,7 +215,7 @@ namespace osmium {
                 void multipolygon_inner_ring_finish() {
                     try {
                         m_rings.emplace_back(m_geos_factory->createLinearRing(m_coordinate_sequence.release()));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -217,7 +223,7 @@ namespace osmium {
                 void multipolygon_add_location(const osmium::geom::Coordinates& xy) {
                     try {
                         m_coordinate_sequence->add(geos::geom::Coordinate(xy.x, xy.y));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
@@ -230,7 +236,7 @@ namespace osmium {
                         });
                         m_polygons.clear();
                         return multipolygon_type(m_geos_factory->createMultiPolygon(polygons));
-                    } catch (geos::util::GEOSException& e) {
+                    } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
diff --git a/include/osmium/geom/haversine.hpp b/include/osmium/geom/haversine.hpp
index 632bc16..ef3f1ff 100644
--- a/include/osmium/geom/haversine.hpp
+++ b/include/osmium/geom/haversine.hpp
@@ -56,7 +56,7 @@ namespace osmium {
         namespace haversine {
 
             /// @brief Earth's quadratic mean radius for WGS84
-            constexpr double EARTH_RADIUS_IN_METERS = 6372797.560856;
+            constexpr const double EARTH_RADIUS_IN_METERS = 6372797.560856;
 
             /**
              * Calculate distance in meters between two sets of coordinates.
diff --git a/include/osmium/geom/ogr.hpp b/include/osmium/geom/ogr.hpp
index a457d66..a91fbe5 100644
--- a/include/osmium/geom/ogr.hpp
+++ b/include/osmium/geom/ogr.hpp
@@ -77,7 +77,7 @@ namespace osmium {
 
             public:
 
-                OGRFactoryImpl(int /* srid */) {
+                explicit OGRFactoryImpl(int /* srid */) {
                 }
 
                 /* Point */
diff --git a/include/osmium/geom/projection.hpp b/include/osmium/geom/projection.hpp
index 42d95b2..eaa9b53 100644
--- a/include/osmium/geom/projection.hpp
+++ b/include/osmium/geom/projection.hpp
@@ -70,15 +70,19 @@ namespace osmium {
 
         public:
 
-            CRS(const std::string& crs) :
+            explicit CRS(const std::string& crs) :
                 m_crs(pj_init_plus(crs.c_str()), ProjCRSDeleter()) {
                 if (!m_crs) {
-                    throw osmium::projection_error(std::string("creation of CRS failed: ") + pj_strerrno(*pj_get_errno_ref()));
+                    throw osmium::projection_error(std::string{"creation of CRS failed: "} + pj_strerrno(*pj_get_errno_ref()));
                 }
             }
 
-            CRS(int epsg) :
-                CRS(std::string("+init=epsg:") + std::to_string(epsg)) {
+            explicit CRS(const char* crs) :
+                CRS(std::string{crs}) {
+            }
+
+            explicit CRS(int epsg) :
+                CRS(std::string{"+init=epsg:"} + std::to_string(epsg)) {
             }
 
             /**
@@ -127,13 +131,19 @@ namespace osmium {
 
         public:
 
-            Projection(const std::string& proj_string) :
+            explicit Projection(const std::string& proj_string) :
+                m_epsg(-1),
+                m_proj_string(proj_string),
+                m_crs_user(proj_string) {
+            }
+
+            explicit Projection(const char* proj_string) :
                 m_epsg(-1),
                 m_proj_string(proj_string),
                 m_crs_user(proj_string) {
             }
 
-            Projection(int epsg) :
+            explicit Projection(int epsg) :
                 m_epsg(epsg),
                 m_proj_string(std::string("+init=epsg:") + std::to_string(epsg)),
                 m_crs_user(epsg) {
diff --git a/include/osmium/geom/rapid_geojson.hpp b/include/osmium/geom/rapid_geojson.hpp
index 340f0c1..7817389 100644
--- a/include/osmium/geom/rapid_geojson.hpp
+++ b/include/osmium/geom/rapid_geojson.hpp
@@ -33,6 +33,8 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstddef>
+
 #include <osmium/geom/coordinates.hpp>
 #include <osmium/geom/factory.hpp>
 
diff --git a/include/osmium/geom/tile.hpp b/include/osmium/geom/tile.hpp
index e35c2ee..672ae54 100644
--- a/include/osmium/geom/tile.hpp
+++ b/include/osmium/geom/tile.hpp
@@ -33,9 +33,12 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cassert>
 #include <cstdint>
 
+#include <osmium/geom/coordinates.hpp>
 #include <osmium/geom/mercator_projection.hpp>
+#include <osmium/osm/location.hpp>
 
 namespace osmium {
 
@@ -57,24 +60,64 @@ namespace osmium {
          */
         struct Tile {
 
+            /// x coordinate
             uint32_t x;
+
+            /// y coordinate
             uint32_t y;
+
+            /// Zoom level
             uint32_t z;
 
+            /**
+             * Create a tile with the given zoom level and x any y tile
+             * coordinates.
+             *
+             * The values are not checked for validity.
+             *
+             * @pre @code zoom <= 30 && x < 2^zoom && y < 2^zoom @endcode
+             */
             explicit Tile(uint32_t zoom, uint32_t tx, uint32_t ty) noexcept : x(tx), y(ty), z(zoom) {
+                assert(zoom <= 30u);
+                assert(x < (1u << zoom));
+                assert(y < (1u << zoom));
             }
 
+            /**
+             * Create a tile with the given zoom level that contains the given
+             * location.
+             *
+             * The values are not checked for validity.
+             *
+             * @pre @code location.valid() && zoom <= 30 @endcode
+             */
             explicit Tile(uint32_t zoom, const osmium::Location& location) :
                 z(zoom) {
-                osmium::geom::Coordinates c = lonlat_to_mercator(location);
+                assert(zoom <= 30u);
+                assert(location.valid());
+                const osmium::geom::Coordinates c = lonlat_to_mercator(location);
                 const int32_t n = 1 << zoom;
                 const double scale = detail::max_coordinate_epsg3857 * 2 / n;
                 x = uint32_t(detail::restrict_to_range<int32_t>(int32_t((c.x + detail::max_coordinate_epsg3857) / scale), 0, n-1));
                 y = uint32_t(detail::restrict_to_range<int32_t>(int32_t((detail::max_coordinate_epsg3857 - c.y) / scale), 0, n-1));
             }
 
+            /**
+             * Check whether this tile is valid. For a tile to be valid the
+             * zoom level must be between 0 and 30 and the coordinates must
+             * each be between 0 and 2^zoom-1.
+             */
+            bool valid() const noexcept {
+                if (z > 30) {
+                    return false;
+                }
+                const uint32_t max = 1 << z;
+                return x < max && y < max;
+            }
+
         }; // 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;
         }
diff --git a/include/osmium/geom/util.hpp b/include/osmium/geom/util.hpp
index fa9c8bc..b5682f9 100644
--- a/include/osmium/geom/util.hpp
+++ b/include/osmium/geom/util.hpp
@@ -44,11 +44,11 @@ namespace osmium {
      */
     struct projection_error : public std::runtime_error {
 
-        projection_error(const std::string& what) :
+        explicit projection_error(const std::string& what) :
             std::runtime_error(what) {
         }
 
-        projection_error(const char* what) :
+        explicit projection_error(const char* what) :
             std::runtime_error(what) {
         }
 
diff --git a/include/osmium/geom/wkb.hpp b/include/osmium/geom/wkb.hpp
index efb07c6..d6b7798 100644
--- a/include/osmium/geom/wkb.hpp
+++ b/include/osmium/geom/wkb.hpp
@@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <algorithm>
 #include <cstddef>
 #include <cstdint>
 #include <string>
@@ -60,14 +61,13 @@ namespace osmium {
 
             template <typename T>
             inline void str_push(std::string& str, T data) {
-                size_t size = str.size();
-                str.resize(size + sizeof(T));
-                std::copy_n(reinterpret_cast<char*>(&data), sizeof(T), &str[size]);
+                str.append(reinterpret_cast<const char*>(&data), sizeof(T));
             }
 
             inline std::string convert_to_hex(const std::string& str) {
                 static const char* lookup_hex = "0123456789ABCDEF";
                 std::string out;
+                out.reserve(str.size() * 2);
 
                 for (char c : str) {
                     out += lookup_hex[(c >> 4) & 0xf];
@@ -132,7 +132,7 @@ namespace osmium {
                     } else {
                         str_push(str, type);
                     }
-                    size_t offset = str.size();
+                    const size_t offset = str.size();
                     if (add_length) {
                         str_push(str, static_cast<uint32_t>(0));
                     }
diff --git a/include/osmium/geom/wkt.hpp b/include/osmium/geom/wkt.hpp
index 6113674..e360f71 100644
--- a/include/osmium/geom/wkt.hpp
+++ b/include/osmium/geom/wkt.hpp
@@ -59,9 +59,6 @@ namespace osmium {
                 int m_precision;
                 wkt_type m_wkt_type;
 
-                void init_srid() {
-                }
-
             public:
 
                 using point_type        = std::string;
diff --git a/include/osmium/handler.hpp b/include/osmium/handler.hpp
index 3620d65..1263910 100644
--- a/include/osmium/handler.hpp
+++ b/include/osmium/handler.hpp
@@ -33,56 +33,82 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <osmium/fwd.hpp>
-
 namespace osmium {
 
+    class Area;
+    class Changeset;
+    class ChangesetDiscussion;
+    class InnerRing;
+    class Node;
+    class OSMObject;
+    class OuterRing;
+    class Relation;
+    class RelationMemberList;
+    class TagList;
+    class Way;
+    class WayNodeList;
+
     /**
      * @brief Osmium handlers provide callbacks for OSM objects
      */
     namespace handler {
 
+        /**
+         * Handler base class. Never used directly. Derive your own class from
+         * this class and "overwrite" the functions. Your functions must be
+         * named the same, but don't have to be const or noexcept or take
+         * their argument as const.
+         *
+         * Usually you will overwrite the node(), way(), and relation()
+         * functions. If your program supports multipolygons, also the area()
+         * function. You can also use the osm_object() function which is
+         * called for all OSM objects (nodes, ways, relations, and areas)
+         * right before each of their specific callbacks is called.
+         *
+         * If you are working with changesets, implement the changeset()
+         * function.
+         */
         class Handler {
 
         public:
 
-            void osm_object(const osmium::OSMObject&) const {
+            void osm_object(const osmium::OSMObject&) const noexcept {
             }
 
-            void node(const osmium::Node&) const {
+            void node(const osmium::Node&) const noexcept {
             }
 
-            void way(const osmium::Way&) const {
+            void way(const osmium::Way&) const noexcept {
             }
 
-            void relation(const osmium::Relation&) const {
+            void relation(const osmium::Relation&) const noexcept {
             }
 
-            void area(const osmium::Area&) const {
+            void area(const osmium::Area&) const noexcept {
             }
 
-            void changeset(const osmium::Changeset&) const {
+            void changeset(const osmium::Changeset&) const noexcept {
             }
 
-            void tag_list(const osmium::TagList&) const {
+            void tag_list(const osmium::TagList&) const noexcept {
             }
 
-            void way_node_list(const osmium::WayNodeList&) const {
+            void way_node_list(const osmium::WayNodeList&) const noexcept {
             }
 
-            void relation_member_list(const osmium::RelationMemberList&) const {
+            void relation_member_list(const osmium::RelationMemberList&) const noexcept {
             }
 
-            void outer_ring(const osmium::OuterRing&) const {
+            void outer_ring(const osmium::OuterRing&) const noexcept {
             }
 
-            void inner_ring(const osmium::InnerRing&) const {
+            void inner_ring(const osmium::InnerRing&) const noexcept {
             }
 
-            void changeset_discussion(const osmium::ChangesetDiscussion&) const {
+            void changeset_discussion(const osmium::ChangesetDiscussion&) const noexcept {
             }
 
-            void flush() const {
+            void flush() const noexcept {
             }
 
         }; // class Handler
diff --git a/include/osmium/handler/check_order.hpp b/include/osmium/handler/check_order.hpp
index 143794b..a9b6834 100644
--- a/include/osmium/handler/check_order.hpp
+++ b/include/osmium/handler/check_order.hpp
@@ -51,11 +51,11 @@ namespace osmium {
      */
     struct out_of_order_error : public std::runtime_error {
 
-        out_of_order_error(const std::string& what) :
+        explicit out_of_order_error(const std::string& what) :
             std::runtime_error(what) {
         }
 
-        out_of_order_error(const char* what) :
+        explicit out_of_order_error(const char* what) :
             std::runtime_error(what) {
         }
 
diff --git a/include/osmium/handler/node_locations_for_ways.hpp b/include/osmium/handler/node_locations_for_ways.hpp
index e836397..a490f9e 100644
--- a/include/osmium/handler/node_locations_for_ways.hpp
+++ b/include/osmium/handler/node_locations_for_ways.hpp
@@ -160,7 +160,7 @@ namespace osmium {
                         if (!node_ref.location()) {
                             error = true;
                         }
-                    } catch (osmium::not_found&) {
+                    } catch (const osmium::not_found&) {
                         error = true;
                     }
                 }
diff --git a/include/osmium/index/detail/create_map_with_fd.hpp b/include/osmium/index/detail/create_map_with_fd.hpp
index 640b3c6..72b326a 100644
--- a/include/osmium/index/detail/create_map_with_fd.hpp
+++ b/include/osmium/index/detail/create_map_with_fd.hpp
@@ -54,9 +54,9 @@ namespace osmium {
                 }
                 assert(config.size() > 1);
                 const std::string& filename = config[1];
-                int fd = ::open(filename.c_str(), O_CREAT | O_RDWR, 0644);
+                const int fd = ::open(filename.c_str(), O_CREAT | O_RDWR, 0644);
                 if (fd == -1) {
-                    throw std::runtime_error(std::string("can't open file '") + filename + "': " + strerror(errno));
+                    throw std::runtime_error(std::string("can't open file '") + filename + "': " + std::strerror(errno));
                 }
                 return new T(fd);
             }
diff --git a/include/osmium/index/detail/mmap_vector_file.hpp b/include/osmium/index/detail/mmap_vector_file.hpp
index e077c60..9e72f5a 100644
--- a/include/osmium/index/detail/mmap_vector_file.hpp
+++ b/include/osmium/index/detail/mmap_vector_file.hpp
@@ -34,6 +34,8 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <algorithm>
+#include <cstddef>
+#include <stdexcept>
 #include <string>
 
 #include <osmium/index/detail/mmap_vector_base.hpp>
@@ -52,7 +54,7 @@ namespace osmium {
         class mmap_vector_file : public mmap_vector_base<T> {
 
             size_t filesize(int fd) const {
-                size_t size = osmium::util::file_size(fd);
+                const size_t size = osmium::util::file_size(fd);
 
                 if (size % sizeof(T) != 0) {
                     throw std::runtime_error("Index file has wrong size (must be multiple of " + std::to_string(sizeof(T)) + ").");
diff --git a/include/osmium/index/detail/vector_map.hpp b/include/osmium/index/detail/vector_map.hpp
index 390a402..ac87c2f 100644
--- a/include/osmium/index/detail/vector_map.hpp
+++ b/include/osmium/index/detail/vector_map.hpp
@@ -88,7 +88,7 @@ namespace osmium {
                             not_found_error(id);
                         }
                         return value;
-                    } catch (std::out_of_range&) {
+                    } catch (const std::out_of_range&) {
                         not_found_error(id);
                     }
                 }
diff --git a/include/osmium/index/detail/vector_multimap.hpp b/include/osmium/index/detail/vector_multimap.hpp
index af2ec53..f859f5b 100644
--- a/include/osmium/index/detail/vector_multimap.hpp
+++ b/include/osmium/index/detail/vector_multimap.hpp
@@ -127,7 +127,7 @@ namespace osmium {
                 }
 
                 void remove(const TId id, const TValue value) {
-                    auto r = get_all(id);
+                    const auto r = get_all(id);
                     for (auto it = r.first; it != r.second; ++it) {
                         if (it->second == value) {
                             it->second = 0;
diff --git a/include/osmium/index/index.hpp b/include/osmium/index/index.hpp
index 37d022d..c3deead 100644
--- a/include/osmium/index/index.hpp
+++ b/include/osmium/index/index.hpp
@@ -49,11 +49,11 @@ namespace osmium {
      */
     struct not_found : public std::runtime_error {
 
-        not_found(const std::string& what) :
+        explicit not_found(const std::string& what) :
             std::runtime_error(what) {
         }
 
-        not_found(const char* what) :
+        explicit not_found(const char* what) :
             std::runtime_error(what) {
         }
 
diff --git a/include/osmium/index/map.hpp b/include/osmium/index/map.hpp
index c8f7598..1d2d5aa 100644
--- a/include/osmium/index/map.hpp
+++ b/include/osmium/index/map.hpp
@@ -207,7 +207,7 @@ namespace osmium {
             }
 
             bool has_map_type(const std::string& map_type_name) const {
-                return m_callbacks.count(map_type_name);
+                return m_callbacks.count(map_type_name) != 0;
             }
 
             std::vector<std::string> map_types() const {
diff --git a/include/osmium/index/map/sparse_mem_map.hpp b/include/osmium/index/map/sparse_mem_map.hpp
index 95f570b..1e3c58c 100644
--- a/include/osmium/index/map/sparse_mem_map.hpp
+++ b/include/osmium/index/map/sparse_mem_map.hpp
@@ -37,7 +37,6 @@ DEALINGS IN THE SOFTWARE.
 #include <cstddef>
 #include <iterator>
 #include <map>
-#include <stdexcept>
 #include <vector>
 
 #include <osmium/index/map.hpp>
diff --git a/include/osmium/io/any_input.hpp b/include/osmium/io/any_input.hpp
index dd84451..e4617e8 100644
--- a/include/osmium/io/any_input.hpp
+++ b/include/osmium/io/any_input.hpp
@@ -45,8 +45,9 @@ DEALINGS IN THE SOFTWARE.
 
 #include <osmium/io/any_compression.hpp> // IWYU pragma: export
 
+#include <osmium/io/o5m_input.hpp> // IWYU pragma: export
+#include <osmium/io/opl_input.hpp> // IWYU pragma: export
 #include <osmium/io/pbf_input.hpp> // IWYU pragma: export
 #include <osmium/io/xml_input.hpp> // IWYU pragma: export
-#include <osmium/io/o5m_input.hpp> // IWYU pragma: export
 
 #endif // OSMIUM_IO_ANY_INPUT_HPP
diff --git a/include/osmium/io/bzip2_compression.hpp b/include/osmium/io/bzip2_compression.hpp
index e5cad0b..63b4cad 100644
--- a/include/osmium/io/bzip2_compression.hpp
+++ b/include/osmium/io/bzip2_compression.hpp
@@ -43,10 +43,9 @@ DEALINGS IN THE SOFTWARE.
  */
 
 #include <cerrno>
-#include <cstddef>
 #include <cstdio>
-#include <stdexcept>
 #include <string>
+#include <system_error>
 
 #include <bzlib.h>
 
@@ -55,6 +54,7 @@ DEALINGS IN THE SOFTWARE.
 #endif
 
 #include <osmium/io/compression.hpp>
+#include <osmium/io/detail/read_write.hpp>
 #include <osmium/io/error.hpp>
 #include <osmium/io/file_compression.hpp>
 #include <osmium/io/writer_options.hpp>
@@ -109,7 +109,7 @@ namespace osmium {
 
             explicit Bzip2Compressor(int fd, fsync sync) :
                 Compressor(sync),
-                m_file(fdopen(dup(fd), "wb")),
+                m_file(fdopen(::dup(fd), "wb")),
                 m_bzerror(BZ_OK),
                 m_bzfile(::BZ2_bzWriteOpen(&m_bzerror, m_file, 6, 0, 0)) {
                 if (!m_bzfile) {
@@ -165,7 +165,7 @@ namespace osmium {
 
             explicit Bzip2Decompressor(int fd) :
                 Decompressor(),
-                m_file(fdopen(dup(fd), "rb")),
+                m_file(fdopen(::dup(fd), "rb")),
                 m_bzerror(BZ_OK),
                 m_bzfile(::BZ2_bzReadOpen(&m_bzerror, m_file, 0, 0, nullptr, 0)) {
                 if (!m_bzfile) {
@@ -215,6 +215,8 @@ namespace osmium {
                     buffer.resize(static_cast<std::string::size_type>(nread));
                 }
 
+                set_offset(size_t(ftell(m_file)));
+
                 return buffer;
             }
 
diff --git a/include/osmium/io/compression.hpp b/include/osmium/io/compression.hpp
index fc14e26..e7f93bd 100644
--- a/include/osmium/io/compression.hpp
+++ b/include/osmium/io/compression.hpp
@@ -33,11 +33,12 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <atomic>
 #include <cerrno>
+#include <cstddef>
 #include <functional>
 #include <map>
 #include <memory>
-#include <stdexcept>
 #include <string>
 #include <system_error>
 #include <tuple>
@@ -54,6 +55,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/file_compression.hpp>
 #include <osmium/io/writer_options.hpp>
 #include <osmium/util/compatibility.hpp>
+#include <osmium/util/file.hpp>
 
 namespace osmium {
 
@@ -86,6 +88,9 @@ namespace osmium {
 
         class Decompressor {
 
+            std::atomic<size_t> m_file_size {0};
+            std::atomic<size_t> m_offset {0};
+
         public:
 
             static constexpr unsigned int input_buffer_size = 1024 * 1024;
@@ -105,6 +110,22 @@ namespace osmium {
 
             virtual void close() = 0;
 
+            size_t file_size() const noexcept {
+                return m_file_size;
+            }
+
+            void set_file_size(size_t size) noexcept {
+                m_file_size = size;
+            }
+
+            size_t offset() const noexcept {
+                return m_offset;
+            }
+
+            void set_offset(size_t offset) noexcept {
+                m_offset = offset;
+            }
+
         }; // class Decompressor
 
         /**
@@ -182,7 +203,9 @@ namespace osmium {
                 auto it = m_callbacks.find(compression);
 
                 if (it != m_callbacks.end()) {
-                    return std::unique_ptr<osmium::io::Decompressor>(std::get<1>(it->second)(fd));
+                    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);
@@ -241,6 +264,7 @@ namespace osmium {
             int m_fd;
             const char *m_buffer;
             size_t m_buffer_size;
+            size_t m_offset = 0;
 
         public:
 
@@ -284,6 +308,9 @@ namespace osmium {
                     buffer.resize(std::string::size_type(nread));
                 }
 
+                m_offset += buffer.size();
+                set_offset(m_offset);
+
                 return buffer;
             }
 
diff --git a/include/osmium/io/detail/debug_output_format.hpp b/include/osmium/io/detail/debug_output_format.hpp
index 7ae0807..dc5323a 100644
--- a/include/osmium/io/detail/debug_output_format.hpp
+++ b/include/osmium/io/detail/debug_output_format.hpp
@@ -34,32 +34,35 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cinttypes>
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <future>
+#include <cmath>
+#include <cstring>
 #include <iterator>
 #include <memory>
 #include <string>
-#include <thread>
 #include <utility>
 
 #include <boost/crc.hpp>
 
 #include <osmium/io/detail/output_format.hpp>
+#include <osmium/io/detail/queue_util.hpp>
+#include <osmium/io/detail/string_util.hpp>
+#include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
+#include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/memory/collection.hpp>
+#include <osmium/memory/item_iterator.hpp>
 #include <osmium/osm/box.hpp>
 #include <osmium/osm/changeset.hpp>
 #include <osmium/osm/crc.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/relation.hpp>
 #include <osmium/osm/tag.hpp>
 #include <osmium/osm/timestamp.hpp>
+#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
 #include <osmium/thread/pool.hpp>
 #include <osmium/util/minmax.hpp>
@@ -69,8 +72,6 @@ namespace osmium {
 
     namespace io {
 
-        class File;
-
         namespace detail {
 
             constexpr const char* color_bold    = "\x1b[1m";
@@ -83,6 +84,10 @@ namespace osmium {
             constexpr const char* color_magenta = "\x1b[35m";
             constexpr const char* color_cyan    = "\x1b[36m";
             constexpr const char* color_white   = "\x1b[37m";
+
+            constexpr const char* color_backg_red   = "\x1b[41m";
+            constexpr const char* color_backg_green = "\x1b[42m";
+
             constexpr const char* color_reset   = "\x1b[0m";
 
             struct debug_output_options {
@@ -96,6 +101,8 @@ namespace osmium {
                 /// Add CRC32 checksum to each object?
                 bool add_crc32;
 
+                /// Write in form of a diff file?
+                bool format_as_diff;
             };
 
             /**
@@ -108,6 +115,8 @@ namespace osmium {
                 const char* m_utf8_prefix = "";
                 const char* m_utf8_suffix = "";
 
+                char m_diff_char = '\0';
+
                 void append_encoded_string(const char* data) {
                     append_debug_encoded_string(*m_out, data, m_utf8_prefix, m_utf8_suffix);
                 }
@@ -123,6 +132,30 @@ namespace osmium {
                     }
                 }
 
+                void write_diff() {
+                    if (!m_diff_char) {
+                        return;
+                    }
+                    if (m_options.use_color) {
+                        if (m_diff_char == '-') {
+                            *m_out += color_backg_red;
+                            *m_out += color_white;
+                            *m_out += color_bold;
+                            *m_out += '-';
+                            *m_out += color_reset;
+                            return;
+                        } else if (m_diff_char == '+') {
+                            *m_out += color_backg_green;
+                            *m_out += color_white;
+                            *m_out += color_bold;
+                            *m_out += '+';
+                            *m_out += color_reset;
+                            return;
+                        }
+                    }
+                    *m_out += m_diff_char;
+                }
+
                 void write_string(const char* string) {
                     *m_out += '"';
                     write_color(color_blue);
@@ -132,6 +165,7 @@ namespace osmium {
                 }
 
                 void write_object_type(const char* object_type, bool visible = true) {
+                    write_diff();
                     if (visible) {
                         write_color(color_bold);
                     } else {
@@ -143,6 +177,7 @@ namespace osmium {
                 }
 
                 void write_fieldname(const char* name) {
+                    write_diff();
                     *m_out += "  ";
                     write_color(color_cyan);
                     *m_out += name;
@@ -208,28 +243,30 @@ namespace osmium {
                 }
 
                 void write_tags(const osmium::TagList& tags, const char* padding="") {
-                    if (!tags.empty()) {
-                        write_fieldname("tags");
-                        *m_out += padding;
-                        *m_out += "     ";
-                        output_int(tags.size());
-                        *m_out += '\n';
+                    if (tags.empty()) {
+                        return;
+                    }
+                    write_fieldname("tags");
+                    *m_out += padding;
+                    *m_out += "     ";
+                    output_int(tags.size());
+                    *m_out += '\n';
 
-                        osmium::max_op<size_t> max;
-                        for (const auto& tag : tags) {
-                            max.update(std::strlen(tag.key()));
-                        }
-                        for (const auto& tag : tags) {
-                            *m_out += "    ";
-                            write_string(tag.key());
-                            auto spacing = max() - std::strlen(tag.key());
-                            while (spacing--) {
-                                *m_out += " ";
-                            }
-                            *m_out += " = ";
-                            write_string(tag.value());
-                            *m_out += '\n';
+                    osmium::max_op<size_t> max;
+                    for (const auto& tag : tags) {
+                        max.update(std::strlen(tag.key()));
+                    }
+                    for (const auto& tag : tags) {
+                        write_diff();
+                        *m_out += "    ";
+                        write_string(tag.key());
+                        auto spacing = max() - std::strlen(tag.key());
+                        while (spacing--) {
+                            *m_out += " ";
                         }
+                        *m_out += " = ";
+                        write_string(tag.value());
+                        *m_out += '\n';
                     }
                 }
 
@@ -303,6 +340,8 @@ namespace osmium {
                 }
 
                 void node(const osmium::Node& node) {
+                    m_diff_char = m_options.format_as_diff ? node.diff_as_char() : '\0';
+
                     write_object_type("node", node.visible());
                     write_meta(node);
 
@@ -320,6 +359,8 @@ namespace osmium {
                 }
 
                 void way(const osmium::Way& way) {
+                    m_diff_char = m_options.format_as_diff ? way.diff_as_char() : '\0';
+
                     write_object_type("way", way.visible());
                     write_meta(way);
                     write_tags(way.tags());
@@ -338,9 +379,10 @@ namespace osmium {
                         *m_out += " (open)\n";
                     }
 
-                    int width = int(log10(way.nodes().size())) + 1;
+                    const int width = int(std::log10(way.nodes().size())) + 1;
                     int n = 0;
                     for (const auto& node_ref : way.nodes()) {
+                        write_diff();
                         write_counter(width, n++);
                         output_formatted("%10" PRId64, node_ref.ref());
                         if (node_ref.location().valid()) {
@@ -360,6 +402,9 @@ namespace osmium {
 
                 void relation(const osmium::Relation& relation) {
                     static const char* short_typename[] = { "node", "way ", "rel " };
+
+                    m_diff_char = m_options.format_as_diff ? relation.diff_as_char() : '\0';
+
                     write_object_type("relation", relation.visible());
                     write_meta(relation);
                     write_tags(relation.tags());
@@ -369,9 +414,10 @@ namespace osmium {
                     output_int(relation.members().size());
                     *m_out += '\n';
 
-                    int width = int(log10(relation.members().size())) + 1;
+                    const int width = int(std::log10(relation.members().size())) + 1;
                     int n = 0;
                     for (const auto& member : relation.members()) {
+                        write_diff();
                         write_counter(width, n++);
                         *m_out += short_typename[item_type_to_nwr_index(member.type())];
                         output_formatted(" %10" PRId64 " ", member.ref());
@@ -426,7 +472,7 @@ namespace osmium {
                         output_int(changeset.num_comments());
                         *m_out += '\n';
 
-                        int width = int(log10(changeset.num_comments())) + 1;
+                        const int width = int(std::log10(changeset.num_comments())) + 1;
                         int n = 0;
                         for (const auto& comment : changeset.discussion()) {
                             write_counter(width, n++);
@@ -477,9 +523,10 @@ namespace osmium {
                 DebugOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) :
                     OutputFormat(output_queue),
                     m_options() {
-                    m_options.add_metadata = file.is_not_false("add_metadata");
-                    m_options.use_color    = file.is_true("color");
-                    m_options.add_crc32    = file.is_true("add_crc32");
+                    m_options.add_metadata   = file.is_not_false("add_metadata");
+                    m_options.use_color      = file.is_true("color");
+                    m_options.add_crc32      = file.is_true("add_crc32");
+                    m_options.format_as_diff = file.is_true("diff");
                 }
 
                 DebugOutputFormat(const DebugOutputFormat&) = delete;
@@ -488,6 +535,10 @@ namespace osmium {
                 ~DebugOutputFormat() noexcept final = default;
 
                 void write_header(const osmium::io::Header& header) final {
+                    if (m_options.format_as_diff) {
+                        return;
+                    }
+
                     std::string out;
 
                     if (m_options.use_color) {
diff --git a/include/osmium/io/detail/input_format.hpp b/include/osmium/io/detail/input_format.hpp
index e04e57f..98081e3 100644
--- a/include/osmium/io/detail/input_format.hpp
+++ b/include/osmium/io/detail/input_format.hpp
@@ -38,11 +38,11 @@ DEALINGS IN THE SOFTWARE.
 #include <future>
 #include <map>
 #include <memory>
-#include <stdexcept>
 #include <string>
 #include <utility>
 
 #include <osmium/io/detail/queue_util.hpp>
+#include <osmium/io/error.hpp>
 #include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
diff --git a/include/osmium/io/detail/o5m_input_format.hpp b/include/osmium/io/detail/o5m_input_format.hpp
index 7293cfa..d642879 100644
--- a/include/osmium/io/detail/o5m_input_format.hpp
+++ b/include/osmium/io/detail/o5m_input_format.hpp
@@ -42,9 +42,9 @@ DEALINGS IN THE SOFTWARE.
 #include <string>
 #include <utility>
 
+#include <protozero/exception.hpp>
 #include <protozero/varint.hpp>
 
-#include <osmium/builder/builder.hpp>
 #include <osmium/builder/osm_object_builder.hpp>
 #include <osmium/io/detail/input_format.hpp>
 #include <osmium/io/detail/queue_util.hpp>
@@ -52,19 +52,26 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/osm.hpp>
 #include <osmium/osm/box.hpp>
 #include <osmium/osm/entity_bits.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
+#include <osmium/osm/node.hpp>
 #include <osmium/osm/object.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/timestamp.hpp>
 #include <osmium/osm/types.hpp>
+#include <osmium/osm/way.hpp>
 #include <osmium/thread/util.hpp>
 #include <osmium/util/cast.hpp>
 #include <osmium/util/delta.hpp>
 
 namespace osmium {
 
+    namespace builder {
+        class Builder;
+    } // namespace builder
+
     /**
      * Exception thrown when the o5m deocder failed. The exception contains
      * (if available) information about the place where the error happened
@@ -529,7 +536,7 @@ namespace osmium {
                             uint64_t length = 0;
                             try {
                                 length = protozero::decode_varint(&m_data, m_end);
-                            } catch (protozero::end_of_buffer_exception&) {
+                            } catch (const protozero::end_of_buffer_exception&) {
                                 throw o5m_error("premature end of file");
                             }
 
diff --git a/include/osmium/io/detail/opl_input_format.hpp b/include/osmium/io/detail/opl_input_format.hpp
new file mode 100644
index 0000000..15a31f3
--- /dev/null
+++ b/include/osmium/io/detail/opl_input_format.hpp
@@ -0,0 +1,156 @@
+#ifndef OSMIUM_IO_DETAIL_OPL_INPUT_FORMAT_HPP
+#define OSMIUM_IO_DETAIL_OPL_INPUT_FORMAT_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 <cstdlib>
+#include <future>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <osmium/io/detail/input_format.hpp>
+#include <osmium/io/detail/opl_parser_functions.hpp>
+#include <osmium/io/detail/queue_util.hpp>
+#include <osmium/io/file_format.hpp>
+#include <osmium/io/header.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm/entity_bits.hpp>
+#include <osmium/thread/util.hpp>
+
+namespace osmium {
+
+    namespace io {
+
+        namespace detail {
+
+            class OPLParser : public Parser {
+
+                osmium::memory::Buffer m_buffer{1024*1024};
+                const char* m_data = nullptr;
+                uint64_t m_line_count = 0;
+
+                void maybe_flush() {
+                    if (m_buffer.committed() > 800*1024) {
+                        osmium::memory::Buffer buffer{1024*1024};
+                        using std::swap;
+                        swap(m_buffer, buffer);
+                        send_to_output_queue(std::move(buffer));
+
+                    }
+                }
+
+                void parse_line() {
+                    if (opl_parse_line(m_line_count, m_data, m_buffer, read_types())) {
+                        maybe_flush();
+                    }
+                    ++m_line_count;
+                }
+
+            public:
+
+                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) {
+                    set_header_value(osmium::io::Header{});
+                }
+
+                ~OPLParser() noexcept final = default;
+
+                void run() final {
+                    osmium::thread::set_thread_name("_osmium_opl_in");
+
+                    std::string rest;
+                    while (!input_done()) {
+                        std::string input = get_input();
+                        std::string::size_type ppos = 0;
+
+                        if (!rest.empty()) {
+                            ppos = input.find('\n');
+                            if (ppos == std::string::npos) {
+                                rest.append(input);
+                                continue;
+                            }
+                            rest.append(input.substr(0, ppos));
+                            m_data = rest.data();
+                            parse_line();
+                            rest.clear();
+                        }
+
+                        std::string::size_type pos = input.find('\n', ppos);
+                        while (pos != std::string::npos) {
+                            m_data = &input[ppos];
+                            input[pos] = '\0';
+                            parse_line();
+                            ppos = pos + 1;
+                            if (ppos >= input.size()) {
+                                break;
+                            }
+                            pos = input.find('\n', ppos);
+                        }
+                        rest = input.substr(ppos);
+                    }
+
+                    if (m_buffer.committed() > 0) {
+                        send_to_output_queue(std::move(m_buffer));
+                    }
+                }
+
+            }; // class OPLParser
+
+            // we want the register_parser() function to run, setting
+            // the variable is only a side-effect, it will never be used
+            const bool registered_opl_parser = ParserFactory::instance().register_parser(
+                file_format::opl,
+                [](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));
+            });
+
+            // dummy function to silence the unused variable warning from above
+            inline bool get_registered_opl_parser() noexcept {
+                return registered_opl_parser;
+            }
+
+        } // namespace detail
+
+    } // namespace io
+
+} // namespace osmium
+
+
+#endif // OSMIUM_IO_DETAIL_OPL_INPUT_FORMAT_HPP
diff --git a/include/osmium/io/detail/opl_output_format.hpp b/include/osmium/io/detail/opl_output_format.hpp
index 56c0182..acdfe29 100644
--- a/include/osmium/io/detail/opl_output_format.hpp
+++ b/include/osmium/io/detail/opl_output_format.hpp
@@ -33,26 +33,26 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cinttypes>
-#include <cstddef>
 #include <cstdint>
-#include <cstdio>
-#include <future>
 #include <iterator>
 #include <memory>
 #include <string>
-#include <thread>
 #include <utility>
 
 #include <osmium/io/detail/output_format.hpp>
+#include <osmium/io/detail/queue_util.hpp>
+#include <osmium/io/detail/string_util.hpp>
+#include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/memory/collection.hpp>
+#include <osmium/memory/item_iterator.hpp>
 #include <osmium/osm/box.hpp>
 #include <osmium/osm/changeset.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/relation.hpp>
 #include <osmium/osm/tag.hpp>
@@ -65,8 +65,6 @@ namespace osmium {
 
     namespace io {
 
-        class File;
-
         namespace detail {
 
             struct opl_output_options {
@@ -77,6 +75,9 @@ namespace osmium {
                 /// Should node locations be added to ways?
                 bool locations_on_ways;
 
+                /// Write in form of a diff file?
+                bool format_as_diff;
+
             };
 
             /**
@@ -152,6 +153,12 @@ namespace osmium {
                     }
                 }
 
+                void write_diff(const osmium::OSMObject& object) {
+                    if (m_options.format_as_diff) {
+                        *m_out += object.diff_as_char();
+                    }
+                }
+
             public:
 
                 OPLOutputBlock(osmium::memory::Buffer&& buffer, const opl_output_options& options) :
@@ -178,6 +185,7 @@ namespace osmium {
                 }
 
                 void node(const osmium::Node& node) {
+                    write_diff(node);
                     *m_out += 'n';
                     write_meta(node);
                     write_location(node.location(), 'x', 'y');
@@ -195,6 +203,7 @@ namespace osmium {
                 }
 
                 void way(const osmium::Way& way) {
+                    write_diff(way);
                     *m_out += 'w';
                     write_meta(way);
 
@@ -228,6 +237,7 @@ namespace osmium {
                 }
 
                 void relation(const osmium::Relation& relation) {
+                    write_diff(relation);
                     *m_out += 'r';
                     write_meta(relation);
 
@@ -278,6 +288,7 @@ namespace osmium {
                     m_options() {
                     m_options.add_metadata      = file.is_not_false("add_metadata");
                     m_options.locations_on_ways = file.is_true("locations_on_ways");
+                    m_options.format_as_diff    = file.is_true("diff");
                 }
 
                 OPLOutputFormat(const OPLOutputFormat&) = delete;
diff --git a/include/osmium/io/detail/opl_parser_functions.hpp b/include/osmium/io/detail/opl_parser_functions.hpp
new file mode 100644
index 0000000..97c5927
--- /dev/null
+++ b/include/osmium/io/detail/opl_parser_functions.hpp
@@ -0,0 +1,747 @@
+#ifndef OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP
+#define OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_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 <cstdint>
+#include <cstdlib>
+#include <iterator>
+#include <limits>
+#include <stdexcept>
+#include <string>
+
+#include <utf8.h>
+
+#include <osmium/builder/osm_object_builder.hpp>
+#include <osmium/io/error.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm/box.hpp>
+#include <osmium/osm/changeset.hpp>
+#include <osmium/osm/entity_bits.hpp>
+#include <osmium/osm/item_type.hpp>
+#include <osmium/osm/location.hpp>
+#include <osmium/osm/node.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/timestamp.hpp>
+#include <osmium/osm/types.hpp>
+#include <osmium/osm/way.hpp>
+
+namespace osmium {
+
+    namespace builder {
+        class Builder;
+    } // namespace builder
+
+    /**
+     * Exception thrown when there was a problem with parsing the OPL format
+     * of a file.
+     */
+    struct opl_error : public io_error {
+
+        uint64_t line = 0;
+        uint64_t column = 0;
+        const char* data;
+        std::string msg;
+
+        explicit opl_error(const std::string& what, const char* d = nullptr) :
+            io_error(std::string("OPL error: ") + what),
+            data(d),
+            msg("OPL error: ") {
+            msg.append(what);
+        }
+
+        explicit opl_error(const char* what, const char* d = nullptr) :
+            io_error(std::string("OPL error: ") + what),
+            data(d),
+            msg("OPL error: ") {
+            msg.append(what);
+        }
+
+        void set_pos(uint64_t l, uint64_t col) {
+            line = l;
+            column = col;
+            msg.append(" on line ");
+            msg.append(std::to_string(line));
+            msg.append(" column ");
+            msg.append(std::to_string(column));
+        }
+
+        const char* what() const noexcept override {
+            return msg.c_str();
+        }
+
+    }; // struct opl_error
+
+    namespace io {
+
+        namespace detail {
+
+            /**
+             * Consume consecutive space and tab characters. There must be
+             * at least one.
+             */
+            inline void opl_parse_space(const char** s) {
+                if (**s != ' ' && **s != '\t') {
+                    throw opl_error{"expected space or tab character", *s};
+                }
+                do {
+                    ++*s;
+                } while (**s == ' ' || **s == '\t');
+            }
+
+            /**
+             * Check whether s points to something else than the end of the
+             * string or a space or tab.
+             */
+            inline bool opl_non_empty(const char *s) {
+                return *s != '\0' && *s != ' ' && *s != '\t';
+            }
+
+            /**
+             * Skip to the next space or tab character or the end of the
+             * string.
+             */
+            inline const char* opl_skip_section(const char** s) noexcept {
+                while (opl_non_empty(*s)) {
+                    ++*s;
+                }
+                return *s;
+            }
+
+            /**
+             * Parse OPL-escaped strings with hex code with a '%' at the end.
+             * Appends resulting unicode character to the result string.
+             *
+             * Returns a pointer to next character that needs to be consumed.
+             */
+            inline void opl_parse_escaped(const char** data, std::string& result) {
+                const char* s = *data;
+                uint32_t value = 0;
+                const int max_length = sizeof(value) * 2 /* hex chars per byte */;
+                int length = 0;
+                while (++length <= max_length) {
+                    if (*s == '\0') {
+                        throw opl_error{"eol", s};
+                    }
+                    if (*s == '%') {
+                        ++s;
+                        utf8::utf32to8(&value, &value + 1, std::back_inserter(result));
+                        *data = s;
+                        return;
+                    }
+                    value <<= 4;
+                    if (*s >= '0' && *s <= '9') {
+                        value += *s - '0';
+                    } else if (*s >= 'a' && *s <= 'f') {
+                        value += *s - 'a' + 10;
+                    } else if (*s >= 'A' && *s <= 'F') {
+                        value += *s - 'A' + 10;
+                    } else {
+                        throw opl_error{"not a hex char", s};
+                    }
+                    ++s;
+                }
+                throw opl_error{"hex escape too long", s};
+            }
+
+            /**
+             * Parse a string up to end of string or next space, tab, comma, or
+             * equal sign.
+             *
+             * Appends characters to the result string.
+             *
+             * Returns a pointer to next character that needs to be consumed.
+             */
+            inline void opl_parse_string(const char** data, std::string& result) {
+                const char* s = *data;
+                while (true) {
+                    if (*s == '\0' || *s == ' ' || *s == '\t' || *s == ',' || *s == '=') {
+                        break;
+                    } else if (*s == '%') {
+                        ++s;
+                        opl_parse_escaped(&s, result);
+                    } else {
+                        result += *s;
+                        ++s;
+                    }
+                }
+                *data = s;
+            }
+
+            // Arbitrary limit how long integers can get
+            constexpr const int max_int_len = 16;
+
+            template <typename T>
+            inline T opl_parse_int(const char** s) {
+                if (**s == '\0') {
+                    throw opl_error{"expected integer", *s};
+                }
+                const bool negative = (**s == '-');
+                if (negative) {
+                    ++*s;
+                }
+
+                int64_t value = 0;
+
+                int n = max_int_len;
+                while (**s >= '0' && **s <= '9') {
+                    if (--n == 0) {
+                        throw opl_error{"integer too long", *s};
+                    }
+                    value *= 10;
+                    value += **s - '0';
+                    ++*s;
+                }
+
+                if (n == max_int_len) {
+                    throw opl_error{"expected integer", *s};
+                }
+
+                if (negative) {
+                    value = -value;
+                    if (value < std::numeric_limits<T>::min()) {
+                        throw opl_error{"integer too long", *s};
+                    }
+                } else {
+                    if (value > std::numeric_limits<T>::max()) {
+                        throw opl_error{"integer too long", *s};
+                    }
+                }
+
+                return T(value);
+            }
+
+            inline osmium::object_id_type opl_parse_id(const char** s) {
+                return opl_parse_int<osmium::object_id_type>(s);
+            }
+
+            inline osmium::changeset_id_type opl_parse_changeset_id(const char** s) {
+                return opl_parse_int<osmium::changeset_id_type>(s);
+            }
+
+            inline osmium::object_version_type opl_parse_version(const char** s) {
+                return opl_parse_int<osmium::object_version_type>(s);
+            }
+
+            inline bool opl_parse_visible(const char** data) {
+                if (**data == 'V') {
+                    ++*data;
+                    return true;
+                }
+                if (**data == 'D') {
+                    ++*data;
+                    return false;
+                }
+                throw opl_error{"invalid visible flag", *data};
+            }
+
+            inline osmium::user_id_type opl_parse_uid(const char** s) {
+                return opl_parse_int<osmium::user_id_type>(s);
+            }
+
+            inline osmium::Timestamp opl_parse_timestamp(const char** s) {
+                try {
+                    if (**s == '\0' || **s == ' ' || **s == '\t') {
+                        return osmium::Timestamp{};
+                    }
+                    osmium::Timestamp timestamp{*s};
+                    *s += 20;
+                    return timestamp;
+                } catch (const std::invalid_argument&) {
+                    throw opl_error{"can not parse timestamp", *s};
+                }
+            }
+
+            /**
+             * Check if data points to given character and consume it.
+             * Throw error otherwise.
+             */
+            inline void opl_parse_char(const char** data, char c) {
+                if (**data == c) {
+                    ++*data;
+                    return;
+                }
+                std::string msg = "expected '";
+                msg += c;
+                msg += "'";
+                throw opl_error{msg, *data};
+            }
+
+            /**
+             * Parse a list of tags in the format 'key=value,key=value,...'
+             *
+             * Tags will be added to the buffer using a TagListBuilder.
+             */
+            inline void opl_parse_tags(const char* s, osmium::memory::Buffer& buffer, osmium::builder::Builder* parent_builder = nullptr) {
+                osmium::builder::TagListBuilder builder{buffer, parent_builder};
+                std::string key;
+                std::string value;
+                while (true) {
+                    opl_parse_string(&s, key);
+                    opl_parse_char(&s, '=');
+                    opl_parse_string(&s, value);
+                    builder.add_tag(key, value);
+                    if (*s == ' ' || *s == '\t' || *s == '\0') {
+                        break;
+                    }
+                    opl_parse_char(&s, ',');
+                    key.clear();
+                    value.clear();
+                }
+            }
+
+            /**
+             * Parse a number of nodes in the format "nID,nID,nID..."
+             *
+             * Nodes will be added to the buffer using a WayNodeListBuilder.
+             */
+            inline void opl_parse_way_nodes(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::WayBuilder* parent_builder = nullptr) {
+                if (s == e) {
+                    return;
+                }
+                osmium::builder::WayNodeListBuilder builder{buffer, parent_builder};
+
+                while (s < e) {
+                    opl_parse_char(&s, 'n');
+                    if (s == e) {
+                        throw opl_error{"expected integer", s};
+                    }
+
+                    const osmium::object_id_type ref = opl_parse_id(&s);
+                    if (s == e) {
+                        builder.add_node_ref(ref);
+                        return;
+                    }
+
+                    osmium::Location location;
+                    if (*s == 'x') {
+                        ++s;
+                        location.set_lon_partial(&s);
+                        if (*s == 'y') {
+                            ++s;
+                            location.set_lat_partial(&s);
+                        }
+                    }
+
+                    builder.add_node_ref(ref, location);
+
+                    if (s == e) {
+                        return;
+                    }
+
+                    opl_parse_char(&s, ',');
+                }
+            }
+
+            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));
+
+                const char* tags_begin = nullptr;
+
+                std::string user;
+                osmium::Location location;
+                while (**data) {
+                    opl_parse_space(data);
+                    const char c = **data;
+                    if (c == '\0') {
+                        break;
+                    }
+                    ++(*data);
+                    switch (c) {
+                        case 'v':
+                            node.set_version(opl_parse_version(data));
+                            break;
+                        case 'd':
+                            node.set_visible(opl_parse_visible(data));
+                            break;
+                        case 'c':
+                            node.set_changeset(opl_parse_changeset_id(data));
+                            break;
+                        case 't':
+                            node.set_timestamp(opl_parse_timestamp(data));
+                            break;
+                        case 'i':
+                            node.set_uid(opl_parse_uid(data));
+                            break;
+                        case 'u':
+                            opl_parse_string(data, user);
+                            break;
+                        case 'T':
+                            if (opl_non_empty(*data)) {
+                                tags_begin = *data;
+                                opl_skip_section(data);
+                            }
+                            break;
+                        case 'x':
+                            if (opl_non_empty(*data)) {
+                                location.set_lon_partial(data);
+                            }
+                            break;
+                        case 'y':
+                            if (opl_non_empty(*data)) {
+                                location.set_lat_partial(data);
+                            }
+                            break;
+                        default:
+                            --(*data);
+                            throw opl_error{"unknown attribute", *data};
+                    }
+                }
+
+                if (location.valid()) {
+                    node.set_location(location);
+                }
+
+                builder.add_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));
+
+                const char* tags_begin = nullptr;
+
+                const char* nodes_begin = nullptr;
+                const char* nodes_end = nullptr;
+
+                std::string user;
+                while (**data) {
+                    opl_parse_space(data);
+                    const char c = **data;
+                    if (c == '\0') {
+                        break;
+                    }
+                    ++(*data);
+                    switch (c) {
+                        case 'v':
+                            way.set_version(opl_parse_version(data));
+                            break;
+                        case 'd':
+                            way.set_visible(opl_parse_visible(data));
+                            break;
+                        case 'c':
+                            way.set_changeset(opl_parse_changeset_id(data));
+                            break;
+                        case 't':
+                            way.set_timestamp(opl_parse_timestamp(data));
+                            break;
+                        case 'i':
+                            way.set_uid(opl_parse_uid(data));
+                            break;
+                        case 'u':
+                            opl_parse_string(data, user);
+                            break;
+                        case 'T':
+                            if (opl_non_empty(*data)) {
+                                tags_begin = *data;
+                                opl_skip_section(data);
+                            }
+                            break;
+                        case 'N':
+                            nodes_begin = *data;
+                            nodes_end = opl_skip_section(data);
+                            break;
+                        default:
+                            --(*data);
+                            throw opl_error{"unknown attribute", *data};
+                    }
+                }
+
+                builder.add_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) {
+                if (s == e) {
+                    return;
+                }
+                osmium::builder::RelationMemberListBuilder builder{buffer, parent_builder};
+
+                while (s < e) {
+                    osmium::item_type type = osmium::char_to_item_type(*s);
+                    if (type != osmium::item_type::node &&
+                        type != osmium::item_type::way &&
+                        type != osmium::item_type::relation) {
+                        throw opl_error{"unknown object type", s};
+                    }
+                    ++s;
+
+                    if (s == e) {
+                        throw opl_error{"expected integer", s};
+                    }
+                    osmium::object_id_type ref = opl_parse_id(&s);
+                    opl_parse_char(&s, '@');
+                    if (s == e) {
+                        builder.add_member(type, ref, "");
+                        return;
+                    }
+                    std::string role;
+                    opl_parse_string(&s, role);
+                    builder.add_member(type, ref, role);
+
+                    if (s == e) {
+                        return;
+                    }
+                    opl_parse_char(&s, ',');
+                }
+            }
+
+            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));
+
+                const char* tags_begin = nullptr;
+
+                const char* members_begin = nullptr;
+                const char* members_end = nullptr;
+
+                std::string user;
+                while (**data) {
+                    opl_parse_space(data);
+                    const char c = **data;
+                    if (c == '\0') {
+                        break;
+                    }
+                    ++(*data);
+                    switch (c) {
+                        case 'v':
+                            relation.set_version(opl_parse_version(data));
+                            break;
+                        case 'd':
+                            relation.set_visible(opl_parse_visible(data));
+                            break;
+                        case 'c':
+                            relation.set_changeset(opl_parse_changeset_id(data));
+                            break;
+                        case 't':
+                            relation.set_timestamp(opl_parse_timestamp(data));
+                            break;
+                        case 'i':
+                            relation.set_uid(opl_parse_uid(data));
+                            break;
+                        case 'u':
+                            opl_parse_string(data, user);
+                            break;
+                        case 'T':
+                            if (opl_non_empty(*data)) {
+                                tags_begin = *data;
+                                opl_skip_section(data);
+                            }
+                            break;
+                        case 'M':
+                            members_begin = *data;
+                            members_end = opl_skip_section(data);
+                            break;
+                        default:
+                            --(*data);
+                            throw opl_error{"unknown attribute", *data};
+                    }
+                }
+
+                builder.add_user(user);
+
+                if (tags_begin) {
+                    opl_parse_tags(tags_begin, buffer, &builder);
+                }
+
+                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));
+
+                const char* tags_begin = nullptr;
+
+                osmium::Location location1;
+                osmium::Location location2;
+                std::string user;
+                while (**data) {
+                    opl_parse_space(data);
+                    const char c = **data;
+                    if (c == '\0') {
+                        break;
+                    }
+                    ++(*data);
+                    switch (c) {
+                        case 'k':
+                            changeset.set_num_changes(opl_parse_int<osmium::num_changes_type>(data));
+                            break;
+                        case 's':
+                            changeset.set_created_at(opl_parse_timestamp(data));
+                            break;
+                        case 'e':
+                            changeset.set_closed_at(opl_parse_timestamp(data));
+                            break;
+                        case 'd':
+                            changeset.set_num_comments(opl_parse_int<osmium::num_comments_type>(data));
+                            break;
+                        case 'i':
+                            changeset.set_uid(opl_parse_uid(data));
+                            break;
+                        case 'u':
+                            opl_parse_string(data, user);
+                            break;
+                        case 'x':
+                            if (opl_non_empty(*data)) {
+                                location1.set_lon_partial(data);
+                            }
+                            break;
+                        case 'y':
+                            if (opl_non_empty(*data)) {
+                                location1.set_lat_partial(data);
+                            }
+                            break;
+                        case 'X':
+                            if (opl_non_empty(*data)) {
+                                location2.set_lon_partial(data);
+                            }
+                            break;
+                        case 'Y':
+                            if (opl_non_empty(*data)) {
+                                location2.set_lat_partial(data);
+                            }
+                            break;
+                        case 'T':
+                            if (opl_non_empty(*data)) {
+                                tags_begin = *data;
+                                opl_skip_section(data);
+                            }
+                            break;
+                        default:
+                            --(*data);
+                            throw opl_error{"unknown attribute", *data};
+                    }
+
+                }
+
+                if (location1.valid() && location2.valid()) {
+                    changeset.bounds().extend(location1);
+                    changeset.bounds().extend(location2);
+                }
+
+                builder.add_user(user);
+
+                if (tags_begin) {
+                    opl_parse_tags(tags_begin, buffer, &builder);
+                }
+
+                buffer.commit();
+            }
+
+            inline bool opl_parse_line(uint64_t line_count,
+                                       const char* data,
+                                       osmium::memory::Buffer& buffer,
+                                       osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all) {
+                const char* start_of_line = data;
+                try {
+                    switch (*data) {
+                        case '\0':
+                            // ignore empty lines
+                            break;
+                        case '#':
+                            // ignore lines starting with #
+                            break;
+                        case 'n':
+                            if (read_types & osmium::osm_entity_bits::node) {
+                                ++data;
+                                opl_parse_node(&data, buffer);
+                                return true;
+                            }
+                            break;
+                        case 'w':
+                            if (read_types & osmium::osm_entity_bits::way) {
+                                ++data;
+                                opl_parse_way(&data, buffer);
+                                return true;
+                            }
+                            break;
+                        case 'r':
+                            if (read_types & osmium::osm_entity_bits::relation) {
+                                ++data;
+                                opl_parse_relation(&data, buffer);
+                                return true;
+                            }
+                            break;
+                        case 'c':
+                            if (read_types & osmium::osm_entity_bits::changeset) {
+                                ++data;
+                                opl_parse_changeset(&data, buffer);
+                                return true;
+                            }
+                            break;
+                        default:
+                            throw opl_error{"unknown type", data};
+                    }
+                } catch (opl_error& e) {
+                    e.set_pos(line_count, e.data ? e.data - start_of_line : 0);
+                    throw;
+                }
+
+                return false;
+            }
+
+        } // namespace detail
+
+    } // namespace io
+
+} // namespace osmium
+
+
+#endif // OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP
diff --git a/include/osmium/io/detail/output_format.hpp b/include/osmium/io/detail/output_format.hpp
index eaff001..0fe4915 100644
--- a/include/osmium/io/detail/output_format.hpp
+++ b/include/osmium/io/detail/output_format.hpp
@@ -33,16 +33,16 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstdint>
 #include <functional>
 #include <map>
 #include <memory>
-#include <stdexcept>
 #include <string>
 #include <utility>
 
 #include <osmium/handler.hpp>
 #include <osmium/io/detail/queue_util.hpp>
-#include <osmium/io/detail/string_util.hpp>
+#include <osmium/io/error.hpp>
 #include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/memory/buffer.hpp>
diff --git a/include/osmium/io/detail/pbf_decoder.hpp b/include/osmium/io/detail/pbf_decoder.hpp
index 6980c48..5df191d 100644
--- a/include/osmium/io/detail/pbf_decoder.hpp
+++ b/include/osmium/io/detail/pbf_decoder.hpp
@@ -33,10 +33,8 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstddef>
 #include <cstdint>
 #include <cstring>
-#include <algorithm>
 #include <limits>
 #include <memory>
 #include <stdexcept>
@@ -44,23 +42,35 @@ DEALINGS IN THE SOFTWARE.
 #include <utility>
 #include <vector>
 
+#include <protozero/iterators.hpp>
 #include <protozero/pbf_message.hpp>
+#include <protozero/types.hpp>
 
 #include <osmium/builder/osm_object_builder.hpp>
 #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/header.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm/box.hpp>
+#include <osmium/osm/entity_bits.hpp>
+#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node.hpp>
+#include <osmium/osm/object.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/timestamp.hpp>
 #include <osmium/osm/types.hpp>
-#include <osmium/memory/buffer.hpp>
-#include <osmium/osm/entity_bits.hpp>
+#include <osmium/osm/way.hpp>
 #include <osmium/util/cast.hpp>
 #include <osmium/util/delta.hpp>
 
 namespace osmium {
 
+    namespace builder {
+        class Builder;
+    } // namespace builder
+
     namespace io {
 
         namespace detail {
@@ -70,7 +80,7 @@ namespace osmium {
 
             class PBFPrimitiveBlockDecoder {
 
-                static constexpr size_t initial_buffer_size = 2 * 1024 * 1024;
+                static constexpr const size_t initial_buffer_size = 2 * 1024 * 1024;
 
                 data_view m_data;
                 std::vector<osm_string_len_type> m_stringtable;
@@ -91,7 +101,7 @@ namespace osmium {
 
                     protozero::pbf_message<OSMFormat::StringTable> pbf_string_table(data);
                     while (pbf_string_table.next(OSMFormat::StringTable::repeated_bytes_s)) {
-                        auto str_view = pbf_string_table.get_view();
+                        const auto str_view = pbf_string_table.get_view();
                         if (str_view.size() > osmium::max_osm_string_length) {
                             throw osmium::pbf_error("overlong string in string table");
                         }
@@ -173,7 +183,7 @@ namespace osmium {
                         switch (pbf_info.tag()) {
                             case OSMFormat::Info::optional_int32_version:
                                 {
-                                    auto version = pbf_info.get_int32();
+                                    const auto version = pbf_info.get_int32();
                                     if (version < 0) {
                                         throw osmium::pbf_error("object version must not be negative");
                                     }
@@ -185,7 +195,7 @@ namespace osmium {
                                 break;
                             case OSMFormat::Info::optional_int64_changeset:
                                 {
-                                    auto changeset_id = pbf_info.get_int64();
+                                    const auto changeset_id = pbf_info.get_int64();
                                     if (changeset_id < 0) {
                                         throw osmium::pbf_error("object changeset_id must not be negative");
                                     }
@@ -404,7 +414,7 @@ namespace osmium {
                         osmium::util::DeltaDecode<int64_t> ref;
                         while (!roles.empty() && !refs.empty() && !types.empty()) {
                             const auto& r = m_stringtable.at(roles.front());
-                            int type = types.front();
+                            const int type = types.front();
                             if (type < 0 || type > 2) {
                                 throw osmium::pbf_error("unknown relation member type");
                             }
@@ -528,14 +538,14 @@ namespace osmium {
                                 throw osmium::pbf_error("PBF format error");
                             }
 
-                            auto version = versions.front();
+                            const auto version = versions.front();
                             versions.drop_front();
                             if (version < 0) {
                                 throw osmium::pbf_error("object version must not be negative");
                             }
                             node.set_version(static_cast<osmium::object_version_type>(version));
 
-                            auto changeset_id = dense_changeset.update(changesets.front());
+                            const auto changeset_id = dense_changeset.update(changesets.front());
                             changesets.drop_front();
                             if (changeset_id < 0) {
                                 throw osmium::pbf_error("object changeset_id must not be negative");
@@ -617,7 +627,7 @@ namespace osmium {
                     try {
                         decode_primitive_block_metadata();
                         decode_primitive_block_data();
-                    } catch (std::out_of_range&) {
+                    } catch (const std::out_of_range&) {
                         throw osmium::pbf_error("string id out of range");
                     }
 
@@ -743,7 +753,7 @@ namespace osmium {
                             break;
                         case OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp:
                             {
-                                auto timestamp = osmium::Timestamp(pbf_header_block.get_int64()).to_iso();
+                                const auto timestamp = osmium::Timestamp(pbf_header_block.get_int64()).to_iso();
                                 header.set("osmosis_replication_timestamp", timestamp);
                                 header.set("timestamp", timestamp);
                             }
diff --git a/include/osmium/io/detail/pbf_input_format.hpp b/include/osmium/io/detail/pbf_input_format.hpp
index 8361c72..1253447 100644
--- a/include/osmium/io/detail/pbf_input_format.hpp
+++ b/include/osmium/io/detail/pbf_input_format.hpp
@@ -38,25 +38,22 @@ DEALINGS IN THE SOFTWARE.
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
+#include <future>
 #include <memory>
-#include <sstream>
 #include <string>
-#include <thread>
 #include <type_traits>
 
 #include <protozero/pbf_message.hpp>
+#include <protozero/types.hpp>
 
 #include <osmium/io/detail/input_format.hpp>
 #include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
 #include <osmium/io/detail/pbf_decoder.hpp>
 #include <osmium/io/detail/protobuf_tags.hpp>
-#include <osmium/io/error.hpp>
-#include <osmium/io/file.hpp>
+#include <osmium/io/detail/queue_util.hpp>
 #include <osmium/io/file_format.hpp>
-#include <osmium/osm.hpp>
+#include <osmium/io/header.hpp>
 #include <osmium/osm/entity_bits.hpp>
-#include <osmium/osm/object.hpp>
-#include <osmium/osm/timestamp.hpp>
 #include <osmium/thread/pool.hpp>
 #include <osmium/thread/util.hpp>
 #include <osmium/util/config.hpp>
@@ -80,7 +77,7 @@ namespace osmium {
                  */
                 std::string read_from_input_queue(size_t size) {
                     while (m_input_buffer.size() < size) {
-                        std::string new_data = get_input();
+                        const std::string new_data = get_input();
                         if (input_done()) {
                             throw osmium::pbf_error("truncated data (EOF encountered)");
                         }
@@ -106,7 +103,7 @@ namespace osmium {
                     try {
                         const std::string input_data = read_from_input_queue(sizeof(size_in_network_byte_order));
                         size_in_network_byte_order = *reinterpret_cast<const uint32_t*>(input_data.data());
-                    } catch (osmium::pbf_error&) {
+                    } catch (const osmium::pbf_error&) {
                         return 0; // EOF
                     }
 
diff --git a/include/osmium/io/detail/pbf_output_format.hpp b/include/osmium/io/detail/pbf_output_format.hpp
index d5f8cd4..43aa8cc 100644
--- a/include/osmium/io/detail/pbf_output_format.hpp
+++ b/include/osmium/io/detail/pbf_output_format.hpp
@@ -41,30 +41,34 @@ DEALINGS IN THE SOFTWARE.
 #include <iterator>
 #include <memory>
 #include <string>
-#include <time.h>
 #include <utility>
 
 #include <protozero/pbf_builder.hpp>
+#include <protozero/pbf_writer.hpp>
+#include <protozero/types.hpp>
 
 #include <osmium/handler.hpp>
 #include <osmium/io/detail/output_format.hpp>
 #include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
 #include <osmium/io/detail/protobuf_tags.hpp>
+#include <osmium/io/detail/queue_util.hpp>
 #include <osmium/io/detail/string_table.hpp>
 #include <osmium/io/detail/zlib.hpp>
 #include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/memory/collection.hpp>
+#include <osmium/memory/item_iterator.hpp>
 #include <osmium/osm/box.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/relation.hpp>
 #include <osmium/osm/tag.hpp>
 #include <osmium/osm/timestamp.hpp>
+#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
 #include <osmium/thread/pool.hpp>
 #include <osmium/util/cast.hpp>
@@ -524,18 +528,18 @@ namespace osmium {
 
                     pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_writingprogram, header.get("generator"));
 
-                    std::string osmosis_replication_timestamp = header.get("osmosis_replication_timestamp");
+                    const std::string osmosis_replication_timestamp = header.get("osmosis_replication_timestamp");
                     if (!osmosis_replication_timestamp.empty()) {
                         osmium::Timestamp ts(osmosis_replication_timestamp.c_str());
                         pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp, uint32_t(ts));
                     }
 
-                    std::string osmosis_replication_sequence_number = header.get("osmosis_replication_sequence_number");
+                    const std::string osmosis_replication_sequence_number = header.get("osmosis_replication_sequence_number");
                     if (!osmosis_replication_sequence_number.empty()) {
                         pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_sequence_number, std::atoll(osmosis_replication_sequence_number.c_str()));
                     }
 
-                    std::string osmosis_replication_base_url = header.get("osmosis_replication_base_url");
+                    const std::string osmosis_replication_base_url = header.get("osmosis_replication_base_url");
                     if (!osmosis_replication_base_url.empty()) {
                         pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_osmosis_replication_base_url, osmosis_replication_base_url);
                     }
diff --git a/include/osmium/io/detail/read_write.hpp b/include/osmium/io/detail/read_write.hpp
index 769e2b3..a086e5b 100644
--- a/include/osmium/io/detail/read_write.hpp
+++ b/include/osmium/io/detail/read_write.hpp
@@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cerrno>
 #include <cstddef>
-#include <errno.h>
 #include <fcntl.h>
 #include <string>
 #include <system_error>
@@ -84,7 +83,7 @@ namespace osmium {
 #ifdef _WIN32
                 flags |= O_BINARY;
 #endif
-                int fd = ::open(filename.c_str(), flags, 0666);
+                const int fd = ::open(filename.c_str(), flags, 0666);
                 if (fd < 0) {
                     throw std::system_error(errno, std::system_category(), std::string("Open failed for '") + filename + "'");
                 }
@@ -108,7 +107,7 @@ namespace osmium {
 #ifdef _WIN32
                 flags |= O_BINARY;
 #endif
-                int fd = ::open(filename.c_str(), flags);
+                const int fd = ::open(filename.c_str(), flags);
                 if (fd < 0) {
                     throw std::system_error(errno, std::system_category(), std::string("Open failed for '") + filename + "'");
                 }
@@ -133,7 +132,7 @@ namespace osmium {
                     if (write_count > max_write) {
                         write_count = max_write;
                     }
-                    auto length = ::write(fd, output_buffer + offset, static_cast<unsigned int>(write_count));
+                    const auto length = ::write(fd, output_buffer + offset, static_cast<unsigned int>(write_count));
                     if (length < 0) {
                         throw std::system_error(errno, std::system_category(), "Write failed");
                     }
diff --git a/include/osmium/io/detail/string_table.hpp b/include/osmium/io/detail/string_table.hpp
index 995e2cf..f1ddc87 100644
--- a/include/osmium/io/detail/string_table.hpp
+++ b/include/osmium/io/detail/string_table.hpp
@@ -34,13 +34,14 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
+#include <cstddef>
 #include <cstdint>
-#include <cstdlib>
 #include <cstring>
 #include <iterator>
 #include <list>
 #include <string>
 #include <unordered_map>
+#include <utility>
 
 #include <osmium/io/detail/pbf.hpp>
 
@@ -94,7 +95,7 @@ namespace osmium {
                  * allocated.
                  */
                 const char* add(const char* string) {
-                    size_t len = std::strlen(string) + 1;
+                    const size_t len = std::strlen(string) + 1;
 
                     assert(len <= m_chunk_size);
 
@@ -134,7 +135,7 @@ namespace osmium {
 
                     const_iterator& operator++() {
                         assert(m_it != m_last);
-                        auto last_pos = m_it->c_str() + m_it->size();
+                        const auto last_pos = m_it->c_str() + m_it->size();
                         while (m_pos != last_pos && *m_pos) ++m_pos;
                         if (m_pos != last_pos) ++m_pos;
                         if (m_pos == last_pos) {
@@ -263,7 +264,7 @@ namespace osmium {
                 }
 
                 uint32_t add(const char* s) {
-                    auto f = m_index.find(s);
+                    const auto f = m_index.find(s);
                     if (f != m_index.end()) {
                         return uint32_t(f->second);
                     }
diff --git a/include/osmium/io/detail/string_util.hpp b/include/osmium/io/detail/string_util.hpp
index 52408ff..0334b0e 100644
--- a/include/osmium/io/detail/string_util.hpp
+++ b/include/osmium/io/detail/string_util.hpp
@@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cassert>
 #include <cstdint>
+#include <cstdio>
 #include <cstring>
 #include <string>
 #include <utility>
@@ -100,24 +101,24 @@ namespace osmium {
                 static const size_t max_size = 0;
 #endif
 
-                size_t old_size = out.size();
+                const size_t old_size = out.size();
 
-                int len = string_snprintf(out,
-                                          old_size,
-                                          max_size,
-                                          format,
-                                          std::forward<TArgs>(args)...);
+                const int len = string_snprintf(out,
+                                                old_size,
+                                                max_size,
+                                                format,
+                                                std::forward<TArgs>(args)...);
                 assert(len > 0);
 
                 if (size_t(len) >= max_size) {
 #ifndef NDEBUG
-                    int len2 =
+                    const int len2 =
 #endif
-                               string_snprintf(out,
-                                               old_size,
-                                               size_t(len) + 1,
-                                               format,
-                                               std::forward<TArgs>(args)...);
+                                     string_snprintf(out,
+                                                     old_size,
+                                                     size_t(len) + 1,
+                                                     format,
+                                                     std::forward<TArgs>(args)...);
                     assert(len2 == len);
                 }
 
@@ -150,7 +151,7 @@ namespace osmium {
 
                 while (data != end) {
                     const char* last = data;
-                    uint32_t c = utf8::next(data, end);
+                    const uint32_t c = utf8::next(data, end);
 
                     // This is a list of Unicode code points that we let
                     // through instead of escaping them. It is incomplete
diff --git a/include/osmium/io/detail/write_thread.hpp b/include/osmium/io/detail/write_thread.hpp
index 7960486..85ef811 100644
--- a/include/osmium/io/detail/write_thread.hpp
+++ b/include/osmium/io/detail/write_thread.hpp
@@ -36,6 +36,7 @@ DEALINGS IN THE SOFTWARE.
 #include <algorithm>
 #include <exception>
 #include <future>
+#include <memory>
 #include <string>
 
 #include <osmium/io/compression.hpp>
diff --git a/include/osmium/io/detail/xml_input_format.hpp b/include/osmium/io/detail/xml_input_format.hpp
index be1e2e7..d8c57d8 100644
--- a/include/osmium/io/detail/xml_input_format.hpp
+++ b/include/osmium/io/detail/xml_input_format.hpp
@@ -34,10 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
-#include <cstddef>
-#include <cstdlib>
 #include <cstring>
-#include <exception>
 #include <future>
 #include <memory>
 #include <string>
@@ -53,15 +50,19 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/osm.hpp>
 #include <osmium/osm/box.hpp>
+#include <osmium/osm/changeset.hpp>
 #include <osmium/osm/entity_bits.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/relation.hpp>
+#include <osmium/osm/timestamp.hpp>
 #include <osmium/osm/types.hpp>
 #include <osmium/osm/types_from_string.hpp>
-#include <osmium/thread/queue.hpp>
+#include <osmium/osm/way.hpp>
 #include <osmium/thread/util.hpp>
 #include <osmium/util/cast.hpp>
 
diff --git a/include/osmium/io/detail/xml_output_format.hpp b/include/osmium/io/detail/xml_output_format.hpp
index cc0e062..3f47b0f 100644
--- a/include/osmium/io/detail/xml_output_format.hpp
+++ b/include/osmium/io/detail/xml_output_format.hpp
@@ -34,27 +34,26 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <algorithm>
-#include <cinttypes>
-#include <cstddef>
-#include <cstdio>
-#include <future>
 #include <iterator>
 #include <memory>
 #include <string>
-#include <thread>
 #include <utility>
 
+#include <osmium/handler.hpp>
 #include <osmium/io/detail/output_format.hpp>
+#include <osmium/io/detail/queue_util.hpp>
+#include <osmium/io/detail/string_util.hpp>
 #include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/memory/collection.hpp>
+#include <osmium/memory/item_iterator.hpp>
 #include <osmium/osm/box.hpp>
 #include <osmium/osm/changeset.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/relation.hpp>
 #include <osmium/osm/tag.hpp>
diff --git a/include/osmium/io/detail/zlib.hpp b/include/osmium/io/detail/zlib.hpp
index 57b456b..15ece7c 100644
--- a/include/osmium/io/detail/zlib.hpp
+++ b/include/osmium/io/detail/zlib.hpp
@@ -33,8 +33,6 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <memory>
-#include <stdexcept>
 #include <string>
 
 #include <zlib.h>
@@ -64,7 +62,7 @@ namespace osmium {
 
                 std::string output(output_size, '\0');
 
-                auto result = ::compress(
+                const auto result = ::compress(
                     reinterpret_cast<unsigned char*>(const_cast<char *>(output.data())),
                     &output_size,
                     reinterpret_cast<const unsigned char*>(input.data()),
@@ -94,7 +92,7 @@ namespace osmium {
             inline protozero::data_view zlib_uncompress_string(const char* input, unsigned long input_size, unsigned long raw_size, std::string& output) {
                 output.resize(raw_size);
 
-                auto result = ::uncompress(
+                const auto result = ::uncompress(
                     reinterpret_cast<unsigned char*>(&*output.begin()),
                     &raw_size,
                     reinterpret_cast<const unsigned char*>(input),
diff --git a/include/osmium/io/file.hpp b/include/osmium/io/file.hpp
index 56fc8d5..812c494 100644
--- a/include/osmium/io/file.hpp
+++ b/include/osmium/io/file.hpp
@@ -34,7 +34,6 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cstddef>
-#include <stdexcept>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -43,7 +42,6 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/file_compression.hpp>
 #include <osmium/util/options.hpp>
-#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
@@ -115,7 +113,7 @@ namespace osmium {
                 }
 
                 // if filename is a URL, default to XML format
-                std::string protocol = m_filename.substr(0, m_filename.find_first_of(':'));
+                const std::string protocol = m_filename.substr(0, m_filename.find_first_of(':'));
                 if (protocol == "http" || protocol == "https") {
                     m_file_format = file_format::xml;
                 }
@@ -174,7 +172,7 @@ namespace osmium {
                 }
 
                 for (auto& option : options) {
-                    size_t pos = option.find_first_of('=');
+                    const size_t pos = option.find_first_of('=');
                     if (pos == std::string::npos) {
                         set(option, true);
                     } else {
diff --git a/include/osmium/io/gzip_compression.hpp b/include/osmium/io/gzip_compression.hpp
index e6ca010..5e3e233 100644
--- a/include/osmium/io/gzip_compression.hpp
+++ b/include/osmium/io/gzip_compression.hpp
@@ -42,15 +42,20 @@ DEALINGS IN THE SOFTWARE.
  * @attention If you include this file, you'll need to link with `libz`.
  */
 
-#include <stdexcept>
+#include <cstddef>
 #include <string>
 
+#ifndef _MSC_VER
+# include <unistd.h>
+#endif
+
 #include <errno.h>
 #include <zlib.h>
 
 #include <osmium/io/compression.hpp>
 #include <osmium/io/error.hpp>
 #include <osmium/io/file_compression.hpp>
+#include <osmium/io/detail/read_write.hpp>
 #include <osmium/io/writer_options.hpp>
 #include <osmium/util/cast.hpp>
 #include <osmium/util/compatibility.hpp>
@@ -102,7 +107,7 @@ namespace osmium {
 
             explicit GzipCompressor(int fd, fsync sync) :
                 Compressor(sync),
-                m_fd(dup(fd)),
+                m_fd(::dup(fd)),
                 m_gzfile(::gzdopen(fd, "w")) {
                 if (!m_gzfile) {
                     detail::throw_gzip_error(m_gzfile, "write initialization failed");
@@ -171,6 +176,9 @@ namespace osmium {
                     detail::throw_gzip_error(m_gzfile, "read failed");
                 }
                 buffer.resize(static_cast<std::string::size_type>(nread));
+#if ZLIB_VERNUM >= 0x1240
+                set_offset(size_t(::gzoffset(m_gzfile)));
+#endif
                 return buffer;
             }
 
diff --git a/include/osmium/io/input_iterator.hpp b/include/osmium/io/input_iterator.hpp
index a66f355..4cde92f 100644
--- a/include/osmium/io/input_iterator.hpp
+++ b/include/osmium/io/input_iterator.hpp
@@ -41,7 +41,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <osmium/memory/buffer.hpp>
 #include <osmium/memory/item.hpp>
-#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/version.hpp b/include/osmium/io/opl_input.hpp
similarity index 83%
copy from include/osmium/version.hpp
copy to include/osmium/io/opl_input.hpp
index ac7a94f..ee9e447 100644
--- a/include/osmium/version.hpp
+++ b/include/osmium/io/opl_input.hpp
@@ -1,5 +1,5 @@
-#ifndef OSMIUM_VERSION_HPP
-#define OSMIUM_VERSION_HPP
+#ifndef OSMIUM_IO_OPL_INPUT_HPP
+#define OSMIUM_IO_OPL_INPUT_HPP
 
 /*
 
@@ -33,10 +33,14 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#define LIBOSMIUM_VERSION_MAJOR 2
-#define LIBOSMIUM_VERSION_MINOR 8
-#define LIBOSMIUM_VERSION_PATCH 0
+/**
+ * @file
+ *
+ * Include this file if you want to read OSM OPL files.
+ *
+ */
 
-#define LIBOSMIUM_VERSION_STRING "2.8.0"
+#include <osmium/io/reader.hpp> // IWYU pragma: export
+#include <osmium/io/detail/opl_input_format.hpp> // IWYU pragma: export
 
-#endif // OSMIUM_VERSION_HPP
+#endif // OSMIUM_IO_OPL_INPUT_HPP
diff --git a/include/osmium/io/output_iterator.hpp b/include/osmium/io/output_iterator.hpp
index f7025fc..cf9291d 100644
--- a/include/osmium/io/output_iterator.hpp
+++ b/include/osmium/io/output_iterator.hpp
@@ -35,10 +35,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cstddef>
 #include <iterator>
-#include <memory>
-#include <utility>
 
-#include <osmium/memory/buffer.hpp>
 #include <osmium/osm/diff_object.hpp>
 #include <osmium/util/compatibility.hpp>
 
diff --git a/include/osmium/io/overwrite.hpp b/include/osmium/io/overwrite.hpp
index 5361698..bc6a503 100644
--- a/include/osmium/io/overwrite.hpp
+++ b/include/osmium/io/overwrite.hpp
@@ -34,6 +34,6 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #pragma message("Including overwrite.hpp is deprecated, #include <osmium/io/writer_options.hpp> instead.")
-#include <osmium/io/writer_options.hpp>
+#include <osmium/io/writer_options.hpp> // IWYU pragma: keep
 
 #endif // OSMIUM_IO_OVERWRITE_HPP
diff --git a/include/osmium/io/reader.hpp b/include/osmium/io/reader.hpp
index b267db6..12f97b8 100644
--- a/include/osmium/io/reader.hpp
+++ b/include/osmium/io/reader.hpp
@@ -71,12 +71,12 @@ namespace osmium {
         namespace detail {
 
             inline size_t get_input_queue_size() noexcept {
-                size_t n = osmium::config::get_max_queue_size("INPUT", 20);
+                const size_t n = osmium::config::get_max_queue_size("INPUT", 20);
                 return n > 2 ? n : 2;
             }
 
             inline size_t get_osmdata_queue_size() noexcept {
-                size_t n = osmium::config::get_max_queue_size("OSMDATA", 20);
+                const size_t n = osmium::config::get_max_queue_size("OSMDATA", 20);
                 return n > 2 ? n : 2;
             }
 
@@ -116,6 +116,8 @@ namespace osmium {
 
             osmium::thread::thread_handler m_thread;
 
+            size_t m_file_size;
+
             // This function will run in a separate thread.
             static void parser_thread(const osmium::io::File& file,
                                       detail::future_string_queue_type& input_queue,
@@ -123,8 +125,8 @@ namespace osmium {
                                       std::promise<osmium::io::Header>&& header_promise,
                                       osmium::osm_entity_bits::type read_which_entities) {
                 std::promise<osmium::io::Header> promise = std::move(header_promise);
-                auto creator = detail::ParserFactory::instance().get_creator_function(file);
-                auto parser = creator(input_queue, osmdata_queue, promise, read_which_entities);
+                const auto creator = detail::ParserFactory::instance().get_creator_function(file);
+                const auto parser = creator(input_queue, osmdata_queue, promise, read_which_entities);
                 parser->parse();
             }
 
@@ -145,7 +147,7 @@ namespace osmium {
                 if (pipe(pipefd) < 0) {
                     throw std::system_error(errno, std::system_category(), "opening pipe failed");
                 }
-                pid_t pid = fork();
+                const pid_t pid = fork();
                 if (pid < 0) {
                     throw std::system_error(errno, std::system_category(), "fork failed");
                 }
@@ -223,7 +225,8 @@ namespace osmium {
                 m_osmdata_queue_wrapper(m_osmdata_queue),
                 m_header_future(),
                 m_header(),
-                m_thread() {
+                m_thread(),
+                m_file_size(m_decompressor->file_size()) {
                 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};
@@ -275,7 +278,7 @@ namespace osmium {
 #ifndef _WIN32
                 if (m_childpid) {
                     int status;
-                    pid_t pid = ::waitpid(m_childpid, &status, 0);
+                    const pid_t pid = ::waitpid(m_childpid, &status, 0);
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wold-style-cast"
                     if (pid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
@@ -362,6 +365,32 @@ namespace osmium {
                 return m_status == status::eof || m_status == status::closed;
             }
 
+            /**
+             * Get the size of the input file. Returns 0 if the file size
+             * is not available (for instance when reading from stdin).
+             */
+            size_t file_size() const noexcept {
+                return m_file_size;
+            }
+
+            /**
+             * Returns the current offset into the input file. Returns 0 if
+             * the offset is not available (for instance when reading from
+             * stdin).
+             *
+             * The offset can be used together with the result of file_size()
+             * to estimate how much of the file has been read. Note that due
+             * to buffering inside Osmium, this value will be larger than
+             * the amount of data actually available to the application.
+             *
+             * Do not call this function too often, certainly not for every
+             * object you are reading. Depending on the file type it might
+             * do an expensive system call.
+             */
+            size_t offset() const noexcept {
+                return m_decompressor->offset();
+            }
+
         }; // class Reader
 
         /**
diff --git a/include/osmium/io/writer.hpp b/include/osmium/io/writer.hpp
index 13ac575..c12d317 100644
--- a/include/osmium/io/writer.hpp
+++ b/include/osmium/io/writer.hpp
@@ -34,11 +34,13 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
+#include <cstddef>
+#include <exception>
+#include <functional>
 #include <future>
+#include <initializer_list>
 #include <memory>
-#include <stdexcept>
 #include <string>
-#include <thread>
 #include <utility>
 
 #include <osmium/io/compression.hpp>
@@ -56,6 +58,10 @@ DEALINGS IN THE SOFTWARE.
 
 namespace osmium {
 
+    namespace memory {
+        class Item;
+    } //namespace memory
+
     namespace io {
 
         namespace detail {
@@ -314,7 +320,7 @@ namespace osmium {
                     }
                     try {
                         m_buffer.push_back(item);
-                    } catch (osmium::buffer_is_full&) {
+                    } catch (const osmium::buffer_is_full&) {
                         do_flush();
                         m_buffer.push_back(item);
                     }
diff --git a/include/osmium/memory/item.hpp b/include/osmium/memory/item.hpp
index 0d550fb..2df33c7 100644
--- a/include/osmium/memory/item.hpp
+++ b/include/osmium/memory/item.hpp
@@ -33,8 +33,10 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstddef>
 #include <cstdint>
-#include <type_traits>
+
+#include <osmium/util/cast.hpp>
 
 namespace osmium {
 
@@ -45,6 +47,13 @@ namespace osmium {
         class Builder;
     } // namespace builder
 
+    enum class diff_indicator_type {
+        none  = 0,
+        left  = 1,
+        right = 2,
+        both  = 3
+    }; // diff_indicator_type
+
     namespace memory {
 
         using item_size_type = uint32_t;
@@ -52,9 +61,7 @@ namespace osmium {
         // align datastructures to this many bytes
         constexpr item_size_type align_bytes = 8;
 
-        template <typename T>
-        inline T padded_length(T length) noexcept {
-            static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, "Template parameter must be unsigned integral type");
+        inline std::size_t padded_length(std::size_t length) noexcept {
             return (length + align_bytes - 1) & ~(align_bytes - 1);
         }
 
@@ -100,7 +107,8 @@ namespace osmium {
             item_size_type m_size;
             item_type m_type;
             uint16_t m_removed : 1;
-            uint16_t m_padding : 15;
+            uint16_t m_diff : 2;
+            uint16_t m_padding : 13;
 
             template <typename TMember>
             friend class CollectionIterator;
@@ -121,6 +129,7 @@ namespace osmium {
                 m_size(size),
                 m_type(type),
                 m_removed(false),
+                m_diff(0),
                 m_padding(0) {
             }
 
@@ -150,7 +159,7 @@ namespace osmium {
             }
 
             item_size_type padded_size() const {
-                return padded_length(m_size);
+                return static_cast_with_assert<item_size_type>(padded_length(m_size));
             }
 
             item_type type() const noexcept {
@@ -165,6 +174,19 @@ namespace osmium {
                 m_removed = removed;
             }
 
+            diff_indicator_type diff() const noexcept {
+                return diff_indicator_type(m_diff);
+            }
+
+            char diff_as_char() const noexcept {
+                static constexpr const char* diff_chars = "*-+ ";
+                return diff_chars[m_diff];
+            }
+
+            void set_diff(diff_indicator_type diff) noexcept {
+                m_diff = uint16_t(diff);
+            }
+
         }; // class Item
 
         static_assert(sizeof(Item) == 8, "Class osmium::Item has wrong size!");
diff --git a/include/osmium/memory/item_iterator.hpp b/include/osmium/memory/item_iterator.hpp
index df72999..27ebc59 100644
--- a/include/osmium/memory/item_iterator.hpp
+++ b/include/osmium/memory/item_iterator.hpp
@@ -34,16 +34,29 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
+#include <cstddef>
 #include <iterator>
 #include <iosfwd>
 #include <type_traits>
 
-#include <osmium/fwd.hpp>
 #include <osmium/memory/item.hpp>
 #include <osmium/osm/item_type.hpp>
 
 namespace osmium {
 
+    class Area;
+    class Changeset;
+    class InnerRing;
+    class Node;
+    class OSMEntity;
+    class OSMObject;
+    class OuterRing;
+    class Relation;
+    class RelationMemberList;
+    class TagList;
+    class Way;
+    class WayNodeList;
+
     namespace memory {
 
         namespace detail {
diff --git a/include/osmium/object_pointer_collection.hpp b/include/osmium/object_pointer_collection.hpp
index ce77bd2..4db1c08 100644
--- a/include/osmium/object_pointer_collection.hpp
+++ b/include/osmium/object_pointer_collection.hpp
@@ -40,11 +40,9 @@ DEALINGS IN THE SOFTWARE.
 #include <boost/iterator/indirect_iterator.hpp>
 
 #include <osmium/handler.hpp>
-#include <osmium/memory/item.hpp>
 #include <osmium/osm/object.hpp>
 
 // IWYU pragma: no_forward_declare osmium::OSMObject
-// IWYU pragma: no_forward_declare osmium::memory::Item
 
 namespace osmium {
 
diff --git a/include/osmium/tags/taglist.hpp b/include/osmium/opl.hpp
similarity index 61%
copy from include/osmium/tags/taglist.hpp
copy to include/osmium/opl.hpp
index b1f346f..5666fa0 100644
--- a/include/osmium/tags/taglist.hpp
+++ b/include/osmium/opl.hpp
@@ -1,5 +1,5 @@
-#ifndef OSMIUM_TAGS_TAGLIST_HPP
-#define OSMIUM_TAGS_TAGLIST_HPP
+#ifndef OSMIUM_OPL_HPP
+#define OSMIUM_OPL_HPP
 
 /*
 
@@ -33,35 +33,35 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <algorithm>
-#include <utility>
-
-#include <osmium/osm/tag.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/io/detail/opl_parser_functions.hpp>
 
 namespace osmium {
 
     /**
-     * @brief Code related to working with OSM tags
+     * Parses one line in OPL format. The line must not have a newline
+     * character at the end. Buffer.commit() is called automatically if the
+     * write succeeded.
+     *
+     * @param data Line must be in this zero-delimited string.
+     * @param buffer Result will be written to this buffer.
+     *
+     * @returns true if an entity was parsed, false otherwise (for instance
+     *          when the line is empty).
+     * @throws osmium::opl_error If the parsing fails.
      */
-    namespace tags {
-
-        template <typename TFilter>
-        inline bool match_any_of(const osmium::TagList& tag_list, TFilter&& filter) {
-            return std::any_of(tag_list.cbegin(), tag_list.cend(), std::forward<TFilter>(filter));
-        }
-
-        template <typename TFilter>
-        inline bool match_all_of(const osmium::TagList& tag_list, TFilter&& filter) {
-            return std::all_of(tag_list.cbegin(), tag_list.cend(), std::forward<TFilter>(filter));
+    inline bool opl_parse(const char* data, osmium::memory::Buffer& buffer) {
+        try {
+            const bool wrote_something = osmium::io::detail::opl_parse_line(0, data, buffer);
+            buffer.commit();
+            return wrote_something;
+        } catch (const osmium::opl_error&) {
+            buffer.rollback();
+            throw;
         }
-
-        template <typename TFilter>
-        inline bool match_none_of(const osmium::TagList& tag_list, TFilter&& filter) {
-            return std::none_of(tag_list.cbegin(), tag_list.cend(), std::forward<TFilter>(filter));
-        }
-
-    } // namespace tags
+    }
 
 } // namespace osmium
 
-#endif // OSMIUM_TAGS_TAGLIST_HPP
+
+#endif // OSMIUM_OPL_HPP
diff --git a/include/osmium/osm.hpp b/include/osmium/osm.hpp
index 594db75..fa8a92d 100644
--- a/include/osmium/osm.hpp
+++ b/include/osmium/osm.hpp
@@ -33,11 +33,20 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <osmium/osm/node.hpp> // IWYU pragma: export
-#include <osmium/osm/way.hpp> // IWYU pragma: export
-#include <osmium/osm/relation.hpp> // IWYU pragma: export
 #include <osmium/osm/area.hpp> // IWYU pragma: export
 #include <osmium/osm/changeset.hpp> // IWYU pragma: export
+#include <osmium/osm/entity.hpp> // IWYU pragma: export
+#include <osmium/osm/entity_bits.hpp> // IWYU pragma: export
+#include <osmium/osm/item_type.hpp> // IWYU pragma: export
+#include <osmium/osm/location.hpp> // IWYU pragma: export
+#include <osmium/osm/node.hpp> // IWYU pragma: export
+#include <osmium/osm/node_ref.hpp> // IWYU pragma: export
+#include <osmium/osm/node_ref_list.hpp> // IWYU pragma: export
+#include <osmium/osm/object.hpp> // IWYU pragma: export
+#include <osmium/osm/relation.hpp> // IWYU pragma: export
+#include <osmium/osm/timestamp.hpp> // IWYU pragma: export
+#include <osmium/osm/types.hpp> // IWYU pragma: export
+#include <osmium/osm/way.hpp> // IWYU pragma: export
 
 /**
  * @brief Namespace for everything in the Osmium library.
diff --git a/include/osmium/osm/area.hpp b/include/osmium/osm/area.hpp
index 858c90d..490fbe9 100644
--- a/include/osmium/osm/area.hpp
+++ b/include/osmium/osm/area.hpp
@@ -35,10 +35,12 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cassert>
 #include <cstdlib>
+#include <iterator>
 #include <utility>
 
 #include <osmium/memory/collection.hpp>
 #include <osmium/memory/item.hpp>
+#include <osmium/memory/item_iterator.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/object.hpp>
 #include <osmium/osm/types.hpp>
diff --git a/include/osmium/osm/box.hpp b/include/osmium/osm/box.hpp
index 6fcf48d..52ca93d 100644
--- a/include/osmium/osm/box.hpp
+++ b/include/osmium/osm/box.hpp
@@ -36,7 +36,6 @@ DEALINGS IN THE SOFTWARE.
 #include <cassert>
 #include <iosfwd>
 
-#include <osmium/util/compatibility.hpp>
 #include <osmium/osm/location.hpp>
 
 namespace osmium {
diff --git a/include/osmium/osm/changeset.hpp b/include/osmium/osm/changeset.hpp
index 0ec994c..8a503ca 100644
--- a/include/osmium/osm/changeset.hpp
+++ b/include/osmium/osm/changeset.hpp
@@ -33,7 +33,9 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstdint>
 #include <cstring>
+#include <iterator>
 
 #include <osmium/memory/collection.hpp>
 #include <osmium/memory/item.hpp>
@@ -185,6 +187,11 @@ namespace osmium {
 
     public:
 
+        // Dummy to avoid warning because of unused private fields. Do not use.
+        int32_t do_not_use() const noexcept {
+            return m_padding1 + m_padding2;
+        }
+
         /// Get ID of this changeset
         changeset_id_type id() const noexcept {
             return m_id;
diff --git a/include/osmium/osm/crc.hpp b/include/osmium/osm/crc.hpp
index e7c233a..2abeac4 100644
--- a/include/osmium/osm/crc.hpp
+++ b/include/osmium/osm/crc.hpp
@@ -36,11 +36,17 @@ DEALINGS IN THE SOFTWARE.
 #include <cstdint>
 
 #include <osmium/osm/area.hpp>
+#include <osmium/osm/box.hpp>
 #include <osmium/osm/changeset.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/node_ref_list.hpp>
+#include <osmium/osm/object.hpp>
 #include <osmium/osm/relation.hpp>
+#include <osmium/osm/tag.hpp>
+#include <osmium/osm/timestamp.hpp>
 #include <osmium/osm/way.hpp>
 #include <osmium/util/endian.hpp>
 
@@ -71,8 +77,8 @@ namespace osmium {
 # if defined(__GNUC__) || defined(__clang__)
             return __builtin_bswap64(value);
 # else
-            uint64_t val1 = byte_swap_32(value & 0xFFFFFFFF);
-            uint64_t val2 = byte_swap_32(value >> 32);
+            const uint64_t val1 = byte_swap_32(value & 0xFFFFFFFF);
+            const uint64_t val2 = byte_swap_32(value >> 32);
             return (val1 << 32) | val2;
 # endif
         }
@@ -86,11 +92,11 @@ namespace osmium {
 
     public:
 
-        TCRC& operator()() {
+        TCRC& operator()() noexcept {
             return m_crc;
         }
 
-        const TCRC& operator()() const {
+        const TCRC& operator()() const noexcept {
             return m_crc;
         }
 
@@ -106,7 +112,7 @@ namespace osmium {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
             m_crc.process_bytes(&value, sizeof(uint16_t));
 #else
-            uint16_t v = osmium::util::byte_swap_16(value);
+            const uint16_t v = osmium::util::byte_swap_16(value);
             m_crc.process_bytes(&v, sizeof(uint16_t));
 #endif
         }
@@ -115,7 +121,7 @@ namespace osmium {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
             m_crc.process_bytes(&value, sizeof(uint32_t));
 #else
-            uint32_t v = osmium::util::byte_swap_32(value);
+            const uint32_t v = osmium::util::byte_swap_32(value);
             m_crc.process_bytes(&v, sizeof(uint32_t));
 #endif
         }
@@ -124,7 +130,7 @@ namespace osmium {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
             m_crc.process_bytes(&value, sizeof(uint64_t));
 #else
-            uint64_t v = osmium::util::byte_swap_64(value);
+            const uint64_t v = osmium::util::byte_swap_64(value);
             m_crc.process_bytes(&v, sizeof(uint64_t));
 #endif
         }
@@ -206,10 +212,10 @@ namespace osmium {
 
         void update(const osmium::Area& area) {
             update(static_cast<const osmium::OSMObject&>(area));
-            for (auto it = area.cbegin(); it != area.cend(); ++it) {
-                if (it->type() == osmium::item_type::outer_ring ||
-                    it->type() == osmium::item_type::inner_ring) {
-                    update(static_cast<const osmium::NodeRefList&>(*it));
+            for (const auto& subitem : area) {
+                if (subitem.type() == osmium::item_type::outer_ring ||
+                    subitem.type() == osmium::item_type::inner_ring) {
+                    update(static_cast<const osmium::NodeRefList&>(subitem));
                 }
             }
         }
diff --git a/include/osmium/osm/diff_object.hpp b/include/osmium/osm/diff_object.hpp
index 609ab74..21cf139 100644
--- a/include/osmium/osm/diff_object.hpp
+++ b/include/osmium/osm/diff_object.hpp
@@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cassert>
 
-#include <osmium/fwd.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/object.hpp>
 #include <osmium/osm/timestamp.hpp>
@@ -43,6 +42,10 @@ DEALINGS IN THE SOFTWARE.
 
 namespace osmium {
 
+    class Node;
+    class Way;
+    class Relation;
+
     /**
      * A DiffObject holds pointers to three OSMObjects, the current object,
      * the previous, and the next. They always have the same type (Node, Way,
diff --git a/include/osmium/osm/location.hpp b/include/osmium/osm/location.hpp
index b8a4660..c5da620 100644
--- a/include/osmium/osm/location.hpp
+++ b/include/osmium/osm/location.hpp
@@ -35,19 +35,14 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cmath>
 #include <cstdint>
+#include <cstring>
 #include <functional>
-#include <iomanip>
 #include <iosfwd>
-#include <locale>
-#include <sstream>
+#include <iterator>
+#include <limits>
 #include <stdexcept>
 #include <string>
 
-#include <iostream>
-
-#include <osmium/util/compatibility.hpp>
-#include <osmium/util/double.hpp>
-
 namespace osmium {
 
     /**
@@ -70,41 +65,20 @@ namespace osmium {
 
         constexpr const int coordinate_precision = 10000000;
 
-        // Fallback function used when a coordinate is written in scientific
-        // notation. This function uses stringstream and is much more expensive
-        // than the handcrafted one. But coordinates in scientific notations
-        // shouldn't be used anyway.
-        inline int32_t string_to_location_coordinate_fallback(const char* str) {
-            double value;
-            std::istringstream ss{str};
-            ss.imbue(std::locale("C"));
-            ss >> std::noskipws >> value;
-
-            if (ss.fail() || !ss.eof() || ss.bad() || value > 215.0 || value < -215.0) {
-                throw invalid_location{std::string{"wrong format for coordinate: '"} + str + "'"};
-            }
-
-            return std::round(value * coordinate_precision);
-        }
-
         // Convert string with a floating point number into integer suitable
         // for use as coordinate in a Location.
-        inline int32_t string_to_location_coordinate(const char* str) {
+        inline int32_t string_to_location_coordinate(const char** data) {
+            const char* str = *data;
             const char* full = str;
 
-            // call fallback if scientific notation is used
-            while (*str) {
-                if (*str == 'e' || *str == 'E') {
-                    return string_to_location_coordinate_fallback(full);
-                }
-                ++str;
-            }
+            int64_t result = 0;
+            int sign = 1;
 
-            str = full;
+            // one more than significant digits to allow rounding
+            int64_t scale = 8;
 
-            int32_t result = 0;
-            int sign = 1;
-            int scale = 7;
+            // paranoia check for maximum number of digits
+            int max_digits = 10;
 
             // optional minus sign
             if (*str == '-') {
@@ -112,7 +86,7 @@ namespace osmium {
                 ++str;
             }
 
-            // first digit before decimal point
+            // there has to be at least one digit
             if (*str >= '0' && *str <= '9') {
                 result = *str - '0';
                 ++str;
@@ -120,55 +94,91 @@ namespace osmium {
                 goto error;
             }
 
-            // optional second digit before decimal point
-            if (*str >= '0' && *str <= '9') {
-                result = result * 10 + *str - '0';
+            // optional additional digits before decimal point
+            while (*str >= '0' && *str <= '9' && max_digits > 0) {
+                result = result * 10 + (*str - '0');
                 ++str;
+                --max_digits;
+            }
 
-                // optional third digit before decimal point
-                if (*str >= '0' && *str <= '9') {
-                    result = result * 10 + *str - '0';
-                    ++str;
-                }
+            if (max_digits == 0) {
+                goto error;
             }
 
-            if (*str != '\0') {
+            // optional decimal point
+            if (*str == '.') {
+                ++str;
+
+                // read significant digits
+                for (; scale > 0 && *str >= '0' && *str <= '9'; --scale, ++str) {
+                    result = result * 10 + (*str - '0');
+                }
+
+                // ignore non-significant digits
+                max_digits = 20;
+                while (*str >= '0' && *str <= '9' && max_digits > 0) {
+                    ++str;
+                    --max_digits;
+                }
 
-                // decimal point
-                if (*str != '.') {
+                if (max_digits == 0) {
                     goto error;
                 }
+            }
 
+            // optional exponent in scientific notation
+            if (*str == 'e' || *str == 'E') {
                 ++str;
 
-                // read significant digits
-                for (; scale > 0 && *str >= '0' && *str <= '9'; --scale, ++str) {
-                    result = result * 10 + (*str - '0');
+                int esign = 1;
+                // optional minus sign
+                if (*str == '-') {
+                    esign = -1;
+                    ++str;
                 }
 
-                // use 8th digit after decimal point for rounding
-                if (scale == 0 && *str >= '5' && *str <= '9') {
-                    ++result;
+                int64_t eresult = 0;
+
+                // there has to be at least one digit in exponent
+                if (*str >= '0' && *str <= '9') {
+                    eresult = *str - '0';
                     ++str;
+                } else {
+                    goto error;
                 }
 
-                // ignore further digits
-                while (*str >= '0' && *str <= '9') {
+                // optional additional digits in exponent
+                max_digits = 5;
+                while (*str >= '0' && *str <= '9' && max_digits > 0) {
+                    eresult = eresult * 10 + (*str - '0');
                     ++str;
+                    --max_digits;
                 }
 
-                // should be at the end now
-                if (*str != '\0') {
+                if (max_digits == 0) {
                     goto error;
                 }
 
+                scale += eresult * esign;
             }
 
-            for (; scale > 0; --scale) {
-                result *= 10;
+            if (scale < 0) {
+                result = 0;
+            } else {
+                for (; scale > 0; --scale) {
+                    result *= 10;
+                }
+
+                result = (result + 5) / 10 * sign;
+
+                if (result > std::numeric_limits<int32_t>::max() ||
+                    result < std::numeric_limits<int32_t>::min()) {
+                    goto error;
+                }
             }
 
-            return result * sign;
+            *data = str;
+            return static_cast<int32_t>(result);
 
         error:
 
@@ -391,12 +401,30 @@ namespace osmium {
             return *this;
         }
 
-        Location& set_lon(const char* str) noexcept {
+        Location& set_lon(const char* str) {
+            const char** data = &str;
+            m_x = detail::string_to_location_coordinate(data);
+            if (**data != '\0') {
+                throw invalid_location{std::string{"characters after coordinate: '"} + *data + "'"};
+            }
+            return *this;
+        }
+
+        Location& set_lat(const char* str) {
+            const char** data = &str;
+            m_y = detail::string_to_location_coordinate(data);
+            if (**data != '\0') {
+                throw invalid_location{std::string{"characters after coordinate: '"} + *data + "'"};
+            }
+            return *this;
+        }
+
+        Location& set_lon_partial(const char** str) {
             m_x = detail::string_to_location_coordinate(str);
             return *this;
         }
 
-        Location& set_lat(const char* str) noexcept {
+        Location& set_lat_partial(const char** str) {
             m_y = detail::string_to_location_coordinate(str);
             return *this;
         }
diff --git a/include/osmium/osm/node_ref_list.hpp b/include/osmium/osm/node_ref_list.hpp
index 84edc07..6cfdf22 100644
--- a/include/osmium/osm/node_ref_list.hpp
+++ b/include/osmium/osm/node_ref_list.hpp
@@ -39,6 +39,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <osmium/memory/item.hpp>
 #include <osmium/osm/item_type.hpp>
+#include <osmium/osm/location.hpp>
 #include <osmium/osm/node_ref.hpp>
 
 namespace osmium {
@@ -66,7 +67,7 @@ namespace osmium {
          * Returns the number of NodeRefs in the collection.
          */
         size_t size() const noexcept {
-            auto size_node_refs = byte_size() - sizeof(NodeRefList);
+            const auto size_node_refs = byte_size() - sizeof(NodeRefList);
             assert(size_node_refs % sizeof(NodeRef) == 0);
             return size_node_refs / sizeof(NodeRef);
         }
diff --git a/include/osmium/osm/object.hpp b/include/osmium/osm/object.hpp
index a373156..caa6fbc 100644
--- a/include/osmium/osm/object.hpp
+++ b/include/osmium/osm/object.hpp
@@ -33,8 +33,6 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstddef>
-#include <cstdint>
 #include <cstdlib>
 #include <cstring>
 #include <stdexcept>
@@ -288,6 +286,20 @@ namespace osmium {
             return *this;
         }
 
+        /**
+         * Set the timestamp when this object last changed.
+         *
+         * @param timestamp Timestamp in ISO format.
+         * @returns Reference to object to make calls chainable.
+         */
+        OSMObject& set_timestamp(const char* timestamp) {
+            m_timestamp = detail::parse_timestamp(timestamp);
+            if (timestamp[20] != '\0') {
+                throw std::invalid_argument{"can not parse timestamp"};
+            }
+            return *this;
+        }
+
         /// Get user name for this object.
         const char* user() const noexcept {
             return reinterpret_cast<const char*>(data() + sizeof_object());
@@ -313,8 +325,9 @@ namespace osmium {
          *
          * @param attr Name of the attribute (must be one of "id", "version", "changeset", "timestamp", "uid", "visible")
          * @param value Value of the attribute
+         * @returns Reference to object to make calls chainable.
          */
-        void set_attribute(const char* attr, const char* value) {
+        OSMObject& set_attribute(const char* attr, const char* value) {
             if (!std::strcmp(attr, "id")) {
                 set_id(value);
             } else if (!std::strcmp(attr, "version")) {
@@ -322,12 +335,14 @@ namespace osmium {
             } else if (!std::strcmp(attr, "changeset")) {
                 set_changeset(value);
             } else if (!std::strcmp(attr, "timestamp")) {
-                set_timestamp(osmium::Timestamp(value));
+                set_timestamp(value);
             } else if (!std::strcmp(attr, "uid")) {
                 set_uid(value);
             } else if (!std::strcmp(attr, "visible")) {
                 set_visible(value);
             }
+
+            return *this;
         }
 
         using iterator       = osmium::memory::CollectionIterator<Item>;
diff --git a/include/osmium/osm/object_comparisons.hpp b/include/osmium/osm/object_comparisons.hpp
index a17d47d..aa0241d 100644
--- a/include/osmium/osm/object_comparisons.hpp
+++ b/include/osmium/osm/object_comparisons.hpp
@@ -33,7 +33,10 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <tuple>
+
 #include <osmium/osm/object.hpp>
+#include <osmium/osm/timestamp.hpp>
 #include <osmium/util/misc.hpp>
 
 namespace osmium {
diff --git a/include/osmium/osm/relation.hpp b/include/osmium/osm/relation.hpp
index 1ea0a5e..2aa9caa 100644
--- a/include/osmium/osm/relation.hpp
+++ b/include/osmium/osm/relation.hpp
@@ -33,13 +33,13 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstddef>
 #include <cstdint>
 #include <cstdlib>
 #include <iterator>
 
 #include <osmium/memory/collection.hpp> // IWYU pragma: keep
 #include <osmium/memory/item.hpp>
+#include <osmium/osm/entity.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/object.hpp>
 #include <osmium/osm/types.hpp>
diff --git a/include/osmium/osm/segment.hpp b/include/osmium/osm/segment.hpp
index d35f970..c36533e 100644
--- a/include/osmium/osm/segment.hpp
+++ b/include/osmium/osm/segment.hpp
@@ -37,7 +37,6 @@ DEALINGS IN THE SOFTWARE.
 #include <utility>
 
 #include <osmium/osm/location.hpp>
-#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/osm/tag.hpp b/include/osmium/osm/tag.hpp
index d5415f7..cd2a913 100644
--- a/include/osmium/osm/tag.hpp
+++ b/include/osmium/osm/tag.hpp
@@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
-#include <cstddef>
 #include <cstring>
 #include <iosfwd>
 #include <iterator>
diff --git a/include/osmium/osm/timestamp.hpp b/include/osmium/osm/timestamp.hpp
index 613752e..5f52430 100644
--- a/include/osmium/osm/timestamp.hpp
+++ b/include/osmium/osm/timestamp.hpp
@@ -40,12 +40,71 @@ DEALINGS IN THE SOFTWARE.
 #include <limits>
 #include <stdexcept>
 #include <string>
+#include <type_traits>
 
 #include <osmium/util/compatibility.hpp>
 #include <osmium/util/minmax.hpp> // IWYU pragma: keep
 
 namespace osmium {
 
+    namespace detail {
+
+        inline time_t parse_timestamp(const char* str) {
+            static const int mon_lengths[] = {
+                31, 29, 31, 30, 31, 30,
+                31, 31, 30, 31, 30, 31
+            };
+            if (str[ 0] >= '0' && str[ 0] <= '9' &&
+                str[ 1] >= '0' && str[ 1] <= '9' &&
+                str[ 2] >= '0' && str[ 2] <= '9' &&
+                str[ 3] >= '0' && str[ 3] <= '9' &&
+                str[ 4] == '-' &&
+                str[ 5] >= '0' && str[ 5] <= '9' &&
+                str[ 6] >= '0' && str[ 6] <= '9' &&
+                str[ 7] == '-' &&
+                str[ 8] >= '0' && str[ 8] <= '9' &&
+                str[ 9] >= '0' && str[ 9] <= '9' &&
+                str[10] == 'T' &&
+                str[11] >= '0' && str[11] <= '9' &&
+                str[12] >= '0' && str[12] <= '9' &&
+                str[13] == ':' &&
+                str[14] >= '0' && str[14] <= '9' &&
+                str[15] >= '0' && str[15] <= '9' &&
+                str[16] == ':' &&
+                str[17] >= '0' && str[17] <= '9' &&
+                str[18] >= '0' && str[18] <= '9' &&
+                str[19] == 'Z') {
+                struct tm tm;
+                tm.tm_year = (str[ 0] - '0') * 1000 +
+                             (str[ 1] - '0') *  100 +
+                             (str[ 2] - '0') *   10 +
+                             (str[ 3] - '0')        - 1900;
+                tm.tm_mon  = (str[ 5] - '0') * 10 + (str[ 6] - '0') - 1;
+                tm.tm_mday = (str[ 8] - '0') * 10 + (str[ 9] - '0');
+                tm.tm_hour = (str[11] - '0') * 10 + (str[12] - '0');
+                tm.tm_min  = (str[14] - '0') * 10 + (str[15] - '0');
+                tm.tm_sec  = (str[17] - '0') * 10 + (str[18] - '0');
+                tm.tm_wday = 0;
+                tm.tm_yday = 0;
+                tm.tm_isdst = 0;
+                if (tm.tm_year >= 0 &&
+                    tm.tm_mon  >= 0 && tm.tm_mon  <= 11 &&
+                    tm.tm_mday >= 1 && tm.tm_mday <= mon_lengths[tm.tm_mon] &&
+                    tm.tm_hour >= 0 && tm.tm_hour <= 23 &&
+                    tm.tm_min  >= 0 && tm.tm_min  <= 59 &&
+                    tm.tm_sec  >= 0 && tm.tm_sec  <= 60) {
+#ifndef _WIN32
+                    return timegm(&tm);
+#else
+                    return _mkgmtime(&tm);
+#endif
+                }
+            }
+            throw std::invalid_argument{"can not parse timestamp"};
+        }
+
+    } // namespace detail
+
     /**
      * A timestamp. Internal representation is an unsigned 32bit integer
      * holding seconds since epoch (1970-01-01T00:00:00Z), so this will
@@ -56,7 +115,7 @@ namespace osmium {
     class Timestamp {
 
         // length of ISO timestamp string yyyy-mm-ddThh:mm:ssZ\0
-        static constexpr int timestamp_length = 20 + 1;
+        static constexpr const int timestamp_length = 20 + 1;
 
         // The timestamp format for OSM timestamps in strftime(3) format.
         // This is the ISO-Format "yyyy-mm-ddThh:mm:ssZ".
@@ -97,27 +156,7 @@ namespace osmium {
          * @throws std::invalid_argument if the timestamp can not be parsed.
          */
         explicit Timestamp(const char* timestamp) {
-#ifndef _WIN32
-            struct tm tm {
-                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-            };
-            if (strptime(timestamp, timestamp_format(), &tm) == nullptr) {
-                throw std::invalid_argument("can't parse timestamp");
-            }
-            m_timestamp = static_cast<uint32_t>(timegm(&tm));
-#else
-            struct tm tm;
-            int n = sscanf(timestamp, "%4d-%2d-%2dT%2d:%2d:%2dZ", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
-            if (n != 6) {
-                throw std::invalid_argument("can't parse timestamp");
-            }
-            tm.tm_year -= 1900;
-            tm.tm_mon--;
-            tm.tm_wday = 0;
-            tm.tm_yday = 0;
-            tm.tm_isdst = 0;
-            m_timestamp = static_cast<uint32_t>(_mkgmtime(&tm));
-#endif
+            m_timestamp = static_cast<uint32_t>(detail::parse_timestamp(timestamp));
         }
 
         /**
diff --git a/include/osmium/osm/types_from_string.hpp b/include/osmium/osm/types_from_string.hpp
index d5da72b..190dd29 100644
--- a/include/osmium/osm/types_from_string.hpp
+++ b/include/osmium/osm/types_from_string.hpp
@@ -35,13 +35,14 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cassert>
 #include <cctype>
-#include <cstdint>
 #include <cstdlib>
 #include <limits>
+#include <stdexcept>
 #include <string>
 #include <utility>
 
 #include <osmium/osm/entity_bits.hpp>
+#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/types.hpp>
 #include <osmium/util/cast.hpp>
 
diff --git a/include/osmium/osm/way.hpp b/include/osmium/osm/way.hpp
index 3bc30b0..f6713fe 100644
--- a/include/osmium/osm/way.hpp
+++ b/include/osmium/osm/way.hpp
@@ -33,12 +33,13 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <osmium/memory/collection.hpp>
 #include <osmium/memory/item.hpp>
+#include <osmium/osm/entity.hpp>
 #include <osmium/osm/item_type.hpp>
-#include <osmium/osm/object.hpp>
-#include <osmium/osm/types.hpp>
 #include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/node_ref_list.hpp>
+#include <osmium/osm/object.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/relations/collector.hpp b/include/osmium/relations/collector.hpp
index 166b00d..b8455b4 100644
--- a/include/osmium/relations/collector.hpp
+++ b/include/osmium/relations/collector.hpp
@@ -39,13 +39,12 @@ DEALINGS IN THE SOFTWARE.
 #include <cstdint>
 #include <functional>
 #include <iomanip>
-//#include <iostream>
+#include <iostream>
 #include <vector>
 
-#include <osmium/fwd.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/object.hpp>
-#include <osmium/osm/relation.hpp> // IWYU pragma: keep
+#include <osmium/osm/relation.hpp>
 #include <osmium/osm/types.hpp>
 #include <osmium/handler.hpp>
 #include <osmium/memory/buffer.hpp>
@@ -57,6 +56,9 @@ DEALINGS IN THE SOFTWARE.
 
 namespace osmium {
 
+    class Node;
+    class Way;
+
     /**
      * @brief Code related to the assembly of OSM relations
      */
@@ -106,7 +108,7 @@ namespace osmium {
 
             public:
 
-                HandlerPass1(TCollector& collector) noexcept :
+                explicit HandlerPass1(TCollector& collector) noexcept :
                     m_collector(collector) {
                 }
 
@@ -129,7 +131,7 @@ namespace osmium {
 
             public:
 
-                HandlerPass2(TCollector& collector) noexcept :
+                explicit HandlerPass2(TCollector& collector) noexcept :
                     m_collector(collector) {
                 }
 
@@ -430,7 +432,7 @@ namespace osmium {
                 const osmium::Relation& relation = get_relation(relation_meta);
                 for (const auto& member : relation.members()) {
                     if (member.ref() != 0) {
-                        auto range = find_member_meta(member.type(), member.ref());
+                        const auto range = find_member_meta(member.type(), member.ref());
                         assert(!range.empty());
 
                         // if this is the last time this object was needed
diff --git a/include/osmium/relations/detail/member_meta.hpp b/include/osmium/relations/detail/member_meta.hpp
index fb71416..b28dca1 100644
--- a/include/osmium/relations/detail/member_meta.hpp
+++ b/include/osmium/relations/detail/member_meta.hpp
@@ -33,10 +33,8 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <algorithm>
 #include <cstddef>
 #include <iosfwd>
-#include <iterator>
 
 #include <osmium/osm/types.hpp>
 
diff --git a/include/osmium/tags/filter.hpp b/include/osmium/tags/filter.hpp
index 7451737..27a8360 100644
--- a/include/osmium/tags/filter.hpp
+++ b/include/osmium/tags/filter.hpp
@@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstddef>
 #include <string>
 #include <type_traits>
 #include <vector>
diff --git a/include/osmium/tags/taglist.hpp b/include/osmium/tags/taglist.hpp
index b1f346f..d786279 100644
--- a/include/osmium/tags/taglist.hpp
+++ b/include/osmium/tags/taglist.hpp
@@ -34,7 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <algorithm>
-#include <utility>
+#include <utility> // IWYU pragma: keep
 
 #include <osmium/osm/tag.hpp>
 
diff --git a/include/osmium/thread/pool.hpp b/include/osmium/thread/pool.hpp
index d1b74c1..613f227 100644
--- a/include/osmium/thread/pool.hpp
+++ b/include/osmium/thread/pool.hpp
@@ -34,9 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <algorithm>
-#include <atomic>
 #include <cstddef>
-#include <cstdlib>
 #include <future>
 #include <thread>
 #include <type_traits>
@@ -79,7 +77,7 @@ namespace osmium {
             }
 
             inline size_t get_work_queue_size() noexcept {
-                size_t n = osmium::config::get_max_queue_size("WORK", 10);
+                const size_t n = osmium::config::get_max_queue_size("WORK", 10);
                 return n > 2 ? n : 2;
             }
 
diff --git a/include/osmium/thread/queue.hpp b/include/osmium/thread/queue.hpp
index 3124611..6f4f7b1 100644
--- a/include/osmium/thread/queue.hpp
+++ b/include/osmium/thread/queue.hpp
@@ -33,7 +33,6 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <atomic>
 #include <chrono>
 #include <condition_variable>
 #include <cstddef>
@@ -41,9 +40,10 @@ DEALINGS IN THE SOFTWARE.
 #include <queue>
 #include <string>
 #include <thread>
-#include <utility> // IWYU pragma: keep (for std::move)
+#include <utility> // IWYU pragma: keep
 
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
+# include <atomic>
 # include <iostream>
 #endif
 
diff --git a/include/osmium/thread/util.hpp b/include/osmium/thread/util.hpp
index 2ef331a..2eeb999 100644
--- a/include/osmium/thread/util.hpp
+++ b/include/osmium/thread/util.hpp
@@ -35,6 +35,8 @@ DEALINGS IN THE SOFTWARE.
 
 #include <chrono>
 #include <future>
+#include <thread>
+#include <utility>
 
 #ifdef __linux__
 # include <sys/prctl.h>
diff --git a/include/osmium/util/delta.hpp b/include/osmium/util/delta.hpp
index 51c5c7b..8894dd7 100644
--- a/include/osmium/util/delta.hpp
+++ b/include/osmium/util/delta.hpp
@@ -33,12 +33,11 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <iterator>
+#include <cstdint>
 #include <type_traits>
 #include <utility>
 
 #include <osmium/util/cast.hpp>
-#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/util/double.hpp b/include/osmium/util/double.hpp
index 1352c5f..9714bf6 100644
--- a/include/osmium/util/double.hpp
+++ b/include/osmium/util/double.hpp
@@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
-#include <cmath>
 #include <cstdio>
 #include <iterator>
 #include <string>
@@ -44,7 +43,7 @@ namespace osmium {
 
     namespace util {
 
-        constexpr int max_double_length = 20; // should fit any double
+        constexpr const int max_double_length = 20; // should fit any double
 
         /**
          * Write double to iterator, removing superfluous '0' characters at
diff --git a/include/osmium/util/file.hpp b/include/osmium/util/file.hpp
index 86b93ff..4c951e7 100644
--- a/include/osmium/util/file.hpp
+++ b/include/osmium/util/file.hpp
@@ -36,6 +36,7 @@ DEALINGS IN THE SOFTWARE.
 #include <cerrno>
 #include <cstddef>
 #include <cstdio>
+#include <string>
 #include <system_error>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -47,9 +48,6 @@ DEALINGS IN THE SOFTWARE.
 
 #ifndef _MSC_VER
 # include <unistd.h>
-#else
-// https://msdn.microsoft.com/en-us/library/whx354w1.aspx
-# define ftruncate _chsize_s
 #endif
 
 #include <osmium/util/cast.hpp>
@@ -70,7 +68,7 @@ namespace osmium {
 #ifdef _MSC_VER
             // Windows implementation
             // https://msdn.microsoft.com/en-us/library/dfbc2kec.aspx
-            auto size = ::_filelengthi64(fd);
+            const auto size = ::_filelengthi64(fd);
             if (size == -1L) {
                 throw std::system_error(errno, std::system_category(), "_filelengthi64 failed");
             }
@@ -86,6 +84,44 @@ namespace osmium {
         }
 
         /**
+         * Get file size.
+         * This is a small wrapper around a system call.
+         *
+         * @param name File name
+         * @returns file size
+         * @throws std::system_error If system call failed
+         */
+        inline size_t file_size(const char* name) {
+#ifdef _MSC_VER
+            // Windows implementation
+            // https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx
+            struct _stat64 s;
+            if (::_stati64(name, &s) != 0) {
+                throw std::system_error(errno, std::system_category(), "_stati64 failed");
+            }
+#else
+            // Unix implementation
+            struct stat s;
+            if (::stat(name, &s) != 0) {
+                throw std::system_error(errno, std::system_category(), "stat failed");
+            }
+#endif
+            return size_t(s.st_size);
+        }
+
+        /**
+         * Get file size.
+         * This is a small wrapper around a system call.
+         *
+         * @param name File name
+         * @returns file size
+         * @throws std::system_error If system call failed
+         */
+        inline size_t file_size(const std::string& name) {
+            return file_size(name.c_str());
+        }
+
+        /**
          * Resize file.
          * Small wrapper around ftruncate(2) system call.
          *
@@ -94,8 +130,13 @@ namespace osmium {
          * @throws std::system_error If ftruncate(2) call failed
          */
         inline void resize_file(int fd, size_t new_size) {
+#ifdef _WIN32
+            // https://msdn.microsoft.com/en-us/library/whx354w1.aspx
+            if (::_chsize_s(fd, static_cast_with_assert<__int64>(new_size)) != 0) {
+#else
             if (::ftruncate(fd, static_cast_with_assert<off_t>(new_size)) != 0) {
-                throw std::system_error(errno, std::system_category(), "ftruncate failed");
+#endif
+                throw std::system_error(errno, std::system_category(), "resizing file failed");
             }
         }
 
@@ -114,6 +155,37 @@ namespace osmium {
 #endif
         }
 
+        /**
+         * Get current offset into file.
+         *
+         * @param fd Open file descriptor.
+         * @returns File offset or 0 if it is not available.
+         */
+        inline size_t file_offset(int fd) {
+#ifdef _MSC_VER
+            // https://msdn.microsoft.com/en-us/library/1yee101t.aspx
+            auto offset = _lseeki64(fd, 0, SEEK_CUR);
+#else
+            auto offset = ::lseek(fd, 0, SEEK_CUR);
+#endif
+            if (offset == -1) {
+                return 0;
+            }
+            return size_t(offset);
+        }
+
+        /**
+         * Check whether the file descriptor refers to a TTY.
+         */
+        inline bool isatty(int fd) {
+#ifdef _MSC_VER
+            // https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx
+            return _isatty(fd) != 0;
+#else
+            return ::isatty(fd) != 0;
+#endif
+        }
+
     } // namespace util
 
 } // namespace osmium
diff --git a/include/osmium/util/iterator.hpp b/include/osmium/util/iterator.hpp
index 15f3f3d..42d23e8 100644
--- a/include/osmium/util/iterator.hpp
+++ b/include/osmium/util/iterator.hpp
@@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cstddef>
+#include <type_traits>
 #include <utility>
 
 namespace osmium {
@@ -43,7 +44,7 @@ namespace osmium {
 
         using iterator = It;
 
-        iterator_range(P&& p) :
+        explicit iterator_range(P&& p) :
             P(std::forward<P>(p)) {
         }
 /*
diff --git a/include/osmium/util/memory_mapping.hpp b/include/osmium/util/memory_mapping.hpp
index cf28e6a..4fc8f01 100644
--- a/include/osmium/util/memory_mapping.hpp
+++ b/include/osmium/util/memory_mapping.hpp
@@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cassert>
 #include <cerrno>
+#include <cstddef>
 #include <stdexcept>
 #include <system_error>
 
@@ -214,7 +215,7 @@ private:
             ~MemoryMapping() noexcept {
                 try {
                     unmap();
-                } catch (std::system_error&) {
+                } catch (const std::system_error&) {
                     // Ignore any exceptions because destructor must not throw.
                 }
             }
@@ -302,7 +303,7 @@ private:
 
         public:
 
-            AnonymousMemoryMapping(size_t size) :
+            explicit AnonymousMemoryMapping(size_t size) :
                 MemoryMapping(size, mapping_mode::write_private) {
             }
 
@@ -338,7 +339,7 @@ private:
              * @param size Number of objects of type T to be mapped
              * @throws std::system_error if the mapping fails
              */
-            TypedMemoryMapping(size_t size) :
+            explicit TypedMemoryMapping(size_t size) :
                 m_mapping(sizeof(T) * size, MemoryMapping::mapping_mode::write_private) {
             }
 
@@ -487,7 +488,7 @@ private:
 
         public:
 
-            AnonymousTypedMemoryMapping(size_t size) :
+            explicit AnonymousTypedMemoryMapping(size_t size) :
                 TypedMemoryMapping<T>(size) {
             }
 
diff --git a/include/osmium/util/options.hpp b/include/osmium/util/options.hpp
index 9b00b48..79818a9 100644
--- a/include/osmium/util/options.hpp
+++ b/include/osmium/util/options.hpp
@@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstddef>
 #include <initializer_list>
 #include <map>
 #include <string>
@@ -107,7 +108,7 @@ namespace osmium {
              * be set to "true".
              */
             void set(std::string data) {
-                size_t pos = data.find_first_of('=');
+                const size_t pos = data.find_first_of('=');
                 if (pos == std::string::npos) {
                     m_options[data] = "true";
                 } else {
@@ -122,7 +123,7 @@ namespace osmium {
              * empty string) is returned.
              */
             std::string get(const std::string& key, const std::string& default_value="") const noexcept {
-                auto it = m_options.find(key);
+                const auto it = m_options.find(key);
                 if (it == m_options.end()) {
                     return default_value;
                 }
@@ -134,7 +135,7 @@ namespace osmium {
              * Will return false if the value is unset.
              */
             bool is_true(const std::string& key) const noexcept {
-                std::string value = get(key);
+                const std::string value = get(key);
                 return (value == "true" || value == "yes");
             }
 
@@ -143,7 +144,7 @@ namespace osmium {
              * Will return true if the value is unset.
              */
             bool is_not_false(const std::string& key) const noexcept {
-                std::string value = get(key);
+                const std::string value = get(key);
                 return !(value == "false" || value == "no");
             }
 
diff --git a/include/osmium/util/progress_bar.hpp b/include/osmium/util/progress_bar.hpp
new file mode 100644
index 0000000..814aa2c
--- /dev/null
+++ b/include/osmium/util/progress_bar.hpp
@@ -0,0 +1,179 @@
+#ifndef OSMIUM_UTIL_PROGRESS_BAR_HPP
+#define OSMIUM_UTIL_PROGRESS_BAR_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 <iostream>
+
+namespace osmium {
+
+    /**
+     * Displays a progress bar on STDERR. Can be used together with the
+     * osmium::io::Reader class for instance.
+     */
+    class ProgressBar {
+
+        static const char* bar() noexcept {
+            return "======================================================================";
+        }
+
+        static const char* spc() noexcept {
+            return "                                                                     ";
+        }
+
+        static constexpr const size_t length = 70;
+
+        // The max size is the file size if there is a single file and the
+        // sum of all file sizes if there are multiple files. It corresponds
+        // to 100%.
+        size_t m_max_size;
+
+        // The sum of the file sizes already done.
+        size_t m_done_size = 0;
+
+        // The currently read size in the current file.
+        size_t m_current_size = 0;
+
+        // The percentage calculated when it was last displayed. Used to decide
+        // whether we need to update the display. Start setting is one that
+        // will always be different from any legal setting.
+        size_t m_prev_percent = 100 + 1;
+
+        // Is the progress bar enabled at all?
+        bool m_enable;
+
+        // Used to make sure we do cleanup in the destructor if it was not
+        // already done.
+        bool m_do_cleanup = true;
+
+        void display() {
+            const size_t percent = 100 * (m_done_size + m_current_size) / m_max_size;
+            if (m_prev_percent == percent) {
+                return;
+            }
+            m_prev_percent = percent;
+
+            const size_t num = size_t(percent * (length / 100.0));
+            std::cerr << '[';
+            if (num >= length) {
+                std::cerr << bar();
+            } else {
+                std::cerr << (bar() + length - num) << '>' << (spc() + num);
+            }
+            std::cerr << "] ";
+            if (percent < 10) {
+                std::cerr << ' ';
+            }
+            if (percent < 100) {
+                std::cerr << ' ';
+            }
+            std::cerr << percent << "% \r";
+        }
+
+    public:
+
+        /**
+         * Initializes the progress bar. No output yet.
+         *
+         * @param max_size Max size equivalent to 100%.
+         * @param enable Set to false to disable (for instance if stderr is
+         *               not a TTY).
+         */
+        ProgressBar(size_t max_size, bool enable) noexcept :
+            m_max_size(max_size),
+            m_enable(max_size > 0 && enable) {
+        }
+
+        ~ProgressBar() {
+            if (m_do_cleanup) {
+                try {
+                    done();
+                } catch (...) {
+                    // Swallow any exceptions, because a destructor should
+                    // not throw.
+                }
+            }
+        }
+
+        /**
+         * Call this function to update the progress bar. Actual update will
+         * only happen if the percentage changed from the last time this
+         * function was called.
+         *
+         * @param current_size Current size. Used together with the max_size
+         *                     from constructor to calculate the percentage.
+         */
+        void update(size_t current_size) {
+            if (!m_enable) {
+                return;
+            }
+
+            m_current_size = current_size;
+
+            display();
+        }
+
+        /**
+         * If you are reading multiple files, call this function after each
+         * file is finished.
+         *
+         * @param file_size The size of the file just finished.
+         */
+        void file_done(size_t file_size) {
+            if (m_enable) {
+                m_done_size += file_size;
+                m_current_size = 0;
+                display();
+            }
+        }
+
+        /**
+         * Call this at the end. Will update the progress bar to 100% and
+         * print a final line feed. If this is not called explicitly the
+         * destructor will also call this.
+         */
+        void done() {
+            m_do_cleanup = false;
+            if (m_enable) {
+                m_done_size = m_max_size;
+                m_current_size = 0;
+                display();
+                std::cerr << '\n';
+            }
+        }
+
+    }; // class ProgressBar
+
+} // namespace osmium
+
+#endif // OSMIUM_UTIL_PROGRESS_BAR_HPP
diff --git a/include/osmium/util/string.hpp b/include/osmium/util/string.hpp
index 1198288..2cdb983 100644
--- a/include/osmium/util/string.hpp
+++ b/include/osmium/util/string.hpp
@@ -33,9 +33,9 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstddef>
 #include <string>
 #include <vector>
-#include <iostream>
 
 namespace osmium {
 
diff --git a/include/osmium/util/verbose_output.hpp b/include/osmium/util/verbose_output.hpp
index c7677a4..f85d265 100644
--- a/include/osmium/util/verbose_output.hpp
+++ b/include/osmium/util/verbose_output.hpp
@@ -33,11 +33,11 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <time.h>
-
+#include <ctime>
 #include <iomanip>
 #include <iostream>
 #include <sstream>
+#include <string>
 
 namespace osmium {
 
@@ -75,9 +75,9 @@ namespace osmium {
              */
             void start_line() {
                 if (m_newline) {
-                    time_t elapsed = runtime();
+                    const time_t elapsed = runtime();
 
-                    char old_fill = std::cerr.fill();
+                    const char old_fill = std::cerr.fill();
                     std::cerr << '[' << std::setw(2) << (elapsed / 60) << ':' << std::setw(2) << std::setfill('0') << (elapsed % 60) << "] ";
                     std::cerr.fill(old_fill);
 
diff --git a/include/osmium/version.hpp b/include/osmium/version.hpp
index ac7a94f..6f3b0a3 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 8
+#define LIBOSMIUM_VERSION_MINOR 9
 #define LIBOSMIUM_VERSION_PATCH 0
 
-#define LIBOSMIUM_VERSION_STRING "2.8.0"
+#define LIBOSMIUM_VERSION_STRING "2.9.0"
 
 #endif // OSMIUM_VERSION_HPP
diff --git a/include/protozero/iterators.hpp b/include/protozero/iterators.hpp
index 813d96b..00ba919 100644
--- a/include/protozero/iterators.hpp
+++ b/include/protozero/iterators.hpp
@@ -76,12 +76,12 @@ public:
     /**
      * Create iterator range from two iterators.
      *
-     * @param first Iterator to beginning or range.
-     * @param last Iterator to end or range.
+     * @param first_iterator Iterator to beginning or range.
+     * @param last_iterator Iterator to end or range.
      */
-    constexpr iterator_range(iterator&& first, iterator&& last) :
-        P(std::forward<iterator>(first),
-          std::forward<iterator>(last)) {
+    constexpr iterator_range(iterator&& first_iterator, iterator&& last_iterator) :
+        P(std::forward<iterator>(first_iterator),
+          std::forward<iterator>(last_iterator)) {
     }
 
     /// Return iterator to beginning of range.
diff --git a/include/protozero/pbf_writer.hpp b/include/protozero/pbf_writer.hpp
index 3ce0f14..bce1c3f 100644
--- a/include/protozero/pbf_writer.hpp
+++ b/include/protozero/pbf_writer.hpp
@@ -156,12 +156,16 @@ class pbf_writer {
     // The number of bytes to reserve for the varint holding the length of
     // a length-delimited field. The length has to fit into pbf_length_type,
     // and a varint needs 8 bit for every 7 bit.
-    static constexpr const int reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1;
+    enum constant_reserve_bytes : int {
+        reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1
+    };
 
     // If m_rollpack_pos is set to this special value, it means that when
     // the submessage is closed, nothing needs to be done, because the length
     // of the submessage has already been written correctly.
-    static constexpr const std::size_t size_is_known = std::numeric_limits<std::size_t>::max();
+    enum constant_size_is_known : std::size_t {
+        size_is_known = std::numeric_limits<std::size_t>::max()
+    };
 
     void open_submessage(pbf_tag_type tag, std::size_t size) {
         protozero_assert(m_pos == 0);
diff --git a/include/protozero/version.hpp b/include/protozero/version.hpp
index d427941..127e649 100644
--- a/include/protozero/version.hpp
+++ b/include/protozero/version.hpp
@@ -23,13 +23,13 @@ documentation.
 #define PROTOZERO_VERSION_MINOR 4
 
 /// The patch number
-#define PROTOZERO_VERSION_PATCH 0
+#define PROTOZERO_VERSION_PATCH 2
 
 /// The complete version number
 #define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH)
 
 /// Version number as string
-#define PROTOZERO_VERSION_STRING "1.4.0"
+#define PROTOZERO_VERSION_STRING "1.4.2"
 
 
 #endif // PROTOZERO_VERSION_HPP
diff --git a/osmium.imp b/osmium.imp
index c45794d..77c5c37 100644
--- a/osmium.imp
+++ b/osmium.imp
@@ -2,10 +2,15 @@
 #
 #  Configuration for Include-What-You-Use tool
 #
-#  https://code.google.com/p/include-what-you-use/
+#  http://include-what-you-use.org/
 #
 #-----------------------------------------------------------------------------
 [
     { "include": ["<bits/fcntl-linux.h>", "private", "<fcntl.h>", "public"] },
-    { "include": ["<sys/types.h>", "public", "<cstdint>", "public"] }
+    { "include": ["<bits/shared_ptr.h>", "private", "<memory>", "public"] },
+    { "include": ["<sys/types.h>", "public", "<cstdint>", "public"] },
+    { "include": ['"utf8/checked.h"', "private", "<utf8.h>", "public"] },
+    { "include": ['"utf8/unchecked.h"', "private", "<utf8.h>", "public"] },
+    { "include": ["<expat_external.h>", "public", "<expat.h>", "public"] },
+    { "include": ["<zconf.h>", "public", "<zlib.h>", "public"] }
 ]
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index f7b35e8..6230cde 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -127,6 +127,7 @@ 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)
@@ -170,6 +171,7 @@ add_unit_test(io test_file_formats)
 add_unit_test(io test_reader LIBS "${OSMIUM_XML_LIBRARIES};${OSMIUM_PBF_LIBRARIES}")
 add_unit_test(io test_reader_with_mock_decompression ENABLE_IF ${Threads_FOUND} LIBS ${OSMIUM_XML_LIBRARIES})
 add_unit_test(io test_reader_with_mock_parser ENABLE_IF ${Threads_FOUND} LIBS ${CMAKE_THREAD_LIBS_INIT})
+add_unit_test(io test_opl_parser)
 add_unit_test(io test_output_utils)
 add_unit_test(io test_output_iterator ENABLE_IF ${Threads_FOUND} LIBS ${CMAKE_THREAD_LIBS_INIT})
 add_unit_test(io test_string_table)
diff --git a/test/data-tests/include/common.hpp b/test/data-tests/include/common.hpp
index a6fd3df..4dad07d 100644
--- a/test/data-tests/include/common.hpp
+++ b/test/data-tests/include/common.hpp
@@ -10,9 +10,9 @@
 #include <osmium/io/xml_input.hpp>
 #include <osmium/visitor.hpp>
 
-typedef osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location> index_neg_type;
-typedef osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location> index_pos_type;
-typedef osmium::handler::NodeLocationsForWays<index_pos_type, index_neg_type> location_handler_type;
+using index_neg_type = osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location>;
+using index_pos_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_pos_type, index_neg_type>;
 
 #include "check_basics_handler.hpp"
 #include "check_wkt_handler.hpp"
diff --git a/test/data-tests/testdata-multipolygon.cpp b/test/data-tests/testdata-multipolygon.cpp
index 6a863c8..6d0328c 100644
--- a/test/data-tests/testdata-multipolygon.cpp
+++ b/test/data-tests/testdata-multipolygon.cpp
@@ -1,7 +1,9 @@
 
+#include <cstring>
 #include <iostream>
 #include <fstream>
 #include <map>
+#include <string>
 
 #include <gdalcpp.hpp>
 
@@ -17,21 +19,20 @@
 #include <osmium/io/xml_input.hpp>
 #include <osmium/visitor.hpp>
 
-typedef osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location> index_type;
-
-typedef osmium::handler::NodeLocationsForWays<index_type> location_handler_type;
+using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
 
 struct less_charptr {
 
-    bool operator()(const char* a, const char* b) const {
+    bool operator()(const char* a, const char* b) const noexcept {
         return std::strcmp(a, b) < 0;
     }
 
 }; // less_charptr
 
-typedef std::map<const char*, const char*, less_charptr> tagmap_type;
+using tagmap_type = std::map<const char*, const char*, less_charptr>;
 
-inline tagmap_type create_map(const osmium::TagList& taglist) {
+tagmap_type create_map(const osmium::TagList& taglist) {
     tagmap_type map;
 
     for (auto& tag : taglist) {
@@ -52,7 +53,7 @@ class TestHandler : public osmium::handler::Handler {
 
     std::ofstream m_out;
 
-    bool m_first_out {true};
+    bool m_first_out{true};
 
 public:
 
@@ -77,7 +78,7 @@ public:
     }
 
     void node(const osmium::Node& node) {
-        gdalcpp::Feature feature(m_layer_point, m_ogr_factory.create_point(node));
+        gdalcpp::Feature feature{m_layer_point, m_ogr_factory.create_point(node)};
         feature.set_field("id", static_cast<double>(node.id()));
         feature.set_field("type", node.tags().get_value_by_key("type"));
         feature.add_to_layer();
@@ -85,11 +86,11 @@ public:
 
     void way(const osmium::Way& way) {
         try {
-            gdalcpp::Feature feature(m_layer_lines, m_ogr_factory.create_linestring(way));
+            gdalcpp::Feature feature{m_layer_lines, m_ogr_factory.create_linestring(way)};
             feature.set_field("id", static_cast<double>(way.id()));
             feature.set_field("type", way.tags().get_value_by_key("type"));
             feature.add_to_layer();
-        } catch (osmium::geometry_error&) {
+        } catch (const osmium::geometry_error&) {
             std::cerr << "Ignoring illegal geometry for way " << way.id() << ".\n";
         }
     }
@@ -103,10 +104,10 @@ public:
         }
         m_out << "{\n  \"test_id\": " << (area.orig_id() / 1000) << ",\n  \"area_id\": " << area.id() << ",\n  \"from_id\": " << area.orig_id() << ",\n  \"from_type\": \"" << (area.from_way() ? "way" : "relation") << "\",\n  \"wkt\": \"";
         try {
-            std::string wkt = m_wkt_factory.create_multipolygon(area);
+            const std::string wkt = m_wkt_factory.create_multipolygon(area);
             m_out << wkt << "\",\n  \"tags\": {";
 
-            auto tagmap = create_map(area.tags());
+            const auto tagmap = create_map(area.tags());
             bool first = true;
             for (auto& tag : tagmap) {
                 if (first) {
@@ -117,11 +118,11 @@ public:
                 m_out << '"' << tag.first << "\": \"" << tag.second << '"';
             }
             m_out << "}\n}";
-        } catch (osmium::geometry_error&) {
+        } catch (const osmium::geometry_error&) {
             m_out << "INVALID\"\n}";
         }
         try {
-            gdalcpp::Feature feature(m_layer_mpoly, m_ogr_factory.create_multipolygon(area));
+            gdalcpp::Feature feature{m_layer_mpoly, m_ogr_factory.create_multipolygon(area)};
             feature.set_field("id", static_cast<double>(area.orig_id()));
 
             std::string from_type;
@@ -132,7 +133,7 @@ public:
             }
             feature.set_field("from_type", from_type.c_str());
             feature.add_to_layer();
-        } catch (osmium::geometry_error&) {
+        } catch (const osmium::geometry_error&) {
             std::cerr << "Ignoring illegal geometry for area " << area.id() << " created from " << (area.from_way() ? "way" : "relation") << " with id=" << area.orig_id() << ".\n";
         }
     }
@@ -144,37 +145,38 @@ public:
 int main(int argc, char* argv[]) {
     if (argc != 2) {
         std::cerr << "Usage: " << argv[0] << " INFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    std::string output_format("SQLite");
-    std::string input_filename(argv[1]);
-    std::string output_filename("multipolygon.db");
+    const std::string output_format{"SQLite"};
+    const std::string input_filename{argv[1]};
+    const std::string output_filename{"multipolygon.db"};
 
     CPLSetConfigOption("OGR_SQLITE_SYNCHRONOUS", "FALSE");
-    gdalcpp::Dataset dataset{output_format, output_filename, gdalcpp::SRS{}, { "SPATIALITE=TRUE" }};
+    gdalcpp::Dataset dataset{output_format, output_filename, gdalcpp::SRS{}, {"SPATIALITE=TRUE"}};
 
-    osmium::area::ProblemReporterOGR problem_reporter(dataset);
-    osmium::area::Assembler::config_type assembler_config(&problem_reporter);
+    osmium::area::ProblemReporterOGR problem_reporter{dataset};
+    osmium::area::Assembler::config_type assembler_config;
+    assembler_config.problem_reporter = &problem_reporter;
     assembler_config.check_roles = true;
     assembler_config.create_empty_areas = true;
     assembler_config.debug_level = 2;
-    osmium::area::MultipolygonCollector<osmium::area::Assembler> collector(assembler_config);
+    osmium::area::MultipolygonCollector<osmium::area::Assembler> collector{assembler_config};
 
     std::cerr << "Pass 1...\n";
-    osmium::io::Reader reader1(input_filename);
+    osmium::io::Reader reader1{input_filename};
     collector.read_relations(reader1);
     reader1.close();
     std::cerr << "Pass 1 done\n";
 
     index_type index;
-    location_handler_type location_handler(index);
+    location_handler_type location_handler{index};
     location_handler.ignore_errors();
 
-    TestHandler test_handler(dataset);
+    TestHandler test_handler{dataset};
 
     std::cerr << "Pass 2...\n";
-    osmium::io::Reader reader2(input_filename);
+    osmium::io::Reader reader2{input_filename};
     osmium::apply(reader2, location_handler, test_handler, collector.handler([&test_handler](const osmium::memory::Buffer& area_buffer) {
         osmium::apply(area_buffer, test_handler);
     }));
diff --git a/test/data-tests/testdata-overview.cpp b/test/data-tests/testdata-overview.cpp
index 43d672d..4994f23 100644
--- a/test/data-tests/testdata-overview.cpp
+++ b/test/data-tests/testdata-overview.cpp
@@ -1,6 +1,7 @@
 /* The code in this file is released into the Public Domain. */
 
 #include <iostream>
+#include <string>
 
 #include <gdalcpp.hpp>
 
@@ -12,8 +13,8 @@
 #include <osmium/io/xml_input.hpp>
 #include <osmium/visitor.hpp>
 
-typedef osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location> index_type;
-typedef osmium::handler::NodeLocationsForWays<index_type> location_handler_type;
+using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
 
 class TestOverviewHandler : public osmium::handler::Handler {
 
@@ -64,7 +65,7 @@ public:
             }
 
             feature.add_to_layer();
-        } catch (osmium::geometry_error&) {
+        } catch (const osmium::geometry_error&) {
             std::cerr << "Ignoring illegal geometry for way " << way.id() << ".\n";
         }
     }
@@ -76,24 +77,24 @@ public:
 int main(int argc, char* argv[]) {
     if (argc != 2) {
         std::cerr << "Usage: " << argv[0] << " INFILE\n";
-        exit(1);
+        std::exit(1);
     }
 
-    std::string output_format("SQLite");
-    std::string input_filename(argv[1]);
-    std::string output_filename("testdata-overview.db");
+    const std::string output_format{"SQLite"};
+    const std::string input_filename{argv[1]};
+    const std::string output_filename{"testdata-overview.db"};
     ::unlink(output_filename.c_str());
 
     CPLSetConfigOption("OGR_SQLITE_SYNCHRONOUS", "FALSE");
-    gdalcpp::Dataset dataset(output_format, output_filename, gdalcpp::SRS{}, { "SPATIALITE=TRUE" });
+    gdalcpp::Dataset dataset{output_format, output_filename, gdalcpp::SRS{}, {"SPATIALITE=TRUE"}};
 
-    osmium::io::Reader reader(input_filename);
+    osmium::io::Reader reader{input_filename};
 
     index_type index;
-    location_handler_type location_handler(index);
+    location_handler_type location_handler{index};
     location_handler.ignore_errors();
 
-    TestOverviewHandler handler(dataset);
+    TestOverviewHandler handler{dataset};
 
     osmium::apply(reader, location_handler, handler);
     reader.close();
diff --git a/test/data-tests/testdata-testcases.cpp b/test/data-tests/testdata-testcases.cpp
index 0ea7fc8..d05c283 100644
--- a/test/data-tests/testdata-testcases.cpp
+++ b/test/data-tests/testdata-testcases.cpp
@@ -15,11 +15,9 @@ int main(int argc, char* argv[]) {
         std::cerr << "Running tests from '" << dirname << "' (from TESTCASES_DIR environment variable)\n";
     } else {
         std::cerr << "Please set TESTCASES_DIR environment variable.\n";
-        exit(1);
+        std::exit(1);
     }
 
-    int result = Catch::Session().run(argc, argv);
-
-    return result;
+    return Catch::Session().run(argc, argv);
 }
 
diff --git a/test/data-tests/testdata-xml.cpp b/test/data-tests/testdata-xml.cpp
index 01cee29..0d2739a 100644
--- a/test/data-tests/testdata-xml.cpp
+++ b/test/data-tests/testdata-xml.cpp
@@ -5,7 +5,9 @@
 
 #include <cassert>
 #include <cstdlib>
+#include <future>
 #include <iostream>
+#include <iterator>
 #include <string>
 
 #include <osmium/io/detail/queue_util.hpp>
@@ -14,14 +16,14 @@
 #include <osmium/visitor.hpp>
 
 std::string S_(const char* s) {
-    return std::string(s);
+    return std::string{s};
 }
 
 std::string filename(const char* test_id, const char* suffix = "osm") {
     const char* testdir = getenv("TESTDIR");
     if (!testdir) {
         std::cerr << "You have to set TESTDIR environment variable before running testdata-xml\n";
-        exit(2);
+        std::exit(2);
     }
 
     std::string f;
@@ -47,11 +49,11 @@ struct header_buffer_type {
 // file contents fit into small buffers.
 
 std::string read_file(const char* test_id) {
-    int fd = osmium::io::detail::open_for_reading(filename(test_id));
+    const int fd = osmium::io::detail::open_for_reading(filename(test_id));
     assert(fd >= 0);
 
     std::string input(10000, '\0');
-    auto n = ::read(fd, reinterpret_cast<unsigned char*>(const_cast<char*>(input.data())), 10000);
+    const auto n = ::read(fd, reinterpret_cast<unsigned char*>(const_cast<char*>(input.data())), 10000);
     assert(n >= 0);
     input.resize(static_cast<std::string::size_type>(n));
 
@@ -61,10 +63,10 @@ std::string read_file(const char* test_id) {
 }
 
 std::string read_gz_file(const char* test_id, const char* suffix) {
-    int fd = osmium::io::detail::open_for_reading(filename(test_id, suffix));
+    const int fd = osmium::io::detail::open_for_reading(filename(test_id, suffix));
     assert(fd >= 0);
 
-    osmium::io::GzipDecompressor gzip_decompressor(fd);
+    osmium::io::GzipDecompressor gzip_decompressor{fd};
     std::string input = gzip_decompressor.read();
     gzip_decompressor.close();
 
@@ -81,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::osm_entity_bits::all};
     parser.parse();
 
     header_buffer_type result;
@@ -117,9 +119,9 @@ TEST_CASE("Reading OSM XML 100") {
     }
 
     SECTION("Using Reader") {
-        osmium::io::Reader reader(filename("100-correct_but_no_data"));
+        osmium::io::Reader reader{filename("100-correct_but_no_data")};
 
-        osmium::io::Header header = reader.header();
+        const osmium::io::Header header{reader.header()};
         REQUIRE(header.get("generator") == "testdata");
 
         osmium::memory::Buffer buffer = reader.read();
@@ -129,9 +131,9 @@ TEST_CASE("Reading OSM XML 100") {
     }
 
     SECTION("Using Reader asking for header only") {
-        osmium::io::Reader reader(filename("100-correct_but_no_data"), osmium::osm_entity_bits::nothing);
+        osmium::io::Reader reader{filename("100-correct_but_no_data"), osmium::osm_entity_bits::nothing};
 
-        osmium::io::Header header = reader.header();
+        const osmium::io::Header header{reader.header()};
         REQUIRE(header.get("generator") == "testdata");
         reader.close();
     }
@@ -146,15 +148,15 @@ TEST_CASE("Reading OSM XML 101") {
         REQUIRE_THROWS_AS(read_xml("101-missing_version"), osmium::format_version_error);
         try {
             read_xml("101-missing_version");
-        } catch (osmium::format_version_error& e) {
+        } catch (const osmium::format_version_error& e) {
             REQUIRE(e.version.empty());
         }
     }
 
     SECTION("Using Reader") {
         REQUIRE_THROWS_AS({
-            osmium::io::Reader reader(filename("101-missing_version"));
-            osmium::io::Header header = reader.header();
+            osmium::io::Reader reader{filename("101-missing_version")};
+            const osmium::io::Header header{reader.header()};
             osmium::memory::Buffer buffer = reader.read();
             reader.close();
         }, osmium::format_version_error);
@@ -170,16 +172,16 @@ TEST_CASE("Reading OSM XML 102") {
         REQUIRE_THROWS_AS(read_xml("102-wrong_version"), osmium::format_version_error);
         try {
             read_xml("102-wrong_version");
-        } catch (osmium::format_version_error& e) {
+        } catch (const osmium::format_version_error& e) {
             REQUIRE(e.version == "0.1");
         }
     }
 
     SECTION("Using Reader") {
         REQUIRE_THROWS_AS({
-            osmium::io::Reader reader(filename("102-wrong_version"));
+            osmium::io::Reader reader{filename("102-wrong_version")};
 
-            osmium::io::Header header = reader.header();
+            const osmium::io::Header header{reader.header()};
             osmium::memory::Buffer buffer = reader.read();
             reader.close();
         }, osmium::format_version_error);
@@ -195,15 +197,15 @@ TEST_CASE("Reading OSM XML 103") {
         REQUIRE_THROWS_AS(read_xml("103-old_version"), osmium::format_version_error);
         try {
             read_xml("103-old_version");
-        } catch (osmium::format_version_error& e) {
+        } catch (const osmium::format_version_error& e) {
             REQUIRE(e.version == "0.5");
         }
     }
 
     SECTION("Using Reader") {
         REQUIRE_THROWS_AS({
-            osmium::io::Reader reader(filename("103-old_version"));
-            osmium::io::Header header = reader.header();
+            osmium::io::Reader reader{filename("103-old_version")};
+            const osmium::io::Header header{reader.header()};
             osmium::memory::Buffer buffer = reader.read();
             reader.close();
         }, osmium::format_version_error);
@@ -219,7 +221,7 @@ TEST_CASE("Reading OSM XML 104") {
         REQUIRE_THROWS_AS(read_xml("104-empty_file"), osmium::xml_error);
         try {
             read_xml("104-empty_file");
-        } catch (osmium::xml_error& e) {
+        } catch (const osmium::xml_error& e) {
             REQUIRE(e.line == 1);
             REQUIRE(e.column == 0);
         }
@@ -227,8 +229,8 @@ TEST_CASE("Reading OSM XML 104") {
 
     SECTION("Using Reader") {
         REQUIRE_THROWS_AS({
-            osmium::io::Reader reader(filename("104-empty_file"));
-            osmium::io::Header header = reader.header();
+            osmium::io::Reader reader{filename("104-empty_file")};
+            const osmium::io::Header header{reader.header()};
             osmium::memory::Buffer buffer = reader.read();
             reader.close();
         }, osmium::xml_error);
@@ -245,8 +247,8 @@ TEST_CASE("Reading OSM XML 105") {
 
     SECTION("Using Reader") {
         REQUIRE_THROWS_AS({
-            osmium::io::Reader reader(filename("105-incomplete_xml_file"));
-            osmium::io::Header header = reader.header();
+            osmium::io::Reader reader{filename("105-incomplete_xml_file")};
+            const osmium::io::Header header{reader.header()};
             osmium::memory::Buffer buffer = reader.read();
             reader.close();
         }, osmium::xml_error);
@@ -270,9 +272,9 @@ TEST_CASE("Reading OSM XML 120") {
     }
 
     SECTION("Using Reader") {
-        osmium::io::Reader reader(filename("120-correct_gzip_file_without_data", "osm.gz"));
+        osmium::io::Reader reader{filename("120-correct_gzip_file_without_data", "osm.gz")};
 
-        osmium::io::Header header = reader.header();
+        const osmium::io::Header header{reader.header()};
         REQUIRE(header.get("generator") == "testdata");
 
         osmium::memory::Buffer buffer = reader.read();
@@ -296,8 +298,8 @@ TEST_CASE("Reading OSM XML 121") {
     SECTION("Using Reader") {
         // can throw osmium::gzip_error or osmium::xml_error
         REQUIRE_THROWS({
-            osmium::io::Reader reader(filename("121-truncated_gzip_file", "osm.gz"));
-            osmium::io::Header header = reader.header();
+            osmium::io::Reader reader{filename("121-truncated_gzip_file", "osm.gz")};
+            const osmium::io::Header header{reader.header()};
             osmium::memory::Buffer buffer = reader.read();
             reader.close();
         });
@@ -317,8 +319,8 @@ TEST_CASE("Reading OSM XML 122") {
 
     SECTION("Using Reader") {
         REQUIRE_THROWS_AS({
-            osmium::io::Reader reader(filename("122-no_osm_element"));
-            osmium::io::Header header = reader.header();
+            osmium::io::Reader reader{filename("122-no_osm_element")};
+            const osmium::io::Header header{reader.header()};
             osmium::memory::Buffer buffer = reader.read();
             reader.close();
         }, osmium::xml_error);
@@ -331,7 +333,7 @@ TEST_CASE("Reading OSM XML 122") {
 TEST_CASE("Reading OSM XML 140") {
 
     SECTION("Using Reader") {
-        osmium::io::Reader reader(filename("140-unicode"));
+        osmium::io::Reader reader{filename("140-unicode")};
         osmium::memory::Buffer buffer = reader.read();
         reader.close();
 
@@ -343,7 +345,7 @@ TEST_CASE("Reading OSM XML 140") {
 
             const char* uc = t["unicode_char"];
 
-            auto len = atoi(t["unicode_utf8_length"]);
+            const auto len = atoi(t["unicode_utf8_length"]);
             REQUIRE(len == strlen(uc));
 
             REQUIRE(S_(uc) == t["unicode_xml"]);
@@ -382,7 +384,7 @@ TEST_CASE("Reading OSM XML 140") {
 TEST_CASE("Reading OSM XML 141") {
 
     SECTION("Using Reader") {
-        osmium::io::Reader reader(filename("141-entities"));
+        osmium::io::Reader reader{filename("141-entities")};
         osmium::memory::Buffer buffer = reader.read();
         reader.close();
         REQUIRE(buffer.committed() > 0);
@@ -406,7 +408,7 @@ TEST_CASE("Reading OSM XML 141") {
 TEST_CASE("Reading OSM XML 142") {
 
     SECTION("Using Reader to read nodes") {
-        osmium::io::Reader reader(filename("142-whitespace"));
+        osmium::io::Reader reader{filename("142-whitespace")};
         osmium::memory::Buffer buffer = reader.read();
         reader.close();
 
@@ -451,7 +453,7 @@ TEST_CASE("Reading OSM XML 142") {
     }
 
     SECTION("Using Reader to read relation") {
-        osmium::io::Reader reader(filename("142-whitespace"));
+        osmium::io::Reader reader{filename("142-whitespace")};
         osmium::memory::Buffer buffer = reader.read();
         reader.close();
 
@@ -505,9 +507,9 @@ TEST_CASE("Reading OSM XML 200") {
     }
 
     SECTION("Using Reader") {
-        osmium::io::Reader reader(filename("200-nodes"));
+        osmium::io::Reader reader{filename("200-nodes")};
 
-        osmium::io::Header header = reader.header();
+        const osmium::io::Header header{reader.header()};
         REQUIRE(header.get("generator") == "testdata");
 
         osmium::memory::Buffer buffer = reader.read();
@@ -519,9 +521,9 @@ TEST_CASE("Reading OSM XML 200") {
     }
 
     SECTION("Using Reader asking for nodes") {
-        osmium::io::Reader reader(filename("200-nodes"), osmium::osm_entity_bits::node);
+        osmium::io::Reader reader{filename("200-nodes"), osmium::osm_entity_bits::node};
 
-        osmium::io::Header header = reader.header();
+        const osmium::io::Header header{reader.header()};
         REQUIRE(header.get("generator") == "testdata");
 
         osmium::memory::Buffer buffer = reader.read();
@@ -533,9 +535,9 @@ TEST_CASE("Reading OSM XML 200") {
     }
 
     SECTION("Using Reader asking for header only") {
-        osmium::io::Reader reader(filename("200-nodes"), osmium::osm_entity_bits::nothing);
+        osmium::io::Reader reader{filename("200-nodes"), osmium::osm_entity_bits::nothing};
 
-        osmium::io::Header header = reader.header();
+        const osmium::io::Header header{reader.header()};
         REQUIRE(header.get("generator") == "testdata");
 
         REQUIRE_THROWS({
@@ -546,9 +548,9 @@ TEST_CASE("Reading OSM XML 200") {
     }
 
     SECTION("Using Reader asking for ways") {
-        osmium::io::Reader reader(filename("200-nodes"), osmium::osm_entity_bits::way);
+        osmium::io::Reader reader{filename("200-nodes"), osmium::osm_entity_bits::way};
 
-        osmium::io::Header header = reader.header();
+        const osmium::io::Header header{reader.header()};
         REQUIRE(header.get("generator") == "testdata");
 
         osmium::memory::Buffer buffer = reader.read();
diff --git a/test/t/basic/test_area.cpp b/test/t/basic/test_area.cpp
new file mode 100644
index 0000000..f2847dc
--- /dev/null
+++ b/test/t/basic/test_area.cpp
@@ -0,0 +1,78 @@
+#include "catch.hpp"
+
+#include <boost/crc.hpp>
+
+#include <osmium/builder/attr.hpp>
+#include <osmium/osm/crc.hpp>
+#include <osmium/osm/area.hpp>
+
+using namespace osmium::builder::attr;
+
+TEST_CASE("Build area") {
+    osmium::memory::Buffer buffer(10000);
+
+    osmium::builder::add_area(buffer,
+        _id(17),
+        _version(3),
+        _visible(),
+        _cid(333),
+        _uid(21),
+        _timestamp(time_t(123)),
+        _user("foo"),
+        _tag("landuse", "forest"),
+        _tag("name", "Sherwood Forest"),
+        _outer_ring({
+            {1, {3.2, 4.2}},
+            {2, {3.5, 4.7}},
+            {3, {3.6, 4.9}},
+            {1, {3.2, 4.2}}
+        }),
+        _inner_ring({
+            {5, {1.0, 1.0}},
+            {6, {8.0, 1.0}},
+            {7, {8.0, 8.0}},
+            {8, {1.0, 8.0}},
+            {5, {1.0, 1.0}}
+        })
+    );
+
+    const osmium::Area& area = buffer.get<osmium::Area>(0);
+
+    REQUIRE(17 == area.id());
+    REQUIRE(3 == area.version());
+    REQUIRE(true == area.visible());
+    REQUIRE(333 == area.changeset());
+    REQUIRE(21 == area.uid());
+    REQUIRE(std::string("foo") == area.user());
+    REQUIRE(123 == uint32_t(area.timestamp()));
+    REQUIRE(2 == area.tags().size());
+
+    int inner = 0;
+    int outer = 0;
+    for (const auto& subitem : area) {
+        switch (subitem.type()) {
+            case osmium::item_type::outer_ring: {
+                    const auto& ring = static_cast<const osmium::OuterRing&>(subitem);
+                    REQUIRE(ring.size() == 4);
+                    ++outer;
+                }
+                break;
+            case osmium::item_type::inner_ring: {
+                    const auto& ring = static_cast<const osmium::OuterRing&>(subitem);
+                    REQUIRE(ring.size() == 5);
+                    ++inner;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    REQUIRE(outer == 1);
+    REQUIRE(inner == 1);
+
+    osmium::CRC<boost::crc_32_type> crc32;
+    crc32.update(area);
+    REQUIRE(crc32().checksum() == 0x2b2b7fa0);
+}
+
diff --git a/test/t/basic/test_location.cpp b/test/t/basic/test_location.cpp
index e6c1b53..dc5b378 100644
--- a/test/t/basic/test_location.cpp
+++ b/test/t/basic/test_location.cpp
@@ -166,13 +166,29 @@ TEST_CASE("Location hash") {
     }
 }
 
-#define C(s, v) REQUIRE(osmium::detail::string_to_location_coordinate(s)     == v); \
-                REQUIRE(osmium::detail::string_to_location_coordinate("-" s) == -v); \
-                REQUIRE(atof(s)     == Approx( v / 10000000.0)); \
-                REQUIRE(atof("-" s) == Approx(-v / 10000000.0));
-
-#define F(s) REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(s),     osmium::invalid_location); \
-             REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate("-" s), osmium::invalid_location);
+#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); \
+             }
 
 TEST_CASE("Parsing coordinates from strings") {
     F("x");
@@ -181,11 +197,12 @@ TEST_CASE("Parsing coordinates from strings") {
     F("");
     F(" ");
     F(" 123");
-    F("123 ");
-    F("123x");
-    F("1.2x");
 
-    C("0", 0);
+    CR("123 ", 1230000000, " ");
+    CR("123x", 1230000000, "x");
+    CR("1.2x",   12000000, "x");
+
+    C("0",              0);
 
     C("1",       10000000);
     C("2",       20000000);
@@ -201,10 +218,11 @@ TEST_CASE("Parsing coordinates from strings") {
     C("00",             0);
     C("01",      10000000);
     C("001",     10000000);
+    C("0001",    10000000);
 
-    F("0001");
     F("1234");
     F("1234.");
+    F("12345678901234567890");
 
     C("0.",             0);
     C("0.0",            0);
@@ -258,15 +276,32 @@ TEST_CASE("Parsing coordinates from strings") {
     C("1.4e-7",           1);
     C("1.5e-7",           2);
     C("1.9e-7",           2);
+    C("0.5e-7",           1);
+    C("0.1e-7",           0);
+    C("0.0e-7",           0);
+    C("1.9e-8",           0);
+    C("1.9e-9",           0);
+    C("1.9e-10",          0);
 
     F("e");
     F(" e");
     F(" 1.1e2");
-    F("1.1e2 ");
-    F("1.1e2x");
+    F("1.0e3");
+    F("5e4");
+    F("5.0e2");
+    F("3e2");
+    F("1e");
+    F("0.5e");
+    F("1e10");
+
+    CR("1e2 ",   1000000000, " ");
+    CR("1.1e2 ", 1100000000, " ");
+    CR("1.1e2x", 1100000000, "x");
+    CR("1.1e2:", 1100000000, ":");
 }
 
 #undef C
+#undef CR
 #undef F
 
 #define CW(v, s) buffer.clear(); \
@@ -305,3 +340,33 @@ TEST_CASE("Writing coordinates into string") {
 
 #undef CW
 
+TEST_CASE("set lon/lat from string") {
+    osmium::Location loc;
+    loc.set_lon("1.2");
+    loc.set_lat("3.4");
+    REQUIRE(loc.lon() == Approx(1.2));
+    REQUIRE(loc.lat() == Approx(3.4));
+}
+
+TEST_CASE("set lon/lat from string with trailing characters") {
+    osmium::Location loc;
+    REQUIRE_THROWS_AS({
+        loc.set_lon("1.2x");
+    }, osmium::invalid_location);
+    REQUIRE_THROWS_AS({
+        loc.set_lat("3.4e1 ");
+    }, osmium::invalid_location);
+}
+
+TEST_CASE("set lon/lat from string with trailing characters using partial") {
+    osmium::Location loc;
+    const char* x = "1.2x";
+    const char* y = "3.4 ";
+    loc.set_lon_partial(&x);
+    loc.set_lat_partial(&y);
+    REQUIRE(loc.lon() == Approx(1.2));
+    REQUIRE(loc.lat() == Approx(3.4));
+    REQUIRE(*x == 'x');
+    REQUIRE(*y == ' ');
+}
+
diff --git a/test/t/basic/test_node.cpp b/test/t/basic/test_node.cpp
index b7f4c6c..e5dbe8a 100644
--- a/test/t/basic/test_node.cpp
+++ b/test/t/basic/test_node.cpp
@@ -9,7 +9,7 @@
 using namespace osmium::builder::attr;
 
 TEST_CASE("Build node") {
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
 
     osmium::builder::add_node(buffer,
         _id(17),
@@ -36,7 +36,7 @@ TEST_CASE("Build node") {
     REQUIRE(false == node.deleted());
     REQUIRE(333 == node.changeset());
     REQUIRE(21 == node.uid());
-    REQUIRE(std::string("foo") == node.user());
+    REQUIRE(std::string{"foo"} == node.user());
     REQUIRE(123 == uint32_t(node.timestamp()));
     REQUIRE(osmium::Location(3.5, 4.7) == node.location());
     REQUIRE(2 == node.tags().size());
@@ -51,7 +51,7 @@ TEST_CASE("Build node") {
 }
 
 TEST_CASE("default values for node attributes") {
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
 
     osmium::builder::add_node(buffer, _id(0));
 
@@ -62,22 +62,23 @@ TEST_CASE("default values for node attributes") {
     REQUIRE(true == node.visible());
     REQUIRE(0 == node.changeset());
     REQUIRE(0 == node.uid());
-    REQUIRE(std::string("") == node.user());
+    REQUIRE(std::string{} == node.user());
     REQUIRE(0 == uint32_t(node.timestamp()));
     REQUIRE(osmium::Location() == node.location());
     REQUIRE(0 == node.tags().size());
 }
 
 TEST_CASE("set node attributes from strings") {
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
 
     osmium::builder::add_node(buffer, _id(0));
 
     osmium::Node& node = buffer.get<osmium::Node>(0);
     node.set_id("-17")
         .set_version("3")
-        .set_visible(true)
+        .set_visible("true")
         .set_changeset("333")
+        .set_timestamp("2014-03-17T16:23:08Z")
         .set_uid("21");
 
     REQUIRE(-17l == node.id());
@@ -85,11 +86,52 @@ TEST_CASE("set node attributes from strings") {
     REQUIRE(3 == node.version());
     REQUIRE(true == node.visible());
     REQUIRE(333 == node.changeset());
+    REQUIRE(std::string{"2014-03-17T16:23:08Z"} == node.timestamp().to_iso());
     REQUIRE(21 == node.uid());
 }
 
+TEST_CASE("set node attributes from strings using set_attribute()") {
+    osmium::memory::Buffer buffer{10000};
+
+    osmium::builder::add_node(buffer, _id(0));
+
+    osmium::Node& node = buffer.get<osmium::Node>(0);
+    node.set_attribute("id", "-17")
+        .set_attribute("version", "3")
+        .set_attribute("visible", "true")
+        .set_attribute("changeset", "333")
+        .set_attribute("timestamp", "2014-03-17T16:23:08Z")
+        .set_attribute("uid", "21");
+
+    REQUIRE(-17l == node.id());
+    REQUIRE(17ul == node.positive_id());
+    REQUIRE(3 == node.version());
+    REQUIRE(true == node.visible());
+    REQUIRE(333 == node.changeset());
+    REQUIRE(std::string{"2014-03-17T16:23:08Z"} == node.timestamp().to_iso());
+    REQUIRE(21 == node.uid());
+}
+
+TEST_CASE("Setting attributes from bad data on strings should fail") {
+    osmium::memory::Buffer buffer{10000};
+
+    osmium::builder::add_node(buffer, _id(0));
+
+    osmium::Node& node = buffer.get<osmium::Node>(0);
+    REQUIRE_THROWS(node.set_id("bar"));
+    REQUIRE_THROWS(node.set_id("123x"));
+    REQUIRE_THROWS(node.set_version("123x"));
+    REQUIRE_THROWS(node.set_visible("foo"));
+    REQUIRE_THROWS(node.set_changeset("123x"));
+    REQUIRE_THROWS(node.set_changeset("NULL"));
+    REQUIRE_THROWS(node.set_timestamp("2014-03-17T16:23:08Zx"));
+    REQUIRE_THROWS(node.set_timestamp("2014-03-17T16:23:99Z"));
+    REQUIRE_THROWS(node.set_uid("123x"));
+    REQUIRE_THROWS(node.set_uid("anonymous"));
+}
+
 TEST_CASE("set large id") {
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
 
     int64_t id = 3000000000l;
     osmium::builder::add_node(buffer, _id(id));
@@ -104,7 +146,7 @@ TEST_CASE("set large id") {
 }
 
 TEST_CASE("set tags on node") {
-    osmium::memory::Buffer buffer(10000);
+    osmium::memory::Buffer buffer{10000};
 
     osmium::builder::add_node(buffer,
         _user("foo"),
@@ -114,11 +156,11 @@ TEST_CASE("set tags on node") {
 
     const osmium::Node& node = buffer.get<osmium::Node>(0);
     REQUIRE(nullptr == node.tags().get_value_by_key("fail"));
-    REQUIRE(std::string("pub") == node.tags().get_value_by_key("amenity"));
-    REQUIRE(std::string("pub") == node.get_value_by_key("amenity"));
+    REQUIRE(std::string{"pub"} == node.tags().get_value_by_key("amenity"));
+    REQUIRE(std::string{"pub"} == node.get_value_by_key("amenity"));
 
-    REQUIRE(std::string("default") == node.tags().get_value_by_key("fail", "default"));
-    REQUIRE(std::string("pub") == node.tags().get_value_by_key("amenity", "default"));
-    REQUIRE(std::string("pub") == node.get_value_by_key("amenity", "default"));
+    REQUIRE(std::string{"default"} == node.tags().get_value_by_key("fail", "default"));
+    REQUIRE(std::string{"pub"} == node.tags().get_value_by_key("amenity", "default"));
+    REQUIRE(std::string{"pub"} == node.get_value_by_key("amenity", "default"));
 }
 
diff --git a/test/t/basic/test_timestamp.cpp b/test/t/basic/test_timestamp.cpp
index f80ffcf..561a705 100644
--- a/test/t/basic/test_timestamp.cpp
+++ b/test/t/basic/test_timestamp.cpp
@@ -75,3 +75,47 @@ TEST_CASE("Timestamp") {
     }
 
 }
+
+TEST_CASE("Valid timestamps") {
+
+    std::vector<std::string> test_cases = {
+        "1970-01-01T00:00:01Z",
+        "2000-01-01T00:00:00Z",
+        "2006-12-31T23:59:59Z",
+        "2030-12-31T23:59:59Z",
+        "2016-02-28T23:59:59Z",
+        "2016-03-31T23:59:59Z"
+    };
+
+    for (const auto& tc : test_cases) {
+        osmium::Timestamp t{tc};
+        REQUIRE(tc == t.to_iso());
+    }
+
+}
+
+TEST_CASE("Invalid timestamps") {
+    REQUIRE_THROWS_AS(osmium::Timestamp{""}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"x"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"xxxxxxxxxxxxxxxxxxxx"}, std::invalid_argument);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01x00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:00x"}, std::invalid_argument);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000x01-01T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01x01T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00x00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00x00Z"}, std::invalid_argument);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"0000-00-01T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-00-01T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-00T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T24:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:60:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:61Z"}, std::invalid_argument);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-32T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-02-30T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-32T00:00:00Z"}, std::invalid_argument);
+}
+
diff --git a/test/t/geom/test_tile.cpp b/test/t/geom/test_tile.cpp
index e80cb96..5454fed 100644
--- a/test/t/geom/test_tile.cpp
+++ b/test/t/geom/test_tile.cpp
@@ -8,86 +8,96 @@
 
 #include "test_tile_data.hpp"
 
-TEST_CASE("Tile") {
+TEST_CASE("Tile from x0.0 y0.0 at zoom 0") {
+    osmium::Location l{0.0, 0.0};
 
-    SECTION("x0.0 y0.0 zoom 0") {
-        osmium::Location l(0.0, 0.0);
+    osmium::geom::Tile t{0, l};
 
-        osmium::geom::Tile t(0, l);
+    REQUIRE(t.x == 0);
+    REQUIRE(t.y == 0);
+    REQUIRE(t.z == 0);
+    REQUIRE(t.valid());
+}
 
-        REQUIRE(t.x == 0);
-        REQUIRE(t.y == 0);
-        REQUIRE(t.z == 0);
-    }
+TEST_CASE("Tile from x180.0 y90.0 at zoom 0") {
+    osmium::Location l{180.0, 90.0};
 
-    SECTION("x180.0 y90.0 zoom 0") {
-        osmium::Location l(180.0, 90.0);
+    osmium::geom::Tile t{0, l};
 
-        osmium::geom::Tile t(0, l);
+    REQUIRE(t.x == 0);
+    REQUIRE(t.y == 0);
+    REQUIRE(t.z == 0);
+    REQUIRE(t.valid());
+}
 
-        REQUIRE(t.x == 0);
-        REQUIRE(t.y == 0);
-        REQUIRE(t.z == 0);
-    }
+TEST_CASE("Tile from x180.0 y90.0 at zoom 4") {
+    osmium::Location l{180.0, 90.0};
 
-    SECTION("x180.0 y90.0 zoom 4") {
-        osmium::Location l(180.0, 90.0);
+    osmium::geom::Tile t{4, l};
 
-        osmium::geom::Tile t(4, l);
+    REQUIRE(t.x == (1 << 4) - 1);
+    REQUIRE(t.y == 0);
+    REQUIRE(t.z == 4);
+    REQUIRE(t.valid());
+}
 
-        REQUIRE(t.x == (1 << 4) - 1);
-        REQUIRE(t.y == 0);
-        REQUIRE(t.z == 4);
-    }
+TEST_CASE("Tile from x0.0 y0.0 at zoom 4") {
+    osmium::Location l{0.0, 0.0};
 
-    SECTION("x0.0 y0.0 zoom 4") {
-        osmium::Location l(0.0, 0.0);
+    osmium::geom::Tile t{4, l};
 
-        osmium::geom::Tile t(4, l);
+    auto n = 1 << (4-1);
+    REQUIRE(t.x == n);
+    REQUIRE(t.y == n);
+    REQUIRE(t.z == 4);
+    REQUIRE(t.valid());
+}
 
-        auto n = 1 << (4-1);
-        REQUIRE(t.x == n);
-        REQUIRE(t.y == n);
-        REQUIRE(t.z == 4);
-    }
+TEST_CASE("Tile from max values at zoom 4") {
+    osmium::geom::Tile t{4u, 15u, 15u};
+    REQUIRE(t.valid());
+}
 
-    SECTION("equality") {
-        osmium::geom::Tile a(4, 3, 4);
-        osmium::geom::Tile b(4, 3, 4);
-        osmium::geom::Tile c(4, 4, 3);
-        REQUIRE(a == b);
-        REQUIRE(a != c);
-        REQUIRE(b != c);
-    }
+TEST_CASE("Tile from max values at zoom 30") {
+    osmium::geom::Tile t{30u, (1u<<30) - 1, (1u<<30) - 1};
+    REQUIRE(t.valid());
+}
 
-    SECTION("order") {
-        osmium::geom::Tile a(2, 3, 4);
-        osmium::geom::Tile b(4, 3, 4);
-        osmium::geom::Tile c(4, 4, 3);
-        osmium::geom::Tile d(4, 4, 2);
-        REQUIRE(a < b);
-        REQUIRE(a < c);
-        REQUIRE(b < c);
-        REQUIRE(d < c);
-    }
+TEST_CASE("Tile equality") {
+    osmium::geom::Tile a{4, 3, 4};
+    osmium::geom::Tile b{4, 3, 4};
+    osmium::geom::Tile c{4, 4, 3};
+    REQUIRE(a == b);
+    REQUIRE(a != c);
+    REQUIRE(b != c);
+}
 
-    SECTION("tilelist") {
-        std::istringstream input_data(s);
-        while (input_data) {
-            double lon, lat;
-            uint32_t x, y, zoom;
-            input_data >> lon;
-            input_data >> lat;
-            input_data >> x;
-            input_data >> y;
-            input_data >> zoom;
-
-            osmium::Location l(lon, lat);
-            osmium::geom::Tile t(zoom, l);
-            REQUIRE(t.x == x);
-            REQUIRE(t.y == y);
-        }
-    }
+TEST_CASE("Tile order") {
+    osmium::geom::Tile a{4, 3, 4};
+    osmium::geom::Tile b{6, 3, 4};
+    osmium::geom::Tile c{6, 4, 3};
+    osmium::geom::Tile d{6, 4, 2};
+    REQUIRE(a < b);
+    REQUIRE(a < c);
+    REQUIRE(b < c);
+    REQUIRE(d < c);
+}
 
+TEST_CASE("Check a random list of tiles") {
+    std::istringstream input_data(s);
+    while (input_data) {
+        double lon, lat;
+        uint32_t x, y, zoom;
+        input_data >> lon;
+        input_data >> lat;
+        input_data >> x;
+        input_data >> y;
+        input_data >> zoom;
+
+        osmium::Location l{lon, lat};
+        osmium::geom::Tile t{zoom, l};
+        REQUIRE(t.x == x);
+        REQUIRE(t.y == y);
+    }
 }
 
diff --git a/test/t/geom/test_wkb.cpp b/test/t/geom/test_wkb.cpp
index 12cd5b6..d4d9228 100644
--- a/test/t/geom/test_wkb.cpp
+++ b/test/t/geom/test_wkb.cpp
@@ -6,125 +6,130 @@
 
 #if __BYTE_ORDER == __LITTLE_ENDIAN
 
-TEST_CASE("WKB_Geometry_byte_order_dependent") {
+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);
+    SECTION("point") {
+        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
 
-    std::string wkb {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"01010000009A99999999990940CDCCCCCCCCCC1040"} == wkb);
-}
+        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);
+    SECTION("point in web mercator") {
+        osmium::geom::WKBFactory<osmium::geom::MercatorProjection> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
 
-    std::string wkb {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"010100000028706E7BF9BD1541B03E0D93E48F1C41"} == wkb);
-}
+        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);
+    SECTION("point in ewkb") {
+        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex);
 
-    std::string wkb {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"0101000020E61000009A99999999990940CDCCCCCCCCCC1040"} == wkb);
-}
+        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);
+    SECTION("point in ewkb in web mercator") {
+        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");
+    }
 
-    std::string wkb {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"0101000020110F000028706E7BF9BD1541B03E0D93E48F1C41"} == wkb);
 }
 
-SECTION("linestring") {
-    osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+TEST_CASE("WKB geometry factory (byte-order-dependant)") {
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_okay(buffer);
+    osmium::memory::Buffer buffer{10000};
 
-    {
-        std::string wkb {factory.create_linestring(wnl)};
-        REQUIRE(std::string{"0102000000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"} == wkb);
-    }
+    SECTION("linestring") {
+        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+        const auto& wnl = create_test_wnl_okay(buffer);
 
-    {
-        std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
-        REQUIRE(std::string{"010200000003000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040"} == wkb);
-    }
+        {
+            const std::string wkb{factory.create_linestring(wnl)};
+            REQUIRE(wkb == "0102000000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340");
+        }
 
-    {
-        std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
-        REQUIRE(std::string{"0102000000040000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"} == wkb);
-    }
+        {
+            const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
+            REQUIRE(wkb == "010200000003000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040");
+        }
+
+        {
+            const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+            REQUIRE(wkb == "0102000000040000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340");
+        }
 
-    {
-        std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
-        REQUIRE(std::string{"010200000004000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040"} == wkb);
+        {
+            const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+            REQUIRE(wkb == "010200000004000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040");
+        }
     }
-}
 
-SECTION("linestring_ewkb") {
-    osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex);
+    SECTION("linestring as ewkb") {
+        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex);
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_okay(buffer);
+        const auto& wnl = create_test_wnl_okay(buffer);
 
-    std::string ewkb {factory.create_linestring(wnl)};
-    REQUIRE(std::string{"0102000020E6100000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"} == ewkb);
-}
+        const std::string ewkb{factory.create_linestring(wnl)};
+        REQUIRE(ewkb == "0102000020E6100000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340");
+    }
 
-SECTION("linestring_with_two_same_locations") {
-    osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+    SECTION("linestring with two same locations") {
+        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_same_location(buffer);
+        const 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);
+        SECTION("unique forwards (default)") {
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
+        }
 
-    {
-        std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
-        REQUIRE(std::string{"0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240"} == wkb);
-    }
+        SECTION("unique backwards") {
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+        }
 
-    {
-        std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
-        REQUIRE(std::string{"0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240"} == wkb);
+        SECTION("all forwards") {
+            const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+            REQUIRE(wkb == "0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240");
+        }
+
+        SECTION("all backwards") {
+            const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+            REQUIRE(wkb == "0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240");
+        }
     }
-}
 
-SECTION("linestring_with_undefined_location") {
-    osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
+    SECTION("linestring with undefined location") {
+        osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_undefined_location(buffer);
+        const auto& wnl = create_test_wnl_undefined_location(buffer);
 
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
-}
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
+    }
 
 }
 
 #endif
 
-TEST_CASE("WKB_Geometry_byte_order_independent") {
+TEST_CASE("WKB geometry (byte-order-independent)") {
 
-SECTION("empty_point") {
     osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
 
-    REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
-}
-
-SECTION("empty_linestring") {
-    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);
+    }
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_empty(buffer);
+    SECTION("empty linestring") {
+        osmium::memory::Buffer buffer{10000};
+        const 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);
+    }
 
 }
 
diff --git a/test/t/geom/test_wkt.cpp b/test/t/geom/test_wkt.cpp
index 3767be3..f6913c4 100644
--- a/test/t/geom/test_wkt.cpp
+++ b/test/t/geom/test_wkt.cpp
@@ -6,146 +6,128 @@
 #include "area_helper.hpp"
 #include "wnl_helper.hpp"
 
-TEST_CASE("WKT_Geometry") {
+TEST_CASE("WKT geometry for point") {
 
-SECTION("point") {
     osmium::geom::WKTFactory<> factory;
 
-    std::string wkt {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"POINT(3.2 4.2)"} == wkt);
-}
-
-SECTION("point in ewkt") {
-    osmium::geom::WKTFactory<> factory(7, osmium::geom::wkt_type::ewkt);
-
-    std::string wkt {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"SRID=4326;POINT(3.2 4.2)"} == wkt);
-}
+    SECTION("point") {
+        const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
+        REQUIRE(wkt == "POINT(3.2 4.2)");
+    }
 
-SECTION("point in ewkt in web mercator") {
-    osmium::geom::WKTFactory<osmium::geom::MercatorProjection> factory(2, osmium::geom::wkt_type::ewkt);
+    SECTION("empty point") {
+        REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
+    }
 
-    std::string wkt {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(std::string{"SRID=3857;POINT(356222.37 467961.14)"} == wkt);
 }
 
-SECTION("empty_point") {
-    osmium::geom::WKTFactory<> factory;
+TEST_CASE("WKT geometry for point in ekwt") {
+    osmium::geom::WKTFactory<> factory(7, osmium::geom::wkt_type::ewkt);
 
-    REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
+    const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
+    REQUIRE(wkt == "SRID=4326;POINT(3.2 4.2)");
 }
 
-SECTION("linestring") {
-    osmium::geom::WKTFactory<> factory;
-
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_okay(buffer);
-
-    {
-        std::string wkt {factory.create_linestring(wnl)};
-        REQUIRE(std::string{"LINESTRING(3.2 4.2,3.5 4.7,3.6 4.9)"} == wkt);
-    }
-
-    {
-        std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
-        REQUIRE(std::string{"LINESTRING(3.6 4.9,3.5 4.7,3.2 4.2)"} == wkt);
-    }
-
-    {
-        std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
-        REQUIRE(std::string{"LINESTRING(3.2 4.2,3.5 4.7,3.5 4.7,3.6 4.9)"} == wkt);
-    }
+TEST_CASE("WKT geometry for point in ekwt in web mercator") {
+    osmium::geom::WKTFactory<osmium::geom::MercatorProjection> factory(2, osmium::geom::wkt_type::ewkt);
 
-    {
-        std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
-        REQUIRE(std::string{"LINESTRING(3.6 4.9,3.5 4.7,3.5 4.7,3.2 4.2)"} == wkt);
-    }
+    const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
+    REQUIRE(wkt == "SRID=3857;POINT(356222.37 467961.14)");
 }
 
-SECTION("empty_linestring") {
+TEST_CASE("WKT geometry factory") {
     osmium::geom::WKTFactory<> factory;
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_empty(buffer);
+    osmium::memory::Buffer buffer{10000};
 
-    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") {
+        const auto& wnl = create_test_wnl_okay(buffer);
 
-SECTION("linestring_with_two_same_locations") {
-    osmium::geom::WKTFactory<> factory;
+        SECTION("unique forwards (default)") {
+            const std::string wkt{factory.create_linestring(wnl)};
+            REQUIRE(wkt == "LINESTRING(3.2 4.2,3.5 4.7,3.6 4.9)");
+        }
 
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_same_location(buffer);
+        SECTION("unique backwards") {
+            const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
+            REQUIRE(wkt == "LINESTRING(3.6 4.9,3.5 4.7,3.2 4.2)");
+        }
 
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
+        SECTION("all forwards") {
+            const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+            REQUIRE(wkt == "LINESTRING(3.2 4.2,3.5 4.7,3.5 4.7,3.6 4.9)");
+        }
 
-    try {
-        factory.create_linestring(wnl);
-    } catch (osmium::geometry_error& e) {
-        REQUIRE(e.id() == 0);
-        REQUIRE(std::string(e.what()) == "need at least two points for linestring");
+        SECTION("all backwards") {
+            const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+            REQUIRE(wkt == "LINESTRING(3.6 4.9,3.5 4.7,3.5 4.7,3.2 4.2)");
+        }
     }
 
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+    SECTION("empty linestring") {
+        const auto& wnl = create_test_wnl_empty(buffer);
 
-    {
-        std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
-        REQUIRE(std::string{"LINESTRING(3.5 4.7,3.5 4.7)"} == wkt);
+        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);
     }
 
-    {
-        std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
-        REQUIRE(std::string{"LINESTRING(3.5 4.7,3.5 4.7)"} == wkt);
+    SECTION("linestring with two same locations") {
+        const auto& wnl = create_test_wnl_same_location(buffer);
+
+        SECTION("unique forwards") {
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
+
+            try {
+                factory.create_linestring(wnl);
+            } catch (const osmium::geometry_error& e) {
+                REQUIRE(e.id() == 0);
+                REQUIRE(std::string(e.what()) == "need at least two points for linestring");
+            }
+        }
+
+        SECTION("unique backwards") {
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+        }
+
+        SECTION("all forwards") {
+            const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
+            REQUIRE(wkt == "LINESTRING(3.5 4.7,3.5 4.7)");
+        }
+
+        SECTION("all backwards") {
+            const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
+            REQUIRE(wkt == "LINESTRING(3.5 4.7,3.5 4.7)");
+        }
     }
-}
 
-SECTION("linestring_with_undefined_location") {
-    osmium::geom::WKTFactory<> factory;
-
-    osmium::memory::Buffer buffer(10000);
-    auto &wnl = create_test_wnl_undefined_location(buffer);
-
-    REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
-}
+    SECTION("linestring with undefined location") {
+        const auto& wnl = create_test_wnl_undefined_location(buffer);
 
-SECTION("area_1outer_0inner") {
-    osmium::geom::WKTFactory<> factory;
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
+    }
 
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_1outer_0inner(buffer);
+    SECTION("area with one outer and no inner rings") {
+        const osmium::Area& area = create_test_area_1outer_0inner(buffer);
 
-    {
-        std::string wkt {factory.create_multipolygon(area)};
-        REQUIRE(std::string{"MULTIPOLYGON(((3.2 4.2,3.5 4.7,3.6 4.9,3.2 4.2)))"} == wkt);
+        const std::string wkt{factory.create_multipolygon(area)};
+        REQUIRE(wkt == "MULTIPOLYGON(((3.2 4.2,3.5 4.7,3.6 4.9,3.2 4.2)))");
     }
-}
-
-SECTION("area_1outer_1inner") {
-    osmium::geom::WKTFactory<> factory;
 
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_1outer_1inner(buffer);
+    SECTION("area with one outer and one inner ring") {
+        const osmium::Area& area = create_test_area_1outer_1inner(buffer);
 
-    {
-        std::string wkt {factory.create_multipolygon(area)};
-        REQUIRE(std::string{"MULTIPOLYGON(((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)))"} == wkt);
+        const std::string wkt{factory.create_multipolygon(area)};
+        REQUIRE(wkt == "MULTIPOLYGON(((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)))");
     }
-}
-
-SECTION("area_2outer_2inner") {
-    osmium::geom::WKTFactory<> factory;
 
-    osmium::memory::Buffer buffer(10000);
-    const osmium::Area& area = create_test_area_2outer_2inner(buffer);
+    SECTION("area with two outer and two inner rings") {
+        const osmium::Area& area = create_test_area_2outer_2inner(buffer);
 
-    {
-        std::string wkt {factory.create_multipolygon(area)};
-        REQUIRE(std::string{"MULTIPOLYGON(((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)))"} == wkt);
+        const std::string wkt{factory.create_multipolygon(area)};
+        REQUIRE(wkt == "MULTIPOLYGON(((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)))");
     }
-}
 
 }
 
diff --git a/test/t/index/test_id_to_location.cpp b/test/t/index/test_id_to_location.cpp
index 90cce3b..810ef3b 100644
--- a/test/t/index/test_id_to_location.cpp
+++ b/test/t/index/test_id_to_location.cpp
@@ -19,8 +19,8 @@ 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);
+    osmium::Location loc1{1.2, 4.5};
+    osmium::Location loc2{3.5, -7.2};
 
     REQUIRE_THROWS_AS(index.get(id1), osmium::not_found);
 
@@ -39,8 +39,8 @@ 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);
+    osmium::Location loc1{1.2, 4.5};
+    osmium::Location loc2{3.5, -7.2};
 
     index.set(id1, loc1);
     index.set(id2, loc2);
@@ -69,7 +69,7 @@ void test_func_real(TIndex& index) {
 TEST_CASE("IdToLocation") {
 
     SECTION("Dummy") {
-        typedef osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location> index_type;
+        using index_type = osmium::index::map::Dummy<osmium::unsigned_object_id_type, osmium::Location>;
 
         index_type index1;
 
@@ -83,7 +83,7 @@ TEST_CASE("IdToLocation") {
     }
 
     SECTION("DenseMemArray") {
-        typedef osmium::index::map::DenseMemArray<osmium::unsigned_object_id_type, osmium::Location> index_type;
+        using index_type = osmium::index::map::DenseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
 
         index_type index1;
         index1.reserve(1000);
@@ -96,7 +96,7 @@ TEST_CASE("IdToLocation") {
 
 #ifdef __linux__
     SECTION("DenseMmapArray") {
-        typedef osmium::index::map::DenseMmapArray<osmium::unsigned_object_id_type, osmium::Location> index_type;
+        using index_type = osmium::index::map::DenseMmapArray<osmium::unsigned_object_id_type, osmium::Location>;
 
         index_type index1;
         test_func_all<index_type>(index1);
@@ -109,7 +109,7 @@ TEST_CASE("IdToLocation") {
 #endif
 
     SECTION("DenseFileArray") {
-        typedef osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location> index_type;
+        using index_type = osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, osmium::Location>;
 
         index_type index1;
         test_func_all<index_type>(index1);
@@ -121,7 +121,7 @@ TEST_CASE("IdToLocation") {
 #ifdef OSMIUM_WITH_SPARSEHASH
 
     SECTION("SparseMemTable") {
-        typedef osmium::index::map::SparseMemTable<osmium::unsigned_object_id_type, osmium::Location> index_type;
+        using index_type = osmium::index::map::SparseMemTable<osmium::unsigned_object_id_type, osmium::Location>;
 
         index_type index1;
         test_func_all<index_type>(index1);
@@ -133,7 +133,7 @@ TEST_CASE("IdToLocation") {
 #endif
 
     SECTION("SparseMemMap") {
-        typedef osmium::index::map::SparseMemMap<osmium::unsigned_object_id_type, osmium::Location> index_type;
+        using index_type = osmium::index::map::SparseMemMap<osmium::unsigned_object_id_type, osmium::Location>;
 
         index_type index1;
         test_func_all<index_type>(index1);
@@ -143,7 +143,7 @@ TEST_CASE("IdToLocation") {
     }
 
     SECTION("SparseMemArray") {
-        typedef osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location> index_type;
+        using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
 
         index_type index1;
 
@@ -159,10 +159,10 @@ TEST_CASE("IdToLocation") {
     }
 
     SECTION("Dynamic map choice") {
-        typedef osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location> map_type;
+        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();
 
-        std::vector<std::string> map_type_names = map_factory.map_types();
+        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) {
diff --git a/test/t/io/test_opl_parser.cpp b/test/t/io/test_opl_parser.cpp
new file mode 100644
index 0000000..9ad6eeb
--- /dev/null
+++ b/test/t/io/test_opl_parser.cpp
@@ -0,0 +1,1075 @@
+
+#include <algorithm>
+#include <cstring>
+
+#include "catch.hpp"
+
+#include <osmium/io/detail/opl_input_format.hpp>
+#include <osmium/opl.hpp>
+
+namespace oid = osmium::io::detail;
+
+TEST_CASE("Parse OPL: base exception") {
+    osmium::opl_error e{"foo"};
+    REQUIRE(e.data == nullptr);
+    REQUIRE(e.line == 0);
+    REQUIRE(e.column == 0);
+    REQUIRE(e.msg == "OPL error: foo");
+    REQUIRE(std::string{e.what()} == "OPL error: foo");
+}
+
+TEST_CASE("Parse OPL: exception with line and column") {
+    const char* d = "data";
+    osmium::opl_error e{"bar", d};
+    e.set_pos(17, 23);
+    REQUIRE(e.data == d);
+    REQUIRE(e.line == 17);
+    REQUIRE(e.column == 23);
+    REQUIRE(e.msg == "OPL error: bar on line 17 column 23");
+    REQUIRE(std::string{e.what()} == "OPL error: bar on line 17 column 23");
+}
+
+TEST_CASE("Parse OPL: space") {
+    std::string d{"a b \t c"};
+
+    const char* s = d.data();
+    REQUIRE_THROWS_AS({
+        oid::opl_parse_space(&s);
+    }, osmium::opl_error);
+
+    s = d.data() + 1;
+    oid::opl_parse_space(&s);
+    REQUIRE(*s == 'b');
+
+    REQUIRE_THROWS_AS({
+        oid::opl_parse_space(&s);
+    }, osmium::opl_error);
+
+    ++s;
+    oid::opl_parse_space(&s);
+    REQUIRE(*s == 'c');
+}
+
+TEST_CASE("Parse OPL: check for space") {
+    REQUIRE(oid::opl_non_empty("aaa"));
+    REQUIRE(oid::opl_non_empty("a b"));
+    REQUIRE_FALSE(oid::opl_non_empty(" "));
+    REQUIRE_FALSE(oid::opl_non_empty(" x"));
+    REQUIRE_FALSE(oid::opl_non_empty("\tx"));
+    REQUIRE_FALSE(oid::opl_non_empty(""));
+}
+
+TEST_CASE("Parse OPL: skip section") {
+    std::string d{"abcd efgh"};
+    const char* skip1 = d.data() + 4;
+    const char* skip2 = d.data() + 9;
+    const char* s = d.data();
+    REQUIRE(oid::opl_skip_section(&s) == skip1);
+    REQUIRE(s == skip1);
+    ++s;
+    REQUIRE(oid::opl_skip_section(&s) == skip2);
+    REQUIRE(s == skip2);
+}
+
+TEST_CASE("Parse OPL: parse escaped") {
+    std::string result;
+
+    SECTION("Empty string") {
+        const char* s = "";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_escaped(&s, result);
+        }, "OPL error: eol");
+    }
+
+    SECTION("Illegal character for hex") {
+        const char* s = "x";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_escaped(&s, result);
+        }, "OPL error: not a hex char");
+    }
+
+    SECTION("Illegal character for hex after legal hex characters") {
+        const char* s = "0123x";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_escaped(&s, result);
+        }, "OPL error: not a hex char");
+    }
+
+    SECTION("Too long") {
+        const char* s = "123456780";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_escaped(&s, result);
+        }, "OPL error: hex escape too long");
+    }
+
+    SECTION("No data") {
+        const char* s = "%";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_escaped(&s, result);
+        REQUIRE(result.size() == 1);
+        REQUIRE(result[0] == '\0');
+        REQUIRE(s == e);
+    }
+
+    SECTION("One hex char") {
+        const char* s = "9%";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_escaped(&s, result);
+        REQUIRE(result.size() == 1);
+        REQUIRE(result == "\t");
+        REQUIRE(s == e);
+    }
+
+    SECTION("Two hex chars (lowercase)") {
+        const char* s = "3c%";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_escaped(&s, result);
+        REQUIRE(result.size() == 1);
+        REQUIRE(result == "<");
+        REQUIRE(s == e);
+    }
+
+    SECTION("Two hex char (uppercase)") {
+        const char* s = "3C%";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_escaped(&s, result);
+        REQUIRE(result.size() == 1);
+        REQUIRE(result == "<");
+        REQUIRE(s == e);
+    }
+
+    SECTION("Longer unicode characters") {
+        const char* s1 = "30dc%";
+        oid::opl_parse_escaped(&s1, result);
+        result.append("_");
+        const char* s2 = "1d11e%";
+        oid::opl_parse_escaped(&s2, result);
+        result.append("_");
+        const char* s3 = "1f6eb%";
+        oid::opl_parse_escaped(&s3, result);
+        REQUIRE(result == u8"\u30dc_\U0001d11e_\U0001f6eb");
+    }
+
+    SECTION("Data after %") {
+        const char* s = "5a%abc";
+        oid::opl_parse_escaped(&s, result);
+        REQUIRE(result.size() == 1);
+        REQUIRE(result == "Z");
+        REQUIRE(std::string{s} == "abc");
+    }
+
+}
+
+TEST_CASE("Parse OPL: parse string") {
+    std::string result;
+
+    SECTION("empty string") {
+        const char* s = "";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 0);
+        REQUIRE(result == "");
+        REQUIRE(s == e);
+    }
+
+    SECTION("normal string") {
+        const char* s = "foo";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 3);
+        REQUIRE(result == "foo");
+        REQUIRE(s == e);
+    }
+
+    SECTION("string with space") {
+        const char* s = "foo bar";
+        const char* e = s + 3;
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 3);
+        REQUIRE(result == "foo");
+        REQUIRE(s == e);
+    }
+
+    SECTION("string with tab") {
+        const char* s = "foo\tbar";
+        const char* e = s + 3;
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 3);
+        REQUIRE(result == "foo");
+        REQUIRE(s == e);
+    }
+
+    SECTION("string with comma") {
+        const char* s = "foo,bar";
+        const char* e = s + 3;
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 3);
+        REQUIRE(result == "foo");
+        REQUIRE(s == e);
+    }
+
+    SECTION("string with equal sign") {
+        const char* s = "foo=bar";
+        const char* e = s + 3;
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 3);
+        REQUIRE(result == "foo");
+        REQUIRE(s == e);
+    }
+
+    SECTION("string with escaped characters") {
+        const char* s = "foo%3d%bar";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 7);
+        REQUIRE(result == "foo=bar");
+        REQUIRE(s == e);
+    }
+
+    SECTION("string with escaped characters at end") {
+        const char* s = "foo%3d%";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_string(&s, result);
+        REQUIRE(result.size() == 4);
+        REQUIRE(result == "foo=");
+        REQUIRE(s == e);
+    }
+
+    SECTION("string with invalid escaping") {
+        const char* s = "foo%";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_string(&s, result);
+        }, "OPL error: eol");
+    }
+
+    SECTION("string with invalid escaped characters") {
+        const char* s = "foo%x%";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_string(&s, result);
+        }, "OPL error: not a hex char");
+    }
+
+}
+
+template <typename T = int64_t>
+T test_parse_int(const char* s) {
+    auto r = oid::opl_parse_int<T>(&s);
+    REQUIRE(*s == 'x');
+    return r;
+}
+
+TEST_CASE("Parse OPL: integer") {
+    REQUIRE(test_parse_int("0x") == 0);
+    REQUIRE(test_parse_int("-0x") == 0);
+    REQUIRE(test_parse_int("1x") == 1);
+    REQUIRE(test_parse_int("17x") == 17);
+    REQUIRE(test_parse_int("-1x") == -1);
+    REQUIRE(test_parse_int("1234567890123x") == 1234567890123);
+    REQUIRE(test_parse_int("-1234567890123x") == -1234567890123);
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int("");
+    }, "OPL error: expected integer");
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int("-x");
+    }, "OPL error: expected integer");
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int(" 1");
+    }, "OPL error: expected integer");
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int("x");
+    }, "OPL error: expected integer");
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int("99999999999999999999999x");
+    }, "OPL error: integer too long");
+}
+
+TEST_CASE("Parse OPL: int32_t") {
+    REQUIRE(test_parse_int<int32_t>("0x") == 0);
+    REQUIRE(test_parse_int<int32_t>("123x") == 123);
+    REQUIRE(test_parse_int<int32_t>("-123x") == -123);
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int<int32_t>("12345678901x");
+    }, "OPL error: integer too long");
+    REQUIRE_THROWS_WITH({
+        test_parse_int<int32_t>("-12345678901x");
+    }, "OPL error: integer too long");
+}
+
+TEST_CASE("Parse OPL: uint32_t") {
+    REQUIRE(test_parse_int<uint32_t>("0x") == 0);
+    REQUIRE(test_parse_int<uint32_t>("123x") == 123);
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int<uint32_t>("-123x");
+    }, "OPL error: integer too long");
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int<uint32_t>("12345678901x");
+    }, "OPL error: integer too long");
+
+    REQUIRE_THROWS_WITH({
+        test_parse_int<uint32_t>("-12345678901x");
+    }, "OPL error: integer too long");
+}
+
+TEST_CASE("Parse OPL: visible flag") {
+    const char* data = "V";
+    const char* e = data + std::strlen(data);
+    REQUIRE(oid::opl_parse_visible(&data));
+    REQUIRE(e == data);
+
+}
+
+TEST_CASE("Parse OPL: deleted flag") {
+    const char* data = "D";
+    const char* e = data + std::strlen(data);
+    REQUIRE_FALSE(oid::opl_parse_visible(&data));
+    REQUIRE(e == data);
+}
+
+TEST_CASE("Parse OPL: invalid visible flag") {
+    const char* data = "x";
+    REQUIRE_THROWS_WITH({
+        oid::opl_parse_visible(&data);
+    }, "OPL error: invalid visible flag");
+}
+
+TEST_CASE("Parse OPL: timestamp (empty)") {
+    const char* data = "";
+    const char* e = data + std::strlen(data);
+    REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{});
+    REQUIRE(e == data);
+}
+
+TEST_CASE("Parse OPL: timestamp (space)") {
+    const char* data = " ";
+    const char* e = data;
+    REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{});
+    REQUIRE(e == data);
+}
+
+TEST_CASE("Parse OPL: timestamp (tab)") {
+    const char* data = "\t";
+    const char* e = data;
+    REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{});
+    REQUIRE(e == data);
+}
+
+TEST_CASE("Parse OPL: timestamp (invalid)") {
+    const char* data = "abc";
+    REQUIRE_THROWS_WITH({
+        oid::opl_parse_timestamp(&data);
+    }, "OPL error: can not parse timestamp");
+}
+
+TEST_CASE("Parse OPL: timestamp (valid)") {
+    const char* data = "2016-03-04T17:28:03Z";
+    const char* e = data + std::strlen(data);
+    REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{"2016-03-04T17:28:03Z"});
+    REQUIRE(e == data);
+}
+
+TEST_CASE("Parse OPL: valid timestamp with trailing data") {
+    const char* data = "2016-03-04T17:28:03Zxxx";
+    REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{"2016-03-04T17:28:03Z"});
+    REQUIRE(std::string{data} == "xxx");
+}
+
+TEST_CASE("Parse OPL: tags") {
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Empty") {
+        const char* data = "";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_tags(data, buffer);
+        }, "OPL error: expected '='");
+    }
+
+    SECTION("One tag") {
+        const char* data = "foo=bar";
+        oid::opl_parse_tags(data, buffer);
+        const auto& taglist = buffer.get<osmium::TagList>(0);
+        REQUIRE(taglist.size() == 1);
+        REQUIRE(std::string{taglist.begin()->key()} == "foo");
+        REQUIRE(std::string{taglist.begin()->value()} == "bar");
+    }
+
+    SECTION("Empty key and value are allowed") {
+        const char* data = "=";
+        oid::opl_parse_tags(data, buffer);
+        const auto& taglist = buffer.get<osmium::TagList>(0);
+        REQUIRE(taglist.size() == 1);
+        REQUIRE(std::string{taglist.begin()->key()} == "");
+        REQUIRE(std::string{taglist.begin()->value()} == "");
+    }
+
+    SECTION("Multiple tags") {
+        const char* data = "highway=residential,oneway=yes,maxspeed=30";
+        oid::opl_parse_tags(data, buffer);
+        const auto& taglist = buffer.get<osmium::TagList>(0);
+        REQUIRE(taglist.size() == 3);
+        auto it = taglist.cbegin();
+        REQUIRE(std::string{it->key()} == "highway");
+        REQUIRE(std::string{it->value()} == "residential");
+        ++it;
+        REQUIRE(std::string{it->key()} == "oneway");
+        REQUIRE(std::string{it->value()} == "yes");
+        ++it;
+        REQUIRE(std::string{it->key()} == "maxspeed");
+        REQUIRE(std::string{it->value()} == "30");
+        ++it;
+        REQUIRE(it == taglist.cend());
+    }
+
+    SECTION("No equal signs") {
+        const char* data = "a";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_tags(data, buffer);
+        }, "OPL error: expected '='");
+    }
+
+    SECTION("Two equal signs") {
+        const char* data = "a=b=c";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_tags(data, buffer);
+        }, "OPL error: expected ','");
+    }
+
+}
+
+TEST_CASE("Parse OPL: nodes") {
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Empty") {
+        const char* const s = "";
+        oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() == 0);
+    }
+
+    SECTION("Invalid format, missing n") {
+        const char* const s = "xyz";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer);
+        }, "OPL error: expected 'n'");
+    }
+
+    SECTION("Invalid format, missing ID") {
+        const char* const s = "nx";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer);
+        }, "OPL error: expected integer");
+    }
+
+    SECTION("Valid format: one node") {
+        const char* const s = "n123";
+        oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& wnl = buffer.get<osmium::WayNodeList>(0);
+        REQUIRE(wnl.size() == 1);
+        REQUIRE(wnl.begin()->ref() == 123);
+    }
+
+    SECTION("Valid format: two nodes") {
+        const char* const s = "n123,n456";
+        oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& wnl = buffer.get<osmium::WayNodeList>(0);
+        REQUIRE(wnl.size() == 2);
+        auto it = wnl.begin();
+        REQUIRE(it->ref() == 123);
+        ++it;
+        REQUIRE(it->ref() == 456);
+        ++it;
+        REQUIRE(it == wnl.end());
+    }
+
+    SECTION("Trailing comma") {
+        const char* const s = "n123,n456,";
+        oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& wnl = buffer.get<osmium::WayNodeList>(0);
+        REQUIRE(wnl.size() == 2);
+        auto it = wnl.begin();
+        REQUIRE(it->ref() == 123);
+        ++it;
+        REQUIRE(it->ref() == 456);
+        ++it;
+        REQUIRE(it == wnl.end());
+    }
+
+    SECTION("Way nodes with coordinates") {
+        const char* const s = "n123x1.2y3.4,n456x33y0.1";
+        oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& wnl = buffer.get<osmium::WayNodeList>(0);
+        REQUIRE(wnl.size() == 2);
+        auto it = wnl.begin();
+        REQUIRE(it->ref() == 123);
+        const osmium::Location loc1{1.2, 3.4};
+        REQUIRE(it->location() == loc1);
+        ++it;
+        REQUIRE(it->ref() == 456);
+        const osmium::Location loc2{33.0, 0.1};
+        REQUIRE(it->location() == loc2);
+        ++it;
+        REQUIRE(it == wnl.end());
+    }
+
+}
+
+TEST_CASE("Parse OPL: members") {
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Empty") {
+        const char* const s = "";
+        oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() == 0);
+    }
+
+    SECTION("Invalid: unknown object type") {
+        const char* const s = "x123 at foo";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        }, "OPL error: unknown object type");
+    }
+
+    SECTION("Invalid: illegal ref") {
+        const char* const s = "wx";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        }, "OPL error: expected integer");
+    }
+
+    SECTION("Invalid: missing @") {
+        const char* const s = "n123foo";
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        }, "OPL error: expected '@'");
+    }
+
+    SECTION("Valid format: one member") {
+        const char* const s = "n123 at foo";
+        oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& rml = buffer.get<osmium::RelationMemberList>(0);
+        REQUIRE(rml.size() == 1);
+        auto it = rml.begin();
+        REQUIRE(it->type() == osmium::item_type::node);
+        REQUIRE(it->ref() == 123);
+        REQUIRE(std::string{it->role()} == "foo");
+        ++it;
+        REQUIRE(it == rml.end());
+    }
+
+    SECTION("Valid format: one member without role") {
+        const char* const s = "n123@";
+        oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& rml = buffer.get<osmium::RelationMemberList>(0);
+        REQUIRE(rml.size() == 1);
+        auto it = rml.begin();
+        REQUIRE(it->type() == osmium::item_type::node);
+        REQUIRE(it->ref() == 123);
+        REQUIRE(std::string{it->role()} == "");
+        ++it;
+        REQUIRE(it == rml.end());
+    }
+
+    SECTION("Valid format: three members") {
+        const char* const s = "n123@,w456 at abc,r78 at type";
+        oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& rml = buffer.get<osmium::RelationMemberList>(0);
+        REQUIRE(rml.size() == 3);
+        auto it = rml.begin();
+        REQUIRE(it->type() == osmium::item_type::node);
+        REQUIRE(it->ref() == 123);
+        REQUIRE(std::string{it->role()} == "");
+        ++it;
+        REQUIRE(it->type() == osmium::item_type::way);
+        REQUIRE(it->ref() == 456);
+        REQUIRE(std::string{it->role()} == "abc");
+        ++it;
+        REQUIRE(it->type() == osmium::item_type::relation);
+        REQUIRE(it->ref() == 78);
+        REQUIRE(std::string{it->role()} == "type");
+        ++it;
+        REQUIRE(it == rml.end());
+    }
+
+    SECTION("Trailing comma") {
+        const char* const s = "n123@,w456 at abc,r78 at type,";
+        oid::opl_parse_relation_members(s, s + std::strlen(s), buffer);
+        REQUIRE(buffer.written() > 0);
+        const auto& rml = buffer.get<osmium::RelationMemberList>(0);
+        REQUIRE(rml.size() == 3);
+        auto it = rml.begin();
+        REQUIRE(it->type() == osmium::item_type::node);
+        REQUIRE(it->ref() == 123);
+        REQUIRE(std::string{it->role()} == "");
+        ++it;
+        REQUIRE(it->type() == osmium::item_type::way);
+        REQUIRE(it->ref() == 456);
+        REQUIRE(std::string{it->role()} == "abc");
+        ++it;
+        REQUIRE(it->type() == osmium::item_type::relation);
+        REQUIRE(it->ref() == 78);
+        REQUIRE(std::string{it->role()} == "type");
+        ++it;
+        REQUIRE(it == rml.end());
+    }
+
+
+}
+
+TEST_CASE("Parse node") {
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Node with id only") {
+        const char* s = "17";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 17);
+    }
+
+    SECTION("Node with trailing space") {
+        const char* s = "17 ";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 17);
+    }
+
+    SECTION("Node with id and version") {
+        const char* s = "17 v23";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 17);
+        REQUIRE(node.version() == 23);
+    }
+
+    SECTION("Node with multiple spaces") {
+        const char* s = "17  v23";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 17);
+        REQUIRE(node.version() == 23);
+    }
+
+    SECTION("Node with tab instead of space") {
+        const char* s = "17\tv23";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 17);
+        REQUIRE(node.version() == 23);
+    }
+
+    SECTION("Full node (no tags)") {
+        const char* s = "125799 v6 dV c7711393 t2011-03-29T21:43:10Z i45445 uUScha T x8.7868047 y53.0749415";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 125799);
+        REQUIRE(node.version() == 6);
+        REQUIRE(node.visible());
+        REQUIRE(node.changeset() == 7711393);
+        REQUIRE(node.timestamp() == osmium::Timestamp{"2011-03-29T21:43:10Z"});
+        REQUIRE(node.uid() == 45445);
+        REQUIRE(std::string{node.user()} == "UScha");
+        osmium::Location loc{8.7868047, 53.0749415};
+        REQUIRE(node.location() == loc);
+        REQUIRE(node.tags().empty());
+    }
+
+    SECTION("Node with tags)") {
+        const char* s = "123 v1 c456 Thighway=residential,oneway=true,name=High%20%Street";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 123);
+        REQUIRE(node.version() == 1);
+        REQUIRE(node.changeset() == 456);
+        REQUIRE(node.tags().size() == 3);
+
+        auto it = node.tags().cbegin();
+        REQUIRE(std::string{it->key()} == "highway");
+        REQUIRE(std::string{it->value()} == "residential");
+        ++it;
+        REQUIRE(std::string{it->key()} == "oneway");
+        REQUIRE(std::string{it->value()} == "true");
+        ++it;
+        REQUIRE(std::string{it->key()} == "name");
+        REQUIRE(std::string{it->value()} == "High Street");
+        ++it;
+        REQUIRE(it == node.tags().cend());
+    }
+
+    SECTION("Order does not matter") {
+        const char* s = "125799 c7711393 dV v6 i45445 uUScha T t2011-03-29T21:43:10Z y53.0749415 x8.7868047";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_node(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Node& node = buffer.get<osmium::Node>(0);
+        REQUIRE(node.id() == 125799);
+        REQUIRE(node.version() == 6);
+        REQUIRE(node.visible());
+        REQUIRE(node.changeset() == 7711393);
+        REQUIRE(node.timestamp() == osmium::Timestamp{"2011-03-29T21:43:10Z"});
+        REQUIRE(node.uid() == 45445);
+        REQUIRE(std::string{node.user()} == "UScha");
+        osmium::Location loc{8.7868047, 53.0749415};
+        REQUIRE(node.location() == loc);
+        REQUIRE(node.tags().empty());
+    }
+
+}
+
+TEST_CASE("Parse way") {
+
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Way with id only") {
+        const char* s = "17";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_way(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Way& way = buffer.get<osmium::Way>(0);
+        REQUIRE(way.id() == 17);
+    }
+
+    SECTION("Complete way") {
+        const char* s = "78216 v12 dV c35895909 t2015-12-11T22:01:57Z i7412 umjulius Tdestination=Interlaken;%20%Kandersteg;%20%Zweisimmen,highway=motorway_link,name=Thun%20%Süd,oneway=yes,ref=17,surface=asphalt Nn1011242,n2569390773,n2569390769,n255308687,n2569390761,n255308689,n255308691,n1407526499,n255308692,n3888362655,n255308693,n255308694,n255308695,n255308686";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_way(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Way& way = buffer.get<osmium::Way>(0);
+        REQUIRE(way.id() == 78216);
+        REQUIRE(way.version() == 12);
+        REQUIRE(way.visible());
+        REQUIRE(way.changeset() == 35895909);
+        REQUIRE(way.timestamp() == osmium::Timestamp{"2015-12-11T22:01:57Z"});
+        REQUIRE(way.uid() == 7412);
+        REQUIRE(std::string{way.user()} == "mjulius");
+        REQUIRE(way.tags().size() == 6);
+
+        auto it = way.tags().cbegin();
+        REQUIRE(std::string{it->key()} == "destination");
+        REQUIRE(std::string{it->value()} == "Interlaken; Kandersteg; Zweisimmen");
+        ++it;
+        REQUIRE(std::string{it->key()} == "highway");
+        REQUIRE(std::string{it->value()} == "motorway_link");
+        ++it;
+        REQUIRE(std::string{it->key()} == "name");
+        REQUIRE(std::string{it->value()} == "Thun Süd");
+        ++it;
+        REQUIRE(std::string{it->key()} == "oneway");
+        REQUIRE(std::string{it->value()} == "yes");
+        ++it;
+        REQUIRE(std::string{it->key()} == "ref");
+        REQUIRE(std::string{it->value()} == "17");
+        ++it;
+        REQUIRE(std::string{it->key()} == "surface");
+        REQUIRE(std::string{it->value()} == "asphalt");
+        ++it;
+        REQUIRE(it == way.tags().cend());
+
+        REQUIRE(way.nodes().size() == 14);
+        std::vector<osmium::object_id_type> ids = {
+            1011242, 2569390773, 2569390769, 255308687, 2569390761, 255308689,
+            255308691, 1407526499, 255308692, 3888362655, 255308693, 255308694,
+            255308695, 255308686
+        };
+        REQUIRE(std::equal(way.nodes().cbegin(), way.nodes().cend(), ids.cbegin()));
+    }
+
+}
+
+TEST_CASE("Parse relation") {
+
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Relation with id only") {
+        const char* s = "17";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_relation(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Relation& relation = buffer.get<osmium::Relation>(0);
+        REQUIRE(relation.id() == 17);
+    }
+
+    SECTION("Complete relation") {
+        const char* s = "1074 v45 dV c20048094 t2014-01-17T10:27:04Z i86566 uwisieb Ttype=multipolygon,landuse=forest Mw255722275 at inner,w256126142 at outer,w24402792 at inner,w256950103 at outer,w255722279 at outer";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_relation(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Relation& relation = buffer.get<osmium::Relation>(0);
+        REQUIRE(relation.id() == 1074);
+        REQUIRE(relation.version() == 45);
+        REQUIRE(relation.visible());
+        REQUIRE(relation.changeset() == 20048094);
+        REQUIRE(relation.timestamp() == osmium::Timestamp{"2014-01-17T10:27:04Z"});
+        REQUIRE(relation.uid() == 86566);
+        REQUIRE(std::string{relation.user()} == "wisieb");
+        REQUIRE(relation.tags().size() == 2);
+
+        auto it = relation.tags().cbegin();
+        REQUIRE(std::string{it->key()} == "type");
+        REQUIRE(std::string{it->value()} == "multipolygon");
+        ++it;
+        REQUIRE(std::string{it->key()} == "landuse");
+        REQUIRE(std::string{it->value()} == "forest");
+        ++it;
+        REQUIRE(it == relation.tags().cend());
+
+        REQUIRE(relation.members().size() == 5);
+        auto mit = relation.members().cbegin();
+        REQUIRE(mit->type() == osmium::item_type::way);
+        REQUIRE(mit->ref() == 255722275);
+        REQUIRE(std::string{mit->role()} == "inner");
+        ++mit;
+        REQUIRE(mit->type() == osmium::item_type::way);
+        REQUIRE(mit->ref() == 256126142);
+        REQUIRE(std::string{mit->role()} == "outer");
+        ++mit;
+        ++mit;
+        ++mit;
+        ++mit;
+        REQUIRE(mit == relation.members().cend());
+    }
+
+}
+
+TEST_CASE("Parse changeset") {
+
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Changeset with id only") {
+        const char* s = "17";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_changeset(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Changeset& changeset = buffer.get<osmium::Changeset>(0);
+        REQUIRE(changeset.id() == 17);
+    }
+
+    SECTION("Complete changeset") {
+        const char* s = "873494 k1 s2009-04-21T08:52:49Z e2009-04-21T09:52:49Z d0 i13093 uTiberiusNero x13.923302 y50.957069 X14.0337519 Y50.9824084 Tcreated_by=Potlatch%20%0.11";
+        const char* e = s + std::strlen(s);
+        oid::opl_parse_changeset(&s, buffer);
+        REQUIRE(s == e);
+        REQUIRE(buffer.written() > 0);
+        const osmium::Changeset& changeset = buffer.get<osmium::Changeset>(0);
+        REQUIRE(changeset.id() == 873494);
+        REQUIRE(changeset.created_at() == osmium::Timestamp{"2009-04-21T08:52:49Z"});
+        REQUIRE(changeset.closed_at() == osmium::Timestamp{"2009-04-21T09:52:49Z"});
+        REQUIRE(changeset.num_changes() == 1);
+        REQUIRE(changeset.num_comments() == 0);
+        REQUIRE(changeset.uid() == 13093);
+        REQUIRE(std::string{changeset.user()} == "TiberiusNero");
+        REQUIRE(changeset.tags().size() == 1);
+
+        auto it = changeset.tags().cbegin();
+        REQUIRE(std::string{it->key()} == "created_by");
+        REQUIRE(std::string{it->value()} == "Potlatch 0.11");
+        ++it;
+        REQUIRE(it == changeset.tags().cend());
+
+        osmium::Box box{13.923302, 50.957069, 14.0337519, 50.9824084};
+        REQUIRE(box == changeset.bounds());
+    }
+
+}
+
+TEST_CASE("Parse line") {
+
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Empty line") {
+        const char* s = "";
+        REQUIRE_FALSE(oid::opl_parse_line(0, "", buffer));
+        REQUIRE(buffer.written() == 0);
+    }
+
+    SECTION("Comment line") {
+        REQUIRE_FALSE(oid::opl_parse_line(0, "# abc", buffer));
+        REQUIRE(buffer.written() == 0);
+    }
+
+    SECTION("Fail") {
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_line(0, "X", buffer);
+        }, "OPL error: unknown type on line 0 column 0");
+        REQUIRE(buffer.written() == 0);
+    }
+
+    SECTION("New line at end not allowed") {
+        REQUIRE_THROWS_WITH({
+            oid::opl_parse_line(0, "n12 v3\n", buffer);
+        }, "OPL error: expected space or tab character on line 0 column 6");
+    }
+
+    SECTION("Node, but not asking for nodes") {
+        REQUIRE_FALSE(oid::opl_parse_line(0, "n12 v1", buffer, osmium::osm_entity_bits::way));
+        REQUIRE(buffer.written() == 0);
+    }
+
+    SECTION("Node") {
+        REQUIRE(oid::opl_parse_line(0, "n12 v3", buffer));
+        REQUIRE(buffer.written() > 0);
+        auto& item = buffer.get<osmium::memory::Item>(0);
+        REQUIRE(item.type() == osmium::item_type::node);
+    }
+
+    SECTION("Way") {
+        REQUIRE(oid::opl_parse_line(0, "w12 v3", buffer));
+        REQUIRE(buffer.written() > 0);
+        auto& item = buffer.get<osmium::memory::Item>(0);
+        REQUIRE(item.type() == osmium::item_type::way);
+    }
+
+    SECTION("Relation") {
+        REQUIRE(oid::opl_parse_line(0, "r12 v3", buffer));
+        REQUIRE(buffer.written() > 0);
+        auto& item = buffer.get<osmium::memory::Item>(0);
+        REQUIRE(item.type() == osmium::item_type::relation);
+    }
+
+    SECTION("Changeset") {
+        REQUIRE(oid::opl_parse_line(0, "c12", buffer));
+        REQUIRE(buffer.written() > 0);
+        auto& item = buffer.get<osmium::memory::Item>(0);
+        REQUIRE(item.type() == osmium::item_type::changeset);
+    }
+
+}
+
+TEST_CASE("Get context for errors") {
+
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Unknown object type") {
+        bool error = false;
+        try {
+            oid::opl_parse_line(0, "~~~", buffer);
+        } catch (const osmium::opl_error& e) {
+            error = true;
+            REQUIRE(e.line == 0);
+            REQUIRE(e.column == 0);
+            REQUIRE(std::string{e.data} == "~~~");
+        }
+        REQUIRE(error);
+    }
+
+    SECTION("Node id") {
+        bool error = false;
+        try {
+            oid::opl_parse_line(0, "n~~~", buffer);
+        } catch (const osmium::opl_error& e) {
+            error = true;
+            REQUIRE(e.line == 0);
+            REQUIRE(e.column == 1);
+            REQUIRE(std::string{e.data} == "~~~");
+        }
+        REQUIRE(error);
+    }
+
+    SECTION("Node expect space") {
+        bool error = false;
+        try {
+            oid::opl_parse_line(1, "n123~~~", buffer);
+        } catch (const osmium::opl_error& e) {
+            error = true;
+            REQUIRE(e.line == 1);
+            REQUIRE(e.column == 4);
+            REQUIRE(std::string{e.data} == "~~~");
+        }
+        REQUIRE(error);
+    }
+
+    SECTION("Node unknown attribute") {
+        bool error = false;
+        try {
+            oid::opl_parse_line(2, "n123 ~~~", buffer);
+        } catch (const osmium::opl_error& e) {
+            error = true;
+            REQUIRE(e.line == 2);
+            REQUIRE(e.column == 5);
+            REQUIRE(std::string{e.data} == "~~~");
+        }
+        REQUIRE(error);
+    }
+
+    SECTION("Node version not an int") {
+        bool error = false;
+        try {
+            oid::opl_parse_line(3, "n123 v~~~", buffer);
+        } catch (const osmium::opl_error& e) {
+            error = true;
+            REQUIRE(e.line == 3);
+            REQUIRE(e.column == 6);
+            REQUIRE(std::string{e.data} == "~~~");
+        }
+        REQUIRE(error);
+    }
+
+}
+
+TEST_CASE("Parse line with external interface") {
+
+    osmium::memory::Buffer buffer{1024};
+
+    SECTION("Node") {
+        REQUIRE(osmium::opl_parse("n12 v3", buffer));
+        REQUIRE(buffer.committed() > 0);
+        REQUIRE(buffer.written() == buffer.committed());
+        const auto& item = buffer.get<osmium::memory::Item>(0);
+        REQUIRE(item.type() == osmium::item_type::node);
+        REQUIRE(static_cast<const osmium::Node&>(item).id() == 12);
+    }
+
+    SECTION("Empty line") {
+        REQUIRE_FALSE(osmium::opl_parse("", buffer));
+        REQUIRE(buffer.written() == 0);
+        REQUIRE(buffer.committed() == 0);
+    }
+
+    SECTION("Failure") {
+        REQUIRE_THROWS_WITH({
+            osmium::opl_parse("x", buffer);
+        }, "OPL error: unknown type on line 0 column 0");
+        REQUIRE(buffer.written() == 0);
+        REQUIRE(buffer.committed() == 0);
+    }
+
+}
+
diff --git a/test/t/io/test_reader_with_mock_decompression.cpp b/test/t/io/test_reader_with_mock_decompression.cpp
index 566295a..63b8bd2 100644
--- a/test/t/io/test_reader_with_mock_decompression.cpp
+++ b/test/t/io/test_reader_with_mock_decompression.cpp
@@ -19,7 +19,7 @@ class MockDecompressor : public osmium::io::Decompressor {
 
 public:
 
-    MockDecompressor(const std::string& fail_in) :
+    explicit MockDecompressor(const std::string& fail_in) :
         Decompressor(),
         m_fail_in(fail_in) {
         if (m_fail_in == "constructor") {
@@ -87,7 +87,7 @@ TEST_CASE("Test Reader using MockDecompressor") {
         try {
             osmium::io::Reader reader(with_data_dir("t/io/data.osm.gz"));
             REQUIRE(false);
-        } catch (std::runtime_error& e) {
+        } catch (const std::runtime_error& e) {
             REQUIRE(std::string{e.what()} == "error constructor");
         }
     }
@@ -99,7 +99,7 @@ TEST_CASE("Test Reader using MockDecompressor") {
             osmium::io::Reader reader(with_data_dir("t/io/data.osm.gz"));
             reader.read();
             REQUIRE(false);
-        } catch (std::runtime_error& e) {
+        } catch (const std::runtime_error& e) {
             REQUIRE(std::string{e.what()} == "error first read");
         }
     }
@@ -112,7 +112,7 @@ TEST_CASE("Test Reader using MockDecompressor") {
             reader.read();
             reader.read();
             REQUIRE(false);
-        } catch (std::runtime_error& e) {
+        } catch (const std::runtime_error& e) {
             REQUIRE(std::string{e.what()} == "error second read");
         }
     }
@@ -127,7 +127,7 @@ TEST_CASE("Test Reader using MockDecompressor") {
             reader.read();
             reader.close();
             REQUIRE(false);
-        } catch (std::runtime_error& e) {
+        } catch (const std::runtime_error& e) {
             REQUIRE(std::string{e.what()} == "error close");
         }
     }
diff --git a/test/t/io/test_reader_with_mock_parser.cpp b/test/t/io/test_reader_with_mock_parser.cpp
index c71847c..c5c9975 100644
--- a/test/t/io/test_reader_with_mock_parser.cpp
+++ b/test/t/io/test_reader_with_mock_parser.cpp
@@ -78,7 +78,7 @@ TEST_CASE("Test Reader using MockParser") {
         try {
             osmium::io::Reader reader(with_data_dir("t/io/data.osm"));
             reader.header();
-        } catch (std::runtime_error& e) {
+        } catch (const std::runtime_error& e) {
             REQUIRE(std::string{e.what()} == "error in header");
         }
     }
@@ -89,7 +89,7 @@ TEST_CASE("Test Reader using MockParser") {
         reader.header();
         try {
             reader.read();
-        } catch (std::runtime_error& e) {
+        } catch (const std::runtime_error& e) {
             REQUIRE(std::string{e.what()} == "error in read");
         }
         reader.close();
@@ -101,7 +101,7 @@ TEST_CASE("Test Reader using MockParser") {
         reader.header();
         try {
             throw std::runtime_error("error in user code");
-        } catch (std::runtime_error& e) {
+        } catch (const std::runtime_error& e) {
             REQUIRE(std::string{e.what()} == "error in user code");
         }
         REQUIRE(reader.read());
diff --git a/test/t/io/test_writer_with_mock_compression.cpp b/test/t/io/test_writer_with_mock_compression.cpp
index 10c19c4..a28d537 100644
--- a/test/t/io/test_writer_with_mock_compression.cpp
+++ b/test/t/io/test_writer_with_mock_compression.cpp
@@ -15,7 +15,7 @@ class MockCompressor : public osmium::io::Compressor {
 
 public:
 
-    MockCompressor(const std::string& fail_in) :
+    explicit MockCompressor(const std::string& fail_in) :
         Compressor(osmium::io::fsync::no),
         m_fail_in(fail_in) {
         if (m_fail_in == "constructor") {
diff --git a/test/t/tags/test_filter.cpp b/test/t/tags/test_filter.cpp
index fa21de1..260a4ba 100644
--- a/test/t/tags/test_filter.cpp
+++ b/test/t/tags/test_filter.cpp
@@ -24,7 +24,7 @@ void check_filter(const osmium::TagList& tag_list, const TFilter filter, const s
 }
 
 const osmium::TagList& make_tag_list(osmium::memory::Buffer& buffer, std::initializer_list<std::pair<const char*, const char*>> tags) {
-    auto pos = osmium::builder::add_tag_list(buffer, osmium::builder::attr::_tags(tags));
+    const auto pos = osmium::builder::add_tag_list(buffer, osmium::builder::attr::_tags(tags));
     return buffer.get<osmium::TagList>(pos);
 }
 
@@ -42,7 +42,7 @@ TEST_CASE("Filter") {
             { "source", "GPS" }        // no match
         });
 
-        std::vector<bool> results = { true, false, false };
+        const std::vector<bool> results = { true, false, false };
 
         check_filter(tag_list, filter, results);
     }
@@ -84,7 +84,7 @@ TEST_CASE("Filter") {
             { "source", "GPS" }
         });
 
-        std::vector<bool> results = {true, true, false};
+        const std::vector<bool> results = {true, true, false};
 
         check_filter(tag_list, filter, results);
     }
diff --git a/test/t/util/test_cast_with_assert.cpp b/test/t/util/test_cast_with_assert.cpp
index 0231f30..044176e 100644
--- a/test/t/util/test_cast_with_assert.cpp
+++ b/test/t/util/test_cast_with_assert.cpp
@@ -3,7 +3,7 @@
 // Define assert() to throw this error. This enables the tests to check that
 // the assert() fails.
 struct assert_error : public std::runtime_error {
-    assert_error(const char* what_arg) : std::runtime_error(what_arg) {
+    explicit assert_error(const char* what_arg) : std::runtime_error(what_arg) {
     }
 };
 #define assert(x) if (!(x)) { throw(assert_error(#x)); }

-- 
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