[pyosmium] 01/07: Imported Upstream version 2.12.1

Bas Couwenberg sebastic at debian.org
Wed Apr 12 06:39:32 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 f3c2b7fb15abd0293a3550a658ad76ad3032e6bc
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Wed Apr 12 07:15:05 2017 +0200

    Imported Upstream version 2.12.1
---
 CHANGELOG.md                      |  28 +++++++++++
 README.md                         |  11 ++++-
 doc/conf.py                       |   8 ++-
 doc/index.rst                     |   1 +
 doc/ref_geom.rst                  |   8 +++
 doc/tools.rst                     |  10 ++++
 doc/tools_get_changes.rst         |   8 +++
 doc/tools_uptodate.rst            |   8 +++
 examples/amenity_list.py          |   2 +-
 examples/convert.py               |   2 +-
 examples/filter_coastlines.py     |   4 +-
 examples/normalize_boolean.py     |   6 +--
 examples/osm_diff_stats.py        |   4 +-
 examples/osm_file_stats.py        |   3 +-
 examples/osm_replication_stats.py |   2 +-
 examples/osm_url_stats.py         |   2 +-
 examples/pub_names.py             |   5 +-
 examples/road_length.py           |   2 +-
 examples/use_nodecache.py         |   2 +-
 lib/generic_handler.hpp           |  73 +++++++++++++++++++---------
 lib/geom.cc                       |  66 +++++++++++++++++++++++++
 lib/osm.cc                        |  29 ++++++++---
 lib/osmium.cc                     |  24 ++-------
 osmium/osm/__init__.py            |  20 ++++++--
 osmium/osm/mutable.py             |   2 +-
 osmium/version.py                 |   4 +-
 test/helpers.py                   |  16 +++++-
 test/test_taglist.py              | 100 ++++++++++++++++++++++++++++++++++++++
 tools/pyosmium-get-changes        |  31 +++++++-----
 tools/pyosmium-up-to-date         |  35 +++++++------
 30 files changed, 413 insertions(+), 103 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79d0089..bc98618 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,34 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Added
 
+### Changed
+
+### Fixed
+
+## [2.12.1] - 2017-04-11
+
+### Added
+
+- geometry factories for WKT and GeoJSON
+- man pages for new tools
+- get() function for TagList
+- tests for TagList
+
+### Changed
+
+- example code simplified
+- use current libosmium
+
+### Fixed
+
+- area creator always called (#32)
+- various typos
+- TagList [] accessor properly throws KeyError on missing element
+
+## [2.12.0] - 2017-03-19
+
+### 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
diff --git a/README.md b/README.md
index 3d86203..740c191 100644
--- a/README.md
+++ b/README.md
@@ -70,14 +70,21 @@ The suite can be run with:
 
 ## Documentation
 
-To build the documentation you need [Sphinx](http://sphinx-doc.org/).
-On Debian/Ubuntu install `python-sphinx` or `python3-sphinx`.
+To build the documentation you need [Sphinx](http://sphinx-doc.org/)
+and the [autoprogram extension](https://pythonhosted.org/sphinxcontrib-autoprogram/)
+On Debian/Ubuntu install `python-sphinx sphinxcontrib-autoprogram`
+or `python3-sphinx python3-sphinxcontrib.autoprogram`.
 
 First compile the bindings as described above and then run:
 
     cd doc
     make html
 
+For building the man pages for the tools run:
+
+    cd doc
+    make man
+
 
 ## License
 
diff --git a/doc/conf.py b/doc/conf.py
index 850c539..67a2a7a 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -27,6 +27,7 @@ build_dir = "../build/lib.%s-%d.%d" % (
 
 # insert after the current directory
 sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath('.'), build_dir)))
+sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath('.'), '../tools')))
 
 try:
     from osmium.version import pyosmium_major, pyosmium_release
