[osmium-tool] 01/05: New upstream version 1.7.0

Bas Couwenberg sebastic at debian.org
Tue Aug 15 17:44:33 UTC 2017


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

sebastic pushed a commit to branch master
in repository osmium-tool.

commit abdb4860724d066d82ac91c0c7ef77d7836bbdfe
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Aug 15 19:29:13 2017 +0200

    New upstream version 1.7.0
---
 CHANGELOG.md                                       |   51 +-
 CMakeLists.txt                                     |    9 +-
 CONTRIBUTING.md                                    |    6 +
 README.md                                          |    4 +-
 cmake/FindOsmium.cmake                             |    4 +
 export-example-config/default-config.json          |   16 +
 export-example-config/example-config.json          |   16 +
 extract-example-config/karlsruhe.osm.bz2           |  Bin 16630 -> 16666 bytes
 man/common-options.md                              |    2 +-
 man/osmium-add-locations-to-ways.md                |   31 +-
 man/osmium-changeset-filter.md                     |    3 +
 man/osmium-check-refs.md                           |   12 +-
 man/osmium-diff.md                                 |    2 +-
 man/osmium-export.md                               |  282 +++++
 man/osmium-fileinfo.md                             |    5 +-
 man/osmium-getid.md                                |    9 +-
 man/osmium-index-types.md                          |   62 +
 man/osmium-merge.md                                |    8 +-
 man/osmium-renumber.md                             |    9 +-
 man/osmium-sort.md                                 |   13 +-
 man/osmium.md                                      |    6 +-
 src/CMakeLists.txt                                 |    2 +-
 src/command_add_locations_to_ways.cpp              |   11 +-
 src/command_add_locations_to_ways.hpp              |    6 +-
 src/command_apply_changes.cpp                      |    3 +-
 src/command_cat.hpp                                |    4 -
 src/command_changeset_filter.cpp                   |   10 +-
 src/command_changeset_filter.hpp                   |    1 +
 src/command_check_refs.cpp                         |   52 +-
 src/command_export.cpp                             |  427 +++++++
 src/command_export.hpp                             |   87 ++
 src/command_extract.cpp                            |   75 +-
 src/command_extract.hpp                            |    4 +-
 src/command_fileinfo.cpp                           |    5 +-
 src/command_getid.cpp                              |    2 +
 src/command_getid.hpp                              |    3 +-
 src/command_help.cpp                               |   12 +-
 src/command_renumber.cpp                           |   39 +-
 src/command_renumber.hpp                           |    7 +-
 src/command_show.cpp                               |    1 +
 src/command_show.hpp                               |    2 -
 src/command_tags_filter.cpp                        |   70 +-
 src/command_time_filter.cpp                        |    1 +
 src/commands.cpp                                   |    5 +
 src/{command_show.hpp => export/export_format.hpp} |   53 +-
 src/export/export_format_json.cpp                  |  219 ++++
 src/export/export_format_json.hpp                  |   87 ++
 src/export/export_format_text.cpp                  |  200 ++++
 src/export/export_format_text.hpp                  |   71 ++
 src/export/export_handler.cpp                      |  127 ++
 src/export/export_handler.hpp                      |   82 ++
 .../extract_bbox.cpp => export/options.hpp}        |   42 +-
 src/extract/extract_bbox.cpp                       |    6 +-
 src/extract/extract_polygon.cpp                    |    2 +-
 src/extract/poly_file_parser.hpp                   |    2 +-
 src/extract/strategy_complete_ways.cpp             |    2 +-
 .../strategy_complete_ways_with_history.cpp        |    2 +-
 src/extract/strategy_simple.cpp                    |    2 +-
 src/extract/strategy_smart.cpp                     |    2 +-
 src/io.cpp                                         |    2 -
 src/main.cpp                                       |    5 +
 src/util.cpp                                       |   94 ++
 src/util.hpp                                       |   20 +-
 test/CMakeLists.txt                                |    3 +-
 test/export/CMakeLists.txt                         |   26 +
 .../input-incomplete-relation.osm}                 |   22 +-
 .../input-missing-node.osm}                        |   24 +-
 .../input-sorted.osm => export/input.osm}          |   23 +-
 test/export/output-incomplete-relation.geojson     |    5 +
 test/export/output-missing-node.geojson            |    5 +
 test/export/output.geojson                         |    6 +
 test/export/output.geojsonseq                      |    4 +
 test/getid/CMakeLists.txt                          |    2 +-
 test/getid/idfile                                  |    6 +-
 .../input-sorted.osm => getid/output-file.osm}     |   17 +-
 test/include/catch.hpp                             | 1222 +++++++++++++-------
 test/renumber/input-sorted.osm                     |    4 +-
 test/sort/CMakeLists.txt                           |    8 +-
 .../input-sorted.osm => sort/input-neg.osm}        |   32 +-
 .../input-sorted.osm => sort/output-neg.osm}       |   22 +-
 test/tags-filter/test_unit.cpp                     |   57 -
 test/util/test_unit.cpp                            |   85 ++
 zsh_completion/_osmium                             |   53 +-
 83 files changed, 3189 insertions(+), 836 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fdb8ffc..f3d5840 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,54 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 
+## [1.7.0] - 2017-08-15
+
+### Added
+
+- New `export` command for exporting OSM data into GeoJSON format. The OSM
+  data model with its nodes, ways, and relations is very different from the
+  data model usually used for geodata with features having point, linestring,
+  or polygon geometries. The export command transforms OSM data into a more
+  usual GIS data model. Nodes will be translated into points and ways into
+  linestrings or polygons (if they are closed ways). Multipolygon and boundary
+  relations will be translated into multipolygons. This transformation is not
+  loss-less, especially information in non-multipolygon, non-boundary relations
+  is lost. All tags are preserved in this process.  Note that most GIS formats
+  (such as Shapefiles, etc.) do not support arbitrary tags. Transformation
+  into other GIS formats will need extra steps mapping tags to a limited list
+  of attributes. This is outside the scope of this command.
+- New `--bbox/-B` option to `changeset-filter` command. Only changesets with
+  bounding boxes overlapping this bounding box are copied to the output.
+- Support for the new `flex_mem` index type for node location indexes. It
+  is used by default in the `add_locations_to_ways` and `export` commands.
+  The new man page `osmium-index-types` documents this and other available
+  indexes.
+
+### Changed
+
+- The order of objects in an OSM file expected by some commands as well as
+  the order created by the `sort` command has changed when negative IDs are
+  involved. (Negative IDs are sometimes used for objects that have not yet
+  been uploaded to the OSM server.) The negative IDs are ordered now before
+  the positive ones, both in order of their absolute value. This is the same
+  ordering as JOSM uses.
+- The commands `check-refs`, `fileinfo`, and `renumber` now also work with
+  negative object IDs.
+- Allow leading spaces in ID files for `getid` command.
+- Various error messages and man pages have been clarified.
+- Updated minimum libosmium version required to 2.13.0.
+- Update version of Catch unit test framework to 1.9.7.
+
+### Fixed
+
+- Libosmium fix: Changesets with more than 2^16 characters in comments now
+  work.
+- Libosmium fix: Changeset bounding boxes are now always output to OSM files
+  (any format) if at least one of the corners is defined. This is needed to
+  handle broken data from the main OSM database which contains such cases.
+  This now also works when reading OPL files.
+
+
 ## [1.6.1] - 2017-04-10
 
 ### Changed
@@ -266,7 +314,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - Minor updates to documentation and build system
 
 
-[unreleased]: https://github.com/osmcode/osmium-tool/compare/v1.6.1...HEAD
+[unreleased]: https://github.com/osmcode/osmium-tool/compare/v1.7.0...HEAD
+[1.7.0]: https://github.com/osmcode/osmium-tool/compare/v1.6.1...v1.7.0
 [1.6.1]: https://github.com/osmcode/osmium-tool/compare/v1.6.0...v1.6.1
 [1.6.0]: https://github.com/osmcode/osmium-tool/compare/v1.5.1...v1.6.0
 [1.5.1]: https://github.com/osmcode/osmium-tool/compare/v1.5.0...v1.5.1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff4187a..da425f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,8 +25,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev"
 project(osmium)
 
 set(OSMIUM_VERSION_MAJOR 1)
-set(OSMIUM_VERSION_MINOR 6)
-set(OSMIUM_VERSION_PATCH 1)
+set(OSMIUM_VERSION_MINOR 7)
+set(OSMIUM_VERSION_PATCH 0)
 
 set(OSMIUM_VERSION ${OSMIUM_VERSION_MAJOR}.${OSMIUM_VERSION_MINOR}.${OSMIUM_VERSION_PATCH})
 
@@ -44,7 +44,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 find_package(Boost 1.55.0 REQUIRED COMPONENTS program_options)
 include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
 
-find_package(Osmium 2.12.1 REQUIRED COMPONENTS io)
+find_package(Osmium 2.13.0 REQUIRED COMPONENTS io)
 include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS})
 
 
@@ -104,6 +104,7 @@ function(add_man_page _section _name)
     file(READ ${CMAKE_SOURCE_DIR}/man/progress-options.md MAN_PROGRESS_OPTIONS)
     file(READ ${CMAKE_SOURCE_DIR}/man/input-options.md MAN_INPUT_OPTIONS)
     file(READ ${CMAKE_SOURCE_DIR}/man/output-options.md MAN_OUTPUT_OPTIONS)
+    file(READ ${CMAKE_SOURCE_DIR}/export-example-config/default-config.json EXPORT_DEFAULT_CONFIG)
     configure_file(${_source_file} ${_dest_file} @ONLY)
     string(TOUPPER ${_name} _name_upcase)
     add_custom_command(OUTPUT ${_output_file}
@@ -142,6 +143,7 @@ if(PANDOC)
     add_man_page(1 osmium-check-refs)
     add_man_page(1 osmium-derive-changes)
     add_man_page(1 osmium-diff)
+    add_man_page(1 osmium-export)
     add_man_page(1 osmium-extract)
     add_man_page(1 osmium-fileinfo)
     add_man_page(1 osmium-getid)
@@ -153,6 +155,7 @@ if(PANDOC)
     add_man_page(1 osmium-tags-filter)
     add_man_page(1 osmium-time-filter)
     add_man_page(5 osmium-file-formats)
+    add_man_page(5 osmium-index-types)
 
     install(DIRECTORY ${CMAKE_BINARY_DIR}/man DESTINATION share)
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 034b2c6..b9e28af 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,3 +6,9 @@ Some rules for contributing to this project:
   keeps issues small and manageable and makes it much easier to follow through
   and make sure each problem is taken care of.
 
+If you are reporting a problem:
+
+* Describe exactly what you did, what you expected to happen and what did happen
+  instead. Include relevant information about the platform, OS version etc. you
+  are using. Include shell commands you typed in, log files, errors messages etc.
+
diff --git a/README.md b/README.md
index 166357b..3fd7e49 100644
--- a/README.md
+++ b/README.md
@@ -16,11 +16,11 @@ later are known to work. It also works on modern Visual Studio C++ compilers.
 
 You also need the following libraries:
 
-    Libosmium (>= 2.12.1)
+    Libosmium (>= 2.13.0)
         http://osmcode.org/libosmium
         Debian/Ubuntu: libosmium2-dev
 
-    Protozero (>= 1.4.5)
+    Protozero (>= 1.5.1)
         This is included in the libosmium repository and might or might not
         have been installed with it. See the libosmium README.
         https://github.com/mapbox/protozero
diff --git a/cmake/FindOsmium.cmake b/cmake/FindOsmium.cmake
index 0877ef2..adc457f 100644
--- a/cmake/FindOsmium.cmake
+++ b/cmake/FindOsmium.cmake
@@ -333,6 +333,10 @@ if(MSVC)
     # old compilers anyway.
     add_definitions(-wd4351)
 
+    # Disable warning C4503: "decorated name length exceeded, name was truncated"
+    # there are more than 150 of generated names in libosmium longer than 4096 symbols supported in MSVC
+    add_definitions(-wd4503)
+
     add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN -D_CRT_SECURE_NO_WARNINGS)
 endif()
 
diff --git a/export-example-config/default-config.json b/export-example-config/default-config.json
new file mode 100644
index 0000000..d544b48
--- /dev/null
+++ b/export-example-config/default-config.json
@@ -0,0 +1,16 @@
+    {
+        "attributes": {
+            "type":      false,
+            "id":        false,
+            "version":   false,
+            "changeset": false,
+            "timestamp": false,
+            "uid":       false,
+            "user":      false,
+            "way_nodes": false
+        },
+        "linear_tags":  [],
+        "area_tags":    [],
+        "exclude_tags": [],
+        "include_tags": []
+    }
diff --git a/export-example-config/example-config.json b/export-example-config/example-config.json
new file mode 100644
index 0000000..7f19247
--- /dev/null
+++ b/export-example-config/example-config.json
@@ -0,0 +1,16 @@
+{
+    "attributes": {
+        "type":      true,
+        "id":        "@id",
+        "version":   false,
+        "changeset": false,
+        "timestamp": false,
+        "uid":       false,
+        "user":      false,
+        "way_nodes": false
+    },
+    "linear_tags":  ["highway", "barrier", "natural=coastline"],
+    "area_tags":    ["aeroway", "amenity", "building", "landuse", "leisure", "man_made", "natural!=coastline"],
+    "exclude_tags": ["created_by", "source", "source:*"],
+    "include_tags": []
+}
diff --git a/extract-example-config/karlsruhe.osm.bz2 b/extract-example-config/karlsruhe.osm.bz2
index f63440d..4be60a3 100644
Binary files a/extract-example-config/karlsruhe.osm.bz2 and b/extract-example-config/karlsruhe.osm.bz2 differ
diff --git a/man/common-options.md b/man/common-options.md
index eac2bf6..06eaf4d 100644
--- a/man/common-options.md
+++ b/man/common-options.md
@@ -6,4 +6,4 @@
 
 -v, --verbose
 :   Set verbose mode. The program will output information about what it is
