This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository pyosmium.
commit bbbc466fa5a8f9b7067e1694251064bb360a49d7 Author: Bas Couwenberg <[email protected]> Date: Wed May 20 21:57:34 2015 +0200 Imported Upstream version 2.1.0 --- .travis.yml | 33 ++++---- README.md | 27 +++++-- doc/.gitignore | 1 + doc/conf.py | 4 +- doc/index.rst | 3 +- doc/intro.rst | 149 +++++++++++++++++++++++++++++++++++- examples/amenity_list.py | 16 +++- examples/create_nodecache.py | 2 +- examples/osm_file_stats.py | 21 +++-- examples/pub_names.py | 12 ++- examples/road_length.py | 20 ++++- lib/geom.cc | 18 ++++- lib/osm.cc | 34 +++++++- test/{test_helper.py => helpers.py} | 0 test/test_geom.py | 40 ++++++++++ test/test_io.py | 2 +- test/test_osm.py | 7 +- 17 files changed, 344 insertions(+), 45 deletions(-) diff --git a/.travis.yml b/.travis.yml index d855c02..19a41aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,9 @@ +#----------------------------------------------------------------------------- +# +# Configuration for continuous integration service at travis-ci.org +# +#----------------------------------------------------------------------------- + language: cpp compiler: @@ -9,34 +15,29 @@ env: - USE_PYTHON_VERSION=3 before_install: - # we need at least g++-4.8 for c++11 features + # upgrade compiler (we need at least g++-4.8 for C++11 features) - sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test - - sudo apt-get update --yes --quiet + - sudo apt-get update --yes -qq + - gcc_version=4.8 + - sudo apt-get install --yes gcc-${gcc_version} g++-${gcc_version} + - gcc-${gcc_version} --version + # make sure compiler executables point to the just installed version + - sudo ln -sf /usr/bin/cpp-${gcc_version} /usr/bin/cpp + - sudo ln -sf /usr/bin/gcc-${gcc_version} /usr/bin/gcc + - sudo ln -sf /usr/bin/g++-${gcc_version} /usr/bin/g++ + # install dependencies + - sudo apt-get install --yes make python3-dev python3 libboost-dev libboost-python-dev libprotobuf-dev protobuf-compiler libsparsehash-dev python-nose python3-nose install: - # upgrade compilers - - sudo apt-get install --yes gcc-4.8 g++-4.8 - # make sure 'cpp' is the just installed current one - - sudo rm /usr/bin/cpp - - sudo ln -s /usr/bin/cpp-4.8 /usr/bin/cpp - # innstall dependencies - - sudo apt-get install --yes make python3-dev python3 libboost-dev libboost-python-dev libprotobuf-dev protobuf-compiler libsparsehash-dev python-nose python3-nose - cd .. - git clone https://github.com/osmcode/libosmium.git - # OSMPBF is too old, install from git - #- sudo apt-get install --yes libosmpbf-dev - git clone https://github.com/scrosby/OSM-binary.git - cd OSM-binary/src - make - sudo make install -#before_script: -# - true - script: - cd $TRAVIS_BUILD_DIR - - if [ "${CXX}" = 'g++' ]; then export CXX=g++-4.8; fi; - - if [ "${CC}" = 'gcc' ]; then export CC=gcc-4.8; fi; - python${USE_PYTHON_VERSION} setup.py build - cd test - python${USE_PYTHON_VERSION} run_tests.py diff --git a/README.md b/README.md index 0a82bad..3e6a983 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ # pyosmium -Provides Python bindings for the [libosmium](https://github.com/osmcode/libosmium) C++ +Provides Python bindings for the [Libosmium](https://github.com/osmcode/libosmium) C++ library, a library for working with OpenStreetMap data in a fast and flexible manner. [](http://travis-ci.org/osmcode/pyosmium) -## Depends + +## Dependencies Python >= 2.7 is supported (that includes python 3.x). 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`. + ## Installation To compile the bindings, run @@ -23,18 +25,20 @@ To compile and install the bindings, run python setup.py install -libosmium is expected to reside in the same directory as pyosmium or to be +Libosmium is expected to reside in the same directory as pyosmium or to be installed globally. + ## Examples The `example` directory contains small examples on how to use the library. -They are for most parts ports of the examples in Libosmium and osmium-contrib. +They are mostly ports of the examples in Libosmium and osmium-contrib. + ## Testing There is a small test suite in the test directory. This provides regression -test for the python bindings, it is not meant to be a test suite for libosmium. +test for the python bindings, it is not meant to be a test suite for Libosmium. You'll need the Python `nose` module. On Debian/Ubuntu install the package `python-nose`. @@ -44,10 +48,23 @@ The suite can be run with: cd test python run_tests.py + +## Documentation + +To build the documentation you need [Sphinx](http://sphinx-doc.org/). +On Debian/Ubuntu install `python-sphinx` or `python3-sphinx`. + +Then run: + + cd doc + make html + + ## License Pyosmium is available under the BSD 2-Clause License. See LICENSE.TXT. + ## Authors Sarah Hoffmann ([email protected]) diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..e35d885 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +_build diff --git a/doc/conf.py b/doc/conf.py index 77ac1ec..883b890 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -63,9 +63,9 @@ copyright = '2015, Sarah Hoffmann' # built documents. # # The short X.Y version. -version = '2.0' +version = '2.1' # The full version, including alpha/beta/rc tags. -release = '2.0.0' +release = '2.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/index.rst b/doc/index.rst index e7ac56a..ebb7fb4 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -7,7 +7,8 @@ Welcome to Pyosmium's documentation! ==================================== Pyosmium is a library to process OSM files in different formats. It is -a wrapper of the C++ library osmium and profits from its fast implementation. +a wrapper of the C++ library `osmium <http://osmcode.org/libosmium/>`_ +and profits from its fast implementation. .. toctree:: :maxdepth: 2 diff --git a/doc/intro.rst b/doc/intro.rst index ee8c1e5..1cdb4b8 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -2,5 +2,150 @@ Basic Usage =========== The following chapter gives a practical introduction on how to use Pyosmium. -For a more detailed introduction into the design of the library, the reader -is referred to the osmium documentation. +It is assumed that you already have a basic knowledge about the +`OSM data model`_. + +For a more detailed introduction into the design of the osmium library, the +reader is referred to the `osmium documentation`_. + +.. _OSM data model: http://wiki.openstreetmap.org/wiki/Elements +.. _osmium documentation: http://osmcode.org/libosmium/manual/libosmium-manual.html + +Using Handler Classes ++++++++++++++++++++++ + +OSM file parsing by osmium is built around the concept of handlers. A handler +is a class with a set of callback functions. Each function processes exactly +one type of object as it is read from the file. + +Let's start with a very simple handler that counts the nodes in the +input file:: + + import osmium + + class CounterHandler(osmium.SimpleHandler): + def __init__(self): + osmium.SimpleHandler.__init__(self) + self.num_nodes = 0 + + def node(self, n): + self.num_nodes += 1 + +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 +can be safely ignored. + +Now the handler needs to be applied to an OSM file. The easiest way to +accomplish that is to call the :py:meth:`~osmium.SimpleHandler.apply_file` +convenience function, which in its simplest form only requires the file name +as a parameter. The main routine of the node counting application +therefore looks like this:: + + if __name__ == '__main__': + + h = CounterHandler() + + h.apply_file("test.osm.pbf") + + print("Number of nodes: %d" % h.num_nodes) + +That already finishes our node counting program. + +Inspecting the OSM objects +++++++++++++++++++++++++++ + +Counting nodes is actually boring because it completely ignores the +content of the nodes. So let's change the handler to only count hotels +(normally tagged with ``tourism=hotel``). They may be tagged as nodes, ways +or relations, so handler functions for all three types need to be implemented:: + + import osmium + + class HotelCounterHandler(osmium.SimpleHandler): + def __init__(self): + osmium.SimpleHandler.__init__(self) + self.num_nodes = 0 + + def count_hotel(self, tags): + if tags['tourism'] == 'hotel': + self.num_nodes += 1 + + def node(self, n): + self.count_hotel(n.tags) + + def way(self, w): + self.count_hotel(w.tags) + + def relation(self, r): + self.count_hotel(r.tags) + +A reference to the object is always given as the only parameter to the +handler functions. The objects have some common methods and attributes, +listed in :py:class:`osmium.osm.OSMObject`, and some that are specific to +each type. As all objects have tags, it is possible to reuse the same +implementation for all types. The main function remains the same. + +It is important to remember that the object +references that are handed to the handler are only temporary. They will +become invalid as soon as the function returns. Handler functions *must* +copy any data that should be kept for later use into their own data +structures. This also includes attributes like tag lists. + +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:: + + h.apply_file("test.osm.pbf", locations=True, idx='sparse_mem_array') + +The third parameter `idx` is optional and states what kind of cache +osmium is supposed to use. The default `sparse_mem_array` is a good +choice for small to medium size extracts of OSM data. If you plan to +process the whole planet file, `dense_mmap_array` is better suited. +If you want the cache to be persistent across invocations, you +can use `dense_file_array` giving an additional file location for the +cache like that:: + + h.apply_file("test.osm.pbf", locations=True, idx='sparse_file_array,example.nodecache') + +where `example.nodecache` is the name of the cache file. + +Interfacing with Shapely +++++++++++++++++++++++++ + +Pyosmium is a library for processing OSM files and therefore offers almost +no functionality for processing geometries further. For this other libraries +exist. 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:: + + import osmium + import shapely.wkb as wkblib + + # A global factory that creates WKB from a osmium geometry + wkbfab = osmium.geom.WKBFactory() + + class WayLenHandler(osmium.SimpleHandler): + def __init__(self): + osmium.SimpleHandler.__init__(self) + self.total = 0 + + def way(self, w): + wkb = wkbfab.create_linestring(w) + line = wkblib.loads(wkb, hex=True) + # Length is computed in WGS84 projection, which is practically meaningless. + # Lets pretend we didn't notice, it is an example after all. + self.total += line.length + + if __name__ == '__main__': + h = WayLenHandler() + h.apply_file("test.osm.pbf", locations=True) + print("Total length: %f" % h.total) + +.. _Shapely: http://toblerity.org/shapely/index.html diff --git a/examples/amenity_list.py b/examples/amenity_list.py index f3bc950..3921cf9 100644 --- a/examples/amenity_list.py +++ b/examples/amenity_list.py @@ -1,3 +1,10 @@ +""" +Extract all objects with an amenity tag from an osm file and list them +with their name and position. + +This example shows how geometries from osmium objects can be imported +into shapely using the WKBFactory. +""" import osmium as o import sys import shapely.wkb as wkblib @@ -22,6 +29,11 @@ class AmenityListHandler(o.SimpleHandler): self.print_amenity(a.tags, centroid.x, centroid.y) -handler = AmenityListHandler() +if __name__ == '__main__': + if len(sys.argv) != 2: + print "Usage: python amenity_list.py <osmfile>" + sys.exit(-1) + + handler = AmenityListHandler() -handler.apply_file(sys.argv[1]) + handler.apply_file(sys.argv[1]) diff --git a/examples/create_nodecache.py b/examples/create_nodecache.py index 8e9ac55..02bd4e2 100644 --- a/examples/create_nodecache.py +++ b/examples/create_nodecache.py @@ -3,7 +3,7 @@ import sys if len(sys.argv) != 3: print("Usage: python create_nodecache.py <osm file> <node cache>") - exit() + exit(-1) reader = o.io.Reader(sys.argv[1], o.osm.osm_entity_bits.NODE) diff --git a/examples/osm_file_stats.py b/examples/osm_file_stats.py index c5dcf09..52ea56c 100644 --- a/examples/osm_file_stats.py +++ b/examples/osm_file_stats.py @@ -1,3 +1,8 @@ +""" +Simple example that counts the number of objects in an osm file. + +Shows how to write a handler for the different types of objects. +""" import osmium as o import sys @@ -18,9 +23,15 @@ class FileStatsHandler(o.SimpleHandler): self.rels += 1 -h = FileStatsHandler() -h.apply_file(sys.argv[1]) +if __name__ == '__main__': + if len(sys.argv) != 2: + print "Usage: python osm_file_stats.py <osmfile>" + sys.exit(-1) + + h = FileStatsHandler() + + h.apply_file(sys.argv[1]) -print("Nodes: %d" % h.nodes) -print("Ways: %d" % h.ways) -print("Relations: %d" % h.rels) + print("Nodes: %d" % h.nodes) + print("Ways: %d" % h.ways) + print("Relations: %d" % h.rels) diff --git a/examples/pub_names.py b/examples/pub_names.py index 69d4f9d..b71c050 100644 --- a/examples/pub_names.py +++ b/examples/pub_names.py @@ -1,3 +1,6 @@ +""" +Search for pubs in an osm file and list their names. +""" import osmium import sys @@ -14,6 +17,11 @@ class NamesHandler(osmium.SimpleHandler): def way(self, w): self.output_pubs(w.tags) -h = NamesHandler() -h.apply_file(sys.argv[1]) +if __name__ == '__main__': + if len(sys.argv) != 2: + print "Usage: python pub_names.py <osmfile>" + sys.exit(-1) + + h = NamesHandler() + h.apply_file(sys.argv[1]) diff --git a/examples/road_length.py b/examples/road_length.py index 915336d..b36cc3e 100644 --- a/examples/road_length.py +++ b/examples/road_length.py @@ -1,3 +1,8 @@ +""" +Compute the total length of highways in an osm file. + +Shows how extract the geometry of a way. +""" import osmium as o import sys @@ -11,9 +16,18 @@ class RoadLengthHandler(o.SimpleHandler): try: self.length += o.geom.haversine_distance(w.nodes) except o.InvalidLocationError: + # A location error might occur if the osm file is an extract + # where nodes of ways near the boundary are missing. print("WARNING: way %d incomplete. Ignoring." % w.id) -h = RoadLengthHandler() -h.apply_file(sys.argv[1], locations=True) +if __name__ == '__main__': + if len(sys.argv) != 2: + print "Usage: python road_length.py <osmfile>" + sys.exit(-1) + + h = RoadLengthHandler() + # As we need the geometry, the node locations need to be cached. Therefore + # set 'locations' to true. + h.apply_file(sys.argv[1], locations=True) -print('Total way length: %.2f km' % (h.length/1000)) + print('Total way length: %.2f km' % (h.length/1000)) diff --git a/lib/geom.cc b/lib/geom.cc index 9a0ba99..a650c39 100644 --- a/lib/geom.cc +++ b/lib/geom.cc @@ -17,6 +17,16 @@ BOOST_PYTHON_MODULE(_geom) using namespace boost::python; docstring_options doc_options(true, true, false); + enum_<osmium::geom::use_nodes>("use_nodes") + .value("UNIQUE", osmium::geom::use_nodes::unique) + .value("ALL", osmium::geom::use_nodes::all) + ; + + enum_<osmium::geom::direction>("direction") + .value("BACKWARD", osmium::geom::direction::backward) + .value("FORWARD", osmium::geom::direction::forward) + ; + def("haversine_distance", static_cast<double (*)(const osmium::WayNodeList&)>(&osmium::geom::haversine::distance), arg("list"), "Compute the distance using the Haversine algorithm which takes the " @@ -39,10 +49,14 @@ BOOST_PYTHON_MODULE(_geom) (arg("self"), arg("ref")), "Create a point geometry from a :py:class:`osmium.osm.NodeRef`.") .def("create_linestring", static_cast<std::string (WKBFactory::*)(const osmium::WayNodeList&, osmium::geom::use_nodes, osmium::geom::direction)>(&WKBFactory::create_linestring), - (arg("self"), arg("list"), arg("use_nodes")="unique", arg("direction")="forward" ), + (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 (WKBFactory::*)(const osmium::Way&, osmium::geom::use_nodes, osmium::geom::direction)>(&WKBFactory::create_linestring), - (arg("self"), arg("way"), arg("use_nodes")="unique", arg("direction")="forward" ), + (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", &WKBFactory::create_multipolygon, (arg("self"), arg("area")), diff --git a/lib/osm.cc b/lib/osm.cc index 85d39eb..c39e08c 100644 --- a/lib/osm.cc +++ b/lib/osm.cc @@ -78,7 +78,39 @@ BOOST_PYTHON_MODULE(_osm) "that it is within the usual bounds.") ; class_<osmium::Box>("Box", - "A bounding box around a geographic area.") + "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.") + .def(init<double, double, double, double>()) + .def(init<osmium::Location, osmium::Location>()) + .add_property("bottom_left", + make_function(static_cast<osmium::Location& (osmium::Box::*)()>(&osmium::Box::bottom_left), + return_value_policy<reference_existing_object>()), + "(read-only) Bottom-left corner of the bounding box.") + .add_property("top_right", + make_function(static_cast<osmium::Location& (osmium::Box::*)()>(&osmium::Box::top_right), + return_value_policy<reference_existing_object>()), + "(read-only) Top-right corner of the bounding box.") + .def("extend", + make_function(static_cast<osmium::Box& (osmium::Box::*)(const osmium::Location&)>(&osmium::Box::extend), + return_value_policy<reference_existing_object>()), + //(arg("self"), arg("location")), + "Extend the box to include the given location. If the location " + "is invalid the box remains unchanged. If the box is invalid, it " + "will contain only the location after the operation.") + .def("extend", + make_function(static_cast<osmium::Box& (osmium::Box::*)(const osmium::Box&)>(&osmium::Box::extend), + return_value_policy<reference_existing_object>()), + //(arg("self"), arg("box")), + "Extend the box to include the given box. If the box to be added " + "is invalid the input box remains unchanged. If the input box is invalid, it " + "will become equal to the box that was added.") + .def("valid", &osmium::Box::valid, args("self"), + "Check if the box coordinates are defined and with the usual bounds.") + .def("size", &osmium::Box::size, args("self"), + "Return the size in square degrees.") + .def("contains", &osmium::Box::contains, (arg("self"), arg("location")), + "Check if the given location is inside the box.") ; class_<osmium::Tag, boost::noncopyable>("Tag", "A single OSM tag.", diff --git a/test/test_helper.py b/test/helpers.py similarity index 100% rename from test/test_helper.py rename to test/helpers.py diff --git a/test/test_geom.py b/test/test_geom.py new file mode 100644 index 0000000..74bdd84 --- /dev/null +++ b/test/test_geom.py @@ -0,0 +1,40 @@ +from nose.tools import * +import unittest + +from helpers import create_osm_file, osmobj, HandlerTestBase +import osmium as o + +wkbfab = o.geom.WKBFactory() + +class TestWkbCreateNode(HandlerTestBase, unittest.TestCase): + data = [osmobj('N', id=1)] + + class Handler(o.SimpleHandler): + def node(self, n): + wkb = wkbfab.create_point(n) + +class TestWkbCreateWay(HandlerTestBase, unittest.TestCase): + data = [osmobj('N', id=1, lat=0, lon=0), + osmobj('N', id=2, lat=0, lon=1), + osmobj('N', id=3, lat=1, lon=0), + osmobj('W', id=1, nodes = [1,2,3])] + apply_locations = True + + class Handler(o.SimpleHandler): + def way(self, w): + wkb = wkbfab.create_linestring(w) + wkb = wkbfab.create_linestring(w, direction=o.geom.direction.BACKWARD) + wkb = wkbfab.create_linestring(w, use_nodes=o.geom.use_nodes.ALL) + +class TestWkbCreatePoly(HandlerTestBase, unittest.TestCase): + data = [osmobj('N', id=1, lat=0, lon=0), + osmobj('N', id=2, lat=0, lon=1), + osmobj('N', id=3, lat=1, lon=0), + osmobj('W', id=23, + nodes = [1,2,3,1], tags = { "area" : "yes" }), + ] + apply_locations = True + + class Handler(o.SimpleHandler): + def area(self, a): + wkb = wkbfab.create_multipolygon(a) diff --git a/test/test_io.py b/test/test_io.py index a36e371..91db118 100644 --- a/test/test_io.py +++ b/test/test_io.py @@ -2,7 +2,7 @@ from nose.tools import * import unittest import os -from test_helper import create_osm_file, osmobj +from helpers import create_osm_file, osmobj import osmium as o diff --git a/test/test_osm.py b/test/test_osm.py index 79fcf96..b8fc14c 100644 --- a/test/test_osm.py +++ b/test/test_osm.py @@ -3,7 +3,7 @@ import unittest import os from datetime import datetime -from test_helper import create_osm_file, osmobj, HandlerTestBase +from helpers import create_osm_file, osmobj, HandlerTestBase import osmium as o @@ -144,4 +144,7 @@ class TestChangesetAttributes(HandlerTestBase, unittest.TestCase): assert_false(c.open) assert_equals(2, c.num_changes) assert_equals(0, len(c.tags)) - + assert_equals(-1464925, c.bounds.top_right.x) + assert_equals(515288620, c.bounds.top_right.y) + assert_equals(-1465242, c.bounds.bottom_left.x) + assert_equals(515288506, c.bounds.bottom_left.y) -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/pyosmium.git _______________________________________________ Pkg-grass-devel mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel

