[osm2pgsql] 01/05: Imported Upstream version 0.92.0~rc1+ds

Bas Couwenberg sebastic at debian.org
Wed Dec 7 23:22:08 UTC 2016


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

sebastic pushed a commit to branch master
in repository osm2pgsql.

commit a816bda9663ceabe617cf03c433fca453d0392e1
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Dec 8 00:11:38 2016 +0100

    Imported Upstream version 0.92.0~rc1+ds
---
 900913.sql                            |   1 -
 CMakeLists.txt                        |  15 +-
 README.md                             |  12 +-
 cmake/config.h.in                     |   3 -
 default.style                         |   2 +-
 docs/export.md                        |   2 +-
 docs/migrations.md                    |  16 ++
 docs/osm2pgsql.1                      |  17 +-
 docs/usage.md                         |  10 +-
 expire-tiles.cpp                      | 338 ++++++++++---------------
 expire-tiles.hpp                      |  57 +++--
 geometry-builder.cpp                  |  48 ++--
 id-tracker.cpp                        |   2 +-
 id-tracker.hpp                        |   2 +-
 middle-pgsql.cpp                      | 451 +++++++++++++++-------------------
 middle-pgsql.hpp                      |   9 +-
 multi.style.json                      |  10 -
 options.cpp                           |  19 +-
 options.hpp                           |   1 +
 osm2pgsql.cpp                         |   2 +-
 osmdata.cpp                           |   4 +-
 output-gazetteer.cpp                  |   2 +-
 output-gazetteer.hpp                  |  10 +-
 output-multi.cpp                      |  93 +++----
 output-multi.hpp                      |  17 +-
 output-pgsql.cpp                      | 102 ++++----
 output-pgsql.hpp                      |  13 +-
 output.cpp                            |  31 +--
 output.hpp                            |   6 +-
 parse-osmium.cpp                      |   2 +-
 pgsql.cpp                             |  10 +-
 pgsql.hpp                             |  11 +-
 reprojection.cpp                      |   2 +-
 reprojection.hpp                      |   2 +-
 sprompt.cpp                           |  10 +-
 style.lua                             | 428 +++++++++++++++++++-------------
 table.cpp                             | 150 ++++++-----
 table.hpp                             |   4 +-
 taginfo.cpp                           |  74 +++---
 taginfo.hpp                           |  21 ++
 taginfo_impl.hpp                      |  30 ++-
 tagtransform.cpp                      |   6 +-
 tests/CMakeLists.txt                  |  12 +
 tests/common-pg.cpp                   |  17 ++
 tests/common-pg.hpp                   |   2 +
 tests/regression-test.py              |  32 +--
 tests/test-expire-tiles.cpp           |  83 +++----
 tests/test-options-projection.cpp     | 114 +++++++++
 tests/test-output-pgsql-validgeom.cpp |  97 ++++++++
 tests/test-output-pgsql.cpp           |   2 +-
 tests/test-parse-xml2.cpp             |   2 +-
 tests/test-wildcard-match.cpp         |  61 +++++
 tests/test_output_multi_tags.json     |  25 +-
 tests/test_output_pgsql_validgeom.osm | 250 +++++++++++++++++++
 wildcmp.cpp                           | 115 ++-------
 wildcmp.hpp                           |   6 +-
 56 files changed, 1676 insertions(+), 1187 deletions(-)

diff --git a/900913.sql b/900913.sql
deleted file mode 100644
index 133099a..0000000
--- a/900913.sql
+++ /dev/null
@@ -1 +0,0 @@
-INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, srtext, proj4text)VALUES (900913,'EPSG',900913,'PROJCS["WGS84 / Simple Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS_1984", 6378137.0, 298.257223563]],PRIMEM["Greenwich", 0.0],UNIT["degree", 0.017453292519943295],AXIS["Longitude", EAST],AXIS["Latitude", NORTH]],PROJECTION["Mercator_1SP_Google"],PARAMETER["latitude_of_origin", 0.0],PARAMETER["central_meridian", 0.0],PARAMETER["scale_factor", 1.0],PARAMETER["false_easting" [...]
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8107a26..3fa79a6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 set(PACKAGE osm2pgsql)
 set(PACKAGE_NAME osm2pgsql)
-set(PACKAGE_VERSION 0.90.1-dev)
+set(PACKAGE_VERSION 0.92.0-RC1)
 
 cmake_minimum_required(VERSION 2.8.7)
 
@@ -11,6 +11,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 set(DATA_DIR \"${CMAKE_INSTALL_PREFIX}/share/osm2pgsql\")
 
 option(BUILD_TESTS "Build test suite" OFF)
+option(WITH_LUA    "Build with lua support" ON)
 
 if (NOT TESTING_TIMEOUT)
   set(TESTING_TIMEOUT 1200)
@@ -65,9 +66,6 @@ add_definitions( -DFIXED_POINT )
 CHECK_INCLUDE_FILES (termios.h HAVE_TERMIOS_H)
 CHECK_INCLUDE_FILES (libgen.h HAVE_LIBGEN_H)
 CHECK_INCLUDE_FILES (unistd.h HAVE_UNISTD_H)
-CHECK_INCLUDE_FILES (sys/wait.h HAVE_SYS_WAIT_H)
-CHECK_INCLUDE_FILES (sys/time.h HAVE_SYS_TIME_H)
-CHECK_INCLUDE_FILES (sys/mman.h HAVE_MMAP)
 
 if (WIN32)
   set(HAVE_LIBGEN_H FALSE)
@@ -77,7 +75,6 @@ CHECK_FUNCTION_EXISTS(lseek64 HAVE_LSEEK64)
 CHECK_FUNCTION_EXISTS(posix_fallocate HAVE_POSIX_FALLOCATE)
 CHECK_FUNCTION_EXISTS(posix_fadvise HAVE_POSIX_FADVISE)
 CHECK_FUNCTION_EXISTS(sync_file_range HAVE_SYNC_FILE_RANGE)
-CHECK_FUNCTION_EXISTS(fork HAVE_FORK)
 
 CHECK_TYPE_SIZE("off_t" SIZEOF_OFF_T)
 
@@ -98,12 +95,10 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
 find_package(Osmium REQUIRED COMPONENTS io geos proj)
 include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS})
 
-find_package(Lua)
-if (LUA_FOUND)
+if (WITH_LUA)
+  find_package(Lua REQUIRED)
   include_directories(${LUA_INCLUDE_DIR})
   set(HAVE_LUA 1)
-else()
-  message(STATUS "lua libraries not found. You will NOT be able to use lua scripts for tag transform.")
 endif()
 
 if (MSVC)
@@ -258,4 +253,4 @@ endif()
 
 install(TARGETS osm2pgsql DESTINATION bin)
 install(FILES docs/osm2pgsql.1 DESTINATION share/man/man1)
-install(FILES default.style empty.style 900913.sql DESTINATION share/osm2pgsql)
+install(FILES default.style empty.style DESTINATION share/osm2pgsql)
diff --git a/README.md b/README.md
index 59abb1d..6f15533 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,8 @@ Nominatim, or general analysis.
 
 ## Installing ##
 
