[libosmium] 01/05: Imported Upstream version 2.8.0

Bas Couwenberg sebastic at debian.org
Thu Aug 4 09:01:46 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 b90fb1a5efa55bf3eeb98aae0f02114ceae12595
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Aug 4 10:21:26 2016 +0200

    Imported Upstream version 2.8.0
---
 .travis.yml                                      |  83 +--
 CHANGELOG.md                                     |  39 +-
 CMakeLists.txt                                   |  25 +-
 appveyor.yml                                     |   2 -
 examples/CMakeLists.txt                          |  32 +-
 examples/osmium_index.cpp                        | 131 ++--
 include/osmium/area/assembler.hpp                |   4 +-
 include/osmium/geom/factory.hpp                  |   4 +-
 include/osmium/geom/geojson.hpp                  |   2 +-
 include/osmium/geom/geos.hpp                     |  15 +-
 include/osmium/geom/ogr.hpp                      |   3 +-
 include/osmium/geom/rapid_geojson.hpp            |   2 +-
 include/osmium/geom/wkb.hpp                      |   9 +-
 include/osmium/geom/wkt.hpp                      |  30 +-
 include/osmium/io/detail/debug_output_format.hpp |   2 +-
 include/osmium/io/detail/pbf_decoder.hpp         | 203 +++---
 include/osmium/io/detail/pbf_input_format.hpp    |   6 +-
 include/osmium/io/detail/pbf_output_format.hpp   |  61 +-
 include/osmium/io/detail/string_table.hpp        |  57 +-
 include/osmium/io/detail/zlib.hpp                |   6 +-
 include/osmium/io/reader.hpp                     |  22 +-
 include/osmium/io/writer.hpp                     |  12 +-
 include/osmium/osm/location.hpp                  |  41 +-
 include/osmium/relations/collector.hpp           |   7 -
 include/osmium/thread/pool.hpp                   |  21 +-
 include/osmium/thread/queue.hpp                  |  60 +-
 include/osmium/util/config.hpp                   |  17 +-
 include/osmium/util/delta.hpp                    |  55 +-
 include/osmium/version.hpp                       |   6 +-
 include/protozero/byteswap.hpp                   |   6 +-
 include/protozero/exception.hpp                  |   8 +-
 include/protozero/iterators.hpp                  | 373 +++++++++++
 include/protozero/pbf_builder.hpp                |  20 +-
 include/protozero/pbf_message.hpp                |  10 +-
 include/protozero/pbf_reader.hpp                 | 763 +++++++++--------------
 include/protozero/pbf_writer.hpp                 | 240 ++++---
 include/protozero/types.hpp                      | 170 ++++-
 include/protozero/varint.hpp                     | 125 ++--
 include/protozero/version.hpp                    |  17 +-
 test/CMakeLists.txt                              |  41 +-
 test/include/catch.hpp                           | 280 +++++----
 test/t/basic/test_location.cpp                   |  33 +-
 test/t/geom/test_geos.cpp                        |  13 +-
 test/t/geom/test_wkb.cpp                         |  17 +-
 test/t/geom/test_wkt.cpp                         |  15 +
 test/t/io/test_string_table.cpp                  |  40 +-
 test/t/util/test_delta.cpp                       |  22 -
 47 files changed, 1966 insertions(+), 1184 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 9da37fa..9918112 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,24 +4,34 @@
 #
 #-----------------------------------------------------------------------------
 
-language: cpp
+language: generic
 
 sudo: false
 
+cache:
+  directories:
+    - $HOME/.ccache
+
+env:
+  global:
+    - CCACHE_TEMPDIR=/tmp/.ccache-temp
+    - CCACHE_COMPRESS=1
+    - CASHER_TIME_OUT=1000
+
 matrix:
   include:
 
     # 1/ Linux Clang Builds
     - os: linux
-      compiler: clang
+      compiler: linux-clang35-release
       addons:
         apt:
           sources: ['llvm-toolchain-precise-3.5', 'ubuntu-toolchain-r-test', 'boost-latest']
