[pyosmium] 01/08: Imported Upstream version 2.12.0

Bas Couwenberg sebastic at debian.org
Mon Mar 20 20:34:19 UTC 2017


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

sebastic pushed a commit to branch master
in repository pyosmium.

commit ad582abe4ffe7fa2abb986549242353afa4cd53a
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Mon Mar 20 18:30:26 2017 +0100

    Imported Upstream version 2.12.0
---
 .gitignore                   |   2 +
 CHANGELOG.md                 |  10 ++-
 README.md                    |  26 +++++-
 README.rst                   |   6 +-
 doc/intro.rst                |  16 ++--
 doc/ref_index.rst            |   2 +-
 doc/ref_osm.rst              |   4 +-
 doc/ref_osmium.rst           |   4 +-
 doc/ref_replication.rst      |  14 +++
 doc/reference.rst            |   1 +
 lib/generic_handler.hpp      |  30 +++----
 lib/index.cc                 |   2 +-
 lib/io.cc                    |  19 ++++-
 lib/merged_input.hpp         |  68 +++++++++++++++
 lib/osm.cc                   |  13 +--
 lib/osmium.cc                |  33 +++++++-
 lib/write_handler.hpp        |  68 +++++++++++++++
 osmium/replication/server.py |  93 ++++++++++++++++++--
 osmium/replication/utils.py  |  66 +++++++++++++++
 osmium/version.py            |   6 +-
 setup.py                     |   5 +-
 tools/pyosmium-get-changes   | 197 +++++++++++++++++++++++++++++++++++++++++++
 tools/pyosmium-up-to-date    | 191 +++++++++++++++++++++++++++++++++++++++++
 23 files changed, 819 insertions(+), 57 deletions(-)

diff --git a/.gitignore b/.gitignore
index 6f97ca1..3ce5847 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 build
+dist
+osmium.egg-info
 *.pyc
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ddc9423..79d0089 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,10 +8,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Added
 
+- WriteHandler for writing data directly to a file
+- tools for downloading changes and updating a OSM files from these changes
+- get/set functions for io.Header
+
 ### Changed
 
+- use current libosmium
+
 ### Fixed
 
+- various typos in documentation
 
 ## [2.11.0] - 2017-01-15
 
@@ -123,7 +130,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 - Exception not caught in test.
 
-[unreleased]: https://github.com/osmcode/pyosmium/compare/v2.11.0...HEAD
+[unreleased]: https://github.com/osmcode/pyosmium/compare/v2.12.0...HEAD
+[2.12.0]: https://github.com/osmcode/pyosmium/compare/v2.11.0...v2.12.0
 [2.11.0]: https://github.com/osmcode/pyosmium/compare/v2.10.2...v2.11.0
 [2.10.2]: https://github.com/osmcode/pyosmium/compare/v2.9.0...v2.10.2
 [2.9.0]: https://github.com/osmcode/pyosmium/compare/v2.8.0...v2.9.0
diff --git a/README.md b/README.md
index c738975..3d86203 100644
--- a/README.md
+++ b/README.md
@@ -11,15 +11,21 @@ manner.
 
 Python >= 2.7 is supported but a version >= 3.3 is strongly recommended.
 