+Most Linux distributions include osm2pgsql. It is also available on macOS with [Homebrew](http://brew.sh/). Unoffical builds for Windows are built by [AppVeyor](https://ci.appveyor.com/project/openstreetmap/osm2pgsql/history) but you need to find the right build artifacts.
+
 The latest source code is available in the OSM git repository on github
 and can be downloaded as follows:
 
@@ -33,7 +35,7 @@ to configure and build itself and requires
 Required libraries are
 
 * [expat](http://www.libexpat.org/)
-* [geos](http://geos.osgeo.org/)
+* [geos](http://trac.osgeo.org/geos)
 * [proj](http://proj.osgeo.org/)
 * [bzip2](http://www.bzip.org/)
 * [zlib](http://www.zlib.net/)
@@ -42,7 +44,7 @@ Required libraries are
 * [Lua](http://www.lua.org/) (Optional, used for [Lua tag transforms](docs/lua.md))
 
 It also requires access to a database server running
-[PostgreSQL](http://www.postgresql.org/) and [PostGIS](http://www.postgis.net/).
+[PostgreSQL](http://www.postgresql.org/) 9.1+ and [PostGIS](http://www.postgis.net/) 2.0+.
 
 Make sure you have installed the development packages for the libraries
 mentioned in the requirements section and a C++ compiler which supports C++11.
@@ -108,7 +110,7 @@ cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON
 
 ## Usage ##
 
-Osm2pgsql has one program, the executable itself, which has **43** command line
+Osm2pgsql has one program, the executable itself, which has **44** command line
 options.
 
 Before loading into a database, the database must be created and the PostGIS
@@ -141,10 +143,12 @@ where
 * ``<cache size>`` is about 75% of memory in MiB, to a maximum of about 30000. Additional RAM will not be used.
 * ``<flat nodes>`` is a location where a 24GiB file can be saved.
 
+Many different data files (e.g., .pbf) can be found at [planet.osm.org](http://planet.osm.org/).
+
 The databases from either of these commands can be used immediately by
 [Mapnik](http://mapnik.org/) for rendering maps with standard tools like
 [renderd/mod_tile](https://github.com/openstreetmap/mod_tile),
-[TileMill](https://www.mapbox.com/tilemill/), [Nik4](https://github.com/Zverik/Nik4),
+[TileMill](https://tilemill-project.github.io/tilemill/), [Nik4](https://github.com/Zverik/Nik4),
 among others. It can also be used for [spatial analysis](docs/analysis.md) or
 [shapefile exports](docs/export.md).
 
diff --git a/cmake/config.h.in b/cmake/config.h.in
index 292f0df..f247826 100644
--- a/cmake/config.h.in
+++ b/cmake/config.h.in
@@ -1,4 +1,3 @@
-#cmakedefine HAVE_FORK 1
 #cmakedefine HAVE_LSEEK64 1
 #cmakedefine HAVE_LUA 1
 #cmakedefine HAVE_POSIX_FADVISE 1
@@ -6,8 +5,6 @@
 #cmakedefine HAVE_SYNC_FILE_RANGE 1
 #cmakedefine HAVE_TERMIOS_H 1
 #cmakedefine HAVE_LIBGEN_H 1
-#cmakedefine HAVE_SYS_WAIT_H 1
-#cmakedefine HAVE_UNISTD_H 1
 #cmakedefine SIZEOF_OFF_T ${SIZEOF_OFF_T}
 
 #ifdef _MSC_VER
diff --git a/default.style b/default.style
index 5dd74bb..921404c 100644
--- a/default.style
+++ b/default.style
@@ -20,7 +20,7 @@
 # should be created, and if ways with the tag are assumed to be areas. The area
 # assumptions can be overridden with an area=yes/no tag
 #
-# polygon - Create a column for this tag, and objects the tag with are areas
+# polygon - Create a column for this tag, and objects with the tag are areas
 #
 # linear - Create a column for this tag
 #
diff --git a/docs/export.md b/docs/export.md
index ae38ded..2fef1b8 100644
--- a/docs/export.md
+++ b/docs/export.md
@@ -5,7 +5,7 @@ Osm2pgsql can be used in combination with [ogr2ogr](http://www.gdal.org/ogr2ogr.
 An example command to export to GeoJSON would be
 
     ogr2ogr -f "GeoJSON" roads.geojson -t_srs EPSG:4326 \
-      PG:"dbname=gis" -s_srs EPSG:900913 \
+      PG:"dbname=gis" -s_srs EPSG:3857 \
       -sql "SELECT name,highway,oneway,toll,way FROM planet_osm_line WHERE highway IS NOT NULL"
 
 Care should be taken if exporting to shapefiles, as characters may be present
diff --git a/docs/migrations.md b/docs/migrations.md
index af1c70a..0aa0fb6 100644
--- a/docs/migrations.md
+++ b/docs/migrations.md
@@ -4,6 +4,22 @@ Some osm2pgsql changes have slightly changed the database schema it expects. If
 updating an old database, a migration may be needed. The migrations here assume
 the default `planet_osm` prefix.
 
+It is frequently better to reimport as this will also recluster the tables and
+remove table or index bloat.
+
+## 0.91 default projection ##
+
+The default projection was moved from 900913 to 3857. This does not effect
+users using `-l` or `-E`, but if using no projection options or `-m` a
+migration is needed.
+
+```sql
+ALTER TABLE planet_osm_roads ALTER COLUMN way TYPE geometry(LineString,3857) USING ST_SetSRID(way,3857);
+ALTER TABLE planet_osm_point ALTER COLUMN way TYPE geometry(Point,3857) USING ST_SetSRID(way,3857);
+ALTER TABLE planet_osm_line ALTER COLUMN way TYPE geometry(LineString,3857) USING ST_SetSRID(way,3857);
+ALTER TABLE planet_osm_polygon ALTER COLUMN way TYPE geometry(Geometry,3857) USING ST_SetSRID(way,3857);
+```
+
 ## 0.88.0 z_order changes ##
 
 0.88.0 z_order logic was changed, requuiring an increase in z_order values. To
diff --git a/docs/osm2pgsql.1 b/docs/osm2pgsql.1
index b2b5ea1..b0fac79 100644
--- a/docs/osm2pgsql.1
+++ b/docs/osm2pgsql.1
@@ -1,4 +1,4 @@
-.TH OSM2PGSQL 1 "April 06, 2013"
+.TH OSM2PGSQL 1 "October 31, 2016"
 .\" Please adjust this date whenever revising the manpage.
 .SH NAME
 osm2pgsql \- Openstreetmap data to PostgreSQL converter.
@@ -181,7 +181,7 @@ Only keep objects that have a value in one of the columns
 \fB\  \fR\-\-hstore\-add\-index
 Create indices for the hstore columns during import.
 .TP
-\fB\-G\fR|\-\-melts\-geometry
+\fB\-G\fR|\-\-multi\-geometry
 Normally osm2pgsql splits multi\-part geometries into separate database rows per part.
 A single OSM id can therefore have several rows. With this option, PostgreSQL instead
 generates multi\-geometry features in the PostgreSQL tables.
@@ -195,10 +195,11 @@ assumption that post-processed Coastline Checker shape files will be used.
 OpenStreetMap data is defined in terms of nodes, ways and relations and not in
 terms of actual geometric features. Osm2pgsql therefore tries to build postgis
 geometries out of this data representation. However not all ways and relations
-correspond to valid postgis geometries (e.g. self intersecting polygons). By
-default osm2pgsql tries to automatically fix these geometries using ST_Buffer(0)
-around the invalid polygons. With this option, invalid polygons are instead
-simply dropped from the database.
+correspond to valid PostGIS geometries (e.g. self intersecting polygons). By
+default osm2pgsql tries to fix these geometries using buffer(0) around the
+invalid polygons. With this option, invalid polygons are instead simply dropped
+from the database. Even without this option, all polygons in the database should
+be valid.
 .TP
 \fB\  \fR\-\-unlogged
 Use postgresql's unlogged tables for storing data. This requires PostgreSQL 9.1
@@ -228,7 +229,7 @@ planet requires about 100GB in PostgreSQL, the same data is stored in only ~16GB
 the flat\-nodes mode. This can also increase the speed of applying diff files. This option
 activates the flat\-nodes mode and specifies the location of the database file. It is a
 single large > 16GB file. This mode is only recommended for full planet imports
-as it doesn't work well with small extracts. The default is disabled.
+as it doesn't work well with small imports. The default is disabled.
 .TP
 \fB\-h\fR|\-\-help
 Help information.
@@ -241,7 +242,7 @@ Verbose output.
 .SH SUPPORTED PROJECTIONS
 Latlong             (\-l) SRS:  4326 (none)
 .br
-Spherical Mercator  (\-m) SRS:900913 +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over
+Spherical Mercator  (\-m) SRS:3857 +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over
 .br
 EPSG-defined        (\-E) SRS: +init=epsg:(as given in parameter)
 .PP
diff --git a/docs/usage.md b/docs/usage.md
index 3bc9ea5..50fd1af 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -1,6 +1,6 @@
 # Command-line usage #
 
-Osm2pgsql has one program, the executable itself, which has **43** command line
+Osm2pgsql has one program, the executable itself, which has **44** command line
 options. A full list of options can be obtained with ``osm2pgsql -h -v``. This
 document provides an overview of options, and more importantly, why you might
 use them.
@@ -29,7 +29,7 @@ that only impact performance.
   ``--slim`` mode, this is just node positions while in non-slim it has to
   store information about ways and relations too. The maximum RAM it is useful
   to set this to in slim mode is 8 bytes * number of nodes / efficiency, where
-  efficiency ranges from 50% on small extracts to 80% for a planet.
+  efficiency ranges from 50% on small imports to 80% for a planet.
 
 * ``--number-processes`` sets the number of processes to use. This should
   typically be set to the number of CPU threads, but gains in speed are minimal
@@ -40,7 +40,7 @@ that only impact performance.
   but causes the last stages to take significantly longer.
 
 * ``--cache-strategy`` sets the cache strategy to use. The defaults are fine
-  here, and optimizied uses less RAM than the other options.
+  here, and optimized uses less RAM than the other options.
 
 ## Database options ##
 
@@ -79,7 +79,7 @@ database if the database server ever crashes, but are faster to import.
 
 ### Column options
 
-* ``--extra-attributes`` creates psudo-tags with OSM meta-data like user,
+* ``--extra-attributes`` creates pseudo-tags with OSM meta-data like user,
   last edited, and changeset. These also need to be added to the style file.
 
 * ``--style`` specifies the location of the style file. This defines what
@@ -124,7 +124,7 @@ osm2pgsql has five hstore options
 Either ``--hstore`` or ``--hstore-all`` when combined with ``--hstore-match-only``
 should give the same rows as no hstore, just with the additional hstore column.
 
-Hstore is used to give more flexability to use additional tags without
+Hstore is used to give more flexibility in using additional tags without
 reimporting the database, at the cost of a
 [less speed and more space.](http://paulnorman.ca/blog/2014/03/osm2pgsql-and-hstore/)
 
diff --git a/expire-tiles.cpp b/expire-tiles.cpp
index ea52341..40cbaa5 100644
--- a/expire-tiles.cpp
+++ b/expire-tiles.cpp
@@ -5,7 +5,7 @@
  *
  * Please refer to the OpenPisteMap expire_tiles.py script for a demonstration
  * of how to make use of the output:
- * https://subversion.nexusuk.org/trac/browser/openpistemap/trunk/scripts/expire_tiles.py
+ * http://subversion.nexusuk.org/projects/openpistemap/trunk/scripts/expire_tiles.py
  */
 
 #include <cmath>
@@ -23,9 +23,7 @@
 #define EARTH_CIRCUMFERENCE		40075016.68
 #define HALF_EARTH_CIRCUMFERENCE	(EARTH_CIRCUMFERENCE / 2)
 #define TILE_EXPIRY_LEEWAY		0.1		/* How many tiles worth of space to leave either side of a changed feature */
-#define EXPIRE_TILES_MAX_BBOX		20000		/* Maximum width or height of a bounding box (metres) */
 
-namespace {
 /*
  * We store the dirty tiles in an in-memory tree during runtime
  * and dump them out to a file at the end.  This allows us to easilly drop
@@ -41,233 +39,155 @@ namespace {
  * which are easilly accommodated.
  */
 
-int calc_complete(struct expire_tiles::tile * tile) {
-	int	c;
 
-	c = tile->complete[0][0];
-	c += tile->complete[0][1];
-	c += tile->complete[1][0];
-	c += tile->complete[1][1];
-	return c;
-}
-
-void destroy_tree(struct expire_tiles::tile * tree) {
-	if (! tree) return;
-	if (tree->subtiles[0][0]) destroy_tree(tree->subtiles[0][0]);
-	if (tree->subtiles[0][1]) destroy_tree(tree->subtiles[0][1]);
-	if (tree->subtiles[1][0]) destroy_tree(tree->subtiles[1][0]);
-	if (tree->subtiles[1][1]) destroy_tree(tree->subtiles[1][1]);
-	free(tree);
-}
-
-/*
- * Mark a tile as dirty.
- * Returns the number of subtiles which have all their children marked as dirty.
- */
-int _mark_tile(struct expire_tiles::tile ** tree, int x, int y, int zoom, int this_zoom) {
-	int	zoom_diff = zoom - this_zoom - 1;
-	int	rel_x;
-	int	rel_y;
-	int	complete;
-
-	if (! *tree) *tree = (struct expire_tiles::tile *)calloc(1, sizeof(**tree));
-	rel_x = (x >> zoom_diff) & 1;
-	rel_y = (y >> zoom_diff) & 1;
-	if (! (*tree)->complete[rel_x][rel_y]) {
-		if (zoom_diff <= 0) {
-			(*tree)->complete[rel_x][rel_y] = 1;
-		} else {
-			complete = _mark_tile(&((*tree)->subtiles[rel_x][rel_y]), x, y, zoom, this_zoom + 1);
-			if (complete >= 4) {
-				(*tree)->complete[rel_x][rel_y] = 1;
-				/* We can destroy the subtree to save memory now all the children are dirty */
-				destroy_tree((*tree)->subtiles[rel_x][rel_y]);
-				(*tree)->subtiles[rel_x][rel_y] = nullptr;
-			}
-		}
-	}
-	return calc_complete(*tree);
-}
-
-/*
- * Mark a tile as dirty.
- * Returns the number of subtiles which have all their children marked as dirty.
- */
-int mark_tile(struct expire_tiles::tile ** tree_head, int x, int y, int zoom) {
-	return _mark_tile(tree_head, x, y, zoom, 0);
-}
-
-void output_dirty_tile_impl(FILE * outfile, int x, int y, int zoom, int min_zoom, int &outcount) {
-	int	y_min;
-	int	x_iter;
-	int	y_iter;
-	int	x_max;
-	int	y_max;
-	int	out_zoom;
-	int	zoom_diff;
-
-	if (zoom > min_zoom) out_zoom = zoom;
-	else out_zoom = min_zoom;
-	zoom_diff = out_zoom - zoom;
-	y_min = y << zoom_diff;
-	x_max = (x + 1) << zoom_diff;
-	y_max = (y + 1) << zoom_diff;
-	for (x_iter = x << zoom_diff; x_iter < x_max; x_iter++) {
-		for (y_iter = y_min; y_iter < y_max; y_iter++) {
-			outcount++;
-            if ((outcount <= 1) || ((outcount % 1000) == 0)) {
-                fprintf(stderr, "\rWriting dirty tile list (%iK)", outcount / 1000);
-                fflush(stderr);
-            }
-            fprintf(outfile, "%i/%i/%i\n", out_zoom, x_iter, y_iter);
-		}
-	}
-}
-
-struct tile_output_file : public expire_tiles::tile_output {
-  tile_output_file(const std::string &expire_tiles_filename)
-    : outcount(0)
-    , outfile(fopen(expire_tiles_filename.c_str(), "a")) {
+struct tile_output_file : public expire_tiles::tile_output
+{
+  tile_output_file(const char *expire_tiles_filename, int zmin)
+  : outcount(0), min_zoom(zmin), outfile(fopen(expire_tiles_filename, "a"))
+  {
     if (outfile == nullptr) {
       fprintf(stderr, "Failed to open expired tiles file (%s).  Tile expiry list will not be written!\n", strerror(errno));
     }
   }
 
-  virtual ~tile_output_file() {
+  ~tile_output_file() {
     if (outfile) {
       fclose(outfile);
     }
   }
 
-  virtual void output_dirty_tile(int x, int y, int zoom, int min_zoom) {
-    output_dirty_tile_impl(outfile, x, y, zoom, min_zoom, outcount);
+  void output_dirty_tile(int x, int y, int zoom) override
+  {
+    if (outfile == nullptr) {
+        return;
+    }
+
+    int out_zoom = std::max(zoom, min_zoom);
+    int zoom_diff = out_zoom - zoom;
+    int x_max = (x + 1) << zoom_diff;
+    int y_max = (y + 1) << zoom_diff;
+
+    for (int x_iter = x << zoom_diff; x_iter < x_max; ++x_iter) {
+        for (int y_iter = y << zoom_diff; y_iter < y_max; ++y_iter) {
+            ++outcount;
+            if ((outcount % 1000) == 0) {
+                fprintf(stderr, "\rWriting dirty tile list (%iK)", outcount / 1000);
+            }
+            fprintf(outfile, "%i/%i/%i\n", out_zoom, x_iter, y_iter);
+        }
+    }
   }
 
 private:
   int outcount;
+  int min_zoom;
   FILE *outfile;
 };
 
-void _output_and_destroy_tree(expire_tiles::tile_output *output, struct expire_tiles::tile * tree, int x, int y, int this_zoom, int min_zoom) {
-	int	sub_x = x << 1;
-	int	sub_y = y << 1;
-        expire_tiles::tile_output *out;
 
-	if (! tree) return;
-
-	out = output;
-	if ((tree->complete[0][0]) && output) {
-		output->output_dirty_tile(sub_x + 0, sub_y + 0, this_zoom + 1, min_zoom);
-		out = nullptr;
-	}
-	if (tree->subtiles[0][0]) _output_and_destroy_tree(out, tree->subtiles[0][0], sub_x + 0, sub_y + 0, this_zoom + 1, min_zoom);
-
-	out = output;
-	if ((tree->complete[0][1]) && output) {
-		output->output_dirty_tile(sub_x + 0, sub_y + 1, this_zoom + 1, min_zoom);
-		out = nullptr;
-	}
-	if (tree->subtiles[0][1]) _output_and_destroy_tree(out, tree->subtiles[0][1], sub_x + 0, sub_y + 1, this_zoom + 1, min_zoom);
-
-	out = output;
-	if ((tree->complete[1][0]) && output) {
-		output->output_dirty_tile(sub_x + 1, sub_y + 0, this_zoom + 1, min_zoom);
-		out = nullptr;
-	}
-	if (tree->subtiles[1][0]) _output_and_destroy_tree(out, tree->subtiles[1][0], sub_x + 1, sub_y + 0, this_zoom + 1, min_zoom);
-
-	out = output;
-	if ((tree->complete[1][1]) && output) {
-		output->output_dirty_tile(sub_x + 1, sub_y + 1, this_zoom + 1, min_zoom);
-		out = nullptr;
-	}
-	if (tree->subtiles[1][1]) _output_and_destroy_tree(out, tree->subtiles[1][1], sub_x + 1, sub_y + 1, this_zoom + 1, min_zoom);
+int tile::mark_tile(int x, int y, int zoom, int this_zoom)
+{
+    int zoom_diff = zoom - this_zoom - 1;
+    int sub = ((x >> zoom_diff) & 1) << 1 | ((y >> zoom_diff) & 1);
+
+    if (!complete[sub]) {
+        if (zoom_diff <= 0) {
+            complete[sub] = 1;
+            subtiles[sub].reset();
+        } else {
+            if (!subtiles[sub])
+                subtiles[sub].reset(new tile);
+            int done = subtiles[sub]->mark_tile(x, y, zoom, this_zoom + 1);
+            if (done >= 4) {
+                complete[sub] = 1;
+                subtiles[sub].reset();
+            }
+        }
+    }
 
-	free(tree);
+    return num_complete();
 }
 
-// merge the two trees, destroying b in the process. returns the
-// number of completed subtrees.
-int _tree_merge(struct expire_tiles::tile **a,
-                struct expire_tiles::tile **b) {
-  if (*a == nullptr) {
-    *a = *b;
-    *b = nullptr;
-
-  } else if (*b != nullptr) {
-    for (int x = 0; x < 2; ++x) {
-      for (int y = 0; y < 2; ++y) {
-        // if b is complete on a subtree, then the merged tree must
-        // be complete too.
-        if ((*b)->complete[x][y]) {
-          (*a)->complete[x][y] = (*b)->complete[x][y];
-          destroy_tree((*a)->subtiles[x][y]);
-          (*a)->subtiles[x][y] = nullptr;
-
-          // but if a is already complete, don't bother moving across
-          // anything
-        } else if (!(*a)->complete[x][y]) {
-          int complete = _tree_merge(&((*a)->subtiles[x][y]), &((*b)->subtiles[x][y]));
-
-          if (complete >= 4) {
-            (*a)->complete[x][y] = 1;
-            destroy_tree((*a)->subtiles[x][y]);
-            (*a)->subtiles[x][y] = nullptr;
-          }
-        }
+void tile::output_and_destroy(expire_tiles::tile_output *output,
+                        int x, int y, int this_zoom)
+{
+    int sub_x = x << 1;
+    int sub_y = y << 1;
 
-        destroy_tree((*b)->subtiles[x][y]);
-        (*b)->subtiles[x][y] = nullptr;
-      }
+    for (int i = 0; i < 4; ++i) {
+        if (complete[i]) {
+            output->output_dirty_tile(sub_x + sub2x(i), sub_y + sub2y(i),
+                                      this_zoom + 1);
+        }
+        if (subtiles[i]) {
+            subtiles[i]->output_and_destroy(output,
+                                            sub_x + sub2x(i), sub_y + sub2y(i),
+                                            this_zoom + 1);
+            subtiles[i].reset();
+        }
     }
-  }
+}
 
-  // count the number complete, so we can return it
-  int a_complete = 0;
-  for (int x = 0; x < 2; ++x) {
-    for (int y = 0; y < 2; ++y) {
-      if ((*a != nullptr) && ((*a)->complete[x][y])) {
-        ++a_complete;
-      }
+int tile::merge(tile *other)
+{
+    for (int i = 0; i < 4; ++i) {
+        // if other is complete, then the merge tree must be complete too
+        if (other->complete[i]) {
+            complete[i] = 1;
+            subtiles[i].reset();
+        // if our subtree is complete don't bother moving anything
+        } else if (!complete[i]) {
+            if (subtiles[i]) {
+                if (other->subtiles[i]) {
+                    int done = subtiles[i]->merge(other->subtiles[i].get());
+                    if (done >= 4) {
+                        complete[i] = 1;
+                        subtiles[i].reset();
+                    }
+                }
+            } else {
+                subtiles[i] = std::move(other->subtiles[i]);
+            }
+        }
+        other->subtiles[i].reset();
     }
-  }
 
-  return a_complete;
+    return num_complete();
 }
 
-} // anonymous namespace
+void expire_tiles::output_and_destroy(tile_output *output)
+{
+    if (!dirty)
+        return;
 
-void expire_tiles::output_and_destroy(tile_output *output) {
-    _output_and_destroy_tree(output, dirty, 0, 0, 0, Options->expire_tiles_zoom_min);
-    dirty = nullptr;
+    dirty->output_and_destroy(output, 0, 0, 0);
+    dirty.reset();
 }
 
-void expire_tiles::output_and_destroy() {
-  if (Options->expire_tiles_zoom >= 0) {
-    tile_output_file output(Options->expire_tiles_filename);
+void expire_tiles::output_and_destroy(const char *filename, int minzoom)
+{
+  if (maxzoom >= 0) {
+    tile_output_file output(filename, minzoom);
 
     output_and_destroy(&output);
   }
 }
 
-expire_tiles::~expire_tiles() {
-  if (dirty != nullptr) {
-    destroy_tree(dirty);
-    dirty = nullptr;
-  }
+expire_tiles::expire_tiles(int max, double bbox, const std::shared_ptr<reprojection> &proj)
+: max_bbox(bbox), maxzoom(max), projection(proj)
+{
+    if (maxzoom >= 0) {
+        map_width = 1 << maxzoom;
+        tile_width = EARTH_CIRCUMFERENCE / map_width;
+    }
 }
 
-expire_tiles::expire_tiles(const struct options_t *options)
-    : map_width(0), tile_width(0), Options(options),
-      dirty(nullptr)
+void expire_tiles::expire_tile(int x, int y)
 {
-	if (Options->expire_tiles_zoom < 0) return;
-	map_width = 1 << Options->expire_tiles_zoom;
-	tile_width = EARTH_CIRCUMFERENCE / map_width;
-}
+    if (!dirty)
+        dirty.reset(new tile);
 
-void expire_tiles::expire_tile(int x, int y) {
-	mark_tile(&dirty, x, y, Options->expire_tiles_zoom);
+    dirty->mark_tile(x, y, maxzoom, 0);
 }
 
 int expire_tiles::normalise_tile_x_coord(int x) {
@@ -300,8 +220,8 @@ void expire_tiles::from_line(double lon_a, double lat_a, double lon_b, double la
 	int	y;
 	int	norm_x;
 
-    Options->projection->coords_to_tile(&tile_x_a, &tile_y_a, lon_a, lat_a, map_width);
-    Options->projection->coords_to_tile(&tile_x_b, &tile_y_b, lon_b, lat_b, map_width);
+    projection->coords_to_tile(&tile_x_a, &tile_y_a, lon_a, lat_a, map_width);
+    projection->coords_to_tile(&tile_x_b, &tile_y_b, lon_b, lat_b, map_width);
 
 	if (tile_x_a > tile_x_b) {
 		/* We always want the line to go from left to right - swap the ends if it doesn't */
@@ -376,7 +296,7 @@ int expire_tiles::from_bbox(double min_lon, double min_lat, double max_lon, doub
     double  tmp_x;
     double  tmp_y;
 
-	if (Options->expire_tiles_zoom < 0) return 0;
+	if (maxzoom < 0) return 0;
 
 	width = max_lon - min_lon;
 	height = max_lat - min_lat;
@@ -388,15 +308,16 @@ int expire_tiles::from_bbox(double min_lon, double min_lat, double max_lon, doub
 		return ret;
 	}
 
-	if (width > EXPIRE_TILES_MAX_BBOX) return -1;
-	if (height > EXPIRE_TILES_MAX_BBOX) return -1;
+    if (width > max_bbox || height > max_bbox) {
+        return -1;
+    }
 
 
 	/* Convert the box's Mercator coordinates into tile coordinates */
-        Options->projection->coords_to_tile(&tmp_x, &tmp_y, min_lon, max_lat, map_width);
+        projection->coords_to_tile(&tmp_x, &tmp_y, min_lon, max_lat, map_width);
         min_tile_x = tmp_x - TILE_EXPIRY_LEEWAY;
         min_tile_y = tmp_y - TILE_EXPIRY_LEEWAY;
-        Options->projection->coords_to_tile(&tmp_x, &tmp_y, max_lon, min_lat, map_width);
+        projection->coords_to_tile(&tmp_x, &tmp_y, max_lon, min_lat, map_width);
         max_tile_x = tmp_x + TILE_EXPIRY_LEEWAY;
         max_tile_y = tmp_y + TILE_EXPIRY_LEEWAY;
 	if (min_tile_x < 0) min_tile_x = 0;
@@ -414,7 +335,7 @@ int expire_tiles::from_bbox(double min_lon, double min_lat, double max_lon, doub
 
 void expire_tiles::from_nodes_line(const nodelist_t &nodes)
 {
-    if (Options->expire_tiles_zoom < 0 || nodes.empty())
+    if (maxzoom < 0 || nodes.empty())
         return;
 
     if (nodes.size() == 1) {
@@ -430,7 +351,7 @@ void expire_tiles::from_nodes_line(const nodelist_t &nodes)
  */
 void expire_tiles::from_nodes_poly(const nodelist_t &nodes, osmid_t osm_id)
 {
-    if (Options->expire_tiles_zoom < 0 || nodes.empty())
+    if (maxzoom < 0 || nodes.empty())
         return;
 
     double min_lon = nodes[0].lon;
@@ -466,7 +387,7 @@ void expire_tiles::from_xnodes_line(const multinodelist_t &xnodes)
 
 void expire_tiles::from_wkb(const char* wkb, osmid_t osm_id)
 {
-    if (Options->expire_tiles_zoom < 0) return;
+    if (maxzoom < 0) return;
 
     multinodelist_t xnodes;
     bool polygon;
@@ -491,7 +412,7 @@ void expire_tiles::from_wkb(const char* wkb, osmid_t osm_id)
  */
 int expire_tiles::from_db(table_t* table, osmid_t osm_id) {
     //bail if we dont care about expiry
-    if (Options->expire_tiles_zoom < 0)
+    if (maxzoom < 0)
         return -1;
 
     //grab the geom for this id
@@ -506,7 +427,12 @@ int expire_tiles::from_db(table_t* table, osmid_t osm_id) {
     return wkbs.get_count();
 }
 
-void expire_tiles::merge_and_destroy(expire_tiles &other) {
+void expire_tiles::merge_and_destroy(expire_tiles &other)
+{
+  if (!other.dirty) {
+      return;
+  }
+
   if (map_width != other.map_width) {
     throw std::runtime_error((boost::format("Unable to merge tile expiry sets when "
                                             "map_width does not match: %1% != %2%.")
@@ -519,8 +445,12 @@ void expire_tiles::merge_and_destroy(expire_tiles &other) {
                               % tile_width % other.tile_width).str());
   }
 
-  _tree_merge(&dirty, &other.dirty);
 
-  destroy_tree(other.dirty);
-  other.dirty = nullptr;
+  if (!dirty) {
+      dirty = std::move(other.dirty);
+  } else {
+      dirty->merge(other.dirty.get());
+  }
+
+  other.dirty.reset();
 }
diff --git a/expire-tiles.hpp b/expire-tiles.hpp
index 8a21653..840e4fd 100644
--- a/expire-tiles.hpp
+++ b/expire-tiles.hpp
@@ -1,18 +1,18 @@
 #ifndef EXPIRE_TILES_H
 #define EXPIRE_TILES_H
 
-#include "osmtypes.hpp"
+#include <memory>
 
-#include <boost/noncopyable.hpp>
+#include "osmtypes.hpp"
 
+class reprojection;
 class table_t;
-struct options_t;
+class tile;
 
-struct expire_tiles : public boost::noncopyable {
-    explicit expire_tiles(const options_t *options);
-    ~expire_tiles();
-
-    //TODO: copy constructor
+struct expire_tiles
+{
+    expire_tiles(int maxzoom, double maxbbox,
+                 const std::shared_ptr<reprojection> &projection);
 
     int from_bbox(double min_lon, double min_lat, double max_lon, double max_lat);
     void from_nodes_line(const nodelist_t &nodes);
@@ -20,25 +20,20 @@ struct expire_tiles : public boost::noncopyable {
     void from_wkb(const char* wkb, osmid_t osm_id);
     int from_db(table_t* table, osmid_t osm_id);
 
-    struct tile {
-        int	complete[2][2];
-        struct tile* subtiles[2][2];
-    };
-
     /* customisable tile output. this can be passed into the
      * `output_and_destroy` function to override output to a file.
      * this is primarily useful for testing.
      */
     struct tile_output {
-        virtual ~tile_output() {}
+        virtual ~tile_output() = default;
         // dirty a tile at x, y & zoom, and all descendants of that
         // tile at the given zoom if zoom < min_zoom.
-        virtual void output_dirty_tile(int x, int y, int zoom, int min_zoom) = 0;
+        virtual void output_dirty_tile(int x, int y, int zoom) = 0;
     };
 
     // output the list of expired tiles to a file. note that this
     // consumes the list of expired tiles destructively.
-    void output_and_destroy();
+    void output_and_destroy(const char *filename, int minzoom);
 
     // output the list of expired tiles using a `tile_output`
     // functor. this consumes the list of expired tiles destructively.
@@ -55,10 +50,34 @@ private:
     void from_xnodes_poly(const multinodelist_t &xnodes, osmid_t osm_id);
     void from_xnodes_line(const multinodelist_t &xnodes);
 
-    int map_width;
     double tile_width;
-    const options_t *Options;
-    struct tile *dirty;
+    double max_bbox;
+    int map_width;
+    int maxzoom;
+    std::shared_ptr<reprojection> projection;
+    std::unique_ptr<tile> dirty;
+};
+
+
+class tile
+{
+public:
+    int mark_tile(int x, int y, int zoom, int this_zoom);
+    void output_and_destroy(expire_tiles::tile_output *output,
+                            int x, int y, int this_zoom);
+    int merge(tile *other);
+
+private:
+    int sub2x(int sub) const { return sub >> 1; }
+    int sub2y(int sub) const { return sub & 1; }
+
+    int num_complete() const
+    {
+        return complete[0] + complete[1] + complete[2] + complete[3];
+    }
+
+    std::unique_ptr<tile> subtiles[4];
+    char complete[4] = {0, 0, 0, 0};
 };
 
 #endif
diff --git a/geometry-builder.cpp b/geometry-builder.cpp
index dd848a9..c9a07ef 100644
--- a/geometry-builder.cpp
+++ b/geometry-builder.cpp
@@ -209,11 +209,17 @@ geom_ptr geometry_builder::create_simple_poly(GeometryFactory &gf,
     std::unique_ptr<std::vector<Geometry *> > empty(new std::vector<Geometry *>);
     geom_ptr geom(gf.createPolygon(shell.release(), empty.release()));
 
+    if (geom->isEmpty()) {
+        throw std::runtime_error("Excluding empty polygon.");
+    }
     if (!geom->isValid()) {
         if (excludepoly) {
             throw std::runtime_error("Excluding broken polygon.");
         } else {
             geom = geom_ptr(geom->buffer(0));
+            if (geom->isEmpty() || !geom->isValid()) {
+                throw std::runtime_error("Excluding unrecoverable broken polygon.");
+            }
         }
     }
     geom->normalize(); // Fix direction of ring
@@ -491,25 +497,33 @@ geometry_builder::pg_geoms_t geometry_builder::build_polygons(const multinodelis
             if ((toplevelpolygons > 1) && enable_multi)
             {
                 geom_ptr multipoly(gf.createMultiPolygon(polygons.release()));
-                if (!multipoly->isValid() && !excludepoly) {
-                    multipoly = geom_ptr(multipoly->buffer(0));
-                }
-                multipoly->normalize();
 
-                if ((excludepoly == 0) || (multipoly->isValid())) {
-                    wkbs.emplace_back(multipoly.get(), true, projection);
+                if (!multipoly->isEmpty()) {
+                    if (!multipoly->isValid() && !excludepoly) {
+                        multipoly = geom_ptr(multipoly->buffer(0));
+                        multipoly->normalize();
+                        if (!!multipoly->isEmpty() && multipoly->isValid()) {
+                            wkbs.emplace_back(multipoly.get(), true, projection);
+                        }
+                    } else {
+                        multipoly->normalize();
+                        wkbs.emplace_back(multipoly.get(), true, projection);
+                    }
                 }
-            }
-            else
-            {
+            } else {
                 for(unsigned i=0; i<toplevelpolygons; i++) {
                     geom_ptr poly(polygons->at(i));
-                    if (!poly->isValid() && !excludepoly) {
-                        poly = geom_ptr(poly->buffer(0));
-                        poly->normalize();
-                    }
-                    if ((excludepoly == 0) || (poly->isValid())) {
-                        wkbs.emplace_back(poly.get(), true, projection);
+                    if (!poly->isEmpty()) {
+                        if (!poly->isValid() && !excludepoly) {
+                            poly = geom_ptr(poly->buffer(0));
+                            poly->normalize();
+                            if (!!poly->isEmpty() && poly->isValid()) {
+                                wkbs.emplace_back(poly.get(), true, projection);
+                            }
+                        } else {
+                            poly->normalize();
+                            wkbs.emplace_back(poly.get(), true, projection);
+                        }
                     }
                 }
             }
@@ -692,7 +706,7 @@ geometry_builder::pg_geoms_t geometry_builder::build_both(const multinodelist_t
                 }
                 multipoly->normalize();
 
-                if ((excludepoly == 0) || (multipoly->isValid())) {
+                if (!multipoly->isEmpty() && multipoly->isValid()) {
                     wkbs.emplace_back(multipoly.get(), true, projection);
                 }
             }
@@ -705,7 +719,7 @@ geometry_builder::pg_geoms_t geometry_builder::build_both(const multinodelist_t
                         poly = geom_ptr(poly->buffer(0));
                         poly->normalize();
                     }
-                    if (!excludepoly || (poly->isValid())) {
+                    if (!poly->isEmpty() && poly->isValid()) {
                         wkbs.emplace_back(poly.get(), true, projection);
                     }
                 }
diff --git a/id-tracker.cpp b/id-tracker.cpp
index e9ee29a..069f67b 100644
--- a/id-tracker.cpp
+++ b/id-tracker.cpp
@@ -169,7 +169,7 @@ osmid_t id_tracker::pop_mark() {
     return id;
 }
 
-size_t id_tracker::size() { return impl->count; }
+size_t id_tracker::size() const { return impl->count; }
 
 osmid_t id_tracker::last_returned() const { return impl->old_id; }
 
diff --git a/id-tracker.hpp b/id-tracker.hpp
index f76ffc6..4f68520 100644
--- a/id-tracker.hpp
+++ b/id-tracker.hpp
@@ -29,7 +29,7 @@ struct id_tracker : public boost::noncopyable {
      * Finds an osmid_t that is marked
      */
     osmid_t pop_mark();
-    size_t size();
+    size_t size() const;
     osmid_t last_returned() const;
 
     static bool is_valid(osmid_t);
diff --git a/middle-pgsql.cpp b/middle-pgsql.cpp
index cb34623..f065f13 100644
--- a/middle-pgsql.cpp
+++ b/middle-pgsql.cpp
@@ -8,19 +8,6 @@
 
 #include "config.h"
 
-#ifdef HAVE_SYS_WAIT_H
-#include <sys/wait.h>
-#endif
-
-#ifdef HAVE_MMAP
-#include <sys/mman.h>
-#ifndef  MAP_ANONYMOUS
-#ifdef MAP_ANON
-#define MAP_ANONYMOUS MAP_ANON
-#endif
-#endif
-#endif
-
 #ifdef _WIN32
 using namespace std;
 #endif
@@ -81,145 +68,7 @@ middle_pgsql_t::table_desc::table_desc(const char *name_,
       sql_conn(nullptr)
 {}
 
-#define HELPER_STATE_UNINITIALIZED -1
-#define HELPER_STATE_FORKED -2
-#define HELPER_STATE_RUNNING 0
-#define HELPER_STATE_FINISHED 1
-#define HELPER_STATE_CONNECTED 2
-#define HELPER_STATE_FAILED 3
-
 namespace {
-char *pgsql_store_nodes(const idlist_t &nds) {
-  static char *buffer;
-  static size_t buflen;
-
-  if( buflen <= nds.size() * 10 )
-  {
-    buflen = ((nds.size() * 10) | 4095) + 1;  // Round up to next page */
-    buffer = (char *)realloc( buffer, buflen );
-  }
-_restart:
-
-  char *ptr = buffer;
-  bool first = true;
-  *ptr++ = '{';
-  for (idlist_t::const_iterator it = nds.begin(); it != nds.end(); ++it)
-  {
-    if (!first)
-      *ptr++ = ',';
-    ptr += sprintf(ptr, "%" PRIdOSMID, *it);
-
-    if( (size_t) (ptr-buffer) > (buflen-20) ) // Almost overflowed? */
-    {
-      buflen <<= 1;
-      buffer = (char *)realloc( buffer, buflen );
-
-      goto _restart;
-    }
-    first = false;
-  }
-
-  *ptr++ = '}';
-  *ptr++ = 0;
-
-  return buffer;
-}
-
-// Special escape routine for escaping strings in array constants: double quote, backslash,newline, tab*/
-inline char *escape_tag( char *ptr, const std::string &in, bool escape )
-{
-  for (const char c: in)
-  {
-    switch(c)
-    {
-      case '"':
-        if( escape ) *ptr++ = '\\';
-        *ptr++ = '\\';
-        *ptr++ = '"';
-        break;
-      case '\\':
-        if( escape ) *ptr++ = '\\';
-        if( escape ) *ptr++ = '\\';
-        *ptr++ = '\\';
-        *ptr++ = '\\';
-        break;
-      case '\n':
-        if( escape ) *ptr++ = '\\';
-        *ptr++ = '\\';
-        *ptr++ = 'n';
-        break;
-      case '\r':
-        if( escape ) *ptr++ = '\\';
-        *ptr++ = '\\';
-        *ptr++ = 'r';
-        break;
-      case '\t':
-        if( escape ) *ptr++ = '\\';
-        *ptr++ = '\\';
-        *ptr++ = 't';
-        break;
-      default:
-        *ptr++ = c;
-        break;
-    }
-  }
-  return ptr;
-}
-
-// escape means we return '\N' for copy mode, otherwise we return just nullptr */
-const char *pgsql_store_tags(const taglist_t &tags, bool escape)
-{
-  static char *buffer;
-  static int buflen;
-
-  int countlist = tags.size();
-  if( countlist == 0 )
-  {
-    if( escape )
-      return "\\N";
-    else
-      return nullptr;
-  }
-
-  if( buflen <= countlist * 24 ) // LE so 0 always matches */
-  {
-    buflen = ((countlist * 24) | 4095) + 1;  // Round up to next page */
-    buffer = (char *)realloc( buffer, buflen );
-  }
-_restart:
-
-  char *ptr = buffer;
-  bool first = true;
-  *ptr++ = '{';
-
-  for (taglist_t::const_iterator it = tags.begin(); it != tags.end(); ++it)
-  {
-    int maxlen = (it->key.length() + it->value.length()) * 4;
-    if( (ptr+maxlen-buffer) > (buflen-20) ) // Almost overflowed? */
-    {
-      buflen <<= 1;
-      buffer = (char *)realloc( buffer, buflen );
-
-      goto _restart;
-    }
-    if( !first ) *ptr++ = ',';
-    *ptr++ = '"';
-    ptr = escape_tag(ptr, it->key, escape);
-    *ptr++ = '"';
-    *ptr++ = ',';
-    *ptr++ = '"';
-    ptr = escape_tag(ptr, it->value, escape);
-    *ptr++ = '"';
-
-    first = false;
-  }
-
-  *ptr++ = '}';
-  *ptr++ = 0;
-
-  return buffer;
-}
-
 // Decodes a portion of an array literal from postgres */
 // Argument should point to beginning of literal, on return points to delimiter */
 inline const char *decode_upto( const char *src, char *dst )
@@ -310,46 +159,135 @@ int pgsql_endCopy(middle_pgsql_t::table_desc *table)
 }
 } // anonymous namespace
 
-void middle_pgsql_t::local_nodes_set(const osmid_t& id, const double& lat,
-                                    const double& lon, const taglist_t &tags)
+
+void middle_pgsql_t::buffer_store_nodes(idlist_t const &nds)
 {
-    if( node_table->copyMode )
-    {
-      const char *tag_buf = pgsql_store_tags(tags,1);
-      int length = strlen(tag_buf) + 64;
-      char *buffer = (char *)alloca( length );
+    if (nds.size() == 0) {
+        copy_buffer += "{}";
+        return;
+    }
+
+    copy_buffer += "{";
+
+    for (auto const &it : nds) {
+        copy_buffer += std::to_string(it);
+        copy_buffer += ',';
+    }
+
+    copy_buffer[copy_buffer.size() - 1] = '}';
+}
+
+void middle_pgsql_t::buffer_store_string(std::string const &in, bool escape)
+{
+    for (char const c: in) {
+        switch (c) {
+            case '"':
+                if (escape) copy_buffer += "\\";
+                copy_buffer += "\\\"";
+                break;
+            case '\\':
+                if (escape) copy_buffer += "\\\\";
+                copy_buffer += "\\\\";
+                break;
+            case '\n':
+                if (escape) copy_buffer += "\\";
+                copy_buffer += "\\n";
+                break;
+            case '\r':
+                if (escape) copy_buffer += "\\";
+                copy_buffer += "\\r";
+                break;
+            case '\t':
+                if (escape) copy_buffer += "\\";
+                copy_buffer += "\\t";
+                break;
+            default:
+                copy_buffer += c;
+                break;
+        }
+    }
+}
+
+// escape means we return '\N' for copy mode, otherwise we return just nullptr
+void middle_pgsql_t::buffer_store_tags(taglist_t const &tags, bool escape)
+{
+    copy_buffer += "{";
+
+    bool first = true;
+    for (auto const &it : tags) {
+        if (!first) {
+            copy_buffer += ',';
+        }
+        copy_buffer += "\"";
+        buffer_store_string(it.key, escape);
+        copy_buffer += "\",\"";
+        buffer_store_string(it.value, escape);
+        copy_buffer += '"';
+
+        first = false;
+    }
+
+    copy_buffer += "}";
+}
+
+void middle_pgsql_t::buffer_correct_params(char const **param, size_t size)
+{
+    if (copy_buffer.c_str() != param[0]) {
+        auto diff = copy_buffer.c_str() - param[0];
+        for (size_t i = 0; i < size; ++i) {
+            if (param[i]) {
+                param[i] += diff;
+            }
+        }
+    }
+}
+
+void middle_pgsql_t::local_nodes_set(osmid_t id, double lat, double lon,
+                                     const taglist_t &tags)
+{
+    copy_buffer.reserve(tags.size() * 24 + 64);
+
+    bool copy = node_table->copyMode;
+    char delim = copy ? '\t' : '\0';
+    const char *paramValues[4] = { copy_buffer.c_str(), };
+
+    copy_buffer = std::to_string(id);
+    copy_buffer += delim;
+
 #ifdef FIXED_POINT
-      ramNode n(lon, lat);
-      if (snprintf(buffer, length, "%" PRIdOSMID "\t%d\t%d\t%s\n",
-                   id, n.int_lat(), n.int_lon(), tag_buf ) > (length-10)) {
-         throw std::runtime_error((boost::format("Buffer overflow node id %1%") % id).str());
-      }
+    ramNode n(lon, lat);
+    paramValues[1] = paramValues[0] + copy_buffer.size();
+    copy_buffer += std::to_string(n.int_lat());
+    copy_buffer += delim;
+
+    paramValues[2] = paramValues[0] + copy_buffer.size();
+    copy_buffer += std::to_string(n.int_lon());
+    copy_buffer += delim;
 #else
-      if (snprintf( buffer, length, "%" PRIdOSMID "\t%.10f\t%.10f\t%s\n", id, lat, lon, tag_buf ) > (length-10)) {
-        throw std::runtime_error((boost::format("Buffer overflow node id %1%") % id).str());
-      }
+    paramValues[1] = paramValues[0] + copy_buffer.size();
+    copy_buffer += std::to_string(lat);
+    copy_buffer += delim;
+
+    paramValues[2] = paramValues[0] + copy_buffer.size();
+    copy_buffer += std::to_string(lon);
+    copy_buffer += delim;
 #endif
-      pgsql_CopyData(__FUNCTION__, node_table->sql_conn, buffer);
+
+    if (tags.size() == 0) {
+        paramValues[3] = nullptr;
+        copy_buffer += "\\N";
     } else {
-        // Four params: id, lat, lon, tags */
-        const char *paramValues[4];
-        char *buffer = (char *)alloca(64);
-        char *ptr = buffer;
-        paramValues[0] = ptr;
-        ptr += sprintf( ptr, "%" PRIdOSMID, id ) + 1;
-        paramValues[1] = ptr;
-#ifdef FIXED_POINT
-        ramNode n(lon, lat);
-        ptr += sprintf(ptr, "%d", n.int_lat()) + 1;
-        paramValues[2] = ptr;
-        sprintf(ptr, "%d", n.int_lon());
-#else
-        ptr += sprintf( ptr, "%.10f", lat ) + 1;
-        paramValues[2] = ptr;
-        sprintf( ptr, "%.10f", lon );
-#endif
-        paramValues[3] = pgsql_store_tags(tags,0);
-        pgsql_execPrepared(node_table->sql_conn, "insert_node", 4, (const char * const *)paramValues, PGRES_COMMAND_OK);
+        paramValues[3] = paramValues[0] + copy_buffer.size();
+        buffer_store_tags(tags, copy);
+    }
+
+    if (copy) {
+        copy_buffer += '\n';
+        pgsql_CopyData(__FUNCTION__, node_table->sql_conn, copy_buffer);
+    } else {
+        buffer_correct_params(paramValues, 4);
+        pgsql_execPrepared(node_table->sql_conn, "insert_node", 4,
+                           (const char * const *)paramValues, PGRES_COMMAND_OK);
     }
 }
 
@@ -360,7 +298,7 @@ size_t middle_pgsql_t::local_nodes_get_list(nodelist_t &out, const idlist_t nds)
 
     char tmp[16];
 
-    char *tmp2 = (char *)malloc(sizeof(char) * nds.size() * 16);
+    char *tmp2 = static_cast<char *>(malloc(sizeof(char) * nds.size() * 16));
     if (tmp2 == nullptr) return 0; //failed to allocate memory, return */
 
 
@@ -519,28 +457,34 @@ void middle_pgsql_t::node_changed(osmid_t osm_id)
 
 void middle_pgsql_t::ways_set(osmid_t way_id, const idlist_t &nds, const taglist_t &tags)
 {
+    copy_buffer.reserve(nds.size() * 10 + tags.size() * 24 + 64);
+    bool copy = way_table->copyMode;
+    char delim = copy ? '\t' : '\0';
     // Three params: id, nodes, tags */
-    const char *paramValues[4];
-    char *buffer;
-
-    if (way_table->copyMode) {
-      const char *tag_buf = pgsql_store_tags(tags,1);
-      char *node_buf = pgsql_store_nodes(nds);
-      int length = strlen(tag_buf) + strlen(node_buf) + 64;
-      buffer = (char *)alloca(length);
-      if (snprintf( buffer, length, "%" PRIdOSMID "\t%s\t%s\n",
-              way_id, node_buf, tag_buf ) > (length-10)) {
-          throw std::runtime_error((boost::format("Buffer overflow way id %1%") % way_id).str());
-      }
-      pgsql_CopyData(__FUNCTION__, way_table->sql_conn, buffer);
+    const char *paramValues[4] = { copy_buffer.c_str(), };
+
+    copy_buffer = std::to_string(way_id);
+    copy_buffer += delim;
+
+    paramValues[1] = paramValues[0] + copy_buffer.size();
+    buffer_store_nodes(nds);
+    copy_buffer += delim;
+
+    if (tags.size() == 0) {
+        paramValues[2] = nullptr;
+        copy_buffer += "\\N";
+    } else {
+        paramValues[2] = paramValues[0] + copy_buffer.size();
+        buffer_store_tags(tags, copy);
+    }
+
+    if (copy) {
+        copy_buffer += '\n';
+        pgsql_CopyData(__FUNCTION__, way_table->sql_conn, copy_buffer);
     } else {
-        buffer = (char *)alloca(64);
-        char *ptr = buffer;
-        paramValues[0] = ptr;
-        sprintf(ptr, "%" PRIdOSMID, way_id);
-        paramValues[1] = pgsql_store_nodes(nds);
-        paramValues[2] = pgsql_store_tags(tags,0);
-        pgsql_execPrepared(way_table->sql_conn, "insert_way", 3, (const char * const *)paramValues, PGRES_COMMAND_OK);
+        buffer_correct_params(paramValues, 3);
+        pgsql_execPrepared(way_table->sql_conn, "insert_way", 3,
+                           (const char * const *)paramValues, PGRES_COMMAND_OK);
     }
 }
 
@@ -585,25 +529,24 @@ size_t middle_pgsql_t::ways_get_list(const idlist_t &ids, idlist_t &way_ids,
         return 0;
 
     char tmp[16];
-    char *tmp2;
+    std::unique_ptr<char[]> tmp2(new (std::nothrow) char[ids.size() * 16]);
     char const *paramValues[1];
 
-    tmp2 = (char *)malloc(sizeof(char)*ids.size()*16);
     if (tmp2 == nullptr) return 0; //failed to allocate memory, return */
 
     // create a list of ids in tmp2 to query the database  */
-    sprintf(tmp2, "{");
+    sprintf(tmp2.get(), "{");
     for(idlist_t::const_iterator it = ids.begin(); it != ids.end(); ++it) {
         snprintf(tmp, sizeof(tmp), "%" PRIdOSMID ",", *it);
-        strncat(tmp2,tmp, sizeof(char)*(ids.size()*16 - 2));
+        strncat(tmp2.get(), tmp, sizeof(char)*(ids.size()*16 - 2));
     }
-    tmp2[strlen(tmp2) - 1] = '}'; // replace last , with } to complete list of ids*/
+    tmp2[strlen(tmp2.get()) - 1] = '}'; // replace last , with } to complete list of ids*/
 
     pgsql_endCopy(way_table);
 
     PGconn *sql_conn = way_table->sql_conn;
 
-    paramValues[0] = tmp2;
+    paramValues[0] = tmp2.get();
     PGresult *res = pgsql_execPrepared(sql_conn, "get_way_list", 1, paramValues, PGRES_TUPLES_OK);
     int countPG = PQntuples(res);
 
@@ -643,7 +586,6 @@ size_t middle_pgsql_t::ways_get_list(const idlist_t &ids, idlist_t &way_ids,
     assert(way_ids.size() <= ids.size());
 
     PQclear(res);
-    free(tmp2);
 
     return way_ids.size();
 }
@@ -704,9 +646,6 @@ void middle_pgsql_t::way_changed(osmid_t osm_id)
 
 void middle_pgsql_t::relations_set(osmid_t id, const memberlist_t &members, const taglist_t &tags)
 {
-    // Params: id, way_off, rel_off, parts, members, tags */
-    const char *paramValues[6];
-    char *buffer;
     taglist_t member_list;
     char buf[64];
 
@@ -735,39 +674,53 @@ void middle_pgsql_t::relations_set(osmid_t id, const memberlist_t &members, cons
     all_parts.insert(all_parts.end(), way_parts.begin(), way_parts.end());
     all_parts.insert(all_parts.end(), rel_parts.begin(), rel_parts.end());
 
-    if (rel_table->copyMode)
-    {
-      char *tag_buf = strdup(pgsql_store_tags(tags,1));
-      const char *member_buf = pgsql_store_tags(member_list,1);
-      char *parts_buf = pgsql_store_nodes(all_parts);
-      int length = strlen(member_buf) + strlen(tag_buf) + strlen(parts_buf) + 64;
-      buffer = (char *)alloca(length);
-      if (snprintf( buffer, length, "%" PRIdOSMID "\t%zu\t%zu\t%s\t%s\t%s\n",
-                    id, node_parts.size(), node_parts.size() + way_parts.size(),
-                    parts_buf, member_buf, tag_buf ) > (length-10)) {
-          throw std::runtime_error((boost::format("Buffer overflow relation id %1%") % id).str());
-      }
-      free(tag_buf);
-      pgsql_CopyData(__FUNCTION__, rel_table->sql_conn, buffer);
+    copy_buffer.reserve(all_parts.size() * 10 + member_list.size() * 24
+                        + tags.size() * 24 + 64);
+
+    // Params: id, way_off, rel_off, parts, members, tags */
+    const char *paramValues[6] = { copy_buffer.c_str(), };
+    bool copy = rel_table->copyMode;
+    char delim = copy ? '\t' : '\0';
+
+    copy_buffer = std::to_string(id);
+    copy_buffer+= delim;
+
+    paramValues[1] = paramValues[0] + copy_buffer.size();
+    copy_buffer += std::to_string(node_parts.size());
+    copy_buffer+= delim;
+
+    paramValues[2] = paramValues[0] + copy_buffer.size();
+    copy_buffer += std::to_string(node_parts.size() + way_parts.size());
+    copy_buffer+= delim;
+
+    paramValues[3] = paramValues[0] + copy_buffer.size();
+    buffer_store_nodes(all_parts);
+    copy_buffer+= delim;
+
+    if (member_list.size() == 0) {
+        paramValues[4] = nullptr;
+        copy_buffer += "\\N";
     } else {
-        buffer = (char *)alloca(64);
-        char *ptr = buffer;
-        paramValues[0] = ptr;
-        ptr += sprintf(ptr, "%" PRIdOSMID, id ) + 1;
-        paramValues[1] = ptr;
-        ptr += sprintf(ptr, "%zu", node_parts.size() ) + 1;
-        paramValues[2] = ptr;
-        sprintf( ptr, "%zu", node_parts.size() + way_parts.size() );
-        paramValues[3] = pgsql_store_nodes(all_parts);
-        paramValues[4] = pgsql_store_tags(member_list,0);
-        if (paramValues[4]) {
-            paramValues[4] = strdup(paramValues[4]);
-        }
-        paramValues[5] = pgsql_store_tags(tags,0);
-        pgsql_execPrepared(rel_table->sql_conn, "insert_rel", 6, (const char * const *)paramValues, PGRES_COMMAND_OK);
-        if (paramValues[4]) {
-            free((void *)paramValues[4]);
-        }
+        paramValues[4] = paramValues[0] + copy_buffer.size();
+        buffer_store_tags(member_list, copy);
+    }
+    copy_buffer+= delim;
+
+    if (tags.size() == 0) {
+        paramValues[5] = nullptr;
+        copy_buffer += "\\N";
+    } else {
+        paramValues[5] = paramValues[0] + copy_buffer.size();
+        buffer_store_tags(tags, copy);
+    }
+
+    if (copy) {
+        copy_buffer+= '\n';
+        pgsql_CopyData(__FUNCTION__, rel_table->sql_conn, copy_buffer);
+    } else {
+        buffer_correct_params(paramValues, 6);
+        pgsql_execPrepared(rel_table->sql_conn, "insert_rel", 6,
+                           (const char * const *)paramValues, PGRES_COMMAND_OK);
     }
 }
 
diff --git a/middle-pgsql.hpp b/middle-pgsql.hpp
index 71c6280..1584570 100644
--- a/middle-pgsql.hpp
+++ b/middle-pgsql.hpp
@@ -87,7 +87,7 @@ private:
      * Sets up sql_conn for the table
      */
     void connect(table_desc& table);
-    void local_nodes_set(const osmid_t& id, const double& lat, const double& lon, const taglist_t &tags);
+    void local_nodes_set(osmid_t id, double lat, double lon, const taglist_t &tags);
     size_t local_nodes_get_list(nodelist_t &out, const idlist_t nds) const;
     void local_nodes_delete(osmid_t osm_id);
 
@@ -103,7 +103,14 @@ private:
 
     std::shared_ptr<id_tracker> ways_pending_tracker, rels_pending_tracker;
 
+    void buffer_store_nodes(idlist_t const &nodes);
+    void buffer_store_string(std::string const &in, bool escape);
+    void buffer_store_tags(taglist_t const &tags, bool escape);
+
+    void buffer_correct_params(char const **param, size_t size);
+
     bool build_indexes;
+    std::string copy_buffer;
 };
 
 #endif
diff --git a/multi.style.json b/multi.style.json
index 3c36aee..76522de 100644
--- a/multi.style.json
+++ b/multi.style.json
@@ -1,10 +1,3 @@
-/* This is an example multi-backend style file that comes with osm2pgsql.
- *
- * It contains comments so is invalid JSON but the parser doesn't complain.
- *
- * To use this file, pass it in as a style file with the --style switch, e.g.
- * with osm2pgsql --output multi --style multi.style.json
- */
 [
   {
     "name": "stops",
@@ -15,15 +8,12 @@
     "tagtransform-relation-function": "drop_all",
     "tagtransform-relation-member-function": "drop_all",
     "tags": [
-      /* We don't need a column for highway, since it's always highway=bus_stop */
       {"name": "name", "type": "text"},
-      /* multi-lingual names can be generated at import time */
       {"name": "name_en", "type": "text"},
       {"name": "name_fr", "type": "text"},
       {"name": "name_de", "type": "text"},
       {"name": "ref", "type": "text"},
       {"name": "operator", "type": "text"},
-      /* By using boolean columns stylesheet processing can be simplified */
       {"name": "shelter", "type": "boolean"},
       {"name": "bench", "type": "boolean"},
       {"name": "wheelchair", "type": "boolean"}
diff --git a/options.cpp b/options.cpp
index 6ee3c56..3c5c8ad 100644
--- a/options.cpp
+++ b/options.cpp
@@ -16,7 +16,7 @@
 
 namespace
 {
-    const char * short_options = "ab:cd:KhlmMp:suvU:WH:P:i:IE:C:S:e:o:O:xkjGz:r:V";
+    const char * short_options = "ab:cd:KhlmMp:suvU:WH:P:i:IE:C:S:e:o:B:O:xkjGz:r:V";
     const struct option long_options[] =
     {
         {"append",   0, 0, 'a'},
@@ -43,6 +43,7 @@ namespace
         {"style",    1, 0, 'S'},
         {"expire-tiles", 1, 0, 'e'},
         {"expire-output", 1, 0, 'o'},
+        {"expire-bbox-size", 1, 0, 214},
         {"output",   1, 0, 'O'},
         {"extra-attributes", 0, 0, 'x'},
         {"hstore", 0, 0, 'k'},
@@ -146,7 +147,7 @@ namespace
                         dense: caching strategy optimised for full planet import\n\
                         chunk: caching strategy optimised for non-contiguous \n\
                             memory allocation\n\
-                        sparse: caching strategy optimised for small extracts\n\
+                        sparse: caching strategy optimised for small imports\n\
                         optimized: automatically combines dense and sparse \n\
                             strategies for optimal storage efficiency. This may\n\
                             us twice as much virtual memory, but no more physical \n\
@@ -168,6 +169,8 @@ namespace
     Expiry options:\n\
        -e|--expire-tiles [min_zoom-]max_zoom    Create a tile expiry list.\n\
        -o|--expire-output filename  Output filename for expired tiles list.\n\
+          --expire-bbox-size Max size for a polygon to expire the whole polygon,\n\
+                             not just the boundary.\n\
     \n\
     Other options:\n\
        -b|--bbox        Apply a bounding box filter on the imported data\n\
@@ -201,7 +204,7 @@ namespace
        -K|--keep-coastlines Keep coastline data rather than filtering it out.\n\
                         By default natural=coastline tagged data will be discarded\n\
                         because renderers usually have shape files for them.\n\
-          --exclude-invalid-polygon   do not import polygons with invalid geometries.\n\
+          --exclude-invalid-polygon   do not attempt to recover invalid geometries.\n\
           --reproject-area   compute area column using spherical mercator coordinates.\n\
        -h|--help        Help information.\n\
        -v|--verbose     Verbose output.\n");
@@ -218,8 +221,8 @@ namespace
             printf("    <flat nodes> is a location where a 19GB file can be saved.\n");
             printf("\n");
             printf("A typical command to update a database imported with the above command is\n");
-            printf("    osmosis --rri workingDirectory=<osmosis dir> --simc --wx - \\\n");
-            printf("      | %s -a -d gis --slim -k --flat-nodes <flat nodes> \n", name);
+            printf("    osmosis --rri workingDirectory=<osmosis dir> --simc --wxc - \\\n");
+            printf("      | %s -a -d gis --slim -k --flat-nodes <flat nodes> -r xml -\n", name);
             printf("where\n");
             printf("    <flat nodes> is the same location as above.\n");
             printf("    <osmosis dir> is the location osmosis replication was initialized to.\n");
@@ -262,7 +265,8 @@ std::string database_options_t::conninfo() const
 options_t::options_t():
     prefix("planet_osm"), scale(DEFAULT_SCALE), projection(reprojection::create_projection(PROJ_SPHERE_MERC)), append(false), slim(false),
     cache(800), tblsmain_index(boost::none), tblsslim_index(boost::none), tblsmain_data(boost::none), tblsslim_data(boost::none), style(OSM2PGSQL_DATADIR "/default.style"),
-    expire_tiles_zoom(-1), expire_tiles_zoom_min(-1), expire_tiles_filename("dirty_tiles"), hstore_mode(HSTORE_NONE), enable_hstore_index(false),
+    expire_tiles_zoom(-1), expire_tiles_zoom_min(-1), expire_tiles_max_bbox(20000.0), expire_tiles_filename("dirty_tiles"),
+    hstore_mode(HSTORE_NONE), enable_hstore_index(false),
     enable_multi(false), hstore_columns(), keep_coastlines(false), parallel_indexing(true),
     #ifdef __amd64__
     alloc_chunkwise(ALLOC_SPARSE | ALLOC_DENSE),
@@ -377,6 +381,9 @@ options_t::options_t(int argc, char *argv[]): options_t()
         case 'o':
             expire_tiles_filename = optarg;
             break;
+        case 214:
+            expire_tiles_max_bbox = atof(optarg);
+            break;
         case 'O':
             output_backend = optarg;
             break;
diff --git a/options.hpp b/options.hpp
index f4e2fc3..c3bb54e 100644
--- a/options.hpp
+++ b/options.hpp
@@ -61,6 +61,7 @@ public:
     std::string style; ///< style file to use
     int expire_tiles_zoom; ///< Zoom level for tile expiry list
     int expire_tiles_zoom_min; ///< Minimum zoom level for tile expiry list
+    double expire_tiles_max_bbox; ///< Max bbox size in either dimension to expire full bbox for a polygon
     std::string expire_tiles_filename; ///< File name to output expired tiles list to
     int hstore_mode; ///< add an additional hstore column with objects key/value pairs, and what type of hstore column
     bool enable_hstore_index; ///< add an index on the hstore column
diff --git a/osm2pgsql.cpp b/osm2pgsql.cpp
index aff39b8..8b51a07 100644
--- a/osm2pgsql.cpp
+++ b/osm2pgsql.cpp
@@ -45,7 +45,7 @@
 
 int main(int argc, char *argv[])
 {
-    fprintf(stderr, "osm2pgsql SVN version %s (%zu bit id space)\n\n", VERSION, 8 * sizeof(osmid_t));
+    fprintf(stderr, "osm2pgsql version %s (%zu bit id space)\n\n", VERSION, 8 * sizeof(osmid_t));
     try
     {
         //parse the args into the different options members
diff --git a/osmdata.cpp b/osmdata.cpp
index f3c0675..ed7c9d2 100644
--- a/osmdata.cpp
+++ b/osmdata.cpp
@@ -285,7 +285,7 @@ struct pending_threaded_processor : public middle_t::pending_processor {
                 //done copying ways for now
                 clone_output->get()->commit();
                 //merge the pending from this threads copy of output back
-                original_output->get()->merge_pending_relations(*clone_output);
+                original_output->get()->merge_pending_relations(clone_output->get());
             }
         }
     }
@@ -344,7 +344,7 @@ struct pending_threaded_processor : public middle_t::pending_processor {
                 //done copying rels for now
                 clone_output->get()->commit();
                 //merge the expire tree from this threads copy of output back
-                original_output->get()->merge_expire_trees(*clone_output);
+                original_output->get()->merge_expire_trees(clone_output->get());
             }
         }
     }
diff --git a/output-gazetteer.cpp b/output-gazetteer.cpp
index 68e619b..1de4fa4 100644
--- a/output-gazetteer.cpp
+++ b/output-gazetteer.cpp
@@ -595,7 +595,7 @@ int output_gazetteer_t::connect() {
         ConnectionDelete = PQconnectdb(m_options.database_options.conninfo().c_str());
         if (PQstatus(ConnectionDelete) != CONNECTION_OK)
         {
-            std::cerr << "Connection to database failed: " << PQerrorMessage(Connection) << "\n";
+            std::cerr << "Connection to database failed: " << PQerrorMessage(ConnectionDelete) << "\n";
             return 1;
         }
 
diff --git a/output-gazetteer.hpp b/output-gazetteer.hpp
index 234ea3f..511a629 100644
--- a/output-gazetteer.hpp
+++ b/output-gazetteer.hpp
@@ -91,7 +91,13 @@ private:
     {
         for (const char c: in) {
             switch(c) {
-                case '\\': out += "\\\\\\\\\\\\\\\\"; break;
+                case '\\':
+                    // Tripple escaping required: string escaping leaves us
+                    // with 4 backslashes, COPY then reduces it to two, which
+                    // are then interpreted as a single backslash by the hash
+                    // parsing code.
+                    out += "\\\\\\\\";
+                    break;
                 case '\n':
                 case '\r':
                 case '\t':
@@ -266,8 +272,6 @@ private:
     // Need to be part of the class, so we have one per thread.
     boost::format single_fmt;
     boost::format point_fmt;
-
-    const static std::string NAME;
 };
 
 extern output_gazetteer_t out_gazetteer;
diff --git a/output-multi.cpp b/output-multi.cpp
index 8e61cc4..c92c752 100644
--- a/output-multi.cpp
+++ b/output-multi.cpp
@@ -27,27 +27,28 @@ output_multi_t::output_multi_t(const std::string &name,
                           m_options.append, m_options.slim, m_options.droptemp,
                           m_options.hstore_mode, m_options.enable_hstore_index,
                           m_options.tblsmain_data, m_options.tblsmain_index)),
-      ways_pending_tracker(new id_tracker()), ways_done_tracker(new id_tracker()), rels_pending_tracker(new id_tracker()),
-      m_expire(new expire_tiles(&m_options)) {
-}
+      ways_done_tracker(new id_tracker()),
+      m_expire(m_options.expire_tiles_zoom, m_options.expire_tiles_max_bbox,
+               m_options.projection)
+{}
 
 output_multi_t::output_multi_t(const output_multi_t& other):
     output_t(other.m_mid, other.m_options), m_tagtransform(new tagtransform(&m_options)), m_export_list(new export_list(*other.m_export_list)),
     m_processor(other.m_processor), m_osm_type(other.m_osm_type), m_table(new table_t(*other.m_table)),
-    ways_pending_tracker(new id_tracker()), ways_done_tracker(new id_tracker()), rels_pending_tracker(new id_tracker()),
-    m_expire(new expire_tiles(&m_options)) {
-}
+    //NOTE: we need to know which ways were used by relations so each thread
+    //must have a copy of the original marked done ways, its read only so its ok
+    ways_done_tracker(other.ways_done_tracker),
+    m_expire(m_options.expire_tiles_zoom, m_options.expire_tiles_max_bbox,
+             m_options.projection)
+{}
 
 
-output_multi_t::~output_multi_t() {
-}
+output_multi_t::~output_multi_t() = default;
 
-std::shared_ptr<output_t> output_multi_t::clone(const middle_query_t* cloned_middle) const{
-    output_multi_t *clone = new output_multi_t(*this);
+std::shared_ptr<output_t> output_multi_t::clone(const middle_query_t* cloned_middle) const
+{
+    auto *clone = new output_multi_t(*this);
     clone->m_mid = cloned_middle;
-    //NOTE: we need to know which ways were used by relations so each thread
-    //must have a copy of the original marked done ways, its read only so its ok
-    clone->ways_done_tracker = ways_done_tracker;
     return std::shared_ptr<output_t>(clone);
 }
 
@@ -57,11 +58,11 @@ int output_multi_t::start() {
 }
 
 size_t output_multi_t::pending_count() const {
-    return ways_pending_tracker->size() + rels_pending_tracker->size();
+    return ways_pending_tracker.size() + rels_pending_tracker.size();
 }
 
 void output_multi_t::enqueue_ways(pending_queue_t &job_queue, osmid_t id, size_t output_id, size_t& added) {
-    osmid_t const prev = ways_pending_tracker->last_returned();
+    osmid_t const prev = ways_pending_tracker.last_returned();
     if (id_tracker::is_valid(prev) && prev >= id) {
         if (prev > id) {
             job_queue.push(pending_job_t(id, output_id));
@@ -77,7 +78,7 @@ void output_multi_t::enqueue_ways(pending_queue_t &job_queue, osmid_t id, size_t
     }
 
     //grab the first one or bail if its not valid
-    osmid_t popped = ways_pending_tracker->pop_mark();
+    osmid_t popped = ways_pending_tracker.pop_mark();
     if(!id_tracker::is_valid(popped))
         return;
 
@@ -87,7 +88,7 @@ void output_multi_t::enqueue_ways(pending_queue_t &job_queue, osmid_t id, size_t
             job_queue.push(pending_job_t(popped, output_id));
             added++;
         }
-        popped = ways_pending_tracker->pop_mark();
+        popped = ways_pending_tracker.pop_mark();
     }
 
     //make sure to get this one as well and move to the next
@@ -114,7 +115,7 @@ int output_multi_t::pending_way(osmid_t id, int exists) {
 }
 
 void output_multi_t::enqueue_relations(pending_queue_t &job_queue, osmid_t id, size_t output_id, size_t& added) {
-    osmid_t const prev = rels_pending_tracker->last_returned();
+    osmid_t const prev = rels_pending_tracker.last_returned();
     if (id_tracker::is_valid(prev) && prev >= id) {
         if (prev > id) {
             job_queue.push(pending_job_t(id, output_id));
@@ -130,7 +131,7 @@ void output_multi_t::enqueue_relations(pending_queue_t &job_queue, osmid_t id, s
     }
 
     //grab the first one or bail if its not valid
-    osmid_t popped = rels_pending_tracker->pop_mark();
+    osmid_t popped = rels_pending_tracker.pop_mark();
     if(!id_tracker::is_valid(popped))
         return;
 
@@ -138,7 +139,7 @@ void output_multi_t::enqueue_relations(pending_queue_t &job_queue, osmid_t id, s
     while (popped < id) {
         job_queue.push(pending_job_t(popped, output_id));
         added++;
-        popped = rels_pending_tracker->pop_mark();
+        popped = rels_pending_tracker.pop_mark();
     }
 
     //make sure to get this one as well and move to the next
@@ -163,10 +164,13 @@ int output_multi_t::pending_relation(osmid_t id, int exists) {
     return ret;
 }
 
-void output_multi_t::stop() {
+void output_multi_t::stop()
+{
     m_table->stop();
-    m_expire->output_and_destroy();
-    m_expire.reset();
+    if (m_options.expire_tiles_zoom_min >= 0) {
+        m_expire.output_and_destroy(m_options.expire_tiles_filename.c_str(),
+                                    m_options.expire_tiles_zoom_min);
+    }
 }
 
 void output_multi_t::commit() {
@@ -269,7 +273,7 @@ int output_multi_t::process_node(osmid_t id, double lat, double lon, const tagli
         //grab its geom
         auto geom = m_processor->process_node(lat, lon);
         if (geom.valid()) {
-            m_expire->from_bbox(lon, lat, lon, lat);
+            m_expire.from_bbox(lon, lat, lon, lat);
             copy_node_to_table(id, geom.geom, outtags);
         }
     }
@@ -284,7 +288,7 @@ int output_multi_t::reprocess_way(osmid_t id, const nodelist_t &nodes, const tag
         way_delete(id);
         const std::vector<osmid_t> rel_ids = m_mid->relations_using_way(id);
         for (std::vector<osmid_t>::const_iterator itr = rel_ids.begin(); itr != rel_ids.end(); ++itr) {
-            rels_pending_tracker->mark(*itr);
+            rels_pending_tracker.mark(*itr);
         }
     }
 
@@ -320,7 +324,7 @@ int output_multi_t::process_way(osmid_t id, const idlist_t &nodes, const taglist
             //if we are also interested in relations we need to mark
             //this way pending just in case it shows up in one
             if (m_processor->interests(geometry_processor::interest_relation)) {
-                ways_pending_tracker->mark(id);
+                ways_pending_tracker.mark(id);
             } else {
                 // We wouldn't be interested in this as a relation, so no need to mark it pending.
                 // TODO: Does this imply anything for non-multipolygon relations?
@@ -381,7 +385,7 @@ int output_multi_t::process_relation(osmid_t id, const memberlist_t &members,
             for (const auto geom: geoms) {
                 //TODO: we actually have the nodes in the m_relation_helper and could use them
                 //instead of having to reparse the wkb in the expiry code
-                m_expire->from_wkb(geom.geom.c_str(), -id);
+                m_expire.from_wkb(geom.geom.c_str(), -id);
                 //what part of the code relies on relation members getting negative ids?
                 copy_to_table(-id, geom, outtags, make_polygon);
             }
@@ -422,7 +426,7 @@ void output_multi_t::copy_to_table(const osmid_t id, const geometry_builder::pg_
     if (geom.is_polygon()) {
         // It's a polygon table (implied by it turning into a poly),
         // and it got formed into a polygon, so expire as a polygon and write the geom
-        m_expire->from_nodes_poly(m_way_helper.node_cache, id);
+        m_expire.from_nodes_poly(m_way_helper.node_cache, id);
         if (geom.area > 0.0) {
             char tmp[32];
             snprintf(tmp, sizeof(tmp), "%g", geom.area);
@@ -433,33 +437,34 @@ void output_multi_t::copy_to_table(const osmid_t id, const geometry_builder::pg_
         // Linestring
         if (!polygon) {
             // non-polygons are okay
-            m_expire->from_nodes_line(m_way_helper.node_cache);
+            m_expire.from_nodes_line(m_way_helper.node_cache);
             m_table->write_row(id, tags, geom.geom);
         }
     }
 }
 
 void output_multi_t::delete_from_output(osmid_t id) {
-    if(m_expire->from_db(m_table.get(), id))
+    if(m_expire.from_db(m_table.get(), id))
         m_table->delete_row(id);
 }
 
-void output_multi_t::merge_pending_relations(std::shared_ptr<output_t> other) {
-    std::shared_ptr<id_tracker> tracker = other.get()->get_pending_relations();
-    osmid_t id;
-    while(tracker.get() && id_tracker::is_valid((id = tracker->pop_mark()))){
-        rels_pending_tracker->mark(id);
+void output_multi_t::merge_pending_relations(output_t *other)
+{
+    auto *omulti = dynamic_cast<output_multi_t *>(other);
+
+    if (omulti) {
+        osmid_t id;
+        while (id_tracker::is_valid((id = omulti->rels_pending_tracker.pop_mark()))) {
+            rels_pending_tracker.mark(id);
+        }
     }
 }
 
-void output_multi_t::merge_expire_trees(std::shared_ptr<output_t> other) {
-    if(other->get_expire_tree().get())
-        m_expire->merge_and_destroy(*other.get()->get_expire_tree());
-}
+void output_multi_t::merge_expire_trees(output_t *other)
+{
+    auto *omulti = dynamic_cast<output_multi_t *>(other);
 
-std::shared_ptr<id_tracker> output_multi_t::get_pending_relations() {
-    return rels_pending_tracker;
-}
-std::shared_ptr<expire_tiles> output_multi_t::get_expire_tree() {
-    return m_expire;
+    if (omulti) {
+        m_expire.merge_and_destroy(omulti->m_expire);
+    }
 }
diff --git a/output-multi.hpp b/output-multi.hpp
index 3877152..0054695 100644
--- a/output-multi.hpp
+++ b/output-multi.hpp
@@ -7,6 +7,8 @@
 #ifndef OUTPUT_MULTI_HPP
 #define OUTPUT_MULTI_HPP
 
+#include "expire-tiles.hpp"
+#include "id-tracker.hpp"
 #include "osmtypes.hpp"
 #include "output.hpp"
 #include "geometry-processor.hpp"
@@ -17,9 +19,7 @@
 
 class table_t;
 class tagtransform;
-struct expire_tiles;
 struct export_list;
-struct id_tracker;
 struct middle_query_t;
 struct options_t;
 
@@ -58,10 +58,8 @@ public:
 
     size_t pending_count() const;
 
-    void merge_pending_relations(std::shared_ptr<output_t> other);
-    void merge_expire_trees(std::shared_ptr<output_t> other);
-    virtual std::shared_ptr<id_tracker> get_pending_relations();
-    virtual std::shared_ptr<expire_tiles> get_expire_tree();
+    void merge_pending_relations(output_t *other);
+    void merge_expire_trees(output_t *other);
 
 protected:
 
@@ -78,12 +76,11 @@ protected:
     std::shared_ptr<geometry_processor> m_processor;
     const OsmType m_osm_type;
     std::unique_ptr<table_t> m_table;
-    std::shared_ptr<id_tracker> ways_pending_tracker, ways_done_tracker, rels_pending_tracker;
-    std::shared_ptr<expire_tiles> m_expire;
+    id_tracker ways_pending_tracker, rels_pending_tracker;
+    std::shared_ptr<id_tracker> ways_done_tracker;
+    expire_tiles m_expire;
     way_helper m_way_helper;
     relation_helper m_relation_helper;
-
-    const static std::string NAME;
 };
 
 #endif
diff --git a/output-pgsql.cpp b/output-pgsql.cpp
index c5388a7..be1ac08 100644
--- a/output-pgsql.cpp
+++ b/output-pgsql.cpp
@@ -67,7 +67,7 @@ int output_pgsql_t::pgsql_out_node(osmid_t id, const taglist_t &tags, double nod
     if (m_tagtransform->filter_node_tags(tags, *m_export_list.get(), outtags))
         return 1;
 
-    expire->from_bbox(node_lon, node_lat, node_lon, node_lat);
+    expire.from_bbox(node_lon, node_lat, node_lon, node_lat);
     m_tables[t_point]->write_node(id, outtags, node_lat, node_lon);
 
     return 0;
@@ -98,14 +98,14 @@ int output_pgsql_t::pgsql_out_way(osmid_t id, taglist_t &outtags,
     for (const auto& wkb: wkbs) {
         /* FIXME: there should be a better way to detect polygons */
         if (wkb.is_polygon()) {
-            expire->from_nodes_poly(nodes, id);
+            expire.from_nodes_poly(nodes, id);
             if ((wkb.area > 0.0) && m_enable_way_area) {
                 snprintf(tmp, sizeof(tmp), "%g", wkb.area);
                 outtags.push_override(tag_t("way_area", tmp));
             }
             m_tables[t_poly]->write_row(id, outtags, wkb.geom);
         } else {
-            expire->from_nodes_line(nodes);
+            expire.from_nodes_line(nodes);
             m_tables[t_line]->write_row(id, outtags, wkb.geom);
             if (roads)
                 m_tables[t_roads]->write_row(id, outtags, wkb.geom);
@@ -154,7 +154,7 @@ int output_pgsql_t::pgsql_out_relation(osmid_t id, const taglist_t &rel_tags,
 
     char tmp[32];
     for (const auto& wkb: wkbs) {
-        expire->from_wkb(wkb.geom.c_str(), -id);
+        expire.from_wkb(wkb.geom.c_str(), -id);
         /* FIXME: there should be a better way to detect polygons */
         if (wkb.is_polygon()) {
             if ((wkb.area > 0.0) && m_enable_way_area) {
@@ -190,7 +190,7 @@ int output_pgsql_t::pgsql_out_relation(osmid_t id, const taglist_t &rel_tags,
     if (make_boundary) {
         wkbs = builder.build_polygons(xnodes, m_options.enable_multi, id);
         for (const auto& wkb: wkbs) {
-            expire->from_wkb(wkb.geom.c_str(), -id);
+            expire.from_wkb(wkb.geom.c_str(), -id);
             if ((wkb.area > 0.0) && m_enable_way_area) {
                 snprintf(tmp, sizeof(tmp), "%g", wkb.area);
                 outtags.push_override(tag_t("way_area", tmp));
@@ -204,7 +204,7 @@ int output_pgsql_t::pgsql_out_relation(osmid_t id, const taglist_t &rel_tags,
 
 
 void output_pgsql_t::enqueue_ways(pending_queue_t &job_queue, osmid_t id, size_t output_id, size_t& added) {
-    osmid_t const prev = ways_pending_tracker->last_returned();
+    osmid_t const prev = ways_pending_tracker.last_returned();
     if (id_tracker::is_valid(prev) && prev >= id) {
         if (prev > id) {
             job_queue.push(pending_job_t(id, output_id));
@@ -220,7 +220,7 @@ void output_pgsql_t::enqueue_ways(pending_queue_t &job_queue, osmid_t id, size_t
     }
 
     //grab the first one or bail if its not valid
-    osmid_t popped = ways_pending_tracker->pop_mark();
+    osmid_t popped = ways_pending_tracker.pop_mark();
     if(!id_tracker::is_valid(popped))
         return;
 
@@ -230,7 +230,7 @@ void output_pgsql_t::enqueue_ways(pending_queue_t &job_queue, osmid_t id, size_t
             job_queue.push(pending_job_t(popped, output_id));
             added++;
         }
-        popped = ways_pending_tracker->pop_mark();
+        popped = ways_pending_tracker.pop_mark();
     }
 
     //make sure to get this one as well and move to the next
@@ -256,7 +256,7 @@ int output_pgsql_t::pending_way(osmid_t id, int exists) {
             // osmdata_t.
             const idlist_t rel_ids = m_mid->relations_using_way(id);
             for (auto &mid: rel_ids) {
-                rels_pending_tracker->mark(mid);
+                rels_pending_tracker.mark(mid);
             }
         }
 
@@ -273,7 +273,7 @@ int output_pgsql_t::pending_way(osmid_t id, int exists) {
 }
 
 void output_pgsql_t::enqueue_relations(pending_queue_t &job_queue, osmid_t id, size_t output_id, size_t& added) {
-    osmid_t const prev = rels_pending_tracker->last_returned();
+    osmid_t const prev = rels_pending_tracker.last_returned();
     if (id_tracker::is_valid(prev) && prev >= id) {
         if (prev > id) {
             job_queue.push(pending_job_t(id, output_id));
@@ -289,7 +289,7 @@ void output_pgsql_t::enqueue_relations(pending_queue_t &job_queue, osmid_t id, s
     }
 
     //grab the first one or bail if its not valid
-    osmid_t popped = rels_pending_tracker->pop_mark();
+    osmid_t popped = rels_pending_tracker.pop_mark();
     if(!id_tracker::is_valid(popped))
         return;
 
@@ -297,7 +297,7 @@ void output_pgsql_t::enqueue_relations(pending_queue_t &job_queue, osmid_t id, s
     while (popped < id) {
         job_queue.push(pending_job_t(popped, output_id));
         added++;
-        popped = rels_pending_tracker->pop_mark();
+        popped = rels_pending_tracker.pop_mark();
     }
 
     //make sure to get this one as well and move to the next
@@ -352,8 +352,10 @@ void output_pgsql_t::stop()
       }
     }
 
-    expire->output_and_destroy();
-    expire.reset();
+    if (m_options.expire_tiles_zoom_min >= 0) {
+        expire.output_and_destroy(m_options.expire_tiles_filename.c_str(),
+                                  m_options.expire_tiles_zoom_min);
+    }
 }
 
 int output_pgsql_t::node_add(osmid_t id, double lat, double lon, const taglist_t &tags)
@@ -375,7 +377,7 @@ int output_pgsql_t::way_add(osmid_t id, const idlist_t &nds, const taglist_t &ta
 
   /* If this isn't a polygon then it can not be part of a multipolygon
      Hence only polygons are "pending" */
-  if (!filter && polygon) { ways_pending_tracker->mark(id); }
+  if (!filter && polygon) { ways_pending_tracker.mark(id); }
 
   if( !polygon && !filter )
   {
@@ -470,7 +472,7 @@ int output_pgsql_t::node_delete(osmid_t osm_id)
         util::exit_nicely();
     }
 
-    if ( expire->from_db(m_tables[t_point].get(), osm_id) != 0)
+    if ( expire.from_db(m_tables[t_point].get(), osm_id) != 0)
         m_tables[t_point]->delete_row(osm_id);
 
     return 0;
@@ -487,9 +489,9 @@ int output_pgsql_t::pgsql_delete_way_from_output(osmid_t osm_id)
         return 0;
 
     m_tables[t_roads]->delete_row(osm_id);
-    if ( expire->from_db(m_tables[t_line].get(), osm_id) != 0)
+    if ( expire.from_db(m_tables[t_line].get(), osm_id) != 0)
         m_tables[t_line]->delete_row(osm_id);
-    if ( expire->from_db(m_tables[t_poly].get(), osm_id) != 0)
+    if ( expire.from_db(m_tables[t_poly].get(), osm_id) != 0)
         m_tables[t_poly]->delete_row(osm_id);
     return 0;
 }
@@ -509,9 +511,9 @@ int output_pgsql_t::way_delete(osmid_t osm_id)
 int output_pgsql_t::pgsql_delete_relation_from_output(osmid_t osm_id)
 {
     m_tables[t_roads]->delete_row(-osm_id);
-    if ( expire->from_db(m_tables[t_line].get(), -osm_id) != 0)
+    if ( expire.from_db(m_tables[t_line].get(), -osm_id) != 0)
         m_tables[t_line]->delete_row(-osm_id);
-    if ( expire->from_db(m_tables[t_poly].get(), -osm_id) != 0)
+    if ( expire.from_db(m_tables[t_poly].get(), -osm_id) != 0)
         m_tables[t_poly]->delete_row(-osm_id);
     return 0;
 }
@@ -578,21 +580,18 @@ int output_pgsql_t::start()
     return 0;
 }
 
-std::shared_ptr<output_t> output_pgsql_t::clone(const middle_query_t* cloned_middle) const {
-    output_pgsql_t *clone = new output_pgsql_t(*this);
+std::shared_ptr<output_t> output_pgsql_t::clone(const middle_query_t* cloned_middle) const
+{
+    auto *clone = new output_pgsql_t(*this);
     clone->m_mid = cloned_middle;
-    //NOTE: we need to know which ways were used by relations so each thread
-    //must have a copy of the original marked done ways, its read only so its ok
-    clone->ways_done_tracker = ways_done_tracker;
     return std::shared_ptr<output_t>(clone);
 }
 
-output_pgsql_t::output_pgsql_t(const middle_query_t* mid_, const options_t &options_)
-    : output_t(mid_, options_),
-      ways_pending_tracker(new id_tracker()),
-      ways_done_tracker(new id_tracker()),
-      rels_pending_tracker(new id_tracker()) {
-
+output_pgsql_t::output_pgsql_t(const middle_query_t* mid, const options_t &o)
+    : output_t(mid, o),
+      expire(o.expire_tiles_zoom, o.expire_tiles_max_bbox, o.projection),
+      ways_done_tracker(new id_tracker())
+{
     reproj = m_options.projection;
     builder.set_exclude_broken_polygon(m_options.excludepoly);
     if (m_options.reproject_area) builder.set_reprojection(reproj.get());
@@ -610,8 +609,6 @@ output_pgsql_t::output_pgsql_t(const middle_query_t* mid_, const options_t &opti
         util::exit_nicely();
     }
 
-    expire.reset(new expire_tiles(&m_options));
-
     //for each table
     m_tables.reserve(t_MAX);
     for (int i = 0; i < t_MAX; i++) {
@@ -661,9 +658,13 @@ output_pgsql_t::output_pgsql_t(const middle_query_t* mid_, const options_t &opti
 
 output_pgsql_t::output_pgsql_t(const output_pgsql_t& other):
     output_t(other.m_mid, other.m_options), m_tagtransform(new tagtransform(&m_options)), m_enable_way_area(other.m_enable_way_area),
-    m_export_list(new export_list(*other.m_export_list)), reproj(other.reproj),
-    expire(new expire_tiles(&m_options)),
-    ways_pending_tracker(new id_tracker()), ways_done_tracker(new id_tracker()), rels_pending_tracker(new id_tracker())
+    m_export_list(new export_list(*other.m_export_list)),
+    expire(m_options.expire_tiles_zoom, m_options.expire_tiles_max_bbox,
+           m_options.projection),
+    reproj(other.reproj),
+    //NOTE: we need to know which ways were used by relations so each thread
+    //must have a copy of the original marked done ways, its read only so its ok
+    ways_done_tracker(other.ways_done_tracker)
 {
     builder.set_exclude_broken_polygon(m_options.excludepoly);
     if (m_options.reproject_area) builder.set_reprojection(reproj.get());
@@ -677,24 +678,23 @@ output_pgsql_t::~output_pgsql_t() {
 }
 
 size_t output_pgsql_t::pending_count() const {
-    return ways_pending_tracker->size() + rels_pending_tracker->size();
+    return ways_pending_tracker.size() + rels_pending_tracker.size();
 }
 
-void output_pgsql_t::merge_pending_relations(std::shared_ptr<output_t> other) {
-    std::shared_ptr<id_tracker> tracker = other->get_pending_relations();
-    osmid_t id;
-    while(tracker.get() && id_tracker::is_valid((id = tracker->pop_mark()))){
-        rels_pending_tracker->mark(id);
+void output_pgsql_t::merge_pending_relations(output_t *other)
+{
+    auto opgsql = dynamic_cast<output_pgsql_t *>(other);
+    if (opgsql) {
+        osmid_t id;
+        while (id_tracker::is_valid((id = opgsql->rels_pending_tracker.pop_mark()))) {
+            rels_pending_tracker.mark(id);
+        }
     }
 }
-void output_pgsql_t::merge_expire_trees(std::shared_ptr<output_t> other) {
-    if(other->get_expire_tree().get())
-        expire->merge_and_destroy(*other->get_expire_tree());
+void output_pgsql_t::merge_expire_trees(output_t *other)
+{
+    auto *opgsql = dynamic_cast<output_pgsql_t *>(other);
+    if (opgsql)
+        expire.merge_and_destroy(opgsql->expire);
 }
 
-std::shared_ptr<id_tracker> output_pgsql_t::get_pending_relations() {
-    return rels_pending_tracker;
-}
-std::shared_ptr<expire_tiles> output_pgsql_t::get_expire_tree() {
-    return expire;
-}
diff --git a/output-pgsql.hpp b/output-pgsql.hpp
index 27bab25..3bffce3 100644
--- a/output-pgsql.hpp
+++ b/output-pgsql.hpp
@@ -53,10 +53,8 @@ public:
 
     size_t pending_count() const;
 
-    void merge_pending_relations(std::shared_ptr<output_t> other);
-    void merge_expire_trees(std::shared_ptr<output_t> other);
-    virtual std::shared_ptr<id_tracker> get_pending_relations();
-    virtual std::shared_ptr<expire_tiles> get_expire_tree();
+    void merge_pending_relations(output_t *other);
+    void merge_expire_trees(output_t *other);
 
 protected:
 
@@ -81,13 +79,12 @@ protected:
     std::unique_ptr<export_list> m_export_list;
 
     geometry_builder builder;
+    expire_tiles expire;
 
     std::shared_ptr<reprojection> reproj;
-    std::shared_ptr<expire_tiles> expire;
 
-    std::shared_ptr<id_tracker> ways_pending_tracker, ways_done_tracker, rels_pending_tracker;
-
-    const static std::string NAME;
+    id_tracker ways_pending_tracker, rels_pending_tracker;
+    std::shared_ptr<id_tracker> ways_done_tracker;
 };
 
 #endif
diff --git a/output.cpp b/output.cpp
index b6e18f8..745c294 100644
--- a/output.cpp
+++ b/output.cpp
@@ -130,30 +130,23 @@ std::vector<std::shared_ptr<output_t> > output_t::create_outputs(const middle_qu
     return outputs;
 }
 
-output_t::output_t(const middle_query_t *mid_, const options_t &options_): m_mid(mid_), m_options(options_) {
+output_t::output_t(const middle_query_t *mid_, const options_t &options_)
+: m_mid(mid_), m_options(options_)
+{}
 
-}
-
-output_t::~output_t() {
+output_t::~output_t() = default;
 
-}
-
-size_t output_t::pending_count() const{
+size_t output_t::pending_count() const
+{
     return 0;
 }
 
-const options_t *output_t::get_options()const {
-	return &m_options;
+const options_t *output_t::get_options() const
+{
+    return &m_options;
 }
 
-void output_t::merge_pending_relations(std::shared_ptr<output_t> other) {
-}
-void output_t::merge_expire_trees(std::shared_ptr<output_t> other) {
-}
+void output_t::merge_pending_relations(output_t*) {}
+
+void output_t::merge_expire_trees(output_t*) {}
 
-std::shared_ptr<id_tracker> output_t::get_pending_relations() {
-    return std::shared_ptr<id_tracker>();
-}
-std::shared_ptr<expire_tiles> output_t::get_expire_tree() {
-    return std::shared_ptr<expire_tiles>();
-}
diff --git a/output.hpp b/output.hpp
index ab89695..e7c7ec4 100644
--- a/output.hpp
+++ b/output.hpp
@@ -65,10 +65,8 @@ public:
 
     const options_t *get_options() const;
 
-    virtual void merge_pending_relations(std::shared_ptr<output_t> other);
-    virtual void merge_expire_trees(std::shared_ptr<output_t> other);
-    virtual std::shared_ptr<id_tracker> get_pending_relations();
-    virtual std::shared_ptr<expire_tiles> get_expire_tree();
+    virtual void merge_pending_relations(output_t *other);
+    virtual void merge_expire_trees(output_t *other);
 
 protected:
 
diff --git a/parse-osmium.cpp b/parse-osmium.cpp
index 9486517..9581f18 100644
--- a/parse-osmium.cpp
+++ b/parse-osmium.cpp
@@ -112,7 +112,7 @@ void parse_osmium_t::stream_file(const std::string &filename, const std::string
     osmium::io::File infile(filename, osmium_format);
 
     if (infile.format() == osmium::io::file_format::unknown)
-        throw std::runtime_error(fmt.empty()
+        throw std::runtime_error(fmt == "auto"
                                    ?"Cannot detect file format. Try using -r."
                                    : ((boost::format("Unknown file format '%1%'.")
                                                     % fmt).str()));
diff --git a/pgsql.cpp b/pgsql.cpp
index 531fa42..667792e 100644
--- a/pgsql.cpp
+++ b/pgsql.cpp
@@ -53,7 +53,7 @@ int pgsql_exec(PGconn *sql_conn, const ExecStatusType expect, const char *fmt, .
     /* Based on vprintf manual page */
     /* Guess we need no more than 100 bytes. */
 
-    if ((sql = (char *)malloc(size)) == nullptr)
+    if ((sql = static_cast<char *>(malloc(size))) == nullptr)
         throw std::runtime_error("Memory allocation failed in pgsql_exec");
 
     while (1) {
@@ -69,7 +69,7 @@ int pgsql_exec(PGconn *sql_conn, const ExecStatusType expect, const char *fmt, .
             size = n+1; /* precisely what is needed */
         else           /* glibc 2.0 */
             size *= 2;  /* twice the old size */
-        if ((nsql = (char *)realloc (sql, size)) == nullptr) {
+        if ((nsql = static_cast<char *>(realloc (sql, size))) == nullptr) {
             free(sql);
             throw std::runtime_error("Memory re-allocation failed in pgsql_exec");
         } else {
@@ -92,12 +92,12 @@ int pgsql_exec(PGconn *sql_conn, const ExecStatusType expect, const char *fmt, .
     return 0;
 }
 
-void pgsql_CopyData(const char *context, PGconn *sql_conn, const char *sql, int len)
+void pgsql_CopyData(const char *context, PGconn *sql_conn, std::string const &sql)
 {
 #ifdef DEBUG_PGSQL
-    fprintf(stderr, "%s>>> %s\n", context, sql );
+    fprintf(stderr, "%s>>> %s\n", context, sql.c_str());
 #endif
-    int r = PQputCopyData(sql_conn, sql, len);
+    int r = PQputCopyData(sql_conn, sql.c_str(), sql.size());
     switch(r)
     {
         //need to wait for write ready
diff --git a/pgsql.hpp b/pgsql.hpp
index 01c5b50..6bd0ed0 100644
--- a/pgsql.hpp
+++ b/pgsql.hpp
@@ -12,7 +12,7 @@
 #include <memory>
 
 PGresult *pgsql_execPrepared( PGconn *sql_conn, const char *stmtName, const int nParams, const char *const * paramValues, const ExecStatusType expect);
-void pgsql_CopyData(const char *context, PGconn *sql_conn, const char *sql, int len);
+void pgsql_CopyData(const char *context, PGconn *sql_conn, std::string const &sql);
 std::shared_ptr<PGresult> pgsql_exec_simple(PGconn *sql_conn, const ExecStatusType expect, const std::string& sql);
 std::shared_ptr<PGresult> pgsql_exec_simple(PGconn *sql_conn, const ExecStatusType expect, const char *sql);
 int pgsql_exec(PGconn *sql_conn, const ExecStatusType expect, const char *fmt, ...)
@@ -22,13 +22,4 @@ int pgsql_exec(PGconn *sql_conn, const ExecStatusType expect, const char *fmt, .
 ;
 
 void escape(const std::string &src, std::string& dst);
-
-
-inline void pgsql_CopyData(const char *context, PGconn *sql_conn, const char *sql) {
-    pgsql_CopyData(context, sql_conn, sql, (int) strlen(sql));
-}
-
-inline void pgsql_CopyData(const char *context, PGconn *sql_conn, const std::string &sql) {
-    pgsql_CopyData(context, sql_conn, sql.c_str(), (int) sql.length());
-}
 #endif
diff --git a/reprojection.cpp b/reprojection.cpp
index 2919803..832980f 100644
--- a/reprojection.cpp
+++ b/reprojection.cpp
@@ -53,7 +53,7 @@ class merc_reprojection_t : public reprojection
 public:
     osmium::geom::Coordinates reproject(osmium::Location loc) const override
     {
-        /* The latitude co-ordinate is clipped at slightly larger than the 900913 'world'
+        /* The latitude co-ordinate is clipped at slightly larger than the 3857 'world'
          * extent of +-85.0511 degrees to ensure that the points appear just outside the
          * edge of the map. */
         double lat = loc.lat_without_check();
diff --git a/reprojection.hpp b/reprojection.hpp
index 8b59ff6..c0609df 100644
--- a/reprojection.hpp
+++ b/reprojection.hpp
@@ -13,7 +13,7 @@
 #include <osmium/geom/projection.hpp>
 #include <osmium/osm/location.hpp>
 
-enum Projection { PROJ_LATLONG = 4326, PROJ_SPHERE_MERC = 900913 };
+enum Projection { PROJ_LATLONG = 4326, PROJ_SPHERE_MERC = 3857 };
 
 class reprojection : public boost::noncopyable
 {
diff --git a/sprompt.cpp b/sprompt.cpp
index 9a4771b..737b47b 100644
--- a/sprompt.cpp
+++ b/sprompt.cpp
@@ -83,11 +83,11 @@ simple_prompt(const char *prompt, int maxlen, int echo)
 #else
 #ifdef _WIN32
 	HANDLE		t = NULL;
-	LPDWORD		t_orig = NULL;
+	DWORD		t_orig;
 #endif
 #endif
 
-	destination = (char *) malloc(maxlen + 1);
+	destination = static_cast<char *>(malloc(maxlen + 1));
 	if (!destination)
 		return NULL;
 
@@ -125,11 +125,10 @@ simple_prompt(const char *prompt, int maxlen, int echo)
 	if (!echo)
 	{
 		/* get a new handle to turn echo off */
-		t_orig = (LPDWORD) malloc(sizeof(DWORD));
 		t = GetStdHandle(STD_INPUT_HANDLE);
 
 		/* save the old configuration first */
-		GetConsoleMode(t, t_orig);
+		GetConsoleMode(t, &t_orig);
 
 		/* set to the new mode */
 		SetConsoleMode(t, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
@@ -177,10 +176,9 @@ simple_prompt(const char *prompt, int maxlen, int echo)
 	if (!echo)
 	{
 		/* reset to the original console mode */
-		SetConsoleMode(t, *t_orig);
+		SetConsoleMode(t, t_orig);
 		fputs("\n", termout);
 		fflush(termout);
-		free(t_orig);
 	}
 #endif
 #endif
diff --git a/style.lua b/style.lua
index 3c85a95..87669bd 100644
--- a/style.lua
+++ b/style.lua
@@ -1,211 +1,301 @@
 -- For documentation of Lua tag transformations, see docs/lua.md.
 
 -- Objects with any of the following keys will be treated as polygon
-polygon_keys = { 'building', 'landuse', 'amenity', 'harbour', 'historic', 'leisure',
-      'man_made', 'military', 'natural', 'office', 'place', 'power',
-      'public_transport', 'shop', 'sport', 'tourism', 'waterway',
-      'wetland', 'water', 'aeroway' }
+polygon_keys = {
+    'aeroway',
+    'amenity',
+    'building',
+    'harbour',
+    'historic',
+    'landuse',
+    'leisure',
+    'man_made',
+    'military',
+    'natural',
+    'office',
+    'place',
+    'power',
+    'public_transport',
+    'shop',
+    'sport',
+    'tourism',
+    'water',
+    'waterway',
+    'wetland'
+}
 
 -- Objects without any of the following keys will be deleted
-generic_keys = {'access','addr:housename','addr:housenumber','addr:interpolation','admin_level','aerialway','aeroway','amenity','area','barrier',
-   'bicycle','brand','bridge','boundary','building','capital','construction','covered','culvert','cutting','denomination','disused','ele',
-   'embarkment','foot','generation:source','harbour','highway','historic','hours','intermittent','junction','landuse','layer','leisure','lock',
-   'man_made','military','motor_car','name','natural','office','oneway','operator','place','population','power','power_source','public_transport',
-   'railway','ref','religion','route','service','shop','sport','surface','toll','tourism','tower:type', 'tracktype','tunnel','water','waterway',
-   'wetland','width','wood','type'}
+generic_keys = {
+    'access',
+    'addr:housename',
+    'addr:housenumber',
+    'addr:interpolation',
+    'admin_level',
+    'aerialway',
+    'aeroway',
+    'amenity',
+    'area',
+    'barrier',
+    'bicycle',
+    'boundary',
+    'brand',
+    'bridge',
+    'building',
+    'capital',
+    'construction',
+    'covered',
+    'culvert',
+    'cutting',
+    'denomination',
+    'disused',
+    'ele',
+    'embarkment',
+    'foot',
+    'generation:source',
+    'harbour',
+    'highway',
+    'historic',
+    'hours',
+    'intermittent',
+    'junction',
+    'landuse',
+    'layer',
+    'leisure',
+    'lock',
+    'man_made',
+    'military',
+    'motor_car',
+    'name',
+    'natural',
+    'office',
+    'oneway',
+    'operator',
+    'place',
+    'population',
+    'power',
+    'power_source',
+    'public_transport',
+    'railway',
+    'ref',
+    'religion',
+    'route',
+    'service',
+    'shop',
+    'sport',
+    'surface',
+    'toll',
+    'tourism',
+    'tower:type',
+    'tracktype',
+    'tunnel',
+    'type',
+    'water',
+    'waterway',
+    'wetland',
+    'width',
+    'wood'
+}
 
 -- The following keys will be deleted
-delete_tags = { 'FIXME', 'note', 'source' }
+delete_tags = {
+    'FIXME',
+    'note',
+    'source'
+}
 
 -- Array used to specify z_order per key/value combination.
 -- Each element has the form {key, value, z_order, is_road}.
 -- If is_road=1, the object will be added to planet_osm_roads.
 zordering_tags = {{ 'railway', nil, 5, 1}, { 'boundary', 'administrative', 0, 1},
-   { 'bridge', 'yes', 10, 0 }, { 'bridge', 'true', 10, 0 }, { 'bridge', 1, 10, 0 },
-   { 'tunnel', 'yes', -10, 0}, { 'tunnel', 'true', -10, 0}, { 'tunnel', 1, -10, 0},
-   { 'highway', 'minor', 3, 0}, { 'highway', 'road', 3, 0 }, { 'highway', 'unclassified', 3, 0 },
-   { 'highway', 'residential', 3, 0 }, { 'highway', 'tertiary_link', 4, 0}, { 'highway', 'tertiary', 4, 0},
-   { 'highway', 'secondary_link', 6, 1}, { 'highway', 'secondary', 6, 1},
-   { 'highway', 'primary_link', 7, 1}, { 'highway', 'primary', 7, 1},
-   { 'highway', 'trunk_link', 8, 1}, { 'highway', 'trunk', 8, 1},
-   { 'highway', 'motorway_link', 9, 1}, { 'highway', 'motorway', 9, 1},
+    { 'bridge', 'yes', 10, 0 }, { 'bridge', 'true', 10, 0 }, { 'bridge', 1, 10, 0 },
+    { 'tunnel', 'yes', -10, 0}, { 'tunnel', 'true', -10, 0}, { 'tunnel', 1, -10, 0},
+    { 'highway', 'minor', 3, 0}, { 'highway', 'road', 3, 0 }, { 'highway', 'unclassified', 3, 0 },
+    { 'highway', 'residential', 3, 0 }, { 'highway', 'tertiary_link', 4, 0}, { 'highway', 'tertiary', 4, 0},
+    { 'highway', 'secondary_link', 6, 1}, { 'highway', 'secondary', 6, 1},
+    { 'highway', 'primary_link', 7, 1}, { 'highway', 'primary', 7, 1},
+    { 'highway', 'trunk_link', 8, 1}, { 'highway', 'trunk', 8, 1},
+    { 'highway', 'motorway_link', 9, 1}, { 'highway', 'motorway', 9, 1},
 }
 
 function add_z_order(keyvalues)
-   -- The default z_order is 0
-   z_order = 0
+    -- The default z_order is 0
+    z_order = 0
 
-   -- Add the value of the layer key times 10 to z_order
-   if (keyvalues["layer"] ~= nil and tonumber(keyvalues["layer"])) then
-      z_order = 10*keyvalues["layer"]
-   end
+    -- Add the value of the layer key times 10 to z_order
+    if (keyvalues["layer"] ~= nil and tonumber(keyvalues["layer"])) then
+       z_order = 10*keyvalues["layer"]
+    end
 
    -- Increase or decrease z_order based on the specific key/value combination as specified in zordering_tags
-   for i,k in ipairs(zordering_tags) do
-      -- If the value in zordering_tags is specified, match key and value. Otherwise, match key only.
-      if ((k[2]  and keyvalues[k[1]] == k[2]) or (k[2] == nil and keyvalues[k[1]] ~= nil)) then
-         -- If the fourth component of the element of zordering_tags is 1, add the object to planet_osm_roads
-         if (k[4] == 1) then
-            roads = 1
-         end
-         z_order = z_order + k[3]
-      end
-   end
-
-   -- Add z_order as key/value combination
-   keyvalues["z_order"] = z_order
-
-   return keyvalues, roads
+    for i,k in ipairs(zordering_tags) do
+        -- If the value in zordering_tags is specified, match key and value. Otherwise, match key only.
+        if ((k[2]  and keyvalues[k[1]] == k[2]) or (k[2] == nil and keyvalues[k[1]] ~= nil)) then
+            -- If the fourth component of the element of zordering_tags is 1, add the object to planet_osm_roads
+            if (k[4] == 1) then
+                roads = 1
+            end
+            z_order = z_order + k[3]
+        end
+    end
+
+    -- Add z_order as key/value combination
+    keyvalues["z_order"] = z_order
+
+    return keyvalues, roads
 end
 
 -- Filtering on nodes, ways, and relations
 function filter_tags_generic(keyvalues, numberofkeys)
-   filter = 0   -- Will object be filtered out?
-
-   -- Filter out objects with 0 tags
-   if numberofkeys == 0 then
-      filter = 1
-      return filter, keyvalues
-   end
-
-   -- Delete tags listed in delete_tags
-   for i,k in ipairs(delete_tags) do
-      keyvalues[k] = nil
-   end
-
-   -- Filter out objects that do not have any of the keys in generic_keys
-   tagcount = 0
-   for k,v in pairs(keyvalues) do
-      for i, k2 in ipairs(generic_keys) do if k2 == k then tagcount = tagcount + 1; end end
-   end
-   if tagcount == 0 then
-      filter = 1
-   end
-
-   return filter, keyvalues
+    filter = 0   -- Will object be filtered out?
+
+    -- Filter out objects with 0 tags
+    if numberofkeys == 0 then
+        filter = 1
+        return filter, keyvalues
+    end
+
+    -- Delete tags listed in delete_tags
+    for i,k in ipairs(delete_tags) do
+        keyvalues[k] = nil
+    end
+
+    -- Filter out objects that do not have any of the keys in generic_keys
+    tagcount = 0
+    for k,v in pairs(keyvalues) do
+        for i, k2 in ipairs(generic_keys) do
+            if k2 == k then
+                tagcount = tagcount + 1
+            end
+        end
+    end
+    if tagcount == 0 then
+        filter = 1
+    end
+
+    return filter, keyvalues
 end
 
 -- Filtering on nodes
 function filter_tags_node (keyvalues, numberofkeys)
-   return filter_tags_generic(keyvalues, numberofkeys)
+    return filter_tags_generic(keyvalues, numberofkeys)
 end
 
 -- Filtering on relations
 function filter_basic_tags_rel (keyvalues, numberofkeys)
-   -- Filter out objects that are filtered out by filter_tags_generic
-   filter, keyvalues = filter_tags_generic(keyvalues, numberofkeys)
-   if filter == 1 then
-      return filter, keyvalues
-   end
-
-   -- Filter out all relations except route, multipolygon and boundary relations
-   if ((keyvalues["type"] ~= "route") and (keyvalues["type"] ~= "multipolygon") and (keyvalues["type"] ~= "boundary")) then
-      filter = 1
-      return filter, keyvalues
-   end
-
-   return filter, keyvalues
+    -- Filter out objects that are filtered out by filter_tags_generic
+    filter, keyvalues = filter_tags_generic(keyvalues, numberofkeys)
+    if filter == 1 then
+        return filter, keyvalues
+    end
+
+    -- Filter out all relations except route, multipolygon and boundary relations
+    if ((keyvalues["type"] ~= "route") and (keyvalues["type"] ~= "multipolygon") and (keyvalues["type"] ~= "boundary")) then
+        filter = 1
+        return filter, keyvalues
+    end
+
+    return filter, keyvalues
 end
 
 -- Filtering on ways
 function filter_tags_way (keyvalues, numberofkeys)
-   filter = 0  -- Will object be filtered out?
-   polygon = 0 -- Will object be treated as polygon?
-   roads = 0   -- Will object be added to planet_osm_roads?
-
-   -- Filter out objects that are filtered out by filter_tags_generic
-   filter, keyvalues = filter_tags_generic(keyvalues, numberofkeys)
-   if filter == 1 then
-      return filter, keyvalues, polygon, roads
-   end
-
-  -- Treat objects with a key in polygon_keys as polygon
-   for i,k in ipairs(polygon_keys) do
-      if keyvalues[k] then
-         polygon=1
-         break
-      end
-   end
-
-   -- Treat objects tagged as area=yes, area=1, or area=true as polygon,
-   -- and treat objects tagged as area=no, area=0, or area=false not as polygon
-   if ((keyvalues["area"] == "yes") or (keyvalues["area"] == "1") or (keyvalues["area"] == "true")) then
-      polygon = 1;
-   elseif ((keyvalues["area"] == "no") or (keyvalues["area"] == "0") or (keyvalues["area"] == "false")) then
-      polygon = 0;
-   end
-
-   -- Add z_order key/value combination and determine if the object should also be added to planet_osm_roads
-   keyvalues, roads = add_z_order(keyvalues)
-
-   return filter, keyvalues, polygon, roads
+    filter = 0  -- Will object be filtered out?
+    polygon = 0 -- Will object be treated as polygon?
+    roads = 0   -- Will object be added to planet_osm_roads?
+
+    -- Filter out objects that are filtered out by filter_tags_generic
+    filter, keyvalues = filter_tags_generic(keyvalues, numberofkeys)
+    if filter == 1 then
+        return filter, keyvalues, polygon, roads
+    end
+
+    -- Treat objects with a key in polygon_keys as polygon
+    for i,k in ipairs(polygon_keys) do
+        if keyvalues[k] then
+            polygon=1
+            break
+        end
+    end
+
+    -- Treat objects tagged as area=yes, area=1, or area=true as polygon,
+    -- and treat objects tagged as area=no, area=0, or area=false not as polygon
+    if ((keyvalues["area"] == "yes") or (keyvalues["area"] == "1") or (keyvalues["area"] == "true")) then
+        polygon = 1;
+    elseif ((keyvalues["area"] == "no") or (keyvalues["area"] == "0") or (keyvalues["area"] == "false")) then
+        polygon = 0;
+    end
+
+    -- Add z_order key/value combination and determine if the object should also be added to planet_osm_roads
+    keyvalues, roads = add_z_order(keyvalues)
+
+    return filter, keyvalues, polygon, roads
 end
 
 function filter_tags_relation_member (keyvalues, keyvaluemembers, roles, membercount)
-   filter = 0     -- Will object be filtered out?
-   linestring = 0 -- Will object be treated as linestring?
-   polygon = 0    -- Will object be treated as polygon?
-   roads = 0      -- Will object be added to planet_osm_roads?
-   membersuperseded = {}
-   for i = 1, membercount do
-      membersuperseded[i] = 0 -- Will member be ignored when handling areas?
-   end
-
-   type = keyvalues["type"]
-
-   -- Remove type key
-   keyvalues["type"] = nil
-
-   -- Relations with type=boundary are treated as linestring
-   if (type == "boundary") then
-      linestring = 1
-   end
-   -- Relations with type=multipolygon and boundary=* are treated as linestring
-   if ((type == "multipolygon") and keyvalues["boundary"]) then
-      linestring = 1
-   -- For multipolygons...
-   elseif (type == "multipolygon") then
-      -- Treat as polygon
-      polygon = 1
-      polytagcount = 0;
-      -- Count the number of polygon tags of the object
-      for i,k in ipairs(polygon_keys) do
-         if keyvalues[k] then
-            polytagcount = polytagcount + 1
-         end
-      end
-      -- If there are no polygon tags, add tags from all outer elements to the multipolygon itself
-      if (polytagcount == 0) then
-         for i = 1,membercount do
-            if (roles[i] == "outer") then
-               for k,v in pairs(keyvaluemembers[i]) do
-                  keyvalues[k] = v
-               end
+    filter = 0     -- Will object be filtered out?
+    linestring = 0 -- Will object be treated as linestring?
+    polygon = 0    -- Will object be treated as polygon?
+    roads = 0      -- Will object be added to planet_osm_roads?
+    membersuperseded = {}
+    for i = 1, membercount do
+        membersuperseded[i] = 0 -- Will member be ignored when handling areas?
+    end
+
+    type = keyvalues["type"]
+
+    -- Remove type key
+    keyvalues["type"] = nil
+
+    -- Relations with type=boundary are treated as linestring
+    if (type == "boundary") then
+        linestring = 1
+    end
+    -- Relations with type=multipolygon and boundary=* are treated as linestring
+    if ((type == "multipolygon") and keyvalues["boundary"]) then
+        linestring = 1
+    -- For multipolygons...
+    elseif (type == "multipolygon") then
+        -- Treat as polygon
+        polygon = 1
+        polytagcount = 0;
+        -- Count the number of polygon tags of the object
+        for i,k in ipairs(polygon_keys) do
+            if keyvalues[k] then
+                polytagcount = polytagcount + 1
+            end
+        end
+        -- If there are no polygon tags, add tags from all outer elements to the multipolygon itself
+        if (polytagcount == 0) then
+            for i = 1,membercount do
+                if (roles[i] == "outer") then
+                    for k,v in pairs(keyvaluemembers[i]) do
+                        keyvalues[k] = v
+                    end
+                end
             end
-         end
-      end
-      -- For any member of the multipolygon, set membersuperseded to 1 (i.e. don't deal with it as area as well),
-      -- except when the member has a key/value combination such that
-      --   1) the key occurs in generic_keys
-      --   2) the key/value combination is not also a key/value combination of the multipolygon itself
-      for i = 1,membercount do
-         superseded = 1
-         for k,v in pairs(keyvaluemembers[i]) do
-            if ((keyvalues[k] == nil) or (keyvalues[k] ~= v)) then
-               for j,k2 in ipairs(generic_keys) do
-                  if (k == k2) then
-                     superseded = 0;
-                     break
-                  end
-               end
+        end
+        -- For any member of the multipolygon, set membersuperseded to 1 (i.e. don't deal with it as area as well),
+        -- except when the member has a key/value combination such that
+        --   1) the key occurs in generic_keys
+        --   2) the key/value combination is not also a key/value combination of the multipolygon itself
+        for i = 1,membercount do
+            superseded = 1
+            for k,v in pairs(keyvaluemembers[i]) do
+                if ((keyvalues[k] == nil) or (keyvalues[k] ~= v)) then
+                    for j,k2 in ipairs(generic_keys) do
+                        if (k == k2) then
+                            superseded = 0;
+                            break
+                        end
+                    end
+                end
             end
-         end
-         membersuperseded[i] = superseded
-      end
-   end
+            membersuperseded[i] = superseded
+        end
+    end
 
-   -- Add z_order key/value combination and determine if the object should also be added to planet_osm_roads
-   keyvalues, roads = add_z_order(keyvalues)
+    -- Add z_order key/value combination and determine if the object should also be added to planet_osm_roads
+    keyvalues, roads = add_z_order(keyvalues)
 
-   return filter, keyvalues, membersuperseded, linestring, polygon, roads
+    return filter, keyvalues, membersuperseded, linestring, polygon, roads
 end
diff --git a/table.cpp b/table.cpp
index 74bf4bf..271a317 100644
--- a/table.cpp
+++ b/table.cpp
@@ -1,6 +1,7 @@
 #include "table.hpp"
 #include "options.hpp"
 #include "util.hpp"
+#include "taginfo.hpp"
 
 #include <exception>
 #include <algorithm>
@@ -23,7 +24,7 @@ table_t::table_t(const string& conninfo, const string& name, const string& type,
     columns(columns), hstore_columns(hstore_columns), table_space(table_space), table_space_index(table_space_index)
 {
     //if we dont have any columns
-    if(columns.size() == 0)
+    if(columns.size() == 0 && hstore_mode != HSTORE_ALL)
         throw std::runtime_error((fmt("No columns provided for table %1%") % name).str());
 
     //nothing to copy to start with
@@ -120,22 +121,23 @@ void table_t::start()
     if (!append)
     {
         //define the new table
-        string sql = (fmt("CREATE TABLE %1% (osm_id %2%,") % name % POSTGRES_OSMID_TYPE).str();
+        string sql = (fmt("CREATE UNLOGGED TABLE %1% (osm_id %2%,") % name % POSTGRES_OSMID_TYPE).str();
 
         //first with the regular columns
-        for(columns_t::const_iterator column = columns.begin(); column != columns.end(); ++column)
-            sql += (fmt("\"%1%\" %2%,") % column->first % column->second).str();
+        for (auto const &column : columns) {
+            sql += (fmt("\"%1%\" %2%,") % column.name % column.type_name).str();
+        }
 
         //then with the hstore columns
         for(hstores_t::const_iterator hcolumn = hstore_columns.begin(); hcolumn != hstore_columns.end(); ++hcolumn)
             sql += (fmt("\"%1%\" hstore,") % (*hcolumn)).str();
 
         //add tags column
-        if (hstore_mode != HSTORE_NONE)
-            sql += "\"tags\" hstore)";
-        //or remove the last ", " from the end
-        else
-            sql[sql.length() - 1] = ')';
+        if (hstore_mode != HSTORE_NONE) {
+            sql += "\"tags\" hstore,";
+        }
+
+        sql += (fmt("way geometry(%1%,%2%) )") % type % srid).str();
 
         // The final tables are created with CREATE TABLE AS ... SELECT * FROM ...
         // This means that they won't get this autovacuum setting, so it doesn't
@@ -148,11 +150,7 @@ void table_t::start()
         //create the table
         pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, sql);
 
-        //add some constraints
-        pgsql_exec_simple(sql_conn, PGRES_TUPLES_OK, (fmt("SELECT AddGeometryColumn('%1%', 'way', %2%, '%3%', 2 )") % name % srid % type).str());
-        pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, (fmt("ALTER TABLE %1% ALTER COLUMN way SET NOT NULL") % name).str());
-
-        //slim mode needs this to be able to apply diffs
+        //slim mode needs this to be able to delete from tables in pending
         if (slim && !drop_temp) {
             sql = (fmt("CREATE INDEX %1%_pkey ON %1% USING BTREE (osm_id)") % name).str();
             if (table_space_index)
@@ -164,15 +162,13 @@ void table_t::start()
     else {
         //check the columns against those in the existing table
         std::shared_ptr<PGresult> res = pgsql_exec_simple(sql_conn, PGRES_TUPLES_OK, (fmt("SELECT * FROM %1% LIMIT 0") % name).str());
-        for(columns_t::const_iterator column = columns.begin(); column != columns.end(); ++column)
-        {
-            if(PQfnumber(res.get(), ('"' + column->first + '"').c_str()) < 0)
-            {
+        for (auto const &column :  columns) {
+            if (PQfnumber(res.get(), ('"' + column.name + '"').c_str()) < 0) {
 #if 0
                 throw std::runtime_error((fmt("Append failed. Column \"%1%\" is missing from \"%1%\"\n") % info.name).str());
 #else
-                fprintf(stderr, "%s", (fmt("Adding new column \"%1%\" to \"%2%\"\n") % column->first % name).str().c_str());
-                pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, (fmt("ALTER TABLE %1% ADD COLUMN \"%2%\" %3%") % name % column->first % column->second).str());
+                fprintf(stderr, "%s", (fmt("Adding new column \"%1%\" to \"%2%\"\n") % column.name % name).str().c_str());
+                pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, (fmt("ALTER TABLE %1% ADD COLUMN \"%2%\" %3%") % name % column.name % column.type_name).str());
 #endif
             }
             //Note: we do not verify the type or delete unused columns
@@ -189,8 +185,9 @@ void table_t::start()
     //generate column list for COPY
     string cols = "osm_id,";
     //first with the regular columns
-    for(columns_t::const_iterator column = columns.begin(); column != columns.end(); ++column)
-        cols += (fmt("\"%1%\",") % column->first).str();
+    for (auto const &column : columns) {
+        cols += (fmt("\"%1%\",") % column.name).str();
+    }
 
     //then with the hstore columns
     for(hstores_t::const_iterator hcolumn = hstore_columns.begin(); hcolumn != hstore_columns.end(); ++hcolumn)
@@ -219,13 +216,9 @@ void table_t::stop()
 
         fprintf(stderr, "Sorting data and creating indexes for %s\n", name.c_str());
 
-        // Special handling for empty geometries because geohash chokes on
-        // empty geometries on postgis 1.5.
-        pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, (fmt("CREATE TABLE %1%_tmp %2% AS SELECT * FROM %1% ORDER BY CASE WHEN ST_IsEmpty(way) THEN NULL ELSE ST_GeoHash(ST_Transform(ST_Envelope(way),4326),10) END COLLATE \"C\"") % name % (table_space ? "TABLESPACE " + table_space.get() : "")).str());
+        pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, (fmt("CREATE TABLE %1%_tmp %2% AS SELECT * FROM %1% ORDER BY ST_GeoHash(ST_Transform(ST_Envelope(way),4326),10) COLLATE \"C\"") % name % (table_space ? "TABLESPACE " + table_space.get() : "")).str());
         pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, (fmt("DROP TABLE %1%") % name).str());
         pgsql_exec_simple(sql_conn, PGRES_COMMAND_OK, (fmt("ALTER TABLE %1%_tmp RENAME TO %1%") % name).str());
-        // Re-add constraints if on 1.x. 2.0 has typemod, and they automatically come with CREATE TABLE AS
-        pgsql_exec_simple(sql_conn, PGRES_TUPLES_OK, (fmt("SELECT CASE WHEN PostGIS_Lib_Version() LIKE '1.%%' THEN Populate_Geometry_Columns('%1%'::regclass) ELSE 1 END;") % name).str());
         fprintf(stderr, "Copying %s to cluster by geometry finished\n", name.c_str());
         fprintf(stderr, "Creating geometry index on %s\n", name.c_str());
 
@@ -355,12 +348,10 @@ void table_t::write_row(const osmid_t id, const taglist_t &tags, const std::stri
 void table_t::write_columns(const taglist_t &tags, string& values, std::vector<bool> *used)
 {
     //for each column
-    for(columns_t::const_iterator column = columns.begin(); column != columns.end(); ++column)
-    {
+    for (auto const &column : columns) {
         int idx;
-        if ((idx = tags.indexof(column->first)) >= 0)
-        {
-            escape_type(tags[idx].value, column->second, values);
+        if ((idx = tags.indexof(column.name)) >= 0) {
+            escape_type(tags[idx].value, column.type, values);
             //remember we already used this one so we cant use again later in the hstore column
             if (used)
                 (*used)[idx] = true;
@@ -467,53 +458,60 @@ void table_t::escape4hstore(const char *src, string& dst)
 }
 
 /* Escape data appropriate to the type */
-void table_t::escape_type(const string &value, const string &type, string& dst) {
-
-    // For integers we take the first number, or the average if it's a-b
-    if (type == "int4") {
-        int from, to;
-        int items = sscanf(value.c_str(), "%d-%d", &from, &to);
-        if (items == 1)
-            dst.append((single_fmt % from).str());
-        else if (items == 2)
-            dst.append((single_fmt % ((from + to) / 2)).str());
-        else
-            dst.append("\\N");
-    }
-        /* try to "repair" real values as follows:
-         * assume "," to be a decimal mark which need to be replaced by "."
-         * like int4 take the first number, or the average if it's a-b
-         * assume SI unit (meters)
-         * convert feet to meters (1 foot = 0.3048 meters)
-         * reject anything else
-         */
-    else if (type == "real")
-    {
-        string escaped(value);
-        std::replace(escaped.begin(), escaped.end(), ',', '.');
+void table_t::escape_type(const string &value, ColumnType type, string& dst) {
 
-        float from, to;
-        int items = sscanf(escaped.c_str(), "%f-%f", &from, &to);
-        if (items == 1)
-        {
-            if (escaped.size() > 1 && escaped.substr(escaped.size() - 2).compare("ft") == 0)
-                from *= 0.3048;
-            dst.append((single_fmt % from).str());
-        }
-        else if (items == 2)
-        {
-            if (escaped.size() > 1 && escaped.substr(escaped.size() - 2).compare("ft") == 0)
+    switch (type) {
+        case COLUMN_TYPE_INT:
             {
-                from *= 0.3048;
-                to *= 0.3048;
+                // For integers we take the first number, or the average if it's a-b
+                long from, to;
+                int items = sscanf(value.c_str(), "%ld-%ld", &from, &to);
+                if (items == 1) {
+                    dst.append((single_fmt % from).str());
+                } else if (items == 2) {
+                    dst.append((single_fmt % ((from + to) / 2)).str());
+                } else {
+                    dst.append("\\N");
+                }
+                break;
             }
-            dst.append((single_fmt % ((from + to) / 2)).str());
-        }
-        else
-            dst.append("\\N");
-    }//just a string
-    else
-        escape(value, dst);
+        case COLUMN_TYPE_REAL:
+            /* try to "repair" real values as follows:
+             * assume "," to be a decimal mark which need to be replaced by "."
+             * like int4 take the first number, or the average if it's a-b
+             * assume SI unit (meters)
+             * convert feet to meters (1 foot = 0.3048 meters)
+             * reject anything else
+             */
+            {
+                string escaped(value);
+                std::replace(escaped.begin(), escaped.end(), ',', '.');
+
+                double from, to;
+                int items = sscanf(escaped.c_str(), "%lf-%lf", &from, &to);
+                if (items == 1) {
+                    if (escaped.size() > 1 && escaped.substr(escaped.size() - 2).compare("ft") == 0) {
+                        from *= 0.3048;
+                    }
+                    dst.append((single_fmt % from).str());
+                }
+                else if (items == 2) {
+                    if (escaped.size() > 1 && escaped.substr(escaped.size() - 2).compare("ft") == 0) {
+                        from *= 0.3048;
+                        to *= 0.3048;
+                    }
+                    dst.append((single_fmt % ((from + to) / 2)).str());
+                }
+                else {
+                    dst.append("\\N");
+                }
+                break;
+            }
+        case COLUMN_TYPE_TEXT:
+            //just a string
+            escape(value, dst);
+            break;
+    }
 }
 
 table_t::wkb_reader table_t::get_wkb_reader(const osmid_t id)
diff --git a/table.hpp b/table.hpp
index 9bd2bb2..f4b2423 100644
--- a/table.hpp
+++ b/table.hpp
@@ -3,6 +3,7 @@
 
 #include "pgsql.hpp"
 #include "osmtypes.hpp"
+#include "taginfo.hpp"
 
 #include <cstddef>
 #include <string>
@@ -14,7 +15,6 @@
 #include <boost/format.hpp>
 
 typedef std::vector<std::string> hstores_t;
-typedef std::vector<std::pair<std::string, std::string> > columns_t;
 
 class table_t
 {
@@ -88,7 +88,7 @@ class table_t
         void write_hstore_columns(const taglist_t &tags, std::string& values);
 
         void escape4hstore(const char *src, std::string& dst);
-        void escape_type(const std::string &value, const std::string &type, std::string& dst);
+        void escape_type(const std::string &value, ColumnType flags, std::string& dst);
 
         std::string conninfo;
         std::string name;
diff --git a/taginfo.cpp b/taginfo.cpp
index 329a818..52e1260 100644
--- a/taginfo.cpp
+++ b/taginfo.cpp
@@ -1,9 +1,9 @@
 #include "taginfo_impl.hpp"
-#include "table.hpp"
 #include "util.hpp"
 
 #include <cassert>
 #include <cstring>
+#include <map>
 #include <stdexcept>
 #include <boost/format.hpp>
 #include <errno.h>
@@ -14,29 +14,25 @@
 #endif
 #endif
 
-/* NOTE: section below for flags genuinely is static and
- * constant, so there's no need to hoist this into a per
- * class variable. It doesn't get modified, so it's safe
- * to share across threads and its lifetime is the whole
- * program.
- */
-struct flagsname {
-    flagsname(const char *name_, int flag_)
-        : name(name_), flag(flag_) {
-    }
-    const char *name;
-    int flag;
+static const std::map<std::string, unsigned> tagflags = {
+    {"polygon", FLAG_POLYGON},
+    {"linear",  FLAG_LINEAR},
+    {"nocache", FLAG_NOCACHE},
+    {"delete",  FLAG_DELETE},
+    {"phstore", FLAG_PHSTORE},
+    {"nocolumn", FLAG_NOCOLUMN}
 };
 
-static const flagsname tagflags[] = {
-    flagsname("polygon", FLAG_POLYGON),
-    flagsname("linear",  FLAG_LINEAR),
-    flagsname("nocache", FLAG_NOCACHE),
-    flagsname("delete",  FLAG_DELETE),
-    flagsname("phstore", FLAG_PHSTORE),
-    flagsname("nocolumn", FLAG_NOCOLUMN)
+static const std::map<std::string, unsigned> tagtypes = {
+    {"smallint", FLAG_INT_TYPE},
+    {"integer", FLAG_INT_TYPE},
+    {"bigint", FLAG_INT_TYPE},
+    {"int2", FLAG_INT_TYPE},
+    {"int4", FLAG_INT_TYPE},
+    {"int8", FLAG_INT_TYPE},
+    {"real", FLAG_REAL_TYPE},
+    {"double precision", FLAG_REAL_TYPE}
 };
-#define NUM_FLAGS ((signed)(sizeof(tagflags) / sizeof(tagflags[0])))
 
 taginfo::taginfo()
     : name(), type(), flags(0) {
@@ -77,24 +73,21 @@ const std::vector<taginfo> &export_list::get(enum OsmType id) const {
     }
 }
 
-columns_t export_list::normal_columns(enum OsmType id) const {
+columns_t export_list::normal_columns(OsmType id) const {
     columns_t columns;
-    const std::vector<taginfo> &infos = get(id);
-    for(std::vector<taginfo>::const_iterator info = infos.begin(); info != infos.end(); ++info)
-    {
-        if( info->flags & FLAG_DELETE )
-            continue;
-        if( (info->flags & FLAG_NOCOLUMN ) == FLAG_NOCOLUMN)
-            continue;
-        columns.push_back(std::pair<std::string, std::string>(info->name, info->type));
+
+    for (auto const &info : get(id)) {
+        if (!(info.flags & (FLAG_DELETE | FLAG_NOCOLUMN))) {
+            columns.emplace_back(info.name, info.type, info.column_type());
+        }
     }
+
     return columns;
 }
 
 int parse_tag_flags(const char *flags_, int lineno) {
     int temp_flags = 0;
     char *str = nullptr, *saveptr = nullptr;
-    int i = 0;
 
     // yuck! but strtok requires a non-const char * pointer, and i'm fairly sure it
     // doesn't actually modify the string.
@@ -103,16 +96,11 @@ int parse_tag_flags(const char *flags_, int lineno) {
     //split the flags column on commas and keep track of which flags you've seen in a bit mask
     for(str = strtok_r(flags, ",\r\n", &saveptr); str != nullptr; str = strtok_r(nullptr, ",\r\n", &saveptr))
     {
-      for( i=0; i<NUM_FLAGS; i++ )
-      {
-        if( strcmp( tagflags[i].name, str ) == 0 )
-        {
-          temp_flags |= tagflags[i].flag;
-          break;
-        }
-      }
-      if( i == NUM_FLAGS )
+      if (tagflags.count(str)) {
+        temp_flags |= tagflags.at(str);
+      } else {
         fprintf( stderr, "Unknown flag '%s' line %d, ignored\n", str, lineno );
+      }
     }
 
     return temp_flags;
@@ -165,6 +153,12 @@ int read_style_file( const std::string &filename, export_list *exlist )
     temp.type.assign(datatype);
     temp.flags = parse_tag_flags(flags, lineno);
 
+    // check for special data types, by default everything is handled as text
+    auto const typ = tagtypes.find(temp.type);
+    if (typ != tagtypes.end()) {
+        temp.flags |= typ->second;
+    }
+
     if ((temp.flags != FLAG_DELETE) &&
         ((temp.name.find('?') != std::string::npos) ||
          (temp.name.find('*') != std::string::npos))) {
diff --git a/taginfo.hpp b/taginfo.hpp
index 9f6d87b..f883a4d 100644
--- a/taginfo.hpp
+++ b/taginfo.hpp
@@ -1,6 +1,27 @@
 #ifndef TAGINFO_HPP
 #define TAGINFO_HPP
 
+#include <string>
+#include <vector>
+
+enum ColumnType {
+    COLUMN_TYPE_INT,
+    COLUMN_TYPE_REAL,
+    COLUMN_TYPE_TEXT
+};
+
+struct Column
+{
+    Column(std::string const &n, std::string const &tn, ColumnType t)
+        : name(n), type_name(tn), type(t) {}
+
+    std::string name;
+    std::string type_name;
+    ColumnType type;
+};
+
+typedef std::vector<Column> columns_t;
+
 /* Table columns, representing key= tags */
 struct taginfo;
 
diff --git a/taginfo_impl.hpp b/taginfo_impl.hpp
index d778be7..19a4120 100644
--- a/taginfo_impl.hpp
+++ b/taginfo_impl.hpp
@@ -7,19 +7,33 @@
 #include <vector>
 #include <utility>
 
-#define FLAG_POLYGON 1    /* For polygon table */
-#define FLAG_LINEAR  2    /* For lines table */
-#define FLAG_NOCACHE 4    /* Optimisation: don't bother remembering this one */
-#define FLAG_DELETE  8    /* These tags should be simply deleted on sight */
-#define FLAG_NOCOLUMN 16    /* objects without column but should be listed in database hstore column */
-#define FLAG_PHSTORE 17   /* same as FLAG_NOCOLUMN & FLAG_POLYGON to maintain compatibility */
+enum column_flags {
+  FLAG_POLYGON = 1,   /* For polygon table */
+  FLAG_LINEAR = 2,    /* For lines table */
+  FLAG_NOCACHE = 4,   /* Optimisation: don't bother remembering this one */
+  FLAG_DELETE = 8,    /* These tags should be simply deleted on sight */
+  FLAG_NOCOLUMN = 16, /* objects without column but should be listed in database hstore column */
+  FLAG_PHSTORE = 17,  /* same as FLAG_NOCOLUMN & FLAG_POLYGON to maintain compatibility */
+  FLAG_INT_TYPE = 32, /* column value should be converted to integer */
+  FLAG_REAL_TYPE = 64 /* column value should be converted to double */
+};
 
 struct taginfo {
     taginfo();
     taginfo(const taginfo &);
 
+    ColumnType column_type() const {
+        if (flags & FLAG_INT_TYPE) {
+            return COLUMN_TYPE_INT;
+        }
+        if (flags & FLAG_REAL_TYPE) {
+            return COLUMN_TYPE_REAL;
+        }
+        return COLUMN_TYPE_TEXT;
+    }
+
     std::string name, type;
-    int flags;
+    unsigned flags;
 };
 
 struct export_list {
@@ -29,7 +43,7 @@ struct export_list {
     std::vector<taginfo> &get(enum OsmType id);
     const std::vector<taginfo> &get(enum OsmType id) const;
 
-    std::vector<std::pair<std::string, std::string> > normal_columns(enum OsmType id) const;
+    columns_t normal_columns(OsmType id) const;
 
     int num_tables;
     std::vector<std::vector<taginfo> > exportList; /* Indexed by enum OsmType */
diff --git a/tagtransform.cpp b/tagtransform.cpp
index 800f03a..0c92986 100644
--- a/tagtransform.cpp
+++ b/tagtransform.cpp
@@ -605,11 +605,11 @@ unsigned int tagtransform::c_filter_basic_tags(OsmType type, const taglist_t &ta
         size_t i = 0;
         for (; i < infos.size(); i++) {
             const taginfo &info = infos[i];
-            if (wildMatch(info.name.c_str(), item->key.c_str())) {
-                if (info.flags & FLAG_DELETE) {
+            if (info.flags & FLAG_DELETE) {
+                if (wildMatch(info.name.c_str(), item->key.c_str())) {
                     break;
                 }
-
+            } else if (info.name == item->key) {
                 filter = 0;
                 flags |= info.flags;
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2e27e22..1716ad4 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -12,6 +12,7 @@ set(TESTS
   test-middle-ram.cpp
   test-options-database.cpp
   test-options-parse.cpp
+  test-options-projection.cpp
   test-output-multi-line-storage.cpp
   test-output-multi-line.cpp
   test-output-multi-point-multi-table.cpp
@@ -22,11 +23,13 @@ set(TESTS
   test-output-pgsql-area.cpp
   test-output-pgsql-schema.cpp
   test-output-pgsql-tablespace.cpp
+  test-output-pgsql-validgeom.cpp
   test-output-pgsql-z_order.cpp
   test-output-pgsql.cpp
   test-parse-diff.cpp
   test-parse-xml2.cpp
   test-pgsql-escape.cpp
+  test-wildcard-match.cpp
 )
 
 foreach (test ${TESTS})
@@ -48,6 +51,7 @@ set(TEST_NODB
  test-parse-diff
  test-parse-xml2
  test-pgsql-escape
+ test-wildcard-match
 )
 
 foreach (test ${TEST_NODB})
@@ -57,12 +61,20 @@ endforeach()
 set_property(TEST test-middle-flat PROPERTY LABELS FlatNodes)
 set_property(TEST test-output-pgsql PROPERTY LABELS FlatNodes)
 
+if (NOT LUA_FOUND)
+  # these tests require LUA support
+  set_tests_properties(test-output-multi-poly-trivial PROPERTIES WILL_FAIL on)
+  set_tests_properties(test-output-multi-tags PROPERTIES WILL_FAIL on)
+endif()
+
 find_package(PythonInterp)
 
 if (PYTHONINTERP_FOUND)
   add_test(NAME regression-test-pbf COMMAND ${PYTHON_EXECUTABLE} tests/regression-test.py -f tests/liechtenstein-2013-08-03.osm.pbf -x $<TARGET_FILE:osm2pgsql>
     WORKING_DIRECTORY ${osm2pgsql_SOURCE_DIR})
   set_tests_properties(regression-test-pbf PROPERTIES TIMEOUT ${TESTING_TIMEOUT})
+  set_tests_properties(regression-test-pbf
+      PROPERTIES ENVIRONMENT "HAVE_LUA=${LUA_FOUND}")
   message(STATUS "Added test: regression-test-pbf (needs Python with psycopg2 module)")
 else()
   message(WARNING "Can not find python, regression test disabled")
diff --git a/tests/common-pg.cpp b/tests/common-pg.cpp
index ad2b6b2..7b5d4dd 100644
--- a/tests/common-pg.cpp
+++ b/tests/common-pg.cpp
@@ -1,7 +1,10 @@
 #include "common-pg.hpp"
 
+#include <iostream>
 #include <sstream>
 #include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
 #include <unistd.h>
 #include <memory>
 
@@ -104,6 +107,20 @@ tempdb::tempdb()
     setup_extension("hstore");
 }
 
+std::unique_ptr<pg::tempdb> tempdb::create_db_or_skip()
+{
+    std::unique_ptr<pg::tempdb> db;
+
+    try {
+        db.reset(new pg::tempdb);
+    } catch (const std::exception &e) {
+        std::cerr << "Unable to setup database: " << e.what() << "\n";
+        exit(77); // <-- code to skip this test.
+    }
+
+    return db;
+}
+
 void tempdb::check_tblspc() {
   result_ptr res = m_conn->exec("SELECT spcname FROM pg_tablespace WHERE "
                                 "spcname = 'tablespacetest'");
diff --git a/tests/common-pg.hpp b/tests/common-pg.hpp
index 32f727a..82e634a 100644
--- a/tests/common-pg.hpp
+++ b/tests/common-pg.hpp
@@ -56,6 +56,8 @@ struct tempdb
     tempdb();
     ~tempdb();
 
+    static std::unique_ptr<pg::tempdb> create_db_or_skip();
+
     database_options_t database_options;
 
     void check_tblspc();
diff --git a/tests/regression-test.py b/tests/regression-test.py
index d1c0b81..bd9aa36 100755
--- a/tests/regression-test.py
+++ b/tests/regression-test.py
@@ -11,6 +11,7 @@ full_import_file="tests/liechtenstein-2013-08-03.osm.pbf"
 multipoly_import_file="tests/test_multipolygon.osm" #This file contains a number of different multi-polygon test cases
 diff_import_file="tests/000466354.osc.gz"
 diff_multipoly_import_file="tests/test_multipolygon_diff.osc" #This file contains a number of different multi-polygon diff processing test cases
+lua_test_enabled = os.environ.get("HAVE_LUA", True)
 
 created_tablespace = 0
 
@@ -168,13 +169,13 @@ sql_test_statements=[
     ( 91, 'Basic line length', 'SELECT round(sum(ST_Length(way))) FROM planet_osm_line;', 4269394),
     ( 92, 'Basic line length', 'SELECT round(sum(ST_Length(way))) FROM planet_osm_roads;', 2032023),
     ( 93, 'Basic number of hstore points tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_point;', 4228),
-    ( 94, 'Basic number of hstore roads tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_roads;', 2316),
-    ( 95, 'Basic number of hstore lines tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_line;', 11131),
-    ( 96, 'Basic number of hstore polygons tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_polygon;', 9540),
+    ( 94, 'Basic number of hstore roads tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_roads;', 2317),
+    ( 95, 'Basic number of hstore lines tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_line;', 11134),
+    ( 96, 'Basic number of hstore polygons tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_polygon;', 9541),
     ( 97, 'Diff import number of hstore points tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_point;', 4352),
-    ( 98, 'Diff import number of hstore roads tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_roads;', 2340),
-    ( 99, 'Diff import number of hstore lines tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_line;', 11254),
-    ( 100, 'Diff import number of hstore polygons tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_polygon;', 9834),
+    ( 98, 'Diff import number of hstore roads tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_roads;', 2341),
+    ( 99, 'Diff import number of hstore lines tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_line;', 11257),
+    ( 100, 'Diff import number of hstore polygons tags', 'SELECT sum(array_length(akeys(tags),1)) FROM planet_osm_polygon;', 9835),
     #**** Tests to check if inner polygon appears when outer tags change after initially identicall inner and outer way tags in a multi-polygon ****
     #**** These tests are currently broken and noted in trac ticket #2853 ****
     ( 101, 'Multipolygon identical tags on inner and outer (presence of relation)',
@@ -231,9 +232,10 @@ class NonSlimRenderingTestSuite(unittest.TestSuite):
         self.addTest(BasicNonSlimTestCase("slim --drop case",["--slim","--drop"], [0,1,2,3, 10, 11, 12, 13, 91, 92]))
         self.addTest(BasicNonSlimTestCase("Hstore index drop", ["--slim", "--hstore", "--hstore-add-index", "--drop"], [51,52,53,54]))
         self.addTest(BasicNonSlimTestCase("lat lon projection",["-l"], [0,4,5,3,10, 11, 12]))
-        #Failing test 3,13 due to difference in handling mixture of tags on ways and relations, where the correct behaviour is non obvious
-        #self.addTest(BasicNonSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,3,10,13,91,92]))
-        self.addTest(BasicNonSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,10,91,92]))
+        if lua_test_enabled:
+            #Failing test 3,13 due to difference in handling mixture of tags on ways and relations, where the correct behaviour is non obvious
+            #self.addTest(BasicNonSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,3,10,13,91,92]))
+            self.addTest(BasicNonSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,10,91,92]))
 
 
 class SlimRenderingTestSuite(unittest.TestSuite):
@@ -261,9 +263,10 @@ class SlimRenderingTestSuite(unittest.TestSuite):
         self.addTest(BasicSlimTestCase("--tablespace-main-index", ["--tablespace-main-index", "tablespacetest"], [0,1,2,3,13,91,92],[6,7,8,9]))
         self.addTest(BasicSlimTestCase("--tablespace-slim-data", ["--tablespace-slim-data", "tablespacetest"], [0,1,2,3,13,91,92],[6,7,8,9]))
         self.addTest(BasicSlimTestCase("--tablespace-slim-index", ["--tablespace-slim-index", "tablespacetest"], [0,1,2,3,13,91,92],[6,7,8,9]))
-        #Failing test 3,13,9 due to difference in handling mixture of tags on ways and relations, where the correct behaviour is non obvious
-        #self.addTest(BasicNonSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,3,10,13,91,92]))
-        self.addTest(BasicSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,91,92],[6,7,8]))
+        if lua_test_enabled:
+            #Failing test 3,13,9 due to difference in handling mixture of tags on ways and relations, where the correct behaviour is non obvious
+            #self.addTest(BasicNonSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,3,10,13,91,92]))
+            self.addTest(BasicSlimTestCase("--tag-transform-script", ["--tag-transform-script", "style.lua"], [0,1,2,91,92],[6,7,8]))
 
 
 class SlimGazetteerTestSuite(unittest.TestSuite):
@@ -304,10 +307,11 @@ class MultiPolygonSlimRenderingTestSuite(unittest.TestSuite):
                                               [26,27,28,29,30,31,32,33,34,35,36,37,38, 39, 40,41,42, 43, 44, 47, 48,  62, 63, 64, 65, 68, 69, 72, 73, 74, 78, 79, 82, 83, 84, 86, 87, 88,
                                                106,107,108,109,110,111,112,113,114,115,116,117,118,119],
                                               [28,29,30,31,32,33,34,35,36,37,38,39,40,41,42, 43, 44, 47, 48, 62, 63, 64, 65, 66, 67, 70, 71, 75, 76, 79, 80, 81, 83, 84, 85, 87, 89, 90]))
-        self.addTest(MultipolygonSlimTestCase("lua tagtransform case", ["--tag-transform-script", "style.lua"],
+        if lua_test_enabled:
+            self.addTest(MultipolygonSlimTestCase("lua tagtransform case", ["--tag-transform-script", "style.lua"],
                                               [26,27,28,29,30,31,32,33,34,35,36,37,38, 39, 40, 41, 42, 43, 44, 47, 48,  62, 64, 65,68,69, 72, 73, 74, 78, 79, 82, 83, 84, 86, 87, 88,116,117,118,119],
                                               [28,29,30,31,32,33,34,35,36,37,38,39,40,41,42, 43, 44, 47, 48, 62, 63,64, 65, 66, 67, 70, 71, 75, 76, 79, 80, 81, 83, 84, 85, 87, 89, 90]))
-        self.addTest(MultipolygonSlimTestCase("lua tagtransform case with hstore", ["--tag-transform-script", "style.lua", "-k"],
+            self.addTest(MultipolygonSlimTestCase("lua tagtransform case with hstore", ["--tag-transform-script", "style.lua", "-k"],
                                               [26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,47,48,62,63,64,65,68,69,72,73,74,78,79,82,83,84,86,87,88,116,117,118,119],
                                               [28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,47,48,62,63,64,65,66,67,70,71,75,76,79,80,81,83,84,85,87,89,90]))
 
diff --git a/tests/test-expire-tiles.cpp b/tests/test-expire-tiles.cpp
index 82480bf..66fe186 100644
--- a/tests/test-expire-tiles.cpp
+++ b/tests/test-expire-tiles.cpp
@@ -64,17 +64,21 @@ struct xyz {
   }
 };
 
+static std::shared_ptr<reprojection> defproj(reprojection::create_projection(PROJ_SPHERE_MERC));
+
 std::ostream &operator<<(std::ostream &out, const xyz &tile) {
   out << tile.z << "/" << tile.x << "/" << tile.y;
   return out;
 }
 
-struct tile_output_set : public expire_tiles::tile_output {
-  tile_output_set() {}
+struct tile_output_set : public expire_tiles::tile_output
+{
+  tile_output_set(int min) : min_zoom(min) {}
 
-  virtual ~tile_output_set() {}
+  ~tile_output_set() = default;
 
-  virtual void output_dirty_tile(int x, int y, int zoom, int min_zoom) {
+  void output_dirty_tile(int x, int y, int zoom) override
+  {
     int	y_min, x_iter, y_iter, x_max, y_max, out_zoom, zoom_diff;
 
     if (zoom > min_zoom) out_zoom = zoom;
@@ -91,15 +95,12 @@ struct tile_output_set : public expire_tiles::tile_output {
   }
 
   std::set<xyz> m_tiles;
+  int min_zoom;
 };
 
 void test_expire_simple_z1() {
-  options_t opt;
-  opt.expire_tiles_zoom = 1;
-  opt.expire_tiles_zoom_min = 1;
-
-  expire_tiles et(&opt);
-  tile_output_set set;
+  expire_tiles et(1, 20000, defproj);
+  tile_output_set set(1);
 
   // as big a bbox as possible at the origin to dirty all four
   // quadrants of the world.
@@ -115,12 +116,8 @@ void test_expire_simple_z1() {
 }
 
 void test_expire_simple_z3() {
-  options_t opt;
-  opt.expire_tiles_zoom = 3;
-  opt.expire_tiles_zoom_min = 3;
-
-  expire_tiles et(&opt);
-  tile_output_set set;
+  expire_tiles et(3, 20000, defproj);
+  tile_output_set set(3);
 
   // as big a bbox as possible at the origin to dirty all four
   // quadrants of the world.
@@ -136,12 +133,8 @@ void test_expire_simple_z3() {
 }
 
 void test_expire_simple_z18() {
-  options_t opt;
-  opt.expire_tiles_zoom = 18;
-  opt.expire_tiles_zoom_min = 18;
-
-  expire_tiles et(&opt);
-  tile_output_set set;
+  expire_tiles et(18, 20000, defproj);
+  tile_output_set set(18);
 
   // dirty a smaller bbox this time, as at z18 the scale is
   // pretty small.
@@ -198,14 +191,10 @@ void expire_centroids(const std::set<xyz> &check_set,
 // tests that expiring a set of tile centroids means that
 // those tiles get expired.
 void test_expire_set() {
-  options_t opt;
   int zoom = 18;
-  opt.expire_tiles_zoom = zoom;
-  opt.expire_tiles_zoom_min = zoom;
-
   for (int i = 0; i < 100; ++i) {
-    expire_tiles et(&opt);
-    tile_output_set set;
+    expire_tiles et(zoom, 20000, defproj);
+    tile_output_set set(zoom);
 
     std::set<xyz> check_set = generate_random(zoom, 100);
     expire_centroids(check_set, et);
@@ -222,14 +211,13 @@ void test_expire_set() {
 // same as if the union of the sets of tiles had been
 // expired.
 void test_expire_merge() {
-  options_t opt;
   int zoom = 18;
-  opt.expire_tiles_zoom = zoom;
-  opt.expire_tiles_zoom_min = zoom;
 
   for (int i = 0; i < 100; ++i) {
-    expire_tiles et(&opt), et1(&opt), et2(&opt);
-    tile_output_set set;
+    expire_tiles et(zoom, 20000, defproj);
+    expire_tiles et1(zoom, 20000, defproj);
+    expire_tiles et2(zoom, 20000, defproj);
+    tile_output_set set(zoom);
 
     std::set<xyz> check_set1 = generate_random(zoom, 100);
     expire_centroids(check_set1, et1);
@@ -257,14 +245,13 @@ void test_expire_merge() {
 // skipped by the random tile set in the previous
 // test.
 void test_expire_merge_same() {
-  options_t opt;
   int zoom = 18;
-  opt.expire_tiles_zoom = zoom;
-  opt.expire_tiles_zoom_min = zoom;
 
   for (int i = 0; i < 100; ++i) {
-    expire_tiles et(&opt), et1(&opt), et2(&opt);
-    tile_output_set set;
+    expire_tiles et(zoom, 20000, defproj);
+    expire_tiles et1(zoom, 20000, defproj);
+    expire_tiles et2(zoom, 20000, defproj);
+    tile_output_set set(zoom);
 
     std::set<xyz> check_set = generate_random(zoom, 100);
     expire_centroids(check_set, et1);
@@ -282,14 +269,13 @@ void test_expire_merge_same() {
 // makes sure that we're testing the case where some
 // tiles are in both.
 void test_expire_merge_overlap() {
-  options_t opt;
   int zoom = 18;
-  opt.expire_tiles_zoom = zoom;
-  opt.expire_tiles_zoom_min = zoom;
 
   for (int i = 0; i < 100; ++i) {
-    expire_tiles et(&opt), et1(&opt), et2(&opt);
-    tile_output_set set;
+    expire_tiles et(zoom, 20000, defproj);
+    expire_tiles et1(zoom, 20000, defproj);
+    expire_tiles et2(zoom, 20000, defproj);
+    tile_output_set set(zoom);
 
     std::set<xyz> check_set1 = generate_random(zoom, 100);
     expire_centroids(check_set1, et1);
@@ -322,14 +308,15 @@ void test_expire_merge_overlap() {
 // large contiguous areas of tiles (i.e: ensure that we
 // handle the "complete" flag correctly).
 void test_expire_merge_complete() {
-  options_t opt;
   int zoom = 18;
-  opt.expire_tiles_zoom = zoom;
-  opt.expire_tiles_zoom_min = zoom;
 
   for (int i = 0; i < 100; ++i) {
-    expire_tiles et(&opt), et1(&opt), et2(&opt), et0(&opt);
-    tile_output_set set, set0;
+    expire_tiles et(zoom, 20000, defproj);
+    expire_tiles et0(zoom, 20000, defproj);
+    expire_tiles et1(zoom, 20000, defproj);
+    expire_tiles et2(zoom, 20000, defproj);
+    tile_output_set set(zoom);
+    tile_output_set set0(zoom);
 
     // et1&2 are two halves of et0's box
     et0.from_bbox(-10000, -10000, 10000, 10000);
diff --git a/tests/test-options-projection.cpp b/tests/test-options-projection.cpp
new file mode 100644
index 0000000..6aadff5
--- /dev/null
+++ b/tests/test-options-projection.cpp
@@ -0,0 +1,114 @@
+/*
+#-----------------------------------------------------------------------------
+# osm2pgsql - converts planet.osm file into PostgreSQL
+# compatible output suitable to be rendered by mapnik
+#-----------------------------------------------------------------------------
+# Original Python implementation by Artem Pavlenko
+# Re-implementation by Jon Burgess, Copyright 2006
+#
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#-----------------------------------------------------------------------------
+*/
+
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+
+#include "middle-ram.hpp"
+#include "options.hpp"
+#include "output-pgsql.hpp"
+#include "reprojection.hpp"
+
+#include "tests/common.hpp"
+#include "tests/common-pg.hpp"
+
+static const char* option_params[] = { "osm2pgsql", "-S", "default.style",
+                                       "--number-processes", "1",
+                                       "foo", "foo", "foo" };
+enum params {  DEF_PARAMS = 5 };
+
+static void check_tables(pg::tempdb *db, options_t &options,
+                        const std::string &expected)
+{
+    options.database_options = db->database_options;
+    auto mid_ram = std::make_shared<middle_ram_t>();
+    auto out_test = std::make_shared<output_pgsql_t>(mid_ram.get(), options);
+    osmdata_t osmdata(mid_ram, out_test);
+
+    testing::parse("tests/liechtenstein-2013-08-03.osm.pbf", "pbf",
+                   options, &osmdata);
+
+    db->check_string(expected, "select find_srid('public', 'planet_osm_roads', 'way')");
+}
+
+void check_projection(options_t &options, const char *expected)
+{
+   if (strcmp(options.projection->target_desc(), expected)) {
+        std::cerr << "Unexpected projection when no option is given. Got "
+                  << options.projection->target_desc() << "\n";
+        throw std::runtime_error("Bad projection");
+    }
+
+}
+
+static void test_no_options(pg::tempdb *db)
+{
+    options_t options(DEF_PARAMS + 1, (char **) option_params);
+    check_projection(options, "Spherical Mercator");
+    check_tables(db, options, "3857");
+}
+
+static void test_latlon_option(pg::tempdb *db)
+{
+    option_params[DEF_PARAMS] = "-l";
+    options_t options(DEF_PARAMS + 2, (char **) option_params);
+    check_projection(options, "Latlong");
+    check_tables(db, options, "4326");
+}
+
+static void test_merc_option(pg::tempdb *db)
+{
+    option_params[DEF_PARAMS] = "-m";
+    options_t options(DEF_PARAMS + 2, (char **) option_params);
+    check_projection(options, "Spherical Mercator");
+    check_tables(db, options, "3857");
+}
+
+static void test_e_option(pg::tempdb *db, const char *param,
+                          const char *expected_proj)
+{
+    option_params[DEF_PARAMS] = "-E";
+    option_params[DEF_PARAMS + 1] = param;
+    options_t options(DEF_PARAMS + 3, (char **) option_params);
+    if (expected_proj)
+        check_projection(options, expected_proj);
+    check_tables(db, options, param);
+}
+
+
+
+int main()
+{
+    auto db = pg::tempdb::create_db_or_skip();
+
+    test_no_options(db.get());
+    test_latlon_option(db.get());
+    test_merc_option(db.get());
+    test_e_option(db.get(), "4326", "Latlong");
+    test_e_option(db.get(), "3857", "Spherical Mercator");
+    test_e_option(db.get(), "32632", nullptr);
+
+    return 0;
+}
diff --git a/tests/test-output-pgsql-validgeom.cpp b/tests/test-output-pgsql-validgeom.cpp
new file mode 100644
index 0000000..280ed82
--- /dev/null
+++ b/tests/test-output-pgsql-validgeom.cpp
@@ -0,0 +1,97 @@
+#include <iostream>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <cassert>
+#include <sstream>
+#include <stdexcept>
+#include <memory>
+
+#include "osmtypes.hpp"
+#include "osmdata.hpp"
+#include "output-pgsql.hpp"
+#include "options.hpp"
+#include "middle-pgsql.hpp"
+#include "middle-ram.hpp"
+#include "taginfo_impl.hpp"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include "tests/middle-tests.hpp"
+#include "tests/common.hpp"
+#include "tests/common-pg.hpp"
+
+namespace {
+
+struct skip_test : public std::exception {
+    const char *what() const noexcept { return "Test skipped."; }
+};
+
+void run_test(const char* test_name, void (*testfunc)()) {
+    try {
+        fprintf(stderr, "%s\n", test_name);
+        testfunc();
+
+    } catch (const skip_test &) {
+        exit(77); // <-- code to skip this test.
+
+    } catch (const std::exception& e) {
+        fprintf(stderr, "%s\n", e.what());
+        fprintf(stderr, "FAIL\n");
+        exit(EXIT_FAILURE);
+    }
+
+    fprintf(stderr, "PASS\n");
+}
+#define RUN_TEST(x) run_test(#x, &(x))
+
+// "simple" test modeled on the basic regression test from
+// the python script. this is just to check everything is
+// working as expected before we start the complex stuff.
+void test_z_order() {
+    std::unique_ptr<pg::tempdb> db;
+
+    try {
+        db.reset(new pg::tempdb);
+    } catch (const std::exception &e) {
+        std::cerr << "Unable to setup database: " << e.what() << "\n";
+        throw skip_test();
+    }
+
+    std::string proc_name("test-output-pgsql-validgeom"), input_file("-");
+    char *argv[] = { &proc_name[0], &input_file[0], nullptr };
+
+    std::shared_ptr<middle_pgsql_t> mid_pgsql(new middle_pgsql_t());
+    options_t options = options_t(2, argv);
+    options.database_options = db->database_options;
+    options.num_procs = 1;
+    options.prefix = "osm2pgsql_test";
+    options.style = "default.style";
+
+    auto out_test = std::make_shared<output_pgsql_t>(mid_pgsql.get(), options);
+
+    osmdata_t osmdata(mid_pgsql, out_test);
+
+    testing::parse("tests/test_output_pgsql_validgeom.osm", "xml",
+                   options, &osmdata);
+
+    db->assert_has_table("osm2pgsql_test_point");
+    db->assert_has_table("osm2pgsql_test_line");
+    db->assert_has_table("osm2pgsql_test_polygon");
+    db->assert_has_table("osm2pgsql_test_roads");
+
+    db->check_count(6, "SELECT COUNT(*) FROM osm2pgsql_test_polygon");
+    db->check_count(0, "SELECT COUNT(*) FROM osm2pgsql_test_polygon WHERE NOT ST_IsValid(way)");
+    db->check_count(0, "SELECT COUNT(*) FROM osm2pgsql_test_polygon WHERE ST_IsEmpty(way)");
+}
+
+} // anonymous namespace
+
+int main(int argc, char *argv[]) {
+    RUN_TEST(test_z_order);
+
+    return 0;
+}
diff --git a/tests/test-output-pgsql.cpp b/tests/test-output-pgsql.cpp
index 5f7ba75..d324ea9 100644
--- a/tests/test-output-pgsql.cpp
+++ b/tests/test-output-pgsql.cpp
@@ -101,7 +101,7 @@ void test_regression_simple() {
     db->check_number(143.81, "SELECT ST_Area(ST_Transform(way,4326)::geography) FROM osm2pgsql_test_polygon WHERE osm_id = 157261342");
 
     // Check a point's location
-    db->check_count(1, "SELECT count(*) FROM osm2pgsql_test_point WHERE ST_DWithin(way, 'SRID=900913;POINT(1062645.12 5972593.4)'::geometry, 0.1)");
+    db->check_count(1, "SELECT count(*) FROM osm2pgsql_test_point WHERE ST_DWithin(way, 'SRID=3857;POINT(1062645.12 5972593.4)'::geometry, 0.1)");
 }
 
 void test_latlong() {
diff --git a/tests/test-parse-xml2.cpp b/tests/test-parse-xml2.cpp
index 88eeef5..8688637 100644
--- a/tests/test-parse-xml2.cpp
+++ b/tests/test-parse-xml2.cpp
@@ -29,7 +29,7 @@ struct test_output_t : public output_t {
     }
 
     explicit test_output_t(const test_output_t &other)
-        : output_t(this->m_mid, this->m_options), sum_ids(0), num_nodes(0), num_ways(0), num_relations(0),
+        : output_t(other.m_mid, other.m_options), sum_ids(0), num_nodes(0), num_ways(0), num_relations(0),
           num_nds(0), num_members(0) {
     }
 
diff --git a/tests/test-wildcard-match.cpp b/tests/test-wildcard-match.cpp
new file mode 100644
index 0000000..e8145d1
--- /dev/null
+++ b/tests/test-wildcard-match.cpp
@@ -0,0 +1,61 @@
+/*
+ * Test wildcard matching function.
+ */
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "wildcmp.hpp"
+
+struct test_t {
+    std::string wildcard;
+    std::string match;
+    bool result;
+
+    test_t(const std::string& w, const std::string& m, bool r)
+    : wildcard(w), match(m), result(r) {}
+};
+
+static std::vector<test_t> tests {
+    {"fhwieurwe", "fhwieurwe", true},
+    {"fhwieurwe", "fhwieurw", false},
+    {"fhwieurw", "fhwieurwe", false},
+    {"*", "foo", true},
+    {"r*", "foo", false},
+    {"r*", "roo", true},
+    {"*bar", "Hausbar", true},
+    {"*bar", "Haustar", false},
+    {"*", "", true},
+    {"kin*la", "kinla", true},
+    {"kin*la", "kinLLla", true},
+    {"kin*la", "kinlalalala", true},
+    {"kin*la", "kinlaa", false},
+    {"kin*la", "ki??laa", false},
+    {"1*2*3", "123", true},
+    {"1*2*3", "1xX23", true},
+    {"1*2*3", "12y23", true},
+    {"1*2*3", "12", false},
+    {"bo??f", "boxxf", true},
+    {"bo??f", "boxf", false},
+    {"?5?", "?5?", true},
+    {"?5?", "x5x", true},
+};
+
+int main()
+{
+    int ret = 0;
+
+    for (const auto& test: tests) {
+        if (bool(wildMatch(test.wildcard.c_str(), test.match.c_str())) != test.result) {
+            std::cerr << "Wildcard match failed:";
+            std::cerr << "\n  expression: " << test.wildcard;
+            std::cerr << "\n  test string: " << test.match;
+            std::cerr << "\n  expected: " << test.result;
+            std::cerr << "\n  got: " << (!test.result) << "\n";
+            ret = 1;
+        }
+    }
+
+    return ret;
+}
diff --git a/tests/test_output_multi_tags.json b/tests/test_output_multi_tags.json
index ec86554..8dbc13b 100644
--- a/tests/test_output_multi_tags.json
+++ b/tests/test_output_multi_tags.json
@@ -1,9 +1,3 @@
-/*
-These tables provide a test that tag processing works, and that one tag
-processing does not clobber another. They are not exhaustive,
-particularly with regards to interactions between way/area detection and
-relations.
-*/
 [
   {
     "name": "test_points_1",
@@ -11,28 +5,25 @@ relations.
     "tagtransform": "tests/test_output_multi_tags.lua",
     "tagtransform-node-function": "test_nodes_1",
     "tagtransform-way-function": "drop_all",
-    /* No relations in the file */
     "tagtransform-relation-function": "drop_all",
     "tagtransform-relation-member-function": "drop_all",
     "tags": [
       {"name": "foo", "type": "text"},
       {"name": "bar", "type": "text"},
-      {"name": "baz", "type": "text"} /* left empty by the transform */
+      {"name": "baz", "type": "text"}
     ]
   },
   {
-    /* This second table will process the same data, but resulting in different tags */
     "name": "test_points_2",
     "type": "point",
     "tagtransform": "tests/test_output_multi_tags.lua",
     "tagtransform-node-function": "test_nodes_2",
     "tagtransform-way-function": "drop_all",
-    /* No relations in the file */
     "tagtransform-relation-function": "drop_all",
     "tagtransform-relation-member-function": "drop_all",
     "tags": [
       {"name": "foo", "type": "text"},
-      {"name": "bar", "type": "text"}, /* left empty by the transform */
+      {"name": "bar", "type": "text"},
       {"name": "baz", "type": "text"}
     ]
   },
@@ -42,13 +33,12 @@ relations.
     "tagtransform": "tests/test_output_multi_tags.lua",
     "tagtransform-node-function": "drop_all",
     "tagtransform-way-function": "test_line_1",
-    /* No relations in the file */
     "tagtransform-relation-function": "drop_all",
     "tagtransform-relation-member-function": "drop_all",
     "tags": [
       {"name": "foo", "type": "text"},
       {"name": "bar", "type": "text"},
-      {"name": "baz", "type": "text"} /* left empty by the transform */
+      {"name": "baz", "type": "text"}
     ]
   },
   {
@@ -57,12 +47,11 @@ relations.
     "tagtransform": "tests/test_output_multi_tags.lua",
     "tagtransform-node-function": "drop_all",
     "tagtransform-way-function": "test_line_2",
-    /* No relations in the file */
     "tagtransform-relation-function": "drop_all",
     "tagtransform-relation-member-function": "drop_all",
     "tags": [
       {"name": "foo", "type": "text"},
-      {"name": "bar", "type": "text"}, /* left empty by the transform */
+      {"name": "bar", "type": "text"},
       {"name": "baz", "type": "text"}
     ]
   },
@@ -72,13 +61,12 @@ relations.
     "tagtransform": "tests/test_output_multi_tags.lua",
     "tagtransform-node-function": "drop_all",
     "tagtransform-way-function": "test_polygon_1",
-    /* No relations in the file */
     "tagtransform-relation-function": "drop_all",
     "tagtransform-relation-member-function": "drop_all",
     "tags": [
       {"name": "foo", "type": "text"},
       {"name": "bar", "type": "text"},
-      {"name": "baz", "type": "text"} /* left empty by the transform */
+      {"name": "baz", "type": "text"}
     ]
   },
   {
@@ -87,13 +75,12 @@ relations.
     "tagtransform": "tests/test_output_multi_tags.lua",
     "tagtransform-node-function": "drop_all",
     "tagtransform-way-function": "test_polygon_2",
-    /* No relations in the file */
     "tagtransform-relation-function": "drop_all",
     "tagtransform-relation-member-function": "drop_all",
     "tags": [
       {"name": "foo", "type": "text"},
       {"name": "bar", "type": "text"},
-      {"name": "baz", "type": "text"} /* left empty by the transform */
+      {"name": "baz", "type": "text"}
     ]
   }
 ]
diff --git a/tests/test_output_pgsql_validgeom.osm b/tests/test_output_pgsql_validgeom.osm
new file mode 100644
index 0000000..6f6d8bb
--- /dev/null
+++ b/tests/test_output_pgsql_validgeom.osm
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6" generator="CGImap 0.4.0 (1701 thorn-02.openstreetmap.org)" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
+ <node id='1804384035' visible='true' lat='50.5539565' lon='8.6482181' />
+ <node id='1804384038' visible='true' lat='50.5539629' lon='8.6481648' />
+ <node id='1804384039' visible='true' lat='50.553966' lon='8.6481391' />
+ <node id='1804384040' visible='true' lat='50.5539686' lon='8.6481399' />
+ <node id='1804384044' visible='true' lat='50.5539795' lon='8.6482828' />
+ <node id='1804384047' visible='true' lat='50.5539862' lon='8.6482269' />
+ <node id='1804384055' visible='true' lat='50.5540076' lon='8.6481515' />
+ <node id='1804384056' visible='true' lat='50.5540093' lon='8.6481372' />
+ <node id='1804384085' visible='true' lat='50.5540642' lon='8.648308' />
+ <node id='1804384087' visible='true' lat='50.5540702' lon='8.6481694' />
+ <node id='1804384089' visible='true' lat='50.5540725' lon='8.648156' />
+ <node id='1804384092' visible='true' lat='50.5540804' lon='8.6481732' />
+ <node id="2659775198" visible="true" lat="51.7084540" lon="-113.2670930"/>
+ <node id="2659775222" visible="true" lat="51.7080945" lon="-113.2671020"/>
+ <node id="2659775225" visible="true" lat="51.7080114" lon="-113.2672826"/>
+ <node id="2659775227" visible="true" lat="51.7080129" lon="-113.2669508"/>
+ <node id="2659775229" visible="true" lat="51.7078990" lon="-113.2669494"/>
+ <node id="2659775231" visible="true" lat="51.7078968" lon="-113.2674229"/>
+ <node id="2659775233" visible="true" lat="51.7080869" lon="-113.2674252"/>
+ <node id="2659775240" visible="true" lat="51.7079749" lon="-113.2677588"/>
+ <node id="2659775242" visible="true" lat="51.7079746" lon="-113.2676063"/>
+ <node id="2659775244" visible="true" lat="51.7076598" lon="-113.2676078"/>
+ <node id="2659775411" visible="true" lat="51.7080661" lon="-113.2672880"/>
+ <node id="2659775461" visible="true" lat="51.7080689" lon="-113.2671023"/>
+ <node id="2659775462" visible="true" lat="51.7079742" lon="-113.2678325"/>
+ <node id="2659775464" visible="true" lat="51.7076402" lon="-113.2667319"/>
+ <node id="2659775466" visible="true" lat="51.7080125" lon="-113.2667340"/>
+ <node id="2659775468" visible="true" lat="51.7080125" lon="-113.2665246"/>
+ <node id="2659775472" visible="true" lat="51.7084410" lon="-113.2661419"/>
+ <node id="2659775475" visible="true" lat="51.7084010" lon="-113.2664730"/>
+ <node id="2659775477" visible="true" lat="51.7083981" lon="-113.2670147"/>
+ <node id="2659775479" visible="true" lat="51.7084326" lon="-113.2671126"/>
+ <node id="2659775481" visible="true" lat="51.7084401" lon="-113.2664240"/>
+ <node id="2659775470" visible="true" lat="51.7080721" lon="-113.2664637"/>
+ <node id="2659775474" visible="true" lat="51.7080732" lon="-113.2661178"/>
+ <node id="2659775413" visible="true" lat="51.7080712" lon="-113.2676946"/>
+ <node id="2659775440" visible="true" lat="51.7080743" lon="-113.2678341"/>
+ <node id="3357025788" visible="true" lat="54.1141803" lon="-3.2313862"/>
+ <node id="3357025789" visible="true" lat="54.1140676" lon="-3.2312034"/>
+ <node id="3357025790" visible="true" lat="54.1139595" lon="-3.2313974"/>
+ <node id="3357025791" visible="true" lat="54.1140722" lon="-3.2315802"/>
+ <node id="3357025792" visible="true" lat="54.1142050" lon="-3.2313843"/>
+ <node id="3357025893" visible="true" lat="54.1140685" lon="-3.2311649"/>
+ <node id="3357025894" visible="true" lat="54.1139382" lon="-3.2313999"/>
+ <node id="3357025895" visible="true" lat="54.1140745" lon="-3.2316202"/>
+ <node id="3357025896" visible="true" lat="54.1141160" lon="-3.2316073"/>
+ <node id="3357025897" visible="true" lat="54.1141971" lon="-3.2313149"/>
+ <node id="3357025898" visible="true" lat="54.1140271" lon="-3.2311778"/>
+ <node id="3357025899" visible="true" lat="54.1139460" lon="-3.2314694"/>
+ <node id="3357025900" visible="true" lat="54.1141530" lon="-3.2315730"/>
+ <node id="3357025901" visible="true" lat="54.1141819" lon="-3.2315207"/>
+ <node id="3357025902" visible="true" lat="54.1141998" lon="-3.2314557"/>
+ <node id="3357025903" visible="true" lat="54.1141771" lon="-3.2312530"/>
+ <node id="3357025904" visible="true" lat="54.1141469" lon="-3.2312046"/>
+ <node id="3357025905" visible="true" lat="54.1141095" lon="-3.2311742"/>
+ <node id="3357025906" visible="true" lat="54.1139902" lon="-3.2312120"/>
+ <node id="3357025907" visible="true" lat="54.1139614" lon="-3.2312641"/>
+ <node id="3357025908" visible="true" lat="54.1139435" lon="-3.2313288"/>
+ <node id="3357025909" visible="true" lat="54.1139659" lon="-3.2315315"/>
+ <node id="3357025910" visible="true" lat="54.1140334" lon="-3.2316107"/>
+ <node id="3357025911" visible="true" lat="54.1139368" lon="-3.2315294"/>
+ <node id="3357025912" visible="true" lat="54.1139689" lon="-3.2315944"/>
+ <node id="3357025913" visible="true" lat="54.1140148" lon="-3.2316414"/>
+ <node id="3357025914" visible="true" lat="54.1141797" lon="-3.2311993"/>
+ <node id="3357025915" visible="true" lat="54.1141387" lon="-3.2311509"/>
+ <node id="3357025916" visible="true" lat="54.1140672" lon="-3.2316592"/>
+ <node id="3357025917" visible="true" lat="54.1141201" lon="-3.2316459"/>
+ <node id="3357025918" visible="true" lat="54.1141673" lon="-3.2316030"/>
+ <node id="3357025919" visible="true" lat="54.1142033" lon="-3.2315355"/>
+ <node id="3357025920" visible="true" lat="54.1142239" lon="-3.2314513"/>
+ <node id="3357025921" visible="true" lat="54.1142267" lon="-3.2313602"/>
+ <node id="3357025922" visible="true" lat="54.1142114" lon="-3.2312728"/>
+ <node id="3357025923" visible="true" lat="54.1140909" lon="-3.2311269"/>
+ <node id="3357025924" visible="true" lat="54.1140411" lon="-3.2311298"/>
+ <node id="3357025925" visible="true" lat="54.1139943" lon="-3.2311592"/>
+ <node id="3357025926" visible="true" lat="54.1139553" lon="-3.2312122"/>
+ <node id="3357025927" visible="true" lat="54.1139281" lon="-3.2312834"/>
+ <node id="3357025928" visible="true" lat="54.1139153" lon="-3.2313655"/>
+ <node id="3357025929" visible="true" lat="54.1139183" lon="-3.2314504"/>
+ <node id="3357025930" visible="true" lat="54.1139960" lon="-3.2315802"/>
+ <node id="4370458249" visible="true" lat="-38.9566624" lon="-68.1336890"/>
+ <node id="4370458250" visible="true" lat="-38.9567937" lon="-68.1336863"/>
+ <node id="4370458251" visible="true" lat="-38.9567916" lon="-68.1335173"/>
+ <node id="4370458252" visible="true" lat="-38.9566603" lon="-68.1335200"/>
+ <way id='169272321' visible='true' version='7'>
+    <nd ref='1804384056' />
+    <nd ref='1804384089' />
+    <nd ref='1804384087' />
+    <nd ref='1804384055' />
+    <nd ref='1804384056' />
+    <tag k='building' v='yes' />
+  </way>
+  <way id='169272605' visible='true' version='7'>
+    <nd ref='1804384038' />
+    <nd ref='1804384035' />
+    <nd ref='1804384047' />
+    <nd ref='1804384044' />
+    <nd ref='1804384085' />
+    <nd ref='1804384092' />
+    <nd ref='1804384087' />
+    <nd ref='1804384055' />
+    <nd ref='1804384040' />
+    <nd ref='1804384039' />
+    <nd ref='1804384038' />
+    <tag k='addr:city' v='Kleinlinden' />
+    <tag k='addr:country' v='DE' />
+    <tag k='addr:housenumber' v='12' />
+    <tag k='addr:postcode' v='35398' />
+    <tag k='addr:state' v='Hessen' />
+    <tag k='addr:street' v='Gregor-Mendel-Straße' />
+    <tag k='building' v='yes' />
+  </way>
+ <way id="260490104" visible="true" uid="1820014">
+  <nd ref="2659775198"/>
+  <nd ref="2659775479"/>
+  <nd ref="2659775222"/>
+  <nd ref="2659775461"/>
+  <nd ref="2659775411"/>
+  <nd ref="2659775225"/>
+  <nd ref="2659775227"/>
+  <nd ref="2659775229"/>
+  <nd ref="2659775231"/>
+  <nd ref="2659775233"/>
+  <nd ref="2659775413"/>
+  <nd ref="2659775440"/>
+  <nd ref="2659775462"/>
+  <nd ref="2659775240"/>
+  <nd ref="2659775242"/>
+  <nd ref="2659775244"/>
+  <nd ref="2659775464"/>
+  <nd ref="2659775466"/>
+  <nd ref="2659775468"/>
+  <nd ref="2659775470"/>
+  <nd ref="2659775474"/>
+  <nd ref="2659775472"/>
+  <nd ref="2659775481"/>
+  <nd ref="2659775475"/>
+  <nd ref="2659775477"/>
+  <nd ref="2659775479"/>
+  <nd ref="2659775198"/>
+  <tag k="landuse" v="grass"/>
+ </way>
+ <way id="328880342" visible="true" version="1" changeset="28922079" >
+  <nd ref="3357025791"/>
+  <nd ref="3357025788"/>
+  <nd ref="3357025789"/>
+  <nd ref="3357025790"/>
+  <nd ref="3357025791"/>
+  <tag k="leisure" v="garden"/>
+ </way>
+ <way id="328880345" visible="true">
+  <nd ref="3357025902"/>
+  <nd ref="3357025792"/>
+  <nd ref="3357025895"/>
+  <nd ref="3357025896"/>
+  <nd ref="3357025900"/>
+  <nd ref="3357025901"/>
+  <nd ref="3357025902"/>
+  <tag k="leisure" v="garden"/>
+ </way>
+ <way id="328880346" visible="true">
+  <nd ref="3357025897"/>
+  <nd ref="3357025903"/>
+  <nd ref="3357025904"/>
+  <nd ref="3357025905"/>
+  <nd ref="3357025893"/>
+  <nd ref="3357025792"/>
+  <nd ref="3357025897"/>
+  <tag k="leisure" v="garden"/>
+ </way>
+ <way id="328880347" visible="true">
+  <nd ref="3357025906"/>
+  <nd ref="3357025907"/>
+  <nd ref="3357025908"/>
+  <nd ref="3357025894"/>
+  <nd ref="3357025893"/>
+  <nd ref="3357025898"/>
+  <nd ref="3357025906"/>
+  <tag k="leisure" v="garden"/>
+ </way>
+ <way id="328880348" visible="true">
+  <nd ref="3357025909"/>
+  <nd ref="3357025930"/>
+  <nd ref="3357025910"/>
+  <nd ref="3357025895"/>
+  <nd ref="3357025894"/>
+  <nd ref="3357025899"/>
+  <nd ref="3357025909"/>
+  <tag k="leisure" v="garden"/>
+ </way>
+ <way id="328880344" visible="true">
+  <nd ref="3357025912"/>
+  <nd ref="3357025911"/>
+  <nd ref="3357025929"/>
+  <nd ref="3357025928"/>
+  <nd ref="3357025927"/>
+  <nd ref="3357025926"/>
+  <nd ref="3357025925"/>
+  <nd ref="3357025924"/>
+  <nd ref="3357025923"/>
+  <nd ref="3357025915"/>
+  <nd ref="3357025914"/>
+  <nd ref="3357025922"/>
+  <nd ref="3357025921"/>
+  <nd ref="3357025920"/>
+  <nd ref="3357025919"/>
+  <nd ref="3357025918"/>
+  <nd ref="3357025917"/>
+  <nd ref="3357025916"/>
+  <nd ref="3357025913"/>
+  <nd ref="3357025912"/>
+ </way>
+ <way id="439432117" visible="true" version="1">
+  <nd ref="4370458249"/>
+  <nd ref="4370458250"/>
+  <nd ref="4370458251"/>
+  <nd ref="4370458252"/>
+  <nd ref="4370458251"/>
+  <nd ref="4370458252"/>
+  <nd ref="4370458249"/>
+  <tag k="building" v="church"/>
+  <tag k="name" v="Parroquia "María Madre de los Pobres""/>
+ </way>
+ <relation id="3726445" version="2" visible="true">
+    <member type="way" ref="169272321" role="outer"/>
+    <member type="way" ref="169272605" role="outer"/>
+    <member type="way" ref="169272321" role="outer"/>
+    <tag k="addr:city" v="Kleinlinden"/>
+    <tag k="addr:country" v="DE"/>
+    <tag k="addr:housenumber" v="12"/>
+    <tag k="addr:postcode" v="35398"/>
+    <tag k="addr:state" v="Hessen"/>
+    <tag k="addr:street" v="Gregor-Mendel-Straße"/>
+    <tag k="building" v="yes"/>
+    <tag k="source" v="HiRes aerial imagery (Aerowest)"/>
+    <tag k="type" v="multipolygon"/>
+ </relation>
+ <relation id="4621350" visible="true">
+  <member type="way" ref="328880344" role="outer"/>
+  <member type="way" ref="328880342" role="inner"/>
+  <member type="way" ref="328880345" role="inner"/>
+  <member type="way" ref="328880346" role="inner"/>
+  <member type="way" ref="328880347" role="inner"/>
+  <member type="way" ref="328880348" role="inner"/>
+  <tag k="natural" v="grassland"/>
+  <tag k="type" v="multipolygon"/>
+ </relation>
+</osm>
diff --git a/wildcmp.cpp b/wildcmp.cpp
index 81b302d..64e68d3 100644
--- a/wildcmp.cpp
+++ b/wildcmp.cpp
@@ -1,106 +1,35 @@
 /* Wildcard matching.
 
-   heavily based on wildcmp.c copyright 2002 Jim Kent
-
 */
-#include <ctype.h>
-#include "wildcmp.hpp"
-
-static int subMatch(const char *str, const char *wild)
-/* Returns number of characters that match between str and wild up
- * to the next wildcard in wild (or up to end of string.). */
-{
-int len = 0;
 
-for(;;)
-    {
-    if(toupper(*str++) != toupper(*wild++) )
-        return(0);
-    ++len;
-    switch(*wild)
-        {
-        case 0:
-        case '?':
-        case '*':
-            return(len);
-        }
-    }
-}
-
-int wildMatch(const char *wildCard, const char *string)
-/* does a case sensitive wild card match with a string.
+/**
+ * Case sensitive wild card match with a string.
  * * matches any string or no character.
  * ? matches any single character.
  * anything else etc must match the character exactly.
-
-returns NO_MATCH, FULL_MATCH or WC_MATCH defined in wildcmp.h
-
-*/
+ *
+ * Returns if a match was found.
+ */
+bool wildMatch(const char *first, const char *second)
 {
-int matchStar = 0;
-int starMatchSize;
-int wildmatch=0;
+    // Code borrowed from
+    // http://www.geeksforgeeks.org/wildcard-character-matching/
+    if (*first == '\0' && *second == '\0') {
+        return true;
+    }
 
-for(;;)
-    {
-NEXT_WILD:
-    switch(*wildCard)
-	{
-	case 0: /* end of wildcard */
-	    {
-	    if(matchStar)
-		{
-		while(*string++)
-                    ;
-		return wildmatch ? WC_MATCH : FULL_MATCH;
-                }
-            else if(*string)
-		return NO_MATCH;
-            else {
-                return wildmatch ? WC_MATCH : FULL_MATCH;
-                }
-	    }
-	case '*':
-	    wildmatch = 1;
-	    matchStar = 1;
-	    break;
-	case '?': /* anything will do */
-	    wildmatch = 1;
-	    {
-	    if(*string == 0)
-	        return NO_MATCH; /* out of string, no match for ? */
-	    ++string;
-	    break;
-	    }
-	default:
-	    {
-	    if(matchStar)
-        {
-		for(;;)
-		    {
-		    if(*string == 0) /* if out of string no match */
-		        return NO_MATCH;
+    if (*first == '*' && *(first+1) != '\0' && *second == '\0') {
+        return false;
+    }
 
-		    /* note matchStar is re-used here for substring
-		     * after star match length */
-		    if((starMatchSize = subMatch(string,wildCard)) != 0)
-		        {
-			string += starMatchSize;
-			wildCard += starMatchSize;
-			matchStar = 0;
-			goto NEXT_WILD;
-		        }
-		    ++string;
-		    }
-	        }
+    if (*first == '?' || *first == *second) {
+        return wildMatch(first+1, second+1);
+    }
 
-	    /* default: they must be equal or no match */
-	    if(toupper(*string) != toupper(*wildCard))
-		return NO_MATCH;
-	    ++string;
-	    break;
-	    }
-	}
-    ++wildCard;
+    if (*first == '*') {
+        return wildMatch(first+1, second) || wildMatch(first, second+1);
     }
+
+    return false;
 }
+
diff --git a/wildcmp.hpp b/wildcmp.hpp
index ff88287..602bbb9 100644
--- a/wildcmp.hpp
+++ b/wildcmp.hpp
@@ -1,10 +1,6 @@
 #ifndef WILDCMP_H
 #define WILDCMP_H
 
-#define NO_MATCH 0
-#define FULL_MATCH 1
-#define WC_MATCH 2
-
-int wildMatch(const char *wildCard, const char *string);
+bool wildMatch(const char* wildCard, const char* string);
 
 #endif

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



More information about the Pkg-grass-devel mailing list