-          packages: ['clang-3.5', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
+          packages: ['clang-3.5', 'cmake', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
       env: COMPILER='clang++-3.5' BUILD_TYPE='Release'
 
     - os: linux
-      compiler: clang
+      compiler: linux-clang35-dev
       addons:
         apt:
           sources: ['llvm-toolchain-precise-3.5', 'ubuntu-toolchain-r-test', 'boost-latest']
@@ -30,42 +40,42 @@ matrix:
 
 
     - os: linux
-      compiler: clang
+      compiler: linux-clang37-release
       addons:
         apt:
-          sources: ['llvm-toolchain-precise-3.6', 'ubuntu-toolchain-r-test', 'boost-latest']
-          packages: ['clang-3.6', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
-      env: COMPILER='clang++-3.6' BUILD_TYPE='Release'
+          sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test', 'boost-latest']
+          packages: ['clang-3.7', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
+      env: COMPILER='clang++-3.7' BUILD_TYPE='Release'
 
     - os: linux
-      compiler: clang
+      compiler: linux-clang37-dev
       addons:
         apt:
-          sources: ['llvm-toolchain-precise-3.6', 'ubuntu-toolchain-r-test', 'boost-latest']
-          packages: ['clang-3.6', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
-      env: COMPILER='clang++-3.6' BUILD_TYPE='Dev'
+          sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test', 'boost-latest']
+          packages: ['clang-3.7', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
+      env: COMPILER='clang++-3.7' BUILD_TYPE='Dev'
 
 
     - os: linux
-      compiler: clang
+      compiler: linux-clang38-release
       addons:
         apt:
-          sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test', 'boost-latest']
-          packages: ['clang-3.7', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
-      env: COMPILER='clang++-3.7' BUILD_TYPE='Release'
+          sources: ['llvm-toolchain-precise-3.8', 'ubuntu-toolchain-r-test', 'boost-latest']
+          packages: ['clang-3.8', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
+      env: COMPILER='clang++-3.8' BUILD_TYPE='Release'
 
     - os: linux
-      compiler: clang
+      compiler: linux-clang38-dev
       addons:
         apt:
-          sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test', 'boost-latest']
-          packages: ['clang-3.7', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
-      env: COMPILER='clang++-3.7' BUILD_TYPE='Dev'
+          sources: ['llvm-toolchain-precise-3.8', 'ubuntu-toolchain-r-test', 'boost-latest']
+          packages: ['clang-3.8', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
+      env: COMPILER='clang++-3.8' BUILD_TYPE='Dev'
 
 
     # 2/ Linux GCC Builds
     - os: linux
-      compiler: gcc
+      compiler: linux-gcc48-release
       addons:
         apt:
           sources: ['ubuntu-toolchain-r-test', 'boost-latest']
@@ -73,7 +83,7 @@ matrix:
       env: COMPILER='g++-4.8' COMPILER_FLAGS='-Wno-return-type' BUILD_TYPE='Release'
 
     - os: linux
-      compiler: gcc
+      compiler: linux-gcc48-dev
       addons:
         apt:
           sources: ['ubuntu-toolchain-r-test', 'boost-latest']
@@ -82,7 +92,7 @@ matrix:
 
 
     - os: linux
-      compiler: gcc
+      compiler: linux-gcc49-release
       addons:
         apt:
           sources: ['ubuntu-toolchain-r-test', 'boost-latest']
@@ -90,7 +100,7 @@ matrix:
       env: COMPILER='g++-4.9' BUILD_TYPE='Release'
 
     - os: linux
-      compiler: gcc
+      compiler: linux-gcc49-dev
       addons:
         apt:
           sources: ['ubuntu-toolchain-r-test', 'boost-latest']
@@ -99,7 +109,7 @@ matrix:
 
 
     - os: linux
-      compiler: gcc
+      compiler: linux-gcc50-release
       addons:
         apt:
           sources: ['ubuntu-toolchain-r-test', 'boost-latest']
@@ -107,7 +117,7 @@ matrix:
       env: COMPILER='g++-5' BUILD_TYPE='Release'
 
     - os: linux
-      compiler: gcc
+      compiler: linux-gcc50-dev
       addons:
         apt:
           sources: ['ubuntu-toolchain-r-test', 'boost-latest']
@@ -118,24 +128,24 @@ matrix:
     # 3/ OSX Clang Builds
     - os: osx
       osx_image: xcode6.4
-      compiler: clang
-      env: COMPILER='clang++' BUILD_TYPE='Dev'
+      compiler: xcode64-clang-release
+      env: COMPILER='clang++' BUILD_TYPE='Release'
 
     - os: osx
       osx_image: xcode6.4
-      compiler: clang
-      env: COMPILER='clang++' BUILD_TYPE='Release'
+      compiler: xcode64-clang-dev
+      env: COMPILER='clang++' BUILD_TYPE='Dev'
 
 
     - os: osx
       osx_image: xcode7
-      compiler: clang
-      env: COMPILER='clang++' BUILD_TYPE='Dev'
+      compiler: xcode7-clang-release
+      env: COMPILER='clang++' BUILD_TYPE='Release'
 
     - os: osx
       osx_image: xcode7
-      compiler: clang
-      env: COMPILER='clang++' BUILD_TYPE='Release'
+      compiler: xcode7-clang-dev
+      env: COMPILER='clang++' BUILD_TYPE='Dev'
 
 
 install:
@@ -145,13 +155,14 @@ install:
   - |
     if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
       brew remove gdal
-      brew install cmake boost google-sparsehash gdal
+      brew install cmake boost google-sparsehash gdal || true
     fi
+  - cmake --version
 
 before_script:
   - cd ${TRAVIS_BUILD_DIR}
   - mkdir build && cd build
-  - CXX=${COMPILER} CXXFLAGS=${COMPILER_FLAGS} cmake -LA .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DOSM_TESTDATA="${TRAVIS_BUILD_DIR}/deps/osm-testdata"
+  - CXX=${COMPILER} CXXFLAGS=${COMPILER_FLAGS} cmake -LA .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_WITH_CCACHE=1 -DOSM_TESTDATA="${TRAVIS_BUILD_DIR}/deps/osm-testdata"
 
 script:
   - make VERBOSE=1 && ctest --output-on-failure
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b8a6a9..bb192ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,42 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 
+## [2.8.0] - 2016-08-04
+
+### Added
+
+- EWKT support.
+- Track `pop` type calls and queue underruns when `OSMIUM_DEBUG_QUEUE_SIZE`
+  environment variable is set.
+
+### Changed
+
+- Switched to newest protozero v1.4.0. This should deliver some speedups
+  when parsing PBF files. This also removes the DeltaEncodeIterator class,
+  which isn't needed any more.
+- Uses `std::unordered_map` instead of `std::map` in PBF string table code
+  speeding up writing of PBF files considerably.
+- Uses less memory when writing PBF files (smaller string table by default).
+- Removes dependency on sparsehash and boost program options libraries for
+  examples.
+- Cleaned up threaded queue code.
+
+### Fixed
+
+- A potentially very bad bug was fixed: When there are many and/or long strings
+  in tag keys and values and/or user names and/or relation roles, the string
+  table inside a PBF block would overflow. I have never seen this happen for
+  normal OSM data, but that doesn't mean it can't happen. The result is that
+  the strings will all be mixed up, keys for values, values for user names or
+  whatever.
+- Automatically set correct SRID when creating WKB and GEOS geometries.
+  Note that this changes the behaviour of libosmium when creating GEOS
+  geometries. Before we created them with -1 as SRID unless set otherwise.
+  Manual setting of the SRID on the GEOSGeometryFactory is now deprecated.
+- Allow coordinates of nodes in scientific notation when reading XML files.
+  This shouldn't be used really, but sometimes you can find them.
+
+
 ## [2.7.2] - 2016-06-08
 
 ### Changed
@@ -341,7 +377,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.7.2...HEAD
+[unreleased]: https://github.com/osmcode/libosmium/compare/v2.8.0...HEAD
+[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
 [2.7.0]: https://github.com/osmcode/libosmium/compare/v2.6.1...v2.7.0
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53251d0..c82cdd0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,8 +24,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev;Cover
 project(libosmium)
 
 set(LIBOSMIUM_VERSION_MAJOR 2)
-set(LIBOSMIUM_VERSION_MINOR 7)
-set(LIBOSMIUM_VERSION_PATCH 2)
+set(LIBOSMIUM_VERSION_MINOR 8)
+set(LIBOSMIUM_VERSION_PATCH 0)
 
 set(LIBOSMIUM_VERSION
     "${LIBOSMIUM_VERSION_MAJOR}.${LIBOSMIUM_VERSION_MINOR}.${LIBOSMIUM_VERSION_PATCH}")
@@ -63,6 +63,27 @@ option(WITH_PROFILING    "add flags needed for profiling" OFF)
 
 #-----------------------------------------------------------------------------
 #
+#  CCache support
+#
+#-----------------------------------------------------------------------------
+
+option(BUILD_WITH_CCACHE "build using ccache" OFF)
+
+if(BUILD_WITH_CCACHE)
+    find_program(CCACHE_PROGRAM ccache)
+    if(CCACHE_PROGRAM)
+        set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "CCACHE_CPP2=1 ${CCACHE_PROGRAM}")
+
+        # workaround for some clang versions
+        if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+            add_definitions(-Qunused-arguments)
+        endif()
+    endif()
+endif()
+
+
+#-----------------------------------------------------------------------------
+#
 #  Coverage support
 #
 #-----------------------------------------------------------------------------
diff --git a/appveyor.yml b/appveyor.yml
index 8244d98..e33b08c 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -72,7 +72,6 @@ build_script:
     -DCMAKE_BUILD_TYPE=%config%
     -DBUILD_HEADERS=OFF
     -DBOOST_ROOT=%LODEPSDIR%\boost
-    -DBoost_PROGRAM_OPTIONS_LIBRARY=%LODEPSDIR%\boost\lib\libboost_program_options-vc140-mt-1_58.lib
     -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
@@ -82,7 +81,6 @@ build_script:
   #  -DOsmium_DEBUG=TRUE
   #  -DCMAKE_BUILD_TYPE=%config%
   #  -DBOOST_ROOT=%LODEPSDIR%\boost
-  #  -DBoost_PROGRAM_OPTIONS_LIBRARY=%LODEPSDIR%\boost\lib\libboost_program_options-vc140-mt-1_57.lib
   #  -DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib
   #  -DZLIB_INCLUDE_DIR=%LODEPSDIR%\zlib\include
   #  -DEXPAT_LIBRARY=%LODEPSDIR%\expat\lib\libexpat.lib
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index a04a843..eff3363 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -28,7 +28,7 @@ set(EXAMPLES
 #  Examples depending on wingetopt
 #
 #-----------------------------------------------------------------------------
-set(GETOPT_EXAMPLES area_test convert serdump)
+set(GETOPT_EXAMPLES area_test convert index serdump)
 if(NOT GETOPT_MISSING)
     foreach(example ${GETOPT_EXAMPLES})
         list(APPEND EXAMPLE_LIBS_${example} ${GETOPT_LIBRARY})
@@ -44,36 +44,6 @@ endif()
 
 #-----------------------------------------------------------------------------
 #
-#  Examples depending on SparseHash
-#
-#-----------------------------------------------------------------------------
-if(NOT SPARSEHASH_FOUND)
-    list(REMOVE_ITEM EXAMPLES area_test)
-    message(STATUS "Configuring examples - Skipping examples because Google SparseHash not found:")
-    message(STATUS "  - osmium_area_test")
-endif()
-
-
-#-----------------------------------------------------------------------------
-#
-#  Examples depending on Boost Program Options
-#
-#-----------------------------------------------------------------------------
-unset(Boost_LIBRARIES)
-unset(Boost_FOUND)
-find_package(Boost 1.38 COMPONENTS program_options)
-
-if(Boost_PROGRAM_OPTIONS_FOUND)
-    list(APPEND EXAMPLE_LIBS_index ${Boost_PROGRAM_OPTIONS_LIBRARY})
-else()
-    list(REMOVE_ITEM EXAMPLES index)
-    message(STATUS "Configuring examples - Skipping examples because Boost program_options not found:")
-    message(STATUS "  - osmium_index")
-endif()
-
-
-#-----------------------------------------------------------------------------
-#
 #  Configure examples
 #
 #-----------------------------------------------------------------------------
diff --git a/examples/osmium_index.cpp b/examples/osmium_index.cpp
index b612140..8d5c5d9 100644
--- a/examples/osmium_index.cpp
+++ b/examples/osmium_index.cpp
@@ -12,7 +12,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <boost/program_options.hpp>
+#include <getopt.h>
 
 #include <osmium/index/map/dense_file_array.hpp>
 #include <osmium/index/map/sparse_file_array.hpp>
@@ -124,82 +124,105 @@ enum return_code : int {
     fatal     = 3
 };
 
-namespace po = boost::program_options;
-
 class Options {
 
-    po::variables_map vm;
+    std::vector<osmium::unsigned_object_id_type> m_ids;
+    std::string m_type;
+    std::string m_filename;
+    bool m_dump = false;
+    bool m_array_format = false;
+    bool m_list_format = false;
+
+    void print_help() {
+        std::cout << "Usage: osmium_index [OPTIONS]\n\n"
+                  << "-h, --help        Print this help message\n"
+                  << "-a, --array=FILE  Read given index file in array format\n"
+                  << "-l, --list=FILE   Read given index file in list format\n"
+                  << "-d, --dump        Dump contents of index file to STDOUT\n"
+                  << "-s, --search=ID   Search for given id (Option can appear multiple times)\n"
+                  << "-t, --type=TYPE   Type of value ('location' or 'offset')\n"
+        ;
+    }
 
 public:
 
     Options(int argc, char* argv[]) {
-        try {
-            po::options_description desc("Allowed options");
-            desc.add_options()
-                ("help,h", "Print this help message")
-                ("array,a", po::value<std::string>(), "Read given index file in array format")
-                ("list,l", po::value<std::string>(), "Read given index file in list format")
-                ("dump,d", "Dump contents of index file to STDOUT")
-                ("search,s", po::value<std::vector<osmium::unsigned_object_id_type>>(), "Search for given id (Option can appear multiple times)")
-                ("type,t", po::value<std::string>(), "Type of value ('location' or 'offset')")
-            ;
-
-            po::store(po::parse_command_line(argc, argv, desc), vm);
-            po::notify(vm);
-
-            if (vm.count("help")) {
-                std::cout << desc << "\n";
-                exit(return_code::okay);
+        static struct option long_options[] = {
+            {"array",  required_argument, 0, 'a'},
+            {"dump",         no_argument, 0, 'd'},
+            {"help",         no_argument, 0, 'h'},
+            {"list",   required_argument, 0, 'l'},
+            {"search", required_argument, 0, 's'},
+            {"type",   required_argument, 0, 't'},
+            {0, 0, 0, 0}
+        };
+
+        while (true) {
+            int c = getopt_long(argc, argv, "a:dhl:s:t:", long_options, 0);
+            if (c == -1) {
+                break;
             }
 
-            if (vm.count("array") && vm.count("list")) {
-                std::cerr << "Only option --array or --list allowed." << std::endl;
-                exit(return_code::fatal);
-            }
-
-            if (!vm.count("array") && !vm.count("list")) {
-                std::cerr << "Need one of option --array or --list." << std::endl;
-                exit(return_code::fatal);
+            switch (c) {
+                case 'a':
+                    m_array_format = true;
+                    m_filename = optarg;
+                    break;
+                case 'd':
+                    m_dump = true;
+                    break;
+                case 'h':
+                    print_help();
+                    exit(return_code::okay);
+                case 'l':
+                    m_list_format = true;
+                    m_filename = optarg;
+                    break;
+                case 's':
+                    m_ids.push_back(std::atoll(optarg));
+                    break;
+                case 't':
+                    m_type = optarg;
+                    if (m_type != "location" && m_type != "offset") {
+                        std::cerr << "Unknown type '" << m_type << "'. Must be 'location' or 'offset'.\n";
+                        exit(return_code::fatal);
+                    }
+                    break;
+                default:
+                    exit(return_code::fatal);
             }
+        }
 
-            if (!vm.count("type")) {
-                std::cerr << "Need --type argument." << std::endl;
-                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);
+        }
 
-            const std::string& type = vm["type"].as<std::string>();
-            if (type != "location" && type != "offset") {
-                std::cerr << "Unknown type '" << type << "'. Must be 'location' or 'offset'." << std::endl;
-                exit(return_code::fatal);
-            }
-        } catch (boost::program_options::error& e) {
-            std::cerr << "Error parsing command line: " << e.what() << std::endl;
+        if (m_type.empty()) {
+            std::cerr << "Need --type argument.\n";
             exit(return_code::fatal);
         }
+
     }
 
-    const std::string& filename() const {
-        if (vm.count("array")) {
-            return vm["array"].as<std::string>();
-        } else {
-            return vm["list"].as<std::string>();
-        }
+    const std::string& filename() const noexcept {
+        return m_filename;
     }
 
-    bool dense_format() const {
-        return vm.count("array") != 0;
+    bool dense_format() const noexcept {
+        return m_array_format;
     }
 
-    bool do_dump() const {
-        return vm.count("dump") != 0;
+    bool do_dump() const noexcept {
+        return m_dump;
     }
 
-    std::vector<osmium::unsigned_object_id_type> search_keys() const {
-        return vm["search"].as<std::vector<osmium::unsigned_object_id_type>>();
+    const std::vector<osmium::unsigned_object_id_type>& search_keys() const noexcept {
+        return m_ids;
     }
 
-    bool type_is(const char* type) const {
-        return vm["type"].as<std::string>() == type;
+    bool type_is(const char* type) const noexcept {
+        return m_type == type;
     }
 
 }; // class Options
diff --git a/include/osmium/area/assembler.hpp b/include/osmium/area/assembler.hpp
index f964d4b..9095302 100644
--- a/include/osmium/area/assembler.hpp
+++ b/include/osmium/area/assembler.hpp
@@ -153,7 +153,7 @@ namespace osmium {
              *
              * @deprecated Set debug_level directly.
              */
-            void enable_debug_output(bool d = true) {
+            OSMIUM_DEPRECATED void enable_debug_output(bool d = true) {
                 debug_level = d;
             }
 
@@ -573,8 +573,6 @@ namespace osmium {
                         const int64_t ly = location.y();
                         const auto z = (bx - ax)*(ly - ay) - (by - ay)*(lx - ax);
 
-//                        std::cerr << "z=" << z << "\n";
-
                         if (z >= 0) {
                             nesting += segment->is_reverse() ? -1 : 1;
                             if (debug()) {
diff --git a/include/osmium/geom/factory.hpp b/include/osmium/geom/factory.hpp
index 03c1015..f03eec5 100644
--- a/include/osmium/geom/factory.hpp
+++ b/include/osmium/geom/factory.hpp
@@ -169,7 +169,7 @@ namespace osmium {
             template <typename... TArgs>
             explicit GeometryFactory<TGeomImpl, TProjection>(TArgs&&... args) :
                 m_projection(),
-                m_impl(std::forward<TArgs>(args)...) {
+                m_impl(m_projection.epsg(), std::forward<TArgs>(args)...) {
             }
 
             /**
@@ -179,7 +179,7 @@ namespace osmium {
             template <typename... TArgs>
             explicit GeometryFactory<TGeomImpl, TProjection>(TProjection&& projection, TArgs&&... args) :
                 m_projection(std::move(projection)),
-                m_impl(std::forward<TArgs>(args)...) {
+                m_impl(m_projection.epsg(), std::forward<TArgs>(args)...) {
             }
 
             using projection_type   = TProjection;
diff --git a/include/osmium/geom/geojson.hpp b/include/osmium/geom/geojson.hpp
index 5291257..ffa9440 100644
--- a/include/osmium/geom/geojson.hpp
+++ b/include/osmium/geom/geojson.hpp
@@ -59,7 +59,7 @@ namespace osmium {
                 using multipolygon_type = std::string;
                 using ring_type         = std::string;
 
-                GeoJSONFactoryImpl(int precision = 7) :
+                GeoJSONFactoryImpl(int /* srid */, int precision = 7) :
                     m_precision(precision) {
                 }
 
diff --git a/include/osmium/geom/geos.hpp b/include/osmium/geom/geos.hpp
index 187c048..4a097e9 100644
--- a/include/osmium/geom/geos.hpp
+++ b/include/osmium/geom/geos.hpp
@@ -59,6 +59,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <osmium/geom/factory.hpp>
 #include <osmium/geom/coordinates.hpp>
+#include <osmium/util/compatibility.hpp>
 
 // MSVC doesn't support throw_with_nested yet
 #ifdef _MSC_VER
@@ -99,13 +100,23 @@ namespace osmium {
                 using multipolygon_type = std::unique_ptr<geos::geom::MultiPolygon>;
                 using ring_type         = std::unique_ptr<geos::geom::LinearRing>;
 
-                explicit GEOSFactoryImpl(geos::geom::GeometryFactory& geos_factory) :
+                explicit GEOSFactoryImpl(int /* srid */, geos::geom::GeometryFactory& geos_factory) :
                     m_precision_model(nullptr),
                     m_our_geos_factory(nullptr),
                     m_geos_factory(&geos_factory) {
                 }
 
-                explicit GEOSFactoryImpl(int srid = -1) :
+                /**
+                 * @deprecated Do not set SRID explicitly. It will be set to the
+                 *             correct value automatically.
+                 */
+                OSMIUM_DEPRECATED explicit GEOSFactoryImpl(int /* srid */, int srid) :
+                    m_precision_model(new geos::geom::PrecisionModel),
+                    m_our_geos_factory(new geos::geom::GeometryFactory(m_precision_model.get(), srid)),
+                    m_geos_factory(m_our_geos_factory.get()) {
+                }
+
+                explicit GEOSFactoryImpl(int srid) :
                     m_precision_model(new geos::geom::PrecisionModel),
                     m_our_geos_factory(new geos::geom::GeometryFactory(m_precision_model.get(), srid)),
                     m_geos_factory(m_our_geos_factory.get()) {
diff --git a/include/osmium/geom/ogr.hpp b/include/osmium/geom/ogr.hpp
index 45586e2..a457d66 100644
--- a/include/osmium/geom/ogr.hpp
+++ b/include/osmium/geom/ogr.hpp
@@ -77,7 +77,8 @@ namespace osmium {
 
             public:
 
-                OGRFactoryImpl() = default;
+                OGRFactoryImpl(int /* srid */) {
+                }
 
                 /* Point */
 
diff --git a/include/osmium/geom/rapid_geojson.hpp b/include/osmium/geom/rapid_geojson.hpp
index b54809a..340f0c1 100644
--- a/include/osmium/geom/rapid_geojson.hpp
+++ b/include/osmium/geom/rapid_geojson.hpp
@@ -59,7 +59,7 @@ namespace osmium {
                 using multipolygon_type = void;
                 using ring_type         = void;
 
-                RapidGeoJSONFactoryImpl(TWriter& writer) :
+                RapidGeoJSONFactoryImpl(int /* srid */, TWriter& writer) :
                     m_writer(&writer) {
                 }
 
diff --git a/include/osmium/geom/wkb.hpp b/include/osmium/geom/wkb.hpp
index 9df1823..efb07c6 100644
--- a/include/osmium/geom/wkb.hpp
+++ b/include/osmium/geom/wkb.hpp
@@ -79,9 +79,6 @@ namespace osmium {
 
             class WKBFactoryImpl {
 
-                /// OSM data always uses SRID 4326 (WGS84).
-                static constexpr uint32_t srid = 4326;
-
                 /**
                 * Type of WKB geometry.
                 * These definitions are from
@@ -112,6 +109,7 @@ namespace osmium {
 
                 std::string m_data;
                 uint32_t m_points {0};
+                int m_srid;
                 wkb_type m_wkb_type;
                 out_type m_out_type;
 
@@ -130,7 +128,7 @@ namespace osmium {
 #endif
                     if (m_wkb_type == wkb_type::ewkb) {
                         str_push(str, type | wkbSRID);
-                        str_push(str, srid);
+                        str_push(str, m_srid);
                     } else {
                         str_push(str, type);
                     }
@@ -154,7 +152,8 @@ namespace osmium {
                 using multipolygon_type = std::string;
                 using ring_type         = std::string;
 
-                explicit WKBFactoryImpl(wkb_type wtype = wkb_type::wkb, out_type otype = out_type::binary) :
+                explicit WKBFactoryImpl(int srid, wkb_type wtype = wkb_type::wkb, out_type otype = out_type::binary) :
+                    m_srid(srid),
                     m_wkb_type(wtype),
                     m_out_type(otype) {
                 }
diff --git a/include/osmium/geom/wkt.hpp b/include/osmium/geom/wkt.hpp
index 57a6625..6113674 100644
--- a/include/osmium/geom/wkt.hpp
+++ b/include/osmium/geom/wkt.hpp
@@ -45,12 +45,22 @@ namespace osmium {
 
     namespace geom {
 
+        enum class wkt_type : bool {
+            wkt  = false,
+            ewkt = true
+        }; // enum class wkt_type
+
         namespace detail {
 
             class WKTFactoryImpl {
 
+                std::string m_srid_prefix;
                 std::string m_str;
                 int m_precision;
+                wkt_type m_wkt_type;
+
+                void init_srid() {
+                }
 
             public:
 
@@ -60,14 +70,22 @@ namespace osmium {
                 using multipolygon_type = std::string;
                 using ring_type         = std::string;
 
-                WKTFactoryImpl(int precision = 7) :
-                    m_precision(precision) {
+                WKTFactoryImpl(int srid, int precision = 7, wkt_type wtype = wkt_type::wkt) :
+                    m_srid_prefix(),
+                    m_precision(precision),
+                    m_wkt_type(wtype) {
+                    if (m_wkt_type == wkt_type::ewkt) {
+                        m_srid_prefix = "SRID=";
+                        m_srid_prefix += std::to_string(srid);
+                        m_srid_prefix += ';';
+                    }
                 }
 
                 /* Point */
 
                 point_type make_point(const osmium::geom::Coordinates& xy) const {
-                    std::string str {"POINT"};
+                    std::string str {m_srid_prefix};
+                    str += "POINT";
                     xy.append_to_string(str, '(', ' ', ')', m_precision);
                     return str;
                 }
@@ -75,7 +93,8 @@ namespace osmium {
                 /* LineString */
 
                 void linestring_start() {
-                    m_str = "LINESTRING(";
+                    m_str = m_srid_prefix;
+                    m_str += "LINESTRING(";
                 }
 
                 void linestring_add_location(const osmium::geom::Coordinates& xy) {
@@ -97,7 +116,8 @@ namespace osmium {
                 /* MultiPolygon */
 
                 void multipolygon_start() {
-                    m_str = "MULTIPOLYGON(";
+                    m_str = m_srid_prefix;
+                    m_str += "MULTIPOLYGON(";
                 }
 
                 void multipolygon_polygon_start() {
diff --git a/include/osmium/io/detail/debug_output_format.hpp b/include/osmium/io/detail/debug_output_format.hpp
index 691dc0c..7ae0807 100644
--- a/include/osmium/io/detail/debug_output_format.hpp
+++ b/include/osmium/io/detail/debug_output_format.hpp
@@ -236,7 +236,7 @@ namespace osmium {
                 void write_location(const osmium::Location& location) {
                     write_fieldname("lon/lat");
                     *m_out += "  ";
-                    location.as_string(std::back_inserter(*m_out));
+                    location.as_string_without_check(std::back_inserter(*m_out));
                     if (!location.valid()) {
                         write_error(" INVALID LOCATION!");
                     }
diff --git a/include/osmium/io/detail/pbf_decoder.hpp b/include/osmium/io/detail/pbf_decoder.hpp
index 1c0d2eb..6980c48 100644
--- a/include/osmium/io/detail/pbf_decoder.hpp
+++ b/include/osmium/io/detail/pbf_decoder.hpp
@@ -65,14 +65,14 @@ namespace osmium {
 
         namespace detail {
 
-            using ptr_len_type = std::pair<const char*, size_t>;
+            using protozero::data_view;
             using osm_string_len_type = std::pair<const char*, osmium::string_size_type>;
 
             class PBFPrimitiveBlockDecoder {
 
                 static constexpr size_t initial_buffer_size = 2 * 1024 * 1024;
 
-                ptr_len_type m_data;
+                data_view m_data;
                 std::vector<osm_string_len_type> m_stringtable;
 
                 int64_t m_lon_offset = 0;
@@ -84,18 +84,18 @@ namespace osmium {
 
                 osmium::memory::Buffer m_buffer { initial_buffer_size };
 
-                void decode_stringtable(const ptr_len_type& data) {
+                void decode_stringtable(const data_view& data) {
                     if (!m_stringtable.empty()) {
                         throw osmium::pbf_error("more than one stringtable in pbf file");
                     }
 
                     protozero::pbf_message<OSMFormat::StringTable> pbf_string_table(data);
                     while (pbf_string_table.next(OSMFormat::StringTable::repeated_bytes_s)) {
-                        auto str_len = pbf_string_table.get_data();
-                        if (str_len.second > osmium::max_osm_string_length) {
+                        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");
                         }
-                        m_stringtable.emplace_back(str_len.first, osmium::string_size_type(str_len.second));
+                        m_stringtable.emplace_back(str_view.data(), osmium::string_size_type(str_view.size()));
                     }
                 }
 
@@ -104,7 +104,7 @@ namespace osmium {
                     while (pbf_primitive_block.next()) {
                         switch (pbf_primitive_block.tag()) {
                             case OSMFormat::PrimitiveBlock::required_StringTable_stringtable:
-                                decode_stringtable(pbf_primitive_block.get_data());
+                                decode_stringtable(pbf_primitive_block.get_view());
                                 break;
                             case OSMFormat::PrimitiveBlock::optional_int32_granularity:
                                 m_granularity = pbf_primitive_block.get_int32();
@@ -132,28 +132,28 @@ namespace osmium {
                             switch (pbf_primitive_group.tag()) {
                                 case OSMFormat::PrimitiveGroup::repeated_Node_nodes:
                                     if (m_read_types & osmium::osm_entity_bits::node) {
-                                        decode_node(pbf_primitive_group.get_data());
+                                        decode_node(pbf_primitive_group.get_view());
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
                                     break;
                                 case OSMFormat::PrimitiveGroup::optional_DenseNodes_dense:
                                     if (m_read_types & osmium::osm_entity_bits::node) {
-                                        decode_dense_nodes(pbf_primitive_group.get_data());
+                                        decode_dense_nodes(pbf_primitive_group.get_view());
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
                                     break;
                                 case OSMFormat::PrimitiveGroup::repeated_Way_ways:
                                     if (m_read_types & osmium::osm_entity_bits::way) {
-                                        decode_way(pbf_primitive_group.get_data());
+                                        decode_way(pbf_primitive_group.get_view());
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
                                     break;
                                 case OSMFormat::PrimitiveGroup::repeated_Relation_relations:
                                     if (m_read_types & osmium::osm_entity_bits::relation) {
-                                        decode_relation(pbf_primitive_group.get_data());
+                                        decode_relation(pbf_primitive_group.get_view());
                                     } else {
                                         pbf_primitive_group.skip();
                                     }
@@ -165,7 +165,7 @@ namespace osmium {
                     }
                 }
 
-                osm_string_len_type decode_info(const ptr_len_type& data, osmium::OSMObject& object) {
+                osm_string_len_type decode_info(const data_view& data, osmium::OSMObject& object) {
                     osm_string_len_type user = std::make_pair("", 0);
 
                     protozero::pbf_message<OSMFormat::Info> pbf_info(data);
@@ -209,15 +209,15 @@ namespace osmium {
                     return user;
                 }
 
-                using kv_type = std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator>;
+                using kv_type = protozero::iterator_range<protozero::pbf_reader::const_uint32_iterator>;
 
                 void build_tag_list(osmium::builder::Builder& builder, const kv_type& keys, const kv_type& vals) {
-                    if (keys.first != keys.second) {
+                    if (!keys.empty()) {
                         osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
-                        auto kit = keys.first;
-                        auto vit = vals.first;
-                        while (kit != keys.second) {
-                            if (vit == vals.second) {
+                        auto kit = keys.begin();
+                        auto vit = vals.begin();
+                        while (kit != keys.end()) {
+                            if (vit == vals.end()) {
                                 // this is against the spec, must have same number of elements
                                 throw osmium::pbf_error("PBF format error");
                             }
@@ -232,7 +232,7 @@ namespace osmium {
                     return int32_t((c * m_granularity + m_lon_offset) / resolution_convert);
                 }
 
-                void decode_node(const ptr_len_type& data) {
+                void decode_node(const data_view& data) {
                     osmium::builder::NodeBuilder builder(m_buffer);
                     osmium::Node& node = builder.object();
 
@@ -256,7 +256,7 @@ namespace osmium {
                                 vals = pbf_node.get_packed_uint32();
                                 break;
                             case OSMFormat::Node::optional_Info_info:
-                                user = decode_info(pbf_node.get_data(), builder.object());
+                                user = decode_info(pbf_node.get_view(), builder.object());
                                 break;
                             case OSMFormat::Node::required_sint64_lat:
                                 lat = pbf_node.get_sint64();
@@ -287,14 +287,14 @@ namespace osmium {
                     m_buffer.commit();
                 }
 
-                void decode_way(const ptr_len_type& data) {
+                void decode_way(const data_view& data) {
                     osmium::builder::WayBuilder builder(m_buffer);
 
                     kv_type keys;
                     kv_type vals;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> refs;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> lats;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> lons;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> refs;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lats;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lons;
 
                     osm_string_len_type user = { "", 0 };
 
@@ -311,7 +311,7 @@ namespace osmium {
                                 vals = pbf_way.get_packed_uint32();
                                 break;
                             case OSMFormat::Way::optional_Info_info:
-                                user = decode_info(pbf_way.get_data(), builder.object());
+                                user = decode_info(pbf_way.get_view(), builder.object());
                                 break;
                             case OSMFormat::Way::packed_sint64_refs:
                                 refs = pbf_way.get_packed_sint64();
@@ -329,22 +329,25 @@ namespace osmium {
 
                     builder.add_user(user.first, user.second);
 
-                    if (refs.first != refs.second) {
+                    if (!refs.empty()) {
                         osmium::builder::WayNodeListBuilder wnl_builder(m_buffer, &builder);
                         osmium::util::DeltaDecode<int64_t> ref;
-                        if (lats.first == lats.second) {
-                            while (refs.first != refs.second) {
-                                wnl_builder.add_node_ref(ref.update(*refs.first++));
+                        if (lats.empty()) {
+                            for (const auto& ref_value : refs) {
+                                wnl_builder.add_node_ref(ref.update(ref_value));
                             }
                         } else {
                             osmium::util::DeltaDecode<int64_t> lon;
                             osmium::util::DeltaDecode<int64_t> lat;
-                            while (refs.first != refs.second && lons.first != lons.second && lats.first != lats.second) {
+                            while (!refs.empty() && !lons.empty() && !lats.empty()) {
                                 wnl_builder.add_node_ref(
-                                    ref.update(*refs.first++),
-                                    osmium::Location{convert_pbf_coordinate(lon.update(*lons.first++)),
-                                                     convert_pbf_coordinate(lat.update(*lats.first++))}
+                                    ref.update(refs.front()),
+                                    osmium::Location{convert_pbf_coordinate(lon.update(lons.front())),
+                                                     convert_pbf_coordinate(lat.update(lats.front()))}
                                 );
+                                refs.drop_front();
+                                lons.drop_front();
+                                lats.drop_front();
                             }
                         }
                     }
@@ -354,14 +357,14 @@ namespace osmium {
                     m_buffer.commit();
                 }
 
-                void decode_relation(const ptr_len_type& data) {
+                void decode_relation(const data_view& data) {
                     osmium::builder::RelationBuilder builder(m_buffer);
 
                     kv_type keys;
                     kv_type vals;
-                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator> roles;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> refs;
-                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator> types;
+                    protozero::iterator_range<protozero::pbf_reader::const_int32_iterator> roles;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> refs;
+                    protozero::iterator_range<protozero::pbf_reader::const_int32_iterator> types;
 
                     osm_string_len_type user = { "", 0 };
 
@@ -378,7 +381,7 @@ namespace osmium {
                                 vals = pbf_relation.get_packed_uint32();
                                 break;
                             case OSMFormat::Relation::optional_Info_info:
-                                user = decode_info(pbf_relation.get_data(), builder.object());
+                                user = decode_info(pbf_relation.get_view(), builder.object());
                                 break;
                             case OSMFormat::Relation::packed_int32_roles_sid:
                                 roles = pbf_relation.get_packed_int32();
@@ -396,21 +399,24 @@ namespace osmium {
 
                     builder.add_user(user.first, user.second);
 
-                    if (refs.first != refs.second) {
+                    if (!refs.empty()) {
                         osmium::builder::RelationMemberListBuilder rml_builder(m_buffer, &builder);
                         osmium::util::DeltaDecode<int64_t> ref;
-                        while (roles.first != roles.second && refs.first != refs.second && types.first != types.second) {
-                            const auto& r = m_stringtable.at(*roles.first++);
-                            int type = *types.first++;
+                        while (!roles.empty() && !refs.empty() && !types.empty()) {
+                            const auto& r = m_stringtable.at(roles.front());
+                            int type = types.front();
                             if (type < 0 || type > 2) {
                                 throw osmium::pbf_error("unknown relation member type");
                             }
                             rml_builder.add_member(
                                 osmium::item_type(type + 1),
-                                ref.update(*refs.first++),
+                                ref.update(refs.front()),
                                 r.first,
                                 r.second
                             );
+                            roles.drop_front();
+                            refs.drop_front();
+                            types.drop_front();
                         }
                     }
 
@@ -419,22 +425,22 @@ namespace osmium {
                     m_buffer.commit();
                 }
 
-                void decode_dense_nodes(const ptr_len_type& data) {
+                void decode_dense_nodes(const data_view& data) {
                     bool has_info     = false;
                     bool has_visibles = false;
 
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> ids;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> lats;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> lons;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> ids;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lats;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lons;
 
-                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator>  tags;
+                    protozero::iterator_range<protozero::pbf_reader::const_int32_iterator>  tags;
 
-                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator>  versions;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> timestamps;
-                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> changesets;
-                    std::pair<protozero::pbf_reader::const_sint32_iterator, protozero::pbf_reader::const_sint32_iterator> uids;
-                    std::pair<protozero::pbf_reader::const_sint32_iterator, protozero::pbf_reader::const_sint32_iterator> user_sids;
-                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator>  visibles;
+                    protozero::iterator_range<protozero::pbf_reader::const_int32_iterator>  versions;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> timestamps;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> changesets;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint32_iterator> uids;
+                    protozero::iterator_range<protozero::pbf_reader::const_sint32_iterator> user_sids;
+                    protozero::iterator_range<protozero::pbf_reader::const_int32_iterator>  visibles;
 
                     protozero::pbf_message<OSMFormat::DenseNodes> pbf_dense_nodes(data);
                     while (pbf_dense_nodes.next()) {
@@ -495,11 +501,11 @@ namespace osmium {
                     osmium::util::DeltaDecode<int64_t> dense_changeset;
                     osmium::util::DeltaDecode<int64_t> dense_timestamp;
 
-                    auto tag_it = tags.first;
+                    auto tag_it = tags.begin();
 
-                    while (ids.first != ids.second) {
-                        if (lons.first == lons.second ||
-                            lats.first == lats.second) {
+                    while (!ids.empty()) {
+                        if (lons.empty() ||
+                            lats.empty()) {
                             // this is against the spec, must have same number of elements
                             throw osmium::pbf_error("PBF format error");
                         }
@@ -509,43 +515,50 @@ namespace osmium {
                         osmium::builder::NodeBuilder builder(m_buffer);
                         osmium::Node& node = builder.object();
 
-                        node.set_id(dense_id.update(*ids.first++));
+                        node.set_id(dense_id.update(ids.front()));
+                        ids.drop_front();
 
                         if (has_info) {
-                            if (versions.first == versions.second ||
-                                changesets.first == changesets.second ||
-                                timestamps.first == timestamps.second ||
-                                uids.first == uids.second ||
-                                user_sids.first == user_sids.second) {
+                            if (versions.empty() ||
+                                changesets.empty() ||
+                                timestamps.empty() ||
+                                uids.empty() ||
+                                user_sids.empty()) {
                                 // this is against the spec, must have same number of elements
                                 throw osmium::pbf_error("PBF format error");
                             }
 
-                            auto version = *versions.first++;
+                            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.first++);
+                            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");
                             }
                             node.set_changeset(static_cast<osmium::changeset_id_type>(changeset_id));
 
-                            node.set_timestamp(dense_timestamp.update(*timestamps.first++) * m_date_factor / 1000);
-                            node.set_uid_from_signed(static_cast<osmium::signed_user_id_type>(dense_uid.update(*uids.first++)));
+                            node.set_timestamp(dense_timestamp.update(timestamps.front()) * m_date_factor / 1000);
+                            timestamps.drop_front();
+                            node.set_uid_from_signed(static_cast<osmium::signed_user_id_type>(dense_uid.update(uids.front())));
+                            uids.drop_front();
 
                             if (has_visibles) {
-                                if (visibles.first == visibles.second) {
+                                if (visibles.empty()) {
                                     // this is against the spec, must have same number of elements
                                     throw osmium::pbf_error("PBF format error");
                                 }
-                                visible = (*visibles.first++) != 0;
+                                visible = (visibles.front() != 0);
+                                visibles.drop_front();
                             }
                             node.set_visible(visible);
 
-                            const auto& u = m_stringtable.at(dense_user_sid.update(*user_sids.first++));
+                            const auto& u = m_stringtable.at(dense_user_sid.update(user_sids.front()));
+                            user_sids.drop_front();
                             builder.add_user(u.first, u.second);
                         } else {
                             builder.add_user("");
@@ -553,8 +566,10 @@ namespace osmium {
 
                         // even if the node isn't visible, there's still a record
                         // of its lat/lon in the dense arrays.
-                        const auto lon = dense_longitude.update(*lons.first++);
-                        const auto lat = dense_latitude.update(*lats.first++);
+                        const auto lon = dense_longitude.update(lons.front());
+                        lons.drop_front();
+                        const auto lat = dense_latitude.update(lats.front());
+                        lats.drop_front();
                         if (visible) {
                             builder.object().set_location(osmium::Location(
                                     convert_pbf_coordinate(lon),
@@ -562,18 +577,18 @@ namespace osmium {
                             ));
                         }
 
-                        if (tag_it != tags.second) {
+                        if (tag_it != tags.end()) {
                             osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
-                            while (tag_it != tags.second && *tag_it != 0) {
+                            while (tag_it != tags.end() && *tag_it != 0) {
                                 const auto& k = m_stringtable.at(*tag_it++);
-                                if (tag_it == tags.second) {
+                                if (tag_it == tags.end()) {
                                     throw osmium::pbf_error("PBF format error"); // this is against the spec, keys/vals must come in pairs
                                 }
                                 const auto& v = m_stringtable.at(*tag_it++);
                                 tl_builder.add_tag(k.first, k.second, v.first, v.second);
                             }
 
-                            if (tag_it != tags.second) {
+                            if (tag_it != tags.end()) {
                                 ++tag_it;
                             }
                         }
@@ -585,7 +600,7 @@ namespace osmium {
 
             public:
 
-                PBFPrimitiveBlockDecoder(const ptr_len_type& data, osmium::osm_entity_bits::type read_types) :
+                PBFPrimitiveBlockDecoder(const data_view& data, osmium::osm_entity_bits::type read_types) :
                     m_data(data),
                     m_read_types(read_types) {
                 }
@@ -611,17 +626,17 @@ namespace osmium {
 
             }; // class PBFPrimitiveBlockDecoder
 
-            inline ptr_len_type decode_blob(const std::string& blob_data, std::string& output) {
+            inline data_view decode_blob(const std::string& blob_data, std::string& output) {
                 int32_t raw_size = 0;
-                std::pair<const char*, protozero::pbf_length_type> zlib_data = {nullptr, 0};
+                protozero::data_view zlib_data;
 
                 protozero::pbf_message<FileFormat::Blob> pbf_blob(blob_data);
                 while (pbf_blob.next()) {
                     switch (pbf_blob.tag()) {
                         case FileFormat::Blob::optional_bytes_raw:
                             {
-                                auto data_len = pbf_blob.get_data();
-                                if (data_len.second > max_uncompressed_blob_size) {
+                                auto data_len = pbf_blob.get_view();
+                                if (data_len.size() > max_uncompressed_blob_size) {
                                     throw osmium::pbf_error("illegal blob size");
                                 }
                                 return data_len;
@@ -633,7 +648,7 @@ namespace osmium {
                             }
                             break;
                         case FileFormat::Blob::optional_bytes_zlib_data:
-                            zlib_data = pbf_blob.get_data();
+                            zlib_data = pbf_blob.get_view();
                             break;
                         case FileFormat::Blob::optional_bytes_lzma_data:
                             throw osmium::pbf_error("lzma blobs not implemented");
@@ -642,10 +657,10 @@ namespace osmium {
                     }
                 }
 
-                if (zlib_data.second != 0 && raw_size != 0) {
+                if (zlib_data.size() != 0 && raw_size != 0) {
                     return osmium::io::detail::zlib_uncompress_string(
-                        zlib_data.first,
-                        static_cast<unsigned long>(zlib_data.second),
+                        zlib_data.data(),
+                        static_cast<unsigned long>(zlib_data.size()),
                         static_cast<unsigned long>(raw_size),
                         output
                     );
@@ -654,7 +669,7 @@ namespace osmium {
                 throw osmium::pbf_error("blob contains no data");
             }
 
-            inline osmium::Box decode_header_bbox(const ptr_len_type& data) {
+            inline osmium::Box decode_header_bbox(const data_view& data) {
                     int64_t left   = std::numeric_limits<int64_t>::max();
                     int64_t right  = std::numeric_limits<int64_t>::max();
                     int64_t top    = std::numeric_limits<int64_t>::max();
@@ -694,7 +709,7 @@ namespace osmium {
                     return box;
             }
 
-            inline osmium::io::Header decode_header_block(const ptr_len_type& data) {
+            inline osmium::io::Header decode_header_block(const data_view& data) {
                 osmium::io::Header header;
                 int i = 0;
 
@@ -702,20 +717,20 @@ namespace osmium {
                 while (pbf_header_block.next()) {
                     switch (pbf_header_block.tag()) {
                         case OSMFormat::HeaderBlock::optional_HeaderBBox_bbox:
-                            header.add_box(decode_header_bbox(pbf_header_block.get_data()));
+                            header.add_box(decode_header_bbox(pbf_header_block.get_view()));
                             break;
                         case OSMFormat::HeaderBlock::repeated_string_required_features:
                             {
-                                auto feature = pbf_header_block.get_data();
-                                if (!strncmp("OsmSchema-V0.6", feature.first, feature.second)) {
+                                auto feature = pbf_header_block.get_view();
+                                if (!std::strncmp("OsmSchema-V0.6", feature.data(), feature.size())) {
                                     // intentionally left blank
-                                } else if (!strncmp("DenseNodes", feature.first, feature.second)) {
+                                } else if (!std::strncmp("DenseNodes", feature.data(), feature.size())) {
                                     header.set("pbf_dense_nodes", true);
-                                } else if (!strncmp("HistoricalInformation", feature.first, feature.second)) {
+                                } else if (!std::strncmp("HistoricalInformation", feature.data(), feature.size())) {
                                     header.set_has_multiple_object_versions(true);
                                 } else {
                                     std::string msg("required feature not supported: ");
-                                    msg.append(feature.first, feature.second);
+                                    msg.append(feature.data(), feature.size());
                                     throw osmium::pbf_error(msg);
                                 }
                             }
diff --git a/include/osmium/io/detail/pbf_input_format.hpp b/include/osmium/io/detail/pbf_input_format.hpp
index 0c6a9ef..8361c72 100644
--- a/include/osmium/io/detail/pbf_input_format.hpp
+++ b/include/osmium/io/detail/pbf_input_format.hpp
@@ -123,13 +123,13 @@ namespace osmium {
                  * type. Return the size of the following Blob.
                  */
                 size_t decode_blob_header(protozero::pbf_message<FileFormat::BlobHeader>&& pbf_blob_header, const char* expected_type) {
-                    std::pair<const char*, size_t> blob_header_type;
+                    protozero::data_view blob_header_type;
                     size_t blob_header_datasize = 0;
 
                     while (pbf_blob_header.next()) {
                         switch (pbf_blob_header.tag()) {
                             case FileFormat::BlobHeader::required_string_type:
-                                blob_header_type = pbf_blob_header.get_data();
+                                blob_header_type = pbf_blob_header.get_view();
                                 break;
                             case FileFormat::BlobHeader::required_int32_datasize:
                                 blob_header_datasize = pbf_blob_header.get_int32();
@@ -143,7 +143,7 @@ namespace osmium {
                         throw osmium::pbf_error("PBF format error: BlobHeader.datasize missing or zero.");
                     }
 
-                    if (strncmp(expected_type, blob_header_type.first, blob_header_type.second)) {
+                    if (std::strncmp(expected_type, blob_header_type.data(), blob_header_type.size())) {
                         throw osmium::pbf_error("blob does not have expected type (OSMHeader in first blob, OSMData in following blobs)");
                     }
 
diff --git a/include/osmium/io/detail/pbf_output_format.hpp b/include/osmium/io/detail/pbf_output_format.hpp
index 5046087..d5f8cd4 100644
--- a/include/osmium/io/detail/pbf_output_format.hpp
+++ b/include/osmium/io/detail/pbf_output_format.hpp
@@ -579,33 +579,29 @@ namespace osmium {
                     pbf_way.add_int64(OSMFormat::Way::required_int64_id, way.id());
                     add_meta(way, pbf_way);
 
-                    const auto& nodes = way.nodes();
-
-                    static auto map_node_ref = [](osmium::NodeRefList::const_iterator node_ref) noexcept -> osmium::object_id_type {
-                        return node_ref->ref();
-                    };
-                    using it_type = osmium::util::DeltaEncodeIterator<osmium::NodeRefList::const_iterator, decltype(map_node_ref), osmium::object_id_type>;
-                    it_type first { nodes.cbegin(), nodes.cend(), map_node_ref };
-                    it_type last { nodes.cend(), nodes.cend(), map_node_ref };
-                    pbf_way.add_packed_sint64(OSMFormat::Way::packed_sint64_refs, first, last);
+                    {
+                        osmium::util::DeltaEncode<object_id_type, int64_t> delta_id;
+                        protozero::packed_field_sint64 field{pbf_way, protozero::pbf_tag_type(OSMFormat::Way::packed_sint64_refs)};
+                        for (const auto& node_ref : way.nodes()) {
+                            field.add_element(delta_id.update(node_ref.ref()));
+                        }
+                    }
 
                     if (m_options.locations_on_ways) {
-                        static auto map_node_x = [](osmium::NodeRefList::const_iterator node_ref) noexcept -> int64_t {
-                            return lonlat2int(node_ref->location().lon_without_check());
-                        };
-                        static auto map_node_y = [](osmium::NodeRefList::const_iterator node_ref) noexcept -> int64_t {
-                            return lonlat2int(node_ref->location().lat_without_check());
-                        };
-                        using it_type_x = osmium::util::DeltaEncodeIterator<osmium::NodeRefList::const_iterator, decltype(map_node_x), int64_t>;
-                        using it_type_y = osmium::util::DeltaEncodeIterator<osmium::NodeRefList::const_iterator, decltype(map_node_y), int64_t>;
-
-                        it_type_x first_x { nodes.cbegin(), nodes.cend(), map_node_x };
-                        it_type_x last_x { nodes.cend(), nodes.cend(), map_node_x };
-                        pbf_way.add_packed_sint64(OSMFormat::Way::packed_sint64_lon, first_x, last_x);
-
-                        it_type_y first_y { nodes.cbegin(), nodes.cend(), map_node_y };
-                        it_type_y last_y { nodes.cend(), nodes.cend(), map_node_y };
-                        pbf_way.add_packed_sint64(OSMFormat::Way::packed_sint64_lat, first_y, last_y);
+                        {
+                            osmium::util::DeltaEncode<int64_t, int64_t> delta_id;
+                            protozero::packed_field_sint64 field{pbf_way, protozero::pbf_tag_type(OSMFormat::Way::packed_sint64_lon)};
+                            for (const auto& node_ref : way.nodes()) {
+                                field.add_element(delta_id.update(lonlat2int(node_ref.location().lon_without_check())));
+                            }
+                        }
+                        {
+                            osmium::util::DeltaEncode<int64_t, int64_t> delta_id;
+                            protozero::packed_field_sint64 field{pbf_way, protozero::pbf_tag_type(OSMFormat::Way::packed_sint64_lat)};
+                            for (const auto& node_ref : way.nodes()) {
+                                field.add_element(delta_id.update(lonlat2int(node_ref.location().lat_without_check())));
+                            }
+                        }
                     }
                 }
 
@@ -623,14 +619,13 @@ namespace osmium {
                         }
                     }
 
-                    static auto map_member_ref = [](osmium::RelationMemberList::const_iterator member) noexcept -> osmium::object_id_type {
-                        return member->ref();
-                    };
-                    using it_type = osmium::util::DeltaEncodeIterator<osmium::RelationMemberList::const_iterator, decltype(map_member_ref), osmium::object_id_type>;
-                    const auto& members = relation.members();
-                    it_type first { members.cbegin(), members.cend(), map_member_ref };
-                    it_type last { members.cend(), members.cend(), map_member_ref };
-                    pbf_relation.add_packed_sint64(OSMFormat::Relation::packed_sint64_memids, first, last);
+                    {
+                        osmium::util::DeltaEncode<object_id_type, int64_t> delta_id;
+                        protozero::packed_field_sint64 field{pbf_relation, protozero::pbf_tag_type(OSMFormat::Relation::packed_sint64_memids)};
+                        for (const auto& member : relation.members()) {
+                            field.add_element(delta_id.update(member.ref()));
+                        }
+                    }
 
                     {
                         protozero::packed_field_int32 field{pbf_relation, protozero::pbf_tag_type(OSMFormat::Relation::packed_MemberType_types)};
diff --git a/include/osmium/io/detail/string_table.hpp b/include/osmium/io/detail/string_table.hpp
index b989530..995e2cf 100644
--- a/include/osmium/io/detail/string_table.hpp
+++ b/include/osmium/io/detail/string_table.hpp
@@ -39,8 +39,8 @@ DEALINGS IN THE SOFTWARE.
 #include <cstring>
 #include <iterator>
 #include <list>
-#include <map>
 #include <string>
+#include <unordered_map>
 
 #include <osmium/io/detail/pbf.hpp>
 
@@ -69,8 +69,8 @@ namespace osmium {
                 std::list<std::string> m_chunks;
 
                 void add_chunk() {
-                    m_chunks.push_front(std::string());
-                    m_chunks.front().reserve(m_chunk_size);
+                    m_chunks.emplace_back();
+                    m_chunks.back().reserve(m_chunk_size);
                 }
 
             public:
@@ -82,6 +82,7 @@ namespace osmium {
                 }
 
                 void clear() noexcept {
+                    assert(!m_chunks.empty());
                     m_chunks.erase(std::next(m_chunks.begin()), m_chunks.end());
                     m_chunks.front().clear();
                 }
@@ -97,16 +98,16 @@ namespace osmium {
 
                     assert(len <= m_chunk_size);
 
-                    size_t chunk_len = m_chunks.front().size();
-                    if (chunk_len + len > m_chunks.front().capacity()) {
+                    size_t chunk_len = m_chunks.back().size();
+                    if (chunk_len + len > m_chunks.back().capacity()) {
                         add_chunk();
                         chunk_len = 0;
                     }
 
-                    m_chunks.front().append(string);
-                    m_chunks.front().append(1, '\0');
+                    m_chunks.back().append(string);
+                    m_chunks.back().append(1, '\0');
 
-                    return m_chunks.front().c_str() + chunk_len;
+                    return m_chunks.back().c_str() + chunk_len;
                 }
 
                 class const_iterator {
@@ -191,18 +192,33 @@ namespace osmium {
                 }
 
                 size_t get_used_bytes_in_last_chunk() const noexcept {
-                    return m_chunks.front().size();
+                    return m_chunks.back().size();
                 }
 
             }; // class StringStore
 
-            struct StrComp {
+            struct str_equal {
 
-                bool operator()(const char* lhs, const char* rhs) const {
-                    return std::strcmp(lhs, rhs) < 0;
+                bool operator()(const char* lhs, const char* rhs) const noexcept {
+                    return lhs == rhs || std::strcmp(lhs, rhs) == 0;
                 }
 
-            }; // struct StrComp
+            }; // struct str_equal
+
+            struct djb2_hash {
+
+                size_t operator()(const char* str) const noexcept {
+                    size_t hash = 5381;
+                    int c;
+
+                    while ((c = *str++)) {
+                        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+                    }
+
+                    return hash;
+                }
+
+            }; // struct djb2_hash
 
             class StringTable {
 
@@ -213,14 +229,23 @@ namespace osmium {
                 // Blob.
                 static constexpr const uint32_t max_entries = max_uncompressed_blob_size;
 
+                // There is one string table per PBF primitive block. Most of
+                // them are really small, because most blocks are full of nodes
+                // with no tags. But string tables can get really large for
+                // ways with many tags or for large relations.
+                // The chosen size is enough so that 99% of all string tables
+                // in typical OSM files will only need a single memory
+                // allocation.
+                static constexpr const size_t default_stringtable_chunk_size = 100 * 1024;
+
                 StringStore m_strings;
-                std::map<const char*, size_t, StrComp> m_index;
+                std::unordered_map<const char*, size_t, djb2_hash, str_equal> m_index;
                 uint32_t m_size;
 
             public:
 
-                StringTable() :
-                    m_strings(1024 * 1024),
+                explicit StringTable(size_t size = default_stringtable_chunk_size) :
+                    m_strings(size),
                     m_index(),
                     m_size(0) {
                     m_strings.add("");
diff --git a/include/osmium/io/detail/zlib.hpp b/include/osmium/io/detail/zlib.hpp
index 2b6dddb..57b456b 100644
--- a/include/osmium/io/detail/zlib.hpp
+++ b/include/osmium/io/detail/zlib.hpp
@@ -39,6 +39,8 @@ DEALINGS IN THE SOFTWARE.
 
 #include <zlib.h>
 
+#include <protozero/types.hpp>
+
 #include <osmium/io/error.hpp>
 #include <osmium/util/cast.hpp>
 
@@ -89,7 +91,7 @@ namespace osmium {
              * @param output Uncompressed result data.
              * @returns Pointer and size to incompressed data.
              */
-            inline std::pair<const char*, size_t> zlib_uncompress_string(const char* input, unsigned long input_size, unsigned long raw_size, std::string& output) {
+            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(
@@ -103,7 +105,7 @@ namespace osmium {
                     throw io_error(std::string("failed to uncompress data: ") + zError(result));
                 }
 
-                return std::make_pair(output.data(), output.size());
+                return protozero::data_view{output.data(), output.size()};
             }
 
         } // namespace detail
diff --git a/include/osmium/io/reader.hpp b/include/osmium/io/reader.hpp
index 7c60511..b267db6 100644
--- a/include/osmium/io/reader.hpp
+++ b/include/osmium/io/reader.hpp
@@ -62,11 +62,26 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/entity_bits.hpp>
 #include <osmium/thread/util.hpp>
+#include <osmium/util/config.hpp>
 
 namespace osmium {
 
     namespace io {
 
+        namespace detail {
+
+            inline size_t get_input_queue_size() noexcept {
+                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);
+                return n > 2 ? n : 2;
+            }
+
+        } // namespace detail
+
         /**
          * This is the user-facing interface for reading OSM files. Instantiate
          * an object of this class with a file name or osmium::io::File object
@@ -75,9 +90,6 @@ namespace osmium {
          */
         class Reader {
 
-            static constexpr size_t max_input_queue_size = 20; // XXX
-            static constexpr size_t max_osmdata_queue_size = 20; // XXX
-
             osmium::io::File m_file;
             osmium::osm_entity_bits::type m_read_which_entities;
 
@@ -202,12 +214,12 @@ namespace osmium {
                 m_read_which_entities(read_which_entities),
                 m_status(status::okay),
                 m_childpid(0),
-                m_input_queue(max_input_queue_size, "raw_input"),
+                m_input_queue(detail::get_input_queue_size(), "raw_input"),
                 m_decompressor(m_file.buffer() ?
                     osmium::io::CompressionFactory::instance().create_decompressor(file.compression(), m_file.buffer(), m_file.buffer_size()) :
                     osmium::io::CompressionFactory::instance().create_decompressor(file.compression(), open_input_file_or_url(m_file.filename(), &m_childpid))),
                 m_read_thread_manager(*m_decompressor, m_input_queue),
-                m_osmdata_queue(max_osmdata_queue_size, "parser_results"),
+                m_osmdata_queue(detail::get_osmdata_queue_size(), "parser_results"),
                 m_osmdata_queue_wrapper(m_osmdata_queue),
                 m_header_future(),
                 m_header(),
diff --git a/include/osmium/io/writer.hpp b/include/osmium/io/writer.hpp
index b389698..13ac575 100644
--- a/include/osmium/io/writer.hpp
+++ b/include/osmium/io/writer.hpp
@@ -52,11 +52,21 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/writer_options.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/thread/util.hpp>
+#include <osmium/util/config.hpp>
 
 namespace osmium {
 
     namespace io {
 
+        namespace detail {
+
+            inline size_t get_output_queue_size() noexcept {
+                size_t n = osmium::config::get_max_queue_size("OUTPUT", 20);
+                return n > 2 ? n : 2;
+            }
+
+        } // namespace detail
+
         /**
          * This is the user-facing interface for writing OSM files. Instantiate
          * an object of this class with a file name or osmium::io::File object
@@ -194,7 +204,7 @@ namespace osmium {
             template <typename... TArgs>
             explicit Writer(const osmium::io::File& file, TArgs&&... args) :
                 m_file(file.check()),
-                m_output_queue(20, "raw_output"), // XXX
+                m_output_queue(detail::get_output_queue_size(), "raw_output"),
                 m_output(osmium::io::detail::OutputFormatFactory::instance().create_output(m_file, m_output_queue)),
                 m_buffer(),
                 m_buffer_size(default_buffer_size),
diff --git a/include/osmium/osm/location.hpp b/include/osmium/osm/location.hpp
index a9d8cb3..b8a4660 100644
--- a/include/osmium/osm/location.hpp
+++ b/include/osmium/osm/location.hpp
@@ -36,7 +36,10 @@ DEALINGS IN THE SOFTWARE.
 #include <cmath>
 #include <cstdint>
 #include <functional>
+#include <iomanip>
 #include <iosfwd>
+#include <locale>
+#include <sstream>
 #include <stdexcept>
 #include <string>
 
@@ -67,11 +70,38 @@ 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) {
             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;
+            }
+
+            str = full;
+
             int32_t result = 0;
             int sign = 1;
             int scale = 7;
@@ -372,13 +402,18 @@ namespace osmium {
         }
 
         template <typename T>
+        T as_string_without_check(T iterator, const char separator = ',') const {
+            iterator = detail::append_location_coordinate_to_string(iterator, x());
+            *iterator++ = separator;
+            return detail::append_location_coordinate_to_string(iterator, y());
+        }
+
+        template <typename T>
         T as_string(T iterator, const char separator = ',') const {
             if (!valid()) {
                 throw osmium::invalid_location("invalid location");
             }
-            iterator = detail::append_location_coordinate_to_string(iterator, x());
-            *iterator++ = separator;
-            return detail::append_location_coordinate_to_string(iterator, y());
+            return as_string_without_check(iterator, separator);
         }
 
     }; // class Location
diff --git a/include/osmium/relations/collector.hpp b/include/osmium/relations/collector.hpp
index 4d2b523..166b00d 100644
--- a/include/osmium/relations/collector.hpp
+++ b/include/osmium/relations/collector.hpp
@@ -362,7 +362,6 @@ namespace osmium {
                 } else {
                     m_relations_buffer.commit();
                     m_relations.push_back(std::move(relation_meta));
-//                    std::cerr << "added relation id=" << relation.id() << "\n";
                 }
             }
 
@@ -371,10 +370,6 @@ namespace osmium {
              * search on them.
              */
             void sort_member_meta() {
-/*                std::cerr << "relations:        " << m_relations.size() << "\n";
-                std::cerr << "node members:     " << m_member_meta[0].size() << "\n";
-                std::cerr << "way members:      " << m_member_meta[1].size() << "\n";
-                std::cerr << "relation members: " << m_member_meta[2].size() << "\n";*/
                 std::sort(m_member_meta[0].begin(), m_member_meta[0].end());
                 std::sort(m_member_meta[1].begin(), m_member_meta[1].end());
                 std::sort(m_member_meta[2].begin(), m_member_meta[2].end());
@@ -417,9 +412,7 @@ namespace osmium {
                     assert(member_meta.member_id() == object.id());
                     assert(member_meta.relation_pos() < m_relations.size());
                     RelationMeta& relation_meta = m_relations[member_meta.relation_pos()];
-//                        std::cerr << "  => " << member_meta.member_pos() << " < " << get_relation(relation_meta).members().size() << " (id=" << get_relation(relation_meta).id() << ")\n";
                     assert(member_meta.member_pos() < get_relation(relation_meta).members().size());
-//                        std::cerr << "  add way " << member_meta.member_id() << " to rel " << get_relation(relation_meta).id() << " at pos " << member_meta.member_pos() << "\n";
                     relation_meta.got_one_member();
                     if (relation_meta.has_all_members()) {
                         const size_t relation_offset = member_meta.relation_pos();
diff --git a/include/osmium/thread/pool.hpp b/include/osmium/thread/pool.hpp
index 44b6d15..d1b74c1 100644
--- a/include/osmium/thread/pool.hpp
+++ b/include/osmium/thread/pool.hpp
@@ -78,6 +78,11 @@ namespace osmium {
                 return num_threads;
             }
 
+            inline size_t get_work_queue_size() noexcept {
+                size_t n = osmium::config::get_max_queue_size("WORK", 10);
+                return n > 2 ? n : 2;
+            }
+
         } // namespace detail
 
         /**
@@ -118,13 +123,11 @@ namespace osmium {
                 osmium::thread::set_thread_name("_osmium_worker");
                 while (true) {
                     function_wrapper task;
-                    m_work_queue.wait_and_pop_with_timeout(task);
-                    if (task) {
-                        if (task()) {
-                            // The called tasks returns true only when the
-                            // worker thread should shut down.
-                            return;
-                        }
+                    m_work_queue.wait_and_pop(task);
+                    if (task && task()) {
+                        // The called tasks returns true only when the
+                        // worker thread should shut down.
+                        return;
                     }
                 }
             }
@@ -160,10 +163,9 @@ namespace osmium {
         public:
 
             static constexpr int default_num_threads = 0;
-            static constexpr size_t max_work_queue_size = 10;
 
             static Pool& instance() {
-                static Pool pool(default_num_threads, max_work_queue_size);
+                static Pool pool(default_num_threads, detail::get_work_queue_size());
                 return pool;
             }
 
@@ -176,7 +178,6 @@ namespace osmium {
 
             ~Pool() {
                 shutdown_all_workers();
-                m_work_queue.shutdown();
             }
 
             size_t queue_size() const {
diff --git a/include/osmium/thread/queue.hpp b/include/osmium/thread/queue.hpp
index 7717358..3124611 100644
--- a/include/osmium/thread/queue.hpp
+++ b/include/osmium/thread/queue.hpp
@@ -43,6 +43,10 @@ DEALINGS IN THE SOFTWARE.
 #include <thread>
 #include <utility> // IWYU pragma: keep (for std::move)
 
+#ifdef OSMIUM_DEBUG_QUEUE_SIZE
+# include <iostream>
+#endif
+
 namespace osmium {
 
     namespace thread {
@@ -69,8 +73,6 @@ namespace osmium {
             /// Used to signal readers when data is available in the queue.
             std::condition_variable m_data_available;
 
-            std::atomic<bool> m_done;
-
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
             /// The largest size the queue has been so far.
             size_t m_largest_size;
@@ -81,6 +83,16 @@ namespace osmium {
             /// The number of times the queue was full and a thread pushing
             /// to the queue was blocked.
             std::atomic<int> m_full_counter;
+
+            /**
+             * The number of times wait_and_pop(with_timeout)() was called
+             * on the queue.
+             */
+            std::atomic<int> m_pop_counter;
+
+            /// The number of times the queue was full and a thread pushing
+            /// to the queue was blocked.
+            std::atomic<int> m_empty_counter;
 #endif
 
         public:
@@ -97,21 +109,21 @@ namespace osmium {
                 m_name(name),
                 m_mutex(),
                 m_queue(),
-                m_data_available(),
-                m_done(false)
+                m_data_available()
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
                 ,
                 m_largest_size(0),
                 m_push_counter(0),
-                m_full_counter(0)
+                m_full_counter(0),
+                m_pop_counter(0),
+                m_empty_counter(0)
 #endif
             {
             }
 
             ~Queue() {
-                shutdown();
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
-                std::cerr << "queue '" << m_name << "' with max_size=" << m_max_size << " had largest size " << m_largest_size << " and was full " << m_full_counter << " times in " << m_push_counter << " push() calls\n";
+                std::cerr << "queue '" << m_name << "' with max_size=" << m_max_size << " had largest size " << m_largest_size << " and was full " << m_full_counter << " times in " << m_push_counter << " push() calls and was empty " << m_empty_counter << " times in " << m_pop_counter << " pop() calls\n";
 #endif
             }
 
@@ -141,15 +153,18 @@ namespace osmium {
                 m_data_available.notify_one();
             }
 
-            void shutdown() {
-                m_done = true;
-                m_data_available.notify_all();
-            }
-
             void wait_and_pop(T& value) {
+#ifdef OSMIUM_DEBUG_QUEUE_SIZE
+                ++m_pop_counter;
+#endif
                 std::unique_lock<std::mutex> lock(m_mutex);
+#ifdef OSMIUM_DEBUG_QUEUE_SIZE
+                if (m_queue.empty()) {
+                    ++m_empty_counter;
+                }
+#endif
                 m_data_available.wait(lock, [this] {
-                    return !m_queue.empty() || m_done;
+                    return !m_queue.empty();
                 });
                 if (!m_queue.empty()) {
                     value = std::move(m_queue.front());
@@ -157,22 +172,15 @@ namespace osmium {
                 }
             }
 
-            void wait_and_pop_with_timeout(T& value) {
-                std::unique_lock<std::mutex> lock(m_mutex);
-                if (!m_data_available.wait_for(lock, std::chrono::seconds(1), [this] {
-                    return !m_queue.empty() || m_done;
-                })) {
-                    return;
-                }
-                if (!m_queue.empty()) {
-                    value = std::move(m_queue.front());
-                    m_queue.pop();
-                }
-            }
-
             bool try_pop(T& value) {
+#ifdef OSMIUM_DEBUG_QUEUE_SIZE
+                ++m_pop_counter;
+#endif
                 std::lock_guard<std::mutex> lock(m_mutex);
                 if (m_queue.empty()) {
+#ifdef OSMIUM_DEBUG_QUEUE_SIZE
+                    ++m_empty_counter;
+#endif
                     return false;
                 }
                 value = std::move(m_queue.front());
diff --git a/include/osmium/util/config.hpp b/include/osmium/util/config.hpp
index c405123..e041235 100644
--- a/include/osmium/util/config.hpp
+++ b/include/osmium/util/config.hpp
@@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <cstdlib>
 #include <cstring>
+#include <string>
 
 #ifdef _MSC_VER
 # define strcasecmp _stricmp
@@ -44,7 +45,7 @@ namespace osmium {
 
     namespace config {
 
-        inline int get_pool_threads() {
+        inline int get_pool_threads() noexcept {
             const char* env = getenv("OSMIUM_POOL_THREADS");
             if (env) {
                 return std::atoi(env);
@@ -52,7 +53,7 @@ namespace osmium {
             return 0;
         }
 
-        inline bool use_pool_threads_for_pbf_parsing() {
+        inline bool use_pool_threads_for_pbf_parsing() noexcept {
             const char* env = getenv("OSMIUM_USE_POOL_THREADS_FOR_PBF_PARSING");
             if (env) {
                 if (!strcasecmp(env, "off") ||
@@ -65,6 +66,18 @@ namespace osmium {
             return true;
         }
 
+        inline size_t get_max_queue_size(const char* queue_name, size_t default_value) noexcept {
+            std::string name {"OSMIUM_MAX_"};
+            name += queue_name;
+            name += "_QUEUE_SIZE";
+            const char* env = getenv(name.c_str());
+            if (env) {
+                auto value = std::atoi(env);
+                return value == 0 ? default_value : value;
+            }
+            return default_value;
+        }
+
     } // namespace config
 
 } // namespace osmium
diff --git a/include/osmium/util/delta.hpp b/include/osmium/util/delta.hpp
index 8fd63a4..51c5c7b 100644
--- a/include/osmium/util/delta.hpp
+++ b/include/osmium/util/delta.hpp
@@ -38,6 +38,7 @@ DEALINGS IN THE SOFTWARE.
 #include <utility>
 
 #include <osmium/util/cast.hpp>
+#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
@@ -118,60 +119,6 @@ namespace osmium {
 
         }; // class DeltaDecode
 
-        template <typename TBaseIterator, typename TTransform, typename TValue, typename TDelta = int64_t>
-        class DeltaEncodeIterator {
-
-            TBaseIterator m_it;
-            TBaseIterator m_end;
-            TTransform m_trans;
-            DeltaEncode<TValue, TDelta> m_value;
-            TDelta m_delta;
-
-        public:
-
-            using iterator_category = std::input_iterator_tag;
-            using value_type        = TValue;
-            using difference_type   = std::ptrdiff_t;
-            using pointer           = value_type*;
-            using reference         = value_type&;
-
-            using delta_type        = TDelta;
-
-            DeltaEncodeIterator(TBaseIterator first, TBaseIterator last, TTransform& trans) :
-                m_it(first),
-                m_end(last),
-                m_trans(trans),
-                m_value(m_it != m_end ? m_trans(m_it) : 0),
-                m_delta(static_cast_with_assert<TDelta>(m_value.value())) {
-            }
-
-            DeltaEncodeIterator& operator++() {
-                if (++m_it != m_end) {
-                    m_delta = m_value.update(m_trans(m_it));
-                }
-                return *this;
-            }
-
-            DeltaEncodeIterator operator++(int) {
-                DeltaEncodeIterator tmp(*this);
-                operator++();
-                return tmp;
-            }
-
-            TDelta operator*() {
-                return m_delta;
-            }
-
-            bool operator==(const DeltaEncodeIterator& rhs) const {
-                return m_it == rhs.m_it && m_end == rhs.m_end;
-            }
-
-            bool operator!=(const DeltaEncodeIterator& rhs) const {
-                return !(*this == rhs);
-            }
-
-        }; // class DeltaEncodeIterator
-
     } // namespace util
 
 } // namespace osmium
diff --git a/include/osmium/version.hpp b/include/osmium/version.hpp
index 7ff931e..ac7a94f 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 7
-#define LIBOSMIUM_VERSION_PATCH 2
+#define LIBOSMIUM_VERSION_MINOR 8
+#define LIBOSMIUM_VERSION_PATCH 0
 
-#define LIBOSMIUM_VERSION_STRING "2.7.2"
+#define LIBOSMIUM_VERSION_STRING "2.8.0"
 
 #endif // OSMIUM_VERSION_HPP
diff --git a/include/protozero/byteswap.hpp b/include/protozero/byteswap.hpp
index a018c1c..06ba6de 100644
--- a/include/protozero/byteswap.hpp
+++ b/include/protozero/byteswap.hpp
@@ -28,7 +28,7 @@ namespace protozero {
  * be specialized to actually work.
  */
 template <int N>
-inline void byteswap(const char* /*data*/, char* /*result*/) {
+inline void byteswap(const char* /*data*/, char* /*result*/) noexcept {
     static_assert(N == 1, "Can only swap 4 or 8 byte values");
 }
 
@@ -36,7 +36,7 @@ inline void byteswap(const char* /*data*/, char* /*result*/) {
  * Swap 4 byte value (int32_t, uint32_t, float) between endianness formats.
  */
 template <>
-inline void byteswap<4>(const char* data, char* result) {
+inline void byteswap<4>(const char* data, char* result) noexcept {
 #ifdef PROTOZERO_USE_BUILTIN_BSWAP
     *reinterpret_cast<uint32_t*>(result) = __builtin_bswap32(*reinterpret_cast<const uint32_t*>(data));
 #else
@@ -51,7 +51,7 @@ inline void byteswap<4>(const char* data, char* result) {
  * Swap 8 byte value (int64_t, uint64_t, double) between endianness formats.
  */
 template <>
-inline void byteswap<8>(const char* data, char* result) {
+inline void byteswap<8>(const char* data, char* result) noexcept {
 #ifdef PROTOZERO_USE_BUILTIN_BSWAP
     *reinterpret_cast<uint64_t*>(result) = __builtin_bswap64(*reinterpret_cast<const uint64_t*>(data));
 #else
diff --git a/include/protozero/exception.hpp b/include/protozero/exception.hpp
index 5c7ab54..ca4340e 100644
--- a/include/protozero/exception.hpp
+++ b/include/protozero/exception.hpp
@@ -29,7 +29,7 @@ namespace protozero {
  */
 struct exception : std::exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "pbf exception"; }
+    const char* what() const noexcept override { return "pbf exception"; }
 };
 
 /**
@@ -38,7 +38,7 @@ struct exception : std::exception {
  */
 struct varint_too_long_exception : exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "varint too long exception"; }
+    const char* what() const noexcept override { return "varint too long exception"; }
 };
 
 /**
@@ -47,7 +47,7 @@ struct varint_too_long_exception : exception {
  */
 struct unknown_pbf_wire_type_exception : exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "unknown pbf field type exception"; }
+    const char* what() const noexcept override { return "unknown pbf field type exception"; }
 };
 
 /**
@@ -60,7 +60,7 @@ struct unknown_pbf_wire_type_exception : exception {
  */
 struct end_of_buffer_exception : exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "end of buffer exception"; }
+    const char* what() const noexcept override { return "end of buffer exception"; }
 };
 
 } // end namespace protozero
diff --git a/include/protozero/iterators.hpp b/include/protozero/iterators.hpp
new file mode 100644
index 0000000..813d96b
--- /dev/null
+++ b/include/protozero/iterators.hpp
@@ -0,0 +1,373 @@
+#ifndef PROTOZERO_ITERATORS_HPP
+#define PROTOZERO_ITERATORS_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+/**
+ * @file iterators.hpp
+ *
+ * @brief Contains the iterators for access to packed repeated fields.
+ */
+
+#include <cstring>
+#include <iterator>
+#include <utility>
+
+#include <protozero/config.hpp>
+#include <protozero/varint.hpp>
+
+#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN
+# include <protozero/byteswap.hpp>
+#endif
+
+namespace protozero {
+
+namespace detail {
+
+    // Copy N bytes from src to dest on little endian machines, on big
+    // endian swap the bytes in the process.
+    template <int N>
+    inline void copy_or_byteswap(const char* src, void* dest) noexcept {
+#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
+        std::memcpy(dest, src, N);
+#else
+        byteswap<N>(src, reinterpret_cast<char*>(dest));
+#endif
+    }
+
+} // end namespace detail
+
+/**
+ * A range of iterators based on std::pair. Created from beginning and
+ * end iterators. Used as a return type from some pbf_reader methods
+ * that is easy to use with range-based for loops.
+ */
+template <typename T, typename P = std::pair<T, T>>
+class iterator_range :
+#ifdef PROTOZERO_STRICT_API
+    protected
+#else
+    public
+#endif
+        P {
+
+public:
+
+    /// The type of the iterators in this range.
+    using iterator = T;
+
+    /// The value type of the underlying iterator.
+    using value_type = typename std::iterator_traits<T>::value_type;
+
+    /**
+     * Default constructor. Create empty iterator_range.
+     */
+    constexpr iterator_range() :
+        P(iterator{}, iterator{}) {
+    }
+
+    /**
+     * Create iterator range from two iterators.
+     *
+     * @param first Iterator to beginning or range.
+     * @param last Iterator to end or range.
+     */
+    constexpr iterator_range(iterator&& first, iterator&& last) :
+        P(std::forward<iterator>(first),
+          std::forward<iterator>(last)) {
+    }
+
+    /// Return iterator to beginning of range.
+    constexpr iterator begin() const noexcept {
+        return this->first;
+    }
+
+    /// Return iterator to end of range.
+    constexpr iterator end() const noexcept {
+        return this->second;
+    }
+
+    /// Return iterator to beginning of range.
+    constexpr iterator cbegin() const noexcept {
+        return this->first;
+    }
+
+    /// Return iterator to end of range.
+    constexpr iterator cend() const noexcept {
+        return this->second;
+    }
+
+    /// Return true if this range is empty.
+    constexpr std::size_t empty() const noexcept {
+        return begin() == end();
+    }
+
+    /**
+     * Get element at the beginning of the range.
+     *
+     * @pre Range must not be empty.
+     */
+    value_type front() const {
+        protozero_assert(!empty());
+        return *(this->first);
+    }
+
+    /**
+     * Advance beginning of range by one.
+     *
+     * @pre Range must not be empty.
+     */
+    void drop_front() {
+        protozero_assert(!empty());
+        ++this->first;
+    }
+
+    /**
+     * Swap the contents of this range with the other.
+     *
+     * @param other Other range to swap data with.
+     */
+    void swap(iterator_range& other) noexcept {
+        using std::swap;
+        swap(this->first, other.first);
+        swap(this->second, other.second);
+    }
+
+}; // struct iterator_range
+
+/**
+ * Swap two iterator_ranges.
+ *
+ * @param lhs First range.
+ * @param rhs Second range.
+ */
+template <typename T>
+inline void swap(iterator_range<T>& lhs, iterator_range<T>& rhs) noexcept {
+    lhs.swap(rhs);
+}
+
+#ifdef PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED
+
+template <typename T>
+using const_fixed_iterator = const T*;
+
+/**
+ * Create iterator_range from char pointers to beginning and end of range.
+ *
+ * @param first Beginning of range.
+ * @param last End of range.
+ */
+template <typename T>
+inline iterator_range<const_fixed_iterator<T>> create_fixed_iterator_range(const char* first, const char* last) {
+    return iterator_range<const_fixed_iterator<T>>{reinterpret_cast<const T*>(first),
+                                                   reinterpret_cast<const T*>(last)};
+}
+
+#else
+
+/**
+ * A forward iterator used for accessing packed repeated fields of fixed
+ * length (fixed32, sfixed32, float, double).
+ */
+template <typename T>
+class const_fixed_iterator {
+
+    /// Pointer to current iterator position
+    const char* m_data;
+
+    /// Pointer to end iterator position
+    const char* m_end;
+
+public:
+
+    using iterator_category = std::forward_iterator_tag;
+    using value_type        = T;
+    using difference_type   = std::ptrdiff_t;
+    using pointer           = value_type*;
+    using reference         = value_type&;
+
+    const_fixed_iterator() noexcept :
+        m_data(nullptr),
+        m_end(nullptr) {
+    }
+
+    const_fixed_iterator(const char* data, const char* end) noexcept :
+        m_data(data),
+        m_end(end) {
+    }
+
+    const_fixed_iterator(const const_fixed_iterator&) noexcept = default;
+    const_fixed_iterator(const_fixed_iterator&&) noexcept = default;
+
+    const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default;
+    const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default;
+
+    ~const_fixed_iterator() noexcept = default;
+
+    value_type operator*() const {
+        value_type result;
+        detail::copy_or_byteswap<sizeof(value_type)>(m_data , &result);
+        return result;
+    }
+
+    const_fixed_iterator& operator++() {
+        m_data += sizeof(value_type);
+        return *this;
+    }
+
+    const_fixed_iterator operator++(int) {
+        const const_fixed_iterator tmp(*this);
+        ++(*this);
+        return tmp;
+    }
+
+    bool operator==(const const_fixed_iterator& rhs) const noexcept {
+        return m_data == rhs.m_data && m_end == rhs.m_end;
+    }
+
+    bool operator!=(const const_fixed_iterator& rhs) const noexcept {
+        return !(*this == rhs);
+    }
+
+}; // class const_fixed_iterator
+
+/**
+ * Create iterator_range from char pointers to beginning and end of range.
+ *
+ * @param first Beginning of range.
+ * @param last End of range.
+ */
+template <typename T>
+inline iterator_range<const_fixed_iterator<T>> create_fixed_iterator_range(const char* first, const char* last) {
+    return iterator_range<const_fixed_iterator<T>>{const_fixed_iterator<T>(first, last),
+                                                   const_fixed_iterator<T>(last, last)};
+}
+
+#endif
+
+/**
+ * A forward iterator used for accessing packed repeated varint fields
+ * (int32, uint32, int64, uint64, bool, enum).
+ */
+template <typename T>
+class const_varint_iterator {
+
+protected:
+
+    /// Pointer to current iterator position
+    const char* m_data;
+
+    /// Pointer to end iterator position
+    const char* m_end;
+
+public:
+
+    using iterator_category = std::forward_iterator_tag;
+    using value_type        = T;
+    using difference_type   = std::ptrdiff_t;
+    using pointer           = value_type*;
+    using reference         = value_type&;
+
+    const_varint_iterator() noexcept :
+        m_data(nullptr),
+        m_end(nullptr) {
+    }
+
+    const_varint_iterator(const char* data, const char* end) noexcept :
+        m_data(data),
+        m_end(end) {
+    }
+
+    const_varint_iterator(const const_varint_iterator&) noexcept = default;
+    const_varint_iterator(const_varint_iterator&&) noexcept = default;
+
+    const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default;
+    const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default;
+
+    ~const_varint_iterator() noexcept = default;
+
+    value_type operator*() const {
+        const char* d = m_data; // will be thrown away
+        return static_cast<value_type>(decode_varint(&d, m_end));
+    }
+
+    const_varint_iterator& operator++() {
+        skip_varint(&m_data, m_end);
+        return *this;
+    }
+
+    const_varint_iterator operator++(int) {
+        const const_varint_iterator tmp(*this);
+        ++(*this);
+        return tmp;
+    }
+
+    bool operator==(const const_varint_iterator& rhs) const noexcept {
+        return m_data == rhs.m_data && m_end == rhs.m_end;
+    }
+
+    bool operator!=(const const_varint_iterator& rhs) const noexcept {
+        return !(*this == rhs);
+    }
+
+}; // class const_varint_iterator
+
+/**
+ * A forward iterator used for accessing packed repeated svarint fields
+ * (sint32, sint64).
+ */
+template <typename T>
+class const_svarint_iterator : public const_varint_iterator<T> {
+
+public:
+
+    using iterator_category = std::forward_iterator_tag;
+    using value_type        = T;
+    using difference_type   = std::ptrdiff_t;
+    using pointer           = value_type*;
+    using reference         = value_type&;
+
+    const_svarint_iterator() noexcept :
+        const_varint_iterator<T>() {
+    }
+
+    const_svarint_iterator(const char* data, const char* end) noexcept :
+        const_varint_iterator<T>(data, end) {
+    }
+
+    const_svarint_iterator(const const_svarint_iterator&) = default;
+    const_svarint_iterator(const_svarint_iterator&&) = default;
+
+    const_svarint_iterator& operator=(const const_svarint_iterator&) = default;
+    const_svarint_iterator& operator=(const_svarint_iterator&&) = default;
+
+    ~const_svarint_iterator() = default;
+
+    value_type operator*() const {
+        const char* d = this->m_data; // will be thrown away
+        return static_cast<value_type>(decode_zigzag64(decode_varint(&d, this->m_end)));
+    }
+
+    const_svarint_iterator& operator++() {
+        skip_varint(&this->m_data, this->m_end);
+        return *this;
+    }
+
+    const_svarint_iterator operator++(int) {
+        const const_svarint_iterator tmp(*this);
+        ++(*this);
+        return tmp;
+    }
+
+}; // class const_svarint_iterator
+
+} // end namespace protozero
+
+#endif // PROTOZERO_ITERATORS_HPP
diff --git a/include/protozero/pbf_builder.hpp b/include/protozero/pbf_builder.hpp
index 548f4ce..39af53f 100644
--- a/include/protozero/pbf_builder.hpp
+++ b/include/protozero/pbf_builder.hpp
@@ -57,7 +57,7 @@ public:
 
 /// @cond INTERNAL
 #define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \
-    inline void add_##name(T tag, type value) { \
+    void add_##name(T tag, type value) { \
         pbf_writer::add_##name(pbf_tag_type(tag), value); \
     }
 
@@ -79,38 +79,38 @@ public:
 #undef PROTOZERO_WRITER_WRAP_ADD_SCALAR
 /// @endcond
 
-    inline void add_bytes(T tag, const char* value, std::size_t size) {
+    void add_bytes(T tag, const char* value, std::size_t size) {
         pbf_writer::add_bytes(pbf_tag_type(tag), value, size);
     }
 
-    inline void add_bytes(T tag, const std::string& value) {
+    void add_bytes(T tag, const std::string& value) {
         pbf_writer::add_bytes(pbf_tag_type(tag), value);
     }
 
-    inline void add_string(T tag, const char* value, std::size_t size) {
+    void add_string(T tag, const char* value, std::size_t size) {
         pbf_writer::add_string(pbf_tag_type(tag), value, size);
     }
 
-    inline void add_string(T tag, const std::string& value) {
+    void add_string(T tag, const std::string& value) {
         pbf_writer::add_string(pbf_tag_type(tag), value);
     }
 
-    inline void add_string(T tag, const char* value) {
+    void add_string(T tag, const char* value) {
         pbf_writer::add_string(pbf_tag_type(tag), value);
     }
 
-    inline void add_message(T tag, const char* value, std::size_t size) {
+    void add_message(T tag, const char* value, std::size_t size) {
         pbf_writer::add_message(pbf_tag_type(tag), value, size);
     }
 
-    inline void add_message(T tag, const std::string& value) {
+    void add_message(T tag, const std::string& value) {
         pbf_writer::add_message(pbf_tag_type(tag), value);
     }
 
 /// @cond INTERNAL
 #define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \
     template <typename InputIterator> \
-    inline void add_packed_##name(T tag, InputIterator first, InputIterator last) { \
+    void add_packed_##name(T tag, InputIterator first, InputIterator last) { \
         pbf_writer::add_packed_##name(pbf_tag_type(tag), first, last); \
     }
 
@@ -132,7 +132,7 @@ public:
 #undef PROTOZERO_WRITER_WRAP_ADD_PACKED
 /// @endcond
 
-};
+}; // class pbf_builder
 
 } // end namespace protozero
 
diff --git a/include/protozero/pbf_message.hpp b/include/protozero/pbf_message.hpp
index 45f01c1..0557734 100644
--- a/include/protozero/pbf_message.hpp
+++ b/include/protozero/pbf_message.hpp
@@ -13,7 +13,7 @@ documentation.
 /**
  * @file pbf_message.hpp
  *
- * @brief Contains the pbf_message class.
+ * @brief Contains the pbf_message template class.
  */
 
 #include <type_traits>
@@ -75,19 +75,19 @@ public:
         pbf_reader(std::forward<Args>(args)...) {
     }
 
-    inline bool next() {
+    bool next() {
         return pbf_reader::next();
     }
 
-    inline bool next(T tag) {
+    bool next(T tag) {
         return pbf_reader::next(pbf_tag_type(tag));
     }
 
-    inline T tag() const noexcept {
+    T tag() const noexcept {
         return T(pbf_reader::tag());
     }
 
-};
+}; // class pbf_message
 
 } // end namespace protozero
 
diff --git a/include/protozero/pbf_reader.hpp b/include/protozero/pbf_reader.hpp
index 58b3884..2f4054d 100644
--- a/include/protozero/pbf_reader.hpp
+++ b/include/protozero/pbf_reader.hpp
@@ -18,13 +18,12 @@ documentation.
 
 #include <cstddef>
 #include <cstdint>
-#include <cstring>
-#include <iterator>
 #include <string>
 #include <utility>
 
 #include <protozero/config.hpp>
 #include <protozero/exception.hpp>
+#include <protozero/iterators.hpp>
 #include <protozero/types.hpp>
 #include <protozero/varint.hpp>
 
@@ -55,16 +54,16 @@ namespace protozero {
  *
  * All methods of the pbf_reader class except get_bytes() and get_string()
  * provide the strong exception guarantee, ie they either succeed or do not
- * change the pbf_reader object they are called on. Use the get_data() method
+ * change the pbf_reader object they are called on. Use the get_view() method
  * instead of get_bytes() or get_string(), if you need this guarantee.
  */
 class pbf_reader {
 
     // A pointer to the next unread data.
-    const char *m_data = nullptr;
+    const char* m_data = nullptr;
 
     // A pointer to one past the end of data.
-    const char *m_end = nullptr;
+    const char* m_end = nullptr;
 
     // The wire type of the current field.
     pbf_wire_type m_wire_type = pbf_wire_type::unknown;
@@ -72,119 +71,84 @@ class pbf_reader {
     // The tag of the current field.
     pbf_tag_type m_tag = 0;
 
-    // Copy N bytes from src to dest on little endian machines, on big endian
-    // swap the bytes in the process.
-    template <int N>
-    static void copy_or_byteswap(const char* src, void* dest) noexcept {
-#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
-        memcpy(dest, src, N);
-#else
-        byteswap<N>(src, reinterpret_cast<char*>(dest));
-#endif
-    }
-
     template <typename T>
-    inline T get_fixed() {
+    T get_fixed() {
         T result;
         skip_bytes(sizeof(T));
-        copy_or_byteswap<sizeof(T)>(m_data - sizeof(T), &result);
+        detail::copy_or_byteswap<sizeof(T)>(m_data - sizeof(T), &result);
         return result;
     }
 
-#ifdef PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED
-
     template <typename T>
-    using const_fixed_iterator = const T*;
+    iterator_range<const_fixed_iterator<T>> packed_fixed() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        const auto len = get_len_and_skip();
+        protozero_assert(len % sizeof(T) == 0);
+        return create_fixed_iterator_range<T>(m_data - len, m_data);
+    }
 
     template <typename T>
-    inline std::pair<const_fixed_iterator<T>, const_fixed_iterator<T>> create_fixed_iterator_pair(const char* first, const char* last) {
-        return std::make_pair(reinterpret_cast<const T*>(first),
-                              reinterpret_cast<const T*>(last));
+    T get_varint() {
+        return static_cast<T>(decode_varint(&m_data, m_end));
     }
 
-#else
-
     template <typename T>
-    class const_fixed_iterator : public std::iterator<std::forward_iterator_tag, T> {
-
-        const char* m_data;
-        const char* m_end;
-
-    public:
-
-        const_fixed_iterator() noexcept :
-            m_data(nullptr),
-            m_end(nullptr) {
-        }
-
-        const_fixed_iterator(const char *data, const char* end) noexcept :
-            m_data(data),
-            m_end(end) {
-        }
-
-        const_fixed_iterator(const const_fixed_iterator&) noexcept = default;
-        const_fixed_iterator(const_fixed_iterator&&) noexcept = default;
-
-        const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default;
-        const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default;
-
-        ~const_fixed_iterator() noexcept = default;
-
-        T operator*() {
-            T result;
-            copy_or_byteswap<sizeof(T)>(m_data , &result);
-            return result;
-        }
-
-        const_fixed_iterator& operator++() {
-            m_data += sizeof(T);
-            return *this;
-        }
-
-        const_fixed_iterator operator++(int) {
-            const const_fixed_iterator tmp(*this);
-            ++(*this);
-            return tmp;
-        }
+    T get_svarint() {
+        protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint");
+        return static_cast<T>(decode_zigzag64(decode_varint(&m_data, m_end)));
+    }
 
-        bool operator==(const const_fixed_iterator& rhs) const noexcept {
-            return m_data == rhs.m_data && m_end == rhs.m_end;
-        }
+    pbf_length_type get_length() {
+        return get_varint<pbf_length_type>();
+    }
 
-        bool operator!=(const const_fixed_iterator& rhs) const noexcept {
-            return !(*this == rhs);
+    void skip_bytes(pbf_length_type len) {
+        if (m_data + len > m_end) {
+            throw end_of_buffer_exception();
         }
+        m_data += len;
 
-    }; // class const_fixed_iterator
-
-    template <typename T>
-    inline std::pair<const_fixed_iterator<T>, const_fixed_iterator<T>> create_fixed_iterator_pair(const char* first, const char* last) {
-        return std::make_pair(const_fixed_iterator<T>(first, last),
-                              const_fixed_iterator<T>(last, last));
+    // In debug builds reset the tag to zero so that we can detect (some)
+    // wrong code.
+#ifndef NDEBUG
+        m_tag = 0;
+#endif
     }
 
-#endif
+    pbf_length_type get_len_and_skip() {
+        const auto len = get_length();
+        skip_bytes(len);
+        return len;
+    }
 
     template <typename T>
-    inline std::pair<const_fixed_iterator<T>, const_fixed_iterator<T>> packed_fixed() {
+    iterator_range<T> get_packed() {
         protozero_assert(tag() != 0 && "call next() before accessing field value");
-        auto len = get_len_and_skip();
-        protozero_assert(len % sizeof(T) == 0);
-        return create_fixed_iterator_pair<T>(m_data-len, m_data);
+        const auto len = get_len_and_skip();
+        return iterator_range<T>{T{m_data - len, m_data},
+                                 T{m_data, m_data}};
     }
 
-    template <typename T> inline T get_varint();
-    template <typename T> inline T get_svarint();
-
-    inline pbf_length_type get_length() { return get_varint<pbf_length_type>(); }
-
-    inline void skip_bytes(pbf_length_type len);
-
-    inline pbf_length_type get_len_and_skip();
-
 public:
 
     /**
+     * Construct a pbf_reader message from a data_view. The pointer from the
+     * data_view will be stored inside the pbf_reader object, no data is
+     * copied. So you must* make sure the view stays valid as long as the
+     * pbf_reader object is used.
+     *
+     * The buffer must contain a complete protobuf message.
+     *
+     * @post There is no current field.
+     */
+    explicit pbf_reader(const data_view& view) noexcept
+        : m_data(view.data()),
+          m_end(view.data() + view.size()),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
+
+    /**
      * Construct a pbf_reader message from a data pointer and a length. The pointer
      * will be stored inside the pbf_reader object, no data is copied. So you must
      * make sure the buffer stays valid as long as the pbf_reader object is used.
@@ -193,7 +157,12 @@ public:
      *
      * @post There is no current field.
      */
-    inline pbf_reader(const char *data, std::size_t length) noexcept;
+    pbf_reader(const char* data, std::size_t length) noexcept
+        : m_data(data),
+          m_end(data + length),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
 
     /**
      * Construct a pbf_reader message from a data pointer and a length. The pointer
@@ -204,7 +173,12 @@ public:
      *
      * @post There is no current field.
      */
-    inline pbf_reader(std::pair<const char *, std::size_t> data) noexcept;
+    pbf_reader(std::pair<const char*, std::size_t> data) noexcept
+        : m_data(data.first),
+          m_end(data.first + data.second),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
 
     /**
      * Construct a pbf_reader message from a std::string. A pointer to the string
@@ -216,33 +190,53 @@ public:
      *
      * @post There is no current field.
      */
-    inline pbf_reader(const std::string& data) noexcept;
+    pbf_reader(const std::string& data) noexcept
+        : m_data(data.data()),
+          m_end(data.data() + data.size()),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
 
     /**
      * pbf_reader can be default constructed and behaves like it has an empty
      * buffer.
      */
-    inline pbf_reader() noexcept = default;
+    pbf_reader() noexcept = default;
 
     /// pbf_reader messages can be copied trivially.
-    inline pbf_reader(const pbf_reader&) noexcept = default;
+    pbf_reader(const pbf_reader&) noexcept = default;
 
     /// pbf_reader messages can be moved trivially.
-    inline pbf_reader(pbf_reader&&) noexcept = default;
+    pbf_reader(pbf_reader&&) noexcept = default;
 
     /// pbf_reader messages can be copied trivially.
-    inline pbf_reader& operator=(const pbf_reader& other) noexcept = default;
+    pbf_reader& operator=(const pbf_reader& other) noexcept = default;
 
     /// pbf_reader messages can be moved trivially.
-    inline pbf_reader& operator=(pbf_reader&& other) noexcept = default;
+    pbf_reader& operator=(pbf_reader&& other) noexcept = default;
+
+    ~pbf_reader() = default;
 
-    inline ~pbf_reader() = default;
+    /**
+     * Swap the contents of this object with the other.
+     *
+     * @param other Other object to swap data with.
+     */
+    void swap(pbf_reader& other) noexcept {
+        using std::swap;
+        swap(m_data, other.m_data);
+        swap(m_end, other.m_end);
+        swap(m_wire_type, other.m_wire_type);
+        swap(m_tag, other.m_tag);
+    }
 
     /**
      * In a boolean context the pbf_reader class evaluates to `true` if there are
      * still fields available and to `false` if the last field has been read.
      */
-    inline operator bool() const noexcept;
+    operator bool() const noexcept {
+        return m_data < m_end;
+    }
 
     /**
      * Return the length in bytes of the current message. If you have
@@ -272,7 +266,31 @@ public:
      * @pre There must be no current field.
      * @post If it returns `true` there is a current field now.
      */
-    inline bool next();
+    bool next() {
+        if (m_data == m_end) {
+            return false;
+        }
+
+        const auto value = get_varint<uint32_t>();
+        m_tag = pbf_tag_type(value >> 3);
+
+        // tags 0 and 19000 to 19999 are not allowed as per
+        // https://developers.google.com/protocol-buffers/docs/proto
+        protozero_assert(((m_tag > 0 && m_tag < 19000) || (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range");
+
+        m_wire_type = pbf_wire_type(value & 0x07);
+        switch (m_wire_type) {
+            case pbf_wire_type::varint:
+            case pbf_wire_type::fixed64:
+            case pbf_wire_type::length_delimited:
+            case pbf_wire_type::fixed32:
+                break;
+            default:
+                throw unknown_pbf_wire_type_exception();
+        }
+
+        return true;
+    }
 
     /**
      * Set next field with given tag in the message as the current field.
@@ -299,7 +317,16 @@ public:
      * @pre There must be no current field.
      * @post If it returns `true` there is a current field now with the given tag.
      */
-    inline bool next(pbf_tag_type tag);
+    bool next(pbf_tag_type tag) {
+        while (next()) {
+            if (m_tag == tag) {
+                return true;
+            } else {
+                skip();
+            }
+        }
+        return false;
+    }
 
     /**
      * The tag of the current field. The tag is the field number from the
@@ -310,7 +337,9 @@ public:
      * @returns tag of the current field.
      * @pre There must be a current field (ie. next() must have returned `true`).
      */
-    inline pbf_tag_type tag() const noexcept;
+    pbf_tag_type tag() const noexcept {
+        return m_tag;
+    }
 
     /**
      * Get the wire type of the current field. The wire types are:
@@ -327,7 +356,9 @@ public:
      * @returns wire type of the current field.
      * @pre There must be a current field (ie. next() must have returned `true`).
      */
-    inline pbf_wire_type wire_type() const noexcept;
+    pbf_wire_type wire_type() const noexcept {
+        return m_wire_type;
+    }
 
     /**
      * Check the wire type of the current field.
@@ -335,7 +366,9 @@ public:
      * @returns `true` if the current field has the given wire type.
      * @pre There must be a current field (ie. next() must have returned `true`).
      */
-    inline bool has_wire_type(pbf_wire_type type) const noexcept;
+    bool has_wire_type(pbf_wire_type type) const noexcept {
+        return wire_type() == type;
+    }
 
     /**
      * Consume the current field.
@@ -343,7 +376,25 @@ public:
      * @pre There must be a current field (ie. next() must have returned `true`).
      * @post The current field was consumed and there is no current field now.
      */
-    inline void skip();
+    void skip() {
+        protozero_assert(tag() != 0 && "call next() before calling skip()");
+        switch (wire_type()) {
+            case pbf_wire_type::varint:
+                skip_varint(&m_data, m_end);
+                break;
+            case pbf_wire_type::fixed64:
+                skip_bytes(8);
+                break;
+            case pbf_wire_type::length_delimited:
+                skip_bytes(get_length());
+                break;
+            case pbf_wire_type::fixed32:
+                skip_bytes(4);
+                break;
+            default:
+                protozero_assert(false && "can not be here because next() should have thrown already");
+        }
+    }
 
     ///@{
     /**
@@ -357,7 +408,13 @@ public:
      * @pre The current field must be of type "bool".
      * @post The current field was consumed and there is no current field now.
      */
-    inline bool get_bool();
+    bool get_bool() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint");
+        skip_bytes(1);
+        return m_data[-1] != 0; // -1 okay because we incremented m_data the line before
+    }
 
     /**
      * Consume and return value of current "enum" field.
@@ -366,7 +423,7 @@ public:
      * @pre The current field must be of type "enum".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_enum() {
+    int32_t get_enum() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<int32_t>();
     }
@@ -378,7 +435,7 @@ public:
      * @pre The current field must be of type "int32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_int32() {
+    int32_t get_int32() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<int32_t>();
     }
@@ -390,7 +447,7 @@ public:
      * @pre The current field must be of type "sint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_sint32() {
+    int32_t get_sint32() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_svarint<int32_t>();
     }
@@ -402,7 +459,7 @@ public:
      * @pre The current field must be of type "uint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint32_t get_uint32() {
+    uint32_t get_uint32() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<uint32_t>();
     }
@@ -414,7 +471,7 @@ public:
      * @pre The current field must be of type "int64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int64_t get_int64() {
+    int64_t get_int64() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<int64_t>();
     }
@@ -426,7 +483,7 @@ public:
      * @pre The current field must be of type "sint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int64_t get_sint64() {
+    int64_t get_sint64() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_svarint<int64_t>();
     }
@@ -438,7 +495,7 @@ public:
      * @pre The current field must be of type "uint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint64_t get_uint64() {
+    uint64_t get_uint64() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<uint64_t>();
     }
@@ -450,7 +507,11 @@ public:
      * @pre The current field must be of type "fixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint32_t get_fixed32();
+    uint32_t get_fixed32() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+        return get_fixed<uint32_t>();
+    }
 
     /**
      * Consume and return value of current "sfixed32" field.
@@ -459,7 +520,11 @@ public:
      * @pre The current field must be of type "sfixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_sfixed32();
+    int32_t get_sfixed32() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+        return get_fixed<int32_t>();
+    }
 
     /**
      * Consume and return value of current "fixed64" field.
@@ -468,7 +533,11 @@ public:
      * @pre The current field must be of type "fixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint64_t get_fixed64();
+    uint64_t get_fixed64() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+        return get_fixed<uint64_t>();
+    }
 
     /**
      * Consume and return value of current "sfixed64" field.
@@ -477,7 +546,11 @@ public:
      * @pre The current field must be of type "sfixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int64_t get_sfixed64();
+    int64_t get_sfixed64() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+        return get_fixed<int64_t>();
+    }
 
     /**
      * Consume and return value of current "float" field.
@@ -486,7 +559,11 @@ public:
      * @pre The current field must be of type "float".
      * @post The current field was consumed and there is no current field now.
      */
-    inline float get_float();
+    float get_float() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+        return get_fixed<float>();
+    }
 
     /**
      * Consume and return value of current "double" field.
@@ -495,8 +572,29 @@ public:
      * @pre The current field must be of type "double".
      * @post The current field was consumed and there is no current field now.
      */
-    inline double get_double();
+    double get_double() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+        return get_fixed<double>();
+    }
+
+    /**
+     * Consume and return value of current "bytes", "string", or "message"
+     * field.
+     *
+     * @returns A data_view object.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "bytes", "string", or "message".
+     * @post The current field was consumed and there is no current field now.
+     */
+    data_view get_view() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message");
+        const auto len = get_len_and_skip();
+        return data_view{m_data-len, len};
+    }
 
+#ifndef PROTOZERO_STRICT_API
     /**
      * Consume and return value of current "bytes" or "string" field.
      *
@@ -505,7 +603,13 @@ public:
      * @pre The current field must be of type "bytes" or "string".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<const char*, pbf_length_type> get_data();
+    std::pair<const char*, pbf_length_type> get_data() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message");
+        const auto len = get_len_and_skip();
+        return std::make_pair(m_data-len, len);
+    }
+#endif
 
     /**
      * Consume and return value of current "bytes" field.
@@ -514,7 +618,9 @@ public:
      * @pre The current field must be of type "bytes".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::string get_bytes();
+    std::string get_bytes() {
+        return std::string(get_view());
+    }
 
     /**
      * Consume and return value of current "string" field.
@@ -523,7 +629,9 @@ public:
      * @pre The current field must be of type "string".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::string get_string();
+    std::string get_string() {
+        return std::string(get_view());
+    }
 
     /**
      * Consume and return value of current "message" field.
@@ -532,136 +640,35 @@ public:
      * @pre The current field must be of type "message".
      * @post The current field was consumed and there is no current field now.
      */
-    inline pbf_reader get_message() {
-        return pbf_reader(get_data());
+    pbf_reader get_message() {
+        return pbf_reader(get_view());
     }
 
     ///@}
 
-private:
-
-    template <typename T>
-    class const_varint_iterator : public std::iterator<std::forward_iterator_tag, T> {
-
-    protected:
-
-        const char* m_data;
-        const char* m_end;
-
-    public:
-
-        const_varint_iterator() noexcept :
-            m_data(nullptr),
-            m_end(nullptr) {
-        }
-
-        const_varint_iterator(const char *data, const char* end) noexcept :
-            m_data(data),
-            m_end(end) {
-        }
-
-        const_varint_iterator(const const_varint_iterator&) noexcept = default;
-        const_varint_iterator(const_varint_iterator&&) noexcept = default;
-
-        const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default;
-        const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default;
-
-        ~const_varint_iterator() noexcept = default;
-
-        T operator*() {
-            const char* d = m_data; // will be thrown away
-            return static_cast<T>(decode_varint(&d, m_end));
-        }
-
-        const_varint_iterator& operator++() {
-            // Ignore the result, we call decode_varint() just for the
-            // side-effect of updating m_data.
-            decode_varint(&m_data, m_end);
-            return *this;
-        }
-
-        const_varint_iterator operator++(int) {
-            const const_varint_iterator tmp(*this);
-            ++(*this);
-            return tmp;
-        }
-
-        bool operator==(const const_varint_iterator& rhs) const noexcept {
-            return m_data == rhs.m_data && m_end == rhs.m_end;
-        }
-
-        bool operator!=(const const_varint_iterator& rhs) const noexcept {
-            return !(*this == rhs);
-        }
-
-    }; // class const_varint_iterator
-
-    template <typename T>
-    class const_svarint_iterator : public const_varint_iterator<T> {
-
-    public:
-
-        const_svarint_iterator() noexcept :
-            const_varint_iterator<T>() {
-        }
-
-        const_svarint_iterator(const char *data, const char* end) noexcept :
-            const_varint_iterator<T>(data, end) {
-        }
-
-        const_svarint_iterator(const const_svarint_iterator&) = default;
-        const_svarint_iterator(const_svarint_iterator&&) = default;
-
-        const_svarint_iterator& operator=(const const_svarint_iterator&) = default;
-        const_svarint_iterator& operator=(const_svarint_iterator&&) = default;
-
-        ~const_svarint_iterator() = default;
-
-        T operator*() {
-            const char* d = this->m_data; // will be thrown away
-            return static_cast<T>(decode_zigzag64(decode_varint(&d, this->m_end)));
-        }
-
-        const_svarint_iterator& operator++() {
-            // Ignore the result, we call decode_varint() just for the
-            // side-effect of updating m_data.
-            decode_varint(&this->m_data, this->m_end);
-            return *this;
-        }
-
-        const_svarint_iterator operator++(int) {
-            const const_svarint_iterator tmp(*this);
-            ++(*this);
-            return tmp;
-        }
-
-    }; // class const_svarint_iterator
-
-public:
-
     /// Forward iterator for iterating over bool (int32 varint) values.
-    typedef const_varint_iterator< int32_t> const_bool_iterator;
+    using const_bool_iterator   = const_varint_iterator< int32_t>;
 
     /// Forward iterator for iterating over enum (int32 varint) values.
-    typedef const_varint_iterator< int32_t> const_enum_iterator;
+    using const_enum_iterator   = const_varint_iterator< int32_t>;
 
     /// Forward iterator for iterating over int32 (varint) values.
-    typedef const_varint_iterator< int32_t> const_int32_iterator;
+    using const_int32_iterator  = const_varint_iterator< int32_t>;
 
     /// Forward iterator for iterating over sint32 (varint) values.
-    typedef const_svarint_iterator<int32_t> const_sint32_iterator;
+    using const_sint32_iterator = const_svarint_iterator<int32_t>;
 
     /// Forward iterator for iterating over uint32 (varint) values.
-    typedef const_varint_iterator<uint32_t> const_uint32_iterator;
+    using const_uint32_iterator = const_varint_iterator<uint32_t>;
 
     /// Forward iterator for iterating over int64 (varint) values.
-    typedef const_varint_iterator< int64_t> const_int64_iterator;
+    using const_int64_iterator  = const_varint_iterator< int64_t>;
 
     /// Forward iterator for iterating over sint64 (varint) values.
-    typedef const_svarint_iterator<int64_t> const_sint64_iterator;
+    using const_sint64_iterator = const_svarint_iterator<int64_t>;
 
     /// Forward iterator for iterating over uint64 (varint) values.
-    typedef const_varint_iterator<uint64_t> const_uint64_iterator;
+    using const_uint64_iterator = const_varint_iterator<uint64_t>;
 
     ///@{
     /**
@@ -677,7 +684,9 @@ public:
      * @pre The current field must be of type "repeated packed bool".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_bool_iterator, pbf_reader::const_bool_iterator> get_packed_bool();
+    iterator_range<pbf_reader::const_bool_iterator> get_packed_bool() {
+        return get_packed<pbf_reader::const_bool_iterator>();
+    }
 
     /**
      * Consume current "repeated packed enum" field.
@@ -688,7 +697,9 @@ public:
      * @pre The current field must be of type "repeated packed enum".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_enum_iterator, pbf_reader::const_enum_iterator> get_packed_enum();
+    iterator_range<pbf_reader::const_enum_iterator> get_packed_enum() {
+        return get_packed<pbf_reader::const_enum_iterator>();
+    }
 
     /**
      * Consume current "repeated packed int32" field.
@@ -699,7 +710,9 @@ public:
      * @pre The current field must be of type "repeated packed int32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_int32_iterator, pbf_reader::const_int32_iterator> get_packed_int32();
+    iterator_range<pbf_reader::const_int32_iterator> get_packed_int32() {
+        return get_packed<pbf_reader::const_int32_iterator>();
+    }
 
     /**
      * Consume current "repeated packed sint32" field.
@@ -710,7 +723,9 @@ public:
      * @pre The current field must be of type "repeated packed sint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_sint32_iterator, pbf_reader::const_sint32_iterator> get_packed_sint32();
+    iterator_range<pbf_reader::const_sint32_iterator> get_packed_sint32() {
+        return get_packed<pbf_reader::const_sint32_iterator>();
+    }
 
     /**
      * Consume current "repeated packed uint32" field.
@@ -721,7 +736,9 @@ public:
      * @pre The current field must be of type "repeated packed uint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_uint32_iterator, pbf_reader::const_uint32_iterator> get_packed_uint32();
+    iterator_range<pbf_reader::const_uint32_iterator> get_packed_uint32() {
+        return get_packed<pbf_reader::const_uint32_iterator>();
+    }
 
     /**
      * Consume current "repeated packed int64" field.
@@ -732,7 +749,9 @@ public:
      * @pre The current field must be of type "repeated packed int64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_int64_iterator, pbf_reader::const_int64_iterator> get_packed_int64();
+    iterator_range<pbf_reader::const_int64_iterator> get_packed_int64() {
+        return get_packed<pbf_reader::const_int64_iterator>();
+    }
 
     /**
      * Consume current "repeated packed sint64" field.
@@ -743,7 +762,9 @@ public:
      * @pre The current field must be of type "repeated packed sint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_sint64_iterator, pbf_reader::const_sint64_iterator> get_packed_sint64();
+    iterator_range<pbf_reader::const_sint64_iterator> get_packed_sint64() {
+        return get_packed<pbf_reader::const_sint64_iterator>();
+    }
 
     /**
      * Consume current "repeated packed uint64" field.
@@ -754,7 +775,9 @@ public:
      * @pre The current field must be of type "repeated packed uint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_uint64_iterator, pbf_reader::const_uint64_iterator> get_packed_uint64();
+    iterator_range<pbf_reader::const_uint64_iterator> get_packed_uint64() {
+        return get_packed<pbf_reader::const_uint64_iterator>();
+    }
 
     /**
      * Consume current "repeated packed fixed32" field.
@@ -765,7 +788,7 @@ public:
      * @pre The current field must be of type "repeated packed fixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_fixed32() -> decltype(packed_fixed<uint32_t>()) {
+    auto get_packed_fixed32() -> decltype(packed_fixed<uint32_t>()) {
         return packed_fixed<uint32_t>();
     }
 
@@ -778,7 +801,7 @@ public:
      * @pre The current field must be of type "repeated packed sfixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_sfixed32() -> decltype(packed_fixed<int32_t>()) {
+    auto get_packed_sfixed32() -> decltype(packed_fixed<int32_t>()) {
         return packed_fixed<int32_t>();
     }
 
@@ -791,7 +814,7 @@ public:
      * @pre The current field must be of type "repeated packed fixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_fixed64() -> decltype(packed_fixed<uint64_t>()) {
+    auto get_packed_fixed64() -> decltype(packed_fixed<uint64_t>()) {
         return packed_fixed<uint64_t>();
     }
 
@@ -804,7 +827,7 @@ public:
      * @pre The current field must be of type "repeated packed sfixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_sfixed64() -> decltype(packed_fixed<int64_t>()) {
+    auto get_packed_sfixed64() -> decltype(packed_fixed<int64_t>()) {
         return packed_fixed<int64_t>();
     }
 
@@ -817,7 +840,7 @@ public:
      * @pre The current field must be of type "repeated packed float".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_float() -> decltype(packed_fixed<float>()) {
+    auto get_packed_float() -> decltype(packed_fixed<float>()) {
         return packed_fixed<float>();
     }
 
@@ -830,7 +853,7 @@ public:
      * @pre The current field must be of type "repeated packed double".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_double() -> decltype(packed_fixed<double>()) {
+    auto get_packed_double() -> decltype(packed_fixed<double>()) {
         return packed_fixed<double>();
     }
 
@@ -838,238 +861,14 @@ public:
 
 }; // class pbf_reader
 
-pbf_reader::pbf_reader(const char *data, std::size_t length) noexcept
-    : m_data(data),
-      m_end(data + length),
-      m_wire_type(pbf_wire_type::unknown),
-      m_tag(0) {
-}
-
-pbf_reader::pbf_reader(std::pair<const char *, std::size_t> data) noexcept
-    : m_data(data.first),
-      m_end(data.first + data.second),
-      m_wire_type(pbf_wire_type::unknown),
-      m_tag(0) {
-}
-
-pbf_reader::pbf_reader(const std::string& data) noexcept
-    : m_data(data.data()),
-      m_end(data.data() + data.size()),
-      m_wire_type(pbf_wire_type::unknown),
-      m_tag(0) {
-}
-
-pbf_reader::operator bool() const noexcept {
-    return m_data < m_end;
-}
-
-bool pbf_reader::next() {
-    if (m_data == m_end) {
-        return false;
-    }
-
-    auto value = get_varint<uint32_t>();
-    m_tag = value >> 3;
-
-    // tags 0 and 19000 to 19999 are not allowed as per
-    // https://developers.google.com/protocol-buffers/docs/proto
-    protozero_assert(((m_tag > 0 && m_tag < 19000) || (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range");
-
-    m_wire_type = pbf_wire_type(value & 0x07);
-    switch (m_wire_type) {
-        case pbf_wire_type::varint:
-        case pbf_wire_type::fixed64:
-        case pbf_wire_type::length_delimited:
-        case pbf_wire_type::fixed32:
-            break;
-        default:
-            throw unknown_pbf_wire_type_exception();
-    }
-
-    return true;
-}
-
-bool pbf_reader::next(pbf_tag_type requested_tag) {
-    while (next()) {
-        if (m_tag == requested_tag) {
-            return true;
-        } else {
-            skip();
-        }
-    }
-    return false;
-}
-
-pbf_tag_type pbf_reader::tag() const noexcept {
-    return m_tag;
-}
-
-pbf_wire_type pbf_reader::wire_type() const noexcept {
-    return m_wire_type;
-}
-
-bool pbf_reader::has_wire_type(pbf_wire_type type) const noexcept {
-    return wire_type() == type;
-}
-
-void pbf_reader::skip_bytes(pbf_length_type len) {
-    if (m_data + len > m_end) {
-        throw end_of_buffer_exception();
-    }
-    m_data += len;
-
-// In debug builds reset the tag to zero so that we can detect (some)
-// wrong code.
-#ifndef NDEBUG
-    m_tag = 0;
-#endif
-}
-
-void pbf_reader::skip() {
-    protozero_assert(tag() != 0 && "call next() before calling skip()");
-    switch (wire_type()) {
-        case pbf_wire_type::varint:
-            (void)get_uint32(); // called for the side-effect of skipping value
-            break;
-        case pbf_wire_type::fixed64:
-            skip_bytes(8);
-            break;
-        case pbf_wire_type::length_delimited:
-            skip_bytes(get_length());
-            break;
-        case pbf_wire_type::fixed32:
-            skip_bytes(4);
-            break;
-        default:
-            protozero_assert(false && "can not be here because next() should have thrown already");
-    }
-}
-
-pbf_length_type pbf_reader::get_len_and_skip() {
-    auto len = get_length();
-    skip_bytes(len);
-    return len;
-}
-
-template <typename T>
-T pbf_reader::get_varint() {
-    return static_cast<T>(decode_varint(&m_data, m_end));
-}
-
-template <typename T>
-T pbf_reader::get_svarint() {
-    protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint");
-    return static_cast<T>(decode_zigzag64(decode_varint(&m_data, m_end)));
-}
-
-uint32_t pbf_reader::get_fixed32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
-    return get_fixed<uint32_t>();
-}
-
-int32_t pbf_reader::get_sfixed32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
-    return get_fixed<int32_t>();
-}
-
-uint64_t pbf_reader::get_fixed64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
-    return get_fixed<uint64_t>();
-}
-
-int64_t pbf_reader::get_sfixed64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
-    return get_fixed<int64_t>();
-}
-
-float pbf_reader::get_float() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
-    return get_fixed<float>();
-}
-
-double pbf_reader::get_double() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
-    return get_fixed<double>();
-}
-
-bool pbf_reader::get_bool() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
-    protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint");
-    skip_bytes(1);
-    return m_data[-1] != 0; // -1 okay because we incremented m_data the line before
-}
-
-std::pair<const char*, pbf_length_type> pbf_reader::get_data() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message");
-    auto len = get_len_and_skip();
-    return std::make_pair(m_data-len, len);
-}
-
-std::string pbf_reader::get_bytes() {
-    auto d = get_data();
-    return std::string(d.first, d.second);
-}
-
-std::string pbf_reader::get_string() {
-    return get_bytes();
-}
-
-std::pair<pbf_reader::const_bool_iterator, pbf_reader::const_bool_iterator> pbf_reader::get_packed_bool() {
-    return get_packed_int32();
-}
-
-std::pair<pbf_reader::const_enum_iterator, pbf_reader::const_enum_iterator> pbf_reader::get_packed_enum() {
-    return get_packed_int32();
-}
-
-std::pair<pbf_reader::const_int32_iterator, pbf_reader::const_int32_iterator> pbf_reader::get_packed_int32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_int32_iterator(m_data-len, m_data),
-                          pbf_reader::const_int32_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_uint32_iterator, pbf_reader::const_uint32_iterator> pbf_reader::get_packed_uint32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_uint32_iterator(m_data-len, m_data),
-                          pbf_reader::const_uint32_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_sint32_iterator, pbf_reader::const_sint32_iterator> pbf_reader::get_packed_sint32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_sint32_iterator(m_data-len, m_data),
-                          pbf_reader::const_sint32_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_int64_iterator, pbf_reader::const_int64_iterator> pbf_reader::get_packed_int64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_int64_iterator(m_data-len, m_data),
-                          pbf_reader::const_int64_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_uint64_iterator, pbf_reader::const_uint64_iterator> pbf_reader::get_packed_uint64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_uint64_iterator(m_data-len, m_data),
-                          pbf_reader::const_uint64_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_sint64_iterator, pbf_reader::const_sint64_iterator> pbf_reader::get_packed_sint64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_sint64_iterator(m_data-len, m_data),
-                          pbf_reader::const_sint64_iterator(m_data, m_data));
+/**
+ * Swap two pbf_reader objects.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline void swap(pbf_reader& lhs, pbf_reader& rhs) noexcept {
+    lhs.swap(rhs);
 }
 
 } // end namespace protozero
diff --git a/include/protozero/pbf_writer.hpp b/include/protozero/pbf_writer.hpp
index 422e147..3ce0f14 100644
--- a/include/protozero/pbf_writer.hpp
+++ b/include/protozero/pbf_writer.hpp
@@ -22,6 +22,7 @@ documentation.
 #include <iterator>
 #include <limits>
 #include <string>
+#include <utility>
 
 #include <protozero/config.hpp>
 #include <protozero/types.hpp>
@@ -68,38 +69,38 @@ class pbf_writer {
     // parent to the position where the data of the submessage is written to.
     std::size_t m_pos = 0;
 
-    inline void add_varint(uint64_t value) {
+    void add_varint(uint64_t value) {
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
         write_varint(std::back_inserter(*m_data), value);
     }
 
-    inline void add_field(pbf_tag_type tag, pbf_wire_type type) {
+    void add_field(pbf_tag_type tag, pbf_wire_type type) {
         protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1 << 29) - 1))) && "tag out of range");
-        uint32_t b = (tag << 3) | uint32_t(type);
+        const uint32_t b = (tag << 3) | uint32_t(type);
         add_varint(b);
     }
 
-    inline void add_tagged_varint(pbf_tag_type tag, uint64_t value) {
+    void add_tagged_varint(pbf_tag_type tag, uint64_t value) {
         add_field(tag, pbf_wire_type::varint);
         add_varint(value);
     }
 
     template <typename T>
-    inline void add_fixed(T value) {
+    void add_fixed(T value) {
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
 #if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
         m_data->append(reinterpret_cast<const char*>(&value), sizeof(T));
 #else
-        auto size = m_data->size();
+        const auto size = m_data->size();
         m_data->resize(size + sizeof(T));
         byteswap<sizeof(T)>(reinterpret_cast<const char*>(&value), const_cast<char*>(m_data->data() + size));
 #endif
     }
 
     template <typename T, typename It>
-    inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) {
+    void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) {
         if (first == last) {
             return;
         }
@@ -112,12 +113,12 @@ class pbf_writer {
     }
 
     template <typename T, typename It>
-    inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) {
+    void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) {
         if (first == last) {
             return;
         }
 
-        auto length = std::distance(first, last);
+        const auto length = std::distance(first, last);
         add_length_varint(tag, sizeof(T) * pbf_length_type(length));
         reserve(sizeof(T) * std::size_t(length));
 
@@ -127,7 +128,7 @@ class pbf_writer {
     }
 
     template <typename It>
-    inline void add_packed_varint(pbf_tag_type tag, It first, It last) {
+    void add_packed_varint(pbf_tag_type tag, It first, It last) {
         if (first == last) {
             return;
         }
@@ -140,7 +141,7 @@ class pbf_writer {
     }
 
     template <typename It>
-    inline void add_packed_svarint(pbf_tag_type tag, It first, It last) {
+    void add_packed_svarint(pbf_tag_type tag, It first, It last) {
         if (first == last) {
             return;
         }
@@ -155,14 +156,14 @@ 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 const int reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1;
+    static constexpr const 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 const std::size_t size_is_known = std::numeric_limits<std::size_t>::max();
+    static constexpr const std::size_t size_is_known = std::numeric_limits<std::size_t>::max();
 
-    inline void open_submessage(pbf_tag_type tag, std::size_t size) {
+    void open_submessage(pbf_tag_type tag, std::size_t size) {
         protozero_assert(m_pos == 0);
         protozero_assert(m_data);
         if (size == 0) {
@@ -177,7 +178,7 @@ class pbf_writer {
         m_pos = m_data->size();
     }
 
-    inline void rollback_submessage() {
+    void rollback_submessage() {
         protozero_assert(m_pos != 0);
         protozero_assert(m_rollback_pos != size_is_known);
         protozero_assert(m_data);
@@ -185,20 +186,20 @@ class pbf_writer {
         m_pos = 0;
     }
 
-    inline void commit_submessage() {
+    void commit_submessage() {
         protozero_assert(m_pos != 0);
         protozero_assert(m_rollback_pos != size_is_known);
         protozero_assert(m_data);
-        auto length = pbf_length_type(m_data->size() - m_pos);
+        const auto length = pbf_length_type(m_data->size() - m_pos);
 
         protozero_assert(m_data->size() >= m_pos - reserve_bytes);
-        auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length);
+        const auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length);
 
         m_data->erase(m_data->begin() + long(m_pos) - reserve_bytes + n, m_data->begin() + long(m_pos));
         m_pos = 0;
     }
 
-    inline void close_submessage() {
+    void close_submessage() {
         protozero_assert(m_data);
         if (m_pos == 0 || m_rollback_pos == size_is_known) {
             return;
@@ -210,7 +211,7 @@ class pbf_writer {
         }
     }
 
-    inline void add_length_varint(pbf_tag_type tag, pbf_length_type length) {
+    void add_length_varint(pbf_tag_type tag, pbf_length_type length) {
         add_field(tag, pbf_wire_type::length_delimited);
         add_varint(length);
     }
@@ -222,7 +223,7 @@ public:
      * stores a reference to that string and adds all data to it. The string
      * doesn't have to be empty. The pbf_writer will just append data.
      */
-    inline explicit pbf_writer(std::string& data) noexcept :
+    explicit pbf_writer(std::string& data) noexcept :
         m_data(&data),
         m_parent_writer(nullptr),
         m_pos(0) {
@@ -232,7 +233,7 @@ public:
      * Create a writer without a data store. In this form the writer can not
      * be used!
      */
-    inline pbf_writer() noexcept :
+    pbf_writer() noexcept :
         m_data(nullptr),
         m_parent_writer(nullptr),
         m_pos(0) {
@@ -248,7 +249,7 @@ public:
      *        Setting this allows some optimizations but is only possible in
      *        a few very specific cases.
      */
-    inline pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) :
+    pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) :
         m_data(parent_writer.m_data),
         m_parent_writer(&parent_writer),
         m_pos(0) {
@@ -262,18 +263,31 @@ public:
     pbf_writer& operator=(const pbf_writer&) noexcept = default;
 
     /// A pbf_writer object can be moved
-    inline pbf_writer(pbf_writer&&) noexcept = default;
+    pbf_writer(pbf_writer&&) noexcept = default;
 
     /// A pbf_writer object can be moved
-    inline pbf_writer& operator=(pbf_writer&&) noexcept = default;
+    pbf_writer& operator=(pbf_writer&&) noexcept = default;
 
-    inline ~pbf_writer() {
+    ~pbf_writer() {
         if (m_parent_writer) {
             m_parent_writer->close_submessage();
         }
     }
 
     /**
+     * Swap the contents of this object with the other.
+     *
+     * @param other Other object to swap data with.
+     */
+    void swap(pbf_writer& other) noexcept {
+        using std::swap;
+        swap(m_data, other.m_data);
+        swap(m_parent_writer, other.m_parent_writer);
+        swap(m_rollback_pos, other.m_rollback_pos);
+        swap(m_pos, other.m_pos);
+    }
+
+    /**
      * Reserve size bytes in the underlying message store in addition to
      * whatever the message store already holds. So unlike
      * the `std::string::reserve()` method this is not an absolute size,
@@ -286,7 +300,14 @@ public:
         m_data->reserve(m_data->size() + size);
     }
 
-    inline void rollback() {
+    /**
+     * Cancel writing of this submessage. The complete submessage will be
+     * removed as if it was never created and no fields were added.
+     *
+     * @pre Must be a pbf_writer of a submessage, ie one opened with the
+     *      pbf_writer constructor taking a parent message.
+     */
+    void rollback() {
         protozero_assert(m_parent_writer && "you can't call rollback() on a pbf_writer without a parent");
         protozero_assert(m_pos == 0 && "you can't call rollback() on a pbf_writer that has an open nested submessage");
         m_parent_writer->rollback_submessage();
@@ -304,7 +325,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_bool(pbf_tag_type tag, bool value) {
+    void add_bool(pbf_tag_type tag, bool value) {
         add_field(tag, pbf_wire_type::varint);
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
@@ -317,7 +338,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_enum(pbf_tag_type tag, int32_t value) {
+    void add_enum(pbf_tag_type tag, int32_t value) {
         add_tagged_varint(tag, uint64_t(value));
     }
 
@@ -327,7 +348,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_int32(pbf_tag_type tag, int32_t value) {
+    void add_int32(pbf_tag_type tag, int32_t value) {
         add_tagged_varint(tag, uint64_t(value));
     }
 
@@ -337,7 +358,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sint32(pbf_tag_type tag, int32_t value) {
+    void add_sint32(pbf_tag_type tag, int32_t value) {
         add_tagged_varint(tag, encode_zigzag32(value));
     }
 
@@ -347,7 +368,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_uint32(pbf_tag_type tag, uint32_t value) {
+    void add_uint32(pbf_tag_type tag, uint32_t value) {
         add_tagged_varint(tag, value);
     }
 
@@ -357,7 +378,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_int64(pbf_tag_type tag, int64_t value) {
+    void add_int64(pbf_tag_type tag, int64_t value) {
         add_tagged_varint(tag, uint64_t(value));
     }
 
@@ -367,7 +388,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sint64(pbf_tag_type tag, int64_t value) {
+    void add_sint64(pbf_tag_type tag, int64_t value) {
         add_tagged_varint(tag, encode_zigzag64(value));
     }
 
@@ -377,7 +398,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_uint64(pbf_tag_type tag, uint64_t value) {
+    void add_uint64(pbf_tag_type tag, uint64_t value) {
         add_tagged_varint(tag, value);
     }
 
@@ -387,7 +408,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_fixed32(pbf_tag_type tag, uint32_t value) {
+    void add_fixed32(pbf_tag_type tag, uint32_t value) {
         add_field(tag, pbf_wire_type::fixed32);
         add_fixed<uint32_t>(value);
     }
@@ -398,7 +419,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sfixed32(pbf_tag_type tag, int32_t value) {
+    void add_sfixed32(pbf_tag_type tag, int32_t value) {
         add_field(tag, pbf_wire_type::fixed32);
         add_fixed<int32_t>(value);
     }
@@ -409,7 +430,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_fixed64(pbf_tag_type tag, uint64_t value) {
+    void add_fixed64(pbf_tag_type tag, uint64_t value) {
         add_field(tag, pbf_wire_type::fixed64);
         add_fixed<uint64_t>(value);
     }
@@ -420,7 +441,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sfixed64(pbf_tag_type tag, int64_t value) {
+    void add_sfixed64(pbf_tag_type tag, int64_t value) {
         add_field(tag, pbf_wire_type::fixed64);
         add_fixed<int64_t>(value);
     }
@@ -431,7 +452,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_float(pbf_tag_type tag, float value) {
+    void add_float(pbf_tag_type tag, float value) {
         add_field(tag, pbf_wire_type::fixed32);
         add_fixed<float>(value);
     }
@@ -442,7 +463,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_double(pbf_tag_type tag, double value) {
+    void add_double(pbf_tag_type tag, double value) {
         add_field(tag, pbf_wire_type::fixed64);
         add_fixed<double>(value);
     }
@@ -454,7 +475,7 @@ public:
      * @param value Pointer to value to be written
      * @param size Number of bytes to be written
      */
-    inline void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) {
+    void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) {
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
         protozero_assert(size <= std::numeric_limits<pbf_length_type>::max());
@@ -468,7 +489,17 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_bytes(pbf_tag_type tag, const std::string& value) {
+    void add_bytes(pbf_tag_type tag, const data_view& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "bytes" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    void add_bytes(pbf_tag_type tag, const std::string& value) {
         add_bytes(tag, value.data(), value.size());
     }
 
@@ -479,7 +510,7 @@ public:
      * @param value Pointer to value to be written
      * @param size Number of bytes to be written
      */
-    inline void add_string(pbf_tag_type tag, const char* value, std::size_t size) {
+    void add_string(pbf_tag_type tag, const char* value, std::size_t size) {
         add_bytes(tag, value, size);
     }
 
@@ -489,7 +520,17 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_string(pbf_tag_type tag, const std::string& value) {
+    void add_string(pbf_tag_type tag, const data_view& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "string" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    void add_string(pbf_tag_type tag, const std::string& value) {
         add_bytes(tag, value.data(), value.size());
     }
 
@@ -500,7 +541,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Pointer to value to be written
      */
-    inline void add_string(pbf_tag_type tag, const char* value) {
+    void add_string(pbf_tag_type tag, const char* value) {
         add_bytes(tag, value, std::strlen(value));
     }
 
@@ -511,7 +552,7 @@ public:
      * @param value Pointer to message to be written
      * @param size Length of the message
      */
-    inline void add_message(pbf_tag_type tag, const char* value, std::size_t size) {
+    void add_message(pbf_tag_type tag, const char* value, std::size_t size) {
         add_bytes(tag, value, size);
     }
 
@@ -521,7 +562,17 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written. The value must be a complete message.
      */
-    inline void add_message(pbf_tag_type tag, const std::string& value) {
+    void add_message(pbf_tag_type tag, const data_view& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "message" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written. The value must be a complete message.
+     */
+    void add_message(pbf_tag_type tag, const std::string& value) {
         add_bytes(tag, value.data(), value.size());
     }
 
@@ -535,126 +586,126 @@ public:
     /**
      * Add "repeated packed bool" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to bool.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed enum" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed int32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed sint32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_svarint(tag, first, last);
     }
 
     /**
      * Add "repeated packed uint32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed int64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed sint64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_svarint(tag, first, last);
     }
 
     /**
      * Add "repeated packed uint64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed fixed32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<uint32_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -662,14 +713,14 @@ public:
     /**
      * Add "repeated packed sfixed32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<int32_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -677,14 +728,14 @@ public:
     /**
      * Add "repeated packed fixed64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<uint64_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -692,14 +743,14 @@ public:
     /**
      * Add "repeated packed sfixed64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<int64_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -707,14 +758,14 @@ public:
     /**
      * Add "repeated packed float" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to float.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<float, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -722,14 +773,14 @@ public:
     /**
      * Add "repeated packed double" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to double.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<double, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -742,6 +793,16 @@ public:
 
 }; // class pbf_writer
 
+/**
+ * Swap two pbf_writer objects.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline void swap(pbf_writer& lhs, pbf_writer& rhs) noexcept {
+    lhs.swap(rhs);
+}
+
 namespace detail {
 
     class packed_field {
@@ -817,19 +878,46 @@ namespace detail {
 
 } // end namespace detail
 
+/// Class for generating packed repeated bool fields.
 using packed_field_bool     = detail::packed_field_varint<bool>;
+
+/// Class for generating packed repeated enum fields.
 using packed_field_enum     = detail::packed_field_varint<int32_t>;
+
+/// Class for generating packed repeated int32 fields.
 using packed_field_int32    = detail::packed_field_varint<int32_t>;
+
+/// Class for generating packed repeated sint32 fields.
 using packed_field_sint32   = detail::packed_field_svarint<int32_t>;
+
+/// Class for generating packed repeated uint32 fields.
 using packed_field_uint32   = detail::packed_field_varint<uint32_t>;
+
+/// Class for generating packed repeated int64 fields.
 using packed_field_int64    = detail::packed_field_varint<int64_t>;
+
+/// Class for generating packed repeated sint64 fields.
 using packed_field_sint64   = detail::packed_field_svarint<int64_t>;
+
+/// Class for generating packed repeated uint64 fields.
 using packed_field_uint64   = detail::packed_field_varint<uint64_t>;
+
+/// Class for generating packed repeated fixed32 fields.
 using packed_field_fixed32  = detail::packed_field_fixed<uint32_t>;
+
+/// Class for generating packed repeated sfixed32 fields.
 using packed_field_sfixed32 = detail::packed_field_fixed<int32_t>;
+
+/// Class for generating packed repeated fixed64 fields.
 using packed_field_fixed64  = detail::packed_field_fixed<uint64_t>;
+
+/// Class for generating packed repeated sfixed64 fields.
 using packed_field_sfixed64 = detail::packed_field_fixed<int64_t>;
+
+/// Class for generating packed repeated float fields.
 using packed_field_float    = detail::packed_field_fixed<float>;
+
+/// Class for generating packed repeated double fields.
 using packed_field_double   = detail::packed_field_fixed<double>;
 
 } // end namespace protozero
diff --git a/include/protozero/types.hpp b/include/protozero/types.hpp
index 6856b3d..8b04638 100644
--- a/include/protozero/types.hpp
+++ b/include/protozero/types.hpp
@@ -16,33 +16,173 @@ documentation.
  * @brief Contains the declaration of low-level types used in the pbf format.
  */
 
+#include <cstddef>
 #include <cstdint>
+#include <cstring>
+#include <string>
+#include <utility>
+
+#include <protozero/config.hpp>
 
 namespace protozero {
 
+/**
+ * The type used for field tags (field numbers).
+ */
+using pbf_tag_type = uint32_t;
+
+/**
+ * The type used to encode type information.
+ * See the table on
+ *    https://developers.google.com/protocol-buffers/docs/encoding
+ */
+enum class pbf_wire_type : uint32_t {
+    varint           = 0, // int32/64, uint32/64, sint32/64, bool, enum
+    fixed64          = 1, // fixed64, sfixed64, double
+    length_delimited = 2, // string, bytes, embedded messages,
+                            // packed repeated fields
+    fixed32          = 5, // fixed32, sfixed32, float
+    unknown          = 99 // used for default setting in this library
+};
+
+/**
+ * The type used for length values, such as the length of a field.
+ */
+using pbf_length_type = uint32_t;
+
+#ifdef PROTOZERO_USE_VIEW
+using data_view = PROTOZERO_USE_VIEW;
+#else
+
+/**
+ * Holds a pointer to some data and a length.
+ *
+ * This class is supposed to be compatible with the std::string_view
+ * that will be available in C++17.
+ */
+class data_view {
+
+    const char* m_data;
+    std::size_t m_size;
+
+public:
+
+    /**
+     * Default constructor. Construct an empty data_view.
+     */
+    constexpr data_view() noexcept
+        : m_data(nullptr),
+          m_size(0) {
+    }
+
+    /**
+     * Create data_view from pointer and size.
+     *
+     * @param data Pointer to the data.
+     * @param size Length of the data.
+     */
+    constexpr data_view(const char* data, std::size_t size) noexcept
+        : m_data(data),
+          m_size(size) {
+    }
+
     /**
-     * The type used for field tags (field numbers).
+     * Create data_view from string.
+     *
+     * @param str String with the data.
      */
-    typedef uint32_t pbf_tag_type;
+    data_view(const std::string& str) noexcept
+        : m_data(str.data()),
+          m_size(str.size()) {
+    }
 
     /**
-     * The type used to encode type information.
-     * See the table on
-     *    https://developers.google.com/protocol-buffers/docs/encoding
+     * Create data_view from zero-terminated string.
+     *
+     * @param data Pointer to the data.
      */
-    enum class pbf_wire_type : uint32_t {
-        varint           = 0, // int32/64, uint32/64, sint32/64, bool, enum
-        fixed64          = 1, // fixed64, sfixed64, double
-        length_delimited = 2, // string, bytes, embedded messages,
-                              // packed repeated fields
-        fixed32          = 5, // fixed32, sfixed32, float
-        unknown          = 99 // used for default setting in this library
-    };
+    data_view(const char* data) noexcept
+        : m_data(data),
+          m_size(std::strlen(data)) {
+    }
 
     /**
-     * The type used for length values, such as the length of a field.
+     * Swap the contents of this object with the other.
+     *
+     * @param other Other object to swap data with.
      */
-    typedef uint32_t pbf_length_type;
+    void swap(data_view& other) noexcept {
+        using std::swap;
+        swap(m_data, other.m_data);
+        swap(m_size, other.m_size);
+    }
+
+    /// Return pointer to data.
+    constexpr const char* data() const noexcept {
+        return m_data;
+    }
+
+    /// Return length of data in bytes.
+    constexpr std::size_t size() const noexcept {
+        return m_size;
+    }
+
+    /**
+     * Convert data view to string.
+     *
+     * @pre Must not be default constructed data_view.
+     */
+    std::string to_string() const {
+        protozero_assert(m_data);
+        return std::string{m_data, m_size};
+    }
+
+    /**
+     * Convert data view to string.
+     *
+     * @pre Must not be default constructed data_view.
+     */
+    explicit operator std::string() const {
+        protozero_assert(m_data);
+        return std::string{m_data, m_size};
+    }
+
+}; // class data_view
+
+/**
+ * Swap two data_view objects.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline void swap(data_view& lhs, data_view& rhs) noexcept {
+    lhs.swap(rhs);
+}
+
+/**
+ * Two data_view instances are equal if they have the same size and the
+ * same content.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator==(data_view& lhs, data_view& rhs) noexcept {
+    return lhs.size() == rhs.size() && !std::strcmp(lhs.data(), rhs.data());
+}
+
+/**
+ * Two data_view instances are not equal if they have different sizes or the
+ * content differs.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator!=(data_view& lhs, data_view& rhs) noexcept {
+    return !(lhs == rhs);
+}
+
+#endif
+
 
 } // end namespace protozero
 
diff --git a/include/protozero/varint.hpp b/include/protozero/varint.hpp
index 4242df9..f6142d1 100644
--- a/include/protozero/varint.hpp
+++ b/include/protozero/varint.hpp
@@ -23,13 +23,54 @@ documentation.
 namespace protozero {
 
 /**
- * The maximum length of a 64bit varint.
+ * The maximum length of a 64 bit varint.
  */
 constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
 
-// from https://github.com/facebook/folly/blob/master/folly/Varint.h
+namespace detail {
+
+    // from https://github.com/facebook/folly/blob/master/folly/Varint.h
+    inline uint64_t decode_varint_impl(const char** data, const char* end) {
+        const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
+        const int8_t* iend = reinterpret_cast<const int8_t*>(end);
+        const int8_t* p = begin;
+        uint64_t val = 0;
+
+        if (iend - begin >= max_varint_length) {  // fast path
+            do {
+                int64_t b;
+                b = *p++; val  = uint64_t((b & 0x7f)      ); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) <<  7); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break;
+                throw varint_too_long_exception();
+            } while (false);
+        } else {
+            int shift = 0;
+            while (p != iend && *p < 0) {
+                val |= uint64_t(*p++ & 0x7f) << shift;
+                shift += 7;
+            }
+            if (p == iend) {
+                throw end_of_buffer_exception();
+            }
+            val |= uint64_t(*p++) << shift;
+        }
+
+        *data = reinterpret_cast<const char*>(p);
+        return val;
+    }
+
+} // end namespace detail
+
 /**
- * Decode a 64bit varint.
+ * Decode a 64 bit varint.
  *
  * Strong exception guarantee: if there is an exception the data pointer will
  * not be changed.
@@ -39,54 +80,68 @@ constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
  * @param[in] end Pointer one past the end of the input data.
  * @returns The decoded integer
  * @throws varint_too_long_exception if the varint is longer then the maximum
- *         length that would fit in a 64bit int. Usually this means your data
+ *         length that would fit in a 64 bit int. Usually this means your data
  *         is corrupted or you are trying to read something as a varint that
  *         isn't.
  * @throws end_of_buffer_exception if the *end* of the buffer was reached
  *         before the end of the varint.
  */
 inline uint64_t decode_varint(const char** data, const char* end) {
+    // If this is a one-byte varint, decode it here.
+    if (end != *data && ((**data & 0x80) == 0)) {
+        uint64_t val = uint64_t(**data);
+        ++(*data);
+        return val;
+    }
+    // If this varint is more than one byte, defer to complete implementation.
+    return detail::decode_varint_impl(data, end);
+}
+
+/**
+ * Skip over a varint.
+ *
+ * Strong exception guarantee: if there is an exception the data pointer will
+ * not be changed.
+ *
+ * @param[in,out] data Pointer to pointer to the input data. After the function
+ *        returns this will point to the next data to be read.
+ * @param[in] end Pointer one past the end of the input data.
+ * @throws end_of_buffer_exception if the *end* of the buffer was reached
+ *         before the end of the varint.
+ */
+inline void skip_varint(const char** data, const char* end) {
     const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
     const int8_t* iend = reinterpret_cast<const int8_t*>(end);
     const int8_t* p = begin;
-    uint64_t val = 0;
-
-    if (iend - begin >= max_varint_length) {  // fast path
-        do {
-            int64_t b;
-            b = *p++; val  = uint64_t((b & 0x7f)      ); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) <<  7); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break;
-            throw varint_too_long_exception();
-        } while (false);
-    } else {
-        int shift = 0;
-        while (p != iend && *p < 0) {
-            val |= uint64_t(*p++ & 0x7f) << shift;
-            shift += 7;
-        }
-        if (p == iend) {
-            throw end_of_buffer_exception();
-        }
-        val |= uint64_t(*p++) << shift;
+
+    while (p != iend && *p < 0) {
+        ++p;
+    }
+
+    if (p >= begin + max_varint_length) {
+        throw varint_too_long_exception();
     }
 
+    if (p == iend) {
+        throw end_of_buffer_exception();
+    }
+
+    ++p;
+
     *data = reinterpret_cast<const char*>(p);
-    return val;
 }
 
 /**
- * Varint-encode a 64bit integer.
+ * Varint encode a 64 bit integer.
+ *
+ * @tparam T An output iterator type.
+ * @param data Output iterator the varint encoded value will be written to
+ *             byte by byte.
+ * @param value The integer that will be encoded.
+ * @throws Any exception thrown by increment or dereference operator on data.
  */
-template <typename OutputIterator>
-inline int write_varint(OutputIterator data, uint64_t value) {
+template <typename T>
+inline int write_varint(T data, uint64_t value) {
     int n=1;
 
     while (value >= 0x80) {
diff --git a/include/protozero/version.hpp b/include/protozero/version.hpp
index 7b60e2e..d427941 100644
--- a/include/protozero/version.hpp
+++ b/include/protozero/version.hpp
@@ -10,13 +10,26 @@ documentation.
 
 *****************************************************************************/
 
+/**
+ * @file version.hpp
+ *
+ * @brief Contains macros defining the protozero version.
+ */
+
+/// The major version number
 #define PROTOZERO_VERSION_MAJOR 1
-#define PROTOZERO_VERSION_MINOR 3
+
+/// The minor version number
+#define PROTOZERO_VERSION_MINOR 4
+
+/// The patch number
 #define PROTOZERO_VERSION_PATCH 0
 
+/// The complete version number
 #define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH)
 
-#define PROTOZERO_VERSION_STRING "1.3.0"
+/// Version number as string
+#define PROTOZERO_VERSION_STRING "1.4.0"
 
 
 #endif // PROTOZERO_VERSION_HPP
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 241c192..f7b35e8 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -85,6 +85,40 @@ function(add_unit_test _tgroup _tname)
     endif()
 endfunction()
 
+
+#-----------------------------------------------------------------------------
+#
+#  Prepare some variables so querying it for tests work properly.
+#
+#-----------------------------------------------------------------------------
+
+if(NOT GEOS_FOUND)
+    set(GEOS_FOUND FALSE)
+endif()
+
+if(NOT PROJ_FOUND)
+    set(PROJ_FOUND FALSE)
+endif()
+
+if(NOT SPARSEHASH_FOUND)
+    set(SPARSEHASH_FOUND FALSE)
+endif()
+
+if(NOT BZIP2_FOUND)
+    set(BZIP2_FOUND FALSE)
+endif()
+
+if(NOT Threads_FOUND)
+    set(Threads_FOUND FALSE)
+endif()
+
+if(GEOS_FOUND AND PROJ_FOUND)
+    set(GEOS_AND_PROJ_FOUND TRUE)
+else()
+    set(GEOS_AND_PROJ_FOUND FALSE)
+endif()
+
+
 #-----------------------------------------------------------------------------
 #
 #  Add all tests.
@@ -112,11 +146,6 @@ add_unit_test(buffer test_buffer_purge)
 
 add_unit_test(builder test_attr)
 
-if(GEOS_FOUND AND PROJ_FOUND)
-    set(GEOS_AND_PROJ_FOUND TRUE)
-else()
-    set(GEOS_AND_PROJ_FOUND FALSE)
-endif()
 add_unit_test(geom test_factory_with_projection
     ENABLE_IF ${GEOS_AND_PROJ_FOUND}
     LIBS ${GEOS_LIBRARY} ${PROJ_LIBRARY})
@@ -129,7 +158,7 @@ add_unit_test(geom test_geos_wkb ENABLE_IF ${GEOS_FOUND} LIBS ${GEOS_LIBRARY})
 add_unit_test(geom test_mercator)
 add_unit_test(geom test_ogr ENABLE_IF ${GDAL_FOUND} LIBS ${GDAL_LIBRARY})
 add_unit_test(geom test_projection ENABLE_IF ${PROJ_FOUND} LIBS ${PROJ_LIBRARY})
-add_unit_test(geom test_tile)
+add_unit_test(geom test_tile ENABLE_IF ${GEOS_FOUND})
 add_unit_test(geom test_wkb)
 add_unit_test(geom test_wkt)
 
diff --git a/test/include/catch.hpp b/test/include/catch.hpp
index 2a7146a..879fc5b 100644
--- a/test/include/catch.hpp
+++ b/test/include/catch.hpp
@@ -1,6 +1,6 @@
 /*
- *  Catch v1.4.0
- *  Generated: 2016-03-15 07:23:12.623111
+ *  Catch v1.5.6
+ *  Generated: 2016-06-09 19:20:41.460328
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
  *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
@@ -106,8 +106,16 @@
 
 // All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11
 
-#if defined(__cplusplus) && __cplusplus >= 201103L
-#  define CATCH_CPP11_OR_GREATER
+#ifdef __cplusplus
+
+#  if __cplusplus >= 201103L
+#    define CATCH_CPP11_OR_GREATER
+#  endif
+
+#  if __cplusplus >= 201402L
+#    define CATCH_CPP14_OR_GREATER
+#  endif
+
 #endif
 
 #ifdef __clang__
@@ -2065,7 +2073,7 @@ namespace Catch {
             __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \
         } \
         INTERNAL_CATCH_REACT( __catchResult ) \
-    } while( Catch::isTrue( false && static_cast<bool>(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
+    } while( Catch::isTrue( false && !!(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
 
 ///////////////////////////////////////////////////////////////////////////////
 #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \
@@ -3450,7 +3458,7 @@ namespace Catch {
     };
 
     class DebugOutStream : public IStream {
-        std::auto_ptr<StreamBufBase> m_streamBuf;
+        CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf;
         mutable std::ostream m_os;
     public:
         DebugOutStream();
@@ -3598,7 +3606,7 @@ namespace Catch {
         }
         ConfigData m_data;
 
-        std::auto_ptr<IStream const> m_stream;
+        CATCH_AUTO_PTR( IStream const ) m_stream;
         TestSpec m_testSpec;
     };
 
@@ -3618,7 +3626,7 @@ namespace Catch {
 #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch {
 // #included from: ../external/clara.h
 
-// Version 0.0.1.1
+// Version 0.0.2.4
 
 // Only use header guard if we are not using an outer namespace
 #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE)
@@ -3934,6 +3942,10 @@ namespace Tbc {
 #include <stdexcept>
 #include <memory>
 
+#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+#define CLARA_PLATFORM_WINDOWS
+#endif
+
 // Use optional outer namespace
 #ifdef STITCH_CLARA_OPEN_NAMESPACE
 STITCH_CLARA_OPEN_NAMESPACE
@@ -3957,9 +3969,6 @@ namespace Clara {
     const unsigned int consoleWidth = 80;
 #endif
 
-        // Use this to try and stop compiler from warning about unreachable code
-        inline bool isTrue( bool value ) { return value; }
-
         using namespace Tbc;
 
         inline bool startsWith( std::string const& str, std::string const& prefix ) {
@@ -3995,14 +4004,6 @@ namespace Clara {
             else
                 throw std::runtime_error( "Expected a boolean value but did not recognise:\n  '" + _source + "'" );
         }
-        inline void convertInto( bool _source, bool& _dest ) {
-            _dest = _source;
-        }
-        template<typename T>
-        inline void convertInto( bool, T& ) {
-            if( isTrue( true ) )
-                throw std::runtime_error( "Invalid conversion" );
-        }
 
         template<typename ConfigT>
         struct IArgFunction {
@@ -4012,7 +4013,6 @@ namespace Clara {
             IArgFunction( IArgFunction const& ) = default;
 #endif
             virtual void set( ConfigT& config, std::string const& value ) const = 0;
-            virtual void setFlag( ConfigT& config ) const = 0;
             virtual bool takesArg() const = 0;
             virtual IArgFunction* clone() const = 0;
         };
@@ -4034,9 +4034,6 @@ namespace Clara {
             void set( ConfigT& config, std::string const& value ) const {
                 functionObj->set( config, value );
             }
-            void setFlag( ConfigT& config ) const {
-                functionObj->setFlag( config );
-            }
             bool takesArg() const { return functionObj->takesArg(); }
 
             bool isSet() const {
@@ -4049,7 +4046,6 @@ namespace Clara {
         template<typename C>
         struct NullBinder : IArgFunction<C>{
             virtual void set( C&, std::string const& ) const {}
-            virtual void setFlag( C& ) const {}
             virtual bool takesArg() const { return true; }
             virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); }
         };
@@ -4060,9 +4056,6 @@ namespace Clara {
             virtual void set( C& p, std::string const& stringValue ) const {
                 convertInto( stringValue, p.*member );
             }
-            virtual void setFlag( C& p ) const {
-                convertInto( true, p.*member );
-            }
             virtual bool takesArg() const { return !IsBool<M>::value; }
             virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); }
             M C::* member;
@@ -4075,11 +4068,6 @@ namespace Clara {
                 convertInto( stringValue, value );
                 (p.*member)( value );
             }
-            virtual void setFlag( C& p ) const {
-                typename RemoveConstRef<M>::type value;
-                convertInto( true, value );
-                (p.*member)( value );
-            }
             virtual bool takesArg() const { return !IsBool<M>::value; }
             virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); }
             void (C::*member)( M );
@@ -4093,9 +4081,6 @@ namespace Clara {
                 if( value )
                     (p.*member)();
             }
-            virtual void setFlag( C& p ) const {
-                (p.*member)();
-            }
             virtual bool takesArg() const { return false; }
             virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); }
             void (C::*member)();
@@ -4110,9 +4095,6 @@ namespace Clara {
                 if( value )
                     function( obj );
             }
-            virtual void setFlag( C& p ) const {
-                function( p );
-            }
             virtual bool takesArg() const { return false; }
             virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); }
             void (*function)( C& );
@@ -4126,11 +4108,6 @@ namespace Clara {
                 convertInto( stringValue, value );
                 function( obj, value );
             }
-            virtual void setFlag( C& obj ) const {
-                typename RemoveConstRef<T>::type value;
-                convertInto( true, value );
-                function( obj, value );
-            }
             virtual bool takesArg() const { return !IsBool<T>::value; }
             virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); }
             void (*function)( C&, T );
@@ -4138,8 +4115,20 @@ namespace Clara {
 
     } // namespace Detail
 
-    struct Parser {
-        Parser() : separators( " \t=:" ) {}
+    inline std::vector<std::string> argsToVector( int argc, char const* const* const argv ) {
+        std::vector<std::string> args( static_cast<std::size_t>( argc ) );
+        for( std::size_t i = 0; i < static_cast<std::size_t>( argc ); ++i )
+            args[i] = argv[i];
+
+        return args;
+    }
+
+    class Parser {
+        enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional };
+        Mode mode;
+        std::size_t from;
+        bool inQuotes;
+    public:
 
         struct Token {
             enum Type { Positional, ShortOpt, LongOpt };
@@ -4148,38 +4137,75 @@ namespace Clara {
             std::string data;
         };
 
-        void parseIntoTokens( int argc, char const* const argv[], std::vector<Parser::Token>& tokens ) const {
+        Parser() : mode( None ), from( 0 ), inQuotes( false ){}
+
+        void parseIntoTokens( std::vector<std::string> const& args, std::vector<Token>& tokens ) {
             const std::string doubleDash = "--";
-            for( int i = 1; i < argc && argv[i] != doubleDash; ++i )
-                parseIntoTokens( argv[i] , tokens);
-        }
-        void parseIntoTokens( std::string arg, std::vector<Parser::Token>& tokens ) const {
-            while( !arg.empty() ) {
-                Parser::Token token( Parser::Token::Positional, arg );
-                arg = "";
-                if( token.data[0] == '-' ) {
-                    if( token.data.size() > 1 && token.data[1] == '-' ) {
-                        token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) );
-                    }
-                    else {
-                        token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) );
-                        if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) {
-                            arg = "-" + token.data.substr( 1 );
-                            token.data = token.data.substr( 0, 1 );
-                        }
-                    }
-                }
-                if( token.type != Parser::Token::Positional ) {
-                    std::size_t pos = token.data.find_first_of( separators );
-                    if( pos != std::string::npos ) {
-                        arg = token.data.substr( pos+1 );
-                        token.data = token.data.substr( 0, pos );
-                    }
-                }
-                tokens.push_back( token );
+            for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i )
+                parseIntoTokens( args[i], tokens);
+        }
+
+        void parseIntoTokens( std::string const& arg, std::vector<Token>& tokens ) {
+            for( std::size_t i = 0; i <= arg.size(); ++i ) {
+                char c = arg[i];
+                if( c == '"' )
+                    inQuotes = !inQuotes;
+                mode = handleMode( i, c, arg, tokens );
+            }
+        }
+        Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
+            switch( mode ) {
+                case None: return handleNone( i, c );
+                case MaybeShortOpt: return handleMaybeShortOpt( i, c );
+                case ShortOpt:
+                case LongOpt:
+                case SlashOpt: return handleOpt( i, c, arg, tokens );
+                case Positional: return handlePositional( i, c, arg, tokens );
+                default: throw std::logic_error( "Unknown mode" );
+            }
+        }
+
+        Mode handleNone( std::size_t i, char c ) {
+            if( inQuotes ) {
+                from = i;
+                return Positional;
+            }
+            switch( c ) {
+                case '-': return MaybeShortOpt;
+#ifdef CLARA_PLATFORM_WINDOWS
+                case '/': from = i+1; return SlashOpt;
+#endif
+                default: from = i; return Positional;
+            }
+        }
+        Mode handleMaybeShortOpt( std::size_t i, char c ) {
+            switch( c ) {
+                case '-': from = i+1; return LongOpt;
+                default: from = i; return ShortOpt;
             }
         }
-        std::string separators;
+        Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
+            if( std::string( ":=\0", 3 ).find( c ) == std::string::npos )
+                return mode;
+
+            std::string optName = arg.substr( from, i-from );
+            if( mode == ShortOpt )
+                for( std::size_t j = 0; j < optName.size(); ++j )
+                    tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) );
+            else if( mode == SlashOpt && optName.size() == 1 )
+                tokens.push_back( Token( Token::ShortOpt, optName ) );
+            else
+                tokens.push_back( Token( Token::LongOpt, optName ) );
+            return None;
+        }
+        Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
+            if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos )
+                return mode;
+
+            std::string data = arg.substr( from, i-from );
+            tokens.push_back( Token( Token::Positional, data ) );
+            return None;
+        }
     };
 
     template<typename ConfigT>
@@ -4482,21 +4508,21 @@ namespace Clara {
             return oss.str();
         }
 
-        ConfigT parse( int argc, char const* const argv[] ) const {
+        ConfigT parse( std::vector<std::string> const& args ) const {
             ConfigT config;
-            parseInto( argc, argv, config );
+            parseInto( args, config );
             return config;
         }
 
-        std::vector<Parser::Token> parseInto( int argc, char const* argv[], ConfigT& config ) const {
-            std::string processName = argv[0];
+        std::vector<Parser::Token> parseInto( std::vector<std::string> const& args, ConfigT& config ) const {
+            std::string processName = args[0];
             std::size_t lastSlash = processName.find_last_of( "/\\" );
             if( lastSlash != std::string::npos )
                 processName = processName.substr( lastSlash+1 );
             m_boundProcessName.set( config, processName );
             std::vector<Parser::Token> tokens;
             Parser parser;
-            parser.parseIntoTokens( argc, argv, tokens );
+            parser.parseIntoTokens( args, tokens );
             return populate( tokens, config );
         }
 
@@ -4527,7 +4553,7 @@ namespace Clara {
                                     arg.boundField.set( config, tokens[++i].data );
                             }
                             else {
-                                arg.boundField.setFlag( config );
+                                arg.boundField.set( config, "true" );
                             }
                             break;
                         }
@@ -5235,6 +5261,8 @@ namespace Catch
         bool aborting;
     };
 
+    class MultipleReporters;
+
     struct IStreamingReporter : IShared {
         virtual ~IStreamingReporter();
 
@@ -5262,6 +5290,8 @@ namespace Catch
         virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
 
         virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
+
+        virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; }
     };
 
     struct IReporterFactory : IShared {
@@ -5479,6 +5509,10 @@ namespace TestCaseTracking {
         virtual void addChild( Ptr<ITracker> const& child ) = 0;
         virtual ITracker* findChild( std::string const& name ) = 0;
         virtual void openChild() = 0;
+
+        // Debug/ checking
+        virtual bool isSectionTracker() const = 0;
+        virtual bool isIndexTracker() const = 0;
     };
 
     class TrackerContext {
@@ -5603,6 +5637,10 @@ namespace TestCaseTracking {
                     m_parent->openChild();
             }
         }
+
+        virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; }
+        virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; }
+
         void open() {
             m_runState = Executing;
             moveToThis();
@@ -5666,13 +5704,16 @@ namespace TestCaseTracking {
         {}
         virtual ~SectionTracker();
 
+        virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; }
+
         static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) {
             SectionTracker* section = CATCH_NULL;
 
             ITracker& currentTracker = ctx.currentTracker();
             if( ITracker* childTracker = currentTracker.findChild( name ) ) {
-                section = dynamic_cast<SectionTracker*>( childTracker );
-                assert( section );
+                assert( childTracker );
+                assert( childTracker->isSectionTracker() );
+                section = static_cast<SectionTracker*>( childTracker );
             }
             else {
                 section = new SectionTracker( name, ctx, &currentTracker );
@@ -5697,13 +5738,16 @@ namespace TestCaseTracking {
         {}
         virtual ~IndexTracker();
 
+        virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; }
+
         static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) {
             IndexTracker* tracker = CATCH_NULL;
 
             ITracker& currentTracker = ctx.currentTracker();
             if( ITracker* childTracker = currentTracker.findChild( name ) ) {
-                tracker = dynamic_cast<IndexTracker*>( childTracker );
-                assert( tracker );
+                assert( childTracker );
+                assert( childTracker->isIndexTracker() );
+                tracker = static_cast<IndexTracker*>( childTracker );
             }
             else {
                 tracker = new IndexTracker( name, ctx, &currentTracker, size );
@@ -6306,10 +6350,10 @@ namespace Catch {
             Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
         }
 
-        int applyCommandLine( int argc, char const* argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
+        int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
             try {
                 m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail );
-                m_unusedTokens = m_cli.parseInto( argc, argv, m_configData );
+                m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData );
                 if( m_configData.showHelp )
                     showHelp( m_configData.processName );
                 m_config.reset();
@@ -6333,16 +6377,13 @@ namespace Catch {
             m_config.reset();
         }
 
-        int run( int argc, char const* argv[] ) {
+        int run( int argc, char const* const* const argv ) {
 
             int returnCode = applyCommandLine( argc, argv );
             if( returnCode == 0 )
                 returnCode = run();
             return returnCode;
         }
-        int run( int argc, char* argv[] ) {
-            return run( argc, const_cast<char const**>( argv ) );
-        }
 
         int run() {
             if( m_configData.showHelp )
@@ -6406,13 +6447,31 @@ namespace Catch {
 #include <iostream>
 #include <algorithm>
 
+#ifdef CATCH_CPP14_OR_GREATER
+#include <random>
+#endif
+
 namespace Catch {
 
-    struct LexSort {
-        bool operator() (TestCase i,TestCase j) const { return (i<j);}
-    };
     struct RandomNumberGenerator {
-        int operator()( int n ) const { return std::rand() % n; }
+        typedef int result_type;
+
+        result_type operator()( result_type n ) const { return std::rand() % n; }
+
+#ifdef CATCH_CPP14_OR_GREATER
+        static constexpr result_type min() { return 0; }
+        static constexpr result_type max() { return 1000000; }
+        result_type operator()() const { return std::rand() % max(); }
+#endif
+        template<typename V>
+        static void shuffle( V& vector ) {
+            RandomNumberGenerator rng;
+#ifdef CATCH_CPP14_OR_GREATER
+            std::shuffle( vector.begin(), vector.end(), rng );
+#else
+            std::random_shuffle( vector.begin(), vector.end(), rng );
+#endif
+        }
     };
 
     inline std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
@@ -6421,14 +6480,12 @@ namespace Catch {
 
         switch( config.runOrder() ) {
             case RunTests::InLexicographicalOrder:
-                std::sort( sorted.begin(), sorted.end(), LexSort() );
+                std::sort( sorted.begin(), sorted.end() );
                 break;
             case RunTests::InRandomOrder:
                 {
                     seedRng( config );
-
-                    RandomNumberGenerator rng;
-                    std::random_shuffle( sorted.begin(), sorted.end(), rng );
+                    RandomNumberGenerator::shuffle( sorted );
                 }
                 break;
             case RunTests::InDeclarationOrder:
@@ -6447,13 +6504,15 @@ namespace Catch {
             it != itEnd;
             ++it ) {
             std::pair<std::set<TestCase>::const_iterator, bool> prev = seenFunctions.insert( *it );
-            if( !prev.second ){
-                Catch::cerr()
-                << Colour( Colour::Red )
-                << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n"
-                << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
-                << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl;
-                exit(1);
+            if( !prev.second ) {
+                std::ostringstream ss;
+
+                ss  << Colour( Colour::Red )
+                    << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n"
+                    << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
+                    << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl;
+
+                throw std::runtime_error(ss.str());
             }
         }
     }
@@ -7512,7 +7571,7 @@ namespace Catch {
         return os;
     }
 
-    Version libraryVersion( 1, 4, 0, "", 0 );
+    Version libraryVersion( 1, 5, 6, "", 0 );
 
 }
 
@@ -8491,13 +8550,18 @@ public: // IStreamingReporter
                 ++it )
             (*it)->skipTest( testInfo );
     }
+
+    virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE {
+        return this;
+    }
+
 };
 
 Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ) {
     Ptr<IStreamingReporter> resultingReporter;
 
     if( existingReporter ) {
-        MultipleReporters* multi = dynamic_cast<MultipleReporters*>( existingReporter.get() );
+        MultipleReporters* multi = existingReporter->tryAsMulti();
         if( !multi ) {
             multi = new MultipleReporters;
             resultingReporter = Ptr<IStreamingReporter>( multi );
@@ -8677,7 +8741,7 @@ namespace Catch {
 
         virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {}
 
-        virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
             assert( !m_sectionStack.empty() );
             SectionNode& sectionNode = *m_sectionStack.back();
             sectionNode.assertions.push_back( assertionStats );
diff --git a/test/t/basic/test_location.cpp b/test/t/basic/test_location.cpp
index 2fe2bc7..e6c1b53 100644
--- a/test/t/basic/test_location.cpp
+++ b/test/t/basic/test_location.cpp
@@ -222,7 +222,7 @@ TEST_CASE("Parsing coordinates from strings") {
     C("1.1234567",  11234567);
     C("1.12345670", 11234567);
     C("1.12345674", 11234567);
-    C("1.12345675", 11234568);
+    C("1.123456751", 11234568);
     C("1.12345679", 11234568);
     C("1.12345680", 11234568);
     C("1.12345681", 11234568);
@@ -233,6 +233,37 @@ TEST_CASE("Parsing coordinates from strings") {
     C("179.99999999", 1800000000);
     C("200.123",      2001230000);
 
+    C("1e2",   1000000000);
+    C("1e1",    100000000);
+    C("1e0",     10000000);
+    C("1e-1",     1000000);
+    C("1e-2",      100000);
+    C("1e-3",       10000);
+    C("1e-4",        1000);
+    C("1e-5",         100);
+    C("1e-6",          10);
+    C("1e-7",           1);
+
+    C("1.0e2",   1000000000);
+    C("1.1e1",    110000000);
+    C("0.1e1",     10000000);
+    C("1.2e0",     12000000);
+    C("1.9e-1",     1900000);
+    C("2.0e-2",      200000);
+    C("2.1e-3",       21000);
+    C("9.0e-4",        9000);
+    C("9.1e-5",         910);
+    C("1.0e-6",          10);
+    C("1.0e-7",           1);
+    C("1.4e-7",           1);
+    C("1.5e-7",           2);
+    C("1.9e-7",           2);
+
+    F("e");
+    F(" e");
+    F(" 1.1e2");
+    F("1.1e2 ");
+    F("1.1e2x");
 }
 
 #undef C
diff --git a/test/t/geom/test_geos.cpp b/test/t/geom/test_geos.cpp
index d85a603..f74027c 100644
--- a/test/t/geom/test_geos.cpp
+++ b/test/t/geom/test_geos.cpp
@@ -1,6 +1,7 @@
 #include "catch.hpp"
 
 #include <osmium/geom/geos.hpp>
+#include <osmium/geom/mercator_projection.hpp>
 
 #include "area_helper.hpp"
 #include "wnl_helper.hpp"
@@ -11,16 +12,16 @@ TEST_CASE("GEOS geometry factory - create point") {
     std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
     REQUIRE(3.2 == point->getX());
     REQUIRE(4.2 == point->getY());
-    REQUIRE(-1 == point->getSRID());
+    REQUIRE(4326 == point->getSRID());
 }
 
-TEST_CASE("GEOS geometry factory - create point with non-default srid") {
-    osmium::geom::GEOSFactory<> factory(4326);
+TEST_CASE("GEOS geometry factory - create point in web mercator") {
+    osmium::geom::GEOSFactory<osmium::geom::MercatorProjection> factory;
 
     std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
-    REQUIRE(3.2 == point->getX());
-    REQUIRE(4.2 == point->getY());
-    REQUIRE(4326 == point->getSRID());
+    REQUIRE(Approx(356222.3705384755l) == point->getX());
+    REQUIRE(Approx(467961.143605213l) == point->getY());
+    REQUIRE(3857 == point->getSRID());
 }
 
 TEST_CASE("GEOS geometry factory - create point with externally created GEOS factory") {
diff --git a/test/t/geom/test_wkb.cpp b/test/t/geom/test_wkb.cpp
index 49710cb..12cd5b6 100644
--- a/test/t/geom/test_wkb.cpp
+++ b/test/t/geom/test_wkb.cpp
@@ -1,5 +1,6 @@
 #include "catch.hpp"
 
+#include <osmium/geom/mercator_projection.hpp>
 #include <osmium/geom/wkb.hpp>
 #include "wnl_helper.hpp"
 
@@ -14,13 +15,27 @@ SECTION("point") {
     REQUIRE(std::string{"01010000009A99999999990940CDCCCCCCCCCC1040"} == wkb);
 }
 
-SECTION("point_ewkb") {
+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);
+}
+
+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);
 }
 
+SECTION("point in ewkb in web mercator") {
+    osmium::geom::WKBFactory<osmium::geom::MercatorProjection> 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{"0101000020110F000028706E7BF9BD1541B03E0D93E48F1C41"} == wkb);
+}
+
 SECTION("linestring") {
     osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex);
 
diff --git a/test/t/geom/test_wkt.cpp b/test/t/geom/test_wkt.cpp
index 55ccb4a..3767be3 100644
--- a/test/t/geom/test_wkt.cpp
+++ b/test/t/geom/test_wkt.cpp
@@ -1,5 +1,6 @@
 #include "catch.hpp"
 
+#include <osmium/geom/mercator_projection.hpp>
 #include <osmium/geom/wkt.hpp>
 
 #include "area_helper.hpp"
@@ -14,6 +15,20 @@ SECTION("point") {
     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 in ewkt in web mercator") {
+    osmium::geom::WKTFactory<osmium::geom::MercatorProjection> factory(2, osmium::geom::wkt_type::ewkt);
+
+    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;
 
diff --git a/test/t/io/test_string_table.cpp b/test/t/io/test_string_table.cpp
index ab977e8..1e76245 100644
--- a/test/t/io/test_string_table.cpp
+++ b/test/t/io/test_string_table.cpp
@@ -7,6 +7,8 @@ TEST_CASE("String store") {
 
     SECTION("empty") {
         REQUIRE(ss.begin() == ss.end());
+        REQUIRE(ss.get_chunk_size() == 100);
+        REQUIRE(ss.get_chunk_count() == 1);
     }
 
     SECTION("add zero-length string") {
@@ -17,6 +19,8 @@ TEST_CASE("String store") {
         REQUIRE(s1 == *it);
         REQUIRE(std::string(*it) == "");
         REQUIRE(++it == ss.end());
+
+        REQUIRE(ss.get_chunk_count() == 1);
     }
 
     SECTION("add strings") {
@@ -30,6 +34,9 @@ TEST_CASE("String store") {
         REQUIRE(s1 == *it++);
         REQUIRE(s2 == *it++);
         REQUIRE(it == ss.end());
+
+        ss.clear();
+        REQUIRE(ss.begin() == ss.end());
     }
 
     SECTION("add zero-length string and longer strings") {
@@ -45,9 +52,9 @@ TEST_CASE("String store") {
     }
 
     SECTION("add many strings") {
-        for (const char* teststring : {"a", "abc", "abcd", "abcde"}) {
+        for (const char* teststring : {"", "a", "abc", "abcd", "abcde"}) {
             int i = 0;
-            for (; i < 100; ++i) {
+            for (; i < 200; ++i) {
                 ss.add(teststring);
             }
 
@@ -57,7 +64,9 @@ TEST_CASE("String store") {
             }
 
             REQUIRE(i == 0);
+            REQUIRE(ss.get_chunk_count() > 1);
             ss.clear();
+            REQUIRE(ss.get_chunk_count() == 1);
         }
     }
 
@@ -90,5 +99,32 @@ TEST_CASE("String table") {
         REQUIRE(st.size() == 1);
     }
 
+    SECTION("add empty string") {
+        REQUIRE(st.add("") == 1);
+        REQUIRE(st.size() == 2);
+        REQUIRE(st.add("") == 1);
+        REQUIRE(st.size() == 2);
+    }
+
+}
+
+TEST_CASE("lots of strings in string table so chunk overflows") {
+    osmium::io::detail::StringTable st{100};
+    REQUIRE(st.size() == 1);
+
+    const int n = 1000;
+    for (int i = 0; i < n; ++i) {
+        auto s = std::to_string(i);
+        st.add(s.c_str());
+    }
+
+    REQUIRE(st.size() == n + 1);
+
+    auto it = st.begin();
+    REQUIRE(std::string{} == *it++);
+    for (int i = 0; i < n; ++i) {
+        REQUIRE(atoi(*it++) == i);
+    }
+    REQUIRE(it == st.end());
 }
 
diff --git a/test/t/util/test_delta.cpp b/test/t/util/test_delta.cpp
index 667c9b4..27bd8be 100644
--- a/test/t/util/test_delta.cpp
+++ b/test/t/util/test_delta.cpp
@@ -70,25 +70,3 @@ TEST_CASE("delta encode and decode") {
 
 }
 
-TEST_CASE("delta encode iterator") {
-    std::vector<int> data = { 4, 5, 13, 22, 12 };
-
-    auto l = [](std::vector<int>::const_iterator it) -> int {
-        return *it;
-    };
-
-    typedef osmium::util::DeltaEncodeIterator<std::vector<int>::const_iterator, decltype(l), int> it_type;
-    it_type it(data.begin(), data.end(), l);
-    it_type end(data.end(), data.end(), l);
-
-    REQUIRE(*it == 4);
-    ++it;
-    REQUIRE(*it++ == 1);
-    REQUIRE(*it == 8);
-    ++it;
-    REQUIRE(*it++ == 9);
-    REQUIRE(*it == -10);
-    ++it;
-    REQUIRE(it == end);
-}
-

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