-    doing to *stderr*.
+    doing to STDERR.
diff --git a/man/osmium-add-locations-to-ways.md b/man/osmium-add-locations-to-ways.md
index f3ef23d..576aedc 100644
--- a/man/osmium-add-locations-to-ways.md
+++ b/man/osmium-add-locations-to-ways.md
@@ -28,12 +28,11 @@ smaller than the input file (unless the **--keep-untagged-nodes**,
 Note that the OSM files generated by this command use a non-standard format
 extension.
 
-The node locations have to be kept in memory while doings this. Use the
-**--index-type**, **-i** option to set the index type used. Default is
-`sparse_mmap_array` on Linux and `sparse_mem_array` on OSX/Windows. This is
-the right index type for small to medium sized extracts. For large
-(continent-sized) extracts or the full planet use `dense_mmap_array` on Linux
-or `dense_mem_array` on OSX/Windows.
+The **osmium add-locations-to-ways** command has to keep an index of the node
+locations in memory or in a temporary file on disk while doing its work. There
+are several different ways it can do that which have different advantages and
+disadvantages. The default is good enough for most cases, but see the
+**osmium-index-types**(5) man page for details.
 
 If the **--keep-untagged-nodes**, **-n** option is used, files created by this
 command can be updated with the **apply-changes** command using the
@@ -45,11 +44,12 @@ This program will not work on full history files.
 # OPTIONS
 
 -i, --index-type=TYPE
-:   Set the index type.
+:   Set the index type. For details see the **osmium-index-types**(5) man
+    page.
 
 -I, --show-index-types
-:   Shows a list of available index types. It depends on your operating system
-    which index types are available.
+:   Shows a list of available index types. For details see the
+    **osmium-index-types**(5) man page.
 
 -n, --keep-untagged-nodes
 :   Keep the untagged nodes in the output file.
@@ -80,14 +80,9 @@ This program will not work on full history files.
 
 # MEMORY USAGE
 
-**osmium add-locations-to-ways** needs to keep all node locations in memory.
-It depends on the index type used how much memory is needed:
-
-* For `sparse` types 16 bytes per node in the input file are used.
-* For `dense` types 8 bytes times the largest node ID in the input file
-  are used.
-
-The `*_mem_*` types use potentially up to twice this amount.
+**osmium add-locations-to-ways** will usually keep all node locations in
+memory. For larger data files, this can need several tens of GBytes of memory.
+See the **osmium-index-types**(5) man page for details.
 
 
 # EXAMPLES
@@ -103,6 +98,6 @@ Add node locations to a planet file (without untagged nodes):
 
 # SEE ALSO
 
-* **osmium**(1), **osmium-file-formats**(5)
+* **osmium**(1), **osmium-file-formats**(5), **osmium-index-types**(5)
 * [Osmium website](http://osmcode.org/osmium-tool/)
 
diff --git a/man/osmium-changeset-filter.md b/man/osmium-changeset-filter.md
index e270408..a4dc226 100644
--- a/man/osmium-changeset-filter.md
+++ b/man/osmium-changeset-filter.md
@@ -23,6 +23,9 @@ criteria are given through command line options.
 -b, --before=TIMESTAMP
 :   Only copy changesets created before the given time.
 
+-B, --bbox=LEFT,BOTTOM,RIGHT,TOP
+:   Only copy changesets with a bounding box overlapping the specified box.
+
 -c, --with-changes
 :   Only copy changesets with changes.
 
diff --git a/man/osmium-check-refs.md b/man/osmium-check-refs.md
index 1671df2..5fdab05 100644
--- a/man/osmium-check-refs.md
+++ b/man/osmium-check-refs.md
@@ -25,6 +25,8 @@ checked.
 
 This command expects the input file to be ordered in the usual way: First
 nodes in order of ID, then ways in order of ID, then relations in order of ID.
+Negative IDs are allowed, they must be ordered before the positive IDs. See
+the **osmium-sort**(1) man page for details of the ordering.
 
 This command will only work for OSM data files, not OSM history files or
 change files.
@@ -33,7 +35,7 @@ change files.
 # OPTIONS
 
 -i, --show-ids
-:   Print all missing IDs to STDOUT. If you don't give this option, only a
+:   Print all missing IDs to STDOUT. If you don't specify this option, only a
     summary is shown.
 
 -r, --check-relations
@@ -49,9 +51,9 @@ change files.
 **osmium check-refs** will do the check in one pass through the input data. It
 needs enough main memory to store all temporary data.
 
-Largest memory need will be about 1 bit for each node ID, for a full planet
-that's roughly 500 MB these days (Summer 2015). With the **-r**,
-**--check-relations** option memory use will be a bit bigger.
+Largest memory need will be about 1 bit for each node ID, that's roughly 540 MB
+these days (Summer 2017). With the **-r**, **--check-relations** option memory
+use will be a bit bigger.
 
 
 # DIAGNOSTICS
@@ -71,7 +73,7 @@ that's roughly 500 MB these days (Summer 2015). With the **-r**,
 
 # SEE ALSO
 
-* **osmium**(1), **osmium-file-formats**(5)
+* **osmium**(1), **osmium-file-formats**(5), **osmium-sort**(1)
 * [Osmium website](http://osmcode.org/osmium-tool/)
 
 
diff --git a/man/osmium-diff.md b/man/osmium-diff.md
index 301fdd7..b5557bc 100644
--- a/man/osmium-diff.md
+++ b/man/osmium-diff.md
@@ -75,7 +75,7 @@ None of the output formats print the headers of the input files.
 
 -s, --summary
 :   Print count of objects that are only in the left or right files, or the
-    same in both or different in both to *stderr*.
+    same in both or different in both to STDERR.
 
 -t, --object-type=TYPE
 :   Read only objects of given type (*node*, *way*, *relation*).
diff --git a/man/osmium-export.md b/man/osmium-export.md
new file mode 100644
index 0000000..4039002
--- /dev/null
+++ b/man/osmium-export.md
@@ -0,0 +1,282 @@
+
+# NAME
+
+osmium-export - export OSM data
+
+
+# SYNOPSIS
+
+**osmium export** \[*OPTIONS*\] *OSM-FILE*
+
+
+# DESCRIPTION
+
+The OSM data model with its nodes, ways, and relations is very different from
+the data model usually used for geodata with features having point, linestring,
+or polygon geometries (or their cousins, the multipoint, multilinestring, or
+multipolygon geometries).
+
+The **export** command transforms OSM data into a more usual GIS data model.
+Nodes will be translated into points and ways into linestrings or polygons (if
+they are closed ways). Multipolygon and boundary relations will be translated
+into multipolygons. This transformation is not loss-less, especially
+information in non-multipolygon, non-boundary relations is lost.
+
+All tags are preserved in this process. Note that most GIS formats (such as
+Shapefiles, etc.) do not support arbitrary tags. Transformation into other GIS
+formats will need extra steps mapping tags to a limited list of attributes.
+This is outside the scope of this command.
+
+The **osmium export** command has to keep an index of the node locations in
+memory or in a temporary file on disk while doing its work. There are several
+different ways it can do that which have different advantages and
+disadvantages. The default is good enough for most cases, but see the
+**osmium-index-types**(5) man page for details.
+
+This program will not work on full history files.
+
+
+# OPTIONS
+
+-c, --config=FILE
+:   Read configuration from specified file.
+
+-e, --show-errors
+:   Output any geometry errors on STDERR. This includes ways with a single
+    node or areas that can't be assembled from multipolygon relations. This
+    output is not suitable for automated use, there are other tools that can
+    create very detailed errors reports that are better for that (see
+    http://osmcode.org/osm-area-tools/).
+
+-E, --stop-on-error
+:   Usually geometry errors (due to missing node locations or broken polygons)
+    are ignored and the features are omitted from the output. If this option
+    is set, any error will immediately stop the program.
+
+-i, --index-type=TYPE
+:   Set the index type. For details see the **osmium-index-types**(5) man
+    page.
+
+-I, --show-index-types
+:   Shows a list of available index types. For details see the
+    **osmium-index-types**(5) man page.
+
+-n, --keep-untagged
+:   If this is set features without any tags will be in the exported data.
+    By default these features will be omitted from the output. Tags are the
+    OSM tags, not attributes (like id, version, uid, ...) without the tags
+    removed by the **exclude_tags** or **include_tags** settings.
+
+-r, --omit-rs
+:   Do not print the RS (0x1e, record separator) character when using the
+    GeoJSON Text Sequence Format. Ignored for other formats.
+
+-u, --add-unique-id=TYPE
+:   Add a unique ID to each feature. TYPE can be either *counter* in which
+    case the first feature will get ID 1, the next ID 2 and so on. The type
+    of object does not matter in this case. Or the TYPE is *type_id* in which
+    case the ID is a string, the first character is the type of object ('n'
+    for nodes, 'w' for linestrings created from ways, and 'a' for areas
+    created from ways and/or relations, after that there is a unique ID based
+    on the original OSM object ID(s).
+
+ at MAN_COMMON_OPTIONS@
+ at MAN_INPUT_OPTIONS@
+
+# OUTPUT OPTIONS
+
+-f, --output-format=FORMAT
+:   The format of the output file. Can be used to set the output file format
+    if it can't be autodetected from the output file name. See the OUTPUT
+    FORMATS section for a list of formats.
+
+--fsync
+:   Call fsync after writing the output file to force flushing buffers to disk.
+
+-o, --output=FILE
+:   Name of the output file. Default is '-' (STDOUT).
+
+-O, --overwrite
+:   Allow an existing output file to be overwritten. Normally **osmium** will
+    refuse to write over an existing file.
+
+
+# CONFIG FILE
+
+The config file is in JSON format. The top-level is an object which contains
+the following optional names:
+
+* `attributes`: An object specifying which attributes of OSM objects to export.
+   See the ATTRIBUTES section.
+* `linear_tags`: An array of expressions specifying tags that should be treated
+   as linear. See the FILTER EXPRESSION and AREA HANDLING sections.
+* `area_tags`: An array of expressions specifying tags that should be treated
+   as area tags. See the FILTER EXPRESSION and AREA HANDLING sections.
+* `exclude_tags`: A list of tag expressions. Tags matching these expressions
+   are excluded from the output. See the FILTER EXPRESSION section.
+* `include_tags`: A list of tag expressions. Tags matching these expressions
+   are included in the output. See the FILTER EXPRESSION section.
+
+The `exclude_tags` and `include_tags` options are mutually exclusive. If you
+want to just exclude some tags but leave most tags untouched, use the
+`exclude_tags` setting. If you only want a defined list of tags, use
+`include_tags`.
+
+When no config file is specified, the following settings are used:
+
+ at EXPORT_DEFAULT_CONFIG@
+
+
+# FILTER EXPRESSIONS
+
+A filter expression specifies a tag or tags that should be matched in the data.
+
+Some examples:
+
+amenity
+:   Matches all objects with the key "amenity".
+
+highway=primary
+:   Matches all objects with the key "highway" and value "primary".
+
+highway!=primary
+:   Matches all objects with the key "highway" and a value other than "primary".
+
+type=multipolygon,boundary
+:   Matches all objects with key "type" and value "multipolygon" or "boundary".
+
+name,name:de=Kastanienallee,Kastanienstrasse
+:   Matches any object with a "name" or "name:de" tag with the value
+    "Kastanienallee" or "Kastanienstrasse".
+
+addr:\*
+:   Matches all objects with any key starting with "addr:"
+
+name=\*Paris\*
+:   Matches all objects with a name that contains the word "Paris".
+
+If there is no equal sign ("=") in the expression only keys are matched and
+values can by anything. If there is an equal sign ("=") in the expression, the
+key is to the left and the value to the right. An exclamation sign ("!") before
+the equal sign means: A tag with that key, but not the value(s) to the right of
+the equal sign. A leading or trailing asterisk ("\*") can be used for substring
+or prefix matching, respectively. Commas (",") can be used to separate several
+keys or values.
+
+All filter expressions are case-sensitive. There is no way to escape the
+special characters such as "=", "\*" and ",". You can not mix
+comma-expressions and "\*"-expressions.
+
+
+# ATTRIBUTES
+
+All OSM objects (nodes, ways, and relations) have *attributes*, areas inherit
+their attributes from the ways and/or relations they were created from. The
+attributes known to `osmium export` are:
+
+* `type` ('node', 'way', or 'relation')
+* `id` (64 bit object ID)
+* `version` (version number)
+* `changeset` (changeset ID)
+* `timestamp` (time of object creation in seconds since Jan 1 1970)
+* `uid` (user ID)
+* `user` (user name)
+* `way_nodes` (ways only, array with node IDs)
+
+For areas, the type will be `way` or `relation` if the area was created
+from a closed way or a multipolygon or boundary relation, respectively. The
+`id` for areas is the id of the closed way or the multipolygon or boundary
+relation.
+
+By default the attributes will not be in the export, because they are not
+necessary for most uses of OSM data. If you are interested in some (or all)
+attributes, add an `attributes` object to the config file. Add a member for
+each attribute you are interested in, the value can be either `false` (do not
+output this attribute), `true` (output this attribute with the attribute name
+prefixed by the `@` sign) or any string, in which case the string will be used
+as the attribute name.
+
+Note that the `id` is not necessarily unique. Even the combination `type` and
+`id` is  not unique, because a way may end up as LineString and as Polygon
+on the file. See the `--add-unique-id` option for a unique ID.
+
+
+# AREA HANDLING
+
+Multipolygon relations will be assembled into multipolygon geometries forming
+areas. Some closed ways will also form areas. Here are the more detailed rules:
+
+* Non-closed ways (last node not the same as the first node) are always
+  linestrings, not areas.
+* Relations tagged `type=multipolygon` or `type=boundary` are always assembled
+  into areas. If they are not valid, they are omitted from the output (unless
+  --stop-on-error/-E is specified). (An error message will be produced if the
+  --show-errors/-e option is specified).
+* For closed ways the tags are checked. If they have an `area` tag other than
+  `area=no`, they are areas and a polygon is created. If they have an `area`
+  tag other than `area=yes`, they are linestrings. So closed ways can be both,
+  an area and a linestring!
+* The configuration options `area_tags` and `linear_tags` can be used to
+  augment the area check. If any of the tags on a closed way matches any of
+  the expressions in `area_tags`, a polygon is created. If any of the tags on
+  a closed way matches any of the expressions in `linear_tags`, a linestring
+  is created. Again: If both match, an area and a linestring is created.
+
+
+# OUTPUT FORMATS
+
+The following output formats are supported:
+
+* `geojson` (alias: `json`): GeoJSON (RFC7946). The output file will contain a
+  single `FeatureCollection` object. This is the default format.
+* `geojsonseq` (alias: `jsonseq`): GeoJSON Text Sequence (RFC8142). Each line
+  (beginning with a RS (0x1e, record separator) and ending in a linefeed
+  character) contains one GeoJSON object. Used for streaming GeoJSON.
+* `text` (alias: `txt`): A simple text format with the geometry in WKT format
+  followed by the comma-delimited tags. This is mainly intended for debugging
+  at the moment. THE FORMAT MIGHT CHANGE WITHOUT NOTICE!
+
+
+# DIAGNOSTICS
+
+**osmium export** exits with exit code
+
+0
+  ~ if everything went alright,
+
+1
+  ~ if there was an error processing the data, or
+
+2
+  ~ if there was a problem with the command line arguments.
+
+
+# MEMORY USAGE
+
+**osmium export** will usually keep all node locations and all objects needed
+for assembling the areas in memory. For larger data files, this can need
+several tens of GBytes of memory. See the **osmium-index-types**(5) man page
+for details.
+
+
+# EXAMPLES
+
+Export into GeoJSON format:
+
+    osmium export data.osm.pbf -o data.geojson
+
+Use a config file and export into GeoJSON Text Sequence format:
+
+    osmium export data.osm.pbf -o data.geojsonseq -c export-config.json
+
+
+# SEE ALSO
+
+* **osmium**(1), **osmium-file-formats**(5), **osmium-index-types**(5),
+  **osmium-add-node-locations-to-ways**(1)
+* [Osmium website](http://osmcode.org/osmium-tool/)
+* [GeoJSON](http://geojson.org/)
+* [RFC7946](https://tools.ietf.org/html/rfc7946)
+* [RFC8142](https://tools.ietf.org/html/rfc8142)
+* [Line delimited JSON](https://en.wikipedia.org/wiki/JSON_Streaming#Line_delimited_JSON)
+
diff --git a/man/osmium-fileinfo.md b/man/osmium-fileinfo.md
index 8f9e559..08b6f12 100644
--- a/man/osmium-fileinfo.md
+++ b/man/osmium-fileinfo.md
@@ -45,7 +45,8 @@ Data
     ways, and relations found in the file, whether the objects in the
     file were ordered by type (nodes, then ways, then relations) and
     id, and whether there were multiple versions of the same object in
-    the file (history files and change files can have that).
+    the file (history files and change files can have that). See the
+    **osmium-sort**(1) man page for details of the expected ordering.
 
 
 # OPTIONS
@@ -125,6 +126,6 @@ main memory.
 
 # SEE ALSO
 
-* **osmium**(1), **osmium-file-formats**(5)
+* **osmium**(1), **osmium-file-formats**(5), **osmium-sort**(1)
 * [Osmium website](http://osmcode.org/osmium-tool/)
 
diff --git a/man/osmium-getid.md b/man/osmium-getid.md
index 97208b9..95118d8 100644
--- a/man/osmium-getid.md
+++ b/man/osmium-getid.md
@@ -43,9 +43,10 @@ single argument separated by spaces, tabs, commas (,), semicolons (;), forward
 slashes (/) or pipe characters (|).
 
 In an ID file (option **-i**/**--id-file**) each line must start with an ID in
-the format described above. Lines can optionally contain a space character or a
-hash sign ('#') after the ID. Any characters after that are ignored. (This also
-allows files in OPL format to be read.) Empty lines are ignored.
+the format described above. Leading space characters in the line are ignored.
+Lines can optionally contain a space character or a hash sign ('#') after the
+ID. Any characters after that are ignored. (This also allows files in OPL
+format to be read.) Empty lines are ignored.
 
 Note that all objects will be taken from the *OSM-FILE*, the *ID-OSM-FILE* is
 only used to detect which objects to get. This might matter if there are
@@ -55,7 +56,7 @@ The *OSM-FILE* can not be a history file unless the **-H**, **--with-history**
 option is used. Then all versions of the objects will be copied to the output.
 
 If referenced objects are missing from the input file, the type and IDs
-of those objects is written out to *stderr* at the end of the program unless
+of those objects is written out to STDERR at the end of the program unless
 the **-H**, **--with-history** option was given.
 
 This command will not work with negative IDs.
diff --git a/man/osmium-index-types.md b/man/osmium-index-types.md
new file mode 100644
index 0000000..4043151
--- /dev/null
+++ b/man/osmium-index-types.md
@@ -0,0 +1,62 @@
+
+# NAME
+
+osmium-index-types - Index types used to store node locations
+
+# DESCRIPTION
+
+The **osmium add-locations-to-ways** and **osmium export** commands have to
+keep an index of the node locations in memory or in a temporary file on disk
+while doing their work. There are several different ways this can be done which
+have different advantages and disadvantages.
+
+Use the **--show-index-types**, **-I** option on these commands to show all
+available index types. It depends on your operating system which index types
+are available.
+
+Use the **--index-type**, **-i** option on these commands to set the index type
+to be used.
+
+The default index type is `flex_mem` which will keep all data in memory and
+works for small extracts as well as the whole planet file. It is the right
+choice for almost all use cases if you have enough memory to keep the whole
+index in memory.
+
+For the **osmium export** command, the special type `none` is used when reading
+from files with the node locations on the ways. (See
+**osmium-add-node-locations-to-ways**(1) for how to get a file like this.)
+
+You can use one of the file-based indexes for the node location store to
+minimize memory use, but performance will suffer. In this case use
+`sparse_file_array` if you have a small or medium sized extract and
+`dense_file_array` if you are working with a full planet or a really large
+extract.
+
+
+# MEMORY USE
+
+It depends on the index type used how much memory is needed:
+
+* For `sparse_*_array` types 16 bytes per node in the input file are used.
+* For `dense_*_array` types 8 bytes times the largest node ID in the input file
+  are used.
+
+The `*_mem_*` types use potentially up to twice this amount.
+
+The `*mem*` and `*mmap*` types store the data in memory, the `*file*` types
+in a file on disk.
+
+The `flex_mem` type automatically switches between something similar to
+`sparse_mmap_array` for smaller extracts and `dense_mmap_array` for larger
+extracts or the whole planet file.
+
+If you specify the **--verbose**, **-v** option, Osmium will display how much
+memory was used for the index.
+
+
+# SEE ALSO
+
+* **osmium**(1), **osmium-add-locations-to-ways**(1), **osmium-export**(1)
+* [Osmium website](http://osmcode.org/osmium-tool/)
+* [Index types](http://osmcode.org/osmium-concepts/#indexes)
+
diff --git a/man/osmium-merge.md b/man/osmium-merge.md
index 90ef1cf..3d7e655 100644
--- a/man/osmium-merge.md
+++ b/man/osmium-merge.md
@@ -25,9 +25,11 @@ files as input creating a new history file. Do not use this command to merge
 non-history files with data from different points in time. It will not work
 correctly.
 
-While merging only object type, id, and version are compared. If you have
-objects with the same type, id, and version but different other data, the
-result of this command is undefined.
+If you have objects with the same type, id, and version but different other
+data, the result of this command is undefined. This situation can never happen
+in correct OSM files, but sometimes buggy programs can generate data like this.
+Osmium doesn't make any promises on what the result of the command is if the
+input data is not correct.
 
 @MAN_COMMON_OPTIONS@
 @MAN_INPUT_OPTIONS@
diff --git a/man/osmium-renumber.md b/man/osmium-renumber.md
index 5077cef..4f9aa3d 100644
--- a/man/osmium-renumber.md
+++ b/man/osmium-renumber.md
@@ -20,9 +20,14 @@ from ways or relations are not guaranteed to be in the correct order.
 
 This command expects the input file to be ordered in the usual way: First
 nodes in order of ID, then ways in order of ID, then relations in order of ID.
+Negative IDs are allowed, they must be ordered before the positive IDs. See
+the **osmium-sort**(1) man page for details of the ordering.
+
 The input file will be read twice, so it will not work with STDIN.
 
-Currently this command can not renumber negative IDs.
+To renumber the IDs in several files, call **osmium renumber** for each file
+and specify the `-i` or `--index-directory` option each time. See the
+**INDEX FILES** section for more details.
 
 You must never upload the data generated by this command to OSM! This would
 really confuse the OSM database because it knows the objects under different
@@ -108,6 +113,6 @@ then rewrite a change file, too:
 
 # SEE ALSO
 
-* **osmium**(1), **osmium-file-formats**(5)
+* **osmium**(1), **osmium-file-formats**(5), **osmium-sort**(1)
 * [Osmium website](http://osmcode.org/osmium-tool/)
 
diff --git a/man/osmium-sort.md b/man/osmium-sort.md
index 5fe40ea..d2aeebd 100644
--- a/man/osmium-sort.md
+++ b/man/osmium-sort.md
@@ -12,7 +12,18 @@ osmium-sort - sort OSM files
 # DESCRIPTION
 
 Merges the content of all input files given on the command line and sort the
-result. Objects are sorted by type, ID, and version.
+result.
+
+Objects are sorted by type, ID, and version. IDs are sorted negative IDs first,
+the positive IDs, both ordered by their absolute values. So the sort order for
+types and IDs is:
+
+node -1, node -2, ..., node 1, node 2, ...,
+way -1, way -2, ..., way 1, way 2, ...,
+relation -1, relation -2, ..., relation 1, relation 2, ...
+
+If there are several objects of the same type and with the same ID they are
+ordered by ascending version.
 
 This works with normal OSM data files, history files, and change files.
 
diff --git a/man/osmium.md b/man/osmium.md
index e1749a2..0b70ebf 100644
--- a/man/osmium.md
+++ b/man/osmium.md
@@ -48,6 +48,9 @@ derive-changes
 diff
 :   display differences between OSM files
 
+export
+:   export OSM data
+
 extract
 :   create geographical extracts from an OSM file
 
@@ -91,7 +94,7 @@ Most commands support the following options:
 
 -v, --verbose
 :   Set verbose mode. The program will output information about what it is
-    doing to *stderr*.
+    doing to STDERR.
 
 
 # MEMORY USAGE
@@ -119,6 +122,7 @@ If an osmium command exits with an "Out of memory" error, try running it with
   **osmium-check-refs**(1),
   **osmium-derive-changes**(1),
   **osmium-diff**(1),
+  **osmium-export**(1),
   **osmium-extract**(1),
   **osmium-fileinfo**(1),
   **osmium-getid**(1),
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c9fbbef..22c68b2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,7 +6,7 @@
 #
 #-----------------------------------------------------------------------------
 
-file(GLOB OSMIUM_SOURCE_FILES *.cpp extract/*.cpp ${PROJECT_BINARY_DIR}/src/version.cpp)
+file(GLOB OSMIUM_SOURCE_FILES *.cpp */*.cpp ${PROJECT_BINARY_DIR}/src/version.cpp)
 
 add_executable(osmium ${OSMIUM_SOURCE_FILES})
 
diff --git a/src/command_add_locations_to_ways.cpp b/src/command_add_locations_to_ways.cpp
index 86b8f51..700bd8a 100644
--- a/src/command_add_locations_to_ways.cpp
+++ b/src/command_add_locations_to_ways.cpp
@@ -48,7 +48,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 bool CommandAddLocationsToWays::setup(const std::vector<std::string>& arguments) {
     const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
-    std::string default_index_type{ map_factory.has_map_type("sparse_mmap_array") ? "sparse_mmap_array" : "sparse_mem_array" };
+    std::string default_index_type{"flex_mem"};
 
     po::options_description opts_cmd{"COMMAND OPTIONS"};
     opts_cmd.add_options()
@@ -82,7 +82,7 @@ bool CommandAddLocationsToWays::setup(const std::vector<std::string>& arguments)
 
     if (vm.count("show-index-types")) {
         for (const auto& map_type : map_factory.map_types()) {
-            std::cout << map_type << "\n";
+            std::cout << map_type << '\n';
         }
         return false;
     }
@@ -115,9 +115,9 @@ void CommandAddLocationsToWays::show_arguments() {
     show_output_arguments(m_vout);
 
     m_vout << "  other options:\n";
-    m_vout << "    index type: " << m_index_type_name << "\n";
+    m_vout << "    index type: " << m_index_type_name << '\n';
     m_vout << "    keep untagged nodes: " << yes_no(m_keep_untagged_nodes);
-    m_vout << "\n";
+    m_vout << '\n';
 }
 
 void CommandAddLocationsToWays::copy_data(osmium::ProgressBar& progress_bar, osmium::io::Reader& reader, osmium::io::Writer& writer, location_handler_type& location_handler) {
@@ -140,7 +140,7 @@ void CommandAddLocationsToWays::copy_data(osmium::ProgressBar& progress_bar, osm
 bool CommandAddLocationsToWays::run() {
     const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
     auto location_index = map_factory.create_map(m_index_type_name);
-    location_handler_type location_handler(*location_index);
+    location_handler_type location_handler{*location_index};
 
     if (m_ignore_missing_nodes) {
         location_handler.ignore_errors();
@@ -181,6 +181,7 @@ bool CommandAddLocationsToWays::run() {
         writer.close();
     }
 
+    m_vout << "About " << (location_index->used_memory() / (1024 * 1024)) << " MBytes used for node location index (in main memory or on disk).\n";
     show_memory_used();
     m_vout << "Done.\n";
 
diff --git a/src/command_add_locations_to_ways.hpp b/src/command_add_locations_to_ways.hpp
index ff54c11..bee74c0 100644
--- a/src/command_add_locations_to_ways.hpp
+++ b/src/command_add_locations_to_ways.hpp
@@ -26,15 +26,11 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <string>
 #include <vector>
 
-#include <osmium/index/map/dense_mem_array.hpp> // IWYU pragma: keep
-#include <osmium/index/map/dense_mmap_array.hpp> // IWYU pragma: keep
-#include <osmium/index/map/sparse_mem_array.hpp> // IWYU pragma: keep
-#include <osmium/index/map/sparse_mmap_array.hpp> // IWYU pragma: keep
 #include <osmium/handler/node_locations_for_ways.hpp>
+#include <osmium/index/map/all.hpp>
 
 namespace osmium {
     namespace io {
-        class Header;
         class Reader;
         class Writer;
     }
diff --git a/src/command_apply_changes.cpp b/src/command_apply_changes.cpp
index 985b3df..768d7d4 100644
--- a/src/command_apply_changes.cpp
+++ b/src/command_apply_changes.cpp
@@ -21,9 +21,10 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
 #include <algorithm>
+#include <stdexcept>
 #include <string>
-#include <vector>
 #include <utility>
+#include <vector>
 
 #include <boost/function_output_iterator.hpp>
 #include <boost/program_options.hpp>
diff --git a/src/command_cat.hpp b/src/command_cat.hpp
index caa3e37..7cd075c 100644
--- a/src/command_cat.hpp
+++ b/src/command_cat.hpp
@@ -28,10 +28,6 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include "cmd.hpp" // IWYU pragma: export
 
-namespace osmium { namespace io {
-    class Header;
-}}
-
 class CommandCat : public Command, public with_multiple_osm_inputs, public with_osm_output {
 
 public:
diff --git a/src/command_changeset_filter.cpp b/src/command_changeset_filter.cpp
index 199e7d2..f048081 100644
--- a/src/command_changeset_filter.cpp
+++ b/src/command_changeset_filter.cpp
@@ -27,6 +27,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include <boost/program_options.hpp>
 
+#include <osmium/geom/relations.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/io/reader.hpp>
 #include <osmium/io/writer.hpp>
@@ -41,6 +42,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include "command_changeset_filter.hpp"
 #include "exception.hpp"
+#include "util.hpp"
 
 bool CommandChangesetFilter::setup(const std::vector<std::string>& arguments) {
     po::options_description opts_cmd{"COMMAND OPTIONS"};
@@ -55,6 +57,7 @@ bool CommandChangesetFilter::setup(const std::vector<std::string>& arguments) {
     ("uid,U", po::value<osmium::user_id_type>(), "Changesets by given user ID")
     ("after,a", po::value<std::string>(), "Changesets opened after this time")
     ("before,b", po::value<std::string>(), "Changesets closed before this time")
+    ("bbox,B", po::value<std::string>(), "Changesets overlapping this bounding box")
     ;
 
     po::options_description opts_common{add_common_options()};
@@ -134,6 +137,10 @@ bool CommandChangesetFilter::setup(const std::vector<std::string>& arguments) {
         }
     }
 
+    if (vm.count("bbox")) {
+        m_box = parse_bbox(vm["bbox"].as<std::string>(), "--bbox/-B");
+    }
+
     if (m_with_discussion && m_without_discussion) {
         throw argument_error{"You can not use --with-discussion/-d and --without-discussion/-D together."};
     }
@@ -231,7 +238,8 @@ bool CommandChangesetFilter::run() {
                    (m_uid == 0            || changeset.uid() == m_uid) &&
                    (m_user.empty()        || m_user == changeset.user()) &&
                    changeset_after(changeset, m_after) &&
-                   changeset_before(changeset, m_before);
+                   changeset_before(changeset, m_before) &&
+                   (!m_box.valid()        || (changeset.bounds().valid() && osmium::geom::overlaps(changeset.bounds(), m_box)));
 
     });
 
diff --git a/src/command_changeset_filter.hpp b/src/command_changeset_filter.hpp
index 3f52b66..8e4d4ee 100644
--- a/src/command_changeset_filter.hpp
+++ b/src/command_changeset_filter.hpp
@@ -43,6 +43,7 @@ class CommandChangesetFilter : public Command, public with_single_osm_input, pub
     std::string m_user;
     osmium::Timestamp m_after = osmium::start_of_time();
     osmium::Timestamp m_before = osmium::end_of_time();
+    osmium::Box m_box;
 
 public:
 
diff --git a/src/command_check_refs.cpp b/src/command_check_refs.cpp
index 70d4df4..9fcb9e5 100644
--- a/src/command_check_refs.cpp
+++ b/src/command_check_refs.cpp
@@ -32,6 +32,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/handler.hpp>
 #include <osmium/handler/check_order.hpp>
 #include <osmium/index/id_set.hpp>
+#include <osmium/index/nwr_array.hpp>
 #include <osmium/io/reader.hpp>
 #include <osmium/osm.hpp>
 #include <osmium/util/progress_bar.hpp>
@@ -93,11 +94,10 @@ void CommandCheckRefs::show_arguments() {
 
 class RefCheckHandler : public osmium::handler::Handler {
 
-    osmium::index::IdSetDense<osmium::unsigned_object_id_type> m_nodes;
-    osmium::index::IdSetDense<osmium::unsigned_object_id_type> m_ways;
-    osmium::index::IdSetDense<osmium::unsigned_object_id_type> m_relations;
+    osmium::nwr_array<osmium::index::IdSetDense<osmium::unsigned_object_id_type>> m_idset_pos;
+    osmium::nwr_array<osmium::index::IdSetDense<osmium::unsigned_object_id_type>> m_idset_neg;
 
-    std::vector<std::pair<uint32_t, uint32_t>> m_relation_refs;
+    std::vector<std::pair<osmium::object_id_type, osmium::object_id_type>> m_relation_refs;
 
     osmium::handler::CheckOrder m_check_order;
 
@@ -114,6 +114,14 @@ class RefCheckHandler : public osmium::handler::Handler {
     bool m_show_ids;
     bool m_check_relations;
 
+    void set(osmium::item_type type, osmium::object_id_type id) {
+        (id > 0 ? m_idset_pos(type) : m_idset_neg(type)).set(std::abs(id));
+    }
+
+    bool get(osmium::item_type type, osmium::object_id_type id) const noexcept {
+        return (id > 0 ? m_idset_pos(type) : m_idset_neg(type)).get(std::abs(id));
+    }
+
 public:
 
     RefCheckHandler(osmium::util::VerboseOutput& vout, osmium::ProgressBar& progress_bar, bool show_ids, bool check_relations) :
@@ -155,8 +163,8 @@ public:
         std::sort(m_relation_refs.begin(), m_relation_refs.end());
 
         m_relation_refs.erase(
-            std::remove_if(m_relation_refs.begin(), m_relation_refs.end(), [this](std::pair<uint32_t, uint32_t> refs){
-                return m_relations.get(refs.first);
+            std::remove_if(m_relation_refs.begin(), m_relation_refs.end(), [this](std::pair<osmium::object_id_type, osmium::object_id_type> refs){
+                return get(osmium::item_type::relation, refs.first);
             }),
             m_relation_refs.end()
         );
@@ -178,7 +186,7 @@ public:
         }
         ++m_node_count;
 
-        m_nodes.set(node.positive_id());
+        set(osmium::item_type::node, node.id());
     }
 
     void way(const osmium::Way& way) {
@@ -191,11 +199,11 @@ public:
         ++m_way_count;
 
         if (m_check_relations) {
-            m_ways.set(way.positive_id());
+            set(osmium::item_type::way, way.id());
         }
 
         for (const auto& node_ref : way.nodes()) {
-            if (!m_nodes.get(node_ref.positive_ref())) {
+            if (!get(osmium::item_type::node, node_ref.ref())) {
                 ++m_missing_nodes_in_ways;
                 if (m_show_ids) {
                     std::cout << "n" << node_ref.ref() << " in w" << way.id() << "\n";
@@ -214,30 +222,30 @@ public:
         ++m_relation_count;
 
         if (m_check_relations) {
-            m_relations.set(relation.positive_id());
+            set(osmium::item_type::relation, relation.id());
             for (const auto& member : relation.members()) {
                 switch (member.type()) {
                     case osmium::item_type::node:
-                        if (!m_nodes.get(member.positive_ref())) {
+                        if (!get(osmium::item_type::node, member.ref())) {
                             ++m_missing_nodes_in_relations;
-                            m_nodes.set(member.positive_ref());
+                            set(osmium::item_type::node, member.ref());
                             if (m_show_ids) {
                                 std::cout << "n" << member.ref() << " in r" << relation.id() << "\n";
                             }
                         }
                         break;
                     case osmium::item_type::way:
-                        if (!m_ways.get(member.positive_ref())) {
+                        if (!get(osmium::item_type::way, member.ref())) {
                             ++m_missing_ways_in_relations;
-                            m_ways.set(member.positive_ref());
+                            set(osmium::item_type::way, member.ref());
                             if (m_show_ids) {
                                 std::cout << "w" << member.ref() << " in r" << relation.id() << "\n";
                             }
                         }
                         break;
                     case osmium::item_type::relation:
-                        if (member.ref() > relation.id() || !m_relations.get(member.positive_ref())) {
-                            m_relation_refs.emplace_back(uint32_t(member.ref()), uint32_t(relation.id()));
+                        if (member.ref() > relation.id() || !get(osmium::item_type::relation, member.ref())) {
+                            m_relation_refs.emplace_back(member.ref(), relation.id());
                         }
                         break;
                     default:
@@ -253,6 +261,16 @@ public:
         }
     }
 
+    std::size_t used_memory() const noexcept {
+        return m_idset_pos(osmium::item_type::node).used_memory() +
+               m_idset_pos(osmium::item_type::way).used_memory() +
+               m_idset_pos(osmium::item_type::relation).used_memory() +
+               m_idset_neg(osmium::item_type::node).used_memory() +
+               m_idset_neg(osmium::item_type::way).used_memory() +
+               m_idset_neg(osmium::item_type::relation).used_memory() +
+               m_relation_refs.capacity() * sizeof(decltype(m_relation_refs)::value_type);
+    }
+
 }; // class RefCheckHandler
 
 bool CommandCheckRefs::run() {
@@ -289,6 +307,8 @@ bool CommandCheckRefs::run() {
         std::cerr << "Nodes in ways missing: " << handler.missing_nodes_in_ways() << "\n";
     }
 
+    m_vout << "Memory used for indexes: " << (handler.used_memory() / (1024 * 1024)) << " MBytes\n";
+
     show_memory_used();
     m_vout << "Done.\n";
 
diff --git a/src/command_export.cpp b/src/command_export.cpp
new file mode 100644
index 0000000..83228c1
--- /dev/null
+++ b/src/command_export.cpp
@@ -0,0 +1,427 @@
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <cctype>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/program_options.hpp>
+
+#include <rapidjson/document.h>
+#include <rapidjson/error/en.h>
+#include <rapidjson/istreamwrapper.h>
+
+#include <osmium/area/assembler.hpp>
+#include <osmium/area/multipolygon_manager.hpp>
+#include <osmium/handler/check_order.hpp>
+#include <osmium/io/any_input.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm.hpp>
+#include <osmium/relations/manager_util.hpp>
+#include <osmium/util/verbose_output.hpp>
+#include <osmium/visitor.hpp>
+
+#include "command_export.hpp"
+#include "exception.hpp"
+#include "util.hpp"
+
+#include "export/export_handler.hpp"
+#include "export/export_format_json.hpp"
+#include "export/export_format_text.hpp"
+
+static std::string get_attr_string(const rapidjson::Value& object, const char* key) {
+    const auto it = object.FindMember(key);
+    if (it == object.MemberEnd()) {
+        return "";
+    }
+
+    if (it->value.IsString()) {
+        return it->value.GetString();
+    }
+
+    if (it->value.IsBool()) {
+        if (it->value.GetBool()) {
+            return std::string{"@"} + key;
+        }
+    }
+
+    return "";
+}
+
+void CommandExport::parse_options(const rapidjson::Value& attributes) {
+    if (!attributes.IsObject()) {
+        throw config_error{"'attributes' member must be an object."};
+    }
+
+    m_options.type      = get_attr_string(attributes, "type");
+    m_options.id        = get_attr_string(attributes, "id");
+    m_options.version   = get_attr_string(attributes, "version");
+    m_options.changeset = get_attr_string(attributes, "changeset");
+    m_options.timestamp = get_attr_string(attributes, "timestamp");
+    m_options.uid       = get_attr_string(attributes, "uid");
+    m_options.user      = get_attr_string(attributes, "user");
+    m_options.way_nodes = get_attr_string(attributes, "way_nodes");
+}
+
+static bool parse_string_array(const rapidjson::Value& object, const char* key, std::vector<std::string>& result) {
+    const auto json = object.FindMember(key);
+    if (json == object.MemberEnd()) {
+        return false;
+    }
+
+    if (!json->value.IsArray()) {
+        throw config_error{std::string{"'"} + key + "' member in top-level object must be array."};
+    }
+
+    for (const auto& value : json->value.GetArray()) {
+        if (!value.IsString()) {
+            throw config_error{std::string{"Array elements in '"} + key + "' must be strings."};
+        }
+
+        if (value.GetString()[0] != '\0') {
+            result.emplace_back(value.GetString());
+        }
+    }
+
+    return true;
+}
+
+void CommandExport::parse_config_file() {
+    std::ifstream config_file{m_config_file_name};
+    rapidjson::IStreamWrapper stream_wrapper{config_file};
+
+    rapidjson::Document doc;
+    if (doc.ParseStream<(rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag)>(stream_wrapper).HasParseError()) {
+        throw config_error{std::string{"JSON error at offset "} +
+                           std::to_string(doc.GetErrorOffset()) +
+                           ": " +
+                           rapidjson::GetParseError_En(doc.GetParseError())
+                          };
+    }
+
+    if (!doc.IsObject()) {
+        throw config_error{"Top-level value must be an object."};
+    }
+
+    const auto json_attr = doc.FindMember("attributes");
+    if (json_attr != doc.MemberEnd()) {
+        parse_options(json_attr->value);
+    }
+
+    parse_string_array(doc, "linear_tags", m_linear_tags);
+    parse_string_array(doc, "area_tags", m_area_tags);
+
+    parse_string_array(doc, "include_tags", m_include_tags);
+    parse_string_array(doc, "exclude_tags", m_exclude_tags);
+}
+
+void CommandExport::canonicalize_output_format() {
+    for (auto& c : m_output_format) {
+        c = std::tolower(c);
+    }
+
+    if (m_output_format == "json") {
+        m_output_format = "geojson";
+        return;
+    }
+
+    if (m_output_format == "jsonseq") {
+        m_output_format = "geojsonseq";
+        return;
+    }
+
+    if (m_output_format == "txt") {
+        m_output_format = "text";
+        return;
+    }
+}
+
+bool CommandExport::setup(const std::vector<std::string>& arguments) {
+    const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
+    std::string default_index_type{"flex_mem"};
+
+    po::options_description opts_cmd{"COMMAND OPTIONS"};
+    opts_cmd.add_options()
+    ("add-unique-id,u", po::value<std::string>(), "Add unique id to each feature ('counter' or 'type_id')")
+    ("config,c", po::value<std::string>(), "Config file")
+    ("fsync", "Call fsync after writing file")
+    ("index-type,i", po::value<std::string>()->default_value(default_index_type), "Index type to use")
+    ("keep-untagged,n", "Keep features that don't have any tags")
+    ("output,o", po::value<std::string>(), "Output file (default: STDOUT)")
+    ("output-format,f", po::value<std::string>(), "Output format (default depends on output file suffix)")
+    ("overwrite,O", "Allow existing output file to be overwritten")
+    ("show-errors,e", "Output any geometry errors on STDOUT")
+    ("stop-on-error,E", "Stop on the first error encountered")
+    ("show-index-types,I", "Show available index types")
+    ("omit-rs,r", "Do not print RS (record separator) character when using JSON Text Sequences")
+    ;
+
+    po::options_description opts_common{add_common_options()};
+    po::options_description opts_input{add_single_input_options()};
+
+    po::options_description hidden;
+    hidden.add_options()
+    ("input-filename", po::value<std::string>(), "OSM input file")
+    ;
+
+    po::options_description desc;
+    desc.add(opts_cmd).add(opts_common).add(opts_input);
+
+    po::options_description parsed_options;
+    parsed_options.add(desc).add(hidden);
+
+    po::positional_options_description positional;
+    positional.add("input-filename", 1);
+
+    po::variables_map vm;
+    po::store(po::command_line_parser(arguments).options(parsed_options).positional(positional).run(), vm);
+    po::notify(vm);
+
+    if (vm.count("show-index-types")) {
+        for (const auto& map_type : map_factory.map_types()) {
+            std::cout << map_type << '\n';
+        }
+        std::cout << "none\n";
+        return false;
+    }
+
+    setup_common(vm, desc);
+    setup_input_file(vm);
+
+    if (vm.count("config")) {
+        m_config_file_name = vm["config"].as<std::string>();
+
+        try {
+            parse_config_file();
+        } catch (const config_error&) {
+            std::cerr << "Error while reading config file '" << m_config_file_name << "':\n";
+            throw;
+        }
+    }
+
+    if (vm.count("add-unique-id")) {
+        const std::string value = vm["add-unique-id"].as<std::string>();
+        if (value == "counter") {
+            m_options.unique_id = unique_id_type::counter;
+        } else if (value == "type_id") {
+            m_options.unique_id = unique_id_type::type_id;
+        } else {
+            throw argument_error{"Unknown --add-unique-id, -u setting. Use 'counter' or 'type_id'."};
+        }
+    }
+
+    if (vm.count("fsync")) {
+        m_fsync = osmium::io::fsync::yes;
+    }
+
+    if (vm.count("index-type")) {
+        m_index_type_name = vm["index-type"].as<std::string>();
+        if (m_index_type_name != "none" && !map_factory.has_map_type(m_index_type_name)) {
+            throw argument_error{std::string{"Unknown index type '"} + m_index_type_name + "'. Use --show-index-types or -I to get a list."};
+        }
+    }
+
+    if (vm.count("keep-untagged")) {
+        m_options.keep_untagged = true;
+    }
+
+    if (vm.count("output")) {
+        m_output_filename = vm["output"].as<std::string>();
+
+        const auto pos = m_output_filename.rfind('.');
+        if (pos != std::string::npos) {
+            m_output_format = m_output_filename.substr(pos + 1);
+        }
+    } else {
+        m_output_filename = "-";
+    }
+
+    if (vm.count("output-format")) {
+        m_output_format = vm["output-format"].as<std::string>();
+    }
+
+    canonicalize_output_format();
+
+    if (m_output_format != "geojson" && m_output_format != "geojsonseq" && m_output_format != "text") {
+        throw argument_error{"Set output format with --output-format or -f to 'geojson', 'geojsonseq', or 'text'."};
+    }
+
+    if (vm.count("overwrite")) {
+        m_output_overwrite = osmium::io::overwrite::allow;
+    }
+
+    if (vm.count("omit-rs")) {
+        m_options.print_record_separator = false;
+        if (m_output_format != "geojsonseq") {
+            warning("The --omit-rs/-r option only works for GeoJSON Text Sequence (geojsonseq) format. Ignored.\n");
+        }
+    }
+
+    if (vm.count("show-errors")) {
+        m_show_errors = true;
+    }
+
+    if (vm.count("stop-on-error")) {
+        m_show_errors = true;
+        m_stop_on_error = true;
+    }
+
+    if (!m_include_tags.empty() && !m_exclude_tags.empty()) {
+        throw config_error{"Setting both 'include_tags' and 'exclude_tags' is not allowed."};
+    }
+
+    if (!m_include_tags.empty()) {
+        initialize_tags_filter(m_options.tags_filter, false, m_include_tags);
+    } else if (!m_exclude_tags.empty()) {
+        initialize_tags_filter(m_options.tags_filter, true, m_exclude_tags);
+    }
+
+    return true;
+}
+
+static void print_taglist(osmium::util::VerboseOutput& vout, const std::vector<std::string>& strings) {
+    for (const auto& str : strings) {
+        vout << "    " << str << '\n';
+    }
+}
+
+static const char* print_unique_id_type(unique_id_type unique_id) {
+    switch (unique_id) {
+        case unique_id_type::counter:
+            return "counter";
+        case unique_id_type::type_id:
+            return "type and id";
+        default:
+            break;
+    }
+
+    return "no";
+}
+
+void CommandExport::show_arguments() {
+    show_single_input_arguments(m_vout);
+
+    m_vout << "  output options:\n";
+    m_vout << "    file name: " << m_output_filename << '\n';
+
+    if (m_output_format == "geojsonseq") {
+        m_vout << "    file format: geojsonseq (with" << (m_options.print_record_separator ? " RS)\n" : "out RS)\n");
+    } else {
+        m_vout << "    file format: " << m_output_format << '\n';
+    }
+    m_vout << "    overwrite: " << yes_no(m_output_overwrite == osmium::io::overwrite::allow);
+    m_vout << "    fsync: " << yes_no(m_fsync == osmium::io::fsync::yes);
+    m_vout << "  attributes:\n";
+    m_vout << "    type:      " << (m_options.type.empty()      ? "(omitted)" : m_options.type)      << '\n';
+    m_vout << "    id:        " << (m_options.id.empty()        ? "(omitted)" : m_options.id)        << '\n';
+    m_vout << "    version:   " << (m_options.version.empty()   ? "(omitted)" : m_options.version)   << '\n';
+    m_vout << "    changeset: " << (m_options.changeset.empty() ? "(omitted)" : m_options.changeset) << '\n';
+    m_vout << "    timestamp: " << (m_options.timestamp.empty() ? "(omitted)" : m_options.timestamp) << '\n';
+    m_vout << "    uid:       " << (m_options.uid.empty()       ? "(omitted)" : m_options.uid)       << '\n';
+    m_vout << "    user:      " << (m_options.user.empty()      ? "(omitted)" : m_options.user)      << '\n';
+    m_vout << "    way_nodes: " << (m_options.way_nodes.empty() ? "(omitted)" : m_options.way_nodes) << '\n';
+
+    m_vout << "  linear tags:\n";
+    print_taglist(m_vout, m_linear_tags);
+    m_vout << "  area tags:\n";
+    print_taglist(m_vout, m_area_tags);
+
+    if (!m_include_tags.empty()) {
+        m_vout << "  include only these tags:\n";
+        print_taglist(m_vout, m_include_tags);
+    } else if (!m_exclude_tags.empty()) {
+        m_vout << "  exclude these tags:\n";
+        print_taglist(m_vout, m_exclude_tags);
+    }
+
+    m_vout << "  other options:\n";
+    m_vout << "    index type: " << m_index_type_name << '\n';
+    m_vout << "    add unique IDs: " << print_unique_id_type(m_options.unique_id) << '\n';
+    m_vout << "    keep untagged features: " << yes_no(m_options.keep_untagged);
+}
+
+static std::unique_ptr<ExportFormat> create_handler(const std::string& output_format,
+                                                    const std::string& output_filename,
+                                                    osmium::io::overwrite overwrite,
+                                                    osmium::io::fsync fsync,
+                                                    const options_type& options) {
+    if (output_format == "geojson" || output_format == "geojsonseq") {
+        return std::unique_ptr<ExportFormat>{new ExportFormatJSON{output_format, output_filename, overwrite, fsync, options}};
+    }
+
+    if (output_format == "text") {
+        return std::unique_ptr<ExportFormat>{new ExportFormatText{output_format, output_filename, overwrite, fsync, options}};
+    }
+
+    throw argument_error{"Unknown output format"};
+}
+
+bool CommandExport::run() {
+    osmium::area::Assembler::config_type assembler_config;
+    osmium::area::MultipolygonManager<osmium::area::Assembler> mp_manager{assembler_config};
+
+    m_vout << "First pass through input file (reading relations)...\n";
+    osmium::relations::read_relations(m_input_file, mp_manager);
+    m_vout << "First pass done.\n";
+
+    m_vout << "Second pass through input file...\n";
+
+    auto handler = create_handler(m_output_format, m_output_filename, m_output_overwrite, m_fsync, m_options);
+    ExportHandler export_handler{std::move(handler), m_linear_tags, m_area_tags, m_show_errors, m_stop_on_error};
+    osmium::handler::CheckOrder check_order_handler;
+
+    if (m_index_type_name == "none") {
+        osmium::io::Reader reader{m_input_file};
+        osmium::apply(reader, check_order_handler, export_handler, mp_manager.handler([&export_handler](osmium::memory::Buffer&& buffer) {
+            osmium::apply(buffer, export_handler);
+        }));
+        reader.close();
+    } else {
+        const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
+        auto location_index = map_factory.create_map(m_index_type_name);
+        location_handler_type location_handler{*location_index};
+        location_handler.ignore_errors();
+
+        osmium::io::Reader reader{m_input_filename};
+        osmium::apply(reader, check_order_handler, location_handler, export_handler, mp_manager.handler([&export_handler](osmium::memory::Buffer&& buffer) {
+            osmium::apply(buffer, export_handler);
+        }));
+        reader.close();
+        m_vout << "About " << (location_index->used_memory() / (1024 * 1024)) << " MBytes used for node location index (in main memory or on disk).\n";
+    }
+    m_vout << "Second pass done.\n";
+    export_handler.close();
+
+    m_vout << "Wrote " << export_handler.count() << " features.\n";
+    m_vout << "Encountered " << export_handler.error_count() << " errors.\n";
+
+    show_memory_used();
+
+    m_vout << "Done.\n";
+
+    return true;
+}
+
diff --git a/src/command_export.hpp b/src/command_export.hpp
new file mode 100644
index 0000000..fbad895
--- /dev/null
+++ b/src/command_export.hpp
@@ -0,0 +1,87 @@
+#ifndef COMMAND_EXPORT_HPP
+#define COMMAND_EXPORT_HPP
+
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <string>
+#include <vector>
+
+#include <rapidjson/document.h>
+
+#include <osmium/handler/node_locations_for_ways.hpp>
+#include <osmium/index/map/all.hpp>
+#include <osmium/io/writer_options.hpp>
+
+#include "cmd.hpp" // IWYU pragma: export
+
+#include "export/options.hpp"
+
+class CommandExport : public Command, public with_single_osm_input {
+
+    using index_type = osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>;
+    using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
+
+    options_type m_options;
+
+    std::vector<std::string> m_linear_tags;
+    std::vector<std::string> m_area_tags;
+    std::vector<std::string> m_include_tags;
+    std::vector<std::string> m_exclude_tags;
+
+    std::string m_config_file_name;
+    std::string m_index_type_name;
+    std::string m_output_filename;
+    std::string m_output_format;
+
+    osmium::io::overwrite m_output_overwrite = osmium::io::overwrite::no;
+    osmium::io::fsync m_fsync = osmium::io::fsync::no;
+
+    bool m_show_errors = false;
+    bool m_stop_on_error = false;
+
+    void canonicalize_output_format();
+    void parse_options(const rapidjson::Value& attributes);
+    void parse_config_file();
+
+public:
+
+    CommandExport() = default;
+
+    bool setup(const std::vector<std::string>& arguments) override final;
+
+    void show_arguments() override final;
+
+    bool run() override final;
+
+    const char* name() const noexcept override final {
+        return "export";
+    }
+
+    const char* synopsis() const noexcept override final {
+        return "osmium export [OPTIONS] OSM-FILE";
+    }
+
+}; // class CommandExport
+
+
+#endif // COMMAND_EXPORT_HPP
diff --git a/src/command_extract.cpp b/src/command_extract.cpp
index 3874252..40b17f2 100644
--- a/src/command_extract.cpp
+++ b/src/command_extract.cpp
@@ -20,12 +20,10 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <cassert>
 #include <cstdlib>
 #include <fstream>
 #include <iostream>
 #include <memory>
-#include <stdexcept>
 #include <string>
 #include <vector>
 
@@ -36,6 +34,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <rapidjson/istreamwrapper.h>
 
 #include <osmium/geom/coordinates.hpp>
+#include <osmium/handler/check_order.hpp>
 #include <osmium/io/any_input.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/io/writer_options.hpp>
@@ -107,24 +106,6 @@ namespace {
         throw config_error{"'bbox' member is not an array or object."};
     }
 
-    osmium::Box parse_bbox(const std::string& str) {
-        const auto coordinates = osmium::split_string(str, ',');
-
-        if (coordinates.size() != 4) {
-            throw argument_error{"Need exactly four coordinates in --bbox/-b option."};
-        }
-
-        const osmium::Location bottom_left{std::atof(coordinates[0].c_str()), std::atof(coordinates[1].c_str())};
-        const osmium::Location top_right{std::atof(coordinates[2].c_str()), std::atof(coordinates[3].c_str())};
-
-        if (bottom_left.x() < top_right.x() &&
-            bottom_left.y() < top_right.y()) {
-            return osmium::Box{bottom_left, top_right};
-        }
-
-        throw argument_error{"Need LEFT < RIGHT and BOTTOM < TOP in --bbox/-b option."};
-    }
-
     std::size_t parse_multipolygon_object(const std::string& directory, std::string file_name, std::string file_type, osmium::memory::Buffer& buffer) {
         if (file_name.empty()) {
             throw config_error{"Missing 'file_name' in '(multi)polygon' object."};
@@ -155,8 +136,12 @@ namespace {
             try {
                 OSMFileParser parser{buffer, file_name};
                 return parser();
+            } catch (const std::system_error& e) {
+                throw osmium::io_error{std::string{"While reading file '"} + file_name + "':\n" + e.what()};
             } catch (const osmium::io_error& e) {
                 throw osmium::io_error{std::string{"While reading file '"} + file_name + "':\n" + e.what()};
+            } catch (const osmium::out_of_order_error& e) {
+                throw osmium::io_error{std::string{"While reading file '"} + file_name + "':\n" + e.what()};
             }
         } else if (file_type == "geojson") {
             GeoJSONFileParser parser{buffer, file_name};
@@ -229,6 +214,7 @@ void CommandExtract::parse_config_file() {
 
     std::string directory{get_value_as_string(doc, "directory")};
     if (!directory.empty() && m_output_directory.empty()) {
+        m_vout << "  Directory set to '" << directory << "'.\n";
         set_directory(directory);
     }
 
@@ -241,6 +227,7 @@ void CommandExtract::parse_config_file() {
         throw config_error{"'extracts' member in top-level object must be array."};
     }
 
+    m_vout << "  Reading extracts from config file...\n";
     int extract_num = 1;
     for (const auto& e : json_extracts->value.GetArray()) {
         std::string output;
@@ -254,6 +241,8 @@ void CommandExtract::parse_config_file() {
                 throw config_error{"Missing 'output' field for extract."};
             }
 
+            m_vout << "    Looking at extract '" << output << "'...\n";
+
             std::string output_format{get_value_as_string(e, "output_format")};
             std::string description{get_value_as_string(e, "description")};
 
@@ -261,7 +250,12 @@ void CommandExtract::parse_config_file() {
             const auto json_polygon      = e.FindMember("polygon");
             const auto json_multipolygon = e.FindMember("multipolygon");
 
-            const osmium::io::File output_file{m_output_directory + output, output_format};
+            osmium::io::File output_file{m_output_directory + output, output_format};
+            if (m_with_history) {
+                output_file.set_has_multiple_object_versions(true);
+            } else if (output_file.has_multiple_object_versions()) {
+                throw config_error{"Looks like you are trying to write a history file, but option --with-history is not set."};
+            }
 
             if (json_bbox != e.MemberEnd()) {
                 m_extracts.emplace_back(new ExtractBBox{output_file, description, parse_bbox(json_bbox->value)});
@@ -306,10 +300,14 @@ void CommandExtract::parse_config_file() {
         } catch (const osmium::io_error&) {
             std::cerr << "Error while reading OSM file for extract " << extract_num << " (" << output << "):\n";
             throw;
+        } catch (const osmium::out_of_order_error&) {
+            std::cerr << "Error while reading OSM file for extract " << extract_num << " (" << output << "):\n";
+            throw;
         }
 
         ++extract_num;
     }
+    m_vout << '\n';
 }
 
 std::unique_ptr<ExtractStrategy> CommandExtract::make_strategy(const std::string& name) {
@@ -380,6 +378,10 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
         throw argument_error{"Can only use one of --config/-c, --bbox/-b, or --polygon/-p."};
     }
 
+    if (vm.count("with-history")) {
+        m_with_history = true;
+    }
+
     if (vm.count("config")) {
         if (vm.count("directory")) {
             set_directory(vm["directory"].as<std::string>());
@@ -396,13 +398,6 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
             m_config_directory = m_config_file_name;
             m_config_directory.resize(slash + 1);
         }
-
-        try {
-            parse_config_file();
-        } catch (const config_error&) {
-            std::cerr << "Error while reading config file '" << m_config_file_name << "':\n";
-            throw;
-        }
     }
 
     if (vm.count("bbox")) {
@@ -410,7 +405,7 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
             warning("Ignoring --directory/-d option.\n");
         }
         check_output_file();
-        m_extracts.emplace_back(new ExtractBBox{m_output_file, "", parse_bbox(vm["bbox"].as<std::string>())});
+        m_extracts.emplace_back(new ExtractBBox{m_output_file, "", parse_bbox(vm["bbox"].as<std::string>(), "--box/-b")});
     }
 
     if (vm.count("polygon")) {
@@ -427,10 +422,6 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
         }
     }
 
-    if (vm.count("with-history")) {
-        m_with_history = true;
-    }
-
     if (vm.count("set-bounds")) {
         m_set_bounds = true;
     }
@@ -456,6 +447,9 @@ void CommandExtract::show_arguments() {
     m_vout << "    output directory: " << m_output_directory << '\n';
 
     m_vout << '\n';
+}
+
+void CommandExtract::show_extracts() {
     m_vout << "Extracts:\n";
 
     int n = 1;
@@ -488,11 +482,26 @@ void CommandExtract::show_arguments() {
 }
 
 bool CommandExtract::run() {
+    if (!m_config_file_name.empty()) {
+        m_vout << "Reading config file...\n";
+        try {
+            parse_config_file();
+        } catch (const config_error&) {
+            std::cerr << "Error while reading config file '" << m_config_file_name << "':\n";
+            throw;
+        }
+    }
+
+    show_extracts();
+
     osmium::io::Header header;
     setup_header(header);
 
     for (const auto& extract : m_extracts) {
         osmium::io::Header file_header{header};
+        if (m_with_history) {
+            file_header.set_has_multiple_object_versions(true);
+        }
         if (m_set_bounds) {
             file_header.add_box(extract->envelope());
         }
diff --git a/src/command_extract.hpp b/src/command_extract.hpp
index 17e0197..9de0346 100644
--- a/src/command_extract.hpp
+++ b/src/command_extract.hpp
@@ -23,6 +23,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <vector>
@@ -36,7 +37,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 class CommandExtract : public Command, public with_single_osm_input, public with_osm_output {
 
-    static const size_t initial_buffer_size = 10 * 1024;
+    static const std::size_t initial_buffer_size = 10 * 1024;
 
     std::string m_config_file_name;
     std::string m_config_directory;
@@ -49,6 +50,7 @@ class CommandExtract : public Command, public with_single_osm_input, public with
     bool m_set_bounds = false;
 
     void parse_config_file();
+    void show_extracts();
 
     void set_directory(const std::string& directory);
 
diff --git a/src/command_fileinfo.cpp b/src/command_fileinfo.cpp
index edad023..7cbf691 100644
--- a/src/command_fileinfo.cpp
+++ b/src/command_fileinfo.cpp
@@ -21,14 +21,12 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
 #include <algorithm>
-#include <cerrno>
 #include <cstdint>
 #include <iostream>
 #include <iterator>
 #include <memory>
 #include <sstream>
 #include <string>
-#include <system_error>
 #include <utility>
 #include <vector>
 
@@ -52,6 +50,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/osm.hpp>
 #include <osmium/osm/box.hpp>
 #include <osmium/osm/crc.hpp>
+#include <osmium/osm/object_comparisons.hpp>
 #include <osmium/util/file.hpp>
 #include <osmium/util/minmax.hpp>
 #include <osmium/util/progress_bar.hpp>
@@ -113,7 +112,7 @@ struct InfoHandler : public osmium::handler::Handler {
             if (last_id == object.id()) {
                 multiple_versions = true;
             }
-            if (last_id > object.id()) {
+            if (osmium::id_order{}(object.id(), last_id)) {
                 ordered = false;
             }
         } else if (last_type != osmium::item_type::changeset && last_type > object.type()) {
diff --git a/src/command_getid.cpp b/src/command_getid.cpp
index 8058549..4792a09 100644
--- a/src/command_getid.cpp
+++ b/src/command_getid.cpp
@@ -23,6 +23,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <cstddef>
 #include <fstream>
 #include <iostream>
+#include <stdexcept>
 #include <string>
 #include <utility>
 #include <vector>
@@ -55,6 +56,7 @@ void CommandGetId::parse_and_add_id(const std::string& s) {
 void CommandGetId::read_id_file(std::istream& stream) {
     m_vout << "Reading ID file...\n";
     for (std::string line; std::getline(stream, line); ) {
+        strip_whitespace(line);
         const auto pos = line.find_first_of(" #");
         if (pos != std::string::npos) {
             line.erase(pos);
diff --git a/src/command_getid.hpp b/src/command_getid.hpp
index d04d7ef..8321084 100644
--- a/src/command_getid.hpp
+++ b/src/command_getid.hpp
@@ -23,7 +23,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <set>
+#include <cstddef>
+#include <iosfwd>
 #include <string>
 #include <vector>
 
diff --git a/src/command_help.cpp b/src/command_help.cpp
index 58cfc74..3fddf33 100644
--- a/src/command_help.cpp
+++ b/src/command_help.cpp
@@ -68,8 +68,13 @@ bool CommandHelp::run() {
                       << "\n";
         }
 
+        std::cout << "\nTOPICS:\n"
+                     "  file-formats           File formats supported by Osmium\n"
+                     "  index-types            Index types for storing node locations\n";
+
         std::cout << "\nUse 'osmium COMMAND -h' for short usage information.\n"
-                     "Use 'osmium help COMMAND' for detailed information on a specific command.\n";
+                     "Use 'osmium help COMMAND' for detailed information on a specific command.\n"
+                     "Use 'osmium help TOPIC' for detailed information on a specific topic.\n";
         return true;
     }
 
@@ -84,6 +89,11 @@ bool CommandHelp::run() {
         return true;
     }
 
+    if (m_topic == "index-types") {
+        show_help("index-types", "");
+        return true;
+    }
+
     std::cerr << "Unknown help topic '" << m_topic << "'.\n";
     return false;
 }
diff --git a/src/command_renumber.cpp b/src/command_renumber.cpp
index e6aad47..200d7d6 100644
--- a/src/command_renumber.cpp
+++ b/src/command_renumber.cpp
@@ -25,6 +25,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <fcntl.h>
 #include <iostream>
 #include <iterator>
+#include <stdexcept>
 #include <string>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -56,18 +57,18 @@ namespace osmium { namespace io {
 
 osmium::object_id_type id_map::operator()(osmium::object_id_type id) {
     // Search for id in m_extra_ids and return if found.
-    auto it = m_extra_ids.find(id);
+    const auto it = m_extra_ids.find(id);
     if (it != m_extra_ids.end()) {
         return it->second;
     }
 
     // New ID is larger than all existing IDs. Add it to end and return.
-    if (m_ids.empty() || id > m_ids.back()) {
+    if (m_ids.empty() || osmium::id_order{}(m_ids.back(), id)) {
         m_ids.push_back(id);
         return m_ids.size();
     }
 
-    const auto element = std::lower_bound(m_ids.cbegin(), m_ids.cend(), id);
+    const auto element = std::lower_bound(m_ids.cbegin(), m_ids.cend(), id, osmium::id_order{});
     // Old ID not found in m_ids, add to m_extra_ids.
     if (element == m_ids.cend() || *element != id) {
         m_ids.push_back(m_ids.back());
@@ -92,13 +93,13 @@ void id_map::write(int fd) {
 }
 
 void id_map::read(int fd, std::size_t file_size) {
-    auto num_elements = file_size / sizeof(osmium::object_id_type);
+    const auto num_elements = file_size / sizeof(osmium::object_id_type);
     m_ids.reserve(num_elements);
     osmium::util::TypedMemoryMapping<osmium::object_id_type> mapping{num_elements, osmium::util::MemoryMapping::mapping_mode::readonly, fd};
 
     osmium::object_id_type last_id = 0;
     for (osmium::object_id_type id : mapping) {
-        if (id > last_id) {
+        if (osmium::id_order{}(last_id, id)) {
             m_ids.push_back(id);
             last_id = id;
         } else {
@@ -175,28 +176,28 @@ void CommandRenumber::renumber(osmium::memory::Buffer& buffer) {
             case osmium::item_type::node:
                 if (osm_entity_bits() & osmium::osm_entity_bits::node) {
                     m_check_order.node(static_cast<const osmium::Node&>(object));
-                    object.set_id(map(osmium::item_type::node)(object.id()));
+                    object.set_id(m_id_map(osmium::item_type::node)(object.id()));
                 }
                 break;
             case osmium::item_type::way:
                 if (osm_entity_bits() & osmium::osm_entity_bits::way) {
                     m_check_order.way(static_cast<const osmium::Way&>(object));
-                    object.set_id(map(osmium::item_type::way)(object.id()));
+                    object.set_id(m_id_map(osmium::item_type::way)(object.id()));
                 }
                 if (osm_entity_bits() & osmium::osm_entity_bits::node) {
                     for (auto& ref : static_cast<osmium::Way&>(object).nodes()) {
-                        ref.set_ref(map(osmium::item_type::node)(ref.ref()));
+                        ref.set_ref(m_id_map(osmium::item_type::node)(ref.ref()));
                     }
                 }
                 break;
             case osmium::item_type::relation:
                 if (osm_entity_bits() & osmium::osm_entity_bits::relation) {
                     m_check_order.relation(static_cast<const osmium::Relation&>(object));
-                    object.set_id(map(osmium::item_type::relation)(object.id()));
+                    object.set_id(m_id_map(osmium::item_type::relation)(object.id()));
                 }
                 for (auto& member : static_cast<osmium::Relation&>(object).members()) {
                     if (osm_entity_bits() & osmium::osm_entity_bits::from_item_type(member.type())) {
-                        member.set_ref(map(member.type())(member.ref()));
+                        member.set_ref(m_id_map(member.type())(member.ref()));
                     }
                 }
                 break;
@@ -230,7 +231,7 @@ void CommandRenumber::read_index(osmium::item_type type) {
         throw std::runtime_error{std::string{"Index file '"} + f + "' has wrong file size"};
     }
 
-    map(type).read(fd, file_size);
+    m_id_map(type).read(fd, file_size);
 
     close(fd);
 }
@@ -249,7 +250,7 @@ void CommandRenumber::write_index(osmium::item_type type) {
     _setmode(fd, _O_BINARY);
 #endif
 
-    map(type).write(fd);
+    m_id_map(type).write(fd);
 
     close(fd);
 }
@@ -272,14 +273,14 @@ bool CommandRenumber::run() {
         read_index(osmium::item_type::way);
         read_index(osmium::item_type::relation);
 
-        m_vout << "  Nodes     index contains " << map(osmium::item_type::node).size() << " items\n";
-        m_vout << "  Ways      index contains " << map(osmium::item_type::way).size() << " items\n";
-        m_vout << "  Relations index contains " << map(osmium::item_type::relation).size() << " items\n";
+        m_vout << "  Nodes     index contains " << m_id_map(osmium::item_type::node).size() << " items\n";
+        m_vout << "  Ways      index contains " << m_id_map(osmium::item_type::way).size() << " items\n";
+        m_vout << "  Relations index contains " << m_id_map(osmium::item_type::relation).size() << " items\n";
     }
 
     if (osm_entity_bits() & osmium::osm_entity_bits::relation) {
         m_vout << "First pass through input file (reading relations)...\n";
-        read_relations(m_input_file, map(osmium::item_type::relation));
+        read_relations(m_input_file, m_id_map(osmium::item_type::relation));
     } else {
         m_vout << "No first pass through input file, because relation IDs are not mapped.\n";
     }
@@ -313,15 +314,15 @@ bool CommandRenumber::run() {
     }
 
     if (osm_entity_bits() & osmium::osm_entity_bits::node) {
-        m_vout << "Largest (referenced) node id: "     << map(osmium::item_type::node).size()     << "\n";
+        m_vout << "Largest (referenced) node id: "     << m_id_map(osmium::item_type::node).size()     << "\n";
     }
 
     if (osm_entity_bits() & osmium::osm_entity_bits::way) {
-        m_vout << "Largest (referenced) way id: "      << map(osmium::item_type::way).size()      << "\n";
+        m_vout << "Largest (referenced) way id: "      << m_id_map(osmium::item_type::way).size()      << "\n";
     }
 
     if (osm_entity_bits() & osmium::osm_entity_bits::relation) {
-        m_vout << "Largest (referenced) relation id: " << map(osmium::item_type::relation).size() << "\n";
+        m_vout << "Largest (referenced) relation id: " << m_id_map(osmium::item_type::relation).size() << "\n";
     }
 
     show_memory_used();
diff --git a/src/command_renumber.hpp b/src/command_renumber.hpp
index b2ab5a1..26d675d 100644
--- a/src/command_renumber.hpp
+++ b/src/command_renumber.hpp
@@ -30,6 +30,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <vector>
 
 #include <osmium/handler/check_order.hpp>
+#include <osmium/index/nwr_array.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/types.hpp>
 
@@ -91,11 +92,7 @@ class CommandRenumber : public Command, public with_single_osm_input, public wit
     osmium::handler::CheckOrder m_check_order;
 
     // id mappings for nodes, ways, and relations
-    id_map m_id_map[3];
-
-    id_map& map(osmium::item_type type) noexcept {
-        return m_id_map[osmium::item_type_to_nwr_index(type)];
-    }
+    osmium::nwr_array<id_map> m_id_map;
 
     void renumber(osmium::memory::Buffer& buffer);
 
diff --git a/src/command_show.cpp b/src/command_show.cpp
index 49179d1..4e1ef3f 100644
--- a/src/command_show.cpp
+++ b/src/command_show.cpp
@@ -20,6 +20,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
+#include <cerrno>
 #include <cstdlib>
 #include <string>
 #include <system_error>
diff --git a/src/command_show.hpp b/src/command_show.hpp
index d3624db..6a9a7f2 100644
--- a/src/command_show.hpp
+++ b/src/command_show.hpp
@@ -26,8 +26,6 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <string>
 #include <vector>
 
-#include <osmium/io/header.hpp>
-
 #include "cmd.hpp" // IWYU pragma: export
 
 class CommandShow : public Command, public with_single_osm_input {
diff --git a/src/command_tags_filter.cpp b/src/command_tags_filter.cpp
index 9ba2761..f15608c 100644
--- a/src/command_tags_filter.cpp
+++ b/src/command_tags_filter.cpp
@@ -20,9 +20,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <cstddef>
 #include <fstream>
-#include <iostream>
 #include <string>
 #include <utility>
 #include <vector>
@@ -35,10 +33,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/io/writer.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm.hpp>
-#include <osmium/osm/types_from_string.hpp>
 #include <osmium/tags/taglist.hpp>
 #include <osmium/util/progress_bar.hpp>
-#include <osmium/util/string.hpp>
 #include <osmium/util/verbose_output.hpp>
 
 #include "command_tags_filter.hpp"
@@ -57,73 +53,9 @@ void CommandTagsFilter::add_filter(osmium::osm_entity_bits::type entities, const
     }
 }
 
-void strip_whitespace(std::string& string) {
-    while (!string.empty() && string.back() == ' ') {
-        string.pop_back();
-    }
-
-    const auto pos = string.find_first_not_of(' ');
-    if (pos != std::string::npos) {
-        string.erase(0, pos);
-    }
-}
-
-osmium::StringMatcher get_matcher(std::string string) {
-    strip_whitespace(string);
-
-    if (string.size() == 1 && string.front() == '*') {
-        return osmium::StringMatcher::always_true{};
-    }
-
-    if (string.empty() || (string.back() != '*' && string.front() != '*')) {
-        if (string.find(',') == std::string::npos) {
-            return osmium::StringMatcher::equal{string};
-        }
-        auto sstrings = osmium::split_string(string, ',');
-        for (auto& s : sstrings) {
-            strip_whitespace(s);
-        }
-        return osmium::StringMatcher::list{sstrings};
-    }
-
-    auto s = string;
-
-    if (s.back() == '*' && s.front() != '*') {
-        s.pop_back();
-        return osmium::StringMatcher::prefix{s};
-    }
-
-    if (s.front() == '*') {
-        s.erase(0, 1);
-    }
-
-    if (!s.empty() && s.back() == '*') {
-        s.pop_back();
-    }
-
-    return osmium::StringMatcher::substring{s};
-}
-
 void CommandTagsFilter::parse_and_add_expression(const std::string& expression) {
     const auto p = get_filter_expression(expression);
-    std::string key = p.second;
-
-    const auto op_pos = key.find('=');
-    if (op_pos == std::string::npos) {
-        add_filter(p.first, osmium::TagMatcher{get_matcher(key)});
-        return;
-    }
-
-    const auto value = key.substr(op_pos + 1);
-    key.erase(op_pos);
-
-    bool invert = false;
-    if (!key.empty() && key.back() == '!') {
-        key.pop_back();
-        invert = true;
-    }
-
-    add_filter(p.first, osmium::TagMatcher{get_matcher(key), get_matcher(value), invert});
+    add_filter(p.first, get_tag_matcher(p.second));
 }
 
 void CommandTagsFilter::read_expressions_file(const std::string& file_name) {
diff --git a/src/command_time_filter.cpp b/src/command_time_filter.cpp
index 3edf370..4fba1d1 100644
--- a/src/command_time_filter.cpp
+++ b/src/command_time_filter.cpp
@@ -38,6 +38,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/osm/diff_object.hpp>
 #include <osmium/osm/entity_bits.hpp>
 #include <osmium/osm/object.hpp>
+#include <osmium/osm/timestamp.hpp>
 #include <osmium/util/verbose_output.hpp>
 
 #include "command_time_filter.hpp"
diff --git a/src/commands.cpp b/src/commands.cpp
index 916e59f..bd16749 100644
--- a/src/commands.cpp
+++ b/src/commands.cpp
@@ -6,6 +6,7 @@
 #include "command_check_refs.hpp"
 #include "command_derive_changes.hpp"
 #include "command_diff.hpp"
+#include "command_export.hpp"
 #include "command_extract.hpp"
 #include "command_fileinfo.hpp"
 #include "command_getid.hpp"
@@ -47,6 +48,10 @@ void register_commands() {
         return new CommandDiff();
     });
 
+    CommandFactory::add("export", "Export OSM data", []() {
+        return new CommandExport();
+    });
+
     CommandFactory::add("extract", "Create geographic extract", []() {
         return new CommandExtract();
     });
diff --git a/src/command_show.hpp b/src/export/export_format.hpp
similarity index 50%
copy from src/command_show.hpp
copy to src/export/export_format.hpp
index d3624db..dbe2aed 100644
--- a/src/command_show.hpp
+++ b/src/export/export_format.hpp
@@ -1,5 +1,5 @@
-#ifndef COMMAND_SHOW_HPP
-#define COMMAND_SHOW_HPP
+#ifndef EXPORT_EXPORT_FORMAT_HPP
+#define EXPORT_EXPORT_FORMAT_HPP
 
 /*
 
@@ -23,40 +23,47 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <string>
-#include <vector>
+#include <cstdint>
 
-#include <osmium/io/header.hpp>
+#include <osmium/fwd.hpp>
+#include <osmium/io/writer_options.hpp>
 
-#include "cmd.hpp" // IWYU pragma: export
+#include "options.hpp"
 
-class CommandShow : public Command, public with_single_osm_input {
+class ExportFormat {
 
-    std::string m_output_format{"debug,color=true"};
-    std::string m_pager;
-    bool m_color_output;
+    const options_type& m_options;
 
-    void setup_pager_from_env() noexcept;
+protected:
+
+    std::uint64_t m_count;
+
+    explicit ExportFormat(const options_type& options) :
+        m_options(options),
+        m_count(0) {
+    }
 
 public:
 
-    CommandShow() = default;
+    const options_type& options() const noexcept {
+         return m_options;
+    }
+
+    std::uint64_t count() const noexcept {
+        return m_count;
+    }
 
-    bool setup(const std::vector<std::string>& arguments) override final;
+    virtual ~ExportFormat() = default;
 
-    void show_arguments() override final;
+    virtual void node(const osmium::Node&) = 0;
 
-    bool run() override final;
+    virtual void way(const osmium::Way&) = 0;
 
-    const char* name() const noexcept override final {
-        return "show";
-    }
+    virtual void area(const osmium::Area&) = 0;
 
-    const char* synopsis() const noexcept override final {
-        return "osmium show [OPTIONS] OSM-FILE";
-    }
+    virtual void close() = 0;
 
-}; // class CommandShow
+}; // class ExportFormat
 
 
-#endif // COMMAND_SHOW_HPP
+#endif // EXPORT_EXPORT_FORMAT_HPP
diff --git a/src/export/export_format_json.cpp b/src/export/export_format_json.cpp
new file mode 100644
index 0000000..6069f89
--- /dev/null
+++ b/src/export/export_format_json.cpp
@@ -0,0 +1,219 @@
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <osmium/io/detail/read_write.hpp>
+
+#include "export_format_json.hpp"
+
+static constexpr const std::size_t initial_buffer_size = 1024 * 1024;
+static constexpr const std::size_t flush_buffer_size   =  800 * 1024;
+
+static void add_to_stream(rapidjson::StringBuffer& stream, const char* s) {
+    while (*s) {
+        stream.Put(*s++);
+    }
+}
+
+ExportFormatJSON::ExportFormatJSON(const std::string& output_format,
+                                   const std::string& output_filename,
+                                   osmium::io::overwrite overwrite,
+                                   osmium::io::fsync fsync,
+                                   const options_type& options) :
+    ExportFormat(options),
+    m_fd(osmium::io::detail::open_for_writing(output_filename, overwrite)),
+    m_fsync(fsync),
+    m_text_sequence_format(output_format == "geojsonseq"),
+    m_with_record_separator(m_text_sequence_format && options.print_record_separator),
+    m_stream(),
+    m_committed_size(0),
+    m_writer(m_stream),
+    m_factory(m_writer) {
+    m_stream.Reserve(initial_buffer_size);
+    if (!m_text_sequence_format) {
+        add_to_stream(m_stream, "{\"type\":\"FeatureCollection\",\"features\":[\n");
+    }
+    m_committed_size = m_stream.GetSize();
+}
+
+void ExportFormatJSON::flush_to_output() {
+    osmium::io::detail::reliable_write(m_fd, m_stream.GetString(), m_stream.GetSize());
+    m_stream.Clear();
+    m_committed_size = 0;
+}
+
+void ExportFormatJSON::start_feature(const std::string& prefix, osmium::object_id_type id) {
+    rollback_uncomitted();
+
+    if (m_count > 0) {
+        if (!m_text_sequence_format) {
+            m_stream.Put(',');
+        }
+        m_stream.Put('\n');
+    }
+    m_writer.Reset(m_stream);
+
+    if (m_with_record_separator) {
+        m_stream.Put(0x1e);
+    }
+    m_writer.StartObject(); // start feature
+    m_writer.Key("type");
+    m_writer.String("Feature");
+
+    if (options().unique_id == unique_id_type::counter) {
+        m_writer.Key("id");
+        m_writer.Int64(m_count + 1);
+    } else if (options().unique_id == unique_id_type::type_id) {
+        m_writer.Key("id");
+        m_writer.String(prefix + std::to_string(id));
+    }
+}
+
+void ExportFormatJSON::add_attributes(const osmium::OSMObject& object) {
+    if (!options().type.empty()) {
+        m_writer.String(options().type);
+        if (object.type() == osmium::item_type::area) {
+            if (static_cast<const osmium::Area&>(object).from_way()) {
+                m_writer.String("way");
+            } else {
+                m_writer.String("relation");
+            }
+        } else {
+            m_writer.String(osmium::item_type_to_name(object.type()));
+        }
+    }
+
+    if (!options().id.empty()) {
+        m_writer.String(options().id);
+        m_writer.Int64(object.type() == osmium::item_type::area ? osmium::area_id_to_object_id(object.id()) : object.id());
+    }
+
+    if (!options().version.empty()) {
+        m_writer.String(options().version);
+        m_writer.Int(object.version());
+    }
+
+    if (!options().changeset.empty()) {
+        m_writer.String(options().changeset);
+        m_writer.Int(object.changeset());
+    }
+
+    if (!options().uid.empty()) {
+        m_writer.String(options().uid);
+        m_writer.Int(object.uid());
+    }
+
+    if (!options().user.empty()) {
+        m_writer.String(options().user);
+        m_writer.String(object.user());
+    }
+
+    if (!options().timestamp.empty()) {
+        m_writer.String(options().timestamp);
+        m_writer.Int(object.timestamp().seconds_since_epoch());
+    }
+
+    if (!options().way_nodes.empty() && object.type() == osmium::item_type::way) {
+        m_writer.String(options().way_nodes);
+        m_writer.StartArray();
+        for (const auto& nr : static_cast<const osmium::Way&>(object).nodes()) {
+            m_writer.Int64(nr.ref());
+        }
+        m_writer.EndArray();
+    }
+}
+
+bool ExportFormatJSON::add_tags(const osmium::OSMObject& object) {
+    bool has_tags = false;
+
+    for (const auto& tag : object.tags()) {
+        if (options().tags_filter(tag)) {
+            has_tags = true;
+            m_writer.String(tag.key());
+            m_writer.String(tag.value());
+        }
+    }
+
+    return has_tags;
+}
+
+void ExportFormatJSON::finish_feature(const osmium::OSMObject& object) {
+    m_writer.Key("properties");
+    m_writer.StartObject(); // start properties
+
+    add_attributes(object);
+
+    if (add_tags(object) || options().keep_untagged) {
+        m_writer.EndObject(); // end properties
+        m_writer.EndObject(); // end feature
+
+        m_committed_size = m_stream.GetSize();
+        ++m_count;
+
+        if (m_stream.GetSize() > flush_buffer_size) {
+            flush_to_output();
+        }
+    }
+}
+
+void ExportFormatJSON::node(const osmium::Node& node) {
+    start_feature("n", node.id());
+    m_factory.create_point(node);
+    finish_feature(node);
+}
+
+void ExportFormatJSON::way(const osmium::Way& way) {
+    start_feature("w", way.id());
+    m_factory.create_linestring(way);
+    finish_feature(way);
+}
+
+void ExportFormatJSON::area(const osmium::Area& area) {
+    start_feature("a", area.id());
+    m_factory.create_multipolygon(area);
+    finish_feature(area);
+}
+
+void ExportFormatJSON::rollback_uncomitted() {
+    const auto uncommitted_size = m_stream.GetSize() - m_committed_size;
+    if (uncommitted_size != 0) {
+        m_stream.Pop(uncommitted_size);
+    }
+}
+
+void ExportFormatJSON::close() {
+    if (m_fd > 0) {
+        rollback_uncomitted();
+
+        add_to_stream(m_stream, "\n");
+        if (!m_text_sequence_format) {
+            add_to_stream(m_stream, "]}\n");
+        }
+
+        flush_to_output();
+        if (m_fsync == osmium::io::fsync::yes) {
+            osmium::io::detail::reliable_fsync(m_fd);
+        }
+        ::close(m_fd);
+        m_fd = -1;
+    }
+}
+
diff --git a/src/export/export_format_json.hpp b/src/export/export_format_json.hpp
new file mode 100644
index 0000000..a62c843
--- /dev/null
+++ b/src/export/export_format_json.hpp
@@ -0,0 +1,87 @@
+#ifndef EXPORT_JSON_HANDLER
+#define EXPORT_JSON_HANDLER
+
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <string>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#ifndef RAPIDJSON_HAS_STDSTRING
+# define RAPIDJSON_HAS_STDSTRING 1
+#endif
+#include <rapidjson/writer.h>
+#include <rapidjson/stringbuffer.h>
+#pragma GCC diagnostic pop
+
+#include <osmium/fwd.hpp>
+#include <osmium/geom/rapid_geojson.hpp>
+#include <osmium/io/writer_options.hpp>
+
+#include "export_format.hpp"
+
+using writer_type = rapidjson::Writer<rapidjson::StringBuffer>;
+
+class ExportFormatJSON : public ExportFormat {
+
+    int m_fd;
+    osmium::io::fsync m_fsync;
+    bool m_text_sequence_format;
+    bool m_with_record_separator;
+    rapidjson::StringBuffer m_stream;
+    std::size_t m_committed_size;
+    writer_type m_writer;
+    osmium::geom::RapidGeoJSONFactory<writer_type> m_factory;
+
+    void flush_to_output();
+
+    void rollback_uncomitted();
+
+    void start_feature(const std::string& prefix, osmium::object_id_type id);
+    void add_attributes(const osmium::OSMObject& object);
+    bool add_tags(const osmium::OSMObject& object);
+    void finish_feature(const osmium::OSMObject& object);
+
+public:
+
+    ExportFormatJSON(const std::string& output_format,
+                     const std::string& output_filename,
+                     osmium::io::overwrite overwrite,
+                     osmium::io::fsync fsync,
+                     const options_type& options);
+
+    ~ExportFormatJSON() override {
+        close();
+    }
+
+    void node(const osmium::Node& node) override;
+
+    void way(const osmium::Way& way) override;
+
+    void area(const osmium::Area& area) override;
+
+    void close() override;
+
+}; // class ExportFormatJSON
+
+#endif // EXPORT_JSON_HANDLER
diff --git a/src/export/export_format_text.cpp b/src/export/export_format_text.cpp
new file mode 100644
index 0000000..550b5bd
--- /dev/null
+++ b/src/export/export_format_text.cpp
@@ -0,0 +1,200 @@
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <osmium/io/detail/read_write.hpp>
+#include <osmium/io/detail/string_util.hpp>
+
+#include "export_format_text.hpp"
+
+static constexpr const std::size_t initial_buffer_size = 1024 * 1024;
+static constexpr const std::size_t flush_buffer_size   =  800 * 1024;
+
+ExportFormatText::ExportFormatText(const std::string& /*output_format*/,
+                                   const std::string& output_filename,
+                                   osmium::io::overwrite overwrite,
+                                   osmium::io::fsync fsync,
+                                   const options_type& options) :
+    ExportFormat(options),
+    m_factory(),
+    m_buffer(),
+    m_commit_size(0),
+    m_fd(osmium::io::detail::open_for_writing(output_filename, overwrite)),
+    m_fsync(fsync) {
+    m_buffer.reserve(initial_buffer_size);
+}
+
+void ExportFormatText::flush_to_output() {
+    osmium::io::detail::reliable_write(m_fd, m_buffer.data(), m_buffer.size());
+    m_buffer.clear();
+    m_commit_size = 0;
+}
+
+void ExportFormatText::start_feature(char type, osmium::object_id_type id) {
+    m_buffer.resize(m_commit_size);
+    if (options().unique_id == unique_id_type::counter) {
+        m_buffer.append(std::to_string(m_count + 1));
+        m_buffer.append(1, ' ');
+    } else if (options().unique_id == unique_id_type::type_id) {
+        m_buffer.append(1, type);
+        m_buffer.append(std::to_string(id));
+        m_buffer.append(1, ' ');
+    }
+}
+
+void ExportFormatText::add_attributes(const osmium::OSMObject& object) {
+    if (!options().type.empty()) {
+        m_buffer.append(options().type);
+        m_buffer.append(1, '=');
+        if (object.type() == osmium::item_type::area) {
+            if (static_cast<const osmium::Area&>(object).from_way()) {
+                m_buffer.append("way");
+            } else {
+                m_buffer.append("relation");
+            }
+        } else {
+            m_buffer.append(osmium::item_type_to_name(object.type()));
+        }
+        m_buffer.append(1, ',');
+    }
+
+    if (!options().id.empty()) {
+        m_buffer.append(options().id);
+        m_buffer.append(1, '=');
+        m_buffer.append(std::to_string(object.type() == osmium::item_type::area ? osmium::area_id_to_object_id(object.id()) : object.id()));
+        m_buffer.append(1, ',');
+    }
+
+    if (!options().version.empty()) {
+        m_buffer.append(options().version);
+        m_buffer.append(1, '=');
+        m_buffer.append(std::to_string(object.version()));
+        m_buffer.append(1, ',');
+    }
+
+    if (!options().changeset.empty()) {
+        m_buffer.append(options().changeset);
+        m_buffer.append(1, '=');
+        m_buffer.append(std::to_string(object.changeset()));
+        m_buffer.append(1, ',');
+    }
+
+    if (!options().uid.empty()) {
+        m_buffer.append(options().uid);
+        m_buffer.append(1, '=');
+        m_buffer.append(std::to_string(object.uid()));
+        m_buffer.append(1, ',');
+    }
+
+    if (!options().user.empty()) {
+        m_buffer.append(options().user);
+        m_buffer.append(1, '=');
+        m_buffer.append(object.user());
+        m_buffer.append(1, ',');
+    }
+
+    if (!options().timestamp.empty()) {
+        m_buffer.append(options().timestamp);
+        m_buffer.append(1, '=');
+        m_buffer.append(std::to_string(object.timestamp().seconds_since_epoch()));
+        m_buffer.append(1, ',');
+    }
+
+    if (!options().way_nodes.empty() && object.type() == osmium::item_type::way) {
+        m_buffer.append(options().way_nodes);
+        m_buffer.append(1, '=');
+        for (const auto& nr : static_cast<const osmium::Way&>(object).nodes()) {
+            m_buffer.append(std::to_string(nr.ref()));
+            m_buffer.append(1, '/');
+        }
+        if (m_buffer.back() == '/') {
+            m_buffer.resize(m_buffer.size() - 1);
+        }
+    }
+}
+
+bool ExportFormatText::add_tags(const osmium::OSMObject& object) {
+    bool has_tags = false;
+
+    for (const auto& tag : object.tags()) {
+        if (options().tags_filter(tag)) {
+            has_tags = true;
+            osmium::io::detail::append_utf8_encoded_string(m_buffer, tag.key());
+            m_buffer.append(1, '=');
+            osmium::io::detail::append_utf8_encoded_string(m_buffer, tag.value());
+            m_buffer.append(1, ',');
+        }
+    }
+
+    return has_tags;
+}
+
+void ExportFormatText::finish_feature(const osmium::OSMObject& object) {
+    m_buffer.append(1, ' ');
+
+    add_attributes(object);
+
+    if (add_tags(object) || options().keep_untagged) {
+        if (m_buffer.back() == ',') {
+            m_buffer.back() = '\n';
+        } else {
+            m_buffer.append(1, '\n');
+        }
+
+        m_commit_size = m_buffer.size();
+
+        ++m_count;
+
+        if (m_buffer.size() > flush_buffer_size) {
+            flush_to_output();
+        }
+    }
+}
+
+void ExportFormatText::node(const osmium::Node& node) {
+    start_feature('n', node.id());
+    m_buffer.append(m_factory.create_point(node));
+    finish_feature(node);
+}
+
+void ExportFormatText::way(const osmium::Way& way) {
+    start_feature('w', way.id());
+    m_buffer.append(m_factory.create_linestring(way));
+    finish_feature(way);
+}
+
+void ExportFormatText::area(const osmium::Area& area) {
+    start_feature('a', area.id());
+    m_buffer.append(m_factory.create_multipolygon(area));
+    finish_feature(area);
+}
+
+void ExportFormatText::close() {
+    if (m_fd > 0) {
+        flush_to_output();
+        if (m_fsync == osmium::io::fsync::yes) {
+            osmium::io::detail::reliable_fsync(m_fd);
+        }
+        ::close(m_fd);
+        m_fd = -1;
+    }
+}
+
diff --git a/src/export/export_format_text.hpp b/src/export/export_format_text.hpp
new file mode 100644
index 0000000..b054929
--- /dev/null
+++ b/src/export/export_format_text.hpp
@@ -0,0 +1,71 @@
+#ifndef EXPORT_TEXT_HANDLER
+#define EXPORT_TEXT_HANDLER
+
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <string>
+
+#include <osmium/fwd.hpp>
+#include <osmium/geom/wkt.hpp>
+#include <osmium/io/writer_options.hpp>
+
+#include "export_format.hpp"
+
+class ExportFormatText : public ExportFormat {
+
+    osmium::geom::WKTFactory<> m_factory;
+    std::string m_buffer;
+    std::size_t m_commit_size;
+    int m_fd;
+    osmium::io::fsync m_fsync;
+
+    void flush_to_output();
+
+    void start_feature(char type, osmium::object_id_type id);
+    void add_attributes(const osmium::OSMObject& object);
+    bool add_tags(const osmium::OSMObject& object);
+    void finish_feature(const osmium::OSMObject& object);
+
+public:
+
+    ExportFormatText(const std::string& output_format,
+                     const std::string& output_filename,
+                     osmium::io::overwrite overwrite,
+                     osmium::io::fsync fsync,
+                     const options_type& options);
+
+    ~ExportFormatText() override {
+        close();
+    }
+
+    void node(const osmium::Node& node) override;
+
+    void way(const osmium::Way& way) override;
+
+    void area(const osmium::Area& area) override;
+
+    void close() override;
+
+}; // class ExportFormatText
+
+#endif // EXPORT_TEXT_HANDLER
diff --git a/src/export/export_handler.cpp b/src/export/export_handler.cpp
new file mode 100644
index 0000000..960e34d
--- /dev/null
+++ b/src/export/export_handler.cpp
@@ -0,0 +1,127 @@
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <cstring>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <osmium/geom/factory.hpp>
+#include <osmium/osm.hpp>
+#include <osmium/osm/entity_bits.hpp>
+#include <osmium/tags/taglist.hpp>
+
+#include "../exception.hpp"
+#include "../util.hpp"
+#include "export_handler.hpp"
+
+static bool check_filter(const osmium::TagList& tags, const char* area_tag_value, const osmium::TagsFilter& filter) noexcept {
+    const char* area_tag = tags.get_value_by_key("area");
+
+    if (area_tag) {
+        // has "area" tag and check that it does NOT have the area_tag_value
+        return std::strcmp(area_tag, area_tag_value);
+    }
+
+    return osmium::tags::match_any_of(tags, filter);
+}
+
+bool ExportHandler::is_linear(const osmium::Way& way) const noexcept {
+    return check_filter(way.tags(), "yes", m_linear_filter);
+}
+
+bool ExportHandler::is_area(const osmium::Area& area) const noexcept {
+    return check_filter(area.tags(), "no", m_area_filter);
+}
+
+ExportHandler::ExportHandler(std::unique_ptr<ExportFormat>&& handler,
+                             const std::vector<std::string>& linear_tags,
+                             const std::vector<std::string>& area_tags,
+                             bool show_errors,
+                             bool stop_on_error) :
+    m_handler(std::move(handler)),
+    m_linear_filter(true),
+    m_area_filter(true),
+    m_show_errors(show_errors),
+    m_stop_on_error(stop_on_error) {
+    if (!linear_tags.empty()) {
+        initialize_tags_filter(m_linear_filter, false, linear_tags);
+    }
+    if (!area_tags.empty()) {
+        initialize_tags_filter(m_area_filter, false, area_tags);
+    }
+}
+
+void ExportHandler::show_error(const std::runtime_error& error) {
+    if (m_stop_on_error) {
+        throw;
+    }
+    ++m_error_count;
+    if (m_show_errors) {
+        std::cerr << "Geometry error: " << error.what() << '\n';
+    }
+}
+
+void ExportHandler::node(const osmium::Node& node) {
+    if (node.tags().empty() && !m_handler->options().keep_untagged) {
+        return;
+    }
+
+    try {
+        m_handler->node(node);
+    } catch (const osmium::geometry_error& e) {
+        show_error(e);
+    } catch (const osmium::invalid_location& e) {
+        show_error(e);
+    }
+}
+
+void ExportHandler::way(const osmium::Way& way) {
+    if (way.nodes().size() <= 1) {
+        return;
+    }
+
+    if (!way.is_closed() || is_linear(way)) {
+        try {
+            m_handler->way(way);
+        } catch (const osmium::geometry_error& e) {
+            show_error(e);
+        } catch (const osmium::invalid_location& e) {
+            show_error(e);
+        }
+    }
+}
+
+void ExportHandler::area(const osmium::Area& area) {
+    if (!area.from_way() || is_area(area)) {
+        try {
+            m_handler->area(area);
+        } catch (const osmium::geometry_error& e) {
+            show_error(e);
+        } catch (const osmium::invalid_location& e) {
+            show_error(e);
+        }
+    }
+}
+
diff --git a/src/export/export_handler.hpp b/src/export/export_handler.hpp
new file mode 100644
index 0000000..a163b45
--- /dev/null
+++ b/src/export/export_handler.hpp
@@ -0,0 +1,82 @@
+#ifndef EXPORT_EXPORT_HANDLER_HPP
+#define EXPORT_EXPORT_HANDLER_HPP
+
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <osmium/fwd.hpp>
+#include <osmium/handler.hpp>
+#include <osmium/io/writer_options.hpp>
+#include <osmium/osm/entity_bits.hpp>
+#include <osmium/tags/tags_filter.hpp>
+
+#include "export_format.hpp"
+
+class ExportHandler : public osmium::handler::Handler {
+
+    std::unique_ptr<ExportFormat> m_handler;
+    osmium::TagsFilter m_linear_filter;
+    osmium::TagsFilter m_area_filter;
+    uint64_t m_error_count = 0;
+    bool m_show_errors;
+    bool m_stop_on_error;
+
+    bool is_linear(const osmium::Way& way) const noexcept;
+
+    bool is_area(const osmium::Area& area) const noexcept;
+
+    void show_error(const std::runtime_error& error);
+
+public:
+
+    ExportHandler(std::unique_ptr<ExportFormat>&& handler,
+                  const std::vector<std::string>& linear_tags,
+                  const std::vector<std::string>& area_tags,
+                  bool show_errors,
+                  bool stop_on_error);
+
+    void node(const osmium::Node& node);
+
+    void way(const osmium::Way& way);
+
+    void area(const osmium::Area& area);
+
+    void close() const {
+        m_handler->close();
+    }
+
+    std::uint64_t count() const noexcept {
+        return m_handler->count();
+    }
+
+    std::uint64_t error_count() const noexcept {
+        return m_error_count;
+    }
+
+}; // class ExportHandler
+
+#endif // EXPORT_EXPORT_HANDLER_HPP
diff --git a/src/extract/extract_bbox.cpp b/src/export/options.hpp
similarity index 57%
copy from src/extract/extract_bbox.cpp
copy to src/export/options.hpp
index 69e09bf..1db0cb0 100644
--- a/src/extract/extract_bbox.cpp
+++ b/src/export/options.hpp
@@ -1,3 +1,6 @@
+#ifndef EXPORT_OPTIONS_HPP
+#define EXPORT_OPTIONS_HPP
+
 /*
 
 Osmium -- OpenStreetMap data manipulation command line tool
@@ -22,26 +25,29 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include <string>
 
-#include "extract_bbox.hpp"
+#include <osmium/tags/tags_filter.hpp>
 
-namespace osmium {
-    class Location;
-}
+enum class unique_id_type {
+    none    = 0,
+    counter = 1,
+    type_id = 2
+};
 
-bool ExtractBBox::contains(const osmium::Location& location) const noexcept {
-    return location.valid() && envelope().contains(location);
-}
+struct options_type {
+    osmium::TagsFilter tags_filter{true};
+    std::string type;
+    std::string id;
+    std::string version;
+    std::string changeset;
+    std::string timestamp;
+    std::string uid;
+    std::string user;
+    std::string way_nodes;
 
-const char* ExtractBBox::geometry_type() const noexcept {
-    return "bbox";
-}
+    unique_id_type unique_id = unique_id_type::none;
 
-std::string ExtractBBox::geometry_as_text() const {
-    std::string s{"BOX("};
-    envelope().bottom_left().as_string(std::back_inserter(s), ' ');
-    s += ',';
-    envelope().top_right().as_string(std::back_inserter(s), ' ');
-    s += ')';
-    return s;
-}
+    bool keep_untagged = false;
+    bool print_record_separator = true;
+};
 
+#endif // EXPORT_OPTIONS_HPP
diff --git a/src/extract/extract_bbox.cpp b/src/extract/extract_bbox.cpp
index 69e09bf..be1276b 100644
--- a/src/extract/extract_bbox.cpp
+++ b/src/extract/extract_bbox.cpp
@@ -22,11 +22,9 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include <string>
 
-#include "extract_bbox.hpp"
+#include <osmium/osm/location.hpp>
 
-namespace osmium {
-    class Location;
-}
+#include "extract_bbox.hpp"
 
 bool ExtractBBox::contains(const osmium::Location& location) const noexcept {
     return location.valid() && envelope().contains(location);
diff --git a/src/extract/extract_polygon.cpp b/src/extract/extract_polygon.cpp
index 43d178b..a474150 100644
--- a/src/extract/extract_polygon.cpp
+++ b/src/extract/extract_polygon.cpp
@@ -121,7 +121,7 @@ bool ExtractPolygon::contains(const osmium::Location& location) const noexcept {
         return false;
     }
 
-    size_t band = (location.y() - y_min()) / m_dy;
+    std::size_t band = (location.y() - y_min()) / m_dy;
     if (band >= m_bands.size()) {
         band = m_bands.size() - 1;
     }
diff --git a/src/extract/poly_file_parser.hpp b/src/extract/poly_file_parser.hpp
index 2558eeb..1bc1edf 100644
--- a/src/extract/poly_file_parser.hpp
+++ b/src/extract/poly_file_parser.hpp
@@ -56,7 +56,7 @@ class PolyFileParser {
     std::unique_ptr<osmium::builder::AreaBuilder> m_builder;
     std::string m_file_name;
     std::vector<std::string> m_data;
-    size_t m_line = 0;
+    std::size_t m_line = 0;
 
     void parse_ring();
     void parse_multipolygon();
diff --git a/src/extract/strategy_complete_ways.cpp b/src/extract/strategy_complete_ways.cpp
index 8dad999..dbbc36f 100644
--- a/src/extract/strategy_complete_ways.cpp
+++ b/src/extract/strategy_complete_ways.cpp
@@ -153,7 +153,7 @@ namespace strategy_complete_ways {
 
     void Strategy::run(osmium::util::VerboseOutput& vout, bool display_progress, const osmium::io::File& input_file) {
         vout << "Running 'complete_ways' strategy in two passes...\n";
-        const size_t file_size = osmium::util::file_size(input_file.filename());
+        const std::size_t file_size = osmium::util::file_size(input_file.filename());
         osmium::ProgressBar progress_bar{file_size * 2, display_progress};
 
         vout << "First pass...\n";
diff --git a/src/extract/strategy_complete_ways_with_history.cpp b/src/extract/strategy_complete_ways_with_history.cpp
index 44663b2..4d6f9af 100644
--- a/src/extract/strategy_complete_ways_with_history.cpp
+++ b/src/extract/strategy_complete_ways_with_history.cpp
@@ -159,7 +159,7 @@ namespace strategy_complete_ways_with_history {
 
     void Strategy::run(osmium::util::VerboseOutput& vout, bool display_progress, const osmium::io::File& input_file) {
         vout << "Running 'complete_ways' strategy in two passes...\n";
-        const size_t file_size = osmium::util::file_size(input_file.filename());
+        const std::size_t file_size = osmium::util::file_size(input_file.filename());
         osmium::ProgressBar progress_bar{file_size * 2, display_progress};
 
         vout << "First pass...\n";
diff --git a/src/extract/strategy_simple.cpp b/src/extract/strategy_simple.cpp
index 477ca95..bf58c36 100644
--- a/src/extract/strategy_simple.cpp
+++ b/src/extract/strategy_simple.cpp
@@ -105,7 +105,7 @@ namespace strategy_simple {
 
     void Strategy::run(osmium::util::VerboseOutput& vout, bool display_progress, const osmium::io::File& input_file) {
         vout << "Running 'simple' strategy in one pass...\n";
-        const size_t file_size = osmium::util::file_size(input_file.filename());
+        const std::size_t file_size = osmium::util::file_size(input_file.filename());
         osmium::ProgressBar progress_bar{file_size, display_progress};
 
         Pass1 pass1{*this};
diff --git a/src/extract/strategy_smart.cpp b/src/extract/strategy_smart.cpp
index dba3021..6824806 100644
--- a/src/extract/strategy_smart.cpp
+++ b/src/extract/strategy_smart.cpp
@@ -228,7 +228,7 @@ namespace strategy_smart {
 
     void Strategy::run(osmium::util::VerboseOutput& vout, bool display_progress, const osmium::io::File& input_file) {
         vout << "Running 'smart' strategy in three passes...\n";
-        const size_t file_size = osmium::util::file_size(input_file.filename());
+        const std::size_t file_size = osmium::util::file_size(input_file.filename());
         osmium::ProgressBar progress_bar{file_size * 3, display_progress};
 
         vout << "First pass...\n";
diff --git a/src/io.cpp b/src/io.cpp
index 6d3ff03..70925f0 100644
--- a/src/io.cpp
+++ b/src/io.cpp
@@ -20,7 +20,6 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <cstddef>
 #include <string>
 #include <vector>
 
@@ -31,7 +30,6 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/io/file.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/io/writer_options.hpp>
-#include <osmium/util/file.hpp>
 #include <osmium/util/verbose_output.hpp>
 
 #include "cmd.hpp"
diff --git a/src/main.cpp b/src/main.cpp
index 47afd27..ae3e327 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -36,6 +36,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <boost/program_options/errors.hpp>
 
 #include <osmium/handler/check_order.hpp>
+#include <osmium/geom/factory.hpp>
 
 #include "cmd.hpp"
 
@@ -133,6 +134,10 @@ int main(int argc, char *argv[]) {
             std::cerr << ". Try using --overwrite if you are sure you want to overwrite the file.";
         }
         std::cerr << '\n';
+    } catch (const osmium::geometry_error& e) {
+        std::cerr << "Geometry error: " << e.what() << '\n';
+    } catch (const osmium::invalid_location& e) {
+        std::cerr << "Geometry error: Invalid location. Usually this means a node was missing from the input data.\n";
     } catch (const std::exception& e) {
         std::cerr << e.what() << '\n';
     }
diff --git a/src/util.cpp b/src/util.cpp
index 7ec8f22..e64a160 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -20,12 +20,15 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
+#include <cstdlib>
 #include <iostream>
 #include <string>
 #include <vector>
 
 #include <osmium/io/file.hpp>
 #include <osmium/util/file.hpp>
+#include <osmium/util/string.hpp>
+#include <osmium/tags/tags_filter.hpp>
 
 #include "exception.hpp"
 #include "util.hpp"
@@ -110,3 +113,94 @@ std::pair<osmium::osm_entity_bits::type, std::string> get_filter_expression(cons
     return std::make_pair(entities, &str[pos]);
 }
 
+void strip_whitespace(std::string& string) {
+    while (!string.empty() && string.back() == ' ') {
+        string.pop_back();
+    }
+
+    const auto pos = string.find_first_not_of(' ');
+    if (pos != std::string::npos) {
+        string.erase(0, pos);
+    }
+}
+
+osmium::StringMatcher get_string_matcher(std::string string) {
+    strip_whitespace(string);
+
+    if (string.size() == 1 && string.front() == '*') {
+        return osmium::StringMatcher::always_true{};
+    }
+
+    if (string.empty() || (string.back() != '*' && string.front() != '*')) {
+        if (string.find(',') == std::string::npos) {
+            return osmium::StringMatcher::equal{string};
+        }
+        auto sstrings = osmium::split_string(string, ',');
+        for (auto& s : sstrings) {
+            strip_whitespace(s);
+        }
+        return osmium::StringMatcher::list{sstrings};
+    }
+
+    auto s = string;
+
+    if (s.back() == '*' && s.front() != '*') {
+        s.pop_back();
+        return osmium::StringMatcher::prefix{s};
+    }
+
+    if (s.front() == '*') {
+        s.erase(0, 1);
+    }
+
+    if (!s.empty() && s.back() == '*') {
+        s.pop_back();
+    }
+
+    return osmium::StringMatcher::substring{s};
+}
+
+osmium::TagMatcher get_tag_matcher(const std::string& expression) {
+    const auto op_pos = expression.find('=');
+    if (op_pos == std::string::npos) {
+        return osmium::TagMatcher{get_string_matcher(expression)};
+    }
+
+    auto key = expression.substr(0, op_pos);
+    const auto value = expression.substr(op_pos + 1);
+
+    bool invert = false;
+    if (!key.empty() && key.back() == '!') {
+        key.pop_back();
+        invert = true;
+    }
+
+    return osmium::TagMatcher{get_string_matcher(key), get_string_matcher(value), invert};
+}
+
+void initialize_tags_filter(osmium::TagsFilter& tags_filter, bool default_result, const std::vector<std::string>& strings) {
+    tags_filter.set_default_result(default_result);
+    for (const auto& str : strings) {
+        assert(!str.empty());
+        tags_filter.add_rule(!default_result, get_tag_matcher(str));
+    }
+}
+
+osmium::Box parse_bbox(const std::string& str, const std::string& option_name) {
+    const auto coordinates = osmium::split_string(str, ',');
+
+    if (coordinates.size() != 4) {
+        throw argument_error{std::string{"Need exactly four coordinates in "} + option_name + " option."};
+    }
+
+    const osmium::Location bottom_left{std::atof(coordinates[0].c_str()), std::atof(coordinates[1].c_str())};
+    const osmium::Location top_right{std::atof(coordinates[2].c_str()), std::atof(coordinates[3].c_str())};
+
+    if (bottom_left.x() < top_right.x() &&
+        bottom_left.y() < top_right.y()) {
+        return osmium::Box{bottom_left, top_right};
+    }
+
+    throw argument_error{std::string{"Need LEFT < RIGHT and BOTTOM < TOP in "} + option_name + " option."};
+}
+
diff --git a/src/util.hpp b/src/util.hpp
index 318cf90..bcf2878 100644
--- a/src/util.hpp
+++ b/src/util.hpp
@@ -27,11 +27,20 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <utility>
 #include <vector>
 
+#include <osmium/osm/box.hpp>
 #include <osmium/osm/entity_bits.hpp>
+#include <osmium/util/string_matcher.hpp>
+#include <osmium/tags/matcher.hpp>
 
-namespace osmium { namespace io {
-    class File;
-}}
+namespace osmium {
+
+    class TagsFilter;
+
+    namespace io {
+        class File;
+    }
+
+}
 
 std::string get_filename_suffix(const std::string& file_name);
 const char* yes_no(bool choice) noexcept;
@@ -40,5 +49,10 @@ void warning(const std::string& text);
 std::size_t file_size_sum(const std::vector<osmium::io::File>& files);
 osmium::osm_entity_bits::type get_types(const std::string& s);
 std::pair<osmium::osm_entity_bits::type, std::string> get_filter_expression(const std::string& s);
+void strip_whitespace(std::string& string);
+osmium::StringMatcher get_string_matcher(std::string string);
+osmium::TagMatcher get_tag_matcher(const std::string& expression);
+void initialize_tags_filter(osmium::TagsFilter& tags_filter, bool default_result, const std::vector<std::string>& strings);
+osmium::Box parse_bbox(const std::string& str, const std::string& option_name);
 
 #endif // UTIL_HPP
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 08e75e1..51b6f21 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -12,7 +12,7 @@ include_directories(../src/extract)
 include_directories(../include)
 
 file(GLOB ALL_UNIT_TESTS */test_setup.cpp */test_unit.cpp)
-file(GLOB ALL_COMMANDS ../src/command_*.cpp ../src/io.cpp ../src/cmd.cpp ../src/cmd_factory.cpp ../src/util.cpp ../src/extract/*.cpp)
+file(GLOB ALL_COMMANDS ../src/command_*.cpp ../src/io.cpp ../src/cmd.cpp ../src/cmd_factory.cpp ../src/util.cpp ../src/*/*.cpp)
 add_executable(unit_tests unit_tests.cpp ${ALL_COMMANDS} ${ALL_UNIT_TESTS} ${PROJECT_BINARY_DIR}/src/version.cpp)
 target_link_libraries(unit_tests ${Boost_LIBRARIES} ${OSMIUM_LIBRARIES})
 set_pthread_on_target(unit_tests)
@@ -82,6 +82,7 @@ check_cmd_help(changeset-filter)
 check_cmd_help(check-refs)
 check_cmd_help(derive-changes)
 check_cmd_help(diff)
+check_cmd_help(export)
 check_cmd_help(extract)
 check_cmd_help(fileinfo)
 check_cmd_help(getid)
diff --git a/test/export/CMakeLists.txt b/test/export/CMakeLists.txt
new file mode 100644
index 0000000..e33a60f
--- /dev/null
+++ b/test/export/CMakeLists.txt
@@ -0,0 +1,26 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake Config
+#
+#  Osmium Tool Tests - export
+#
+#-----------------------------------------------------------------------------
+
+function(check_export _name _options _input _output)
+    check_output(export ${_name} "export ${_options} export/${_input}" "export/${_output}")
+endfunction()
+
+check_export(geojson    "-f geojson"       input.osm output.geojson)
+check_export(geojsonseq "-f geojsonseq -r" input.osm output.geojsonseq)
+
+check_export(missing-node "-f geojson"  input-missing-node.osm output-missing-node.geojson)
+
+check_export(error-node "-f geojson -E" input-missing-node.osm none.geojson)
+set_tests_properties(export-error-node PROPERTIES WILL_FAIL true)
+
+check_export(invalid-area "-f geojson"  input-incomplete-relation.osm output-incomplete-relation.geojson)
+
+check_export(error-area "-f geojson -E" input-incomplete-relation.osm none.geojson)
+set_tests_properties(export-error-area PROPERTIES WILL_FAIL true)
+
+#-----------------------------------------------------------------------------
diff --git a/test/renumber/input-sorted.osm b/test/export/input-incomplete-relation.osm
similarity index 64%
copy from test/renumber/input-sorted.osm
copy to test/export/input-incomplete-relation.osm
index 521dc5d..83d7879 100644
--- a/test/renumber/input-sorted.osm
+++ b/test/export/input-incomplete-relation.osm
@@ -3,21 +3,29 @@
   <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
   <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
-  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1.5" lon="2">
+    <tag k="amenity" v="post_box"/>
+  </node>
   <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
     <nd ref="10"/>
     <nd ref="11"/>
     <nd ref="12"/>
-    <tag k="foo" v="bar"/>
+    <tag k="highway" v="track"/>
   </way>
   <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <nd ref="12"/>
+    <nd ref="10"/>
+    <nd ref="11"/>
     <nd ref="14"/>
-    <tag k="xyz" v="abc"/>
+    <tag k="barrier" v="fence"/>
+  </way>
+  <way id="22" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="14"/>
+    <nd ref="10"/>
   </way>
   <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <member type="node" ref="12" role="m1"/>
-    <member type="node" ref="13" role="s1"/>
-    <member type="way" ref="20" role="m2"/>
+    <member type="way" ref="21" role="outer"/>
+    <tag k="type" v="multipolygon"/>
+    <tag k="landuse" v="forest"/>
   </relation>
 </osm>
diff --git a/test/renumber/input-sorted.osm b/test/export/input-missing-node.osm
similarity index 59%
copy from test/renumber/input-sorted.osm
copy to test/export/input-missing-node.osm
index 521dc5d..a44a425 100644
--- a/test/renumber/input-sorted.osm
+++ b/test/export/input-missing-node.osm
@@ -2,22 +2,30 @@
 <osm version="0.6" upload="false" generator="testdata">
   <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
-  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
-  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1.5" lon="2">
+    <tag k="amenity" v="post_box"/>
+  </node>
   <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
     <nd ref="10"/>
     <nd ref="11"/>
     <nd ref="12"/>
-    <tag k="foo" v="bar"/>
+    <tag k="highway" v="track"/>
   </way>
   <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <nd ref="12"/>
+    <nd ref="10"/>
+    <nd ref="11"/>
     <nd ref="14"/>
-    <tag k="xyz" v="abc"/>
+    <tag k="barrier" v="fence"/>
+  </way>
+  <way id="22" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="14"/>
+    <nd ref="10"/>
   </way>
   <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <member type="node" ref="12" role="m1"/>
-    <member type="node" ref="13" role="s1"/>
-    <member type="way" ref="20" role="m2"/>
+    <member type="way" ref="21" role="outer"/>
+    <member type="way" ref="22" role="outer"/>
+    <tag k="type" v="multipolygon"/>
+    <tag k="landuse" v="forest"/>
   </relation>
 </osm>
diff --git a/test/renumber/input-sorted.osm b/test/export/input.osm
similarity index 62%
copy from test/renumber/input-sorted.osm
copy to test/export/input.osm
index 521dc5d..c79e4e5 100644
--- a/test/renumber/input-sorted.osm
+++ b/test/export/input.osm
@@ -3,21 +3,30 @@
   <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
   <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
-  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1.5" lon="2">
+    <tag k="amenity" v="post_box"/>
+  </node>
   <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
     <nd ref="10"/>
     <nd ref="11"/>
     <nd ref="12"/>
-    <tag k="foo" v="bar"/>
+    <tag k="highway" v="track"/>
   </way>
   <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <nd ref="12"/>
+    <nd ref="10"/>
+    <nd ref="11"/>
     <nd ref="14"/>
-    <tag k="xyz" v="abc"/>
+    <tag k="barrier" v="fence"/>
+  </way>
+  <way id="22" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="14"/>
+    <nd ref="10"/>
   </way>
   <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <member type="node" ref="12" role="m1"/>
-    <member type="node" ref="13" role="s1"/>
-    <member type="way" ref="20" role="m2"/>
+    <member type="way" ref="21" role="outer"/>
+    <member type="way" ref="22" role="outer"/>
+    <tag k="type" v="multipolygon"/>
+    <tag k="landuse" v="forest"/>
   </relation>
 </osm>
diff --git a/test/export/output-incomplete-relation.geojson b/test/export/output-incomplete-relation.geojson
new file mode 100644
index 0000000..00bd16c
--- /dev/null
+++ b/test/export/output-incomplete-relation.geojson
@@ -0,0 +1,5 @@
+{"type":"FeatureCollection","features":[
+{"type":"Feature","geometry":{"type":"Point","coordinates":[2.0,1.5]},"properties":{"amenity":"post_box"}},
+{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.0,1.0],[1.0,2.0],[1.0,3.0]]},"properties":{"highway":"track"}},
+{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.0,1.0],[1.0,2.0],[2.0,1.5]]},"properties":{"barrier":"fence"}}
+]}
diff --git a/test/export/output-missing-node.geojson b/test/export/output-missing-node.geojson
new file mode 100644
index 0000000..7494262
--- /dev/null
+++ b/test/export/output-missing-node.geojson
@@ -0,0 +1,5 @@
+{"type":"FeatureCollection","features":[
+{"type":"Feature","geometry":{"type":"Point","coordinates":[2.0,1.5]},"properties":{"amenity":"post_box"}},
+{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.0,1.0],[1.0,2.0],[2.0,1.5]]},"properties":{"barrier":"fence"}},
+{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[1.0,1.0],[2.0,1.5],[1.0,2.0],[1.0,1.0]]]]},"properties":{"landuse":"forest"}}
+]}
diff --git a/test/export/output.geojson b/test/export/output.geojson
new file mode 100644
index 0000000..2c9ce21
--- /dev/null
+++ b/test/export/output.geojson
@@ -0,0 +1,6 @@
+{"type":"FeatureCollection","features":[
+{"type":"Feature","geometry":{"type":"Point","coordinates":[2.0,1.5]},"properties":{"amenity":"post_box"}},
+{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.0,1.0],[1.0,2.0],[1.0,3.0]]},"properties":{"highway":"track"}},
+{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.0,1.0],[1.0,2.0],[2.0,1.5]]},"properties":{"barrier":"fence"}},
+{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[1.0,1.0],[2.0,1.5],[1.0,2.0],[1.0,1.0]]]]},"properties":{"landuse":"forest"}}
+]}
diff --git a/test/export/output.geojsonseq b/test/export/output.geojsonseq
new file mode 100644
index 0000000..809f5fd
--- /dev/null
+++ b/test/export/output.geojsonseq
@@ -0,0 +1,4 @@
+{"type":"Feature","geometry":{"type":"Point","coordinates":[2.0,1.5]},"properties":{"amenity":"post_box"}}
+{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.0,1.0],[1.0,2.0],[1.0,3.0]]},"properties":{"highway":"track"}}
+{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.0,1.0],[1.0,2.0],[2.0,1.5]]},"properties":{"barrier":"fence"}}
+{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[1.0,1.0],[2.0,1.5],[1.0,2.0],[1.0,1.0]]]]},"properties":{"landuse":"forest"}}
diff --git a/test/getid/CMakeLists.txt b/test/getid/CMakeLists.txt
index 100a985..502f698 100644
--- a/test/getid/CMakeLists.txt
+++ b/test/getid/CMakeLists.txt
@@ -15,7 +15,7 @@ function(check_getid_file _name _file _input _output)
 endfunction()
 
 check_getid(n input.osm output.osm)
-check_getid_file(file1 idfile input.osm output.osm)
+check_getid_file(file1 idfile input.osm output-file.osm)
 
 #-----------------------------------------------------------------------------
 
diff --git a/test/getid/idfile b/test/getid/idfile
index 4e5d94d..895c164 100644
--- a/test/getid/idfile
+++ b/test/getid/idfile
@@ -1,5 +1,9 @@
 n11
 
+    
 n12 foo
+ n10
+  n13
 # comment
-w21
+  # comment  
+w21   
diff --git a/test/renumber/input-sorted.osm b/test/getid/output-file.osm
similarity index 50%
copy from test/renumber/input-sorted.osm
copy to test/getid/output-file.osm
index 521dc5d..7b3e55d 100644
--- a/test/renumber/input-sorted.osm
+++ b/test/getid/output-file.osm
@@ -1,23 +1,12 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<osm version="0.6" upload="false" generator="testdata">
+<osm version="0.6" generator="test">
   <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
   <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
-  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
-  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <nd ref="10"/>
-    <nd ref="11"/>
-    <nd ref="12"/>
-    <tag k="foo" v="bar"/>
-  </way>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
   <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
     <nd ref="12"/>
-    <nd ref="14"/>
+    <nd ref="13"/>
     <tag k="xyz" v="abc"/>
   </way>
-  <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <member type="node" ref="12" role="m1"/>
-    <member type="node" ref="13" role="s1"/>
-    <member type="way" ref="20" role="m2"/>
-  </relation>
 </osm>
diff --git a/test/include/catch.hpp b/test/include/catch.hpp
index 6f9334b..7c351e9 100644
--- a/test/include/catch.hpp
+++ b/test/include/catch.hpp
@@ -1,6 +1,6 @@
 /*
- *  Catch v1.8.1
- *  Generated: 2017-03-01 16:04:19.016511
+ *  Catch v1.9.7
+ *  Generated: 2017-08-10 23:49:15.233907
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
  *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
@@ -40,11 +40,7 @@
 #elif defined __GNUC__
 #    pragma GCC diagnostic ignored "-Wvariadic-macros"
 #    pragma GCC diagnostic ignored "-Wunused-variable"
-
-     // For newer version we can use __Pragma to disable the warnings locally
-#    if __GNUC__ == 4 && __GNUC_MINOR__ >= 4 && __GNUC_MINOR__ <= 7
-#        pragma GCC diagnostic ignored "-Wparentheses"
-#    endif
+#    pragma GCC diagnostic ignored "-Wparentheses"
 
 #    pragma GCC diagnostic push
 #    pragma GCC diagnostic ignored "-Wpadded"
@@ -124,6 +120,12 @@
 #  endif
 
 #   if defined(CATCH_CPP11_OR_GREATER)
+#       define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
 #       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
             _Pragma( "clang diagnostic push" ) \
             _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
@@ -134,13 +136,24 @@
 #endif // __clang__
 
 ////////////////////////////////////////////////////////////////////////////////
-// Cygwin
-#ifdef __CYGWIN__
+// We know some environments not to support full POSIX signals
+#if defined(__CYGWIN__) || defined(__QNX__)
 
 #   if !defined(CATCH_CONFIG_POSIX_SIGNALS)
 #       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
 #   endif
 
+#endif
+
+#ifdef __OS400__
+#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#       define CATCH_CONFIG_COLOUR_NONE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Cygwin
+#ifdef __CYGWIN__
+
 // Required for some versions of Cygwin to declare gettimeofday
 // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
 #   define _BSD_SOURCE
@@ -169,22 +182,10 @@
 // GCC
 #ifdef __GNUC__
 
-#   if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
-#       define CATCH_GCC_HAS_NEW_PRAGMA
-#   endif
-
 #   if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
 #       define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #   endif
 
-#   if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_GCC_HAS_NEW_PRAGMA)
-#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
-            _Pragma( "GCC diagnostic push" ) \
-            _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" )
-#       define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
-            _Pragma( "GCC diagnostic pop" )
-#   endif
-
 // - otherwise more recent versions define __cplusplus >= 201103L
 // and will get picked up below
 
@@ -224,7 +225,7 @@
 
 // Use __COUNTER__ if the compiler supports it
 #if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \
-    ( defined __GNUC__  && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \
+    ( defined __GNUC__  && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \
     ( defined __clang__ && __clang_major__ >= 3 )
 
 #define CATCH_INTERNAL_CONFIG_COUNTER
@@ -332,6 +333,10 @@
 #   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
 #   define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
 #endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
+#endif
 
 // noexcept support:
 #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT)
@@ -414,14 +419,14 @@ namespace Catch {
     };
 
     template<typename ContainerT>
-    inline void deleteAll( ContainerT& container ) {
+    void deleteAll( ContainerT& container ) {
         typename ContainerT::const_iterator it = container.begin();
         typename ContainerT::const_iterator itEnd = container.end();
         for(; it != itEnd; ++it )
             delete *it;
     }
     template<typename AssociativeContainerT>
-    inline void deleteAllValues( AssociativeContainerT& container ) {
+    void deleteAllValues( AssociativeContainerT& container ) {
         typename AssociativeContainerT::const_iterator it = container.begin();
         typename AssociativeContainerT::const_iterator itEnd = container.end();
         for(; it != itEnd; ++it )
@@ -501,7 +506,6 @@ namespace Catch {
     {
     public:
         NotImplementedException( SourceLineInfo const& lineInfo );
-        NotImplementedException( NotImplementedException const& ) {}
 
         virtual ~NotImplementedException() CATCH_NOEXCEPT {}
 
@@ -770,59 +774,76 @@ void registerTestCaseFunction
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
         static void TestName(); \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         static void TestName()
     #define INTERNAL_CATCH_TESTCASE( ... ) \
         INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); }
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
         namespace{ \
             struct TestName : ClassName{ \
                 void test(); \
             }; \
-            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \
         } \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         void TestName::test()
     #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
         INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
-        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) );
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 
 #else
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \
         static void TestName(); \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         static void TestName()
     #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \
         INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); }
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
         namespace{ \
             struct TestCaseName : ClassName{ \
                 void test(); \
             }; \
-            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \
         } \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         void TestCaseName::test()
     #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\
         INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \
-        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) );
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
+
 #endif
 
 // #included from: internal/catch_capture.hpp
@@ -910,22 +931,24 @@ namespace Catch {
         template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& );
         template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& );
 
-	private:
-		DecomposedExpression& operator = (DecomposedExpression const&);
+    private:
+        DecomposedExpression& operator = (DecomposedExpression const&);
     };
 
     struct AssertionInfo
     {
-        AssertionInfo() {}
-        AssertionInfo(  std::string const& _macroName,
+        AssertionInfo();
+        AssertionInfo(  char const * _macroName,
                         SourceLineInfo const& _lineInfo,
-                        std::string const& _capturedExpression,
-                        ResultDisposition::Flags _resultDisposition );
+                        char const * _capturedExpression,
+                        ResultDisposition::Flags _resultDisposition,
+                        char const * _secondArg = "");
 
-        std::string macroName;
+        char const * macroName;
         SourceLineInfo lineInfo;
-        std::string capturedExpression;
+        char const * capturedExpression;
         ResultDisposition::Flags resultDisposition;
+        char const * secondArg;
     };
 
     struct AssertionResultData
@@ -1021,16 +1044,24 @@ namespace Matchers {
             }
 
         protected:
+            virtual ~MatcherUntypedBase();
             virtual std::string describe() const = 0;
             mutable std::string m_cachedToString;
         private:
             MatcherUntypedBase& operator = ( MatcherUntypedBase const& );
         };
 
-        template<typename ObjectT, typename ComparatorT = ObjectT>
-        struct MatcherBase : MatcherUntypedBase {
-
+        template<typename ObjectT>
+        struct MatcherMethod {
             virtual bool match( ObjectT const& arg ) const = 0;
+        };
+        template<typename PtrT>
+        struct MatcherMethod<PtrT*> {
+            virtual bool match( PtrT* arg ) const = 0;
+        };
+
+        template<typename ObjectT, typename ComparatorT = ObjectT>
+        struct MatcherBase : MatcherUntypedBase, MatcherMethod<ObjectT> {
 
             MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const;
             MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const;
@@ -1131,23 +1162,23 @@ namespace Matchers {
     // This allows the types to be inferred
     // - deprecated: prefer ||, && and !
     template<typename T>
-    inline Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) {
+    Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) {
         return Impl::MatchNotOf<T>( underlyingMatcher );
     }
     template<typename T>
-    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
+    Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
         return Impl::MatchAllOf<T>() && m1 && m2;
     }
     template<typename T>
-    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
+    Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
         return Impl::MatchAllOf<T>() && m1 && m2 && m3;
     }
     template<typename T>
-    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
+    Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
         return Impl::MatchAnyOf<T>() || m1 || m2;
     }
     template<typename T>
-    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
+    Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
         return Impl::MatchAnyOf<T>() || m1 || m2 || m3;
     }
 
@@ -1184,6 +1215,7 @@ namespace Catch {
                         char const* capturedExpression,
                         ResultDisposition::Flags resultDisposition,
                         char const* secondArg = "" );
+        ~ResultBuilder();
 
         template<typename T>
         ExpressionLhs<T const&> operator <= ( T const& operand );
@@ -1191,7 +1223,7 @@ namespace Catch {
 
         template<typename T>
         ResultBuilder& operator << ( T const& value ) {
-            m_stream.oss << value;
+            stream().oss << value;
             return *this;
         }
 
@@ -1218,13 +1250,33 @@ namespace Catch {
         template<typename ArgT, typename MatcherT>
         void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString );
 
+        void setExceptionGuard();
+        void unsetExceptionGuard();
+
     private:
         AssertionInfo m_assertionInfo;
         AssertionResultData m_data;
-        CopyableStream m_stream;
+
+        CopyableStream &stream()
+        {
+            if(!m_usedStream)
+            {
+                m_usedStream = true;
+                m_stream().oss.str("");
+            }
+            return m_stream();
+        }
+
+        static CopyableStream &m_stream()
+        {
+            static CopyableStream s;
+            return s;
+        }
 
         bool m_shouldDebugBreak;
         bool m_shouldThrow;
+        bool m_guardException;
+        bool m_usedStream;
     };
 
 } // namespace Catch
@@ -1265,7 +1317,7 @@ namespace Internal {
     template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } };
 
     template<typename T>
-    inline T& opCast(T const& t) { return const_cast<T&>(t); }
+    T& opCast(T const& t) { return const_cast<T&>(t); }
 
 // nullptr_t support based on pull request #154 from Konstantin Baumann
 #ifdef CATCH_CONFIG_CPP11_NULLPTR
@@ -1275,7 +1327,7 @@ namespace Internal {
     // So the compare overloads can be operator agnostic we convey the operator as a template
     // enum, which is used to specialise an Evaluator for doing the comparison.
     template<typename T1, typename T2, Operator Op>
-    class Evaluator{};
+    struct Evaluator{};
 
     template<typename T1, typename T2>
     struct Evaluator<T1, T2, IsEqualTo> {
@@ -1541,7 +1593,7 @@ std::string toString( std::nullptr_t );
 
 #ifdef __OBJC__
     std::string toString( NSString const * const& nsstring );
-    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
+    std::string toString( NSString * CATCH_ARC_STRONG & nsstring );
     std::string toString( NSObject* const& nsObject );
 #endif
 
@@ -1549,6 +1601,7 @@ namespace Detail {
 
     extern const std::string unprintableString;
 
+ #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK)
     struct BorgType {
         template<typename T> BorgType( T const& );
     };
@@ -1567,6 +1620,20 @@ namespace Detail {
         static T  const&t;
         enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
     };
+#else
+    template<typename T>
+    class IsStreamInsertable {
+        template<typename SS, typename TT>
+        static auto test(int)
+        -> decltype( std::declval<SS&>() << std::declval<TT>(), std::true_type() );
+
+        template<typename, typename>
+        static auto test(...) -> std::false_type;
+
+    public:
+        static const bool value = decltype(test<std::ostream,const T&>(0))::value;
+    };
+#endif
 
 #if defined(CATCH_CONFIG_CPP11_IS_ENUM)
     template<typename T,
@@ -1615,7 +1682,7 @@ namespace Detail {
     std::string rawMemoryToString( const void *object, std::size_t size );
 
     template<typename T>
-    inline std::string rawMemoryToString( const T& object ) {
+    std::string rawMemoryToString( const T& object ) {
       return rawMemoryToString( &object, sizeof(object) );
     }
 
@@ -1810,7 +1877,7 @@ public:
     }
 
     virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
-        dest = Catch::toString( m_truthy );
+        dest = Catch::toString( m_lhs );
     }
 
 private:
@@ -1904,7 +1971,7 @@ private:
 namespace Catch {
 
     template<typename T>
-    inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
+    ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
         return ExpressionLhs<T const&>( *this, operand );
     }
 
@@ -1913,7 +1980,7 @@ namespace Catch {
     }
 
     template<typename ArgT, typename MatcherT>
-    inline void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher,
+    void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher,
                                              char const* matcherString ) {
         MatchExpression<ArgT const&, MatcherT const&> expr( arg, matcher, matcherString );
         setResultType( matcher.match( arg ) );
@@ -2009,7 +2076,13 @@ namespace Catch {
         virtual std::string getCurrentTestName() const = 0;
         virtual const AssertionResult* getLastResult() const = 0;
 
+        virtual void exceptionEarlyReported() = 0;
+
         virtual void handleFatalErrorCondition( std::string const& message ) = 0;
+
+        virtual bool lastAssertionPassed() = 0;
+        virtual void assertionPassed() = 0;
+        virtual void assertionRun() = 0;
     };
 
     IResultCapture& getResultCapture();
@@ -2052,9 +2125,9 @@ namespace Catch{
     #if defined(__ppc64__) || defined(__ppc__)
         #define CATCH_TRAP() \
                 __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
-                : : : "memory","r0","r3","r4" )
+                : : : "memory","r0","r3","r4" ) /* NOLINT */
     #else
-        #define CATCH_TRAP() __asm__("int $3\n" : : )
+        #define CATCH_TRAP() __asm__("int $3\n" : : /* NOLINT */ )
     #endif
 
 #elif defined(CATCH_PLATFORM_LINUX)
@@ -2062,7 +2135,7 @@ namespace Catch{
     // directly at the location of the failing check instead of breaking inside
     // raise() called from it, i.e. one stack frame below.
     #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
-        #define CATCH_TRAP() asm volatile ("int $3")
+        #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */
     #else // Fall back to the generic way.
         #include <signal.h>
 
@@ -2093,45 +2166,6 @@ namespace Catch {
     };
 }
 
-// #included from: catch_type_traits.hpp
-#define TWOBLUECUBES_CATCH_TYPE_TRAITS_HPP_INCLUDED
-
-#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
-#include <type_traits>
-#endif
-
-namespace Catch {
-
-#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
-
-     template <typename T>
-     using add_lvalue_reference = std::add_lvalue_reference<T>;
-
-     template <typename T>
-     using add_const = std::add_const<T>;
-
-#else
-
-    template <typename T>
-    struct add_const {
-        typedef const T type;
-    };
-
-    template <typename T>
-    struct add_lvalue_reference {
-        typedef T& type;
-    };
-    template <typename T>
-    struct add_lvalue_reference<T&> {
-        typedef T& type;
-    };
-    // No && overload, because that is C++11, in which case we have
-    // proper type_traits implementation from the standard library
-
-#endif
-
-}
-
 #if defined(CATCH_CONFIG_FAST_COMPILE)
 ///////////////////////////////////////////////////////////////////////////////
 // We can speedup compilation significantly by breaking into debugger lower in
@@ -2139,6 +2173,33 @@ namespace Catch {
 // macro in each assertion
 #define INTERNAL_CATCH_REACT( resultBuilder ) \
     resultBuilder.react();
+
+///////////////////////////////////////////////////////////////////////////////
+// Another way to speed-up compilation is to omit local try-catch for REQUIRE*
+// macros.
+// This can potentially cause false negative, if the test code catches
+// the exception before it propagates back up to the runner.
+#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+        __catchResult.setExceptionGuard(); \
+        CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+        ( __catchResult <= expr ).endExpression(); \
+        CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+        __catchResult.unsetExceptionGuard(); \
+        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
+// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
+
+#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
+        __catchResult.setExceptionGuard(); \
+        __catchResult.captureMatch( arg, matcher, #matcher ); \
+        __catchResult.unsetExceptionGuard(); \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::alwaysFalse() )
+
 #else
 ///////////////////////////////////////////////////////////////////////////////
 // In the event of a failure works out if the debugger needs to be invoked
@@ -2151,7 +2212,7 @@ namespace Catch {
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \
+#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
         try { \
@@ -2167,17 +2228,17 @@ namespace Catch {
     // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \
-    INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
-    if( Catch::getResultCapture().getLastResult()->succeeded() )
+#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \
+    if( Catch::getResultCapture().lastAssertionPassed() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \
-    INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
-    if( !Catch::getResultCapture().getLastResult()->succeeded() )
+#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \
+    if( !Catch::getResultCapture().lastAssertionPassed() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \
+#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
         try { \
@@ -2191,7 +2252,7 @@ namespace Catch {
     } while( Catch::alwaysFalse() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \
+#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \
         if( __catchResult.allowThrows() ) \
@@ -2208,7 +2269,7 @@ namespace Catch {
     } while( Catch::alwaysFalse() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \
+#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr ", " #exceptionType, resultDisposition ); \
         if( __catchResult.allowThrows() ) \
@@ -2216,7 +2277,7 @@ namespace Catch {
                 static_cast<void>(expr); \
                 __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
             } \
-            catch( Catch::add_const<Catch::add_lvalue_reference<exceptionType>::type>::type ) { \
+            catch( exceptionType ) { \
                 __catchResult.captureResult( Catch::ResultWas::Ok ); \
             } \
             catch( ... ) { \
@@ -2229,7 +2290,7 @@ namespace Catch {
 
 ///////////////////////////////////////////////////////////////////////////////
 #ifdef CATCH_CONFIG_VARIADIC_MACROS
-    #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \
+    #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \
         do { \
             Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
             __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \
@@ -2237,7 +2298,7 @@ namespace Catch {
             INTERNAL_CATCH_REACT( __catchResult ) \
         } while( Catch::alwaysFalse() )
 #else
-    #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \
+    #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \
         do { \
             Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
             __catchResult << log + ::Catch::StreamEndStop(); \
@@ -2247,11 +2308,11 @@ namespace Catch {
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_INFO( log, macroName ) \
+#define INTERNAL_CATCH_INFO( macroName, log ) \
     Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log;
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \
+#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
         try { \
@@ -2368,14 +2429,19 @@ namespace Catch {
 // #included from: catch_timer.h
 #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED
 
-#ifdef CATCH_PLATFORM_WINDOWS
-typedef unsigned long long uint64_t;
+#ifdef _MSC_VER
+
+namespace Catch {
+    typedef unsigned long long UInt64;
+}
 #else
 #include <stdint.h>
+namespace Catch {
+    typedef uint64_t UInt64;
+}
 #endif
 
 namespace Catch {
-
     class Timer {
     public:
         Timer() : m_ticks( 0 ) {}
@@ -2385,7 +2451,7 @@ namespace Catch {
         double getElapsedSeconds() const;
 
     private:
-        uint64_t m_ticks;
+        UInt64 m_ticks;
     };
 
 } // namespace Catch
@@ -2424,7 +2490,6 @@ namespace Catch {
 // #included from: internal/catch_generators.hpp
 #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
 
-#include <iterator>
 #include <vector>
 #include <string>
 #include <stdlib.h>
@@ -2538,7 +2603,7 @@ public:
 private:
 
     void move( CompositeGenerator& other ) {
-        std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) );
+        m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() );
         m_totalSize += other.m_totalSize;
         other.m_composed.clear();
     }
@@ -2620,12 +2685,15 @@ namespace Catch {
     struct IExceptionTranslator;
     struct IReporterRegistry;
     struct IReporterFactory;
+    struct ITagAliasRegistry;
 
     struct IRegistryHub {
         virtual ~IRegistryHub();
 
         virtual IReporterRegistry const& getReporterRegistry() const = 0;
         virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
+        virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;
+
         virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0;
     };
 
@@ -2635,6 +2703,7 @@ namespace Catch {
         virtual void registerListener( Ptr<IReporterFactory> const& factory ) = 0;
         virtual void registerTest( TestCase const& testInfo ) = 0;
         virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
+        virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
     };
 
     IRegistryHub& getRegistryHub();
@@ -2726,26 +2795,25 @@ namespace Detail {
             m_value( value )
         {}
 
-        Approx( Approx const& other )
-        :   m_epsilon( other.m_epsilon ),
-            m_margin( other.m_margin ),
-            m_scale( other.m_scale ),
-            m_value( other.m_value )
-        {}
-
         static Approx custom() {
             return Approx( 0 );
         }
 
-        Approx operator()( double value ) {
-            Approx approx( value );
+#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx operator()( T value ) {
+            Approx approx( static_cast<double>(value) );
             approx.epsilon( m_epsilon );
             approx.margin( m_margin );
             approx.scale( m_scale );
             return approx;
         }
 
-#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        explicit Approx( T value ): Approx(static_cast<double>(value))
+        {}
+
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
         friend bool operator == ( const T& lhs, Approx const& rhs ) {
             // Thanks to Richard Harris for his help refining this formula
@@ -2773,29 +2841,53 @@ namespace Detail {
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator <= ( T lhs, Approx const& rhs )
-        {
-          return double(lhs) < rhs.m_value || lhs == rhs;
+        friend bool operator <= ( T lhs, Approx const& rhs ) {
+            return double(lhs) < rhs.m_value || lhs == rhs;
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator <= ( Approx const& lhs, T rhs )
-        {
-          return lhs.m_value < double(rhs) || lhs == rhs;
+        friend bool operator <= ( Approx const& lhs, T rhs ) {
+            return lhs.m_value < double(rhs) || lhs == rhs;
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator >= ( T lhs, Approx const& rhs )
-        {
-          return double(lhs) > rhs.m_value || lhs == rhs;
+        friend bool operator >= ( T lhs, Approx const& rhs ) {
+            return double(lhs) > rhs.m_value || lhs == rhs;
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator >= ( Approx const& lhs, T rhs )
-        {
-          return lhs.m_value > double(rhs) || lhs == rhs;
+        friend bool operator >= ( Approx const& lhs, T rhs ) {
+            return lhs.m_value > double(rhs) || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& epsilon( T newEpsilon ) {
+            m_epsilon = double(newEpsilon);
+            return *this;
         }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& margin( T newMargin ) {
+            m_margin = double(newMargin);
+            return *this;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& scale( T newScale ) {
+            m_scale = double(newScale);
+            return *this;
+        }
+
 #else
+
+        Approx operator()( double value ) {
+            Approx approx( value );
+            approx.epsilon( m_epsilon );
+            approx.margin( m_margin );
+            approx.scale( m_scale );
+            return approx;
+        }
+
         friend bool operator == ( double lhs, Approx const& rhs ) {
             // Thanks to Richard Harris for his help refining this formula
             bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) );
@@ -2817,26 +2909,21 @@ namespace Detail {
             return !operator==( rhs, lhs );
         }
 
-        friend bool operator <= ( double lhs, Approx const& rhs )
-        {
-          return lhs < rhs.m_value || lhs == rhs;
+        friend bool operator <= ( double lhs, Approx const& rhs ) {
+            return lhs < rhs.m_value || lhs == rhs;
         }
 
-        friend bool operator <= ( Approx const& lhs, double rhs )
-        {
-          return lhs.m_value < rhs || lhs == rhs;
+        friend bool operator <= ( Approx const& lhs, double rhs ) {
+            return lhs.m_value < rhs || lhs == rhs;
         }
 
-        friend bool operator >= ( double lhs, Approx const& rhs )
-        {
-          return lhs > rhs.m_value || lhs == rhs;
+        friend bool operator >= ( double lhs, Approx const& rhs ) {
+            return lhs > rhs.m_value || lhs == rhs;
         }
 
-        friend bool operator >= ( Approx const& lhs, double rhs )
-        {
-          return lhs.m_value > rhs || lhs == rhs;
+        friend bool operator >= ( Approx const& lhs, double rhs ) {
+            return lhs.m_value > rhs || lhs == rhs;
         }
-#endif
 
         Approx& epsilon( double newEpsilon ) {
             m_epsilon = newEpsilon;
@@ -2852,6 +2939,7 @@ namespace Detail {
             m_scale = newScale;
             return *this;
         }
+#endif
 
         std::string toString() const {
             std::ostringstream oss;
@@ -2893,7 +2981,7 @@ namespace Matchers {
         };
 
         struct StringMatcherBase : MatcherBase<std::string> {
-            StringMatcherBase( std::string operation, CasedString const& comparator );
+            StringMatcherBase( std::string const& operation, CasedString const& comparator );
             virtual std::string describe() const CATCH_OVERRIDE;
 
             CasedString m_comparator;
@@ -3032,7 +3120,7 @@ namespace Matchers {
 namespace Catch {
 
     struct TagAlias {
-        TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
+        TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
 
         std::string tag;
         SourceLineInfo lineInfo;
@@ -3104,8 +3192,18 @@ namespace Catch {
         }
 
     private:
-        T* nullableValue;
-        char storage[sizeof(T)];
+        T *nullableValue;
+        union {
+            char storage[sizeof(T)];
+
+            // These are here to force alignment for the storage
+            long double dummy1;
+            void (*dummy2)();
+            long double dummy3;
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+            long long dummy4;
+#endif
+        };
     };
 
 } // end namespace Catch
@@ -3304,64 +3402,67 @@ namespace Catch {
         namespace Impl {
         namespace NSStringMatchers {
 
-            template<typename MatcherT>
-            struct StringHolder : MatcherImpl<MatcherT, NSString*>{
+            struct StringHolder : MatcherBase<NSString*>{
                 StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
                 StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
                 StringHolder() {
                     arcSafeRelease( m_substr );
                 }
 
+                virtual bool match( NSString* arg ) const CATCH_OVERRIDE {
+                    return false;
+                }
+
                 NSString* m_substr;
             };
 
-            struct Equals : StringHolder<Equals> {
+            struct Equals : StringHolder {
                 Equals( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const CATCH_OVERRIDE {
                     return  (str != nil || m_substr == nil ) &&
                             [str isEqualToString:m_substr];
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "equals string: " + Catch::toString( m_substr );
                 }
             };
 
-            struct Contains : StringHolder<Contains> {
+            struct Contains : StringHolder {
                 Contains( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const {
                     return  (str != nil || m_substr == nil ) &&
                             [str rangeOfString:m_substr].location != NSNotFound;
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "contains string: " + Catch::toString( m_substr );
                 }
             };
 
-            struct StartsWith : StringHolder<StartsWith> {
+            struct StartsWith : StringHolder {
                 StartsWith( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const {
                     return  (str != nil || m_substr == nil ) &&
                             [str rangeOfString:m_substr].location == 0;
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "starts with: " + Catch::toString( m_substr );
                 }
             };
-            struct EndsWith : StringHolder<EndsWith> {
+            struct EndsWith : StringHolder {
                 EndsWith( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const {
                     return  (str != nil || m_substr == nil ) &&
                             [str rangeOfString:m_substr].location == [str length] - [m_substr length];
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "ends with: " + Catch::toString( m_substr );
                 }
             };
@@ -3408,16 +3509,16 @@ return @ desc; \
 #include <crtdbg.h>
 class LeakDetector {
 public:
-	LeakDetector() {
-		int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
-		flag |= _CRTDBG_LEAK_CHECK_DF;
-		flag |= _CRTDBG_ALLOC_MEM_DF;
-		_CrtSetDbgFlag(flag);
-		_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
-		_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
-		// Change this to leaking allocation's number to break there
-		_CrtSetBreakAlloc(-1);
-	}
+    LeakDetector() {
+        int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+        flag |= _CRTDBG_LEAK_CHECK_DF;
+        flag |= _CRTDBG_ALLOC_MEM_DF;
+        _CrtSetDbgFlag(flag);
+        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+        _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+        // Change this to leaking allocation's number to break there
+        _CrtSetBreakAlloc(-1);
+    }
 };
 #else
 class LeakDetector {};
@@ -3617,7 +3718,7 @@ namespace Catch {
         ITagAliasRegistry const* m_tagAliases;
 
     public:
-        TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
+        TestSpecParser( ITagAliasRegistry const& tagAliases ) :m_mode(None), m_exclusion(false), m_start(0), m_pos(0), m_tagAliases( &tagAliases ) {}
 
         TestSpecParser& parse( std::string const& arg ) {
             m_mode = None;
@@ -3801,6 +3902,7 @@ namespace Catch {
 
     std::ostream& cout();
     std::ostream& cerr();
+    std::ostream& clog();
 
     struct IStream {
         virtual ~IStream() CATCH_NOEXCEPT;
@@ -3856,6 +3958,7 @@ namespace Catch {
             listTags( false ),
             listReporters( false ),
             listTestNamesOnly( false ),
+            listExtraInfo( false ),
             showSuccessfulTests( false ),
             shouldDebugBreak( false ),
             noThrow( false ),
@@ -3875,6 +3978,7 @@ namespace Catch {
         bool listTags;
         bool listReporters;
         bool listTestNamesOnly;
+        bool listExtraInfo;
 
         bool showSuccessfulTests;
         bool shouldDebugBreak;
@@ -3933,6 +4037,7 @@ namespace Catch {
         bool listTestNamesOnly() const { return m_data.listTestNamesOnly; }
         bool listTags() const { return m_data.listTags; }
         bool listReporters() const { return m_data.listReporters; }
+        bool listExtraInfo() const { return m_data.listExtraInfo; }
 
         std::string getProcessName() const { return m_data.processName; }
 
@@ -4020,6 +4125,7 @@ namespace Catch {
 #include <vector>
 #include <sstream>
 #include <algorithm>
+#include <cctype>
 
 // Use optional outer namespace
 #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
@@ -4129,7 +4235,7 @@ namespace Tbc {
             return oss.str();
         }
 
-        inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+        friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
             for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
                 it != itEnd; ++it ) {
                 if( it != _text.begin() )
@@ -4362,7 +4468,7 @@ namespace Clara {
             _dest = _source;
         }
         char toLowerCh(char c) {
-            return static_cast<char>( ::tolower( c ) );
+            return static_cast<char>( std::tolower( c ) );
         }
         inline void convertInto( std::string const& _source, bool& _dest ) {
             std::string sourceLC = _source;
@@ -4516,12 +4622,13 @@ namespace Clara {
         }
 
         void parseIntoTokens( std::string const& arg, std::vector<Token>& tokens ) {
-            for( std::size_t i = 0; i <= arg.size(); ++i ) {
+            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( arg.size(), '\0', arg, tokens );
         }
         Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
             switch( mode ) {
@@ -4554,6 +4661,7 @@ namespace Clara {
                 default: from = i; return ShortOpt;
             }
         }
+
         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;
@@ -4885,7 +4993,7 @@ namespace Clara {
         }
 
         std::vector<Parser::Token> parseInto( std::vector<std::string> const& args, ConfigT& config ) const {
-            std::string processName = args[0];
+            std::string processName = args.empty() ? std::string() : args[0];
             std::size_t lastSlash = processName.find_last_of( "/\\" );
             if( lastSlash != std::string::npos )
                 processName = processName.substr( lastSlash+1 );
@@ -5191,6 +5299,10 @@ namespace Catch {
             .describe( "list all/matching test cases names only" )
             .bind( &ConfigData::listTestNamesOnly );
 
+        cli["--list-extra-info"]
+            .describe( "list all/matching test cases with more info" )
+            .bind( &ConfigData::listExtraInfo );
+
         cli["--list-reporters"]
             .describe( "list all reporters" )
             .bind( &ConfigData::listReporters );
@@ -5719,8 +5831,9 @@ namespace Catch {
         }
 
         std::size_t matchedTests = 0;
-        TextAttributes nameAttr, tagsAttr;
+        TextAttributes nameAttr, descAttr, tagsAttr;
         nameAttr.setInitialIndent( 2 ).setIndent( 4 );
+        descAttr.setIndent( 4 );
         tagsAttr.setIndent( 6 );
 
         std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
@@ -5735,6 +5848,13 @@ namespace Catch {
             Colour colourGuard( colour );
 
             Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
+            if( config.listExtraInfo() ) {
+                Catch::cout() << "    " << testCaseInfo.lineInfo << std::endl;
+                std::string description = testCaseInfo.description;
+                if( description.empty() )
+                    description = "(NO DESCRIPTION)";
+                Catch::cout() << Text( description, descAttr ) << std::endl;
+            }
             if( !testCaseInfo.tags.empty() )
                 Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
         }
@@ -5758,9 +5878,12 @@ namespace Catch {
             matchedTests++;
             TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
             if( startsWith( testCaseInfo.name, '#' ) )
-               Catch::cout() << '"' << testCaseInfo.name << '"' << std::endl;
+               Catch::cout() << '"' << testCaseInfo.name << '"';
             else
-               Catch::cout() << testCaseInfo.name << std::endl;
+               Catch::cout() << testCaseInfo.name;
+            if ( config.listExtraInfo() )
+                Catch::cout() << "\t@" << testCaseInfo.lineInfo;
+            Catch::cout() << std::endl;
         }
         return matchedTests;
     }
@@ -5852,7 +5975,7 @@ namespace Catch {
 
     inline Option<std::size_t> list( Config const& config ) {
         Option<std::size_t> listedCount;
-        if( config.listTests() )
+        if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) )
             listedCount = listedCount.valueOr(0) + listTests( config );
         if( config.listTestNamesOnly() )
             listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
@@ -5871,13 +5994,14 @@ namespace Catch {
 // #included from: catch_test_case_tracker.hpp
 #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
 
-#include <map>
+#include <algorithm>
 #include <string>
 #include <assert.h>
 #include <vector>
-#include <iterator>
 #include <stdexcept>
 
+CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS
+
 namespace Catch {
 namespace TestCaseTracking {
 
@@ -6148,12 +6272,12 @@ namespace TestCaseTracking {
             if( !filters.empty() ) {
                 m_filters.push_back(""); // Root - should never be consulted
                 m_filters.push_back(""); // Test Case - not a section filter
-                std::copy( filters.begin(), filters.end(), std::back_inserter( m_filters ) );
+                m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
             }
         }
         void addNextFilters( std::vector<std::string> const& filters ) {
             if( filters.size() > 1 )
-                std::copy( filters.begin()+1, filters.end(), std::back_inserter( m_filters ) );
+                m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
         }
     };
 
@@ -6223,6 +6347,8 @@ using TestCaseTracking::IndexTracker;
 
 } // namespace Catch
 
+CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
+
 // #included from: catch_fatal_condition.hpp
 #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
 
@@ -6292,7 +6418,6 @@ namespace Catch {
         static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
             for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
                 if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
-                    reset();
                     reportFatal(signalDefs[i].name);
                 }
             }
@@ -6462,6 +6587,29 @@ namespace Catch {
         std::string& m_targetString;
     };
 
+    // StdErr has two constituent streams in C++, std::cerr and std::clog
+    // This means that we need to redirect 2 streams into 1 to keep proper
+    // order of writes and cannot use StreamRedirect on its own
+    class StdErrRedirect {
+    public:
+        StdErrRedirect(std::string& targetString)
+        :m_cerrBuf( cerr().rdbuf() ), m_clogBuf(clog().rdbuf()),
+        m_targetString(targetString){
+            cerr().rdbuf(m_oss.rdbuf());
+            clog().rdbuf(m_oss.rdbuf());
+        }
+        ~StdErrRedirect() {
+            m_targetString += m_oss.str();
+            cerr().rdbuf(m_cerrBuf);
+            clog().rdbuf(m_clogBuf);
+        }
+    private:
+        std::streambuf* m_cerrBuf;
+        std::streambuf* m_clogBuf;
+        std::ostringstream m_oss;
+        std::string& m_targetString;
+    };
+
     ///////////////////////////////////////////////////////////////////////////
 
     class RunContext : public IResultCapture, public IRunner {
@@ -6476,7 +6624,8 @@ namespace Catch {
             m_context( getCurrentMutableContext() ),
             m_activeTestCase( CATCH_NULL ),
             m_config( _config ),
-            m_reporter( reporter )
+            m_reporter( reporter ),
+            m_shouldReportUnexpected ( true )
         {
             m_context.setRunner( this );
             m_context.setConfig( m_config );
@@ -6554,14 +6703,32 @@ namespace Catch {
                 m_totals.assertions.failed++;
             }
 
-            if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) )
-                m_messages.clear();
+            // We have no use for the return value (whether messages should be cleared), because messages were made scoped
+            // and should be let to clear themselves out.
+            static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)));
 
             // Reset working state
-            m_lastAssertionInfo = AssertionInfo( std::string(), m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
+            m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
             m_lastResult = result;
         }
 
+        virtual bool lastAssertionPassed()
+        {
+            return m_totals.assertions.passed == (m_prevPassed + 1);
+        }
+
+        virtual void assertionPassed()
+        {
+            m_totals.assertions.passed++;
+            m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}";
+            m_lastAssertionInfo.macroName = "";
+        }
+
+        virtual void assertionRun()
+        {
+            m_prevPassed = m_totals.assertions.passed;
+        }
+
         virtual bool sectionStarted (
             SectionInfo const& sectionInfo,
             Counts& assertions
@@ -6633,11 +6800,19 @@ namespace Catch {
             return &m_lastResult;
         }
 
+        virtual void exceptionEarlyReported() {
+            m_shouldReportUnexpected = false;
+        }
+
         virtual void handleFatalErrorCondition( std::string const& message ) {
-            ResultBuilder resultBuilder = makeUnexpectedResultBuilder();
-            resultBuilder.setResultType( ResultWas::FatalErrorCondition );
-            resultBuilder << message;
-            resultBuilder.captureExpression();
+            // Don't rebuild the result -- the stringification itself can cause more fatal errors
+            // Instead, fake a result data.
+            AssertionResultData tempResult;
+            tempResult.resultType = ResultWas::FatalErrorCondition;
+            tempResult.message = message;
+            AssertionResult result(m_lastAssertionInfo, tempResult);
+
+            getResultCapture().assertionEnded(result);
 
             handleUnfinishedSections();
 
@@ -6654,6 +6829,7 @@ namespace Catch {
 
             Totals deltaTotals;
             deltaTotals.testCases.failed = 1;
+            deltaTotals.assertions.failed = 1;
             m_reporter->testCaseEnded( TestCaseStats(   testInfo,
                                                         deltaTotals,
                                                         std::string(),
@@ -6678,8 +6854,9 @@ namespace Catch {
             m_reporter->sectionStarting( testCaseSection );
             Counts prevAssertions = m_totals.assertions;
             double duration = 0;
+            m_shouldReportUnexpected = true;
             try {
-                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, std::string(), ResultDisposition::Normal );
+                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal );
 
                 seedRng( *m_config );
 
@@ -6687,7 +6864,7 @@ namespace Catch {
                 timer.start();
                 if( m_reporter->getPreferences().shouldRedirectStdOut ) {
                     StreamRedirect coutRedir( Catch::cout(), redirectedCout );
-                    StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
+                    StdErrRedirect errRedir( redirectedCerr );
                     invokeActiveTestCase();
                 }
                 else {
@@ -6699,7 +6876,11 @@ namespace Catch {
                 // This just means the test was aborted due to failure
             }
             catch(...) {
-                makeUnexpectedResultBuilder().useActiveException();
+                // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
+                // are reported without translation at the point of origin.
+                if (m_shouldReportUnexpected) {
+                    makeUnexpectedResultBuilder().useActiveException();
+                }
             }
             m_testCaseTracker->close();
             handleUnfinishedSections();
@@ -6727,9 +6908,9 @@ namespace Catch {
     private:
 
         ResultBuilder makeUnexpectedResultBuilder() const {
-            return ResultBuilder(   m_lastAssertionInfo.macroName.c_str(),
+            return ResultBuilder(   m_lastAssertionInfo.macroName,
                                     m_lastAssertionInfo.lineInfo,
-                                    m_lastAssertionInfo.capturedExpression.c_str(),
+                                    m_lastAssertionInfo.capturedExpression,
                                     m_lastAssertionInfo.resultDisposition );
         }
 
@@ -6759,6 +6940,8 @@ namespace Catch {
         std::vector<SectionEndInfo> m_unfinishedSections;
         std::vector<ITracker*> m_activeSections;
         TrackerContext m_trackerContext;
+        size_t m_prevPassed;
+        bool m_shouldReportUnexpected;
     };
 
     IResultCapture& getResultCapture() {
@@ -6780,7 +6963,7 @@ namespace Catch {
         Version(    unsigned int _majorVersion,
                     unsigned int _minorVersion,
                     unsigned int _patchNumber,
-                    std::string const& _branchName,
+                    char const * const _branchName,
                     unsigned int _buildNumber );
 
         unsigned int const majorVersion;
@@ -6788,7 +6971,7 @@ namespace Catch {
         unsigned int const patchNumber;
 
         // buildNumber is only used if branchName is not null
-        std::string const branchName;
+        char const * const branchName;
         unsigned int const buildNumber;
 
         friend std::ostream& operator << ( std::ostream& os, Version const& version );
@@ -6797,7 +6980,7 @@ namespace Catch {
         void operator=( Version const& );
     };
 
-    extern Version libraryVersion;
+    inline Version libraryVersion();
 }
 
 #include <fstream>
@@ -6816,10 +6999,14 @@ namespace Catch {
         return reporter;
     }
 
+#if !defined(CATCH_CONFIG_DEFAULT_REPORTER)
+#define CATCH_CONFIG_DEFAULT_REPORTER "console"
+#endif
+
     Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) {
         std::vector<std::string> reporters = config->getReporterNames();
         if( reporters.empty() )
-            reporters.push_back( "console" );
+            reporters.push_back( CATCH_CONFIG_DEFAULT_REPORTER );
 
         Ptr<IStreamingReporter> reporter;
         for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end();
@@ -6879,11 +7066,11 @@ namespace Catch {
             if( lastSlash != std::string::npos )
                 filename = filename.substr( lastSlash+1 );
 
-            std::string::size_type lastDot = filename.find_last_of( "." );
+            std::string::size_type lastDot = filename.find_last_of( '.' );
             if( lastDot != std::string::npos )
                 filename = filename.substr( 0, lastDot );
 
-            tags.insert( "#" + filename );
+            tags.insert( '#' + filename );
             setTags( test, tags );
         }
     }
@@ -6909,7 +7096,7 @@ namespace Catch {
         }
 
         void showHelp( std::string const& processName ) {
-            Catch::cout() << "\nCatch v" << libraryVersion << "\n";
+            Catch::cout() << "\nCatch v" << libraryVersion() << "\n";
 
             m_cli.usage( Catch::cout(), processName );
             Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
@@ -6950,6 +7137,32 @@ namespace Catch {
             return returnCode;
         }
 
+    #if defined(WIN32) && defined(UNICODE)
+        int run( int argc, wchar_t const* const* const argv ) {
+
+            char **utf8Argv = new char *[ argc ];
+
+            for ( int i = 0; i < argc; ++i ) {
+                int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL );
+
+                utf8Argv[ i ] = new char[ bufSize ];
+
+                WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL );
+            }
+
+            int returnCode = applyCommandLine( argc, utf8Argv );
+            if( returnCode == 0 )
+                returnCode = run();
+
+            for ( int i = 0; i < argc; ++i )
+                delete [] utf8Argv[ i ];
+
+            delete [] utf8Argv;
+
+            return returnCode;
+        }
+    #endif
+
         int run() {
             if( m_configData.showHelp )
                 return 0;
@@ -7297,6 +7510,26 @@ namespace Catch {
     };
 }
 
+// #included from: catch_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+    class TagAliasRegistry : public ITagAliasRegistry {
+    public:
+        virtual ~TagAliasRegistry();
+        virtual Option<TagAlias> find( std::string const& alias ) const;
+        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
+        void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo );
+
+    private:
+        std::map<std::string, TagAlias> m_registry;
+    };
+
+} // end namespace Catch
+
 namespace Catch {
 
     namespace {
@@ -7318,6 +7551,9 @@ namespace Catch {
             virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE {
                 return m_exceptionTranslatorRegistry;
             }
+            virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE {
+                return m_tagAliasRegistry;
+            }
 
         public: // IMutableRegistryHub
             virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
@@ -7332,11 +7568,15 @@ namespace Catch {
             virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE {
                 m_exceptionTranslatorRegistry.registerTranslator( translator );
             }
+            virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE {
+                m_tagAliasRegistry.add( alias, tag, lineInfo );
+            }
 
         private:
             TestRegistry m_testCaseRegistry;
             ReporterRegistry m_reporterRegistry;
             ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
+            TagAliasRegistry m_tagAliasRegistry;
         };
 
         // Single, global, instance
@@ -7482,6 +7722,9 @@ namespace Catch {
     std::ostream& cerr() {
         return std::cerr;
     }
+    std::ostream& clog() {
+        return std::clog;
+    }
 #endif
 }
 
@@ -7581,6 +7824,23 @@ namespace Catch {
 // #included from: catch_console_colour_impl.hpp
 #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
 
+// #included from: catch_errno_guard.hpp
+#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED
+
+#include <cerrno>
+
+namespace Catch {
+
+    class ErrnoGuard {
+    public:
+        ErrnoGuard():m_oldErrno(errno){}
+        ~ErrnoGuard() { errno = m_oldErrno; }
+    private:
+        int m_oldErrno;
+    };
+
+}
+
 namespace Catch {
     namespace {
 
@@ -7716,6 +7976,7 @@ namespace {
     };
 
     IColourImpl* platformColourInstance() {
+        ErrnoGuard guard;
         Ptr<IConfig const> config = getCurrentContext().getConfig();
         UseColour::YesOrNo colourMode = config
             ? config->useColour()
@@ -7834,14 +8095,18 @@ namespace Catch {
 
 namespace Catch {
 
-    AssertionInfo::AssertionInfo(   std::string const& _macroName,
+    AssertionInfo::AssertionInfo():macroName(""), capturedExpression(""), resultDisposition(ResultDisposition::Normal), secondArg(""){}
+
+    AssertionInfo::AssertionInfo(   char const * _macroName,
                                     SourceLineInfo const& _lineInfo,
-                                    std::string const& _capturedExpression,
-                                    ResultDisposition::Flags _resultDisposition )
+                                    char const * _capturedExpression,
+                                    ResultDisposition::Flags _resultDisposition,
+                                    char const * _secondArg)
     :   macroName( _macroName ),
         lineInfo( _lineInfo ),
         capturedExpression( _capturedExpression ),
-        resultDisposition( _resultDisposition )
+        resultDisposition( _resultDisposition ),
+        secondArg( _secondArg )
     {}
 
     AssertionResult::AssertionResult() {}
@@ -7868,24 +8133,30 @@ namespace Catch {
     }
 
     bool AssertionResult::hasExpression() const {
-        return !m_info.capturedExpression.empty();
+        return m_info.capturedExpression[0] != 0;
     }
 
     bool AssertionResult::hasMessage() const {
         return !m_resultData.message.empty();
     }
 
+    std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) {
+        return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"')
+            ? capturedExpression
+            : std::string(capturedExpression) + ", " + secondArg;
+    }
+
     std::string AssertionResult::getExpression() const {
         if( isFalseTest( m_info.resultDisposition ) )
-            return '!' + m_info.capturedExpression;
+            return '!' + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
         else
-            return m_info.capturedExpression;
+            return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
     }
     std::string AssertionResult::getExpressionInMacro() const {
-        if( m_info.macroName.empty() )
-            return m_info.capturedExpression;
+        if( m_info.macroName[0] == 0 )
+            return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
         else
-            return m_info.macroName + "( " + m_info.capturedExpression + " )";
+            return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )";
     }
 
     bool AssertionResult::hasExpandedExpression() const {
@@ -7945,17 +8216,13 @@ namespace Catch {
     }
     inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
         if( isReservedTag( tag ) ) {
-            {
-                Colour colourGuard( Colour::Red );
-                Catch::cerr()
-                    << "Tag name [" << tag << "] not allowed.\n"
-                    << "Tag names starting with non alpha-numeric characters are reserved\n";
-            }
-            {
-                Colour colourGuard( Colour::FileName );
-                Catch::cerr() << _lineInfo << std::endl;
-            }
-            exit(1);
+            std::ostringstream ss;
+            ss << Colour(Colour::Red)
+               << "Tag name [" << tag << "] not allowed.\n"
+               << "Tag names starting with non alpha-numeric characters are reserved\n"
+               << Colour(Colour::FileName)
+               << _lineInfo << '\n';
+            throw std::runtime_error(ss.str());
         }
     }
 
@@ -8117,7 +8384,7 @@ namespace Catch {
         (   unsigned int _majorVersion,
             unsigned int _minorVersion,
             unsigned int _patchNumber,
-            std::string const& _branchName,
+            char const * const _branchName,
             unsigned int _buildNumber )
     :   majorVersion( _majorVersion ),
         minorVersion( _minorVersion ),
@@ -8130,15 +8397,18 @@ namespace Catch {
         os  << version.majorVersion << '.'
             << version.minorVersion << '.'
             << version.patchNumber;
-
-        if( !version.branchName.empty() ) {
-            os  << '-' << version.branchName
-                << '.' << version.buildNumber;
+        // branchName is never null -> 0th char is \0 if it is empty
+        if (version.branchName[0]) {
+            os << '-' << version.branchName
+               << '.' << version.buildNumber;
         }
         return os;
     }
 
-    Version libraryVersion( 1, 8, 1, "", 0 );
+    inline Version libraryVersion() {
+        static Version version( 1, 9, 7, "", 0 );
+        return version;
+    }
 
 }
 
@@ -8172,7 +8442,9 @@ namespace Catch {
     {}
 
     ScopedMessage::~ScopedMessage() {
-        getResultCapture().popScopedMessage( m_info );
+        if ( !std::uncaught_exception() ){
+            getResultCapture().popScopedMessage(m_info);
+        }
     }
 
 } // end namespace Catch
@@ -8320,21 +8592,21 @@ namespace Catch {
 
     namespace {
 #ifdef CATCH_PLATFORM_WINDOWS
-        uint64_t getCurrentTicks() {
-            static uint64_t hz=0, hzo=0;
+        UInt64 getCurrentTicks() {
+            static UInt64 hz=0, hzo=0;
             if (!hz) {
                 QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
                 QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
             }
-            uint64_t t;
+            UInt64 t;
             QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
             return ((t-hzo)*1000000)/hz;
         }
 #else
-        uint64_t getCurrentTicks() {
+        UInt64 getCurrentTicks() {
             timeval t;
             gettimeofday(&t,CATCH_NULL);
-            return static_cast<uint64_t>( t.tv_sec ) * 1000000ull + static_cast<uint64_t>( t.tv_usec );
+            return static_cast<UInt64>( t.tv_sec ) * 1000000ull + static_cast<UInt64>( t.tv_usec );
         }
 #endif
     }
@@ -8486,6 +8758,10 @@ namespace Catch {
         m_timer.start();
     }
 
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17
+#endif
     Section::~Section() {
         if( m_sectionIncluded ) {
             SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() );
@@ -8495,6 +8771,9 @@ namespace Catch {
                 getResultCapture().sectionEnded( endInfo );
         }
     }
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
 
     // This indicates whether the section should be executed or not
     Section::operator bool() const {
@@ -8567,6 +8846,9 @@ namespace Catch {
         // be strace, for example) in /proc/$PID/status, so just get it from
         // there instead.
         bool isDebuggerActive(){
+            // Libstdc++ has a bug, where std::ifstream sets errno to 0
+            // This way our users can properly assert over errno values
+            ErrnoGuard guard;
             std::ifstream in("/proc/self/status");
             for( std::string line; std::getline(in, line); ) {
                 static const int PREFIX_LEN = 11;
@@ -8807,7 +9089,7 @@ std::string toString( std::nullptr_t ) {
             return "nil";
         return "@" + toString([nsstring UTF8String]);
     }
-    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) {
+    std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) {
         if( !nsstring )
             return "nil";
         return "@" + toString([nsstring UTF8String]);
@@ -8824,21 +9106,28 @@ std::string toString( std::nullptr_t ) {
 
 namespace Catch {
 
-    std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) {
-        return secondArg.empty() || secondArg == "\"\""
-            ? capturedExpression
-            : capturedExpression + ", " + secondArg;
-    }
     ResultBuilder::ResultBuilder(   char const* macroName,
                                     SourceLineInfo const& lineInfo,
                                     char const* capturedExpression,
                                     ResultDisposition::Flags resultDisposition,
                                     char const* secondArg )
-    :   m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ),
+    :   m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ),
         m_shouldDebugBreak( false ),
-        m_shouldThrow( false )
+        m_shouldThrow( false ),
+        m_guardException( false ),
+        m_usedStream( false )
     {}
 
+    ResultBuilder::~ResultBuilder() {
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+        if ( m_guardException ) {
+            stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE";
+            captureResult( ResultWas::ThrewException );
+            getCurrentContext().getResultCapture()->exceptionEarlyReported();
+        }
+#endif
+    }
+
     ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) {
         m_data.resultType = result;
         return *this;
@@ -8849,13 +9138,25 @@ namespace Catch {
     }
 
     void ResultBuilder::endExpression( DecomposedExpression const& expr ) {
-        AssertionResult result = build( expr );
-        handleResult( result );
+        // Flip bool results if FalseTest flag is set
+        if( isFalseTest( m_assertionInfo.resultDisposition ) ) {
+            m_data.negate( expr.isBinaryExpression() );
+        }
+
+        getResultCapture().assertionRun();
+
+        if(getCurrentContext().getConfig()->includeSuccessfulResults() || m_data.resultType != ResultWas::Ok)
+        {
+            AssertionResult result = build( expr );
+            handleResult( result );
+        }
+        else
+            getResultCapture().assertionPassed();
     }
 
     void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) {
         m_assertionInfo.resultDisposition = resultDisposition;
-        m_stream.oss << Catch::translateActiveException();
+        stream().oss << Catch::translateActiveException();
         captureResult( ResultWas::ThrewException );
     }
 
@@ -8876,7 +9177,7 @@ namespace Catch {
         assert( !isFalseTest( m_assertionInfo.resultDisposition ) );
         AssertionResultData data = m_data;
         data.resultType = ResultWas::Ok;
-        data.reconstructedExpression = m_assertionInfo.capturedExpression;
+        data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg);
 
         std::string actualMessage = Catch::translateActiveException();
         if( !matcher.match( actualMessage ) ) {
@@ -8937,18 +9238,21 @@ namespace Catch {
         assert( m_data.resultType != ResultWas::Unknown );
         AssertionResultData data = m_data;
 
-        // Flip bool results if FalseTest flag is set
-        if( isFalseTest( m_assertionInfo.resultDisposition ) ) {
-            data.negate( expr.isBinaryExpression() );
-        }
-
-        data.message = m_stream.oss.str();
+        if(m_usedStream)
+            data.message = m_stream().oss.str();
         data.decomposedExpression = &expr; // for lazy reconstruction
         return AssertionResult( m_assertionInfo, data );
     }
 
     void ResultBuilder::reconstructExpression( std::string& dest ) const {
-        dest = m_assertionInfo.capturedExpression;
+        dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg);
+    }
+
+    void ResultBuilder::setExceptionGuard() {
+        m_guardException = true;
+    }
+    void ResultBuilder::unsetExceptionGuard() {
+        m_guardException = false;
     }
 
 } // end namespace Catch
@@ -8956,27 +9260,6 @@ namespace Catch {
 // #included from: catch_tag_alias_registry.hpp
 #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED
 
-// #included from: catch_tag_alias_registry.h
-#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
-
-#include <map>
-
-namespace Catch {
-
-    class TagAliasRegistry : public ITagAliasRegistry {
-    public:
-        virtual ~TagAliasRegistry();
-        virtual Option<TagAlias> find( std::string const& alias ) const;
-        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
-        void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
-        static TagAliasRegistry& get();
-
-    private:
-        std::map<std::string, TagAlias> m_registry;
-    };
-
-} // end namespace Catch
-
 namespace Catch {
 
     TagAliasRegistry::~TagAliasRegistry() {}
@@ -9004,40 +9287,36 @@ namespace Catch {
         return expandedTestSpec;
     }
 
-    void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {
 
         if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) {
             std::ostringstream oss;
-            oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo;
+            oss << Colour( Colour::Red )
+                << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n"
+                << Colour( Colour::FileName )
+                << lineInfo << '\n';
             throw std::domain_error( oss.str().c_str() );
         }
         if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) {
             std::ostringstream oss;
-            oss << "error: tag alias, \"" << alias << "\" already registered.\n"
-                << "\tFirst seen at " << find(alias)->lineInfo << '\n'
-                << "\tRedefined at " << lineInfo;
+            oss << Colour( Colour::Red )
+                << "error: tag alias, \"" << alias << "\" already registered.\n"
+                << "\tFirst seen at "
+                << Colour( Colour::Red ) << find(alias)->lineInfo << '\n'
+                << Colour( Colour::Red ) << "\tRedefined at "
+                << Colour( Colour::FileName) << lineInfo << '\n';
             throw std::domain_error( oss.str().c_str() );
         }
     }
 
-    TagAliasRegistry& TagAliasRegistry::get() {
-        static TagAliasRegistry instance;
-        return instance;
+    ITagAliasRegistry::~ITagAliasRegistry() {}
 
+    ITagAliasRegistry const& ITagAliasRegistry::get() {
+        return getRegistryHub().getTagAliasRegistry();
     }
 
-    ITagAliasRegistry::~ITagAliasRegistry() {}
-    ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); }
-
     RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
-        try {
-            TagAliasRegistry::get().add( alias, tag, lineInfo );
-        }
-        catch( std::exception& ex ) {
-            Colour colourGuard( Colour::Red );
-            Catch::cerr() << ex.what() << std::endl;
-            exit(1);
-        }
+        getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo );
     }
 
 } // end namespace Catch
@@ -9064,7 +9343,7 @@ namespace Matchers {
                    : std::string();
         }
 
-        StringMatcherBase::StringMatcherBase( std::string operation, CasedString const& comparator )
+        StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator )
         : m_comparator( comparator ),
           m_operation( operation ) {
         }
@@ -9265,10 +9544,34 @@ Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingRepo
 #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
 
 #include <cstring>
+#include <cfloat>
+#include <cstdio>
 #include <assert.h>
 
 namespace Catch {
 
+    namespace {
+        // Because formatting using c++ streams is stateful, drop down to C is required
+        // Alternatively we could use stringstream, but its performance is... not good.
+        std::string getFormattedDuration( double duration ) {
+            // Max exponent + 1 is required to represent the whole part
+            // + 1 for decimal point
+            // + 3 for the 3 decimal places
+            // + 1 for null terminator
+            const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
+            char buffer[maxDoubleSize];
+
+            // Save previous errno, to prevent sprintf from overwriting it
+            ErrnoGuard guard;
+#ifdef _MSC_VER
+            sprintf_s(buffer, "%.3f", duration);
+#else
+            sprintf(buffer, "%.3f", duration);
+#endif
+            return std::string(buffer);
+        }
+    }
+
     struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
 
         StreamingReporterBase( ReporterConfig const& _config )
@@ -9365,7 +9668,8 @@ namespace Catch {
             BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
             BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
             bool operator() ( Ptr<SectionNode> const& node ) const {
-                return node->stats.sectionInfo.lineInfo == m_other.lineInfo;
+                return ((node->stats.sectionInfo.name == m_other.name) &&
+                        (node->stats.sectionInfo.lineInfo == m_other.lineInfo));
             }
         private:
             void operator=( BySectionInfo const& );
@@ -9594,9 +9898,13 @@ namespace Catch {
 #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \
     namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
 
+// Deprecated - use the form without INTERNAL_
 #define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \
     namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
 
+#define CATCH_REGISTER_LISTENER( listenerType ) \
+    namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
+
 // #included from: ../internal/catch_xmlwriter.hpp
 #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
 
@@ -9829,20 +10137,6 @@ namespace Catch {
     };
 
 }
-// #included from: catch_reenable_warnings.h
-
-#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
-
-#ifdef __clang__
-#    ifdef __ICC // icpc defines the __clang__ macro
-#        pragma warning(pop)
-#    else
-#        pragma clang diagnostic pop
-#    endif
-#elif defined __GNUC__
-#    pragma GCC diagnostic pop
-#endif
-
 
 namespace Catch {
     class XmlReporter : public StreamingReporterBase {
@@ -9921,73 +10215,76 @@ namespace Catch {
         virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { }
 
         virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
-            const AssertionResult& assertionResult = assertionStats.assertionResult;
 
-            // Print any info messages in <Info> tags.
-            if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+            AssertionResult const& result = assertionStats.assertionResult;
+
+            bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
+
+            if( includeResults ) {
+                // Print any info messages in <Info> tags.
                 for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
-                        it != itEnd;
-                        ++it ) {
+                     it != itEnd;
+                     ++it ) {
                     if( it->type == ResultWas::Info ) {
                         m_xml.scopedElement( "Info" )
-                            .writeText( it->message );
+                                .writeText( it->message );
                     } else if ( it->type == ResultWas::Warning ) {
                         m_xml.scopedElement( "Warning" )
-                            .writeText( it->message );
+                                .writeText( it->message );
                     }
                 }
             }
 
             // Drop out if result was successful but we're not printing them.
-            if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+            if( !includeResults && result.getResultType() != ResultWas::Warning )
                 return true;
 
             // Print the expression if there is one.
-            if( assertionResult.hasExpression() ) {
+            if( result.hasExpression() ) {
                 m_xml.startElement( "Expression" )
-                    .writeAttribute( "success", assertionResult.succeeded() )
-                    .writeAttribute( "type", assertionResult.getTestMacroName() );
+                    .writeAttribute( "success", result.succeeded() )
+                    .writeAttribute( "type", result.getTestMacroName() );
 
-                writeSourceInfo( assertionResult.getSourceInfo() );
+                writeSourceInfo( result.getSourceInfo() );
 
                 m_xml.scopedElement( "Original" )
-                    .writeText( assertionResult.getExpression() );
+                    .writeText( result.getExpression() );
                 m_xml.scopedElement( "Expanded" )
-                    .writeText( assertionResult.getExpandedExpression() );
+                    .writeText( result.getExpandedExpression() );
             }
 
             // And... Print a result applicable to each result type.
-            switch( assertionResult.getResultType() ) {
+            switch( result.getResultType() ) {
                 case ResultWas::ThrewException:
                     m_xml.startElement( "Exception" );
-                    writeSourceInfo( assertionResult.getSourceInfo() );
-                    m_xml.writeText( assertionResult.getMessage() );
+                    writeSourceInfo( result.getSourceInfo() );
+                    m_xml.writeText( result.getMessage() );
                     m_xml.endElement();
                     break;
                 case ResultWas::FatalErrorCondition:
                     m_xml.startElement( "FatalErrorCondition" );
-                    writeSourceInfo( assertionResult.getSourceInfo() );
-                    m_xml.writeText( assertionResult.getMessage() );
+                    writeSourceInfo( result.getSourceInfo() );
+                    m_xml.writeText( result.getMessage() );
                     m_xml.endElement();
                     break;
                 case ResultWas::Info:
                     m_xml.scopedElement( "Info" )
-                        .writeText( assertionResult.getMessage() );
+                        .writeText( result.getMessage() );
                     break;
                 case ResultWas::Warning:
                     // Warning will already have been written
                     break;
                 case ResultWas::ExplicitFailure:
                     m_xml.startElement( "Failure" );
-                    writeSourceInfo( assertionResult.getSourceInfo() );
-                    m_xml.writeText( assertionResult.getMessage() );
+                    writeSourceInfo( result.getSourceInfo() );
+                    m_xml.writeText( result.getMessage() );
                     m_xml.endElement();
                     break;
                 default:
                     break;
             }
 
-            if( assertionResult.hasExpression() )
+            if( result.hasExpression() )
                 m_xml.endElement();
 
             return true;
@@ -10093,7 +10390,9 @@ namespace Catch {
     public:
         JunitReporter( ReporterConfig const& _config )
         :   CumulativeReporterBase( _config ),
-            xml( _config.stream() )
+            xml( _config.stream() ),
+            unexpectedExceptions( 0 ),
+            m_okToFail( false )
         {
             m_reporterPrefs.shouldRedirectStdOut = true;
         }
@@ -10119,8 +10418,11 @@ namespace Catch {
             CumulativeReporterBase::testGroupStarting( groupInfo );
         }
 
+        virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE {
+            m_okToFail = testCaseInfo.okToFail();
+        }
         virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
-            if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException )
+            if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
                 unexpectedExceptions++;
             return CumulativeReporterBase::assertionEnded( assertionStats );
         }
@@ -10285,6 +10587,7 @@ namespace Catch {
         std::ostringstream stdOutForSuite;
         std::ostringstream stdErrForSuite;
         unsigned int unexpectedExceptions;
+        bool m_okToFail;
     };
 
     INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
@@ -10299,25 +10602,6 @@ namespace Catch {
 
 namespace Catch {
 
-    namespace {
-        // Because formatting using c++ streams is stateful, drop down to C is required
-        // Alternatively we could use stringstream, but its performance is... not good.
-        std::string getFormattedDuration( double duration ) {
-            // Max exponent + 1 is required to represent the whole part
-            // + 1 for decimal point
-            // + 3 for the 3 decimal places
-            // + 1 for null terminator
-            const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
-            char buffer[maxDoubleSize];
-#ifdef _MSC_VER
-            sprintf_s(buffer, "%.3f", duration);
-#else
-            sprintf(buffer, "%.3f", duration);
-#endif
-            return std::string(buffer);
-        }
-    }
-
     struct ConsoleReporter : StreamingReporterBase {
         ConsoleReporter( ReporterConfig const& _config )
         :   StreamingReporterBase( _config ),
@@ -10339,18 +10623,15 @@ namespace Catch {
         virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE {
             AssertionResult const& result = _assertionStats.assertionResult;
 
-            bool printInfoMessages = true;
+            bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
 
-            // Drop out if result was successful and we're not printing those
-            if( !m_config->includeSuccessfulResults() && result.isOk() ) {
-                if( result.getResultType() != ResultWas::Warning )
-                    return false;
-                printInfoMessages = false;
-            }
+            // Drop out if result was successful but we're not printing them.
+            if( !includeResults && result.getResultType() != ResultWas::Warning )
+                return false;
 
             lazyPrint();
 
-            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+            AssertionPrinter printer( stream, _assertionStats, includeResults );
             printer.print();
             stream << std::endl;
             return true;
@@ -10440,7 +10721,11 @@ namespace Catch {
                     case ResultWas::ThrewException:
                         colour = Colour::Error;
                         passOrFail = "FAILED";
-                        messageLabel = "due to unexpected exception with message";
+                        messageLabel = "due to unexpected exception with ";
+                        if (_stats.infoMessages.size() == 1)
+                            messageLabel += "message";
+                        if (_stats.infoMessages.size() > 1)
+                            messageLabel += "messages";
                         break;
                     case ResultWas::FatalErrorCondition:
                         colour = Colour::Error;
@@ -10556,7 +10841,7 @@ namespace Catch {
             stream  << '\n' << getLineOfChars<'~'>() << '\n';
             Colour colour( Colour::SecondaryText );
             stream  << currentTestRunInfo->name
-                    << " is a Catch v"  << libraryVersion << " host application.\n"
+                    << " is a Catch v"  << libraryVersion() << " host application.\n"
                     << "Run with -? for options\n\n";
 
             if( m_config->rngSeed() != 0 )
@@ -10769,8 +11054,7 @@ namespace Catch {
             stream << "No test cases matched '" << spec << '\'' << std::endl;
         }
 
-        virtual void assertionStarting( AssertionInfo const& ) {
-        }
+        virtual void assertionStarting( AssertionInfo const& ) {}
 
         virtual bool assertionEnded( AssertionStats const& _assertionStats ) {
             AssertionResult const& result = _assertionStats.assertionResult;
@@ -10791,6 +11075,12 @@ namespace Catch {
             return true;
         }
 
+        virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE {
+            if (m_config->showDurations() == ShowDurations::Always) {
+                stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
+            }
+        }
+
         virtual void testRunEnded( TestRunStats const& _testRunStats ) {
             printTotals( _testRunStats.totals );
             stream << '\n' << std::endl;
@@ -10896,7 +11186,7 @@ namespace Catch {
                 stream << result.getSourceInfo() << ':';
             }
 
-            void printResultType( Colour::Code colour, std::string passOrFail ) const {
+            void printResultType( Colour::Code colour, std::string const& passOrFail ) const {
                 if( !passOrFail.empty() ) {
                     {
                         Colour colourGuard( colour );
@@ -10906,7 +11196,7 @@ namespace Catch {
                 }
             }
 
-            void printIssue( std::string issue ) const {
+            void printIssue( std::string const& issue ) const {
                 stream << ' ' << issue;
             }
 
@@ -11077,6 +11367,7 @@ namespace Catch {
     TestSpec::NamePattern::~NamePattern() {}
     TestSpec::TagPattern::~TagPattern() {}
     TestSpec::ExcludedPattern::~ExcludedPattern() {}
+    Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {}
 
     void Config::dummy() {}
 
@@ -11100,9 +11391,15 @@ namespace Catch {
 
 #ifndef __OBJC__
 
+#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
+// Standard C/C++ Win32 Unicode wmain entry point
+extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
+#else
 // Standard C/C++ main entry point
 int main (int argc, char * argv[]) {
-	int result = Catch::Session().run( argc, argv );
+#endif
+
+    int result = Catch::Session().run( argc, argv );
     return ( result < 0xff ? result : 0xff );
 }
 
@@ -11137,33 +11434,43 @@ int main (int argc, char * const argv[]) {
 // If this config identifier is defined then all CATCH macros are prefixed with CATCH_
 #ifdef CATCH_CONFIG_PREFIX_ALL
 
-#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" )
-#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
+#else
+#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr  )
+#endif
 
-#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" )
-#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" )
-#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" )
-#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" )
+#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr )
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr )
 
-#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" )
-#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" )
-#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" )
-#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" )
-#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" )
+#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr )
+#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr )
 
-#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CATCH_CHECK_THROWS" )
-#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" )
-#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" )
-#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" )
+#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr )
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr )
 
-#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" )
-#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" )
+#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
 
-#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
-#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg )
-#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
-#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
-#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#else
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif
+
+#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
+#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
+#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) )
+#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) )
 
 #ifdef CATCH_CONFIG_VARIADIC_MACROS
     #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
@@ -11171,16 +11478,18 @@ int main (int argc, char * const argv[]) {
     #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
     #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
     #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
-    #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ )
-    #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ )
+    #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+    #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+    #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 #else
     #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
     #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
     #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
     #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description )
     #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
-    #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg )
-    #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg )
+    #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg )
+    #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg )
+    #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg )
 #endif
 #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
 
@@ -11206,50 +11515,63 @@ int main (int argc, char * const argv[]) {
 // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
 #else
 
-#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" )
-#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr )
+#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
+
+#else
+#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr  )
+#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
+#endif
+
+#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr )
+#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr )
 
-#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" )
-#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" )
-#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" )
-#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" )
+#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr )
+#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr )
 
-#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" )
-#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" )
-#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" )
-#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" )
-#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" )
+#define CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr )
+#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr )
 
-#define CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" )
-#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" )
-#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" )
-#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" )
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
 
-#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" )
-#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#else
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif
 
-#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
-#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg )
-#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
-#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
-#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
+#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
+#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) )
+#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) )
 
 #ifdef CATCH_CONFIG_VARIADIC_MACROS
-    #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
-    #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
-    #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
-    #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
-    #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
-    #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ )
-    #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ )
+#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 #else
-    #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
     #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
     #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
     #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description )
     #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
-    #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg )
-    #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg )
+    #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg )
+    #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg )
+    #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg )
 #endif
 #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
 
@@ -11278,5 +11600,19 @@ int main (int argc, char * const argv[]) {
 
 using Catch::Detail::Approx;
 
+// #included from: internal/catch_reenable_warnings.h
+
+#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
+
+#ifdef __clang__
+#    ifdef __ICC // icpc defines the __clang__ macro
+#        pragma warning(pop)
+#    else
+#        pragma clang diagnostic pop
+#    endif
+#elif defined __GNUC__
+#    pragma GCC diagnostic pop
+#endif
+
 #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
 
diff --git a/test/renumber/input-sorted.osm b/test/renumber/input-sorted.osm
index 521dc5d..e23ea11 100644
--- a/test/renumber/input-sorted.osm
+++ b/test/renumber/input-sorted.osm
@@ -1,11 +1,11 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <osm version="0.6" upload="false" generator="testdata">
-  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="-11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
   <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
   <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
   <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <nd ref="10"/>
+    <nd ref="-11"/>
     <nd ref="11"/>
     <nd ref="12"/>
     <tag k="foo" v="bar"/>
diff --git a/test/sort/CMakeLists.txt b/test/sort/CMakeLists.txt
index 730f1d2..23f941f 100644
--- a/test/sort/CMakeLists.txt
+++ b/test/sort/CMakeLists.txt
@@ -10,8 +10,8 @@ function(check_sort2 _name _in1 _in2 _output)
     check_output(sort ${_name} "sort --generator=test -f osm sort/${_in1} sort/${_in2}" "sort/${_output}")
 endfunction()
 
-function(check_sort1 _name _input _output)
-    check_output(sort ${_name} "sort --generator=test -f osc sort/${_input}" "sort/${_output}")
+function(check_sort1 _name _input _output _format)
+    check_output(sort ${_name} "sort --generator=test -f ${_format} sort/${_input}" "sort/${_output}")
 endfunction()
 
 
@@ -20,7 +20,9 @@ endfunction()
 check_sort2(simple input-simple1.osm input-simple2.osm output-simple.osm)
 check_sort2(bounds input-bounds1.osm input-bounds2.osm output-bounds.osm)
 check_sort2(history input-history1.osm input-history2.osm output-history.osm)
-check_sort1(change input-change.osc output-change.osc)
+
+check_sort1(neg input-neg.osm output-neg.osm osm)
+check_sort1(change input-change.osc output-change.osc osc)
 
 
 #-----------------------------------------------------------------------------
diff --git a/test/renumber/input-sorted.osm b/test/sort/input-neg.osm
similarity index 60%
copy from test/renumber/input-sorted.osm
copy to test/sort/input-neg.osm
index 521dc5d..651bd97 100644
--- a/test/renumber/input-sorted.osm
+++ b/test/sort/input-neg.osm
@@ -1,23 +1,27 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <osm version="0.6" upload="false" generator="testdata">
-  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="-11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="12" role="m1"/>
+    <member type="way" ref="20" role="m2"/>
+  </relation>
   <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
-  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
-  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <nd ref="10"/>
-    <nd ref="11"/>
-    <nd ref="12"/>
-    <tag k="foo" v="bar"/>
-  </way>
+  <node id="-10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
     <nd ref="12"/>
-    <nd ref="14"/>
+    <nd ref="13"/>
+    <tag k="xyz" v="abc"/>
+  </way>
+  <way id="22" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="-11"/>
+    <nd ref="-10"/>
+    <tag k="xyz" v="abc"/>
+  </way>
+  <way id="-22" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="-10"/>
+    <nd ref="13"/>
     <tag k="xyz" v="abc"/>
   </way>
-  <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <member type="node" ref="12" role="m1"/>
-    <member type="node" ref="13" role="s1"/>
-    <member type="way" ref="20" role="m2"/>
-  </relation>
 </osm>
diff --git a/test/renumber/input-sorted.osm b/test/sort/output-neg.osm
similarity index 57%
copy from test/renumber/input-sorted.osm
copy to test/sort/output-neg.osm
index 521dc5d..cd6ff2b 100644
--- a/test/renumber/input-sorted.osm
+++ b/test/sort/output-neg.osm
@@ -1,23 +1,27 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<osm version="0.6" upload="false" generator="testdata">
+<osm version="0.6" generator="test">
+  <node id="-10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="-11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
   <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
   <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
   <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
-  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
-  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
-    <nd ref="10"/>
-    <nd ref="11"/>
-    <nd ref="12"/>
-    <tag k="foo" v="bar"/>
+  <way id="-22" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="-10"/>
+    <nd ref="13"/>
+    <tag k="xyz" v="abc"/>
   </way>
   <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
     <nd ref="12"/>
-    <nd ref="14"/>
+    <nd ref="13"/>
+    <tag k="xyz" v="abc"/>
+  </way>
+  <way id="22" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="-11"/>
+    <nd ref="-10"/>
     <tag k="xyz" v="abc"/>
   </way>
   <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
     <member type="node" ref="12" role="m1"/>
-    <member type="node" ref="13" role="s1"/>
     <member type="way" ref="20" role="m2"/>
   </relation>
 </osm>
diff --git a/test/tags-filter/test_unit.cpp b/test/tags-filter/test_unit.cpp
deleted file mode 100644
index e8c4322..0000000
--- a/test/tags-filter/test_unit.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-
-#include <sstream>
-
-#include "test.hpp" // IWYU pragma: keep
-
-#include "command_tags_filter.hpp"
-#include "util.hpp"
-
-static void test_filter(const std::string& expression, osmium::osm_entity_bits::type entities, const char* filter) {
-    const auto p = get_filter_expression(expression);
-    REQUIRE(p.first == entities);
-    REQUIRE(p.second == filter);
-}
-
-TEST_CASE("Get tags filter expression") {
-    test_filter("highway", osmium::osm_entity_bits::nwr, "highway");
-    test_filter("/highway", osmium::osm_entity_bits::nwr, "highway");
-    test_filter("n/highway", osmium::osm_entity_bits::node, "highway");
-    test_filter("w/highway", osmium::osm_entity_bits::way, "highway");
-    test_filter("r/highway", osmium::osm_entity_bits::relation, "highway");
-    test_filter("nw/highway", osmium::osm_entity_bits::node | osmium::osm_entity_bits::way, "highway");
-    test_filter("n/highway/foo", osmium::osm_entity_bits::node, "highway/foo");
-    REQUIRE_THROWS_AS(get_filter_expression("highway/foo"), argument_error);
-}
-
-osmium::StringMatcher get_matcher(std::string string);
-
-void test_matcher(const char* string, const char* print_out) {
-    std::stringstream ss;
-    ss << get_matcher(string);
-    REQUIRE(ss.str() == print_out);
-}
-
-TEST_CASE("get_matcher") {
-    test_matcher("foo", "equal[foo]");
-    test_matcher("", "equal[]");
-    test_matcher("foo*", "prefix[foo]");
-    test_matcher(" foo* ", "prefix[foo]");
-    test_matcher("*foo", "substring[foo]");
-    test_matcher("*foo*", "substring[foo]");
-    test_matcher(" *foo* ", "substring[foo]");
-    test_matcher("*", "always_true");
-    test_matcher(" * ", "always_true");
-    test_matcher("f*oo", "equal[f*oo]");
-    test_matcher("foo,bar", "list[[foo][bar]]");
-    test_matcher("foo,bar*,baz", "list[[foo][bar*][baz]]");
-    test_matcher("*foo,bar", "substring[foo,bar]");
-    test_matcher("foo ", "equal[foo]");
-    test_matcher(" foo", "equal[foo]");
-    test_matcher(" foo ", "equal[foo]");
-    test_matcher("foo    ", "equal[foo]");
-    test_matcher("    foo", "equal[foo]");
-    test_matcher("  foo ", "equal[foo]");
-    test_matcher("foo, bar, baz", "list[[foo][bar][baz]]");
-    test_matcher("  foo, bar   ,baz   ", "list[[foo][bar][baz]]");
-}
-
diff --git a/test/util/test_unit.cpp b/test/util/test_unit.cpp
index d72bc9e..ba8c47a 100644
--- a/test/util/test_unit.cpp
+++ b/test/util/test_unit.cpp
@@ -34,3 +34,88 @@ TEST_CASE("Get object types") {
     REQUIRE_THROWS_AS(get_types("nwx"), argument_error);
 }
 
+static void test_filter(const std::string& expression, osmium::osm_entity_bits::type entities, const char* filter) {
+    const auto p = get_filter_expression(expression);
+    REQUIRE(p.first == entities);
+    REQUIRE(p.second == filter);
+}
+
+TEST_CASE("Get tags filter expression") {
+    test_filter("highway", osmium::osm_entity_bits::nwr, "highway");
+    test_filter("/highway", osmium::osm_entity_bits::nwr, "highway");
+    test_filter("n/highway", osmium::osm_entity_bits::node, "highway");
+    test_filter("w/highway", osmium::osm_entity_bits::way, "highway");
+    test_filter("r/highway", osmium::osm_entity_bits::relation, "highway");
+    test_filter("nw/highway", osmium::osm_entity_bits::node | osmium::osm_entity_bits::way, "highway");
+    test_filter("n/highway/foo", osmium::osm_entity_bits::node, "highway/foo");
+    REQUIRE_THROWS_AS(get_filter_expression("highway/foo"), argument_error);
+}
+
+void test_strip_whitespace(const char* in, const char* out) {
+    std::string str{in};
+    strip_whitespace(str);
+    REQUIRE(str == out);
+}
+
+TEST_CASE("strip whitespace") {
+    test_strip_whitespace("foo", "foo");
+    test_strip_whitespace(" foo", "foo");
+    test_strip_whitespace("foo ", "foo");
+    test_strip_whitespace(" foo ", "foo");
+    test_strip_whitespace("   foo   ", "foo");
+    test_strip_whitespace("f o o", "f o o");
+}
+
+void test_string_matcher(const char* string, const char* print_out) {
+    std::stringstream ss;
+    ss << get_string_matcher(string);
+    REQUIRE(ss.str() == print_out);
+}
+
+TEST_CASE("get_string_matcher") {
+    test_string_matcher("foo", "equal[foo]");
+    test_string_matcher("", "equal[]");
+    test_string_matcher("foo*", "prefix[foo]");
+    test_string_matcher(" foo* ", "prefix[foo]");
+    test_string_matcher("*foo", "substring[foo]");
+    test_string_matcher("*foo*", "substring[foo]");
+    test_string_matcher(" *foo* ", "substring[foo]");
+    test_string_matcher("*", "always_true");
+    test_string_matcher(" * ", "always_true");
+    test_string_matcher("f*oo", "equal[f*oo]");
+    test_string_matcher("foo,bar", "list[[foo][bar]]");
+    test_string_matcher("foo,bar*,baz", "list[[foo][bar*][baz]]");
+    test_string_matcher("*foo,bar", "substring[foo,bar]");
+    test_string_matcher("foo ", "equal[foo]");
+    test_string_matcher(" foo", "equal[foo]");
+    test_string_matcher(" foo ", "equal[foo]");
+    test_string_matcher("foo    ", "equal[foo]");
+    test_string_matcher("    foo", "equal[foo]");
+    test_string_matcher("  foo ", "equal[foo]");
+    test_string_matcher("foo, bar, baz", "list[[foo][bar][baz]]");
+    test_string_matcher("  foo, bar   ,baz   ", "list[[foo][bar][baz]]");
+}
+
+bool test_tag_matcher(const char* expression, const char* key, const char* value) {
+    const auto matcher = get_tag_matcher(expression);
+    return matcher(key, value);
+}
+
+TEST_CASE("get_tag_matcher") {
+    REQUIRE(test_tag_matcher("foo", "foo", "bar"));
+    REQUIRE(test_tag_matcher("foo=bar", "foo", "bar"));
+    REQUIRE(test_tag_matcher("foo!=bar", "foo", "baz"));
+    REQUIRE_FALSE(test_tag_matcher("foo!=bar", "foo", "bar"));
+
+    REQUIRE(test_tag_matcher("highway=primary,secondary", "highway", "primary"));
+    REQUIRE(test_tag_matcher("highway=primary,secondary", "highway", "secondary"));
+    REQUIRE_FALSE(test_tag_matcher("highway=primary,secondary", "highway", "residential"));
+
+    REQUIRE(test_tag_matcher("landuse,natural", "landuse", "forest"));
+    REQUIRE(test_tag_matcher("landuse,natural", "natural", "wood"));
+    REQUIRE_FALSE(test_tag_matcher("landuse,natural", "highway", "motorway"));
+
+    REQUIRE(test_tag_matcher("addr:*", "addr:city", "Berlin"));
+    REQUIRE_FALSE(test_tag_matcher("addr:*", "addr", "Berlin"));
+}
+
diff --git a/zsh_completion/_osmium b/zsh_completion/_osmium
index c6739e1..fb9cb74 100644
--- a/zsh_completion/_osmium
+++ b/zsh_completion/_osmium
@@ -18,7 +18,7 @@ polygon_file_glob="'*.json *.geojson *.poly *.(osm|osh|osc|o5m|o5c|pbf|osm.pbf)
 
 _osmium() {
     local -a osmium_commands
-    osmium_commands=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes extract fileinfo getid help merge merge-changes renumber show sort tags-filter time-filter)
+    osmium_commands=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes export extract fileinfo getid help merge merge-changes renumber show sort tags-filter time-filter)
     if (( CURRENT > 2 )); then
         # Remember the subcommand name
         local cmd=${words[2]}
@@ -137,6 +137,8 @@ _osmium-changeset-filter() {
         '--after[changesets closed after]:timestamp:' \
         '-b[changesets opened before]:timestamp:' \
         '--before[changesets opened before]:timestamp:' \
+        '(--bbox)-B[bounding box]:changesets in bounding box (format\: LEFT,BOTTOM,RIGHT,TOP):' \
+        '(-B)--bbox[bounding box]:changesets in bounding box (format\: LEFT,BOTTOM,RIGHT,TOP):' \
         '(--progress)--no-progress[disable progress bar]' \
         '(--no-progress)--progress[enable progress bar]'
 }
@@ -180,6 +182,35 @@ _osmium-diff() {
         '*--object-type[read only objects of given output types]:OSM entity type:_osmium_object_type'
 }
 
+_osmium-export() {
+    _arguments : \
+        ${(f)"$(_osmium-common-options)"} \
+        ${(f)"$(_osmium-single-input-options)"} \
+        '--fsync[call fsync after writing output file(s)]' \
+        '(--output)-o[output file name]:output OSM file:_files -g "*.json *.geojson *.jsonseq *.geojsonseq"' \
+        '(-o)--output[output file name]:output OSM file:_files -g "*.json *.geojson *.jsonseq *.geojsonseq"' \
+        '(--overwrite)-O[allow overwriting of existing output file]' \
+        '(-O)--overwrite[allow overwriting of existing output file]' \
+        '(--output-format)-f[format of output file]:file format:_export_file_formats' \
+        '(-f)--output-format[format of output file]:file format:_export_file_formats' \
+        '(--config)-c[config file]:config file:_files -f "*.json"' \
+        '(-c)--config[config file]:config file:_files -f "*.json"' \
+        '(--show-errors)-e[output errors to stderr]' \
+        '(-e)--show-errors[output errors to stderr]' \
+        '(--stop-on-error)-E[stop on first geometry error]' \
+        '(-E)--stop-on-error[stop on first geometry error]' \
+        '(--index-type -I --show-index-types)-i[set index type]:index types:_osmium_export_index_types' \
+        '(-i -I --show-index-types)--index-type[set index type]:index types:_osmium_export_index_types' \
+        '(--help -h --verbose -v --input-format -F --fsync --output -o --overwrite -O --output-format -f --config -c --show-errors -e --stop-on-error -E --index-type -i --keep-untagged -n --omit-rs -r --add-unique-id -u --show-index-types)-I[show a list of available index types]' \
+        '(--help -h --verbose -v --input-format -F --fsync --output -o --overwrite -O --output-format -f --config -c --show-errors -e --stop-on-error -E --index-type -i --keep-untagged -n --omit-rs -r --add-unique-id -u -I)--show-index-types[show a list of available index types]' \
+        '(--keep-untagged)-n[keep untagged features]' \
+        '(-n)--keep-untagged[keep untagged features]' \
+        '(--omit-rs)-r[omit record separator when using geojsonseq format]' \
+        '(-r)--omit-rs[omit record separator when using geojsonseq format]' \
+        '(--add-unique-id)-u[add unique id]:unique id format:_export_id_type' \
+        '(-u)--add-unique-id[add unique id]:unique id format:_export_id_type'
+}
+
 _osmium-extract() {
     _arguments : \
         ${(f)"$(_osmium-common-options)"} \
@@ -379,6 +410,20 @@ _osmium_entity_type() {
         'changeset'
 }
 
+_export_file_formats() {
+    _values 'export file formats' \
+        'json[GeoJSON format]' \
+        'geojson[GeoJSON format]' \
+        'jsonseq[GeoJSON Text Sequence format]' \
+        'geojsonseq[GeoJSON Text Sequence format]'
+}
+
+_export_id_type() {
+    _values 'export unique id type' \
+        'counter' \
+        'type_id'
+}
+
 _osmium_extract_strategy() {
     _values 'extract strategy' \
         'simple' \
@@ -394,9 +439,13 @@ _osmium_index_types() {
     _values 'index types' $(osmium add-locations-to-ways --show-index-types)
 }
 
+_osmium_export_index_types() {
+    _values 'index types' $(osmium export --show-index-types)
+}
+
 _osmium-help() {
     local -a osmium_help_topics
-    osmium_help_topics=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes extract fileinfo getid help merge merge-changes renumber show sort tags-filter time-filter file-formats)
+    osmium_help_topics=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes export extract fileinfo getid help merge merge-changes renumber show sort tags-filter time-filter file-formats index-types)
     _describe -t osmium-help-topics 'osmium help topics' osmium_help_topics
 }
 

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



More information about the Pkg-grass-devel mailing list