+You'll need Python setuptools. On Debian/Ubuntu install `python-setuptools`.
+
 pyosmium uses [Boost.Python](http://www.boost.org/doc/libs/1_56_0/libs/python/doc/index.html)
-to create the bindings. On Debian/Ubuntu install `libboost-python-dev`. OS X run `brew install boost-python` or `brew install boost-python --with-python3` depending on which python version you want to use – You can also (re)install both.
+to create the bindings. On Debian/Ubuntu install `libboost-python-dev`. OS X run
+`brew install boost-python` or `brew install boost-python --with-python3`
+depending on which python version you want to use. You can also (re)install
+both.
+
+Other dependencies are expat, libz, libbz2 and Boost iterator.
+See [Libosmium dependencies](http://osmcode.org/libosmium/manual.html#dependencies)
+for required packages for various distributions.
 
 You have to compile with the same compiler version python is compiled with on
 your system, otherwise it might not work.
 
-Libosmium is expected to reside in the same directory as pyosmium or to be
-installed globally.
-
 ## Installation
 
 To compile the bindings, run
@@ -28,8 +34,20 @@ To compile the bindings, run
 
 To compile and install the bindings, run
 
+    python setup.py install --user
+
+to install only for your user, or
+
     python setup.py install
 
+to install globally.
+
+The setup script uses per default either a globally installed libosmium or
+looks for the source in `../libosmium`. You can set a custom location with
+`LIBOSMIUM_PREFIX`.
+
+To use a custom boost installation, set `BOOST_PREFIX`.
+
 ## Examples
 
 The `example` directory contains small examples on how to use the library.
diff --git a/README.rst b/README.rst
index 5a3fe97..b45db7e 100644
--- a/README.rst
+++ b/README.rst
@@ -23,11 +23,11 @@ depending on which python version you want to use. You can also (re)install both
 
 Python >= 2.7 is supported but a version >= 3.3 is strongly recommended.
 
-Other dependencies are expat, libz, and libbz2. See `Libosmium dependencies`_
-for required packages for various distributions.
+Other dependencies are expat, libz, libbz2 and Boost iterator.
+See `Libosmium dependencies`_ for required packages for various distributions.
 
 .. _Boost.Python: http://www.boost.org/doc/libs/1_56_0/libs/python/doc/index.html
-.. _Libosmium dependencies: https://github.com/osmcode/libosmium/wiki/Libosmium-dependencies
+.. _Libosmium dependencies: http://osmcode.org/libosmium/manual.html#dependencies
 
 Documentation
 =============
diff --git a/doc/intro.rst b/doc/intro.rst
index fe2f35b..13dd236 100644
--- a/doc/intro.rst
+++ b/doc/intro.rst
@@ -37,7 +37,7 @@ input file::
 A handler first of all needs to inherit from one of the handler classes.
 At the moment this is always :py:class:`osmium.SimpleHandler`. Then it
 needs to implement functions for each object type it wants to process. In
-out case it is exactly one function `node()`. All other potential callbacks
+our case it is exactly one function `node()`. All other potential callbacks
 can be safely ignored.
 
 Now the handler needs to be applied to an OSM file. The easiest way to
@@ -102,8 +102,8 @@ Handling Geometries
 Because of the way that OSM data is structured, osmium needs to internally
 cache node geometries, when the handler wants to process the geometries of
 ways and areas. The :py:meth:`~!osmium.SimpleHandler.apply_file` method cannot
-deduct by itself, if this cache is needed. Therefore locations need to be
-explicitly enabled by setting the location parameter to true::
+deduce by itself if this cache is needed. Therefore locations need to be
+explicitly enabled by setting the locations parameter to True::
 
     h.apply_file("test.osm.pbf", locations=True, idx='sparse_mem_array')
 
@@ -124,7 +124,7 @@ Interfacing with Shapely
 
 Pyosmium is a library for processing OSM files and therefore offers almost
 no functionality for processing geometries further. There are other libraries
-for that puspose. To interface with these libraries you can simply convert the
+for that purpose. To interface with these libraries you can simply convert the
 osmium geometries into WKB or WKT format and import the result. The following
 example uses the libgeos wrapper `Shapely`_ to compute the total way length::
 
@@ -168,7 +168,7 @@ opens a new writer for a packed OSM XML file. Objects can be written
 by using one of the writers ``add_*`` functions.
 
 A simple handler, that only writes out all the nodes from the input
-file into out new ``nodes.osm.bz2`` file would look like this::
+file into our new ``nodes.osm.bz2`` file would look like this::
 
     import osmium
 
@@ -183,15 +183,15 @@ file into out new ``nodes.osm.bz2`` file would look like this::
 This example shows that an unmodified object can be written out directly
 to the writer. Normally, however, you want to modify some data. The native
 osmium OSM types are immutable and cannot be changed directly. Therefore
-you have create a copy that can be changed. The ``node``, ``way`` and ``relation``
+you have to create a copy that can be changed. The ``node``, ``way`` and ``relation``
 objects offer a convenient ``replace()`` function to achieve exactly that.
-The function makes a copy and a the same time replaces all attibutes where
+The function makes a copy and at the same time replaces all attributes where
 new values are given as parameters to the function.
 
 Let's say you want to
 remove all the user names from your nodes before saving them to the new
 file (maybe to save some space), then the ``node()`` handler callback above
-needs to be changed like that::
+needs to be changed like this::
 
     class NodeWriter(osmium.SimpleHandler):
         ...
diff --git a/doc/ref_index.rst b/doc/ref_index.rst
index e8cac63..b04e5c0 100644
--- a/doc/ref_index.rst
+++ b/doc/ref_index.rst
@@ -12,7 +12,7 @@ Node location can be cached in a ``LocationTable``. There are different
 implementations available which should be choosen according to the size of
 data and whether or not the cache should be permanent. See the Osmium manual
 for a detailed explaination. The compiled in types can be listed with the
-``map_types`` function, new storages can be created with ``create_map``.
+``map_types`` function, new stores can be created with ``create_map``.
 
 .. autofunction:: osmium.index.map_types
 
diff --git a/doc/ref_osm.rst b/doc/ref_osm.rst
index 7e4b1a9..7e6307f 100644
--- a/doc/ref_osm.rst
+++ b/doc/ref_osm.rst
@@ -1,14 +1,14 @@
 ``osm`` - Basic Datatypes
 -------------------------
 
-The ``osm`` submodule contains definition of the basic data types used
+The ``osm`` submodule contains definitions of the basic data types used
 throughout the library.
 
 Native OSM Objects
 ^^^^^^^^^^^^^^^^^^
 
 Native OSM object classes are lightwight wrappers around the osmium OSM
-data classes. They are immutable and generally bound to the life-time of
+data classes. They are immutable and generally bound to the lifetime of
 the buffer they are saved in.
 
 There are five classes representing the basic OSM entities.
diff --git a/doc/ref_osmium.rst b/doc/ref_osmium.rst
index 1867ddb..9947c39 100644
--- a/doc/ref_osmium.rst
+++ b/doc/ref_osmium.rst
@@ -12,7 +12,7 @@ functions and processors are exported as well in this module.
 Input Handlers
 ^^^^^^^^^^^^^^
 
-An input handler implements provides the base class for writing custom
+An input handler provides the base class for writing custom
 data processors. They take input data, usually from a file, and forward
 it to handler functions.
 
@@ -28,7 +28,7 @@ handle native ``osmium.osm`` objects as well as any Python object that
 exposes the same attributes. It is not necessary to implement the full
 list of attributes as any missing attributes will be replaced with a
 sensible default value when writing. See :ref:`mutable-objects`
-for a detailed discussion what data formats are understood for each attribute.
+for a detailed discussion of the data formats understood for each attribute.
 
 .. warning::
 
diff --git a/doc/ref_replication.rst b/doc/ref_replication.rst
new file mode 100644
index 0000000..8ea100a
--- /dev/null
+++ b/doc/ref_replication.rst
@@ -0,0 +1,14 @@
+``replication`` - Handling Updates of OSM Data
+----------------------------------------------
+
+Replication servers provide regular updates of OSM data. This module
+provides helper functions to access the servers and download and
+apply updates.
+
+Replication Server Class
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: osmium.replication.server.ReplicationServer
+    :members:
+    :undoc-members:
+
diff --git a/doc/reference.rst b/doc/reference.rst
index 2d30d81..a7a36d6 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -14,3 +14,4 @@ information can be found in the osmium manual.
     ref_io
     ref_index
     ref_geom
+    ref_replication
diff --git a/lib/generic_handler.hpp b/lib/generic_handler.hpp
index 46a2958..03dc489 100644
--- a/lib/generic_handler.hpp
+++ b/lib/generic_handler.hpp
@@ -26,11 +26,11 @@ protected:
 
 public:
 // handler functions
-virtual void node(const osmium::Node&) const = 0;
-virtual void way(const osmium::Way&) const = 0;
-virtual void relation(const osmium::Relation&) const = 0;
-virtual void changeset(const osmium::Changeset&) const = 0;
-virtual void area(const osmium::Area&) const = 0;
+virtual void node(const osmium::Node&)= 0;
+virtual void way(const osmium::Way&) = 0;
+virtual void relation(const osmium::Relation&) = 0;
+virtual void changeset(const osmium::Changeset&) = 0;
+virtual void area(const osmium::Area&) = 0;
 
 
 private:
@@ -103,44 +103,44 @@ using namespace boost::python;
 
 struct SimpleHandlerWrap: BaseHandler, wrapper<BaseHandler> {
 
-    void node(const osmium::Node& node) const {
+    void node(const osmium::Node& node) {
         if (override f = this->get_override("node"))
             f(boost::ref(node));
     }
 
-    void default_node(const osmium::Node&) const {
+    void default_node(const osmium::Node&) {
     }
 
-    void way(const osmium::Way& way) const {
+    void way(const osmium::Way& way) {
         if (override f = this->get_override("way"))
             f(boost::ref(way));
     }
 
-    void default_way(const osmium::Way&) const {
+    void default_way(const osmium::Way&) {
     }
 
-    void relation(const osmium::Relation& rel) const {
+    void relation(const osmium::Relation& rel) {
         if (override f = this->get_override("relation"))
             f(boost::ref(rel));
     }
 
-    void default_relation(const osmium::Relation&) const {
+    void default_relation(const osmium::Relation&) {
     }
 
-    void changeset(const osmium::Changeset& cs) const {
+    void changeset(const osmium::Changeset& cs) {
         if (override f = this->get_override("changeset"))
             f(boost::ref(cs));
     }
 
-    void default_changeset(const osmium::Changeset&) const {
+    void default_changeset(const osmium::Changeset&) {
     }
 
-    void area(const osmium::Area& area) const {
+    void area(const osmium::Area& area) {
         if (override f = this->get_override("area"))
             f(boost::ref(area));
     }
 
-    void default_area(const osmium::Area&) const {
+    void default_area(const osmium::Area&) {
     }
 
     void apply_file(const std::string &filename, bool locations = false,
diff --git a/lib/index.cc b/lib/index.cc
index 981feeb..02352e7 100644
--- a/lib/index.cc
+++ b/lib/index.cc
@@ -43,7 +43,7 @@ BOOST_PYTHON_MODULE(index)
         (arg("map_type")),
         "Create a new location store. The string parameter takes the type "
         "and, where required, additional arguments separated by comma. For "
-        "example, to create a array cache backed by a file ``foo.store``, "
+        "example, to create an array cache backed by a file ``foo.store``, "
         "the map_type should be ``dense_file_array,foo.store``.");
     def("map_types", &map_types,
         "Return a list of strings with valid types for the location table.");
diff --git a/lib/io.cc b/lib/io.cc
index 9e6bc0c..18c57fc 100644
--- a/lib/io.cc
+++ b/lib/io.cc
@@ -1,6 +1,7 @@
 #include <boost/python.hpp>
 
 #include <osmium/io/any_input.hpp>
+#include <osmium/io/any_output.hpp>
 
 #include "osm.cc"
 
@@ -20,9 +21,16 @@ BOOST_PYTHON_MODULE(io)
         .def("box", &osmium::io::Header::box, arg("self"),
                 "Return the bounding box of the data in the file or an invalid "
                 "box if the information is not available.")
+        .def("get", &osmium::io::Header::get, (arg("self"), arg("key"), arg("default")=""),
+                "Get the value of header option 'key' or default value if "
+                "there is no header option with that name. The default cannot be "
+                "None.")
+        .def("set", static_cast<void (osmium::io::Header::*)(const std::string&, const char*)>(&osmium::io::Header::set),
+                (arg("self"), arg("key"), arg("value")),
+                "Set the value of header option 'key'.")
     ;
 
-    class_<osmium::io::Reader, boost::noncopyable>("Reader", 
+    class_<osmium::io::Reader, boost::noncopyable>("Reader",
         "A class that reads OSM data from a file.",
         init<std::string>())
         .def(init<std::string, osmium::osm_entity_bits::type>())
@@ -34,4 +42,13 @@ BOOST_PYTHON_MODULE(io)
              "Return the header with file information, see :py:class:`osmium.io.Header`.")
     ;
 
+    class_<osmium::io::Writer, boost::noncopyable>("Writer",
+        "Class for writing OSM data to a file. This class just encapsulates an "
+        "OSM file,. Have a look `osmium.SimpleWriter` for a high-level interface "
+        "for writing out data.",
+        init<std::string>())
+        .def(init<std::string, osmium::io::Header>())
+        .def("close", &osmium::io::Writer::close, arg("self"),
+             "Close any open file handles. The writer is unusable afterwards.")
+    ;
 }
diff --git a/lib/merged_input.hpp b/lib/merged_input.hpp
index cffd70d..b35503d 100644
--- a/lib/merged_input.hpp
+++ b/lib/merged_input.hpp
@@ -3,14 +3,50 @@
 
 #include <vector>
 
+#include <boost/function_output_iterator.hpp>
+
 #include <osmium/osm/object_comparisons.hpp>
 #include <osmium/io/any_input.hpp>
+#include <osmium/io/any_output.hpp>
+#include <osmium/io/output_iterator.hpp>
 #include <osmium/handler.hpp>
 #include <osmium/object_pointer_collection.hpp>
 #include <osmium/visitor.hpp>
 
 #include <boost/python.hpp>
 
+namespace {
+
+    /**
+     *  Copy the first OSM object with a given Id to the output. Keep
+     *  track of the Id of each object to do this.
+     *
+     *  We are using this functor class instead of a simple lambda, because the
+     *  lambda doesn't build on MSVC.
+     */
+    class copy_first_with_id {
+        osmium::io::Writer* writer;
+        osmium::object_id_type id = 0;
+
+    public:
+        explicit copy_first_with_id(osmium::io::Writer& w) :
+            writer(&w) {
+        }
+
+        void operator()(const osmium::OSMObject& obj) {
+            if (obj.id() != id) {
+                if (obj.visible()) {
+                    (*writer)(obj);
+                }
+                id = obj.id();
+            }
+        }
+
+    };
+
+} // anonymous namespace
+
+
 namespace pyosmium {
 
 class MergeInputReader {
@@ -34,7 +70,39 @@ public:
 
         objects = osmium::ObjectPointerCollection();
         changes.clear();
+    }
+
+    void apply_to_reader(osmium::io::Reader &reader, osmium::io::Writer &writer,
+                         bool with_history = true) {
+        auto input = osmium::io::make_input_iterator_range<osmium::OSMObject>(reader);
+        if (with_history) {
+            // For history files this is a straightforward sort of the change
+            // files followed by a merge with the input file.
+            objects.sort(osmium::object_order_type_id_version());
 
+            auto out = osmium::io::make_output_iterator(writer);
+            std::set_union(objects.begin(),
+                    objects.end(),
+                    input.begin(),
+                    input.end(),
+                    out);
+        } else {
+            // For normal data files we sort with the largest version of each
+            // object first and then only copy this last version of any object
+            // to the output.
+            objects.sort(osmium::object_order_type_id_reverse_version());
+
+            auto output_it = boost::make_function_output_iterator(
+                    copy_first_with_id(writer)
+                    );
+
+            std::set_union(objects.begin(),
+                    objects.end(),
+                    input.begin(),
+                    input.end(),
+                    output_it,
+                    osmium::object_order_type_id_reverse_version());
+        }
     }
 
     size_t add_file(const std::string &filename) {
diff --git a/lib/osm.cc b/lib/osm.cc
index f9b8425..9dbb52c 100644
--- a/lib/osm.cc
+++ b/lib/osm.cc
@@ -65,7 +65,7 @@ BOOST_PYTHON_MODULE(_osm)
     ;
     class_<osmium::Location>("Location",
         "A geographic coordinate in WGS84 projection. A location doesn't "
-         "have to be necessarily valid.")
+         "necessarily have to be valid.")
         .def(init<double, double>())
         .add_property("x", &osmium::Location::x,
                       "(read-only) X coordinate (longitude) as a fixed-point integer.")
@@ -80,9 +80,10 @@ BOOST_PYTHON_MODULE(_osm)
                       "that it is within the usual bounds.")
     ;
     class_<osmium::Box>("Box",
-        "A bounding box around a geographic area. Such a box consists of two "
-        ":py:class:`osmium.osm.Location`s. Those locations may be invalid in "
-        "which case the box is considered invalid, too.")
+        "A bounding box around a geographic area. It is defined by an "
+        ":py:class:`osmium.osm.Location` for the bottem-left corner and an "
+        "``osmium.osm.Location`` for the top-right corner. Those locations may "
+        " be invalid in which case the box is considered invalid, too.")
         .def(init<double, double, double, double>())
         .def(init<osmium::Location, osmium::Location>())
         .add_property("bottom_left",
@@ -229,7 +230,7 @@ BOOST_PYTHON_MODULE(_osm)
              "Get the absolute value of the id of this object.")
         .def("user_is_anonymous", &osmium::OSMObject::user_is_anonymous,
              arg("self"),
-             "Check if the user anonymous. If true, the uid does not uniquely "
+             "Check if the user is anonymous. If true, the uid does not uniquely "
              "identify a single user but only the group of all anonymous users "
              "in general.")
     ;
@@ -310,7 +311,7 @@ BOOST_PYTHON_MODULE(_osm)
                       "(read-only) Timestamp when the changeset was first opened.")
         .add_property("closed_at", &osmium::Changeset::closed_at,
                       "(read-only) Timestamp when the changeset was finalized. May be "
-                      "None when the changeset is still open/")
+                      "None when the changeset is still open.")
         .add_property("open", &osmium::Changeset::open,
                       "(read-only) True when the changeset is still open.")
         .add_property("num_changes", &osmium::Changeset::num_changes,
diff --git a/lib/osmium.cc b/lib/osmium.cc
index b1299f0..31b4997 100644
--- a/lib/osmium.cc
+++ b/lib/osmium.cc
@@ -7,6 +7,7 @@
 #include "generic_writer.hpp"
 #include "generic_handler.hpp"
 #include "merged_input.hpp"
+#include "write_handler.hpp"
 
 template <typename T>
 void apply_reader_simple(osmium::io::Reader &rd, T &h) {
@@ -96,7 +97,7 @@ BOOST_PYTHON_MODULE(_osmium)
              "positions. In that case, the type of this position index can be\n"
              "further selected in idx. If an area callback is implemented, then\n"
              "the file will be scanned twice and a location handler and a\n"
-             "handler for assembling multipolygones and areas from ways will\n"
+             "handler for assembling multipolygons and areas from ways will\n"
              "be executed.")
         .def("apply_buffer", &SimpleHandlerWrap::apply_buffer,
               (arg("self"), arg("buffer"), arg("format"),
@@ -112,7 +113,7 @@ BOOST_PYTHON_MODULE(_osmium)
     class_<SimpleWriterWrap, boost::noncopyable>("SimpleWriter",
         "The most generic class to write osmium objects into a file. The writer "
         "takes a file name as its mandatory parameter. The file must not yet "
-        "exists. The file type to output is determined from the file extension. "
+        "exist. The file type to output is determined from the file extension. "
         "The second (optional) parameter is the buffer size. osmium caches the "
         "output data in an internal memory buffer before writing it on disk. This "
         "parameter allows changing the default buffer size of 4MB. Larger buffers "
@@ -143,6 +144,26 @@ BOOST_PYTHON_MODULE(_osmium)
              "that the buffer memory can be freed.")
     ;
 
+    class_<WriteHandler, boost::noncopyable>("WriteHandler",
+        "Handler function that writes all data directly to a file."
+        "The handler takes a file name as its mandatory parameter. The file "
+        "must not yet exist. The file type to output is determined from the "
+        "file extension. "
+        "The second (optional) parameter is the buffer size. osmium caches the "
+        "output data in an internal memory buffer before writing it on disk. This "
+        "parameter allows changing the default buffer size of 4MB. Larger buffers "
+        "are normally better but you should be aware that there are normally multiple "
+        "buffers in use during the write process.",
+        init<const char*, unsigned long>())
+        .def(init<const char*>())
+        .def("close", &WriteHandler::close,
+             args("self"),
+             "Flush the remaining buffers and close the writer. While it is not "
+             "strictly necessary to call this function explicitly, it is still "
+             "strongly recommended to close the writer as soon as possible, so "
+             "that the buffer memory can be freed.")
+    ;
+
     class_<pyosmium::MergeInputReader, boost::noncopyable>("MergeInputReader",
         "Collects data from multiple input files and sorts and optionally "
         "deduplicates the data before applying it to a handler.")
@@ -153,6 +174,14 @@ BOOST_PYTHON_MODULE(_osmium)
             "and only the newest version of each object kept. After the data "
             "has been applied the buffer of the MergeInputReader is empty and "
             "new data can be added for the next round of application.")
+        .def("apply_to_reader", &pyosmium::MergeInputReader::apply_to_reader,
+            (arg("self"), arg("reader"), arg("writer"), arg("with_history")=false),
+            "Apply the collected data to data from the given `reader` and write "
+            "the result to `writer`. This function can be used to merge the diff "
+            "data together with other OSM data (for example when updating a "
+            "planet file. If `with_history` is true, then the collected data will "
+            "be applied verbatim without removing duplicates. This is important "
+            "when using OSM history files as input.")
         .def("add_file", &pyosmium::MergeInputReader::add_file,
             (arg("self"), arg("file")),
              "Add data from a file to the internal cache. The file type will be "
diff --git a/lib/write_handler.hpp b/lib/write_handler.hpp
new file mode 100644
index 0000000..b83e683
--- /dev/null
+++ b/lib/write_handler.hpp
@@ -0,0 +1,68 @@
+#ifndef PYOSMIUM_WRITE_HANDLER_HPP
+#define PYOSMIUM_WRITE_HANDLER_HPP
+
+#include <osmium/io/any_output.hpp>
+#include <osmium/io/writer.hpp>
+#include <osmium/memory/buffer.hpp>
+
+#include <boost/python.hpp>
+
+#include "generic_handler.hpp"
+
+class WriteHandler : public BaseHandler, public boost::python::wrapper<BaseHandler> {
+    enum { BUFFER_WRAP = 4096 };
+
+public:
+    WriteHandler(const char* filename, size_t bufsz=4096*1024)
+    : writer(filename),
+      buffer(bufsz < 2*BUFFER_WRAP ? 2*BUFFER_WRAP : bufsz, osmium::memory::Buffer::auto_grow::yes)
+    {}
+
+    virtual ~WriteHandler() {
+        close();
+    }
+
+    void node(const osmium::Node& o) {
+        buffer.add_item(o);
+        flush_buffer();
+    }
+
+    void way(const osmium::Way& o) {
+        buffer.add_item(o);
+        flush_buffer();
+    }
+
+    void relation(const osmium::Relation& o) {
+        buffer.add_item(o);
+        flush_buffer();
+    }
+
+    void changeset(const osmium::Changeset&) {}
+
+    void area(const osmium::Area&) {}
+
+    void close() {
+        if (buffer) {
+            writer(std::move(buffer));
+            writer.close();
+            buffer = osmium::memory::Buffer();
+        }
+    }
+
+private:
+    void flush_buffer() {
+        buffer.commit();
+
+        if (buffer.committed() > buffer.capacity() - BUFFER_WRAP) {
+            osmium::memory::Buffer new_buffer(buffer.capacity(), osmium::memory::Buffer::auto_grow::yes);
+            using std::swap;
+            swap(buffer, new_buffer);
+            writer(std::move(new_buffer));
+        }
+    }
+
+    osmium::io::Writer writer;
+    osmium::memory::Buffer buffer;
+};
+
+#endif // PYOSMIUM_WRITE_HANDLER_HPP
diff --git a/osmium/replication/server.py b/osmium/replication/server.py
index d7b01ab..345b333 100644
--- a/osmium/replication/server.py
+++ b/osmium/replication/server.py
@@ -14,8 +14,15 @@ import datetime as dt
 from collections import namedtuple
 from math import ceil
 from osmium import MergeInputReader
+from osmium import io as oio
+
+import logging
+
+log = logging.getLogger('pyosmium')
+log.addHandler(logging.NullHandler())
 
 OsmosisState = namedtuple('OsmosisState', ['sequence', 'timestamp'])
+DownloadResult = namedtuple('DownloadResult', ['id', 'reader', 'newest'])
 
 class ReplicationServer(object):
     """ Represents a server that publishes replication data. Replication
@@ -27,15 +34,20 @@ class ReplicationServer(object):
         self.baseurl = url
         self.diff_type = diff_type
 
-    def apply_diffs(self, handler, start_id, max_size=1024, simplify=True):
-        """ Download diffs starting with sequence id `start_id`, merge them
-            together and then apply them to handler `handler`. `max_size`
+    def collect_diffs(self, start_id, max_size=1024):
+        """ Create a MergeInputReader and download diffs starting with sequence
+            id `start_id` into it. `max_size`
             restricts the number of diffs that are downloaded. The download
             stops as soon as either a diff cannot be downloaded or the
             unpacked data in memory exceeds `max_size` kB.
 
-            The function returns the sequence id of the last diff that was
-            downloaded or None if the download failed completely.
+            If some data was downloaded, returns a namedtuple with three fields:
+            `id` contains the sequence id of the last downloaded diff, `reader`
+            contains the MergeInputReader with the data and `newest` is a
+            sequence id of the most recent diff available.
+
+            Returns None if there was an error during download or no new
+            data was available.
         """
         left_size = max_size * 1024
         current_id = start_id
@@ -60,11 +72,77 @@ class ReplicationServer(object):
                 break
 
             left_size -= rd.add_buffer(diffdata, self.diff_type)
+            log.debug("Downloaded change %d. (%d kB available in download buffer)"
+                      % (current_id, left_size / 1024))
             current_id += 1
 
-        rd.apply(handler, simplify)
+        return DownloadResult(current_id - 1, rd, newest.sequence)
+
+    def apply_diffs(self, handler, start_id, max_size=1024, simplify=True):
+        """ Download diffs starting with sequence id `start_id`, merge them
+            together and then apply them to handler `handler`. `max_size`
+            restricts the number of diffs that are downloaded. The download
+            stops as soon as either a diff cannot be downloaded or the
+            unpacked data in memory exceeds `max_size` kB.
+
+            The function returns the sequence id of the last diff that was
+            downloaded or None if the download failed completely.
+        """
+        diffs = self.collect_diffs(start_id, max_size)
+
+        if diffs is None:
+            return None
+
+        diffs.reader.apply(handler, simplify)
+
+        return diffs.id
+
+    def apply_diffs_to_file(self, infile, outfile, start_id, max_size=1024,
+                            set_replication_header=True):
+        """ Download diffs starting with sequence id `start_id`, merge them
+            with the data from the OSM file named `infile` and write the result
+            into a file with the name `outfile`. The output file must not yet
+            exist.
+
+            `max_size` restricts the number of diffs that are downloaded. The
+            download stops as soon as either a diff cannot be downloaded or the
+            unpacked data in memory exceeds `max_size` kB.
+
+            If `set_replication_header` is true then the URL of the replication
+            server and the sequence id and timestamp of the last diff applied
+            will be written into the `writer`. Note that this currently works
+            only for the PBF format.
+
+            The function returns a tuple of last downloaded sequence id and
+            newest available sequence id if new data has been written or None
+            if no data was available or the download failed completely.
+        """
+        diffs = self.collect_diffs(start_id, max_size)
+
+        if diffs is None:
+            return None
+
+        reader = oio.Reader(infile)
+        has_history = reader.header().has_multiple_object_versions
+
+        h = oio.Header()
+        h.has_multiple_object_versions = has_history
+        if set_replication_header:
+            h.set("osmosis_replication_base_url", self.baseurl)
+            h.set("osmosis_replication_sequence_number", str(diffs.id))
+            info = self.get_state_info(diffs.id)
+            h.set("osmosis_replication_timestamp", info.timestamp.strftime("%Y-%m-%dT%H:%M:%SZ"))
+
+        writer = oio.Writer(outfile, h)
+
+        log.debug("Merging changes into OSM file.")
+
+        diffs.reader.apply_to_reader(reader, writer, has_history)
+
+        reader.close()
+        writer.close()
 
-        return current_id - 1
+        return (diffs.id, diffs.newest)
 
 
     def timestamp_to_sequence(self, timestamp, balanced_search=False):
@@ -89,6 +167,7 @@ class ReplicationServer(object):
         lower = None
         lowerid = 0
         while lower is None:
+            log.debug("Trying with Id %s" % lowerid)
             lower = self.get_state_info(lowerid)
 
             if lower is not None and lower.timestamp >= timestamp:
diff --git a/osmium/replication/utils.py b/osmium/replication/utils.py
new file mode 100644
index 0000000..3a54aca
--- /dev/null
+++ b/osmium/replication/utils.py
@@ -0,0 +1,66 @@
+""" Helper functions for change file handling. """
+
+import logging
+import datetime as dt
+from collections import namedtuple
+from osmium.io import Reader as oreader
+from sys import version_info as python_version
+
+log = logging.getLogger('pyosmium')
+
+ReplicationHeader = namedtuple('ReplicationHeader',
+                                ['url', 'sequence', 'timestamp'])
+
+def get_replication_header(fname):
+    """ Scans the given file for an Osmosis replication header. It returns
+        a namedtuple with `url`, `sequence` and `timestamp`. Each or all fields
+        may be None, if the piece of information is not avilable. If any of
+        the fields has an invalid format, it is simply ignored.
+
+        The given file must exist and be readable for osmium, otherwise
+        a `RuntimeError` is raised.
+    """
+
+    r = oreader(fname)
+    h = r.header()
+
+    ts = h.get("osmosis_replication_timestamp")
+    url = h.get("osmosis_replication_base_url")
+
+    if url or ts:
+        log.debug("Replication information found in OSM file header.")
+
+    if url:
+        log.debug("Replication URL: %s" % url)
+        # the sequence ID is only considered valid, if an URL is given
+        seq = h.get("osmosis_replication_sequence_number")
+        if seq:
+            log.debug("Replication sequence: %s" % seq)
+            try:
+                seq = int(seq)
+                if seq < 0:
+                    log.warning("Sequence id '%d' in OSM file header is negative. Ignored." % seq)
+                    seq = None
+            except ValueError:
+                log.warning("Sequence id '%s' in OSM file header is not a number.Ignored" % seq)
+                seq = None
+        else:
+            seq = None
+    else:
+        url = None
+        seq = None
+
+    if ts:
+        log.debug("Replication timestamp: %s" % ts)
+        try:
+            ts = dt.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ")
+            if python_version >= (3,0):
+                ts = ts.replace(tzinfo=dt.timezone.utc)
+
+        except ValueError:
+            log.warning("Date in OSM file header is not in ISO8601 format (e.g. 2015-12-24T08:08Z). Ignored")
+            ts = None
+    else:
+        ts = None
+
+    return ReplicationHeader(url, seq, ts)
diff --git a/osmium/version.py b/osmium/version.py
index 92b5eb9..804c532 100644
--- a/osmium/version.py
+++ b/osmium/version.py
@@ -3,9 +3,9 @@ Version information.
 """
 
 # the major version
-pyosmium_major = '2.11'
+pyosmium_major = '2.12'
 # current release (Pip version)
-pyosmium_release = '2.11.0'
+pyosmium_release = '2.12.0'
 
 # libosmium version shipped with the Pip release
-libosmium_version = '2.11.0'
+libosmium_version = '2.12.0'
diff --git a/setup.py b/setup.py
index 053dad5..89dab74 100644
--- a/setup.py
+++ b/setup.py
@@ -44,6 +44,8 @@ elif osplatform in ["linux", "linux2"]:
 # try to find the boost library matching the python version
 suffixes = [ # Debian naming convention for version installed in parallel
              "-py%d%d" % (pyversion.major, pyversion.minor),
+             # Gentoo naming convention for version installed in parallel
+             "-%d.%d" % (pyversion.major, pyversion.minor),
              # standard suffix for Python3
              "%d" % (pyversion.major),
              # standard naming
@@ -81,7 +83,7 @@ osmium_libs = ('expat', 'pthread', 'z', 'bz2')
 libs.extend(osmium_libs)
 
 extensions = []
-extra_compile_args = [ '-std=c++11', '-D_LARGEFILE_SOURCE', '-D_FILE_OFFSET_BITS=64' ]
+extra_compile_args = [ '-std=c++11', '-D_LARGEFILE_SOURCE', '-D_FILE_OFFSET_BITS=64', '-D__STDC_FORMAT_MACROS' ]
 
 extensions.append(Extension('osmium._osmium',
        sources = ['lib/osmium.cc'],
@@ -143,6 +145,7 @@ setup (name = 'osmium',
        url='http://osmcode.org/pyosmium',
        keywords=["OSM", "OpenStreetMap", "Osmium"],
        license='BSD',
+       scripts=['tools/pyosmium-get-changes', 'tools/pyosmium-up-to-date'],
        classifiers=classifiers,
        packages = packages,
        cmdclass={'sdist' : My_sdist },
diff --git a/tools/pyosmium-get-changes b/tools/pyosmium-get-changes
new file mode 100755
index 0000000..5be7c49
--- /dev/null
+++ b/tools/pyosmium-get-changes
@@ -0,0 +1,197 @@
+#!/usr/bin/python
+"""
+Fetch diffs from an OSM planet server.
+
+The starting point of the diff must be given either as a sequence ID or a date
+or can be computed from an OSM file. If no output file is given, the program
+will just print the intial sequence ID it would use (or save it in a file, if
+requested) and exit. This can be used to bootstrap the update process.
+
+The program tries to download until the latest change on the server is found
+or the maximum requested diff size is reached. Note that diffs are kept in
+memory during download.
+
+On success, the program will print a single number to stdout, the sequence
+number where to continue updates in the next run. This output can also be
+written to (and later read from) a file.
+"""
+
+from argparse import ArgumentParser, RawDescriptionHelpFormatter, ArgumentTypeError
+import datetime as dt
+from sys import version_info as python_version
+from osmium.replication import server as rserv
+from osmium.replication import newest_change_from_file
+from osmium.replication.utils import get_replication_header
+from osmium import SimpleHandler, WriteHandler
+
+import sys
+import logging
+from textwrap import dedent as msgfmt
+
+log = logging.getLogger()
+
+class ReplicationStart(object):
+    """ Represents the point where changeset download should begin.
+    """
+
+    def __init__(self, date=None, seq_id=None, src=None):
+        self.date = date
+        self.seq_id = seq_id
+        self.source = src
+
+    def get_sequence(self, svr):
+        if self.seq_id is not None:
+            log.debug("Using given sequence ID %d" % self.seq_id)
+            return self.seq_id + 1
+
+        log.debug("Looking up sequence ID for timestamp %s" % self.date)
+        seq = svr.timestamp_to_sequence(self.date)
+
+        return seq + 1 if seq is not None else None
+
+    @staticmethod
+    def from_id(idstr):
+        try:
+            seq_id=int(idstr)
+        except ValueError:
+            raise ArgumentTypeError("Sequence id '%s' is not a number" % idstr)
+
+        if seq_id < 0:
+            raise ArgumentTypeError("Sequence id '%s' is negative" % idstr)
+
+        return ReplicationStart(seq_id=seq_id)
+
+    @staticmethod
+    def from_date(datestr):
+        try:
+            date = dt.datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ")
+            if python_version >= (3,0):
+                date = date.replace(tzinfo=dt.timezone.utc)
+        except ValueError:
+            raise ArgumentTypeError("Date needs to be in ISO8601 format (e.g. 2015-12-24T08:08Z).")
+
+        return ReplicationStart(date=date)
+
+    @staticmethod
+    def from_osm_file(fname, ignore_headers):
+        if ignore_headers:
+            ts = None
+            seq = None
+            url = None
+        else:
+            try:
+                (url, seq, ts) = get_replication_header(fname)
+            except RuntimeError as e:
+                raise ArgumentTypeError(e)
+
+        if ts is None and seq is None:
+            log.debug("OSM file has no replication headers. Looking for newest OSM object.")
+            try:
+                ts = newest_change_from_file(fname)
+            except RuntimeError as e:
+                raise ArgumentTypeError(e)
+
+            if ts is None:
+                raise ArgumentTypeError("OSM file does not seem to contain valid data.")
+
+        return ReplicationStart(seq_id=seq, date=ts, src=url)
+
+def write_end_sequence(fname, seqid):
+    """Either writes out the sequence file or prints the sequence id to stdout.
+    """
+    if fname is None:
+        print(seqid)
+    else:
+        with open(fname, 'w') as fd:
+            fd.write(str(startseq))
+
+if __name__ == '__main__':
+    logging.basicConfig(stream=sys.stderr,
+                        format='%(levelname)s: %(message)s')
+
+    parser = ArgumentParser(description=__doc__,
+                            formatter_class=RawDescriptionHelpFormatter)
+    parser.add_argument('-v', dest='loglevel', action='count', default=0,
+                        help='Increase verbosity')
+    parser.add_argument('-o', '--outfile', dest='outfile',
+                        help="""Name of diff output file. If omitted, only the
+                              sequence ID will be printed where updates would start.""")
+    parser.add_argument('--server', action='store', dest='server_url',
+                        help='Base URL of the replication server')
+    parser.add_argument('-s', '--size', dest='outsize', type=int, default=100,
+                        help='Maximum data to load in MB (default: 100MB).')
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument('-I', '--start-id', dest='start',
+                       type=ReplicationStart.from_id, metavar='ID',
+                       help='Sequence ID to start with')
+    group.add_argument('-D', '--start-date', dest='start', metavar='DATE',
+                       type=ReplicationStart.from_date,
+                       help='Date when to start updates')
+    group.add_argument('-O', '--start-osm-data', dest='start_file', metavar='OSMFILE',
+                       help='start at the date of the newest OSM object in the file')
+    parser.add_argument('-f', '--sequence-file', dest='seq_file',
+                       help="""Sequence file. If the file exists, then updates
+                               will start after the id given in the file. At the
+                               end of the process, the last sequence ID contained
+                               in the diff is written.""")
+    parser.add_argument('--ignore-osmosis-headers', dest='ignore_headers',
+                        action='store_true',
+                        help="""When determining the start from an OSM file,
+                                ignore potential replication information in the
+                                header and search for the newest OSM object.""")
+    parser.add_argument('-d', '--no-deduplicate', action='store_false', dest='simplify',
+                        help='Do not deduplicate and sort diffs.')
+
+    options = parser.parse_args()
+
+    log.setLevel(max(3 - options.loglevel, 0) * 10)
+
+    if options.start_file is not None:
+        options.start = ReplicationStart.from_osm_file(options.start_file,
+                                                       options.ignore_headers)
+    if options.start is None:
+        if options.seq_file is None:
+            log.error(msgfmt("""
+              Don't know with which change to start. One of the parameters
+                -I / -D / -O / -f
+              needs to begiven."""))
+            exit(1)
+
+        with open(opt.start_file, 'r') as f:
+            seq = f.readline()
+            options.start = ReplicationStart_from_id(seq)
+
+    if options.server_url is not None and options.start.source is not None:
+        if options.server_url != options.start.source:
+            log.error(msgfmt("""
+              You asked to use server URL:
+                %s
+              but the referenced OSM file points to replication server:
+                %s
+              If you really mean to overwrite the URL, use --ignore-osmosis-headers."""
+              % (options.server_url, options.start.source)))
+            exit(2)
+    url = options.server_url \
+            or options.start.source \
+            or 'https://planet.osm.org/replication/minute/'
+    logging.info("Using replication server at %s" % url)
+
+    svr = rserv.ReplicationServer(url)
+
+    startseq = options.start.get_sequence(svr)
+
+    if options.outfile is None:
+        write_end_sequence(options.seq_file, startseq)
+        exit(0)
+
+    log.debug("Starting download at ID %d (max %d MB)" % (startseq, options.outsize))
+    outhandler = WriteHandler(options.outfile)
+
+    endseq = svr.apply_diffs(outhandler, startseq, max_size=options.outsize*1024,
+                             simplify=options.simplify)
+    outhandler.close()
+
+    if endseq is None:
+        exit(3)
+
+    write_end_sequence(options.seq_file, endseq)
diff --git a/tools/pyosmium-up-to-date b/tools/pyosmium-up-to-date
new file mode 100755
index 0000000..cc54ad1
--- /dev/null
+++ b/tools/pyosmium-up-to-date
@@ -0,0 +1,191 @@
+#!/usr/bin/python
+"""
+Update an OSM file with changes from a OSM replication server.
+
+Diffs are downloaded and kept in memory. To avoid running out of memory,
+the maximum size of diffs that can be downloaded at once is limited
+to 1 GB per default. This corresponds to approximately 3 days of update.
+The limit can be changed with the --size parameter. However, you should
+take into account that processing the files requires additional memory
+(about 1GB more).
+
+The starting time is automatically determined from the data in the file.
+For PBF files, it is also possible to read and write the replication
+information from the osmosis headers. That means that after the first update,
+subsequent calls to pyosmium-up-to-date will continue the updates from the same
+server exactly where they have left of.
+
+The program returns 0, if updates have been successfully applied up to
+the newest data. It returns 1, if some updates have been applied but
+there is still data available on the server (either because the size
+limit has been reached or there was a network error which could not be
+resolved). Any other error results in a return code larger than 1. The
+output file is guaranteed to be unmodified in that case.
+"""
+
+import sys
+import logging
+
+from argparse import ArgumentParser, RawDescriptionHelpFormatter
+import datetime as dt
+from sys import version_info as python_version
+from osmium.replication import server as rserv
+from osmium.replication.utils import get_replication_header
+from osmium.replication import newest_change_from_file
+from textwrap import dedent as msgfmt
+from tempfile import mktemp
+import os.path
+
+log = logging.getLogger()
+
+def update_from_osm_server(ts, options):
+    """Update the OSM file using the official OSM servers at
+       https://planet.osm.org/replication. This strategy will attempt
+       to start with daily updates before going down to minutelies.
+       TODO: only updates from hourlies currently implemented.
+    """
+    return update_from_custom_server("https://planet.osm.org/replication/hour/",
+                                     None, ts, options)
+
+
+def update_from_custom_server(url, seq, ts, options):
+    """Update from a custom URL, simply using the diff sequence as is."""
+
+    svr = rserv.ReplicationServer(url)
+    log.info("Using replication service at %s" % url)
+
+    if seq is None:
+        log.info("Using timestamp %s as starting point." % ts)
+        startseq = svr.timestamp_to_sequence(ts)
+        if startseq is None:
+            log.error("No starting point found for time %s on server %s"
+                      % (str(ts), url))
+            return 3
+    else:
+        log.debug("Using given sequence ID %d" % seq)
+        startseq = seq + 1
+        ts = svr.get_state_info(seq=startseq)
+        if ts is None:
+            log.error("Cannot download state information for ID %d. Is the URL correct?" % seq)
+            return 3
+        ts = ts.timestamp
+
+    if not options.force_update:
+        cmpdate = dt.datetime.utcnow() - dt.timedelta(days=90)
+        if python_version >= (3,0):
+            cmpdate = cmpdate.replace(tzinfo=dt.timezone.utc)
+        if ts < cmpdate:
+            log.error(
+              """The OSM file is more than 3 months old. You should download a
+                 more recent file instead of updating. If you really want to
+                 update the file, use --force-update-of-old-planet.""")
+            return 3
+
+    log.info("Starting download at ID %d (max %d MB)" % (startseq, options.outsize))
+
+    outfile = options.outfile
+    infile = options.infile
+
+    if outfile is None:
+        fdir, fname = os.path.split(infile)
+        if options.tmpdir is not None:
+            fdir = options.tmpdir
+        ofname = mktemp(suffix='-' + fname, dir=fdir)
+    else:
+        ofname = outfile
+
+    outseqs = svr.apply_diffs_to_file(infile, ofname, startseq,
+                                     max_size=options.outsize*1024)
+
+    if outseqs is None:
+        log.info("No new updates found.")
+        return 3
+
+    if outfile is None:
+        os.rename(ofname, infile)
+
+    log.info("Downloaded until %d. Server has data available until %d." % outseqs)
+
+    return 0 if outseqs[1] == outseqs[0] else 1
+
+
+def compute_start_point(options):
+    if options.ignore_headers:
+        url, seq, ts = None, None, None
+    else:
+        url, seq, ts = get_replication_header(options.infile)
+
+    if options.server_url is not None:
+        if url is not None and url != options.server_url:
+            log.error(msgfmt("""
+                  You asked to use server URL:
+                    %s
+                  but the referenced OSM file points to replication server:
+                    %s
+                  If you really mean to overwrite the URL, use --ignore-osmosis-headers."""
+                  % (options.server_url, url)))
+            exit(2)
+        url = options.server_url
+
+    if seq is None and ts is None:
+        log.info("No replication information found, scanning for newest OSM object.")
+        ts = newest_change_from_file(options.infile)
+
+        if ts is None:
+            log.error("OSM file does not seem to contain valid data.")
+            exit(2)
+
+    if ts is not None:
+        ts -= dt.timedelta(minutes=options.wind_back)
+
+    return url, seq, ts
+
+
+if __name__ == '__main__':
+    logging.basicConfig(stream=sys.stderr,
+                        format='%(asctime)s %(levelname)s: %(message)s')
+
+    parser = ArgumentParser(description=__doc__,
+                            formatter_class=RawDescriptionHelpFormatter)
+    parser.add_argument('-v', dest='loglevel', action='count', default=0,
+                        help='Increase verbosity')
+    parser.add_argument('infile', metavar='<OSM file>', help="OSM file to update")
+    parser.add_argument('-o', '--outfile', dest='outfile',
+                        help="""Name output of file. If missing, the input file
+                                will be overwritten.""")
+    parser.add_argument('--server', action='store', dest='server_url',
+            help="""Base URL of the replication server. Default:
+                    'https://planet.osm.org/replication/hour/' (
+                    hourly diffs from osm.org).""")
+    parser.add_argument('-s', '--size', dest='outsize', metavar='SIZE', type=int, default=1024,
+                        help='Maximum size of change to apply at once in MB. Default: 1GB.')
+    parser.add_argument('--tmpdir', dest='tmpdir',
+            help='Directory to use for temporary files. Default: directory of input file')
+    parser.add_argument('--ignore-osmosis-headers', dest='ignore_headers',
+                        action='store_true',
+                        help="""Ignore potential replication information in the
+                                header of the input file and search for the
+                                newest OSM object in the file instead.""")
+    parser.add_argument('-b', '--wind-back', dest='wind_back', type=int, default=60,
+                        help="""Number of minutes to start downloading before
+                                the newest addition to input data. (Ignored when
+                                the file contains a sequence ID.) Default: 60.""")
+    parser.add_argument('--force-update-of-old-planet', action='store_true',
+                        dest='force_update',
+                        help="Apply update even if the input data is really old.")
+
+    options = parser.parse_args()
+    log.setLevel(max(3 - options.loglevel, 0) * 10)
+
+    try:
+        url, seq, ts = compute_start_point(options)
+    except RuntimeError as e:
+        log.error(str(e))
+        exit(2)
+
+    if url is None:
+        ret = update_from_osm_server(ts, options)
+    else:
+        ret = update_from_custom_server(url, seq, ts, options)
+
+    exit(ret)

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



More information about the Pkg-grass-devel mailing list