@@ -49,6 +50,7 @@ except ImportError:
 extensions = [
     'sphinx.ext.autodoc',
     'sphinx.ext.todo',
+    'sphinxcontrib.autoprogram',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -247,8 +249,10 @@ latex_documents = [
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('index', 'pyosmium', 'Pyosmium Documentation',
-     ['Sarah Hoffmann'], 1)
+    ('tools_get_changes', 'pyosmium-get-changes', 'Download OSM change files',
+     ['Sarah Hoffmann'], 1),
+    ('tools_uptodate', 'pyosmium-up-to-date', 'Bring OSM files up-to-date',
+     ['Sarah Hoffmann'], 1),
 ]
 
 # If true, show URL addresses after external links.
diff --git a/doc/index.rst b/doc/index.rst
index ebb7fb4..7245d0d 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -15,6 +15,7 @@ and profits from its fast implementation.
 
    intro
    reference
+   tools
     
 
 * :ref:`genindex`
diff --git a/doc/ref_geom.rst b/doc/ref_geom.rst
index 0ac9ff4..1b78540 100644
--- a/doc/ref_geom.rst
+++ b/doc/ref_geom.rst
@@ -11,6 +11,14 @@ Geometry Factories
     :members:
     :undoc-members:
 
+.. autoclass:: osmium.geom.WKTFactory
+    :members:
+    :undoc-members:
+
+.. autoclass:: osmium.geom.GeoJSONFactory
+    :members:
+    :undoc-members:
+
 
 Other Functions
 ^^^^^^^^^^^^^^^
diff --git a/doc/tools.rst b/doc/tools.rst
new file mode 100644
index 0000000..2c5563b
--- /dev/null
+++ b/doc/tools.rst
@@ -0,0 +1,10 @@
+Pyosmium Tools
+==============
+
+Pyosmium comes with a couple of scripts for handling change files:
+
+.. toctree::
+    :maxdepth: 1
+
+    tools_get_changes
+    tools_uptodate
diff --git a/doc/tools_get_changes.rst b/doc/tools_get_changes.rst
new file mode 100644
index 0000000..b105171
--- /dev/null
+++ b/doc/tools_get_changes.rst
@@ -0,0 +1,8 @@
+pyosmium-get-changes - Downloading OSM change files
+===================================================
+
+Usage
+-----
+
+.. autoprogram:: pyosmium-get-changes:get_arg_parser()
+    :prog: 
diff --git a/doc/tools_uptodate.rst b/doc/tools_uptodate.rst
new file mode 100644
index 0000000..e84adf5
--- /dev/null
+++ b/doc/tools_uptodate.rst
@@ -0,0 +1,8 @@
+pyosmium-up-to-date - Bringing OSM files up-to-date
+===================================================
+
+Usage
+-----
+
+.. autoprogram:: pyosmium-up-to-date:get_arg_parser()
+    :prog: 
diff --git a/examples/amenity_list.py b/examples/amenity_list.py
index f3be92d..6c6b7a1 100644
--- a/examples/amenity_list.py
+++ b/examples/amenity_list.py
@@ -14,7 +14,7 @@ wkbfab = o.geom.WKBFactory()
 class AmenityListHandler(o.SimpleHandler):
 
     def print_amenity(amenity, tags, lon, lat):
-        name = tags['name'] if 'name' in tags else ''
+        name = tags.get('name', '')
         print("%f %f %-15s %s" % (lon, lat, tags['amenity'], name))
 
     def node(self, n):
diff --git a/examples/convert.py b/examples/convert.py
index 0c97c2d..d2563d6 100644
--- a/examples/convert.py
+++ b/examples/convert.py
@@ -11,7 +11,7 @@ import sys
 class Convert(o.SimpleHandler):
 
     def __init__(self, writer):
-        o.SimpleHandler.__init__(self)
+        super(Convert, self).__init__()
         self.writer = writer
 
     def node(self, n):
diff --git a/examples/filter_coastlines.py b/examples/filter_coastlines.py
index 4fda1d7..8a0cb15 100644
--- a/examples/filter_coastlines.py
+++ b/examples/filter_coastlines.py
@@ -14,7 +14,7 @@ import sys
 class WayFilter(o.SimpleHandler):
 
     def __init__(self):
-        o.SimpleHandler.__init__(self)
+        super(WayFilter, self).__init__()
         self.nodes = set()
 
     def way(self, w):
@@ -26,7 +26,7 @@ class WayFilter(o.SimpleHandler):
 class CoastlineWriter(o.SimpleHandler):
 
     def __init__(self, writer, nodes):
-        o.SimpleHandler.__init__(self)
+        super(CoastlineWriter, self).__init__()
         self.writer = writer
         self.nodes = nodes
 
diff --git a/examples/normalize_boolean.py b/examples/normalize_boolean.py
index 7639985..3573e72 100644
--- a/examples/normalize_boolean.py
+++ b/examples/normalize_boolean.py
@@ -1,5 +1,5 @@
 """
-This example shows how to filter and modify tags and write the rusults back.
+This example shows how to filter and modify tags and write the results back.
 It changes all tag values 'yes/no' to '1/0'.
 """
 
@@ -9,12 +9,12 @@ import sys
 class BoolNormalizer(o.SimpleHandler):
 
     def __init__(self, writer):
-        o.SimpleHandler.__init__(self)
+        super(BoolNormalizer, self).__init__()
         self.writer = writer
 
     def normalize(self, o):
         # if there are no tags we are done
-        if len(o.tags) == 0:
+        if not o.tags:
             return o
 
         # new tags should be kept in a list so that the order is preserved
diff --git a/examples/osm_diff_stats.py b/examples/osm_diff_stats.py
index aeab6d8..f960d19 100644
--- a/examples/osm_diff_stats.py
+++ b/examples/osm_diff_stats.py
@@ -21,15 +21,15 @@ class Stats(object):
         else:
             self.modified += 1
 
-
     def outstats(self, prefix):
         print("%s added: %d" % (prefix, self.added))
         print("%s modified: %d" % (prefix, self.modified))
         print("%s deleted: %d" % (prefix, self.deleted))
 
+
 class FileStatsHandler(o.SimpleHandler):
     def __init__(self):
-        o.SimpleHandler.__init__(self)
+        super(FileStatsHandler, self).__init__()
         self.nodes = Stats()
         self.ways = Stats()
         self.rels = Stats()
diff --git a/examples/osm_file_stats.py b/examples/osm_file_stats.py
index 2482243..13ea102 100644
--- a/examples/osm_file_stats.py
+++ b/examples/osm_file_stats.py
@@ -7,8 +7,9 @@ import osmium as o
 import sys
 
 class FileStatsHandler(o.SimpleHandler):
+
     def __init__(self):
-        o.SimpleHandler.__init__(self)
+        super(FileStatsHandler, self).__init__()
         self.nodes = 0
         self.ways = 0
         self.rels = 0
diff --git a/examples/osm_replication_stats.py b/examples/osm_replication_stats.py
index 00edc33..ab75eaf 100644
--- a/examples/osm_replication_stats.py
+++ b/examples/osm_replication_stats.py
@@ -32,7 +32,7 @@ class Stats(object):
 
 class FileStatsHandler(o.SimpleHandler):
     def __init__(self):
-        o.SimpleHandler.__init__(self)
+        super(FileStatsHandler, self).__init__()
         self.nodes = Stats()
         self.ways = Stats()
         self.rels = Stats()
diff --git a/examples/osm_url_stats.py b/examples/osm_url_stats.py
index 0f87779..5e2be08 100644
--- a/examples/osm_url_stats.py
+++ b/examples/osm_url_stats.py
@@ -10,7 +10,7 @@ import urllib.request
 
 class FileStatsHandler(o.SimpleHandler):
     def __init__(self):
-        o.SimpleHandler.__init__(self)
+        super(FileStatsHandler, self).__init__()
         self.nodes = 0
         self.ways = 0
         self.rels = 0
diff --git a/examples/pub_names.py b/examples/pub_names.py
index 4066d8f..2b7aafd 100644
--- a/examples/pub_names.py
+++ b/examples/pub_names.py
@@ -7,7 +7,7 @@ import sys
 class NamesHandler(osmium.SimpleHandler):
 
     def output_pubs(self, tags):
-        if 'amenity' in tags and tags['amenity'] == 'pub':
+        if tags.get('amenity') == 'pub':
             if 'name' in tags:
                 print(tags['name'])
 
@@ -22,6 +22,5 @@ if __name__ == '__main__':
         print("Usage: python pub_names.py <osmfile>")
         sys.exit(-1)
 
-    h = NamesHandler()
-    h.apply_file(sys.argv[1])
+    NamesHandler().apply_file(sys.argv[1])
 
diff --git a/examples/road_length.py b/examples/road_length.py
index 7b14d7e..89730f6 100644
--- a/examples/road_length.py
+++ b/examples/road_length.py
@@ -8,7 +8,7 @@ import sys
 
 class RoadLengthHandler(o.SimpleHandler):
     def __init__(self):
-        o.SimpleHandler.__init__(self)
+        super(RoadLengthHandler, self).__init__()
         self.length = 0.0
 
     def way(self, w):
diff --git a/examples/use_nodecache.py b/examples/use_nodecache.py
index e819257..7e2fa90 100644
--- a/examples/use_nodecache.py
+++ b/examples/use_nodecache.py
@@ -4,7 +4,7 @@ import sys
 class WayHandler(o.SimpleHandler):
 
     def __init__(self, idx):
-        o.SimpleHandler.__init__(self)
+        super(WayHandler).__init__()
         self.idx = idx
 
     def way(self, w):
diff --git a/lib/generic_handler.hpp b/lib/generic_handler.hpp
index 03dc489..18823ef 100644
--- a/lib/generic_handler.hpp
+++ b/lib/generic_handler.hpp
@@ -103,46 +103,47 @@ using namespace boost::python;
 
 struct SimpleHandlerWrap: BaseHandler, wrapper<BaseHandler> {
 
-    void node(const osmium::Node& node) {
-        if (override f = this->get_override("node"))
-            f(boost::ref(node));
-    }
+    void node(const osmium::Node& node) override {
+        if (!(m_callbacks & osmium::osm_entity_bits::node))
+            return;
 
-    void default_node(const osmium::Node&) {
+        if (override f = this->get_override("node")) {
+            f(boost::ref(node));
+        }
     }
 
     void way(const osmium::Way& way) {
+        if (!(m_callbacks & osmium::osm_entity_bits::way))
+            return;
+
         if (override f = this->get_override("way"))
             f(boost::ref(way));
     }
 
-    void default_way(const osmium::Way&) {
-    }
-
     void relation(const osmium::Relation& rel) {
+        if (!(m_callbacks & osmium::osm_entity_bits::relation))
+            return;
+
         if (override f = this->get_override("relation"))
             f(boost::ref(rel));
     }
 
-    void default_relation(const osmium::Relation&) {
-    }
-
     void changeset(const osmium::Changeset& cs) {
+        if (!(m_callbacks & osmium::osm_entity_bits::changeset))
+            return;
+
         if (override f = this->get_override("changeset"))
             f(boost::ref(cs));
     }
 
-    void default_changeset(const osmium::Changeset&) {
-    }
-
     void area(const osmium::Area& area) {
+        if (!(m_callbacks & osmium::osm_entity_bits::area))
+            return;
+
         if (override f = this->get_override("area"))
             f(boost::ref(area));
     }
 
-    void default_area(const osmium::Area&) {
-    }
-
     void apply_file(const std::string &filename, bool locations = false,
                     const std::string &idx = "sparse_mem_array")
     {
@@ -155,7 +156,7 @@ struct SimpleHandlerWrap: BaseHandler, wrapper<BaseHandler> {
     {
         Py_buffer pybuf;
         PyObject_GetBuffer(buf.ptr(), &pybuf, PyBUF_C_CONTIGUOUS);
-        size_t len = pybuf.len;
+        size_t len = (size_t) pybuf.len;
         const char *cbuf = reinterpret_cast<const char *>(pybuf.buf);
         const char *cfmt = boost::python::extract<const char *>(format);
 
@@ -170,24 +171,50 @@ private:
                                             BaseHandler::location_handler
                                             :BaseHandler::no_handler;
 
-        if (this->get_override("area"))
+        m_callbacks = osmium::osm_entity_bits::nothing;
+        if (hasfunc("node"))
+            m_callbacks |= osmium::osm_entity_bits::node;
+        if (hasfunc("way"))
+            m_callbacks |= osmium::osm_entity_bits::way;
+        if (hasfunc("relation"))
+            m_callbacks |= osmium::osm_entity_bits::relation;
+        if (hasfunc("area"))
+            m_callbacks |= osmium::osm_entity_bits::area;
+        if (hasfunc("changeset"))
+            m_callbacks |= osmium::osm_entity_bits::changeset;
+
+        if (m_callbacks & osmium::osm_entity_bits::area)
         {
             entities = osmium::osm_entity_bits::object;
             handler = BaseHandler::area_handler;
         } else {
-            if (locations || this->get_override("node"))
+            if (locations || m_callbacks & osmium::osm_entity_bits::node)
                 entities |= osmium::osm_entity_bits::node;
-            if (this->get_override("way"))
+            if (m_callbacks & osmium::osm_entity_bits::way)
                 entities |= osmium::osm_entity_bits::way;
-            if (this->get_override("relation"))
+            if (m_callbacks & osmium::osm_entity_bits::relation)
                 entities |= osmium::osm_entity_bits::relation;
         }
 
-        if (this->get_override("changeset"))
+        if (m_callbacks & osmium::osm_entity_bits::changeset)
             entities |= osmium::osm_entity_bits::changeset;
 
         apply(file, entities, handler, idx);
     }
+
+    bool hasfunc(char const *name) {
+        reference_existing_object::apply<SimpleHandlerWrap*>::type converter;
+        PyObject* obj = converter( this );
+
+        if (PyObject_HasAttrString(obj, name)) {
+            auto o = boost::python::object(handle<>(obj));
+            return o.attr(name) != boost::python::object();
+        }
+
+        return false;
+    }
+
+    osmium::osm_entity_bits::type m_callbacks;
 };
 
 #endif
diff --git a/lib/geom.cc b/lib/geom.cc
index 626c5e3..309002e 100644
--- a/lib/geom.cc
+++ b/lib/geom.cc
@@ -3,6 +3,8 @@
 #include <osmium/geom/haversine.hpp>
 #include <osmium/geom/factory.hpp>
 #include <osmium/geom/wkb.hpp>
+#include <osmium/geom/wkt.hpp>
+#include <osmium/geom/geojson.hpp>
 
 class WKBFactory : public osmium::geom::WKBFactory<> {
 
@@ -12,6 +14,9 @@ public:
     {}
 };
 
+using WKTFactory = osmium::geom::WKTFactory<>;
+using GeoJSONFactory = osmium::geom::GeoJSONFactory<>;
+
 BOOST_PYTHON_MODULE(geom)
 {
     using namespace boost::python;
@@ -62,4 +67,65 @@ BOOST_PYTHON_MODULE(geom)
              (arg("self"), arg("area")),
              "Create a MultiPolygon geometry from a :py:class:`osmium.osm.Area`.")
     ;
+
+    class_<WKTFactory>("WKTFactory",
+        "Factory that creates WKT from osmium geometries.")
+        .add_property("epsg", &WKTFactory::epsg,
+                      "(read-only) EPSG number of the output geometry.")
+        .add_property("proj_string", &WKTFactory::proj_string,
+                      "(read-only) projection string of the output geometry.")
+        .def("create_point", static_cast<std::string (WKTFactory::*)(const osmium::Location&) const>(&WKTFactory::create_point),
+             (arg("self"), arg("location")),
+             "Create a point geometry from a :py:class:`osmium.osm.Location`.")
+        .def("create_point", static_cast<std::string (WKTFactory::*)(const osmium::Node&)>(&WKTFactory::create_point),
+             (arg("self"), arg("node")),
+             "Create a point geometry from a :py:class:`osmium.osm.Node`.")
+        .def("create_point", static_cast<std::string (WKTFactory::*)(const osmium::NodeRef&)>(&WKTFactory::create_point),
+             (arg("self"), arg("ref")),
+             "Create a point geometry from a :py:class:`osmium.osm.NodeRef`.")
+        .def("create_linestring", static_cast<std::string (WKTFactory::*)(const osmium::WayNodeList&, osmium::geom::use_nodes, osmium::geom::direction)>(&WKTFactory::create_linestring),
+             (arg("self"), arg("list"),
+              arg("use_nodes")=osmium::geom::use_nodes::unique,
+              arg("direction")=osmium::geom::direction::forward),
+             "Create a LineString geometry from a :py:class:`osmium.osm.WayNodeList`.")
+        .def("create_linestring", static_cast<std::string (WKTFactory::*)(const osmium::Way&, osmium::geom::use_nodes, osmium::geom::direction)>(&WKTFactory::create_linestring),
+             (arg("self"), arg("way"),
+              arg("use_nodes")=osmium::geom::use_nodes::unique,
+              arg("direction")=osmium::geom::direction::forward),
+             "Create a LineString geometry from a :py:class:`osmium.osm.Way`.")
+        .def("create_multipolygon", &WKTFactory::create_multipolygon,
+             (arg("self"), arg("area")),
+             "Create a MultiPolygon geometry from a :py:class:`osmium.osm.Area`.")
+    ;
+
+    class_<GeoJSONFactory>("GeoJSONFactory",
+        "Factory that creates GeoJSON geometries from osmium geometries.")
+        .add_property("epsg", &GeoJSONFactory::epsg,
+                      "(read-only) EPSG number of the output geometry.")
+        .add_property("proj_string", &GeoJSONFactory::proj_string,
+                      "(read-only) projection string of the output geometry.")
+        .def("create_point", static_cast<std::string (GeoJSONFactory::*)(const osmium::Location&) const>(&GeoJSONFactory::create_point),
+             (arg("self"), arg("location")),
+             "Create a point geometry from a :py:class:`osmium.osm.Location`.")
+        .def("create_point", static_cast<std::string (GeoJSONFactory::*)(const osmium::Node&)>(&GeoJSONFactory::create_point),
+             (arg("self"), arg("node")),
+             "Create a point geometry from a :py:class:`osmium.osm.Node`.")
+        .def("create_point", static_cast<std::string (GeoJSONFactory::*)(const osmium::NodeRef&)>(&GeoJSONFactory::create_point),
+             (arg("self"), arg("ref")),
+             "Create a point geometry from a :py:class:`osmium.osm.NodeRef`.")
+        .def("create_linestring", static_cast<std::string (GeoJSONFactory::*)(const osmium::WayNodeList&, osmium::geom::use_nodes, osmium::geom::direction)>(&GeoJSONFactory::create_linestring),
+             (arg("self"), arg("list"),
+              arg("use_nodes")=osmium::geom::use_nodes::unique,
+              arg("direction")=osmium::geom::direction::forward),
+             "Create a LineString geometry from a :py:class:`osmium.osm.WayNodeList`.")
+        .def("create_linestring", static_cast<std::string (GeoJSONFactory::*)(const osmium::Way&, osmium::geom::use_nodes, osmium::geom::direction)>(&GeoJSONFactory::create_linestring),
+             (arg("self"), arg("way"),
+              arg("use_nodes")=osmium::geom::use_nodes::unique,
+              arg("direction")=osmium::geom::direction::forward),
+             "Create a LineString geometry from a :py:class:`osmium.osm.Way`.")
+        .def("create_multipolygon", &GeoJSONFactory::create_multipolygon,
+             (arg("self"), arg("area")),
+             "Create a MultiPolygon geometry from a :py:class:`osmium.osm.Area`.")
+    ;
+
 }
diff --git a/lib/osm.cc b/lib/osm.cc
index 9dbb52c..74bd212 100644
--- a/lib/osm.cc
+++ b/lib/osm.cc
@@ -8,19 +8,31 @@
 
 #include "std_pair.hpp"
 
-
-inline const char *get_tag_by_key(osmium::TagList const& obj, const char *value)
+inline const char *get_tag_by_key(osmium::TagList const& obj, const char *key)
 {
-    const char* v = obj.get_value_by_key(value);
-    if (!v)
+    if (!key) {
+        PyErr_SetString(PyExc_KeyError, "Key 'None' not allowed.");
+        boost::python::throw_error_already_set();
+    }
+
+    const char* v = obj.get_value_by_key(key);
+    if (!v) {
         PyErr_SetString(PyExc_KeyError, "No tag with that key.");
+        boost::python::throw_error_already_set();
+    }
     return v;
 }
 
-inline bool taglist_contains_tag(osmium::TagList const& obj, const char *value)
+inline const char *get_tag_by_key_with_none(osmium::TagList const& obj,
+                                            const char *key)
 {
-    const char* v = obj.get_value_by_key(value);
-    return v;
+    return key ? obj.get_value_by_key(key) : nullptr;
+}
+
+
+inline bool taglist_contains_tag(osmium::TagList const& obj, const char *key)
+{
+    return key && obj.has_key(key);
 }
 
 inline const char member_item_type(osmium::RelationMember& obj)
@@ -132,6 +144,9 @@ BOOST_PYTHON_MODULE(_osm)
         .def("__getitem__", &get_tag_by_key)
         .def("__contains__", &taglist_contains_tag)
         .def("__iter__", iterator<osmium::TagList,return_internal_reference<>>())
+        .def("get", &osmium::TagList::get_value_by_key,
+             (arg("self"), arg("key"), arg("default")))
+        .def("get", &get_tag_by_key_with_none)
     ;
     class_<osmium::NodeRef>("NodeRef",
         "A reference to a OSM node that also caches the nodes location.")
diff --git a/lib/osmium.cc b/lib/osmium.cc
index 31b4997..f4a2b94 100644
--- a/lib/osmium.cc
+++ b/lib/osmium.cc
@@ -67,28 +67,14 @@ BOOST_PYTHON_MODULE(_osmium)
     ;
 
     class_<SimpleHandlerWrap, boost::noncopyable>("SimpleHandler",
-        "The most generic of OSM data handlers. For each data type "
-        "a callback can be implemented where the object is processed. Note that "
+        "The most generic of OSM data handlers. Derive your data processor "
+        "from this class and implement callbacks for each object type you are "
+        "interested in. The following data types are recognised: \n"
+        " `node`, `way`, `relation`, `area` and `changeset`.\n "
+        "A callback takes exactly one parameter which is the object. Note that "
         "all objects that are handed into the handler are only readable and are "
         "only valid until the end of the callback is reached. Any data that "
         "should be retained must be copied into other data structures.")
-        .def("node", &BaseHandler::node, &SimpleHandlerWrap::default_node,
-             (arg("self"), arg("node")),
-             "Handler called for node objects.")
-        .def("way", &BaseHandler::way, &SimpleHandlerWrap::default_way,
-             (arg("self"), arg("way")),
-             "Handler called for way objects. If the geometry of the way is "
-             "needed then ``locations`` must be set to true when calling "
-             "apply_file.")
-        .def("relation", &BaseHandler::relation, &SimpleHandlerWrap::default_relation,
-             (arg("self"), arg("relation")),
-             "Handler called for relation objects.")
-        .def("changeset", &BaseHandler::changeset, &SimpleHandlerWrap::default_changeset,
-             (arg("self"), arg("changeset")),
-             "Handler called for changeset objects.")
-        .def("area", &BaseHandler::area, &SimpleHandlerWrap::default_area,
-             (arg("self"), arg("area")),
-             "Handler called for area objects.")
         .def("apply_file", &SimpleHandlerWrap::apply_file,
               (arg("self"), arg("filename"),
                arg("locations")=false, arg("idx")="sparse_mem_array"),
diff --git a/osmium/osm/__init__.py b/osmium/osm/__init__.py
index 8a4908b..22712b4 100644
--- a/osmium/osm/__init__.py
+++ b/osmium/osm/__init__.py
@@ -2,13 +2,25 @@ from ._osm import *
 import osmium.osm.mutable
 
 def create_mutable_node(node, **args):
+    """ Create a mutable node replacing the properties given in the
+        named parameters. Note that this function only creates a shallow
+        copy which is still bound to the scope of the original object.
+    """
     return osmium.osm.mutable.Node(base=node, **args)
 
-def create_mutable_way(node, **args):
-    return osmium.osm.mutable.Way(base=node, **args)
+def create_mutable_way(way, **args):
+    """ Create a mutable way replacing the properties given in the
+        named parameters. Note that this function only creates a shallow
+        copy which is still bound to the scope of the original object.
+    """
+    return osmium.osm.mutable.Way(base=way, **args)
 
-def create_mutable_relation(node, **args):
-    return osmium.osm.mutable.Relation(base=node, **args)
+def create_mutable_relation(rel, **args):
+    """ Create a mutable relation replacing the properties given in the
+        named parameters. Note that this function only creates a shallow
+        copy which is still bound to the scope of the original object.
+    """
+    return osmium.osm.mutable.Relation(base=rel, **args)
 
 Node.replace = create_mutable_node
 Way.replace = create_mutable_way
diff --git a/osmium/osm/mutable.py b/osmium/osm/mutable.py
index edf0421..b700087 100644
--- a/osmium/osm/mutable.py
+++ b/osmium/osm/mutable.py
@@ -40,7 +40,7 @@ class Node(OSMObject):
         if base is None:
             self.location = location
         else:
-            self.location = loctation if location is not None else base.location
+            self.location = location if location is not None else base.location
 
 
 class Way(OSMObject):
diff --git a/osmium/version.py b/osmium/version.py
index 804c532..c3ee027 100644
--- a/osmium/version.py
+++ b/osmium/version.py
@@ -5,7 +5,7 @@ Version information.
 # the major version
 pyosmium_major = '2.12'
 # current release (Pip version)
-pyosmium_release = '2.12.0'
+pyosmium_release = '2.12.1'
 
 # libosmium version shipped with the Pip release
-libosmium_version = '2.12.0'
+libosmium_version = '2.12.1'
diff --git a/test/helpers.py b/test/helpers.py
index fb00777..03084bb 100644
--- a/test/helpers.py
+++ b/test/helpers.py
@@ -3,6 +3,7 @@
 import random
 import tempfile
 import os
+from textwrap import dedent
 import osmium
 
 def _complete_object(o):
@@ -87,6 +88,15 @@ def create_osm_file(data):
 
     return fname
 
+def create_opl_file(data):
+    with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.opl', delete=False) as fd:
+        fname = fd.name
+        fd.write(dedent(data).encode('utf-8'))
+        fd.write(b'\n')
+
+    return fname
+
+
 def osmobj(kind, **args):
     ret = dict(args)
     ret['type'] = kind
@@ -99,7 +109,11 @@ class HandlerTestBase:
     apply_idx = 'sparse_mem_array'
 
     def test_func(self):
-        fn = create_osm_file(self.data)
+        if isinstance(self.data, (list, tuple)):
+            fn = create_osm_file(self.data)
+        else:
+            fn = create_opl_file(self.data)
+
         try:
             self.Handler().apply_file(fn, self.apply_locations, self.apply_idx)
         finally:
diff --git a/test/test_taglist.py b/test/test_taglist.py
new file mode 100644
index 0000000..0437644
--- /dev/null
+++ b/test/test_taglist.py
@@ -0,0 +1,100 @@
+from nose.tools import *
+import unittest
+import os
+import sys
+from datetime import datetime
+
+from helpers import create_osm_file, osmobj, HandlerTestBase
+
+import osmium as o
+
+class TestTagEmptyTagListLength(HandlerTestBase, unittest.TestCase):
+    data = "n234 x1 y2"
+
+    class Handler(o.SimpleHandler):
+
+        def node(self, n):
+            assert_equals(0, len(n.tags))
+            assert_false(n.tags)
+
+class TestTagEmptyTagListContains(HandlerTestBase, unittest.TestCase):
+    data = "n234 x1 y2"
+
+    class Handler(o.SimpleHandler):
+
+        def node(self, n):
+            assert_not_in("a", n.tags)
+
+class TestTagEmptyTagListGet(HandlerTestBase, unittest.TestCase):
+    data = "n234 x1 y2"
+
+    class Handler(o.SimpleHandler):
+
+        def node(self, n):
+            assert_equals(None, n.tags.get("foo"))
+            assert_equals(None, n.tags.get("foo", None))
+            assert_equals("fs", n.tags.get("foo", "fs"))
+
+class TestTagEmptyTagListIndexOp(HandlerTestBase, unittest.TestCase):
+    data = "n234 x1 y2"
+
+    class Handler(o.SimpleHandler):
+
+        def node(self, n):
+            with assert_raises(KeyError):
+                n.tags["foo"]
+            with assert_raises(KeyError):
+                n.tags[None]
+
+class TestTagListLen(HandlerTestBase, unittest.TestCase):
+    data = """\
+           n1 x0 y0 Ta=a
+           n2 Tkey=value
+           n3 Tfoo=1,bar=2,foobar=33
+           """
+    class Handler(o.SimpleHandler):
+
+        expected_len = { 1 : 1, 2 : 1, 3 : 3}
+
+        def node(self, n):
+            assert_true(n.tags)
+            assert_equals(self.expected_len[n.id], len(n.tags))
+
+class TestTagContains(HandlerTestBase, unittest.TestCase):
+    data = "n234 Tabba=x,2=vvv,xx=abba"
+
+    class Handler(o.SimpleHandler):
+
+        def node(self, n):
+            assert_in("abba", n.tags)
+            assert_in("2", n.tags)
+            assert_in("xx", n.tags)
+            assert_not_in("x", n.tags)
+            assert_not_in(None, n.tags)
+            assert_not_in("", n.tags)
+
+class TestTagIndexOp(HandlerTestBase, unittest.TestCase):
+    data = "n234 Tabba=x,2=vvv,xx=abba"
+
+    class Handler(o.SimpleHandler):
+
+        def node(self, n):
+            eq_("x", n.tags["abba"])
+            eq_("vvv", n.tags["2"])
+            eq_("abba", n.tags["xx"])
+            for k in ("x", "addad", "..", None):
+                with assert_raises(KeyError):
+                    n.tags[k]
+
+class TestTagGet(HandlerTestBase, unittest.TestCase):
+    data = "n234 Tabba=x,2=vvv,xx=abba"
+
+    class Handler(o.SimpleHandler):
+
+        def node(self, n):
+            eq_("x", n.tags.get("abba"))
+            eq_("vvv", n.tags.get("2", None))
+            eq_("abba", n.tags.get("xx", "ff"))
+            eq_("43 fg", n.tags.get("_", "43 fg"))
+            assert_is_none(n.tags.get("gerger4"))
+            assert_is_none(n.tags.get("ffleo", None))
diff --git a/tools/pyosmium-get-changes b/tools/pyosmium-get-changes
index 5be7c49..f10b305 100755
--- a/tools/pyosmium-get-changes
+++ b/tools/pyosmium-get-changes
@@ -4,7 +4,7 @@ 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
+will just print the initial 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
@@ -24,6 +24,7 @@ from osmium.replication import newest_change_from_file
 from osmium.replication.utils import get_replication_header
 from osmium import SimpleHandler, WriteHandler
 
+import re
 import sys
 import logging
 from textwrap import dedent as msgfmt
@@ -105,17 +106,18 @@ def write_end_sequence(fname, seqid):
         with open(fname, 'w') as fd:
             fd.write(str(startseq))
 
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr,
-                        format='%(levelname)s: %(message)s')
+def get_arg_parser(from_main=False):
+    def h(s):
+        return re.sub("\s\s+" , " ", s)
 
     parser = ArgumentParser(description=__doc__,
+                            usage=None if from_main else 'pyosmium-get-changes [options]',
                             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.""")
+                        help=h("""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,
@@ -130,19 +132,26 @@ if __name__ == '__main__':
     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
+                       help=h("""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.""")
+                               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,
+                        help=h("""When determining the start from an OSM file,
                                 ignore potential replication information in the
-                                header and search for the newest OSM object.""")
+                                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()
+    return parser
+
+
+if __name__ == '__main__':
+    logging.basicConfig(stream=sys.stderr,
+                        format='%(levelname)s: %(message)s')
+
+    options = get_arg_parser(from_main=True).parse_args()
 
     log.setLevel(max(3 - options.loglevel, 0) * 10)
 
diff --git a/tools/pyosmium-up-to-date b/tools/pyosmium-up-to-date
index cc54ad1..34fddcd 100755
--- a/tools/pyosmium-up-to-date
+++ b/tools/pyosmium-up-to-date
@@ -23,6 +23,7 @@ resolved). Any other error results in a return code larger than 1. The
 output file is guaranteed to be unmodified in that case.
 """
 
+import re
 import sys
 import logging
 
@@ -140,41 +141,47 @@ def compute_start_point(options):
 
     return url, seq, ts
 
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr,
-                        format='%(asctime)s %(levelname)s: %(message)s')
+def get_arg_parser(from_main=False):
+    def h(s):
+        return re.sub("\s\s+" , " ", s)
 
     parser = ArgumentParser(description=__doc__,
+                            usage=None if from_main else 'pyosmium-up-to-date [options] <osm file>',
                             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.""")
+                        help=h("""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).""")
+            help=h("""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
+                        help=h("""Ignore potential replication information in the
                                 header of the input file and search for the
-                                newest OSM object in the file instead.""")
+                                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
+                        help=h("""Number of minutes to start downloading before
                                 the newest addition to input data. (Ignored when
-                                the file contains a sequence ID.) Default: 60.""")
+                                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()
+    return parser
+
+if __name__ == '__main__':
+    logging.basicConfig(stream=sys.stderr,
+                        format='%(asctime)s %(levelname)s: %(message)s')
+
+    options = get_arg_parser(from_main=True).parse_args()
     log.setLevel(max(3 - options.loglevel, 0) * 10)
 
     try:

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