From 879f7eb04200d7d2c28af565229bf6e3d54274fd Mon Sep 17 00:00:00 2001 From: karenzshea Date: Mon, 3 Oct 2016 13:08:59 -0400 Subject: [PATCH] Squashed 'third_party/libosmium/' changes from 2282c84..80df1d6 80df1d6 Release v2.9.0 110dc5c Update change log. 6ad5829 Better handling of areas with duplicate segments. f5985ed Better exception message for invalid areas. fa09300 Explicit cast to make intent clear. 6f9b522 Fix name of struct stat on Windows. 6b0a47b Clean up code in data tests. aa1226c Fix progress bar. 3663a19 Extend ProgressBar class so that it works with multiple files. 40c4d5a Add version of file_size() taking a file name. 43a2fac Merge pull request #162 from osmcode/windows-build-scripts cc2305d [skip travis] 1st iteration of new build scripts 7abe4e1 Clean up disk location cache examples. 48841d5 Update change log. cf854e9 Change timestamp parser. 01aa8c7 Add examples osmium_pub_names and osmium_road_length. 483c9f2 Benchmark code cleanup. 3ffea2d Cleaned up some test code. 80f0ff7 Explicit conversion from int to bool. 0ba5918 Write space after progress bar to defend against glitches in output. 8584423 Change progress bar to take max_size on construction. d2c7585 Only call gzoffset when compiling with zlib > 1.2.4. 1b417e5 Add support for a progress report in osmium::io::Reader(). 3b4c8c8 Minor cleanup of appveyor config. d787e25 Fix OPL parser: Relation member without role at end of line. 53ca080 Make lots of variables const. d776ab2 Add to change log. eec3b62 Properly initialize m_data field. cc607e1 Take argument by const ref. be1e346 Remove unused function. 2a356ee Make lots of one-argument constructors explicit. adca74f Add comments to and cleanup up examples. 381e535 Simplify WKB code. b49efd8 Fix opl_parse_changeset_id() return type. bb52e57 Use uint64_t for line count and column to be on the safe side. 243f6a7 Use parentheses to make sure the right precedence is used. 5a7648e Consistently catch by const ref unless var needs to be non-const. e3be990 Avoid some warnings. c436d92 Do not include unistd.h on Windows. 95b228c Add dummy function to avoid warnings. f276ca3 Fixed includes and changelog update. 8c54bd9 Change timestamp error message. 27e1d5c Add OPL parser. 1d2caab Add more includes to osm.hpp to make usual osmium use simpler. 9d88361 More tests for area CRC. 4f8964d Initialize Item::m_diff member on construction. f2b648b Parse coordinates in scientific notations ourselves. b01323f More include fixes. 69f39d4 Fix some includes. 156536d Make padded_length a plain function, not a template function. 65cd1dc Extend functions to set Location lon/lat. 98b7b17 Update to protozero 1.4.2. a6420cf Add diff indicator to items and use for diff opl and debug output. 0ef02a3 Add workaround for YCM. 3a986f4 Update protozero version. 5245c5b Document osmium_count example program and add memory usage output. 796ca13 Document handler class. 2ba1c1f Add example for mercator projection and tiles. 201f744 Restrict tiles to zoom <= 30. 202291d Add member_type_string class. 494ed6e Cleaned up Tile tests. af13a8b Add documentation and range checks to Tile struct. 9df5d91 Some small changes to avoid conversion warnings. afac031 Explicit cast to avoid warning. 8188f66 Better contribution info. fa89d1d Fixed a problem limiting cache file sizes on Windows to 32 bit. 23a89df Remove obsolete info about versioning from CONTRIBUTING.md file. 115ae23 Release v2.8.0 4174b3c Style fix. 1795dcb Function wait_and_pop_with_timeout() is not needed any more. 4a3a71b Fix for possible threading problem. cc85925 Updated change log. 67bc8b1 Use unordered_map instead of map in PBF string table code. 18b7b66 Set better default for string table chunk size and document it. e6d7410 Remove dependency on sparsehash and boost program_options for examples. 14d92d6 Fix regression: Debug output of invalid location works again. ef91ce1 Bugfix: PBF String table corruption when there are many strings. 649af78 Remove DeltaEncodeIterator completely. 56e5ac2 Function getting queue sizes from environment uses default when getting 0. bfaab7d Add change log. d260339 Remove use of PROTOZERO_STRICT_API macro. c61722d Remove use of DeltaEncodeIterator simplifying code. f7c60b6 Updates for new protozero. 0bdfb9d Updated change log. bb56cbb Switch to newest protozero 1.4.0. 9e19a82 Add ccache support to CMake cnfig, better travis builds. 00d8868 Make I/O max queue sizes configurable via environment. dc7e504 Remove unused debugging code. 13f66a0 Track pop() calls and queue underruns when OSMIUM_DEBUG_QUEUE_SIZE is set. 5c2e367 Add EWKT support. 8f7c7d3 Automatically set correct SRID when creating geometries. ff11893 Better check of optional components in CMake config. 4562429 Use fallback implementation for coordinates given in scientific notation. 3bdf46e Mark enable_debug_output() as deprecated. ea1093e Update catch unit test framework. 8623f1e Release v2.7.2 e135dd8 Fix data corruption regression in mmap based indexes. adbd3b0 Do not output empty discussion tags in changeset XML output. 8126fbb Formatting. c6970fd Fix coordinate output. 3471b4b Resize output string once in output_int(). 0ddf0e7 Use our own function to convert integers to strings instead of printf. f9a1dd3 Reading and writing coordinates is now independant of locale. 8104294 Use hand-crafted function for hex output (faster than printf). 0bb452a Fix links in change log. 1862d06 Release v2.7.1 8bfe2ba Release v2.7.0 c3604f3 Use 64bit counter in area stats. 9e589b3 Update gdalcpp.hpp from upstream. fd55d9b Cleanup of OGR-related code. d0c53e0 Fix bug: Relation wasn't found correctly from member. 24145f9 Use make_iterator helper function. a8a287d Refactor count_not_removed function. (No template necessary.) 389332a Also print removed flag from member_meta. 5e7c5d0 Remove unnecessary overload of begin() and end() function in iterator_range. 2ec007f Do not add rings to invalid area, even if create_empty_areas is set. fee8b73 Optionally keep type tag in area assembler. Better doc for config. c7e1f8a Fix timer output in assembler code. 032ab40 Update change log. dcfa439 Node location store keeps track of whether node ids are ordered. 54d5eb8 Add tests for new file based index code. 4fe5b30 Use correct empty value when initializing index. 40b5c79 Static or not static, that is the question. aaa9b46 Open index file with minimum size, because zero-sized mmap is not allowed. fea2337 Fix for disk-based indexes. 428a413 More tests of corner cases for id to location index. 9d2a31b Add config option to areas assembler for only creating some areas. d11bf8d Count and report inner rings with the same tags as relation/outer rings. bde10c4 Speed up copy of tags. e4c9f87 Revert "Consistently remove some tags from area." 9cd7a03 Set areas assembly config setting create_empty_areas to true by default. 660fb63 Better ordering of OSMObjects. b4199c2 Use std::strcmp instead of just strcmp. 579c34b Better field width/precision in problem reporter. a2ebeeb Use field names with 8 characters or less in OGR problem reporter. ef523fe Switch remaining "typedef"s to "using". 19425f8 Switching from "typedef" to "using" in geom code. b13c2be More cases of switching from "typedef" to "using". 7f53977 Refactoring iterators: Not derived from std::iterator any more. 1922224 Consistently remove some tags from area. 295495f Fix check for detecting wrong role. 9aa6d46 Report more IDs in problem reporters. d7a5da7 Remove now unused spike segment reporter. 0666d66 Only report duplicate segments if they belong to the same way. 9e17f89 Improved error reporting for area assembler. e983a48 More code cleanup and docs. 927eeda Replace awkward std::pair construct by real class. d0543b9 Various area code refactorings. 0ae8f07 Do not build areas for ways with tag area=no. d4cabe7 Add some convenience functions to check for tags in TagList. 99f4be9 Add missing include. a8dda78 Travis: Only run tests if build succeeded. 9db3034 Add missing "nodes" fields. 50e9fcb Report ways that are in multiple rings as errors. 58a3669 Add some paranoia asserts. 3958c1d Use iterator_range to make equal_range results easier to use. c12c710 Add for_each_member() function to iterate over members of an mp relation. ca35452 Change argument order in create_area() functions. 4473ae1 Keep stats on multipolygons with no tags on the relation. 12c5335 Bugfix: Check that there is a problem reporter before using it. ec2afce Update change log. 5af2ec9 Use new area assembler interface in multipolygon collector. 73e3440 Some code cleanup in area code and new interface for calling assembler. 7737479 Add the number of nodes in area to problem reporter. b4f9343 Use const_iterator where possible. 02372b2 Simplify code that checks for open rings. 8d6099e Pull out location_to_ring_map into details ns and add == and < ops. 1a05042 Mixed code cleanups and added comments. 4b8d1be Ignore empty role when checking inner/outer roles on multipolygons. e22f573 Now GCC is complaining about the clang pragma... 48000c0 Add some missing includes and forward declarations. ba9504a Workaround for bug in old libc. a138265 Completely new algorithm for assembling multipolygons. 74054bd Add specialization of std::hash function for Location. 5ed4c90 Use newest gdalcpp.hpp with implicit transaction support. 676949e Add "locations_on_ways" support for OPL format, too. ce05c19 Add support for reading/writing XML/PBF files with locations on ways. 62b2ee4 Fix checksum test. bd512a8 Added "add_crc32" file option for adding CRC32 checksum to debug output. 3a100fa Incorporate locations in NodeRefs into CRC32 checksum. ac02f86 Update catch.hpp to newest version. Removed outdated info in README. 481f48b When assembling areas ignore ways containing no or only a single node. a0ae33a Fix unsigned overflow in pool.hpp. 91b8adf Fix undefined behaviour in WKB writer. 697f460 Check results of dynamic casts. f1e4571 Fix from_item_type() implementation so it also works with undefined type. 65df99b Add future_queue_type alias to simplify code. 4340e4d Removed SortedQueue implementation which was never used. cdd8f8c Add version.hpp with macros defining version of the library. ff5d42a Update to newest gdalcpp.hpp. a184f66 Update change log. 0ea76f7 Add osmium::Area::outer_rings() and inner_rings() functions. b0404b7 New ItemIteratorRange class for iteration over buffers and subitems. eff8a7c Add default type to string_to_object_id for IDs without type prefix. e877a6f Clean up code inner vs. outer ring in geometry factory. 9224be5 Disable use of XML entities in OSM files. 9d9fa08 Output operator of location shows full precision of coordinates. 9a8e7c0 Documentation fixes. git-subtree-dir: third_party/libosmium git-subtree-split: 80df1d6850bdfa661587839b77dcea0ab8fc814a --- .gitignore | 2 + .travis.yml | 96 +- .ycm_extra_conf.py | 5 + CHANGELOG.md | 141 +- CMakeLists.txt | 25 +- CONTRIBUTING.md | 147 +- NOTES_FOR_DEVELOPERS.md | 143 ++ appveyor.yml | 85 +- benchmarks/osmium_benchmark_count.cpp | 8 +- benchmarks/osmium_benchmark_count_tag.cpp | 8 +- benchmarks/osmium_benchmark_index_map.cpp | 16 +- ...mium_benchmark_static_vs_dynamic_index.cpp | 58 +- benchmarks/osmium_benchmark_write_pbf.cpp | 17 +- build-appveyor.bat | 123 ++ build-local.bat | 43 + cmake/iwyu.sh | 5 + examples/CMakeLists.txt | 40 +- examples/README.md | 32 + examples/osmium_area_test.cpp | 129 +- examples/osmium_convert.cpp | 92 +- examples/osmium_count.cpp | 69 +- examples/osmium_create_node_cache.cpp | 55 - examples/osmium_debug.cpp | 46 +- examples/osmium_filter_discussions.cpp | 48 +- examples/osmium_index.cpp | 147 +- examples/osmium_location_cache_create.cpp | 87 + examples/osmium_location_cache_use.cpp | 101 + examples/osmium_pub_names.cpp | 89 + examples/osmium_read.cpp | 26 +- examples/osmium_read_with_progress.cpp | 56 + examples/osmium_road_length.cpp | 92 + examples/osmium_serdump.cpp | 24 +- examples/osmium_tiles.cpp | 72 + examples/osmium_use_node_cache.cpp | 68 - include/gdalcpp.hpp | 91 +- include/osmium/area/assembler.hpp | 1760 ++++++++++++----- .../osmium/area/detail/node_ref_segment.hpp | 310 ++- include/osmium/area/detail/proto_ring.hpp | 256 +-- include/osmium/area/detail/segment_list.hpp | 173 +- include/osmium/area/detail/vector.hpp | 121 ++ .../osmium/area/multipolygon_collector.hpp | 31 +- include/osmium/area/problem_reporter.hpp | 78 +- .../area/problem_reporter_exception.hpp | 31 +- include/osmium/area/problem_reporter_ogr.hpp | 143 +- .../osmium/area/problem_reporter_stream.hpp | 33 +- include/osmium/area/stats.hpp | 128 ++ include/osmium/builder/attr.hpp | 37 +- include/osmium/builder/builder.hpp | 3 +- include/osmium/builder/builder_helper.hpp | 8 +- include/osmium/builder/osm_object_builder.hpp | 22 +- include/osmium/diff_iterator.hpp | 10 +- include/osmium/dynamic_handler.hpp | 9 +- include/osmium/experimental/flex_reader.hpp | 3 +- include/osmium/geom/factory.hpp | 34 +- include/osmium/geom/geojson.hpp | 13 +- include/osmium/geom/geos.hpp | 57 +- include/osmium/geom/haversine.hpp | 2 +- include/osmium/geom/ogr.hpp | 38 +- include/osmium/geom/projection.hpp | 22 +- include/osmium/geom/rapid_geojson.hpp | 14 +- include/osmium/geom/tile.hpp | 45 +- include/osmium/geom/util.hpp | 4 +- include/osmium/geom/wkb.hpp | 30 +- include/osmium/geom/wkt.hpp | 37 +- include/osmium/handler.hpp | 56 +- include/osmium/handler/chain.hpp | 2 +- include/osmium/handler/check_order.hpp | 4 +- include/osmium/handler/disk_store.hpp | 2 +- .../handler/node_locations_for_ways.hpp | 18 +- include/osmium/handler/object_relations.hpp | 2 +- .../index/detail/create_map_with_fd.hpp | 4 +- .../osmium/index/detail/mmap_vector_base.hpp | 38 +- .../osmium/index/detail/mmap_vector_file.hpp | 19 +- include/osmium/index/detail/vector_map.hpp | 18 +- .../osmium/index/detail/vector_multimap.hpp | 10 +- include/osmium/index/index.hpp | 4 +- include/osmium/index/map.hpp | 14 +- include/osmium/index/map/sparse_mem_map.hpp | 3 +- include/osmium/index/multimap.hpp | 8 +- include/osmium/index/multimap/hybrid.hpp | 14 +- .../index/multimap/sparse_mem_multimap.hpp | 11 +- include/osmium/io/any_input.hpp | 3 +- include/osmium/io/bzip2_compression.hpp | 10 +- include/osmium/io/compression.hpp | 45 +- .../osmium/io/detail/debug_output_format.hpp | 215 +- include/osmium/io/detail/input_format.hpp | 16 +- include/osmium/io/detail/o5m_input_format.hpp | 13 +- include/osmium/io/detail/opl_input_format.hpp | 156 ++ .../osmium/io/detail/opl_output_format.hpp | 192 +- .../osmium/io/detail/opl_parser_functions.hpp | 747 +++++++ include/osmium/io/detail/output_format.hpp | 33 +- include/osmium/io/detail/pbf.hpp | 2 +- include/osmium/io/detail/pbf_decoder.hpp | 237 ++- include/osmium/io/detail/pbf_input_format.hpp | 21 +- .../osmium/io/detail/pbf_output_format.hpp | 68 +- include/osmium/io/detail/protobuf_tags.hpp | 4 +- include/osmium/io/detail/queue_util.hpp | 19 +- include/osmium/io/detail/read_write.hpp | 7 +- include/osmium/io/detail/string_table.hpp | 77 +- include/osmium/io/detail/string_util.hpp | 57 +- include/osmium/io/detail/write_thread.hpp | 1 + include/osmium/io/detail/xml_input_format.hpp | 157 +- .../osmium/io/detail/xml_output_format.hpp | 116 +- include/osmium/io/detail/zlib.hpp | 12 +- include/osmium/io/file.hpp | 6 +- include/osmium/io/gzip_compression.hpp | 12 +- include/osmium/io/input_iterator.hpp | 21 +- include/osmium/io/opl_input.hpp | 46 + include/osmium/io/output_iterator.hpp | 11 +- include/osmium/io/overwrite.hpp | 2 +- include/osmium/io/reader.hpp | 61 +- include/osmium/io/writer.hpp | 24 +- include/osmium/memory/buffer.hpp | 10 + include/osmium/memory/collection.hpp | 16 +- include/osmium/memory/item.hpp | 36 +- include/osmium/memory/item_iterator.hpp | 125 +- include/osmium/object_pointer_collection.hpp | 6 +- include/osmium/opl.hpp | 67 + include/osmium/osm.hpp | 15 +- include/osmium/osm/area.hpp | 35 +- include/osmium/osm/box.hpp | 1 - include/osmium/osm/changeset.hpp | 25 +- include/osmium/osm/crc.hpp | 29 +- include/osmium/osm/diff_object.hpp | 5 +- include/osmium/osm/entity_bits.hpp | 18 +- include/osmium/osm/location.hpp | 284 ++- include/osmium/osm/node_ref_list.hpp | 3 +- include/osmium/osm/object.hpp | 91 +- include/osmium/osm/object_comparisons.hpp | 29 +- include/osmium/osm/relation.hpp | 4 +- include/osmium/osm/segment.hpp | 1 - include/osmium/osm/tag.hpp | 60 +- include/osmium/osm/timestamp.hpp | 83 +- include/osmium/osm/types.hpp | 20 +- include/osmium/osm/types_from_string.hpp | 21 +- include/osmium/osm/way.hpp | 5 +- include/osmium/relations/collector.hpp | 53 +- .../osmium/relations/detail/member_meta.hpp | 4 +- .../osmium/relations/detail/relation_meta.hpp | 4 +- include/osmium/tags/filter.hpp | 19 +- include/osmium/tags/regex_filter.hpp | 2 +- include/osmium/tags/taglist.hpp | 2 +- include/osmium/thread/pool.hpp | 27 +- include/osmium/thread/queue.hpp | 64 +- include/osmium/thread/sorted_queue.hpp | 159 -- include/osmium/thread/util.hpp | 2 + include/osmium/util/config.hpp | 17 +- include/osmium/util/delta.hpp | 51 +- include/osmium/util/double.hpp | 3 +- include/osmium/util/file.hpp | 82 +- include/osmium/util/iterator.hpp | 18 +- include/osmium/util/memory_mapping.hpp | 25 +- include/osmium/util/misc.hpp | 52 + include/osmium/util/options.hpp | 9 +- include/osmium/util/progress_bar.hpp | 179 ++ include/osmium/util/string.hpp | 2 +- include/osmium/util/timer.hpp | 98 + include/osmium/util/verbose_output.hpp | 8 +- include/osmium/version.hpp | 42 + include/protozero/byteswap.hpp | 6 +- include/protozero/exception.hpp | 8 +- include/protozero/iterators.hpp | 373 ++++ include/protozero/pbf_builder.hpp | 20 +- include/protozero/pbf_message.hpp | 10 +- include/protozero/pbf_reader.hpp | 785 +++----- include/protozero/pbf_writer.hpp | 244 ++- include/protozero/types.hpp | 174 +- include/protozero/varint.hpp | 135 +- include/protozero/version.hpp | 21 +- osmium.imp | 9 +- test/CMakeLists.txt | 44 +- test/README | 8 - test/data-tests/include/common.hpp | 6 +- test/data-tests/testdata-multipolygon.cpp | 60 +- test/data-tests/testdata-overview.cpp | 23 +- test/data-tests/testdata-testcases.cpp | 6 +- test/data-tests/testdata-xml.cpp | 118 +- test/include/catch.hpp | 620 ++++-- test/t/area/test_node_ref_segment.cpp | 197 +- test/t/basic/test_area.cpp | 78 + test/t/basic/test_entity_bits.cpp | 1 + test/t/basic/test_location.cpp | 220 ++- test/t/basic/test_node.cpp | 68 +- test/t/basic/test_object_comparisons.cpp | 114 +- test/t/basic/test_timestamp.cpp | 44 + test/t/basic/test_types_from_string.cpp | 14 +- test/t/basic/test_way.cpp | 2 +- test/t/builder/test_attr.cpp | 4 +- test/t/geom/test_geojson.cpp | 22 +- test/t/geom/test_geos.cpp | 17 +- test/t/geom/test_ogr.cpp | 4 + test/t/geom/test_tile.cpp | 168 +- test/t/geom/test_wkb.cpp | 164 +- test/t/geom/test_wkt.cpp | 197 +- test/t/index/test_file_based_index.cpp | 155 ++ test/t/index/test_id_to_location.cpp | 36 +- test/t/io/test_opl_parser.cpp | 1075 ++++++++++ .../test_reader_with_mock_decompression.cpp | 10 +- test/t/io/test_reader_with_mock_parser.cpp | 6 +- test/t/io/test_string_table.cpp | 40 +- test/t/io/test_writer.cpp | 9 +- .../io/test_writer_with_mock_compression.cpp | 5 +- test/t/io/test_writer_with_mock_encoder.cpp | 3 +- test/t/tags/test_filter.cpp | 6 +- test/t/tags/test_tag_list.cpp | 8 +- test/t/util/test_cast_with_assert.cpp | 2 +- test/t/util/test_delta.cpp | 22 - test/t/util/test_memory_mapping.cpp | 13 +- 208 files changed, 11590 insertions(+), 4051 deletions(-) create mode 100644 NOTES_FOR_DEVELOPERS.md create mode 100644 build-appveyor.bat create mode 100644 build-local.bat create mode 100644 examples/README.md delete mode 100644 examples/osmium_create_node_cache.cpp create mode 100644 examples/osmium_location_cache_create.cpp create mode 100644 examples/osmium_location_cache_use.cpp create mode 100755 examples/osmium_pub_names.cpp create mode 100644 examples/osmium_read_with_progress.cpp create mode 100755 examples/osmium_road_length.cpp create mode 100644 examples/osmium_tiles.cpp delete mode 100644 examples/osmium_use_node_cache.cpp create mode 100644 include/osmium/area/detail/vector.hpp create mode 100644 include/osmium/area/stats.hpp create mode 100644 include/osmium/io/detail/opl_input_format.hpp create mode 100644 include/osmium/io/detail/opl_parser_functions.hpp create mode 100644 include/osmium/io/opl_input.hpp create mode 100644 include/osmium/opl.hpp delete mode 100644 include/osmium/thread/sorted_queue.hpp create mode 100644 include/osmium/util/misc.hpp create mode 100644 include/osmium/util/progress_bar.hpp create mode 100644 include/osmium/util/timer.hpp create mode 100644 include/osmium/version.hpp create mode 100644 include/protozero/iterators.hpp create mode 100644 test/t/basic/test_area.cpp create mode 100644 test/t/index/test_file_based_index.cpp create mode 100644 test/t/io/test_opl_parser.cpp diff --git a/.gitignore b/.gitignore index 50139035b..79e5e65d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.swp .ycm_extra_conf.pyc +/build +/libosmium-deps diff --git a/.travis.yml b/.travis.yml index ac0d270e2..991811260 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,24 +4,34 @@ # #----------------------------------------------------------------------------- -language: cpp +language: generic sudo: false +cache: + directories: + - $HOME/.ccache + +env: + global: + - CCACHE_TEMPDIR=/tmp/.ccache-temp + - CCACHE_COMPRESS=1 + - CASHER_TIME_OUT=1000 + matrix: include: # 1/ Linux Clang Builds - os: linux - compiler: clang + compiler: linux-clang35-release addons: apt: sources: ['llvm-toolchain-precise-3.5', 'ubuntu-toolchain-r-test', 'boost-latest'] - packages: ['clang-3.5', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin'] + packages: ['clang-3.5', 'cmake', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin'] env: COMPILER='clang++-3.5' BUILD_TYPE='Release' - os: linux - compiler: clang + compiler: linux-clang35-dev addons: apt: sources: ['llvm-toolchain-precise-3.5', 'ubuntu-toolchain-r-test', 'boost-latest'] @@ -30,24 +40,7 @@ matrix: - os: linux - compiler: clang - addons: - apt: - sources: ['llvm-toolchain-precise-3.6', 'ubuntu-toolchain-r-test', 'boost-latest'] - packages: ['clang-3.6', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin'] - env: COMPILER='clang++-3.6' BUILD_TYPE='Release' - - - os: linux - compiler: clang - addons: - apt: - sources: ['llvm-toolchain-precise-3.6', 'ubuntu-toolchain-r-test', 'boost-latest'] - packages: ['clang-3.6', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin'] - env: COMPILER='clang++-3.6' BUILD_TYPE='Dev' - - - - os: linux - compiler: clang + compiler: linux-clang37-release addons: apt: sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test', 'boost-latest'] @@ -55,7 +48,7 @@ matrix: env: COMPILER='clang++-3.7' BUILD_TYPE='Release' - os: linux - compiler: clang + compiler: linux-clang37-dev addons: apt: sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test', 'boost-latest'] @@ -63,9 +56,26 @@ matrix: env: COMPILER='clang++-3.7' BUILD_TYPE='Dev' + - os: linux + compiler: linux-clang38-release + addons: + apt: + sources: ['llvm-toolchain-precise-3.8', 'ubuntu-toolchain-r-test', 'boost-latest'] + packages: ['clang-3.8', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin'] + env: COMPILER='clang++-3.8' BUILD_TYPE='Release' + + - os: linux + compiler: linux-clang38-dev + addons: + apt: + sources: ['llvm-toolchain-precise-3.8', 'ubuntu-toolchain-r-test', 'boost-latest'] + packages: ['clang-3.8', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin'] + env: COMPILER='clang++-3.8' BUILD_TYPE='Dev' + + # 2/ Linux GCC Builds - os: linux - compiler: gcc + compiler: linux-gcc48-release addons: apt: sources: ['ubuntu-toolchain-r-test', 'boost-latest'] @@ -73,7 +83,7 @@ matrix: env: COMPILER='g++-4.8' COMPILER_FLAGS='-Wno-return-type' BUILD_TYPE='Release' - os: linux - compiler: gcc + compiler: linux-gcc48-dev addons: apt: sources: ['ubuntu-toolchain-r-test', 'boost-latest'] @@ -82,7 +92,7 @@ matrix: - os: linux - compiler: gcc + compiler: linux-gcc49-release addons: apt: sources: ['ubuntu-toolchain-r-test', 'boost-latest'] @@ -90,7 +100,7 @@ matrix: env: COMPILER='g++-4.9' BUILD_TYPE='Release' - os: linux - compiler: gcc + compiler: linux-gcc49-dev addons: apt: sources: ['ubuntu-toolchain-r-test', 'boost-latest'] @@ -99,7 +109,7 @@ matrix: - os: linux - compiler: gcc + compiler: linux-gcc50-release addons: apt: sources: ['ubuntu-toolchain-r-test', 'boost-latest'] @@ -107,7 +117,7 @@ matrix: env: COMPILER='g++-5' BUILD_TYPE='Release' - os: linux - compiler: gcc + compiler: linux-gcc50-dev addons: apt: sources: ['ubuntu-toolchain-r-test', 'boost-latest'] @@ -118,25 +128,25 @@ matrix: # 3/ OSX Clang Builds - os: osx osx_image: xcode6.4 - compiler: clang - env: COMPILER='clang++' BUILD_TYPE='Dev' + compiler: xcode64-clang-release + env: COMPILER='clang++' BUILD_TYPE='Release' - os: osx osx_image: xcode6.4 - compiler: clang - env: COMPILER='clang++' BUILD_TYPE='Release' - - - - os: osx - osx_image: xcode7 - compiler: clang + compiler: xcode64-clang-dev env: COMPILER='clang++' BUILD_TYPE='Dev' + - os: osx osx_image: xcode7 - compiler: clang + compiler: xcode7-clang-release env: COMPILER='clang++' BUILD_TYPE='Release' + - os: osx + osx_image: xcode7 + compiler: xcode7-clang-dev + env: COMPILER='clang++' BUILD_TYPE='Dev' + install: - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" @@ -145,15 +155,15 @@ install: - | if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then brew remove gdal - brew install cmake boost google-sparsehash gdal + brew install cmake boost google-sparsehash gdal || true fi + - cmake --version before_script: - cd ${TRAVIS_BUILD_DIR} - mkdir build && cd build - - CXX=${COMPILER} CXXFLAGS=${COMPILER_FLAGS} cmake -LA .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DOSM_TESTDATA="${TRAVIS_BUILD_DIR}/deps/osm-testdata" + - CXX=${COMPILER} CXXFLAGS=${COMPILER_FLAGS} cmake -LA .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_WITH_CCACHE=1 -DOSM_TESTDATA="${TRAVIS_BUILD_DIR}/deps/osm-testdata" script: - - make VERBOSE=1 - - ctest --output-on-failure + - make VERBOSE=1 && ctest --output-on-failure diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 2b8730616..6fc19376d 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -29,6 +29,11 @@ flags = [ '-x', 'c++', +# workaround for https://github.com/Valloric/YouCompleteMe/issues/303 +# also see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=800618 +'-isystem', +'/usr/lib/ycmd/clang_includes/', + # libosmium include dirs '-I%s/include' % basedir, '-I%s/test/include' % basedir, diff --git a/CHANGELOG.md b/CHANGELOG.md index 394644890..0d132ef59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,140 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [2.9.0] - 2016-09-15 + +### Added + +- Support for reading OPL files. +- For diff output OSM objects in buffers can be marked as only in one or the + other file. The OPL and debug output formats support diff output based on + this. +- Add documentation and range checks to `Tile` struct. +- More documentation. +- More examples and more extensive comments on examples. +- Support for a progress report in `osmium::io::Reader()` and a `ProgressBar` + utility class to use it. +- New `OSMObject::set_timestamp(const char*)` function. + +### Changed + +- Parse coordinates in scientific notations ourselves. +- Updated included protozero version to 1.4.2. +- Lots of one-argument constructors are now explicit. +- Timestamp parser now uses our own implementation instead of strptime. + This is faster and independant of locale settings. +- More cases of invalid areas with duplicate segments are reported as + errors. + +### Fixed + +- Fixed a problem limiting cache file sizes on Windows to 32 bit. +- Fixed includes. +- Exception messages for invalid areas do not report "area contains no rings" + any more, but "invalid area". + + +## [2.8.0] - 2016-08-04 + +### Added + +- EWKT support. +- Track `pop` type calls and queue underruns when `OSMIUM_DEBUG_QUEUE_SIZE` + environment variable is set. + +### Changed + +- Switched to newest protozero v1.4.0. This should deliver some speedups + when parsing PBF files. This also removes the DeltaEncodeIterator class, + which isn't needed any more. +- Uses `std::unordered_map` instead of `std::map` in PBF string table code + speeding up writing of PBF files considerably. +- Uses less memory when writing PBF files (smaller string table by default). +- Removes dependency on sparsehash and boost program options libraries for + examples. +- Cleaned up threaded queue code. + +### Fixed + +- A potentially very bad bug was fixed: When there are many and/or long strings + in tag keys and values and/or user names and/or relation roles, the string + table inside a PBF block would overflow. I have never seen this happen for + normal OSM data, but that doesn't mean it can't happen. The result is that + the strings will all be mixed up, keys for values, values for user names or + whatever. +- Automatically set correct SRID when creating WKB and GEOS geometries. + Note that this changes the behaviour of libosmium when creating GEOS + geometries. Before we created them with -1 as SRID unless set otherwise. + Manual setting of the SRID on the GEOSGeometryFactory is now deprecated. +- Allow coordinates of nodes in scientific notation when reading XML files. + This shouldn't be used really, but sometimes you can find them. + + +## [2.7.2] - 2016-06-08 + +### Changed + +- Much faster output of OSM files in XML, OPL, or debug formats. + +### Fixed + +- Parsing and output of coordinates now faster and always uses decimal dot + independant of locale setting. +- Do not output empty discussion elements in changeset XML output. +- Data corruption regression in mmap based indexes. + + +## [2.7.1] - 2016-06-01 + +### Fixes + +- Update version number in version.hpp. + + +## [2.7.0] - 2016-06-01 + +### Added + +- New functions for iterating over specific item types in buffers + (`osmium::memory::Buffer::select()`), over specific subitems + (`osmium::OSMObject::subitems()`), and for iterating over all rings of + an area (`osmium::Areas::outer_rings(`), `inner_rings()`). +- Debug output optionally prints CRC32 when `add_crc32` file option is set. + +### Changed + +- XML parser will not allow any XML entities which are usually not used in OSM + files anyway. This can help avoiding DOS attacks. +- Removed SortedQueue implementation which was never used. +- Also incorporate Locations in NodeRefs into CRC32 checksums. This means + all checksums will be different compared to earlier versions of libosmium. +- The completely new algorithm for assembling multipolygons is much faster, + has better error reporting, generates statistics and can build more complex + multipolygons correctly. The ProblemReporter classes have changed to make + this happen, if you have written your own, you have to fix it. +- Sparse node location stores are now only sorted if needed, ie. when nodes + come in unordered. + +### Fixed + +- Output operator for Location shows full precision. +- Undefined behaviour in WKB writer and `types_from_string()` function. +- Fix unsigned overflow in pool.hpp. +- OSM objects are now ordered by type (nodes, then ways, then relations), + then ID, then version, then timestamp. Ordering by timestamp is normally + not necessary, because there can't be two objects with same type, ID, and + version but different timestamp. But this can happen when diffs are + created from OSM extracts, so we check for this here. This change also + makes sure IDs are always ordered by absolute IDs, positives first, so + order is 0, 1, -1, 2, -2, ... +- Data corruption bug fixed in disk based indexes (used for the node + location store for instance). This only affected you, if you created + and index, closed it, and re-opened it (possibly in a different process) + and if there were missing nodes. If you looked up those nodes, you got + location (0,0) back instead of an error. +- Memory corruption bug showing up with GDAL 2. + + ## [2.6.1] - 2016-02-22 ### Added @@ -276,7 +410,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). Doxygen (up to version 1.8.8). This version contains a workaround to fix this. -[unreleased]: https://github.com/osmcode/libosmium/compare/v2.6.1...HEAD +[unreleased]: https://github.com/osmcode/libosmium/compare/v2.9.0...HEAD +[2.9.0]: https://github.com/osmcode/libosmium/compare/v2.8.0...v2.9.0 +[2.8.0]: https://github.com/osmcode/libosmium/compare/v2.7.2...v2.8.0 +[2.7.2]: https://github.com/osmcode/libosmium/compare/v2.7.1...v2.7.2 +[2.7.1]: https://github.com/osmcode/libosmium/compare/v2.7.0...v2.7.1 +[2.7.0]: https://github.com/osmcode/libosmium/compare/v2.6.1...v2.7.0 [2.6.1]: https://github.com/osmcode/libosmium/compare/v2.6.0...v2.6.1 [2.6.0]: https://github.com/osmcode/libosmium/compare/v2.5.4...v2.6.0 [2.5.4]: https://github.com/osmcode/libosmium/compare/v2.5.3...v2.5.4 diff --git a/CMakeLists.txt b/CMakeLists.txt index bc9b12ea5..095137b39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev;Cover project(libosmium) set(LIBOSMIUM_VERSION_MAJOR 2) -set(LIBOSMIUM_VERSION_MINOR 6) -set(LIBOSMIUM_VERSION_PATCH 1) +set(LIBOSMIUM_VERSION_MINOR 9) +set(LIBOSMIUM_VERSION_PATCH 0) set(LIBOSMIUM_VERSION "${LIBOSMIUM_VERSION_MAJOR}.${LIBOSMIUM_VERSION_MINOR}.${LIBOSMIUM_VERSION_PATCH}") @@ -61,6 +61,27 @@ option(INSTALL_UTFCPP "also install utfcpp headers" OFF) option(WITH_PROFILING "add flags needed for profiling" OFF) +#----------------------------------------------------------------------------- +# +# CCache support +# +#----------------------------------------------------------------------------- + +option(BUILD_WITH_CCACHE "build using ccache" OFF) + +if(BUILD_WITH_CCACHE) + find_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "CCACHE_CPP2=1 ${CCACHE_PROGRAM}") + + # workaround for some clang versions + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_definitions(-Qunused-arguments) + endif() + endif() +endif() + + #----------------------------------------------------------------------------- # # Coverage support diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1064b94de..a054f362e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,143 +1,12 @@ -# Notes for Developers +Some rules for contributing to this project: -Read this if you want to contribute to Libosmium. +* Please open a separate issue for each problem, question, or comment you have. + Do not re-use existing issues for other topics, even if they are similar. This + keeps issues small and manageable and makes it much easier to follow through + and make sure each problem is taken care of. - -## Versioning - -Osmium is currently considered in beta and doesn't use versioning yet. Proper -versions will be introduced as soon as it is somewhat stable. - - -## Namespace - -All Osmium code MUST be in the `osmium` namespace or one of its sub-namespaces. - - -## Include-Only - -Osmium is a include-only library. You can't compile the library itself. There -is no libosmium.so. - -One drawback ist that you can't have static data in classes, because there -is no place to put this data. - -All free functions must be declared `inline`. - - -## Coding Conventions - -These coding conventions have been changing over time and some code is still -different. - -* All include files have `#ifdef` guards around them, macros are the path name - in all uppercase where the slashes (`/`) have been changed to underscore (`_`). -* Class names begin with uppercase chars and use CamelCase. Smaller helper - classes are usually defined as struct and have lowercase names. -* Macros (and only macros) are all uppercase. Use macros sparingly, usually - a simple (maybe constexpr) inline function is better. Undef macros after use - if possible. -* Macros should only be used for controlling which parts of the code should be - included when compiling or to avoid major code repetitions. -* Variables, attributes, and function names are lowercase with - `underscores_between_words`. -* Class attribute names start with `m_` (member). -* Use `descriptive_variable_names`, exceptions are well-established conventions - like `i` for a loop variable. Iterators are usually called `it`. -* Declare variables where they are first used (C++ style), not at the beginning - of a function (old C style). -* Names from external namespaces (even `std`) are always mentioned explicitly. - Do not use `using` (except for `std::swap`). This way we can't even by - accident pollute the namespace of the code using Osmium. -* Always use the standard swap idiom: `using std::swap; swap(foo, bar);`. -* `#include` directives appear in three "blocks" after the copyright notice. - The blocks are separated by blank lines. First block contains `#include`s for - standard C/C++ includes, second block for any external libs used, third - block for osmium internal includes. Within each block `#include`s are usually - sorted by path name. All `#include`s use `<>` syntax not `""`. -* Names not to be used from outside the library should be in a namespace - called `detail` under the namespace where they would otherwise appear. If - whole include files are never meant to be included from outside they should - be in a subdirectory called `detail`. -* All files have suffix `.hpp`. -* Closing } of all classes and namespaces should have a trailing comment - with the name of the class/namespace. -* All constructors with one (or more arguments if they have a default) should - be declared "explicit" unless there is a reason for them not to be. Document - that reason. -* If a class has any of the special methods (copy/move constructor/assigment, - destructor) it should have all of them, possibly marking them as default or - deleted. -* Typedefs have `names_like_this_type` which end in `_type`. Typedefs should - use the new `using foo_type = bar` syntax instead of the old - `typedef bar foo_type`. -* Template parameters are single uppercase letters or start with uppercase `T` - and use CamelCase. -* Always use `typename` in templates, not `class`: `template `. -* The ellipsis in variadic template never has a space to the left of it and - always has a space to the right: `template ` etc. - -Keep to the indentation and other styles used in the code. Use `make indent` -in the toplevel directory to fix indentation and styling. It calls `astyle` -with the right parameters. This program is in the `astyle` Debian package. - - -## C++11 - -Osmium uses C++11 and you can use its features such as auto, lambdas, -threading, etc. There are a few features we do not use, because even modern -compilers don't support them yet. This list might change as we get more data -about which compilers support which feature and what operating system versions -or distributions have which versions of these compilers installed. - -GCC 4.6 - too old, not supported (Ubuntu 12.04 LTS) -GCC 4.7.2 - can probably not be supported (Debian wheezy) -GCC 4.7.3 - probably works -GCC 4.8 - works and is supported from here on -clang 3.0 - too old, not supported (Debian wheezy, Ubuntu 12.04 LTS) -clang 3.2 - probably works -clang 3.5 - works and is supported from here on - -Use `include/osmium/util/compatibility.hpp` if there are compatibility problems -between compilers due to different C++11 support. - - -## Checking your code - -The Osmium makefiles use pretty draconian warning options for the compiler. -This is good. Code MUST never produce any warnings, even with those settings. -If absolutely necessary pragmas can be used to disable certain warnings in -specific areas of the code. - -If the static code checker `cppcheck` is installed, the CMake configuration -will add a new build target `cppcheck` that will check all `.cpp` and `.hpp` -files. Cppcheck finds some bugs that gcc/clang doesn't. But take the result -with a grain of salt, it also sometimes produces wrong warnings. - -Set `BUILD_HEADERS=ON` in the CMake config to enable compiling all include -files on their own to check whether dependencies are all okay. All include -files MUST include all other include files they depend on. - -Call `cmake/iwyu.sh` to check for proper includes and forward declarations. -This uses the clang-based `include-what-you-use` program. Note that it does -produce some false reports and crashes often. The `osmium.imp` file can be -used to define mappings for iwyu. See the IWYU tool at -. - - -## Testing - -There are a unit tests using the Catch Unit Test Framework in the `test` -directory and some data tests in `test/osm-testdata`. They are built by the -default cmake config. Run `ctest` to run them. Many more tests are needed. - - -## Documenting the code - -All namespaces, classes, functions, attributes, etc. should be documented. - -Osmium uses the Doxygen (www.doxygen.org) source code documentation system. -If it is installed, the CMake configuration will add a new build target, so -you can build it with `make doc`. +* We'd love for you to send pull requests for fixes you have made or new features + you have added. Please read the [notes for developers](NOTES_FOR_DEVELOPERS.md) + beforehand which contain some coding guidelines. diff --git a/NOTES_FOR_DEVELOPERS.md b/NOTES_FOR_DEVELOPERS.md new file mode 100644 index 000000000..3c1f73b78 --- /dev/null +++ b/NOTES_FOR_DEVELOPERS.md @@ -0,0 +1,143 @@ + +# Notes for Developers + +Read this if you want to contribute to Libosmium. + + +## Namespace + +All Osmium code MUST be in the `osmium` namespace or one of its sub-namespaces. + + +## Include-Only + +Osmium is a include-only library. You can't compile the library itself. There +is no libosmium.so. + +One drawback ist that you can't have static data in classes, because there +is no place to put this data. + +All free functions must be declared `inline`. + + +## Coding Conventions + +These coding conventions have been changing over time and some code is still +different. + +* All include files have `#ifdef` guards around them, macros are the path name + in all uppercase where the slashes (`/`) have been changed to underscore (`_`). +* Class names begin with uppercase chars and use CamelCase. Smaller helper + classes are usually defined as struct and have lowercase names. +* Macros (and only macros) are all uppercase. Use macros sparingly, usually + a simple (maybe constexpr) inline function is better. Undef macros after use + if possible. +* Macros should only be used for controlling which parts of the code should be + included when compiling or to avoid major code repetitions. +* Variables, attributes, and function names are lowercase with + `underscores_between_words`. +* Class attribute names start with `m_` (member). +* Use `descriptive_variable_names`, exceptions are well-established conventions + like `i` for a loop variable. Iterators are usually called `it`. +* Declare variables where they are first used (C++ style), not at the beginning + of a function (old C style). +* Names from external namespaces (even `std`) are always mentioned explicitly. + Do not use `using` (except for `std::swap`). This way we can't even by + accident pollute the namespace of the code using Osmium. +* Always use the standard swap idiom: `using std::swap; swap(foo, bar);`. +* `#include` directives appear in three "blocks" after the copyright notice. + The blocks are separated by blank lines. First block contains `#include`s for + standard C/C++ includes, second block for any external libs used, third + block for osmium internal includes. Within each block `#include`s are usually + sorted by path name. All `#include`s use `<>` syntax not `""`. +* Names not to be used from outside the library should be in a namespace + called `detail` under the namespace where they would otherwise appear. If + whole include files are never meant to be included from outside they should + be in a subdirectory called `detail`. +* All files have suffix `.hpp`. +* Closing } of all classes and namespaces should have a trailing comment + with the name of the class/namespace. +* All constructors with one (or more arguments if they have a default) should + be declared "explicit" unless there is a reason for them not to be. Document + that reason. +* If a class has any of the special methods (copy/move constructor/assigment, + destructor) it should have all of them, possibly marking them as default or + deleted. +* Typedefs have `names_like_this_type` which end in `_type`. Typedefs should + use the new `using foo_type = bar` syntax instead of the old + `typedef bar foo_type`. +* Template parameters are single uppercase letters or start with uppercase `T` + and use CamelCase. +* Always use `typename` in templates, not `class`: `template `. +* The ellipsis in variadic template never has a space to the left of it and + always has a space to the right: `template ` etc. + +Keep to the indentation and other styles used in the code. Use `make indent` +in the toplevel directory to fix indentation and styling. It calls `astyle` +with the right parameters. This program is in the `astyle` Debian package. + + +## C++11 + +Osmium uses C++11 and you can use its features such as auto, lambdas, +threading, etc. There are a few features we do not use, because even modern +compilers don't support them yet. This list might change as we get more data +about which compilers support which feature and what operating system versions +or distributions have which versions of these compilers installed. + +GCC 4.6 - too old, not supported (Ubuntu 12.04 LTS) +GCC 4.7.2 - can probably not be supported (Debian wheezy) +GCC 4.7.3 - probably works +GCC 4.8 - works and is supported from here on +clang 3.0 - too old, not supported (Debian wheezy, Ubuntu 12.04 LTS) +clang 3.2 - probably works +clang 3.5 - works and is supported from here on + +Use `include/osmium/util/compatibility.hpp` if there are compatibility problems +between compilers due to different C++11 support. + + +## Operating systems + +Usually all code must work on Linux, OSX, and Windows. Execptions are allowed +for some minor functionality, but please discuss this first. + + +## Checking your code + +The Osmium makefiles use pretty draconian warning options for the compiler. +This is good. Code MUST never produce any warnings, even with those settings. +If absolutely necessary pragmas can be used to disable certain warnings in +specific areas of the code. + +If the static code checker `cppcheck` is installed, the CMake configuration +will add a new build target `cppcheck` that will check all `.cpp` and `.hpp` +files. Cppcheck finds some bugs that gcc/clang doesn't. But take the result +with a grain of salt, it also sometimes produces wrong warnings. + +Set `BUILD_HEADERS=ON` in the CMake config to enable compiling all include +files on their own to check whether dependencies are all okay. All include +files MUST include all other include files they depend on. + +Call `cmake/iwyu.sh` to check for proper includes and forward declarations. +This uses the clang-based `include-what-you-use` program. Note that it does +produce some false reports and crashes often. The `osmium.imp` file can be +used to define mappings for iwyu. See the IWYU tool at +. + + +## Testing + +There are a unit tests using the Catch Unit Test Framework in the `test` +directory and some data tests in `test/osm-testdata`. They are built by the +default cmake config. Run `ctest` to run them. Many more tests are needed. + + +## Documenting the code + +All namespaces, classes, functions, attributes, etc. should be documented. + +Osmium uses the Doxygen (www.doxygen.org) source code documentation system. +If it is installed, the CMake configuration will add a new build target, so +you can build it with `make doc`. + diff --git a/appveyor.yml b/appveyor.yml index 8244d98e7..7e6060a7f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,88 +22,5 @@ clone_folder: c:\projects\libosmium platform: x64 -install: - # show all available env vars - - set - - echo cmake on AppVeyor - - cmake -version - - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 - - set PATH=c:\projects\libosmium\cmake-3.1.0-win32-x86\bin;%PATH% - - set LODEPSDIR=c:\projects\libosmium\libosmium-deps - - set PROJ_LIB=%LODEPSDIR%\proj\share - - set GDAL_DATA=%LODEPSDIR%\gdal\data - #geos.dll - - set PATH=%LODEPSDIR%\geos\lib;%PATH% - #gdal.dll - - set PATH=%LODEPSDIR%\gdal\lib;%PATH% - #libexpat.dll - - set PATH=%LODEPSDIR%\expat\lib;%PATH% - #libtiff.dll - - set PATH=%LODEPSDIR%\libtiff\lib;%PATH% - #jpeg.dll - - set PATH=%LODEPSDIR%\jpeg\lib;%PATH% - #zlibwapi.dll - - set PATH=%LODEPSDIR%\zlib\lib;%PATH% - #convert backslashes in bzip2 path to forward slashes - #cmake cannot find it otherwise - - set LIBBZIP2=%LODEPSDIR%\bzip2\lib\libbz2.lib - - set LIBBZIP2=%LIBBZIP2:\=/% - - ps: Start-FileDownload https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/cmake-3.1.0-win32-x86.7z -FileName cm.7z - - ps: Start-FileDownload https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/libosmium-deps-win-14.0-x64.7z -FileName lodeps.7z - - 7z x cm.7z | %windir%\system32\find "ing archive" - - 7z x lodeps.7z | %windir%\system32\find "ing archive" - - echo %LODEPSDIR% - - dir %LODEPSDIR% - - echo our own cmake - - cmake -version - - cd c:\projects - - git clone --depth 1 https://github.com/osmcode/osm-testdata.git - build_script: - - cd c:\projects\libosmium - - mkdir build - - cd build - - echo %config% - # This will produce lots of LNK4099 warnings which can be ignored. - # Unfortunately they can't be disabled, see - # http://stackoverflow.com/questions/661606/visual-c-how-to-disable-specific-linker-warnings - - cmake -LA -G "Visual Studio 14 Win64" - -DOsmium_DEBUG=TRUE - -DCMAKE_BUILD_TYPE=%config% - -DBUILD_HEADERS=OFF - -DBOOST_ROOT=%LODEPSDIR%\boost - -DBoost_PROGRAM_OPTIONS_LIBRARY=%LODEPSDIR%\boost\lib\libboost_program_options-vc140-mt-1_58.lib - -DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib - -DBZIP2_LIBRARY_RELEASE=%LIBBZIP2% - -DCMAKE_PREFIX_PATH=%LODEPSDIR%\zlib;%LODEPSDIR%\expat;%LODEPSDIR%\bzip2;%LODEPSDIR%\geos;%LODEPSDIR%\gdal;%LODEPSDIR%\proj;%LODEPSDIR%\sparsehash;%LODEPSDIR%\wingetopt - .. - - msbuild libosmium.sln /p:Configuration=%config% /toolsversion:14.0 /p:Platform=x64 /p:PlatformToolset=v140 - #- cmake .. -LA -G "NMake Makefiles" - # -DOsmium_DEBUG=TRUE - # -DCMAKE_BUILD_TYPE=%config% - # -DBOOST_ROOT=%LODEPSDIR%\boost - # -DBoost_PROGRAM_OPTIONS_LIBRARY=%LODEPSDIR%\boost\lib\libboost_program_options-vc140-mt-1_57.lib - # -DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib - # -DZLIB_INCLUDE_DIR=%LODEPSDIR%\zlib\include - # -DEXPAT_LIBRARY=%LODEPSDIR%\expat\lib\libexpat.lib - # -DEXPAT_INCLUDE_DIR=%LODEPSDIR%\expat\include - # -DBZIP2_LIBRARIES=%LIBBZIP2% - # -DBZIP2_INCLUDE_DIR=%LODEPSDIR%\bzip2\include - # -DGDAL_LIBRARY=%LODEPSDIR%\gdal\lib\gdal_i.lib - # -DGDAL_INCLUDE_DIR=%LODEPSDIR%\gdal\include - # -DGEOS_LIBRARY=%LODEPSDIR%\geos\lib\geos.lib - # -DGEOS_INCLUDE_DIR=%LODEPSDIR%\geos\include - # -DPROJ_LIBRARY=%LODEPSDIR%\proj\lib\proj.lib - # -DPROJ_INCLUDE_DIR=%LODEPSDIR%\proj\include - # -DSPARSEHASH_INCLUDE_DIR=%LODEPSDIR%\sparsehash\include - # -DGETOPT_LIBRARY=%LODEPSDIR%\wingetopt\lib\wingetopt.lib - # -DGETOPT_INCLUDE_DIR=%LODEPSDIR%\wingetopt\include - #- nmake - -test_script: - # "-E testdata-overview" exempts one test we know fails on Appveyor - # because we currently don't have spatialite support. - - ctest --output-on-failure - -C %config% - -E testdata-overview - + - build-appveyor.bat diff --git a/benchmarks/osmium_benchmark_count.cpp b/benchmarks/osmium_benchmark_count.cpp index d50c53dc8..1a16275f0 100644 --- a/benchmarks/osmium_benchmark_count.cpp +++ b/benchmarks/osmium_benchmark_count.cpp @@ -5,7 +5,9 @@ */ #include +#include #include +#include #include #include @@ -35,12 +37,12 @@ struct CountHandler : public osmium::handler::Handler { int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; - exit(1); + std::exit(1); } - std::string input_filename = argv[1]; + const std::string input_filename{argv[1]}; - osmium::io::Reader reader(input_filename); + osmium::io::Reader reader{input_filename}; CountHandler handler; osmium::apply(reader, handler); diff --git a/benchmarks/osmium_benchmark_count_tag.cpp b/benchmarks/osmium_benchmark_count_tag.cpp index 8fa696a4e..6062ecceb 100644 --- a/benchmarks/osmium_benchmark_count_tag.cpp +++ b/benchmarks/osmium_benchmark_count_tag.cpp @@ -5,7 +5,9 @@ */ #include +#include #include +#include #include #include @@ -38,12 +40,12 @@ struct CountHandler : public osmium::handler::Handler { int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; - exit(1); + std::exit(1); } - std::string input_filename = argv[1]; + const std::string input_filename{argv[1]}; - osmium::io::Reader reader(input_filename); + osmium::io::Reader reader{input_filename}; CountHandler handler; osmium::apply(reader, handler); diff --git a/benchmarks/osmium_benchmark_index_map.cpp b/benchmarks/osmium_benchmark_index_map.cpp index 025782618..2cf550ca9 100644 --- a/benchmarks/osmium_benchmark_index_map.cpp +++ b/benchmarks/osmium_benchmark_index_map.cpp @@ -4,7 +4,9 @@ */ +#include #include +#include #include #include @@ -13,24 +15,24 @@ #include #include -typedef osmium::index::map::Map index_type; +using index_type = osmium::index::map::Map; -typedef osmium::handler::NodeLocationsForWays location_handler_type; +using location_handler_type = osmium::handler::NodeLocationsForWays; int main(int argc, char* argv[]) { if (argc != 3) { std::cerr << "Usage: " << argv[0] << " OSMFILE FORMAT\n"; - exit(1); + std::exit(1); } - std::string input_filename = argv[1]; - std::string location_store = argv[2]; + const std::string input_filename{argv[1]}; + const std::string location_store{argv[2]}; - osmium::io::Reader reader(input_filename); + osmium::io::Reader reader{input_filename}; const auto& map_factory = osmium::index::MapFactory::instance(); std::unique_ptr index = map_factory.create_map(location_store); - location_handler_type location_handler(*index); + location_handler_type location_handler{*index}; location_handler.ignore_errors(); osmium::apply(reader, location_handler); diff --git a/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp b/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp index 66e2a0bd9..e9f950c31 100644 --- a/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp +++ b/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -29,23 +31,23 @@ #include #include -typedef osmium::index::map::SparseMemArray static_index_type; -const std::string location_store="sparse_mem_array"; +using static_index_type = osmium::index::map::SparseMemArray; +const std::string location_store{"sparse_mem_array"}; -typedef osmium::index::map::Map dynamic_index_type; +using dynamic_index_type = osmium::index::map::Map; -typedef osmium::handler::NodeLocationsForWays static_location_handler_type; -typedef osmium::handler::NodeLocationsForWays dynamic_location_handler_type; +using static_location_handler_type = osmium::handler::NodeLocationsForWays; +using dynamic_location_handler_type = osmium::handler::NodeLocationsForWays; int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; - exit(1); + std::exit(1); } - std::string input_filename = argv[1]; + const std::string input_filename{argv[1]}; - osmium::memory::Buffer buffer = osmium::io::read_file(input_filename); + osmium::memory::Buffer buffer{osmium::io::read_file(input_filename)}; const auto& map_factory = osmium::index::MapFactory::instance(); @@ -67,20 +69,20 @@ int main(int argc, char* argv[]) { { // static index - osmium::memory::Buffer tmp_buffer(buffer.committed()); + osmium::memory::Buffer tmp_buffer{buffer.committed()}; for (const auto& item : buffer) { tmp_buffer.add_item(item); tmp_buffer.commit(); } static_index_type static_index; - static_location_handler_type static_location_handler(static_index); + static_location_handler_type static_location_handler{static_index}; - auto start = std::chrono::steady_clock::now(); + const auto start = std::chrono::steady_clock::now(); osmium::apply(tmp_buffer, static_location_handler); - auto end = std::chrono::steady_clock::now(); + const auto end = std::chrono::steady_clock::now(); - double duration = std::chrono::duration(end-start).count(); + const double duration = std::chrono::duration(end-start).count(); if (duration < static_min) static_min = duration; if (duration > static_max) static_max = duration; @@ -89,21 +91,21 @@ int main(int argc, char* argv[]) { { // dynamic index - osmium::memory::Buffer tmp_buffer(buffer.committed()); + osmium::memory::Buffer tmp_buffer{buffer.committed()}; for (const auto& item : buffer) { tmp_buffer.add_item(item); tmp_buffer.commit(); } std::unique_ptr index = map_factory.create_map(location_store); - dynamic_location_handler_type dynamic_location_handler(*index); + dynamic_location_handler_type dynamic_location_handler{*index}; dynamic_location_handler.ignore_errors(); - auto start = std::chrono::steady_clock::now(); + const auto start = std::chrono::steady_clock::now(); osmium::apply(tmp_buffer, dynamic_location_handler); - auto end = std::chrono::steady_clock::now(); + const auto end = std::chrono::steady_clock::now(); - double duration = std::chrono::duration(end-start).count(); + const double duration = std::chrono::duration(end-start).count(); if (duration < dynamic_min) dynamic_min = duration; if (duration > dynamic_max) dynamic_max = duration; @@ -111,21 +113,21 @@ int main(int argc, char* argv[]) { } } - double static_avg = static_sum/runs; - double dynamic_avg = dynamic_sum/runs; + const double static_avg = static_sum/runs; + const double dynamic_avg = dynamic_sum/runs; std::cout << "static min=" << static_min << "ms avg=" << static_avg << "ms max=" << static_max << "ms\n"; std::cout << "dynamic min=" << dynamic_min << "ms avg=" << dynamic_avg << "ms max=" << dynamic_max << "ms\n"; - double rfactor = 100.0; - double diff_min = std::round((dynamic_min - static_min) * rfactor) / rfactor; - double diff_avg = std::round((dynamic_avg - static_avg) * rfactor) / rfactor; - double diff_max = std::round((dynamic_max - static_max) * rfactor) / rfactor; + const double rfactor = 100.0; + const double diff_min = std::round((dynamic_min - static_min) * rfactor) / rfactor; + const double diff_avg = std::round((dynamic_avg - static_avg) * rfactor) / rfactor; + const double diff_max = std::round((dynamic_max - static_max) * rfactor) / rfactor; - double prfactor = 10.0; - double percent_min = std::round((100.0 * diff_min / static_min) * prfactor) / prfactor; - double percent_avg = std::round((100.0 * diff_avg / static_avg) * prfactor) / prfactor; - double percent_max = std::round((100.0 * diff_max / static_max) * prfactor) / prfactor; + const double prfactor = 10.0; + const double percent_min = std::round((100.0 * diff_min / static_min) * prfactor) / prfactor; + const double percent_avg = std::round((100.0 * diff_avg / static_avg) * prfactor) / prfactor; + const double percent_max = std::round((100.0 * diff_max / static_max) * prfactor) / prfactor; std::cout << "difference:"; std::cout << " min=" << diff_min << "ms (" << percent_min << "%)"; diff --git a/benchmarks/osmium_benchmark_write_pbf.cpp b/benchmarks/osmium_benchmark_write_pbf.cpp index 869f3a8f8..632dd26eb 100644 --- a/benchmarks/osmium_benchmark_write_pbf.cpp +++ b/benchmarks/osmium_benchmark_write_pbf.cpp @@ -4,8 +4,9 @@ */ -#include -#include +#include +#include +#include #include #include @@ -13,16 +14,16 @@ int main(int argc, char* argv[]) { if (argc != 3) { std::cerr << "Usage: " << argv[0] << " INPUT-FILE OUTPUT-FILE\n"; - exit(1); + std::exit(1); } - std::string input_filename = argv[1]; - std::string output_filename = argv[2]; + std::string input_filename{argv[1]}; + std::string output_filename{argv[2]}; - osmium::io::Reader reader(input_filename); - osmium::io::File output_file(output_filename, "pbf"); + osmium::io::Reader reader{input_filename}; + osmium::io::File output_file{output_filename, "pbf"}; osmium::io::Header header; - osmium::io::Writer writer(output_file, header, osmium::io::overwrite::allow); + osmium::io::Writer writer{output_file, header, osmium::io::overwrite::allow}; while (osmium::memory::Buffer buffer = reader.read()) { writer(std::move(buffer)); diff --git a/build-appveyor.bat b/build-appveyor.bat new file mode 100644 index 000000000..27d387c33 --- /dev/null +++ b/build-appveyor.bat @@ -0,0 +1,123 @@ +@ECHO OFF +SETLOCAL +SET EL=0 + +ECHO ~~~~~~ %~f0 ~~~~~~ + +SET CUSTOM_CMAKE=cmake-3.6.2-win64-x64 +::show all available env vars +SET +ECHO cmake on AppVeyor +cmake -version + +ECHO activating VS cmd prompt && CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +SET lodir=%CD% +SET PATH=%lodir%\%CUSTOM_CMAKE%\bin;%PATH% +SET LODEPSDIR=%lodir%\libosmium-deps +SET PROJ_LIB=%LODEPSDIR%\proj\share +SET GDAL_DATA=%LODEPSDIR%\gdal\data +::gdal.dll +SET PATH=%LODEPSDIR%\gdal\lib;%PATH% +::geos.dll +SET PATH=%LODEPSDIR%\geos\lib;%PATH% +::libtiff.dll +SET PATH=%LODEPSDIR%\libtiff\lib;%PATH% +::jpeg.dll +SET PATH=%LODEPSDIR%\jpeg\lib;%PATH% +::libexpat.dll +SET PATH=%LODEPSDIR%\expat\lib;%PATH% +::zlibwapi.dll +SET PATH=%LODEPSDIR%\zlib\lib;%PATH% +::convert backslashes in bzip2 path to forward slashes +::cmake cannot find it otherwise +SET LIBBZIP2=%LODEPSDIR%\bzip2\lib\libbz2.lib +SET LIBBZIP2=%LIBBZIP2:\=/% + +IF NOT EXIST cm.7z ECHO downloading cmake %CUSTOM_CMAKE% ... && powershell Invoke-WebRequest https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/%CUSTOM_CMAKE%.7z -OutFile cm.7z +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +IF NOT EXIST lodeps.7z ECHO downloading binary dependencies... && powershell Invoke-WebRequest https://mapbox.s3.amazonaws.com/windows-builds/windows-build-deps/libosmium-deps-win-14.0-x64.7z -OutFile lodeps.7z +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +IF NOT EXIST %CUSTOM_CMAKE% ECHO extracting cmake... && 7z x cm.7z | %windir%\system32\find "ing archive" +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +IF NOT EXIST %LODEPSDIR% ECHO extracting binary dependencies... && 7z x lodeps.7z | %windir%\system32\find "ing archive" +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +ECHO %LODEPSDIR% +DIR %LODEPSDIR% +::TREE %LODEPSDIR% + +::powershell (Get-ChildItem $env:LODEPSDIR\boost\lib -Filter *boost*.dll)[0].BaseName.split('_')[-1] +FOR /F "tokens=1 usebackq" %%i in (`powershell ^(Get-ChildItem %LODEPSDIR%\boost\lib -Filter *boost*.dll^)[0].BaseName.split^('_'^)[-1]`) DO SET BOOST_VERSION=%%i +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +ECHO BOOST_VERSION^: %BOOST_VERSION% + +ECHO our own cmake +cmake -version + +CD %lodir%\.. + +IF NOT EXIST osm-testdata ECHO cloning osm-testdata && git clone --depth 1 https://github.com/osmcode/osm-testdata.git +IF %ERRORLEVEL% NEQ 0 GOTO ERROR +CD osm-testdata +git fetch +IF %ERRORLEVEL% NEQ 0 GOTO ERROR +git pull +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +CD %lodir% +IF EXIST build ECHO deleting build dir... && RD /Q /S build +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +MKDIR build +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +CD build +ECHO config^: %config% + +::This will produce lots of LNK4099 warnings which can be ignored. +::Unfortunately they can't be disabled, see +::http://stackoverflow.com/questions/661606/visual-c-how-to-disable-specific-linker-warnings +SET CMAKE_CMD=cmake .. ^ +-LA -G "Visual Studio 14 Win64" ^ +-DOsmium_DEBUG=TRUE ^ +-DCMAKE_BUILD_TYPE=%config% ^ +-DBUILD_HEADERS=OFF ^ +-DBOOST_ROOT=%LODEPSDIR%\boost ^ +-DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib ^ +-DBZIP2_LIBRARY_RELEASE=%LIBBZIP2% ^ +-DCMAKE_PREFIX_PATH=%LODEPSDIR%\zlib;%LODEPSDIR%\expat;%LODEPSDIR%\bzip2;%LODEPSDIR%\geos;%LODEPSDIR%\gdal;%LODEPSDIR%\proj;%LODEPSDIR%\sparsehash;%LODEPSDIR%\wingetopt + +ECHO calling^: %CMAKE_CMD% +%CMAKE_CMD% +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +msbuild libosmium.sln ^ +/p:Configuration=%config% ^ +/toolsversion:14.0 ^ +/p:Platform=x64 ^ +/p:PlatformToolset=v140 +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +ctest --output-on-failure ^ +-C %config% ^ +-E testdata-overview +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + + +GOTO DONE + +:ERROR +ECHO ~~~~~~ ERROR %~f0 ~~~~~~ +SET EL=%ERRORLEVEL% + +:DONE +IF %EL% NEQ 0 ECHO. && ECHO !!! ERRORLEVEL^: %EL% !!! && ECHO. +ECHO ~~~~~~ DONE %~f0 ~~~~~~ + +EXIT /b %EL% diff --git a/build-local.bat b/build-local.bat new file mode 100644 index 000000000..202d0b2fa --- /dev/null +++ b/build-local.bat @@ -0,0 +1,43 @@ +@ECHO OFF +SETLOCAL +SET EL=0 + +ECHO ~~~~~~ %~f0 ~~~~~~ + +ECHO. +ECHO build-local ["config=Dev"] +ECHO default config^: RelWithDebInfo +ECHO. + +SET platform=x64 +SET config=RelWithDebInfo + +:: OVERRIDE PARAMETERS >>>>>>>> +:NEXT-ARG + +IF '%1'=='' GOTO ARGS-DONE +ECHO setting %1 +SET %1 +SHIFT +GOTO NEXT-ARG + +:ARGS-DONE +::<<<<< OVERRIDE PARAMETERS + +WHERE 7z +IF %ERRORLEVEL% NEQ 0 ECHO 7zip not on PATH && GOTO ERROR + +CALL build-appveyor.bat +IF %ERRORLEVEL% NEQ 0 GOTO ERROR + +GOTO DONE + +:ERROR +ECHO ~~~~~~ ERROR %~f0 ~~~~~~ +SET EL=%ERRORLEVEL% + +:DONE +IF %EL% NEQ 0 ECHO. && ECHO !!! ERRORLEVEL^: %EL% !!! && ECHO. +ECHO ~~~~~~ DONE %~f0 ~~~~~~ + +EXIT /b %EL% diff --git a/cmake/iwyu.sh b/cmake/iwyu.sh index ceea106c3..b386159c4 100755 --- a/cmake/iwyu.sh +++ b/cmake/iwyu.sh @@ -6,6 +6,11 @@ # TODO: This script should be integrated with cmake in some way... # +# If these are set, the wrong compiler is used by iwyu and there will be +# errors about missing includes. +unset CC +unset CXX + cmdline="iwyu -Xiwyu --mapping_file=osmium.imp -std=c++11 -I include" log=build/iwyu.log diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a04a843f2..b47cfdcf7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -12,13 +12,17 @@ set(EXAMPLES area_test convert count - create_node_cache debug filter_discussions index + location_cache_create + location_cache_use + pub_names read + read_with_progress + road_length serdump - use_node_cache + tiles CACHE STRING "Example programs" ) @@ -28,7 +32,7 @@ set(EXAMPLES # Examples depending on wingetopt # #----------------------------------------------------------------------------- -set(GETOPT_EXAMPLES area_test convert serdump) +set(GETOPT_EXAMPLES area_test convert index serdump) if(NOT GETOPT_MISSING) foreach(example ${GETOPT_EXAMPLES}) list(APPEND EXAMPLE_LIBS_${example} ${GETOPT_LIBRARY}) @@ -42,36 +46,6 @@ else() endif() -#----------------------------------------------------------------------------- -# -# Examples depending on SparseHash -# -#----------------------------------------------------------------------------- -if(NOT SPARSEHASH_FOUND) - list(REMOVE_ITEM EXAMPLES area_test) - message(STATUS "Configuring examples - Skipping examples because Google SparseHash not found:") - message(STATUS " - osmium_area_test") -endif() - - -#----------------------------------------------------------------------------- -# -# Examples depending on Boost Program Options -# -#----------------------------------------------------------------------------- -unset(Boost_LIBRARIES) -unset(Boost_FOUND) -find_package(Boost 1.38 COMPONENTS program_options) - -if(Boost_PROGRAM_OPTIONS_FOUND) - list(APPEND EXAMPLE_LIBS_index ${Boost_PROGRAM_OPTIONS_LIBRARY}) -else() - list(REMOVE_ITEM EXAMPLES index) - message(STATUS "Configuring examples - Skipping examples because Boost program_options not found:") - message(STATUS " - osmium_index") -endif() - - #----------------------------------------------------------------------------- # # Configure examples diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..e29103292 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,32 @@ + +# Osmium example programs + +The programs in this directory are intended as examples for developers. They +contain extensive comments explaining what's going on. Note that the examples +only cover a small part of what Osmium can do, you should also read the +documentation and API documentation. + +All programs can be run without arguments and they will tell you how to run +them. + +## Very simple examples + +* `osmium_read` +* `osmium_count` +* `osmium_debug` +* `osmium_tiles` + +## Still reasonably simple examples + +* `osmium_filter_discussions` +* `osmium_convert` + +## More advanced examples + +* `osmium_area_test` + +## License + +The code in these example files is released into the Public Domain. Feel free +to copy the code and build on it. + diff --git a/examples/osmium_area_test.cpp b/examples/osmium_area_test.cpp index e9b7a18ad..030337454 100644 --- a/examples/osmium_area_test.cpp +++ b/examples/osmium_area_test.cpp @@ -1,48 +1,81 @@ /* - This is an example tool that creates multipolygons from OSM data - and dumps them to stdout. + EXAMPLE osmium_area_test + Create multipolygons from OSM data and dump them to stdout in one of two + formats: WKT or using the built-in Dump format. + + DEMONSTRATES USE OF: + * file input + * location indexes and the NodeLocationsForWays handler + * the MultipolygonCollector and Assembler to assemble areas (multipolygons) + * your own handler that works with areas (multipolygons) + * the WKTFactory to write geometries in WKT format + * the Dump handler + * the DynamicHandler + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + * osmium_count + * osmium_debug + + LICENSE The code in this example file is released into the Public Domain. */ -#include - -#include +#include // for std::exit +#include // for getopt_long +#include // for std::cout, std::cerr +// For assembling multipolygons #include #include + +// For the DynamicHandler class #include + +// For the WKT factory #include + +// For the Dump handler #include + +// For the NodeLocationForWays handler #include -#include -#include + +// Allow any format of input files (XML, PBF, ...) #include + +// For osmium::apply() #include -typedef osmium::index::map::Dummy index_neg_type; -typedef osmium::index::map::SparseMemArray index_pos_type; -typedef osmium::handler::NodeLocationsForWays location_handler_type; +// For the location index. There are different types of indexes available. +// This will work for small and medium sized input files. +#include +// The type of index used. This must match the include file above +using index_type = osmium::index::map::SparseMemArray; + +// The location handler always depends on the index type +using location_handler_type = osmium::handler::NodeLocationsForWays; + +// This handler writes all area geometries out in WKT (Well Known Text) format. class WKTDump : public osmium::handler::Handler { - osmium::geom::WKTFactory<> m_factory ; - - std::ostream& m_out; + // This factory is used to create a geometry in WKT format from OSM + // objects. The template parameter is empty here, because we output WGS84 + // coordinates, but could be used for a projection. + osmium::geom::WKTFactory<> m_factory; public: - WKTDump(std::ostream& out) : - m_out(out) { - } - + // This callback is called by osmium::apply for each area in the data. void area(const osmium::Area& area) { try { - m_out << m_factory.create_multipolygon(area) << "\n"; - } catch (osmium::geometry_error& e) { - m_out << "GEOMETRY ERROR: " << e.what() << "\n"; + std::cout << m_factory.create_multipolygon(area) << "\n"; + } catch (const osmium::geometry_error& e) { + std::cout << "GEOMETRY ERROR: " << e.what() << "\n"; } } @@ -65,8 +98,13 @@ int main(int argc, char* argv[]) { {0, 0, 0, 0} }; + // Initialize an empty DynamicHandler. Later it will be associated + // with one of the handlers. You can think of the DynamicHandler as + // a kind of "variant handler" or a "pointer handler" pointing to the + // real handler. osmium::handler::DynamicHandler handler; + // Read options from command line. while (true) { int c = getopt_long(argc, argv, "hwo", long_options, 0); if (c == -1) { @@ -76,54 +114,81 @@ int main(int argc, char* argv[]) { switch (c) { case 'h': print_help(); - exit(0); + std::exit(0); case 'w': - handler.set(std::cout); + handler.set(); break; case 'o': handler.set(std::cout); break; default: - exit(1); + std::exit(1); } } int remaining_args = argc - optind; if (remaining_args != 1) { std::cerr << "Usage: " << argv[0] << " [OPTIONS] OSMFILE\n"; - exit(1); + std::exit(1); } - osmium::io::File infile(argv[optind]); + osmium::io::File input_file{argv[optind]}; + // Configuration for the multipolygon assembler. Here the default settings + // are used, but you could change multiple settings. osmium::area::Assembler::config_type assembler_config; - osmium::area::MultipolygonCollector collector(assembler_config); + // Initialize the MultipolygonCollector. Its job is to collect all + // relations and member ways needed for each area. It then calls an + // instance of the osmium::area::Assembler class (with the given config) + // to actually assemble one area. + osmium::area::MultipolygonCollector collector{assembler_config}; + + // We read the input file twice. In the first pass, only relations are + // read and fed into the multipolygon collector. std::cerr << "Pass 1...\n"; - osmium::io::Reader reader1(infile, osmium::osm_entity_bits::relation); + osmium::io::Reader reader1{input_file, osmium::osm_entity_bits::relation}; collector.read_relations(reader1); reader1.close(); std::cerr << "Pass 1 done\n"; + // Output the amount of main memory used so far. All multipolygon relations + // are in memory now. std::cerr << "Memory:\n"; collector.used_memory(); - index_pos_type index_pos; - index_neg_type index_neg; - location_handler_type location_handler(index_pos, index_neg); - location_handler.ignore_errors(); // XXX + // The index storing all node locations. + index_type index; + // The handler that stores all node locations in the index and adds them + // to the ways. + location_handler_type location_handler{index}; + + // If a location is not available in the index, we ignore it. It might + // not be needed (if it is not part of a multipolygon relation), so why + // create an error? + location_handler.ignore_errors(); + + // On the second pass we read all objects and run them first through the + // node location handler and then the multipolygon collector. The collector + // will put the areas it has created into the "buffer" which are then + // fed through our "handler". std::cerr << "Pass 2...\n"; - osmium::io::Reader reader2(infile); + osmium::io::Reader reader2{input_file}; osmium::apply(reader2, location_handler, collector.handler([&handler](osmium::memory::Buffer&& buffer) { osmium::apply(buffer, handler); })); reader2.close(); std::cerr << "Pass 2 done\n"; + // Output the amount of main memory used so far. All complete multipolygon + // relations have been cleaned up. std::cerr << "Memory:\n"; collector.used_memory(); + // If there were multipolgyon relations in the input, but some of their + // members are not in the input file (which often happens for extracts) + // this will write the IDs of the incomplete relations to stderr. std::vector incomplete_relations = collector.get_incomplete_relations(); if (!incomplete_relations.empty()) { std::cerr << "Warning! Some member ways missing for these multipolygon relations:"; diff --git a/examples/osmium_convert.cpp b/examples/osmium_convert.cpp index 4f2ba3318..48a0823db 100644 --- a/examples/osmium_convert.cpp +++ b/examples/osmium_convert.cpp @@ -1,16 +1,32 @@ /* + EXAMPLE osmium_convert + Convert OSM files from one format into another. + DEMONSTRATES USE OF: + * file input and output + * file types + * Osmium buffers + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + + LICENSE The code in this example file is released into the Public Domain. */ -#include -#include +#include // for std::exit +#include // for std::exception +#include // for getopt_long +#include // for std::cout, std::cerr +#include // for std::string +// Allow any format of input files (XML, PBF, ...) #include +// Allow any format of output files (XML, PBF, ...) #include void print_help() { @@ -43,9 +59,13 @@ int main(int argc, char* argv[]) { {0, 0, 0, 0} }; + // Input and output format are empty by default. Later this will mean that + // the format should be taken from the input and output file suffix, + // respectively. std::string input_format; std::string output_format; + // Read options from command line. while (true) { int c = getopt_long(argc, argv, "dhf:t:", long_options, 0); if (c == -1) { @@ -55,7 +75,7 @@ int main(int argc, char* argv[]) { switch (c) { case 'h': print_help(); - exit(0); + std::exit(0); case 'f': input_format = optarg; break; @@ -63,49 +83,73 @@ int main(int argc, char* argv[]) { output_format = optarg; break; default: - exit(1); + std::exit(1); } } - std::string input; - std::string output; int remaining_args = argc - optind; if (remaining_args > 2) { - std::cerr << "Usage: " << argv[0] << " [OPTIONS] [INFILE [OUTFILE]]" << std::endl; - exit(1); - } else if (remaining_args == 2) { - input = argv[optind]; - output = argv[optind+1]; - } else if (remaining_args == 1) { - input = argv[optind]; + std::cerr << "Usage: " << argv[0] << " [OPTIONS] [INFILE [OUTFILE]]\n"; + std::exit(1); } - osmium::io::File infile(input, input_format); + // Get input file name from command line. + std::string input_file_name; + if (remaining_args >= 1) { + input_file_name = argv[optind]; + } - osmium::io::File outfile(output, output_format); + // Get output file name from command line. + std::string output_file_name; + if (remaining_args == 2) { + output_file_name = argv[optind+1]; + } - if (infile.has_multiple_object_versions() && !outfile.has_multiple_object_versions()) { + // This declares the input and output files using either the suffix of + // the file names or the format in the 2nd argument. It does not yet open + // the files. + osmium::io::File input_file{input_file_name, input_format}; + osmium::io::File output_file{output_file_name, output_format}; + + // Input and output files can be OSM data files (without history) or + // OSM history files. History files are detected if they use the '.osh' + // file suffix. + if ( input_file.has_multiple_object_versions() && + !output_file.has_multiple_object_versions()) { std::cerr << "Warning! You are converting from an OSM file with (potentially) several versions of the same object to one that is not marked as such.\n"; } - int exit_code = 0; - try { - osmium::io::Reader reader(infile); + // Initialize Reader + osmium::io::Reader reader{input_file}; + + // Get header from input file and change the "generator" setting to + // outselves. osmium::io::Header header = reader.header(); header.set("generator", "osmium_convert"); - osmium::io::Writer writer(outfile, header, osmium::io::overwrite::allow); + // Initialize Writer using the header from above and tell it that it + // is allowed to overwrite a possibly existing file. + osmium::io::Writer writer(output_file, header, osmium::io::overwrite::allow); + + // Copy the contents from the input to the output file one buffer at + // a time. This is much easier and faster than copying each object + // in the file. Buffers are moved around, so there is no cost for + // copying in memory. while (osmium::memory::Buffer buffer = reader.read()) { writer(std::move(buffer)); } + + // Explicitly close the writer and reader. Will throw an exception if + // there is a problem. If you wait for the destructor to close the writer + // and reader, you will not notice the problem, because destructors must + // not throw. writer.close(); reader.close(); - } catch (std::exception& e) { + } catch (const std::exception& e) { + // All exceptions used by the Osmium library derive from std::exception. std::cerr << e.what() << "\n"; - exit_code = 1; + std::exit(1); } - - return exit_code; } diff --git a/examples/osmium_count.cpp b/examples/osmium_count.cpp index baea153b8..7de1b6b7c 100644 --- a/examples/osmium_count.cpp +++ b/examples/osmium_count.cpp @@ -1,56 +1,95 @@ /* - This is a small tool that counts the number of nodes, ways, and relations in - the input file. + EXAMPLE osmium_count + Counts the number of nodes, ways, and relations in the input file. + + DEMONSTRATES USE OF: + * OSM file input + * your own handler + * the memory usage utility class + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + + LICENSE The code in this example file is released into the Public Domain. */ -#include -#include +#include // for std::uint64_t +#include // for std::exit +#include // for std::cout, std::cerr +// Allow any format of input files (XML, PBF, ...) #include + +// We want to use the handler interface #include + +// Utility class gives us access to memory usage information +#include + +// For osmium::apply() #include +// Handler derive from the osmium::handler::Handler base class. Usually you +// overwrite functions node(), way(), and relation(). Other functions are +// available, too. Read the API documentation for details. struct CountHandler : public osmium::handler::Handler { - uint64_t nodes = 0; - uint64_t ways = 0; - uint64_t relations = 0; + std::uint64_t nodes = 0; + std::uint64_t ways = 0; + std::uint64_t relations = 0; - void node(osmium::Node&) { + // This callback is called by osmium::apply for each node in the data. + void node(const osmium::Node&) noexcept { ++nodes; } - void way(osmium::Way&) { + // This callback is called by osmium::apply for each way in the data. + void way(const osmium::Way&) noexcept { ++ways; } - void relation(osmium::Relation&) { + // This callback is called by osmium::apply for each relation in the data. + void relation(const osmium::Relation&) noexcept { ++relations; } -}; +}; // struct CountHandler int main(int argc, char* argv[]) { - if (argc != 2) { std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; - exit(1); + std::exit(1); } - osmium::io::File infile(argv[1]); - osmium::io::Reader reader(infile); + // The Reader is initialized here with an osmium::io::File, but could + // also be directly initialized with a file name. + osmium::io::File input_file{argv[1]}; + osmium::io::Reader reader{input_file}; + // Create an instance of our own CountHandler and push the data from the + // input file through it. CountHandler handler; osmium::apply(reader, handler); + + // You do not have to close the Reader explicitly, but because the + // destructor can't throw, you will not see any errors otherwise. reader.close(); std::cout << "Nodes: " << handler.nodes << "\n"; std::cout << "Ways: " << handler.ways << "\n"; std::cout << "Relations: " << handler.relations << "\n"; + + // Because of the huge amount of OSM data, some Osmium-based programs + // (though not this one) can use huge amounts of data. So checking actual + // memore usage is often useful and can be done easily with this class. + // (Currently only works on Linux, not OSX and Windows.) + osmium::MemoryUsage memory; + + std::cout << "\nMemory used: " << memory.peak() << " MBytes\n"; } diff --git a/examples/osmium_create_node_cache.cpp b/examples/osmium_create_node_cache.cpp deleted file mode 100644 index 359fa1978..000000000 --- a/examples/osmium_create_node_cache.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - - This reads an OSM file and writes out the node locations to a cache - file. - - The code in this example file is released into the Public Domain. - -*/ - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -typedef osmium::index::map::Dummy index_neg_type; -//typedef osmium::index::map::DenseMmapArray index_pos_type; -typedef osmium::index::map::DenseFileArray index_pos_type; - -typedef osmium::handler::NodeLocationsForWays location_handler_type; - -int main(int argc, char* argv[]) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n"; - return 1; - } - - std::string input_filename(argv[1]); - osmium::io::Reader reader(input_filename, osmium::osm_entity_bits::node); - - int fd = open(argv[2], O_RDWR | O_CREAT, 0666); - if (fd == -1) { - std::cerr << "Can not open node cache file '" << argv[2] << "': " << strerror(errno) << "\n"; - return 1; - } - - index_pos_type index_pos {fd}; - index_neg_type index_neg; - location_handler_type location_handler(index_pos, index_neg); - location_handler.ignore_errors(); - - osmium::apply(reader, location_handler); - reader.close(); - - return 0; -} - diff --git a/examples/osmium_debug.cpp b/examples/osmium_debug.cpp index 365fc7297..813349190 100644 --- a/examples/osmium_debug.cpp +++ b/examples/osmium_debug.cpp @@ -1,27 +1,46 @@ /* - This is a small tool to dump the contents of the input file. + EXAMPLE osmium_debug + Dump the contents of the input file in a debug format. + + DEMONSTRATES USE OF: + * file input reading only some types + * the dump handler + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + * osmium_count + + LICENSE The code in this example file is released into the Public Domain. */ -#include +#include // for std::exit +#include // for std::cout, std::cerr +#include // for std::string +// The Dump handler #include + +// Allow any format of input files (XML, PBF, ...) #include int main(int argc, char* argv[]) { + // Speed up output (not Osmium-specific) std::ios_base::sync_with_stdio(false); if (argc < 2 || argc > 3) { std::cerr << "Usage: " << argv[0] << " OSMFILE [TYPES]\n"; std::cerr << "TYPES can be any combination of 'n', 'w', 'r', and 'c' to indicate what types of OSM entities you want (default: all).\n"; - exit(1); + std::exit(1); } + // Default is all entity types: nodes, ways, relations, and changesets osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all; + // Get entity types from command line if there is a 2nd argument. if (argc == 3) { read_types = osmium::osm_entity_bits::nothing; std::string types = argv[2]; @@ -31,20 +50,27 @@ int main(int argc, char* argv[]) { if (types.find('c') != std::string::npos) read_types |= osmium::osm_entity_bits::changeset; } - osmium::io::Reader reader(argv[1], read_types); - osmium::io::Header header = reader.header(); + // Initialize Reader with file name and the types of entities we want to + // read. + osmium::io::Reader reader{argv[1], read_types}; + // The file header can contain metadata such as the program that generated + // the file and the bounding box of the data. + osmium::io::Header header = reader.header(); std::cout << "HEADER:\n generator=" << header.get("generator") << "\n"; - for (auto& bbox : header.boxes()) { + for (const auto& bbox : header.boxes()) { std::cout << " bbox=" << bbox << "\n"; } - osmium::handler::Dump dump(std::cout); - while (osmium::memory::Buffer buffer = reader.read()) { - osmium::apply(buffer, dump); - } + // Initialize Dump handler. + osmium::handler::Dump dump{std::cout}; + // Read from input and send everything to Dump handler. + osmium::apply(reader, dump); + + // You do not have to close the Reader explicitly, but because the + // destructor can't throw, you will not see any errors otherwise. reader.close(); } diff --git a/examples/osmium_filter_discussions.cpp b/examples/osmium_filter_discussions.cpp index bba25b752..633498199 100644 --- a/examples/osmium_filter_discussions.cpp +++ b/examples/osmium_filter_discussions.cpp @@ -1,53 +1,73 @@ /* + EXAMPLE osmium_filter_discussions + Read OSM changesets with discussions from a changeset dump like the one you get from http://planet.osm.org/planet/discussions-latest.osm.bz2 and write out only those changesets which have discussions (ie comments). + DEMONSTRATES USE OF: + * file input and output + * setting file formats using the osmium::io::File class + * OSM file headers + * input and output iterators + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + * osmium_count + + LICENSE The code in this example file is released into the Public Domain. */ #include // for std::copy_if -#include // for std::cout, std::cerr +#include // for std::exit +#include // for std::cout, std::cerr -// we want to read OSM files in XML format -// (other formats don't support full changesets, so only XML is needed here) +// We want to read OSM files in XML format +// (other formats don't support full changesets, so only XML is needed here). #include -#include -// we want to write OSM files in XML format +// We want to write OSM files in XML format. #include + +// We want to use input and output iterators for easy integration with the +// algorithms of the standard library. +#include #include -// we want to support any compressioon (.gz2 and .bz2) +// We want to support any compression (none, gzip, and bzip2). #include int main(int argc, char* argv[]) { if (argc != 3) { std::cout << "Usage: " << argv[0] << " INFILE OUTFILE\n"; - exit(1); + std::exit(1); } - // The input file, deduce file format from file suffix - osmium::io::File infile(argv[1]); + // The input file, deduce file format from file suffix. + osmium::io::File input_file{argv[1]}; - // The output file, force class XML OSM file format - osmium::io::File outfile(argv[2], "osm"); + // The output file, force XML OSM file format. + osmium::io::File output_file{argv[2], "osm"}; // Initialize Reader for the input file. // Read only changesets (will ignore nodes, ways, and // relations if there are any). - osmium::io::Reader reader(infile, osmium::osm_entity_bits::changeset); + osmium::io::Reader reader{input_file, osmium::osm_entity_bits::changeset}; - // Get the header from the input file + // Get the header from the input file. osmium::io::Header header = reader.header(); + // Set the "generator" on the header to ourselves. + header.set("generator", "osmium_filter_discussions"); + // Initialize writer for the output file. Use the header from the input // file for the output file. This will copy over some header information. // The last parameter will tell the writer that it is allowed to overwrite // an existing file. Without it, it will refuse to do so. - osmium::io::Writer writer(outfile, header, osmium::io::overwrite::allow); + osmium::io::Writer writer(output_file, header, osmium::io::overwrite::allow); // Create range of input iterators that will iterator over all changesets // delivered from input file through the "reader". diff --git a/examples/osmium_index.cpp b/examples/osmium_index.cpp index b61214097..478b6c6b6 100644 --- a/examples/osmium_index.cpp +++ b/examples/osmium_index.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -31,7 +31,7 @@ class IndexSearch { void dump_dense() { dense_index_type index(m_fd); - for (size_t i = 0; i < index.size(); ++i) { + for (std::size_t i = 0; i < index.size(); ++i) { if (index.get(i) != TValue()) { std::cout << i << " " << index.get(i) << "\n"; } @@ -51,9 +51,9 @@ class IndexSearch { try { TValue value = index.get(key); - std::cout << key << " " << value << std::endl; + std::cout << key << " " << value << "\n"; } catch (...) { - std::cout << key << " not found" << std::endl; + std::cout << key << " not found\n"; return false; } @@ -69,7 +69,7 @@ class IndexSearch { return lhs.first < rhs.first; }); if (positions.first == positions.second) { - std::cout << key << " not found" << std::endl; + std::cout << key << " not found\n"; return false; } @@ -103,7 +103,7 @@ public: } } - bool search(std::vector keys) { + bool search(const std::vector& keys) { bool found_all = true; for (const auto key : keys) { @@ -124,82 +124,105 @@ enum return_code : int { fatal = 3 }; -namespace po = boost::program_options; - class Options { - po::variables_map vm; + std::vector m_ids; + std::string m_type; + std::string m_filename; + bool m_dump = false; + bool m_array_format = false; + bool m_list_format = false; + + void print_help() { + std::cout << "Usage: osmium_index [OPTIONS]\n\n" + << "-h, --help Print this help message\n" + << "-a, --array=FILE Read given index file in array format\n" + << "-l, --list=FILE Read given index file in list format\n" + << "-d, --dump Dump contents of index file to STDOUT\n" + << "-s, --search=ID Search for given id (Option can appear multiple times)\n" + << "-t, --type=TYPE Type of value ('location' or 'offset')\n" + ; + } public: Options(int argc, char* argv[]) { - try { - po::options_description desc("Allowed options"); - desc.add_options() - ("help,h", "Print this help message") - ("array,a", po::value(), "Read given index file in array format") - ("list,l", po::value(), "Read given index file in list format") - ("dump,d", "Dump contents of index file to STDOUT") - ("search,s", po::value>(), "Search for given id (Option can appear multiple times)") - ("type,t", po::value(), "Type of value ('location' or 'offset')") - ; + static struct option long_options[] = { + {"array", required_argument, 0, 'a'}, + {"dump", no_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"list", required_argument, 0, 'l'}, + {"search", required_argument, 0, 's'}, + {"type", required_argument, 0, 't'}, + {0, 0, 0, 0} + }; - po::store(po::parse_command_line(argc, argv, desc), vm); - po::notify(vm); - - if (vm.count("help")) { - std::cout << desc << "\n"; - exit(return_code::okay); + while (true) { + int c = getopt_long(argc, argv, "a:dhl:s:t:", long_options, 0); + if (c == -1) { + break; } - if (vm.count("array") && vm.count("list")) { - std::cerr << "Only option --array or --list allowed." << std::endl; - exit(return_code::fatal); + switch (c) { + case 'a': + m_array_format = true; + m_filename = optarg; + break; + case 'd': + m_dump = true; + break; + case 'h': + print_help(); + std::exit(return_code::okay); + case 'l': + m_list_format = true; + m_filename = optarg; + break; + case 's': + m_ids.push_back(std::atoll(optarg)); + break; + case 't': + m_type = optarg; + if (m_type != "location" && m_type != "offset") { + std::cerr << "Unknown type '" << m_type << "'. Must be 'location' or 'offset'.\n"; + std::exit(return_code::fatal); + } + break; + default: + std::exit(return_code::fatal); } - - if (!vm.count("array") && !vm.count("list")) { - std::cerr << "Need one of option --array or --list." << std::endl; - exit(return_code::fatal); - } - - if (!vm.count("type")) { - std::cerr << "Need --type argument." << std::endl; - exit(return_code::fatal); - } - - const std::string& type = vm["type"].as(); - if (type != "location" && type != "offset") { - std::cerr << "Unknown type '" << type << "'. Must be 'location' or 'offset'." << std::endl; - exit(return_code::fatal); - } - } catch (boost::program_options::error& e) { - std::cerr << "Error parsing command line: " << e.what() << std::endl; - exit(return_code::fatal); } - } - const std::string& filename() const { - if (vm.count("array")) { - return vm["array"].as(); - } else { - return vm["list"].as(); + if (m_array_format == m_list_format) { + std::cerr << "Need option --array or --list, but not both\n"; + std::exit(return_code::fatal); } + + if (m_type.empty()) { + std::cerr << "Need --type argument.\n"; + std::exit(return_code::fatal); + } + } - bool dense_format() const { - return vm.count("array") != 0; + const std::string& filename() const noexcept { + return m_filename; } - bool do_dump() const { - return vm.count("dump") != 0; + bool dense_format() const noexcept { + return m_array_format; } - std::vector search_keys() const { - return vm["search"].as>(); + bool do_dump() const noexcept { + return m_dump; } - bool type_is(const char* type) const { - return vm["type"].as() == type; + const std::vector& search_keys() const noexcept { + return m_ids; + } + + bool type_is(const char* type) const noexcept { + return m_type == type; } }; // class Options @@ -232,6 +255,6 @@ int main(int argc, char* argv[]) { } } - exit(result_okay ? return_code::okay : return_code::not_found); + std::exit(result_okay ? return_code::okay : return_code::not_found); } diff --git a/examples/osmium_location_cache_create.cpp b/examples/osmium_location_cache_create.cpp new file mode 100644 index 000000000..9de41d12f --- /dev/null +++ b/examples/osmium_location_cache_create.cpp @@ -0,0 +1,87 @@ +/* + + EXAMPLE osmium_location_cache_create + + Reads nodes from an OSM file and writes out their locations to a cache + file. The cache file can then be read with osmium_location_cache_use. + + Warning: The locations cache file will get huge (>32GB) if you are using + the DenseFileArray index even if the input file is small, because + it depends on the *largest* node ID, not the number of nodes. + + DEMONSTRATES USE OF: + * file input + * location indexes and the NodeLocationsForWays handler + * location indexes on disk + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + * osmium_count + * osmium_road_length + + LICENSE + The code in this example file is released into the Public Domain. + +*/ + +#include // for errno +#include // for std::exit +#include // for strerror +#include // for open +#include // for std::cout, std::cerr +#include // for std::string +#include // for open +#include // for open + +// Allow any format of input files (XML, PBF, ...) +#include + +// For the location index. There are different types of index implementation +// available. These implementations put the index on disk. See below. +#include +#include + +// For the NodeLocationForWays handler +#include + +// For osmium::apply() +#include + +// Chose one of these two. "sparse" is best used for small and medium extracts, +// the "dense" index for large extracts or the whole planet. +using index_type = osmium::index::map::SparseFileArray; +//using index_type = osmium::index::map::DenseFileArray; + +// The location handler always depends on the index type +using location_handler_type = osmium::handler::NodeLocationsForWays; + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n"; + std::exit(1); + } + + const std::string input_filename{argv[1]}; + const std::string cache_filename{argv[2]}; + + // Construct Reader reading only nodes + osmium::io::Reader reader{input_filename, osmium::osm_entity_bits::node}; + + // Initialize location index on disk creating a new file. + const int fd = open(cache_filename.c_str(), O_RDWR | O_CREAT, 0666); + if (fd == -1) { + std::cerr << "Can not open location cache file '" << cache_filename << "': " << std::strerror(errno) << "\n"; + std::exit(1); + } + index_type index{fd}; + + // The handler that stores all node locations in the index. + location_handler_type location_handler{index}; + + // Feed all nodes through the location handler. + osmium::apply(reader, location_handler); + + // Explicitly close input so we get notified of any errors. + reader.close(); +} + diff --git a/examples/osmium_location_cache_use.cpp b/examples/osmium_location_cache_use.cpp new file mode 100644 index 000000000..f5db0becf --- /dev/null +++ b/examples/osmium_location_cache_use.cpp @@ -0,0 +1,101 @@ +/* + + EXAMPLE osmium_location_cache_use + + This reads ways from an OSM file and writes out the way node locations + it got from a location cache generated with osmium_location_cache_create. + + Warning: The locations cache file will get huge (>32GB) if you are using + the DenseFileArray index even if the input file is small, because + it depends on the *largest* node ID, not the number of nodes. + + DEMONSTRATES USE OF: + * file input + * location indexes and the NodeLocationsForWays handler + * location indexes on disk + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + * osmium_count + * osmium_road_length + + LICENSE + The code in this example file is released into the Public Domain. + +*/ + +#include // for errno +#include // for std::exit +#include // for strerror +#include // for open +#include // for std::cout, std::cerr +#include // for std::string +#include // for open +#include // for open + +// Allow any format of input files (XML, PBF, ...) +#include + +// For the location index. There are different types of index implementation +// available. These implementations put the index on disk. See below. +#include +#include + +// For the NodeLocationForWays handler +#include + +// For osmium::apply() +#include + +// Chose one of these two. "sparse" is best used for small and medium extracts, +// the "dense" index for large extracts or the whole planet. +using index_type = osmium::index::map::SparseFileArray; +//using index_type = osmium::index::map::DenseFileArray; + +// The location handler always depends on the index type +using location_handler_type = osmium::handler::NodeLocationsForWays; + +// This handler only implements the way() function which prints out the way +// ID and all nodes IDs and locations in those ways. +struct MyHandler : public osmium::handler::Handler { + + void way(const osmium::Way& way) { + std::cout << "way " << way.id() << "\n"; + for (const auto& nr : way.nodes()) { + std::cout << " node " << nr.ref() << " " << nr.location() << "\n"; + } + } + +}; // struct MyHandler + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n"; + std::exit(1); + } + + const std::string input_filename{argv[1]}; + const std::string cache_filename{argv[2]}; + + // Construct Reader reading only ways + osmium::io::Reader reader{input_filename, osmium::osm_entity_bits::way}; + + // Initialize location index on disk using an existing file + const int fd = open(cache_filename.c_str(), O_RDWR); + if (fd == -1) { + std::cerr << "Can not open location cache file '" << cache_filename << "': " << std::strerror(errno) << "\n"; + return 1; + } + index_type index{fd}; + + // The handler that adds node locations from the index to the ways. + location_handler_type location_handler{index}; + + // Feed all ways through the location handler and then our own handler. + MyHandler handler; + osmium::apply(reader, location_handler, handler); + + // Explicitly close input so we get notified of any errors. + reader.close(); +} + diff --git a/examples/osmium_pub_names.cpp b/examples/osmium_pub_names.cpp new file mode 100755 index 000000000..dbc37c332 --- /dev/null +++ b/examples/osmium_pub_names.cpp @@ -0,0 +1,89 @@ +/* + + EXAMPLE osmium_pub_names + + Show the names and addresses of all pubs found in an OSM file. + + DEMONSTRATES USE OF: + * file input + * your own handler + * access to tags + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + * osmium_count + + LICENSE + The code in this example file is released into the Public Domain. + +*/ + +#include // for std::exit +#include // for std::strncmp +#include // for std::cout, std::cerr + +// Allow any format of input files (XML, PBF, ...) +#include + +// We want to use the handler interface +#include + +// For osmium::apply() +#include + +class NamesHandler : public osmium::handler::Handler { + + void output_pubs(const osmium::OSMObject& object) { + const osmium::TagList& tags = object.tags(); + if (tags.has_tag("amenity", "pub")) { + + // Print name of the pub if it is set. + const char* name = tags["name"]; + if (name) { + std::cout << name << "\n"; + } else { + std::cout << "pub with unknown name\n"; + } + + // Iterate over all tags finding those which start with "addr:" + // and print them. + for (const osmium::Tag& tag : tags) { + if (!std::strncmp(tag.key(), "addr:", 5)) { + std::cout << " " << tag.key() << ": " << tag.value() << "\n"; + } + } + } + } + +public: + + // Nodes can be tagged amenity=pub. + void node(const osmium::Node& node) { + output_pubs(node); + } + + // Ways can be tagged amenity=pub, too (typically buildings). + void way(const osmium::Way& way) { + output_pubs(way); + } + +}; // class NamesHandler + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; + std::exit(1); + } + + // Construct the handler defined above + NamesHandler names_handler; + + // Initialize the reader with the filename from the command line and + // tell it to only read nodes and ways. We are ignoring multipolygon + // relations in this simple example. + osmium::io::Reader reader{argv[1], osmium::osm_entity_bits::node | osmium::osm_entity_bits::way}; + + // Apply input data to our own handler + osmium::apply(reader, names_handler); +} + diff --git a/examples/osmium_read.cpp b/examples/osmium_read.cpp index 653600684..9f391c874 100644 --- a/examples/osmium_read.cpp +++ b/examples/osmium_read.cpp @@ -1,30 +1,42 @@ /* - This is a small tool that reads and discards the contents of the input file. - (Used for timing.) + EXAMPLE osmium_read + Reads and discards the contents of the input file. + (It can be used for timing.) + + DEMONSTRATES USE OF: + * file input + + LICENSE The code in this example file is released into the Public Domain. */ -#include +#include // for std::exit +#include // for std::cerr +// Allow any format of input files (XML, PBF, ...) #include int main(int argc, char* argv[]) { - if (argc != 2) { std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; - exit(1); + std::exit(1); } - osmium::io::File infile(argv[1]); - osmium::io::Reader reader(infile); + // The Reader is initialized here with an osmium::io::File, but could + // also be directly initialized with a file name. + osmium::io::File input_file{argv[1]}; + osmium::io::Reader reader{input_file}; + // OSM data comes in buffers, read until there are no more. while (osmium::memory::Buffer buffer = reader.read()) { // do nothing } + // You do not have to close the Reader explicitly, but because the + // destructor can't throw, you will not see any errors otherwise. reader.close(); } diff --git a/examples/osmium_read_with_progress.cpp b/examples/osmium_read_with_progress.cpp new file mode 100644 index 000000000..81437a665 --- /dev/null +++ b/examples/osmium_read_with_progress.cpp @@ -0,0 +1,56 @@ +/* + + EXAMPLE osmium_read_with_progress + + Reads the contents of the input file showing a progress bar. + + DEMONSTRATES USE OF: + * file input + * ProgressBar utility function + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + + LICENSE + The code in this example file is released into the Public Domain. + +*/ + +#include // for std::exit +#include // for std::cerr + +// Allow any format of input files (XML, PBF, ...) +#include + +// Get access to isatty utility function and progress bar utility class. +#include +#include + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; + std::exit(1); + } + + // The Reader is initialized here with an osmium::io::File, but could + // also be directly initialized with a file name. + osmium::io::File input_file{argv[1]}; + osmium::io::Reader reader{input_file}; + + // Initialize progress bar, enable it only if STDERR is a TTY. + osmium::ProgressBar progress{reader.file_size(), osmium::util::isatty(2)}; + + // OSM data comes in buffers, read until there are no more. + while (osmium::memory::Buffer buffer = reader.read()) { + // Update progress bar for each buffer. + progress.update(reader.offset()); + } + + // Progress bar is done. + progress.done(); + + // You do not have to close the Reader explicitly, but because the + // destructor can't throw, you will not see any errors otherwise. + reader.close(); +} + diff --git a/examples/osmium_road_length.cpp b/examples/osmium_road_length.cpp new file mode 100755 index 000000000..2e1be9080 --- /dev/null +++ b/examples/osmium_road_length.cpp @@ -0,0 +1,92 @@ +/* + + EXAMPLE osmium_road_length + + Calculate the length of the road network (everything tagged `highway=*`) + from the given OSM file. + + DEMONSTRATES USE OF: + * file input + * location indexes and the NodeLocationsForWays handler + * length calculation on the earth using the haversine function + + SIMPLER EXAMPLES you might want to understand first: + * osmium_read + * osmium_count + * osmium_pub_names + + LICENSE + The code in this example file is released into the Public Domain. + +*/ + +#include // for std::exit +#include // for std::cout, std::cerr + +// Allow any format of input files (XML, PBF, ...) +#include + +// For the osmium::geom::haversine::distance() function +#include + +// For osmium::apply() +#include + +// For the location index. There are different types of indexes available. +// This will work for small and medium sized input files. +#include + +// For the NodeLocationForWays handler +#include + +// The type of index used. This must match the include file above +using index_type = osmium::index::map::SparseMemArray; + +// The location handler always depends on the index type +using location_handler_type = osmium::handler::NodeLocationsForWays; + +// This handler only implements the way() function, we are not interested in +// any other objects. +struct RoadLengthHandler : public osmium::handler::Handler { + + double length = 0; + + // If the way has a "highway" tag, find its length and add it to the + // overall length. + void way(const osmium::Way& way) { + const char* highway = way.tags()["highway"]; + if (highway) { + length += osmium::geom::haversine::distance(way.nodes()); + } + } + +}; // struct RoadLengthHandler + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " OSMFILE\n"; + std::exit(1); + } + + // Initialize the reader with the filename from the command line and + // tell it to only read nodes and ways. + osmium::io::Reader reader{argv[1], osmium::osm_entity_bits::node | osmium::osm_entity_bits::way}; + + // The index to hold node locations. + index_type index; + + // The location handler will add the node locations to the index and then + // to the ways + location_handler_type location_handler{index}; + + // Our handler defined above + RoadLengthHandler road_length_handler; + + // Apply input data to first the location handler and then our own handler + osmium::apply(reader, location_handler, road_length_handler); + + // Output the length. The haversine function calculates it in meters, + // so we first devide by 1000 to get kilometers. + std::cout << "Length: " << road_length_handler.length / 1000 << " km\n"; +} + diff --git a/examples/osmium_serdump.cpp b/examples/osmium_serdump.cpp index 9ab26e4ee..81a6d0cc5 100644 --- a/examples/osmium_serdump.cpp +++ b/examples/osmium_serdump.cpp @@ -69,9 +69,9 @@ int main(int argc, char* argv[]) { switch (c) { case 'h': print_help(); - exit(0); + std::exit(0); default: - exit(2); + std::exit(2); } } @@ -79,7 +79,7 @@ int main(int argc, char* argv[]) { if (remaining_args != 2) { std::cerr << "Usage: " << argv[0] << " OSMFILE DIR\n"; - exit(2); + std::exit(2); } std::string dir(argv[optind+1]); @@ -90,14 +90,14 @@ int main(int argc, char* argv[]) { #endif if (result == -1 && errno != EEXIST) { std::cerr << "Problem creating directory '" << dir << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } std::string data_file(dir + "/data.osm.ser"); int data_fd = ::open(data_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (data_fd < 0) { std::cerr << "Can't open data file '" << data_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } offset_index_type node_index; @@ -127,7 +127,7 @@ int main(int argc, char* argv[]) { int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { std::cerr << "Can't open nodes index file '" << index_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } node_index.dump_as_list(fd); close(fd); @@ -138,7 +138,7 @@ int main(int argc, char* argv[]) { int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { std::cerr << "Can't open ways index file '" << index_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } way_index.dump_as_list(fd); close(fd); @@ -149,7 +149,7 @@ int main(int argc, char* argv[]) { int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { std::cerr << "Can't open relations index file '" << index_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } relation_index.dump_as_list(fd); close(fd); @@ -161,7 +161,7 @@ int main(int argc, char* argv[]) { int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { std::cerr << "Can't open node->way map file '" << index_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } map_node2way.dump_as_list(fd); close(fd); @@ -173,7 +173,7 @@ int main(int argc, char* argv[]) { int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { std::cerr << "Can't open node->rel map file '" << index_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } map_node2relation.dump_as_list(fd); close(fd); @@ -185,7 +185,7 @@ int main(int argc, char* argv[]) { int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { std::cerr << "Can't open way->rel map file '" << index_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } map_way2relation.dump_as_list(fd); close(fd); @@ -197,7 +197,7 @@ int main(int argc, char* argv[]) { int fd = ::open(index_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { std::cerr << "Can't open rel->rel map file '" << index_file << "': " << strerror(errno) << "\n"; - exit(2); + std::exit(2); } map_relation2relation.dump_as_list(fd); close(fd); diff --git a/examples/osmium_tiles.cpp b/examples/osmium_tiles.cpp new file mode 100644 index 000000000..7dbeb3e29 --- /dev/null +++ b/examples/osmium_tiles.cpp @@ -0,0 +1,72 @@ +/* + + EXAMPLE osmium_tiles + + Convert WGS84 longitude and latitude to Mercator coordinates and tile + coordinates. + + DEMONSTRATES USE OF: + * the Location and Coordinates classes + * the Mercator projection function + * the Tile class + + LICENSE + The code in this example file is released into the Public Domain. + +*/ + +#include // for std::exit, std::atoi, std::atof +#include // for std::cout, std::cerr + +// The Location contains a longitude and latitude and is usually used inside +// a node to store its location in the world. +#include + +// Needed for the Mercator projection function. Osmium supports the Mercator +// projection out of the box, or pretty much any projection using the Proj.4 +// library (with the osmium::geom::Projection class). +#include + +// The Tile class handles tile coordinates and zoom levels. +#include + +int main(int argc, char* argv[]) { + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " ZOOM LON LAT\n"; + std::exit(1); + } + + const int zoom = std::atoi(argv[1]); + + if (zoom < 0 || zoom > 30) { + std::cerr << "ERROR: Zoom must be between 0 and 30\n"; + std::exit(1); + } + + const double lon = std::atof(argv[2]); + const double lat = std::atof(argv[3]); + + // Create location from WGS84 coordinates. In Osmium the order of + // coordinate values is always x/longitude first, then y/latitude. + const osmium::Location location{lon, lat}; + + std::cout << "WGS84: lon=" << lon << " lat=" << lat << "\n"; + + // A location can store some invalid locations, ie locations outside the + // -180 to 180 and -90 to 90 degree range. This function checks for that. + if (!location.valid()) { + std::cerr << "ERROR: Location is invalid\n"; + std::exit(1); + } + + // Project the coordinates using a helper function. You can also use the + // osmium::geom::MercatorProjection class. + const osmium::geom::Coordinates c = osmium::geom::lonlat_to_mercator(location); + std::cout << "Mercator: x=" << c.x << " y=" << c.y << "\n"; + + // Create a tile at this location. This will also internally use the + // Mercator projection and then calculate the tile coordinates. + const osmium::geom::Tile tile{uint32_t(zoom), location}; + std::cout << "Tile: zoom=" << tile.z << " x=" << tile.x << " y=" << tile.y << "\n"; +} + diff --git a/examples/osmium_use_node_cache.cpp b/examples/osmium_use_node_cache.cpp deleted file mode 100644 index cfee6df46..000000000 --- a/examples/osmium_use_node_cache.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - - This reads ways from an OSM file and writes out the node locations - it got from a node cache generated with osmium_create_node_cache. - - The code in this example file is released into the Public Domain. - -*/ - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -typedef osmium::index::map::Dummy index_neg_type; -//typedef osmium::index::map::DenseMmapArray index_pos_type; -typedef osmium::index::map::DenseFileArray index_pos_type; - -typedef osmium::handler::NodeLocationsForWays location_handler_type; - -class MyHandler : public osmium::handler::Handler { - -public: - - void way(osmium::Way& way) { - for (auto& nr : way.nodes()) { - std::cout << nr << "\n"; - } - } - -}; // class MyHandler - -int main(int argc, char* argv[]) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " OSM_FILE CACHE_FILE\n"; - return 1; - } - - std::string input_filename(argv[1]); - osmium::io::Reader reader(input_filename, osmium::osm_entity_bits::way); - - int fd = open(argv[2], O_RDWR); - if (fd == -1) { - std::cerr << "Can not open node cache file '" << argv[2] << "': " << strerror(errno) << "\n"; - return 1; - } - - index_pos_type index_pos {fd}; - index_neg_type index_neg; - location_handler_type location_handler(index_pos, index_neg); - location_handler.ignore_errors(); - - MyHandler handler; - osmium::apply(reader, location_handler, handler); - reader.close(); - - return 0; -} - diff --git a/include/gdalcpp.hpp b/include/gdalcpp.hpp index 1502f2f0c..4f3d48096 100644 --- a/include/gdalcpp.hpp +++ b/include/gdalcpp.hpp @@ -110,8 +110,12 @@ namespace gdalcpp { namespace detail { struct init_wrapper { +#if GDAL_VERSION_MAJOR >= 2 + init_wrapper() { GDALAllRegister(); } +#else init_wrapper() { OGRRegisterAll(); } ~init_wrapper() { OGRCleanupAll(); } +#endif }; struct init_library { @@ -237,6 +241,8 @@ namespace gdalcpp { detail::Options m_options; SRS m_srs; std::unique_ptr m_dataset; + uint64_t m_edit_count = 0; + uint64_t m_max_edit_count = 0; public: @@ -255,6 +261,15 @@ namespace gdalcpp { } } + ~Dataset() { + try { + if (m_edit_count > 0) { + commit_transaction(); + } + } catch (...) { + } + } + const std::string& driver_name() const { return m_driver_name; } @@ -282,10 +297,14 @@ namespace gdalcpp { exec(sql.c_str()); } - Dataset& start_transaction() { #if GDAL_VERSION_MAJOR >= 2 m_dataset->StartTransaction(); +#else + OGRLayer* layer = m_dataset->GetLayer(0); + if (layer) { + layer->StartTransaction(); + } #endif return *this; } @@ -293,7 +312,38 @@ namespace gdalcpp { Dataset& commit_transaction() { #if GDAL_VERSION_MAJOR >= 2 m_dataset->CommitTransaction(); +#else + OGRLayer* layer = m_dataset->GetLayer(0); + if (layer) { + layer->CommitTransaction(); + } #endif + m_edit_count = 0; + return *this; + } + + void prepare_edit() { + if (m_max_edit_count != 0 && m_edit_count == 0) { + start_transaction(); + } + } + + void finalize_edit() { + if (m_max_edit_count != 0 && ++m_edit_count > m_max_edit_count) { + commit_transaction(); + } + } + + Dataset& enable_auto_transactions(uint64_t edits = 100000) { + m_max_edit_count = edits; + return *this; + } + + Dataset& disable_auto_transactions() { + if (m_max_edit_count != 0 && m_edit_count > 0) { + commit_transaction(); + } + m_max_edit_count = 0; return *this; } @@ -346,19 +396,32 @@ namespace gdalcpp { return *this; } + void create_feature(OGRFeature* feature) { + dataset().prepare_edit(); + OGRErr result = m_layer->CreateFeature(feature); + if (result != OGRERR_NONE) { + throw gdal_error(std::string("creating feature in layer '") + name() + "' failed", result, dataset().driver_name(), dataset().dataset_name()); + } + dataset().finalize_edit(); + } + Layer& start_transaction() { +#if GDAL_VERSION_MAJOR < 2 OGRErr result = m_layer->StartTransaction(); if (result != OGRERR_NONE) { throw gdal_error(std::string("starting transaction on layer '") + name() + "' failed", result, m_dataset.driver_name(), m_dataset.dataset_name(), name()); } +#endif return *this; } Layer& commit_transaction() { +#if GDAL_VERSION_MAJOR < 2 OGRErr result = m_layer->CommitTransaction(); if (result != OGRERR_NONE) { throw gdal_error(std::string("committing transaction on layer '") + name() + "' failed", result, m_dataset.driver_name(), m_dataset.dataset_name(), name()); } +#endif return *this; } @@ -366,36 +429,44 @@ namespace gdalcpp { class Feature { + struct ogr_feature_deleter { + + void operator()(OGRFeature* feature) { + OGRFeature::DestroyFeature(feature); + } + + }; // struct ogr_feature_deleter + Layer& m_layer; - OGRFeature m_feature; + std::unique_ptr m_feature; public: Feature(Layer& layer, std::unique_ptr&& geometry) : m_layer(layer), - m_feature(m_layer.get().GetLayerDefn()) { - OGRErr result = m_feature.SetGeometryDirectly(geometry.release()); + m_feature(OGRFeature::CreateFeature(m_layer.get().GetLayerDefn())) { + if (!m_feature) { + throw std::bad_alloc(); + } + OGRErr result = m_feature->SetGeometryDirectly(geometry.release()); if (result != OGRERR_NONE) { throw gdal_error(std::string("setting feature geometry in layer '") + m_layer.name() + "' failed", result, m_layer.dataset().driver_name(), m_layer.dataset().dataset_name()); } } void add_to_layer() { - OGRErr result = m_layer.get().CreateFeature(&m_feature); - if (result != OGRERR_NONE) { - throw gdal_error(std::string("creating feature in layer '") + m_layer.name() + "' failed", result, m_layer.dataset().driver_name(), m_layer.dataset().dataset_name()); - } + m_layer.create_feature(m_feature.get()); } template Feature& set_field(int n, T&& arg) { - m_feature.SetField(n, std::forward(arg)); + m_feature->SetField(n, std::forward(arg)); return *this; } template Feature& set_field(const char* name, T&& arg) { - m_feature.SetField(name, std::forward(arg)); + m_feature->SetField(name, std::forward(arg)); return *this; } diff --git a/include/osmium/area/assembler.hpp b/include/osmium/area/assembler.hpp index a2d1c8ebd..d5bf8d8da 100644 --- a/include/osmium/area/assembler.hpp +++ b/include/osmium/area/assembler.hpp @@ -35,6 +35,8 @@ DEALINGS IN THE SOFTWARE. #include #include +#include +#include #include #include #include @@ -42,95 +44,251 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include +#include +#include #include #include #include #include +#include #include +#include #include +#include +#include +#include #include +#include +#include +#include #include #include #include #include +#include namespace osmium { namespace area { - using osmium::area::detail::ProtoRing; - + /** + * Configuration for osmium::area::Assembler objects. Create this + * once, set the options you want and then re-use it every time you + * create an Assembler object. + */ struct AssemblerConfig { - osmium::area::ProblemReporter* problem_reporter; + /** + * Optional pointer to problem reporter. + */ + osmium::area::ProblemReporter* problem_reporter = nullptr; - // Enables debug output to stderr - bool debug; + /** + * Debug level. If this is greater than zero, debug messages will + * be printed to stderr. Available levels are 1 to 3. Note that + * level 2 and above will generate a lot of messages! + */ + int debug_level = 0; - explicit AssemblerConfig(osmium::area::ProblemReporter* pr = nullptr, bool d = false) : + /** + * The roles of multipolygon members are ignored when assembling + * multipolygons, because they are often missing or wrong. If this + * is set, the roles are checked after the multipolygons are built + * against what the assembly process decided where the inner and + * outer rings are. This slows down the processing, so it only + * makes sense if you want to get the problem reports. + */ + bool check_roles = false; + + /** + * When the assembler can't create an area, usually because its + * geometry would be invalid, it will create an "empty" area object + * without rings. This allows you to detect where an area was + * invalid. + * + * If this is set to false, invalid areas will simply be discarded. + */ + bool create_empty_areas = true; + + /** + * Create areas for (multi)polygons where the tags are on the + * relation. + * + * If this is set to false, those areas will simply be discarded. + */ + bool create_new_style_polygons = true; + + /** + * Create areas for (multi)polygons where the tags are on the + * outer way(s). + * + * If this is set to false, those areas will simply be discarded. + */ + bool create_old_style_polygons = true; + + /** + * Create areas for polygons created from ways. + * + * If this is set to false, those areas will simply be discarded. + */ + bool create_way_polygons = true; + + /** + * Keep the type tag from multipolygon relations on the area + * object. By default this is false, and the type tag will be + * removed. + */ + bool keep_type_tag = false; + + AssemblerConfig() noexcept = default; + + /** + * Constructor + * @deprecated Use default constructor and set values afterwards. + */ + explicit AssemblerConfig(osmium::area::ProblemReporter* pr, bool d = false) : problem_reporter(pr), - debug(d) { + debug_level(d) { } /** * Enable or disable debug output to stderr. This is for Osmium * developers only. + * + * @deprecated Set debug_level directly. */ - void enable_debug_output(bool d = true) { - debug = d; + OSMIUM_DEPRECATED void enable_debug_output(bool d = true) { + debug_level = d; } }; // struct AssemblerConfig + namespace detail { + + using open_ring_its_type = std::list::iterator>; + + struct location_to_ring_map { + osmium::Location location; + open_ring_its_type::iterator ring_it; + bool start; + + location_to_ring_map(const osmium::Location& l, const open_ring_its_type::iterator& r, bool s) noexcept : + location(l), + ring_it(r), + start(s) { + } + + explicit location_to_ring_map(const osmium::Location& l) noexcept : + location(l), + ring_it(), + start(false) { + } + + const detail::ProtoRing& ring() const noexcept { + return **ring_it; + } + + }; // struct location_to_ring_map + + inline bool operator==(const location_to_ring_map& a, const location_to_ring_map& b) noexcept { + return a.location == b.location; + } + + inline bool operator<(const location_to_ring_map& a, const location_to_ring_map& b) noexcept { + return a.location < b.location; + } + + } // namespace detail + /** - * Assembles area objects from multipolygon relations and their - * members. This is called by the MultipolygonCollector object - * after all members have been collected. + * Assembles area objects from closed ways or multipolygon relations + * and their members. */ class Assembler { - const AssemblerConfig m_config; + using open_ring_its_type = detail::open_ring_its_type; + using location_to_ring_map = detail::location_to_ring_map; - // The way segments + struct slocation { + + static constexpr const uint32_t invalid_item = 1 << 30; + + uint32_t item : 31; + uint32_t reverse : 1; + + slocation() noexcept : + item(invalid_item), + reverse(false) { + } + + explicit slocation(uint32_t n, bool r = false) noexcept : + item(n), + reverse(r) { + } + + osmium::Location location(const detail::SegmentList& segment_list) const noexcept { + const auto& segment = segment_list[item]; + return reverse ? segment.second().location() : segment.first().location(); + } + + const osmium::NodeRef& node_ref(const detail::SegmentList& segment_list) const noexcept { + const auto& segment = segment_list[item]; + return reverse ? segment.second() : segment.first(); + } + + osmium::Location location(const detail::SegmentList& segment_list, const osmium::Location& default_location) const noexcept { + if (item == invalid_item) { + return default_location; + } + return location(segment_list); + } + + }; // struct slocation + + // Configuration settings for this Assembler + const AssemblerConfig& m_config; + + // List of segments (connection between two nodes) osmium::area::detail::SegmentList m_segment_list; - // The rings we are building from the way segments - std::list m_rings; + // The rings we are building from the segments + std::list m_rings; - std::vector m_outer_rings; - std::vector m_inner_rings; + // All node locations + std::vector m_locations; - int m_inner_outer_mismatches { 0 }; + // All locations where more than two segments start/end + std::vector m_split_locations; - bool debug() const { - return m_config.debug; + // Statistics + area_stats m_stats; + + // The number of members the multipolygon relation has + size_t m_num_members = 0; + + bool debug() const noexcept { + return m_config.debug_level > 1; } - /** - * Checks whether the given NodeRefs have the same location. - * Uses the actual location for the test, not the id. If both - * have the same location, but not the same id, a problem - * point will be added to the list of problem points. - */ - bool has_same_location(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) { - if (nr1.location() != nr2.location()) { + bool report_ways() const noexcept { + if (!m_config.problem_reporter) { return false; } - if (nr1.ref() != nr2.ref()) { - if (m_config.problem_reporter) { - m_config.problem_reporter->report_duplicate_node(nr1.ref(), nr2.ref(), nr1.location()); - } - } - return true; + return m_stats.duplicate_nodes || + m_stats.duplicate_segments || + m_stats.intersections || + m_stats.open_rings || + m_stats.short_ways || + m_stats.touching_rings || + m_stats.ways_in_multiple_rings || + m_stats.wrong_role; } void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Way& way) const { - osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder); - for (const osmium::Tag& tag : way.tags()) { - tl_builder.add_tag(tag.key(), tag.value()); - } + builder.add_item(&way.tags()); } void add_common_tags(osmium::builder::TagListBuilder& tl_builder, std::set& ways) const { @@ -144,13 +302,13 @@ namespace osmium { } } - size_t num_ways = ways.size(); + const size_t num_ways = ways.size(); for (const auto& t_c : counter) { if (debug()) { std::cerr << " tag " << t_c.first << " is used " << t_c.second << " times in " << num_ways << " ways\n"; } if (t_c.second == num_ways) { - size_t len = std::strlen(t_c.first.c_str()); + const size_t len = std::strlen(t_c.first.c_str()); tl_builder.add_tag(t_c.first.c_str(), t_c.first.c_str() + len + 1); } } @@ -169,13 +327,22 @@ namespace osmium { }; // struct MPFilter - static MPFilter& filter() { - static MPFilter filter; + static const MPFilter& filter() noexcept { + static const MPFilter filter; return filter; } - void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Relation& relation) const { - const auto count = std::count_if(relation.tags().begin(), relation.tags().end(), filter()); + static void copy_tags_without_type(osmium::builder::AreaBuilder& builder, const osmium::TagList& tags) { + osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder); + for (const osmium::Tag& tag : tags) { + if (std::strcmp(tag.key(), "type")) { + tl_builder.add_tag(tag.key(), tag.value()); + } + } + } + + void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Relation& relation) { + const auto count = std::count_if(relation.tags().cbegin(), relation.tags().cend(), filter()); if (debug()) { std::cerr << " found " << count << " tags on relation (without ignored ones)\n"; @@ -186,29 +353,27 @@ namespace osmium { std::cerr << " use tags from relation\n"; } - // write out all tags except type=* - osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder); - for (const osmium::Tag& tag : relation.tags()) { - if (strcmp(tag.key(), "type")) { - tl_builder.add_tag(tag.key(), tag.value()); - } + if (m_config.keep_type_tag) { + builder.add_item(&relation.tags()); + } else { + copy_tags_without_type(builder, relation.tags()); } } else { + ++m_stats.no_tags_on_relation; if (debug()) { std::cerr << " use tags from outer ways\n"; } std::set ways; - for (const auto& ring : m_outer_rings) { - ring->get_ways(ways); + for (const auto& ring : m_rings) { + if (ring.is_outer()) { + ring.get_ways(ways); + } } if (ways.size() == 1) { if (debug()) { std::cerr << " only one outer way\n"; } - osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder); - for (const osmium::Tag& tag : (*ways.begin())->tags()) { - tl_builder.add_tag(tag.key(), tag.value()); - } + builder.add_item(&(*ways.cbegin())->tags()); } else { if (debug()) { std::cerr << " multiple outer ways, get common tags\n"; @@ -219,206 +384,12 @@ namespace osmium { } } - /** - * Go through all the rings and find rings that are not closed. - * Problems are reported through the problem reporter. - * - * @returns true if any rings were not closed, false otherwise - */ - bool check_for_open_rings() { - bool open_rings = false; - - for (const auto& ring : m_rings) { - if (!ring.closed()) { - open_rings = true; - if (m_config.problem_reporter) { - m_config.problem_reporter->report_ring_not_closed(ring.get_node_ref_front().location(), ring.get_node_ref_back().location()); - } - } - } - - return open_rings; - } - - /** - * Check whether there are any rings that can be combined with the - * given ring to one larger ring by appending the other ring to - * the end of this ring. - * If the rings can be combined they are and the function returns - * true. - */ - bool possibly_combine_rings_back(ProtoRing& ring) { - const osmium::NodeRef& nr = ring.get_node_ref_back(); - - if (debug()) { - std::cerr << " possibly_combine_rings_back()\n"; - } - for (auto it = m_rings.begin(); it != m_rings.end(); ++it) { - if (&*it != &ring && !it->closed()) { - if (has_same_location(nr, it->get_node_ref_front())) { - if (debug()) { - std::cerr << " ring.last=it->first\n"; - } - ring.merge_ring(*it, debug()); - m_rings.erase(it); - return true; - } - if (has_same_location(nr, it->get_node_ref_back())) { - if (debug()) { - std::cerr << " ring.last=it->last\n"; - } - ring.merge_ring_reverse(*it, debug()); - m_rings.erase(it); - return true; - } - } - } - return false; - } - - /** - * Check whether there are any rings that can be combined with the - * given ring to one larger ring by prepending the other ring to - * the start of this ring. - * If the rings can be combined they are and the function returns - * true. - */ - bool possibly_combine_rings_front(ProtoRing& ring) { - const osmium::NodeRef& nr = ring.get_node_ref_front(); - - if (debug()) { - std::cerr << " possibly_combine_rings_front()\n"; - } - for (auto it = m_rings.begin(); it != m_rings.end(); ++it) { - if (&*it != &ring && !it->closed()) { - if (has_same_location(nr, it->get_node_ref_back())) { - if (debug()) { - std::cerr << " ring.first=it->last\n"; - } - ring.swap_segments(*it); - ring.merge_ring(*it, debug()); - m_rings.erase(it); - return true; - } - if (has_same_location(nr, it->get_node_ref_front())) { - if (debug()) { - std::cerr << " ring.first=it->first\n"; - } - ring.reverse(); - ring.merge_ring(*it, debug()); - m_rings.erase(it); - return true; - } - } - } - return false; - } - - void split_off_subring(osmium::area::detail::ProtoRing& ring, osmium::area::detail::ProtoRing::segments_type::iterator it, osmium::area::detail::ProtoRing::segments_type::iterator it_begin, osmium::area::detail::ProtoRing::segments_type::iterator it_end) { - if (debug()) { - std::cerr << " subring found at: " << *it << "\n"; - } - ProtoRing new_ring(it_begin, it_end); - ring.remove_segments(it_begin, it_end); - if (debug()) { - std::cerr << " split into two rings:\n"; - std::cerr << " " << new_ring << "\n"; - std::cerr << " " << ring << "\n"; - } - m_rings.push_back(std::move(new_ring)); - } - - bool has_closed_subring_back(ProtoRing& ring, const NodeRef& nr) { - if (ring.segments().size() < 3) { - return false; - } - if (debug()) { - std::cerr << " has_closed_subring_back()\n"; - } - const auto end = ring.segments().end(); - for (auto it = ring.segments().begin() + 1; it != end - 1; ++it) { - if (has_same_location(nr, it->first())) { - split_off_subring(ring, it, it, end); - return true; - } - } - return false; - } - - bool has_closed_subring_front(ProtoRing& ring, const NodeRef& nr) { - if (ring.segments().size() < 3) { - return false; - } - if (debug()) { - std::cerr << " has_closed_subring_front()\n"; - } - const auto end = ring.segments().end(); - for (auto it = ring.segments().begin() + 1; it != end - 1; ++it) { - if (has_same_location(nr, it->second())) { - split_off_subring(ring, it, ring.segments().begin(), it+1); - return true; - } - } - return false; - } - - bool check_for_closed_subring(ProtoRing& ring) { - if (debug()) { - std::cerr << " check_for_closed_subring()\n"; - } - - osmium::area::detail::ProtoRing::segments_type segments(ring.segments().size()); - std::copy(ring.segments().cbegin(), ring.segments().cend(), segments.begin()); - std::sort(segments.begin(), segments.end()); - const auto it = std::adjacent_find(segments.begin(), segments.end(), [this](const osmium::area::detail::NodeRefSegment& s1, const osmium::area::detail::NodeRefSegment& s2) { - return has_same_location(s1.first(), s2.first()); - }); - if (it == segments.end()) { - return false; - } - const auto r1 = std::find_first_of(ring.segments().begin(), ring.segments().end(), it, it+1); - assert(r1 != ring.segments().end()); - const auto r2 = std::find_first_of(ring.segments().begin(), ring.segments().end(), it+1, it+2); - assert(r2 != ring.segments().end()); - - if (debug()) { - std::cerr << " found subring in ring " << ring << " at " << it->first() << "\n"; - } - - const auto m = std::minmax(r1, r2); - - ProtoRing new_ring(m.first, m.second); - ring.remove_segments(m.first, m.second); - - if (debug()) { - std::cerr << " split ring1=" << new_ring << "\n"; - std::cerr << " split ring2=" << ring << "\n"; - } - - m_rings.emplace_back(new_ring); - - return true; - } - - void combine_rings_front(const osmium::area::detail::NodeRefSegment& segment, ProtoRing& ring) { - if (debug()) { - std::cerr << " => match at front of ring\n"; - } - ring.add_segment_front(segment); - has_closed_subring_front(ring, segment.first()); - if (possibly_combine_rings_front(ring)) { - check_for_closed_subring(ring); - } - } - - void combine_rings_back(const osmium::area::detail::NodeRefSegment& segment, ProtoRing& ring) { - if (debug()) { - std::cerr << " => match at back of ring\n"; - } - ring.add_segment_back(segment); - has_closed_subring_back(ring, segment.second()); - if (possibly_combine_rings_back(ring)) { - check_for_closed_subring(ring); + template + static void build_ring_from_proto_ring(osmium::builder::AreaBuilder& builder, const detail::ProtoRing& ring) { + TBuilder ring_builder(builder.buffer(), &builder); + ring_builder.add_node_ref(ring.get_node_ref_start()); + for (const auto& segment : ring.segments()) { + ring_builder.add_node_ref(segment->stop()); } } @@ -427,286 +398,1073 @@ namespace osmium { * area in the buffer. */ void add_rings_to_area(osmium::builder::AreaBuilder& builder) const { - for (const ProtoRing* ring : m_outer_rings) { - if (debug()) { - std::cerr << " ring " << *ring << " is outer\n"; - } - { - osmium::builder::OuterRingBuilder ring_builder(builder.buffer(), &builder); - ring_builder.add_node_ref(ring->get_node_ref_front()); - for (const auto& segment : ring->segments()) { - ring_builder.add_node_ref(segment.second()); + for (const detail::ProtoRing& ring : m_rings) { + if (ring.is_outer()) { + build_ring_from_proto_ring(builder, ring); + for (const detail::ProtoRing* inner : ring.inner_rings()) { + build_ring_from_proto_ring(builder, *inner); } } - for (ProtoRing* inner : ring->inner_rings()) { - osmium::builder::InnerRingBuilder ring_builder(builder.buffer(), &builder); - ring_builder.add_node_ref(inner->get_node_ref_front()); - for (const auto& segment : inner->segments()) { - ring_builder.add_node_ref(segment.second()); - } - } - } - } - - bool add_to_existing_ring(osmium::area::detail::NodeRefSegment segment) { - int n = 0; - for (auto& ring : m_rings) { - if (debug()) { - std::cerr << " check against ring " << n << " " << ring; - } - if (ring.closed()) { - if (debug()) { - std::cerr << " => ring CLOSED\n"; - } - } else { - if (has_same_location(ring.get_node_ref_back(), segment.first())) { - combine_rings_back(segment, ring); - return true; - } - if (has_same_location(ring.get_node_ref_back(), segment.second())) { - segment.swap_locations(); - combine_rings_back(segment, ring); - return true; - } - if (has_same_location(ring.get_node_ref_front(), segment.first())) { - segment.swap_locations(); - combine_rings_front(segment, ring); - return true; - } - if (has_same_location(ring.get_node_ref_front(), segment.second())) { - combine_rings_front(segment, ring); - return true; - } - if (debug()) { - std::cerr << " => no match\n"; - } - } - - ++n; - } - return false; - } - - void check_inner_outer(ProtoRing& ring) { - const osmium::NodeRef& min_node = ring.min_node(); - if (debug()) { - std::cerr << " check_inner_outer min_node=" << min_node << "\n"; - } - - int count = 0; - int above = 0; - - for (auto it = m_segment_list.begin(); it != m_segment_list.end() && it->first().location().x() <= min_node.location().x(); ++it) { - if (!ring.contains(*it)) { - if (debug()) { - std::cerr << " segments for count: " << *it; - } - if (it->to_left_of(min_node.location())) { - ++count; - if (debug()) { - std::cerr << " counted\n"; - } - } else { - if (debug()) { - std::cerr << " not counted\n"; - } - } - if (it->first().location() == min_node.location()) { - if (it->second().location().y() > min_node.location().y()) { - ++above; - } - } - if (it->second().location() == min_node.location()) { - if (it->first().location().y() > min_node.location().y()) { - ++above; - } - } - } - } - - if (debug()) { - std::cerr << " count=" << count << " above=" << above << "\n"; - } - - count += above % 2; - - if (count % 2) { - ring.set_inner(); } } void check_inner_outer_roles() { if (debug()) { - std::cerr << " check_inner_outer_roles\n"; + std::cerr << " Checking inner/outer roles\n"; } - for (const auto ringptr : m_outer_rings) { - for (const auto& segment : ringptr->segments()) { - if (!segment.role_outer()) { - ++m_inner_outer_mismatches; + std::unordered_map way_rings; + std::unordered_set ways_in_multiple_rings; + + for (const detail::ProtoRing& ring : m_rings) { + for (const auto& segment : ring.segments()) { + assert(segment->way()); + + if (!segment->role_empty() && (ring.is_outer() ? !segment->role_outer() : !segment->role_inner())) { + ++m_stats.wrong_role; if (debug()) { - std::cerr << " segment " << segment << " from way " << segment.way()->id() << " should have role 'outer'\n"; + std::cerr << " Segment " << *segment << " from way " << segment->way()->id() << " has role '" << segment->role_name() + << "', but should have role '" << (ring.is_outer() ? "outer" : "inner") << "'\n"; } if (m_config.problem_reporter) { - m_config.problem_reporter->report_role_should_be_outer(segment.way()->id(), segment.first().location(), segment.second().location()); + if (ring.is_outer()) { + m_config.problem_reporter->report_role_should_be_outer(segment->way()->id(), segment->first().location(), segment->second().location()); + } else { + m_config.problem_reporter->report_role_should_be_inner(segment->way()->id(), segment->first().location(), segment->second().location()); + } + } + } + + auto& r = way_rings[segment->way()]; + if (!r) { + r = ˚ + } else if (r != &ring) { + ways_in_multiple_rings.insert(segment->way()); + } + + } + } + + for (const osmium::Way* way : ways_in_multiple_rings) { + ++m_stats.ways_in_multiple_rings; + if (debug()) { + std::cerr << " Way " << way->id() << " is in multiple rings\n"; + } + if (m_config.problem_reporter) { + m_config.problem_reporter->report_way_in_multiple_rings(*way); + } + } + + } + + detail::NodeRefSegment* get_next_segment(const osmium::Location& location) { + auto it = std::lower_bound(m_locations.begin(), m_locations.end(), slocation{}, [this, &location](const slocation& a, const slocation& b) { + return a.location(m_segment_list, location) < b.location(m_segment_list, location); + }); + + assert(it != m_locations.end()); + if (m_segment_list[it->item].is_done()) { + ++it; + } + assert(it != m_locations.end()); + + assert(!m_segment_list[it->item].is_done()); + return &m_segment_list[it->item]; + } + + class rings_stack_element { + + int32_t m_y; + detail::ProtoRing* m_ring_ptr; + + public: + + rings_stack_element(int32_t y, detail::ProtoRing* ring_ptr) : + m_y(y), + m_ring_ptr(ring_ptr) { + } + + int32_t y() const noexcept { + return m_y; + } + + const detail::ProtoRing& ring() const noexcept { + return *m_ring_ptr; + } + + detail::ProtoRing* ring_ptr() noexcept { + return m_ring_ptr; + } + + bool operator==(const rings_stack_element& rhs) const noexcept { + return m_ring_ptr == rhs.m_ring_ptr; + } + + bool operator<(const rings_stack_element& rhs) const noexcept { + return m_y < rhs.m_y; + } + + }; // class ring_stack_element + + using rings_stack = std::vector; + + void remove_duplicates(rings_stack& outer_rings) { + while (true) { + const auto it = std::adjacent_find(outer_rings.begin(), outer_rings.end()); + if (it == outer_rings.end()) { + return; + } + outer_rings.erase(it, std::next(it, 2)); + } + } + + detail::ProtoRing* find_enclosing_ring(detail::NodeRefSegment* segment) { + if (debug()) { + std::cerr << " Looking for ring enclosing " << *segment << "\n"; + } + + const auto location = segment->first().location(); + const auto end_location = segment->second().location(); + + while (segment->first().location() == location) { + if (segment == &m_segment_list.back()) { + break; + } + ++segment; + } + + int nesting = 0; + + rings_stack outer_rings; + while (segment >= &m_segment_list.front()) { + if (!segment->is_direction_done()) { + --segment; + continue; + } + if (debug()) { + std::cerr << " Checking against " << *segment << "\n"; + } + const osmium::Location& a = segment->first().location(); + const osmium::Location& b = segment->second().location(); + + if (segment->first().location() == location) { + const int64_t ax = a.x(); + const int64_t bx = b.x(); + const int64_t lx = end_location.x(); + const int64_t ay = a.y(); + const int64_t by = b.y(); + const int64_t ly = end_location.y(); + const auto z = (bx - ax)*(ly - ay) - (by - ay)*(lx - ax); + if (debug()) { + std::cerr << " Segment XXXX z=" << z << "\n"; + } + if (z > 0) { + nesting += segment->is_reverse() ? -1 : 1; + if (debug()) { + std::cerr << " Segment is below (nesting=" << nesting << ")\n"; + } + if (segment->ring()->is_outer()) { + if (debug()) { + std::cerr << " Segment belongs to outer ring\n"; + } + outer_rings.emplace_back(a.y(), segment->ring()); + } + } + } else if (a.x() <= location.x() && location.x() < b.x()) { + if (debug()) { + std::cerr << " Is in x range\n"; + } + + const int64_t ax = a.x(); + const int64_t bx = b.x(); + const int64_t lx = location.x(); + const int64_t ay = a.y(); + const int64_t by = b.y(); + const int64_t ly = location.y(); + const auto z = (bx - ax)*(ly - ay) - (by - ay)*(lx - ax); + + if (z >= 0) { + nesting += segment->is_reverse() ? -1 : 1; + if (debug()) { + std::cerr << " Segment is below (nesting=" << nesting << ")\n"; + } + if (segment->ring()->is_outer()) { + if (debug()) { + std::cerr << " Segment belongs to outer ring\n"; + } + const int32_t y = int32_t(ay + (by - ay) * (lx - ax) / (bx - ax)); + outer_rings.emplace_back(y, segment->ring()); + } + } + } + --segment; + } + + if (nesting % 2 == 0) { + if (debug()) { + std::cerr << " Decided that this is an outer ring\n"; + } + return nullptr; + } else { + if (debug()) { + std::cerr << " Decided that this is an inner ring\n"; + } + assert(!outer_rings.empty()); + + std::sort(outer_rings.rbegin(), outer_rings.rend()); + if (debug()) { + for (const auto& o : outer_rings) { + std::cerr << " y=" << o.y() << " " << o.ring() << "\n"; + } + } + + remove_duplicates(outer_rings); + if (debug()) { + std::cerr << " after remove duplicates:\n"; + for (const auto& o : outer_rings) { + std::cerr << " y=" << o.y() << " " << o.ring() << "\n"; + } + } + + assert(!outer_rings.empty()); + return outer_rings.front().ring_ptr(); + } + } + + bool is_split_location(const osmium::Location& location) const noexcept { + return std::find(m_split_locations.cbegin(), m_split_locations.cend(), location) != m_split_locations.cend(); + } + + uint32_t add_new_ring(slocation& node) { + detail::NodeRefSegment* segment = &m_segment_list[node.item]; + assert(!segment->is_done()); + + if (debug()) { + std::cerr << " Starting new ring at location " << node.location(m_segment_list) << " with segment " << *segment << "\n"; + } + + if (node.reverse) { + segment->reverse(); + } + + detail::ProtoRing* outer_ring = nullptr; + + if (segment != &m_segment_list.front()) { + outer_ring = find_enclosing_ring(segment); + } + segment->mark_direction_done(); + + m_rings.emplace_back(segment); + detail::ProtoRing* ring = &m_rings.back(); + if (outer_ring) { + if (debug()) { + std::cerr << " This is an inner ring. Outer ring is " << *outer_ring << "\n"; + } + outer_ring->add_inner_ring(ring); + ring->set_outer_ring(outer_ring); + } else if (debug()) { + std::cerr << " This is an outer ring\n"; + } + + const osmium::Location& first_location = node.location(m_segment_list); + osmium::Location last_location = segment->stop().location(); + + uint32_t nodes = 1; + while (first_location != last_location) { + ++nodes; + detail::NodeRefSegment* next_segment = get_next_segment(last_location); + next_segment->mark_direction_done(); + if (next_segment->start().location() != last_location) { + next_segment->reverse(); + } + ring->add_segment_back(next_segment); + if (debug()) { + std::cerr << " Next segment is " << *next_segment << "\n"; + } + last_location = next_segment->stop().location(); + } + + ring->fix_direction(); + + if (debug()) { + std::cerr << " Completed ring: " << *ring << "\n"; + } + + return nodes; + } + + uint32_t add_new_ring_complex(slocation& node) { + detail::NodeRefSegment* segment = &m_segment_list[node.item]; + assert(!segment->is_done()); + + if (debug()) { + std::cerr << " Starting new ring at location " << node.location(m_segment_list) << " with segment " << *segment << "\n"; + } + + if (node.reverse) { + segment->reverse(); + } + + m_rings.emplace_back(segment); + detail::ProtoRing* ring = &m_rings.back(); + + const osmium::Location& first_location = node.location(m_segment_list); + osmium::Location last_location = segment->stop().location(); + + uint32_t nodes = 1; + while (first_location != last_location && !is_split_location(last_location)) { + ++nodes; + detail::NodeRefSegment* next_segment = get_next_segment(last_location); + if (next_segment->start().location() != last_location) { + next_segment->reverse(); + } + ring->add_segment_back(next_segment); + if (debug()) { + std::cerr << " Next segment is " << *next_segment << "\n"; + } + last_location = next_segment->stop().location(); + } + + if (debug()) { + if (first_location == last_location) { + std::cerr << " Completed ring: " << *ring << "\n"; + } else { + std::cerr << " Completed partial ring: " << *ring << "\n"; + } + } + + return nodes; + } + + void create_locations_list() { + m_locations.reserve(m_segment_list.size() * 2); + + for (uint32_t n = 0; n < m_segment_list.size(); ++n) { + m_locations.emplace_back(n, false); + m_locations.emplace_back(n, true); + } + + std::stable_sort(m_locations.begin(), m_locations.end(), [this](const slocation& a, const slocation& b) { + return a.location(m_segment_list) < b.location(m_segment_list); + }); + } + + void find_inner_outer_complex(detail::ProtoRing* ring) { + detail::ProtoRing* outer_ring = find_enclosing_ring(ring->min_segment()); + if (outer_ring) { + outer_ring->add_inner_ring(ring); + ring->set_outer_ring(outer_ring); + } + ring->fix_direction(); + ring->mark_direction_done(); + } + + void find_inner_outer_complex() { + if (debug()) { + std::cerr << " Finding inner/outer rings\n"; + } + std::vector rings; + rings.reserve(m_rings.size()); + for (auto& ring : m_rings) { + if (ring.closed()) { + rings.push_back(&ring); + } + } + + if (rings.empty()) { + return; + } + + std::sort(rings.begin(), rings.end(), [](detail::ProtoRing* a, detail::ProtoRing* b) { + return a->min_segment() < b->min_segment(); + }); + + rings.front()->fix_direction(); + rings.front()->mark_direction_done(); + if (debug()) { + std::cerr << " First ring is outer: " << *rings.front() << "\n"; + } + for (auto it = std::next(rings.begin()); it != rings.end(); ++it) { + if (debug()) { + std::cerr << " Checking (at min segment " << *((*it)->min_segment()) << ") ring " << **it << "\n"; + } + find_inner_outer_complex(*it); + if (debug()) { + std::cerr << " Ring is " << ((*it)->is_outer() ? "OUTER: " : "INNER: ") << **it << "\n"; + } + } + } + + /** + * Finds all locations where more than two segments meet. If there + * are any open rings found along the way, they are reported + * and the function returns false. + */ + bool find_split_locations() { + osmium::Location previous_location; + for (auto it = m_locations.cbegin(); it != m_locations.cend(); ++it) { + const osmium::NodeRef& nr = it->node_ref(m_segment_list); + const osmium::Location& loc = nr.location(); + if (std::next(it) == m_locations.cend() || loc != std::next(it)->location(m_segment_list)) { + if (debug()) { + std::cerr << " Found open ring at " << nr << "\n"; + } + if (m_config.problem_reporter) { + const auto& segment = m_segment_list[it->item]; + m_config.problem_reporter->report_ring_not_closed(nr, segment.way()); + } + ++m_stats.open_rings; + } else { + if (loc == previous_location && (m_split_locations.empty() || m_split_locations.back() != previous_location )) { + m_split_locations.push_back(previous_location); + } + ++it; + if (it == m_locations.end()) { + break; + } + } + previous_location = loc; + } + return m_stats.open_rings == 0; + } + + void create_rings_simple_case() { + auto count_remaining = m_segment_list.size(); + for (slocation& sl : m_locations) { + const detail::NodeRefSegment& segment = m_segment_list[sl.item]; + if (!segment.is_done()) { + count_remaining -= add_new_ring(sl); + if (count_remaining == 0) { + return; + } + } + } + } + + std::vector create_location_to_ring_map(open_ring_its_type& open_ring_its) { + std::vector xrings; + xrings.reserve(open_ring_its.size() * 2); + + for (auto it = open_ring_its.begin(); it != open_ring_its.end(); ++it) { + if (debug()) { + std::cerr << " Ring: " << **it << "\n"; + } + xrings.emplace_back((*it)->get_node_ref_start().location(), it, true); + xrings.emplace_back((*it)->get_node_ref_stop().location(), it, false); + } + + std::sort(xrings.begin(), xrings.end()); + + return xrings; + } + + void merge_two_rings(open_ring_its_type& open_ring_its, const location_to_ring_map& m1, const location_to_ring_map& m2) { + auto& r1 = *m1.ring_it; + auto& r2 = *m2.ring_it; + + if (r1->get_node_ref_stop().location() == r2->get_node_ref_start().location()) { + r1->join_forward(*r2); + } else if (r1->get_node_ref_stop().location() == r2->get_node_ref_stop().location()) { + r1->join_backward(*r2); + } else if (r1->get_node_ref_start().location() == r2->get_node_ref_start().location()) { + r1->reverse(); + r1->join_forward(*r2); + } else if (r1->get_node_ref_start().location() == r2->get_node_ref_stop().location()) { + r1->reverse(); + r1->join_backward(*r2); + } else { + assert(false); + } + + m_rings.erase(r2); + open_ring_its.remove(r2); + + if (r1->closed()) { + open_ring_its.remove(r1); + } + } + + bool try_to_merge(open_ring_its_type& open_ring_its) { + if (open_ring_its.empty()) { + return false; + } + + if (debug()) { + std::cerr << " Trying to merge " << open_ring_its.size() << " open rings\n"; + } + + std::vector xrings = create_location_to_ring_map(open_ring_its); + + auto it = xrings.cbegin(); + while (it != xrings.cend()) { + it = std::adjacent_find(it, xrings.cend()); + if (it == xrings.cend()) { + return false; + } + auto after = std::next(it, 2); + if (after == xrings.cend() || after->location != it->location) { + if (debug()) { + std::cerr << " Merging two rings\n"; + } + merge_two_rings(open_ring_its, *it, *std::next(it)); + return true; + } + while (it != xrings.cend() && it->location == after->location) { + ++it; + } + } + + return false; + } + + bool there_are_open_rings() const noexcept { + return std::any_of(m_rings.cbegin(), m_rings.cend(), [](const detail::ProtoRing& ring){ + return !ring.closed(); + }); + } + + struct candidate { + int64_t sum; + std::vector> rings; + osmium::Location start_location; + osmium::Location stop_location; + + explicit candidate(location_to_ring_map& ring, bool reverse) : + sum(ring.ring().sum()), + rings(), + start_location(ring.ring().get_node_ref_start().location()), + stop_location(ring.ring().get_node_ref_stop().location()) { + rings.emplace_back(ring, reverse); + } + + bool closed() const noexcept { + return start_location == stop_location; + } + + }; + + void find_candidates(std::vector& candidates, std::unordered_set& loc_done, const std::vector& xrings, candidate& cand) { + if (debug()) { + std::cerr << " find_candidates sum=" << cand.sum << " start=" << cand.start_location << " stop=" << cand.stop_location << "\n"; + for (const auto& ring : cand.rings) { + std::cerr << " " << ring.first.ring() << (ring.second ? " reverse" : "") << "\n"; + } + } + + const auto connections = make_range(std::equal_range(xrings.cbegin(), + xrings.cend(), + location_to_ring_map{cand.stop_location})); + + assert(connections.begin() != connections.end()); + + assert(!cand.rings.empty()); + const detail::ProtoRing* ring_leading_here = &cand.rings.back().first.ring(); + for (const location_to_ring_map& m : connections) { + const detail::ProtoRing& ring = m.ring(); + + if (&ring != ring_leading_here) { + if (debug()) { + std::cerr << " next possible connection: " << ring << (m.start ? "" : " reverse") << "\n"; + } + + candidate c = cand; + if (m.start) { + c.rings.emplace_back(m, false); + c.stop_location = ring.get_node_ref_stop().location(); + c.sum += ring.sum(); + } else { + c.rings.emplace_back(m, true); + c.stop_location = ring.get_node_ref_start().location(); + c.sum -= ring.sum(); + } + if (c.closed()) { + if (debug()) { + std::cerr << " found candidate\n"; + } + candidates.push_back(c); + } else if (loc_done.count(c.stop_location) == 0) { + if (debug()) { + std::cerr << " recurse...\n"; + } + loc_done.insert(c.stop_location); + find_candidates(candidates, loc_done, xrings, c); + if (debug()) { + std::cerr << " ...back\n"; + } + } else if (debug()) { + std::cerr << " loop found\n"; + } + } + } + } + + /** + * If there are multiple open rings and mltiple ways to join them, + * this function is called. It will take the first open ring and + * try recursively all ways of closing it. Of all the candidates + * the one with the smallest area is chosen and closed. If it + * can't close this ring, an error is reported and the function + * returns false. + */ + bool join_connected_rings(open_ring_its_type& open_ring_its) { + assert(!open_ring_its.empty()); + + if (debug()) { + std::cerr << " Trying to merge " << open_ring_its.size() << " open rings\n"; + } + + std::vector xrings = create_location_to_ring_map(open_ring_its); + + const auto ring_min = std::min_element(xrings.begin(), xrings.end(), [](const location_to_ring_map& a, const location_to_ring_map& b) { + return a.ring().min_segment() < b.ring().min_segment(); + }); + + find_inner_outer_complex(); + detail::ProtoRing* outer_ring = find_enclosing_ring(ring_min->ring().min_segment()); + bool ring_min_is_outer = !outer_ring; + if (debug()) { + std::cerr << " Open ring is " << (ring_min_is_outer ? "outer" : "inner") << " ring\n"; + } + for (auto& ring : m_rings) { + ring.reset(); + } + + candidate cand{*ring_min, false}; + + // Locations we have visited while finding candidates, used + // to detect loops. + std::unordered_set loc_done; + + loc_done.insert(cand.stop_location); + + std::vector candidates; + find_candidates(candidates, loc_done, xrings, cand); + + if (candidates.empty()) { + if (debug()) { + std::cerr << " Found no candidates\n"; + } + if (!open_ring_its.empty()) { + ++m_stats.open_rings; + if (m_config.problem_reporter) { + for (auto& it : open_ring_its) { + m_config.problem_reporter->report_ring_not_closed(it->get_node_ref_start()); + m_config.problem_reporter->report_ring_not_closed(it->get_node_ref_stop()); + } + } + } + return false; + } + + if (debug()) { + std::cerr << " Found candidates:\n"; + for (const auto& cand : candidates) { + std::cerr << " sum=" << cand.sum << "\n"; + for (const auto& ring : cand.rings) { + std::cerr << " " << ring.first.ring() << (ring.second ? " reverse" : "") << "\n"; + } + } + } + + // Find the candidate with the smallest/largest area + const auto chosen_cand = ring_min_is_outer ? + std::min_element(candidates.cbegin(), candidates.cend(), [](const candidate& a, const candidate& b) { + return std::abs(a.sum) < std::abs(b.sum); + }) : + std::max_element(candidates.cbegin(), candidates.cend(), [](const candidate& a, const candidate& b) { + return std::abs(a.sum) < std::abs(b.sum); + }); + + if (debug()) { + std::cerr << " Decided on: sum=" << chosen_cand->sum << "\n"; + for (const auto& ring : chosen_cand->rings) { + std::cerr << " " << ring.first.ring() << (ring.second ? " reverse" : "") << "\n"; + } + } + + // Join all (open) rings in the candidate to get one closed ring. + assert(chosen_cand->rings.size() > 1); + const auto& first_ring = chosen_cand->rings.front().first; + for (auto it = chosen_cand->rings.begin() + 1; it != chosen_cand->rings.end(); ++it) { + merge_two_rings(open_ring_its, first_ring, it->first); + } + + if (debug()) { + std::cerr << " Merged to " << first_ring.ring() << "\n"; + } + + return true; + } + + bool create_rings_complex_case() { + // First create all the (partial) rings starting at the split locations + auto count_remaining = m_segment_list.size(); + for (const osmium::Location& location : m_split_locations) { + const auto locs = make_range(std::equal_range(m_locations.begin(), + m_locations.end(), + slocation{}, + [this, &location](const slocation& a, const slocation& b) { + return a.location(m_segment_list, location) < b.location(m_segment_list, location); + })); + for (auto& loc : locs) { + if (!m_segment_list[loc.item].is_done()) { + count_remaining -= add_new_ring_complex(loc); + if (count_remaining == 0) { + break; } } } } - for (const auto ringptr : m_inner_rings) { - for (const auto& segment : ringptr->segments()) { - if (!segment.role_inner()) { - ++m_inner_outer_mismatches; - if (debug()) { - std::cerr << " segment " << segment << " from way " << segment.way()->id() << " should have role 'inner'\n"; - } - if (m_config.problem_reporter) { - m_config.problem_reporter->report_role_should_be_inner(segment.way()->id(), segment.first().location(), segment.second().location()); + + // Now find all the rest of the rings (ie not starting at split locations) + if (count_remaining > 0) { + for (slocation& sl : m_locations) { + const detail::NodeRefSegment& segment = m_segment_list[sl.item]; + if (!segment.is_done()) { + count_remaining -= add_new_ring_complex(sl); + if (count_remaining == 0) { + break; } } } } + + // Now all segments are in exactly one (partial) ring. + + // If there are open rings, try to join them to create closed + // rings. + if (there_are_open_rings()) { + ++m_stats.area_really_complex_case; + + open_ring_its_type open_ring_its; + for (auto it = m_rings.begin(); it != m_rings.end(); ++it) { + if (!it->closed()) { + open_ring_its.push_back(it); + } + } + + while (!open_ring_its.empty()) { + if (debug()) { + std::cerr << " There are " << open_ring_its.size() << " open rings\n"; + } + while (try_to_merge(open_ring_its)); + + if (!open_ring_its.empty()) { + if (debug()) { + std::cerr << " After joining obvious cases there are still " << open_ring_its.size() << " open rings\n"; + } + if (!join_connected_rings(open_ring_its)) { + return false; + } + } + } + + if (debug()) { + std::cerr << " Joined all open rings\n"; + } + } + + // Now all rings are complete. + + find_inner_outer_complex(); + + return true; + } + + /** + * Checks if any ways were completely removed in the + * erase_duplicate_segments step. + */ + bool ways_were_lost() { + std::unordered_set ways_in_segments; + + for (const auto& segment : m_segment_list) { + ways_in_segments.insert(segment.way()); + } + + return ways_in_segments.size() < m_num_members; } /** * Create rings from segments. */ bool create_rings() { + m_stats.nodes += m_segment_list.size(); + + // Sort the list of segments (from left to right and bottom + // to top). + osmium::Timer timer_sort; m_segment_list.sort(); - m_segment_list.erase_duplicate_segments(); + timer_sort.stop(); + + // Remove duplicate segments. Removal is in pairs, so if there + // are two identical segments, they will both be removed. If + // there are three, two will be removed and one remains. + osmium::Timer timer_dupl; + m_stats.duplicate_segments = m_segment_list.erase_duplicate_segments(m_config.problem_reporter); + timer_dupl.stop(); + + // If there are no segments left at this point, this isn't + // a valid area. + if (m_segment_list.empty()) { + if (debug()) { + std::cerr << " No segments left\n"; + } + return false; + } + + // If one or more complete ways was removed because of + // duplicate segments, this isn't a valid area. + if (ways_were_lost()) { + if (debug()) { + std::cerr << " Complete ways removed because of duplicate segments\n"; + } + return false; + } + + if (m_config.debug_level >= 3) { + std::cerr << "Sorted de-duplicated segment list:\n"; + for (const auto& s : m_segment_list) { + std::cerr << " " << s << "\n"; + } + } // Now we look for segments crossing each other. If there are // any, the multipolygon is invalid. // In the future this could be improved by trying to fix those // cases. - if (m_segment_list.find_intersections(m_config.problem_reporter)) { + osmium::Timer timer_intersection; + m_stats.intersections = m_segment_list.find_intersections(m_config.problem_reporter); + timer_intersection.stop(); + + if (m_stats.intersections) { return false; } - // Now iterator over all segments and add them to rings. Each segment - // is tacked on to either end of an existing ring if possible, or a - // new ring is started with it. - for (const auto& segment : m_segment_list) { + // This creates an ordered list of locations of both endpoints + // of all segments with pointers back to the segments. We will + // use this list later to quickly find which segment(s) fits + // onto a known segment. + osmium::Timer timer_locations_list; + create_locations_list(); + timer_locations_list.stop(); + + // Find all locations where more than two segments start or + // end. We call those "split" locations. If there are any + // "spike" segments found while doing this, we know the area + // geometry isn't valid and return. + osmium::Timer timer_split; + if (!find_split_locations()) { + return false; + } + timer_split.stop(); + + // Now report all split locations to the problem reporter. + m_stats.touching_rings += m_split_locations.size(); + if (!m_split_locations.empty()) { if (debug()) { - std::cerr << " checking segment " << segment << "\n"; + std::cerr << " Found split locations:\n"; } - if (!add_to_existing_ring(segment)) { + for (const auto& location : m_split_locations) { + if (m_config.problem_reporter) { + auto it = std::lower_bound(m_locations.cbegin(), m_locations.cend(), slocation{}, [this, &location](const slocation& a, const slocation& b) { + return a.location(m_segment_list, location) < b.location(m_segment_list, location); + }); + assert(it != m_locations.cend()); + const osmium::object_id_type id = it->node_ref(m_segment_list).ref(); + m_config.problem_reporter->report_touching_ring(id, location); + } if (debug()) { - std::cerr << " new ring for segment " << segment << "\n"; + std::cerr << " " << location << "\n"; } - m_rings.emplace_back(segment); } } - if (debug()) { - std::cerr << " Rings:\n"; - for (const auto& ring : m_rings) { - std::cerr << " " << ring; - if (ring.closed()) { - std::cerr << " (closed)"; - } - std::cerr << "\n"; - } - } - - if (check_for_open_rings()) { + // From here on we use two different algorithms depending on + // whether there were any split locations or not. If there + // are no splits, we use the faster "simple algorithm", if + // there are, we use the slower "complex algorithm". + osmium::Timer timer_simple_case; + osmium::Timer timer_complex_case; + if (m_split_locations.empty()) { if (debug()) { - std::cerr << " not all rings are closed\n"; + std::cerr << " No split locations -> using simple algorithm\n"; } - return false; - } + ++m_stats.area_simple_case; - if (debug()) { - std::cerr << " Find inner/outer...\n"; - } - - if (m_rings.size() == 1) { - m_outer_rings.push_back(&m_rings.front()); + timer_simple_case.start(); + create_rings_simple_case(); + timer_simple_case.stop(); } else { - for (auto& ring : m_rings) { - check_inner_outer(ring); - if (ring.outer()) { - if (!ring.is_cw()) { - ring.reverse(); - } - m_outer_rings.push_back(&ring); - } else { - if (ring.is_cw()) { - ring.reverse(); - } - m_inner_rings.push_back(&ring); - } + if (debug()) { + std::cerr << " Found split locations -> using complex algorithm\n"; } + ++m_stats.area_touching_rings_case; - if (m_outer_rings.size() == 1) { - for (auto inner : m_inner_rings) { - m_outer_rings.front()->add_inner_ring(inner); - } - } else { - // sort outer rings by size, smallest first - std::sort(m_outer_rings.begin(), m_outer_rings.end(), [](ProtoRing* a, ProtoRing* b) { - return a->area() < b->area(); - }); - for (auto inner : m_inner_rings) { - for (auto outer : m_outer_rings) { - if (inner->is_in(outer)) { - outer->add_inner_ring(inner); - break; - } - } - } + timer_complex_case.start(); + if (!create_rings_complex_case()) { + return false; } + timer_complex_case.stop(); } - check_inner_outer_roles(); + // If the assembler was so configured, now check whether the + // member roles are correctly tagged. + if (m_config.check_roles && m_stats.from_relations) { + osmium::Timer timer_roles; + check_inner_outer_roles(); + timer_roles.stop(); + } + + m_stats.outer_rings = std::count_if(m_rings.cbegin(), m_rings.cend(), [](const detail::ProtoRing& ring){ + return ring.is_outer(); + }); + m_stats.inner_rings = m_rings.size() - m_stats.outer_rings; + +#ifdef OSMIUM_WITH_TIMER + std::cout << m_stats.nodes << ' ' << m_stats.outer_rings << ' ' << m_stats.inner_rings << + ' ' << timer_sort.elapsed_microseconds() << + ' ' << timer_dupl.elapsed_microseconds() << + ' ' << timer_intersection.elapsed_microseconds() << + ' ' << timer_locations_list.elapsed_microseconds() << + ' ' << timer_split.elapsed_microseconds(); + + if (m_split_locations.empty()) { + std::cout << ' ' << timer_simple_case.elapsed_microseconds() << + " 0"; + } else { + std::cout << " 0" << + ' ' << timer_complex_case.elapsed_microseconds(); + } + + std::cout << +# ifdef OSMIUM_AREA_CHECK_INNER_OUTER_ROLES + ' ' << timer_roles.elapsed_microseconds() << +# else + " 0" << +# endif + '\n'; +#endif return true; } +#ifdef OSMIUM_WITH_TIMER + static bool print_header() { + std::cout << "nodes outer_rings inner_rings sort dupl intersection locations split simple_case complex_case roles_check\n"; + return true; + } + + static bool init_header() { + static bool printed_print_header = print_header(); + return printed_print_header; + } +#endif + + bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Way& way) { + osmium::builder::AreaBuilder builder(out_buffer); + builder.initialize_from_object(way); + + const bool area_okay = create_rings(); + if (area_okay || m_config.create_empty_areas) { + add_tags_to_area(builder, way); + } + if (area_okay) { + add_rings_to_area(builder); + } + + if (report_ways()) { + m_config.problem_reporter->report_way(way); + } + + return area_okay || m_config.create_empty_areas; + } + + bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Relation& relation, const std::vector& members) { + m_num_members = members.size(); + osmium::builder::AreaBuilder builder(out_buffer); + builder.initialize_from_object(relation); + + const bool area_okay = create_rings(); + if (area_okay || m_config.create_empty_areas) { + add_tags_to_area(builder, relation); + } + if (area_okay) { + add_rings_to_area(builder); + } + + if (report_ways()) { + for (const osmium::Way* way : members) { + m_config.problem_reporter->report_way(*way); + } + } + + return area_okay || m_config.create_empty_areas; + } + public: - typedef osmium::area::AssemblerConfig config_type; + using config_type = osmium::area::AssemblerConfig; explicit Assembler(const config_type& config) : m_config(config), - m_segment_list(config.debug) { + m_segment_list(config.debug_level > 1) { +#ifdef OSMIUM_WITH_TIMER + init_header(); +#endif } - ~Assembler() = default; + ~Assembler() noexcept = default; /** * Assemble an area from the given way. * The resulting area is put into the out_buffer. */ void operator()(const osmium::Way& way, osmium::memory::Buffer& out_buffer) { + if (!m_config.create_way_polygons) { + return; + } + + if (way.tags().has_tag("area", "no")) { + return; + } + if (m_config.problem_reporter) { m_config.problem_reporter->set_object(osmium::item_type::way, way.id()); + m_config.problem_reporter->set_nodes(way.nodes().size()); + } + + // Ignore (but count) ways without segments. + if (way.nodes().size() < 2) { + ++m_stats.short_ways; + return; } if (!way.ends_have_same_id()) { + ++m_stats.duplicate_nodes; if (m_config.problem_reporter) { m_config.problem_reporter->report_duplicate_node(way.nodes().front().ref(), way.nodes().back().ref(), way.nodes().front().location()); } } - m_segment_list.extract_segments_from_way(way, "outer"); + ++m_stats.from_ways; + m_stats.duplicate_nodes += m_segment_list.extract_segments_from_way(m_config.problem_reporter, way); - if (debug()) { - std::cerr << "\nBuild way id()=" << way.id() << " segments.size()=" << m_segment_list.size() << "\n"; + if (m_config.debug_level > 0) { + std::cerr << "\nAssembling way " << way.id() << " containing " << m_segment_list.size() << " nodes\n"; } // Now create the Area object and add the attributes and tags // from the way. - { - osmium::builder::AreaBuilder builder(out_buffer); - builder.initialize_from_object(way); - - if (create_rings()) { - add_tags_to_area(builder, way); - add_rings_to_area(builder); - } + if (create_area(out_buffer, way)) { + out_buffer.commit(); + } else { + out_buffer.rollback(); + } + + if (debug()) { + std::cerr << "Done: " << m_stats << "\n"; } - out_buffer.commit(); } /** @@ -714,32 +1472,62 @@ namespace osmium { * All members are to be found in the in_buffer at the offsets * given by the members parameter. * The resulting area is put into the out_buffer. + * + * @deprecated + * This function is deprecated. Use the other form of the function + * instead. */ - void operator()(const osmium::Relation& relation, const std::vector& members, const osmium::memory::Buffer& in_buffer, osmium::memory::Buffer& out_buffer) { + OSMIUM_DEPRECATED void operator()(const osmium::Relation& relation, const std::vector& members, const osmium::memory::Buffer& in_buffer, osmium::memory::Buffer& out_buffer) { + std::vector ways; + for (size_t offset : members) { + const osmium::Way& way = in_buffer.get(offset); + ways.push_back(&way); + } + operator()(relation, ways, out_buffer); + } + + /** + * Assemble an area from the given relation and its members. + * The resulting area is put into the out_buffer. + */ + void operator()(const osmium::Relation& relation, const std::vector& members, osmium::memory::Buffer& out_buffer) { + assert(relation.members().size() >= members.size()); + if (m_config.problem_reporter) { m_config.problem_reporter->set_object(osmium::item_type::relation, relation.id()); } - m_segment_list.extract_segments_from_ways(relation, members, in_buffer); - - if (debug()) { - std::cerr << "\nBuild relation id()=" << relation.id() << " members.size()=" << members.size() << " segments.size()=" << m_segment_list.size() << "\n"; + if (relation.members().empty()) { + ++m_stats.no_way_in_mp_relation; + return; } - size_t area_offset = out_buffer.committed(); + ++m_stats.from_relations; + m_stats.duplicate_nodes += m_segment_list.extract_segments_from_ways(m_config.problem_reporter, relation, members); + m_stats.member_ways = members.size(); + + if (m_stats.member_ways == 1) { + ++m_stats.single_way_in_mp_relation; + } + + if (m_config.debug_level > 0) { + std::cerr << "\nAssembling relation " << relation.id() << " containing " << members.size() << " way members with " << m_segment_list.size() << " nodes\n"; + } + + const size_t area_offset = out_buffer.committed(); // Now create the Area object and add the attributes and tags // from the relation. - { - osmium::builder::AreaBuilder builder(out_buffer); - builder.initialize_from_object(relation); - - if (create_rings()) { - add_tags_to_area(builder, relation); - add_rings_to_area(builder); + if (create_area(out_buffer, relation, members)) { + if ((m_config.create_new_style_polygons && m_stats.no_tags_on_relation == 0) || + (m_config.create_old_style_polygons && m_stats.no_tags_on_relation != 0)) { + out_buffer.commit(); + } else { + out_buffer.rollback(); } + } else { + out_buffer.rollback(); } - out_buffer.commit(); const osmium::TagList& area_tags = out_buffer.get(area_offset).tags(); // tags of the area we just built @@ -748,27 +1536,33 @@ namespace osmium { // just built, add them to a list and later build areas for // them, too. std::vector ways_that_should_be_areas; - if (m_inner_outer_mismatches == 0) { - auto memit = relation.members().begin(); - for (size_t offset : members) { - if (!std::strcmp(memit->role(), "inner")) { - const osmium::Way& way = in_buffer.get(offset); + if (m_stats.wrong_role == 0) { + detail::for_each_member(relation, members, [this, &ways_that_should_be_areas, &area_tags](const osmium::RelationMember& member, const osmium::Way& way) { + if (!std::strcmp(member.role(), "inner")) { if (!way.nodes().empty() && way.is_closed() && way.tags().size() > 0) { - auto d = std::count_if(way.tags().begin(), way.tags().end(), filter()); + const auto d = std::count_if(way.tags().cbegin(), way.tags().cend(), filter()); if (d > 0) { - osmium::tags::KeyFilter::iterator way_fi_begin(filter(), way.tags().begin(), way.tags().end()); - osmium::tags::KeyFilter::iterator way_fi_end(filter(), way.tags().end(), way.tags().end()); - osmium::tags::KeyFilter::iterator area_fi_begin(filter(), area_tags.begin(), area_tags.end()); - osmium::tags::KeyFilter::iterator area_fi_end(filter(), area_tags.end(), area_tags.end()); + osmium::tags::KeyFilter::iterator way_fi_begin(filter(), way.tags().cbegin(), way.tags().cend()); + osmium::tags::KeyFilter::iterator way_fi_end(filter(), way.tags().cend(), way.tags().cend()); + osmium::tags::KeyFilter::iterator area_fi_begin(filter(), area_tags.cbegin(), area_tags.cend()); + osmium::tags::KeyFilter::iterator area_fi_end(filter(), area_tags.cend(), area_tags.cend()); if (!std::equal(way_fi_begin, way_fi_end, area_fi_begin) || d != std::distance(area_fi_begin, area_fi_end)) { ways_that_should_be_areas.push_back(&way); + } else { + ++m_stats.inner_with_same_tags; + if (m_config.problem_reporter) { + m_config.problem_reporter->report_inner_with_same_tags(way); + } } } } } - ++memit; - } + }); + } + + if (debug()) { + std::cerr << "Done: " << m_stats << "\n"; } // Now build areas for all ways found in the last step. @@ -778,6 +1572,14 @@ namespace osmium { } } + /** + * Get statistics from assembler. Call this after running the + * assembler to get statistics and data about errors. + */ + const osmium::area::area_stats& stats() const noexcept { + return m_stats; + } + }; // class Assembler } // namespace area diff --git a/include/osmium/area/detail/node_ref_segment.hpp b/include/osmium/area/detail/node_ref_segment.hpp index 7c1555a33..b131a4317 100644 --- a/include/osmium/area/detail/node_ref_segment.hpp +++ b/include/osmium/area/detail/node_ref_segment.hpp @@ -34,11 +34,12 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include -#include #include #include +#include #include #include @@ -53,108 +54,178 @@ namespace osmium { */ namespace detail { + class ProtoRing; + + enum class role_type : uint8_t { + unknown = 0, + outer = 1, + inner = 2, + empty = 3 + }; + /** - * This helper class for the Assembler class models a segment. - * Segments are the connection between - * two nodes and they all have their smaller coordinate at the - * beginning of the segment. Smaller, in this case, means smaller x - * coordinate, and if they are the same smaller y coordinate. + * This helper class for the Assembler class models a segment, + * the connection between two nodes. + * + * Internally segments have their smaller coordinate at the + * beginning of the segment. Smaller, in this case, means smaller + * x coordinate, and, if they are the same, smaller y coordinate. */ class NodeRefSegment { + // First node in order described above. osmium::NodeRef m_first; + + // Second node in order described above. osmium::NodeRef m_second; - /// Role of the member this segment was from. - const char* m_role; - - /// Way this segment was from. + // Way this segment was from. const osmium::Way* m_way; + // The ring this segment is part of. Initially nullptr, this + // will be filled in once we know which ring the segment is in. + ProtoRing* m_ring; + + // The role of this segment from the member role. + role_type m_role; + + // Nodes have to be reversed to get the intended order. + bool m_reverse = false; + + // We found the right direction for this segment in the ring. + // (This depends on whether it is an inner or outer ring.) + bool m_direction_done = false; + public: - void swap_locations() { - using std::swap; - swap(m_first, m_second); - } - - explicit NodeRefSegment() noexcept : + NodeRefSegment() noexcept : m_first(), m_second(), - m_role(nullptr), - m_way(nullptr) { + m_way(nullptr), + m_ring(nullptr), + m_role(role_type::unknown) { } - explicit NodeRefSegment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2, const char* role, const osmium::Way* way) : + NodeRefSegment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2, role_type role = role_type::unknown, const osmium::Way* way = nullptr) noexcept : m_first(nr1), m_second(nr2), - m_role(role), - m_way(way) { + m_way(way), + m_ring(nullptr), + m_role(role) { if (nr2.location() < nr1.location()) { - swap_locations(); + using std::swap; + swap(m_first, m_second); } } - NodeRefSegment(const NodeRefSegment&) = default; - NodeRefSegment(NodeRefSegment&&) = default; + /** + * The ring this segment is a part of. nullptr if we don't + * have the ring yet. + */ + ProtoRing* ring() const noexcept { + return m_ring; + } - NodeRefSegment& operator=(const NodeRefSegment&) = default; - NodeRefSegment& operator=(NodeRefSegment&&) = default; + /** + * Returns true if the segment has already been placed in a + * ring. + */ + bool is_done() const noexcept { + return m_ring != nullptr; + } - ~NodeRefSegment() = default; + void set_ring(ProtoRing* ring) noexcept { + assert(ring); + m_ring = ring; + } - /// Return first NodeRef of Segment according to sorting order (bottom left to top right). + bool is_reverse() const noexcept { + return m_reverse; + } + + void reverse() noexcept { + m_reverse = !m_reverse; + } + + bool is_direction_done() const noexcept { + return m_direction_done; + } + + void mark_direction_done() noexcept { + m_direction_done = true; + } + + void mark_direction_not_done() noexcept { + m_direction_done = false; + } + + /** + * Return first NodeRef of Segment according to sorting + * order (bottom left to top right). + */ const osmium::NodeRef& first() const noexcept { return m_first; } - /// Return second NodeRef of Segment according to sorting order (bottom left to top right). + /** + * Return second NodeRef of Segment according to sorting + * order (bottom left to top right). + */ const osmium::NodeRef& second() const noexcept { return m_second; } - bool to_left_of(const osmium::Location& location) const { - // std::cerr << "segment " << first() << "--" << second() << " to_left_of(" << location << "\n"; + /** + * Return real first NodeRef of Segment. + */ + const osmium::NodeRef& start() const noexcept { + return m_reverse ? m_second : m_first; + } - if (first().location() == location || second().location() == location) { - return false; - } - - const std::pair mm = std::minmax(first().location(), second().location(), [](const osmium::Location a, const osmium::Location b) { - return a.y() < b.y(); - }); - - if (mm.first.y() >= location.y() || mm.second.y() < location.y() || first().location().x() > location.x()) { - // std::cerr << " false\n"; - return false; - } - - int64_t ax = mm.first.x(); - int64_t bx = mm.second.x(); - int64_t lx = location.x(); - int64_t ay = mm.first.y(); - int64_t by = mm.second.y(); - int64_t ly = location.y(); - return ((bx - ax)*(ly - ay) - (by - ay)*(lx - ax)) <= 0; + /** + * Return real second NodeRef of Segment. + */ + const osmium::NodeRef& stop() const noexcept { + return m_reverse ? m_first : m_second; } bool role_outer() const noexcept { - return !strcmp(m_role, "outer"); + return m_role == role_type::outer; } bool role_inner() const noexcept { - return !strcmp(m_role, "inner"); + return m_role == role_type::inner; + } + + bool role_empty() const noexcept { + return m_role == role_type::empty; + } + + const char* role_name() const noexcept { + static const char* names[] = { "unknown", "outer", "inner", "empty" }; + return names[int(m_role)]; } const osmium::Way* way() const noexcept { return m_way; } + /** + * The "determinant" of this segment. Used for calculating + * the winding order or a ring. + */ + int64_t det() const noexcept { + const vec a{start()}; + const vec b{stop()}; + return a * b; + } + }; // class NodeRefSegment /// NodeRefSegments are equal if both their locations are equal inline bool operator==(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept { - return lhs.first().location() == rhs.first().location() && lhs.second().location() == rhs.second().location(); + return lhs.first().location() == rhs.first().location() && + lhs.second().location() == rhs.second().location(); } inline bool operator!=(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept { @@ -162,12 +233,33 @@ namespace osmium { } /** - * NodeRefSegments are "smaller" if they are to the left and down of another - * segment. The first() location is checked first() and only if they have the - * same first() location the second() location is taken into account. + * A NodeRefSegment is "smaller" if the first point is to the + * left and down of the first point of the second segment. + * If both first points are the same, the segment with the higher + * slope comes first. If the slope is the same, the shorter + * segment comes first. */ inline bool operator<(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept { - return (lhs.first().location() == rhs.first().location() && lhs.second().location() < rhs.second().location()) || lhs.first().location() < rhs.first().location(); + if (lhs.first().location() == rhs.first().location()) { + const vec p0{lhs.first().location()}; + const vec p1{lhs.second().location()}; + const vec q0{rhs.first().location()}; + const vec q1{rhs.second().location()}; + const vec p = p1 - p0; + const vec q = q1 - q0; + + if (p.x == 0 && q.x == 0) { + return p.y < q.y; + } + + const auto a = p.y * q.x; + const auto b = q.y * p.x; + if (a == b) { + return p.x < q.x; + } + return a > b; + } + return lhs.first().location() < rhs.first().location(); } inline bool operator>(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept { @@ -184,7 +276,10 @@ namespace osmium { template inline std::basic_ostream& operator<<(std::basic_ostream& out, const NodeRefSegment& segment) { - return out << segment.first() << "--" << segment.second(); + return out << segment.start() << "--" << segment.stop() + << "[" << (segment.is_reverse() ? 'R' : '_') + << (segment.is_done() ? 'd' : '_') + << (segment.is_direction_done() ? 'D' : '_') << "]"; } inline bool outside_x_range(const NodeRefSegment& s1, const NodeRefSegment& s2) noexcept { @@ -194,7 +289,7 @@ namespace osmium { return false; } - inline bool y_range_overlap(const NodeRefSegment& s1, const NodeRefSegment& s2) { + inline bool y_range_overlap(const NodeRefSegment& s1, const NodeRefSegment& s2) noexcept { const std::pair m1 = std::minmax(s1.first().location().y(), s1.second().location().y()); const std::pair m2 = std::minmax(s2.first().location().y(), s2.second().location().y()); if (m1.first > m2.second || m2.first > m1.second) { @@ -210,55 +305,94 @@ namespace osmium { * might be slightly different than the numerically correct * location. * - * This function uses integer arithmentic as much as possible and + * This function uses integer arithmetic as much as possible and * will not work if the segments are longer than about half the * planet. This shouldn't happen with real data, so it isn't a big * problem. * - * If the segments touch in one of their endpoints, it doesn't - * count as an intersection. + * If the segments touch in one or both of their endpoints, it + * doesn't count as an intersection. * * If the segments intersect not in a single point but in multiple - * points, ie if they overlap, this is NOT detected. + * points, ie if they are collinear and overlap, the smallest + * of the endpoints that is in the overlapping section is returned. * * @returns Undefined osmium::Location if there is no intersection * or a defined Location if the segments intersect. */ - inline osmium::Location calculate_intersection(const NodeRefSegment& s1, const NodeRefSegment& s2) { - if (s1.first().location() == s2.first().location() || - s1.first().location() == s2.second().location() || - s1.second().location() == s2.first().location() || - s1.second().location() == s2.second().location()) { + inline osmium::Location calculate_intersection(const NodeRefSegment& s1, const NodeRefSegment& s2) noexcept { + // See http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect + // for some hints about how the algorithm works. + const vec p0{s1.first()}; + const vec p1{s1.second()}; + const vec q0{s2.first()}; + const vec q1{s2.second()}; + + if ((p0 == q0 && p1 == q1) || + (p0 == q1 && p1 == q0)) { + // segments are the same return osmium::Location(); } - int64_t s1ax = s1.first().x(); - int64_t s1ay = s1.first().y(); - int64_t s1bx = s1.second().x(); - int64_t s1by = s1.second().y(); - int64_t s2ax = s2.first().x(); - int64_t s2ay = s2.first().y(); - int64_t s2bx = s2.second().x(); - int64_t s2by = s2.second().y(); - - int64_t d = (s2by - s2ay) * (s1bx - s1ax) - - (s2bx - s2ax) * (s1by - s1ay); + const vec pd = p1 - p0; + const int64_t d = pd * (q1 - q0); if (d != 0) { - int64_t na = (s2bx - s2ax) * (s1ay - s2ay) - - (s2by - s2ay) * (s1ax - s2ax); + // segments are not collinear - int64_t nb = (s1bx - s1ax) * (s1ay - s2ay) - - (s1by - s1ay) * (s1ax - s2ax); + if (p0 == q0 || p0 == q1 || p1 == q0 || p1 == q1) { + // touching at an end point + return osmium::Location(); + } + + // intersection in a point + + const int64_t na = (q1.x - q0.x) * (p0.y - q0.y) - + (q1.y - q0.y) * (p0.x - q0.x); + + const int64_t nb = (p1.x - p0.x) * (p0.y - q0.y) - + (p1.y - p0.y) * (p0.x - q0.x); if ((d > 0 && na >= 0 && na <= d && nb >= 0 && nb <= d) || (d < 0 && na <= 0 && na >= d && nb <= 0 && nb >= d)) { + const double ua = double(na) / d; + const vec i = p0 + ua * (p1 - p0); + return osmium::Location(int32_t(i.x), int32_t(i.y)); + } - double ua = double(na) / d; - int32_t ix = int32_t(s1ax + ua*(s1bx - s1ax)); - int32_t iy = int32_t(s1ay + ua*(s1by - s1ay)); + return osmium::Location(); + } - return osmium::Location(ix, iy); + // segments are collinear + + if (pd * (q0 - p0) == 0) { + // segments are on the same line + + struct seg_loc { + int segment; + osmium::Location location; + }; + + seg_loc sl[4]; + sl[0] = {0, s1.first().location() }; + sl[1] = {0, s1.second().location()}; + sl[2] = {1, s2.first().location() }; + sl[3] = {1, s2.second().location()}; + + std::sort(sl, sl+4, [](const seg_loc& a, const seg_loc& b) { + return a.location < b.location; + }); + + if (sl[1].location == sl[2].location) { + return osmium::Location(); + } + + if (sl[0].segment != sl[1].segment) { + if (sl[0].location == sl[1].location) { + return sl[2].location; + } else { + return sl[1].location; + } } } diff --git a/include/osmium/area/detail/proto_ring.hpp b/include/osmium/area/detail/proto_ring.hpp index c4edf4018..bce681711 100644 --- a/include/osmium/area/detail/proto_ring.hpp +++ b/include/osmium/area/detail/proto_ring.hpp @@ -34,10 +34,9 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include -#include #include -#include #include #include @@ -47,6 +46,8 @@ DEALINGS IN THE SOFTWARE. namespace osmium { + class Way; + namespace area { namespace detail { @@ -58,214 +59,155 @@ namespace osmium { public: - typedef std::vector segments_type; + using segments_type = std::vector; private: - // segments in this ring + // Segments in this ring. segments_type m_segments; - bool m_outer {true}; - - // if this is an outer ring, these point to it's inner rings (if any) + // If this is an outer ring, these point to it's inner rings + // (if any). std::vector m_inner; + // The smallest segment. Will be kept current whenever a new + // segment is added to the ring. + NodeRefSegment* m_min_segment; + + // If this is an inner ring, points to the outer ring. + ProtoRing* m_outer_ring; + + int64_t m_sum; + public: - explicit ProtoRing(const NodeRefSegment& segment) noexcept : - m_segments() { + explicit ProtoRing(NodeRefSegment* segment) noexcept : + m_segments(), + m_inner(), + m_min_segment(segment), + m_outer_ring(nullptr), + m_sum(0) { add_segment_back(segment); } - explicit ProtoRing(segments_type::const_iterator sbegin, segments_type::const_iterator send) : - m_segments(static_cast(std::distance(sbegin, send))) { - std::copy(sbegin, send, m_segments.begin()); + void add_segment_back(NodeRefSegment* segment) { + assert(segment); + if (*segment < *m_min_segment) { + m_min_segment = segment; + } + m_segments.push_back(segment); + segment->set_ring(this); + m_sum += segment->det(); } - bool outer() const noexcept { - return m_outer; + NodeRefSegment* min_segment() const noexcept { + return m_min_segment; } - void set_inner() noexcept { - m_outer = false; + ProtoRing* outer_ring() const noexcept { + return m_outer_ring; } - segments_type& segments() noexcept { - return m_segments; + void set_outer_ring(ProtoRing* outer_ring) noexcept { + assert(outer_ring); + assert(m_inner.empty()); + m_outer_ring = outer_ring; + } + + const std::vector& inner_rings() const noexcept { + return m_inner; + } + + void add_inner_ring(ProtoRing* ring) { + assert(ring); + assert(!m_outer_ring); + m_inner.push_back(ring); + } + + bool is_outer() const noexcept { + return !m_outer_ring; } const segments_type& segments() const noexcept { return m_segments; } - void remove_segments(segments_type::iterator sbegin, segments_type::iterator send) { - m_segments.erase(sbegin, send); + const NodeRef& get_node_ref_start() const noexcept { + return m_segments.front()->start(); } - void add_segment_front(const NodeRefSegment& segment) { - m_segments.insert(m_segments.begin(), segment); + const NodeRef& get_node_ref_stop() const noexcept { + return m_segments.back()->stop(); } - void add_segment_back(const NodeRefSegment& segment) { - m_segments.push_back(segment); + bool closed() const noexcept { + return get_node_ref_start().location() == get_node_ref_stop().location(); } - const NodeRefSegment& get_segment_front() const { - return m_segments.front(); + void reverse() { + std::for_each(m_segments.begin(), m_segments.end(), [](NodeRefSegment* segment) { + segment->reverse(); + }); + std::reverse(m_segments.begin(), m_segments.end()); + m_sum = -m_sum; } - NodeRefSegment& get_segment_front() { - return m_segments.front(); + void mark_direction_done() { + std::for_each(m_segments.begin(), m_segments.end(), [](NodeRefSegment* segment) { + segment->mark_direction_done(); + }); } - const NodeRef& get_node_ref_front() const { - return get_segment_front().first(); + bool is_cw() const noexcept { + return m_sum <= 0; } - const NodeRefSegment& get_segment_back() const { - return m_segments.back(); + int64_t sum() const noexcept { + return m_sum; } - NodeRefSegment& get_segment_back() { - return m_segments.back(); - } - - const NodeRef& get_node_ref_back() const { - return get_segment_back().second(); - } - - bool closed() const { - return m_segments.front().first().location() == m_segments.back().second().location(); - } - - int64_t sum() const { - int64_t sum = 0; - - for (const auto& segment : m_segments) { - sum += static_cast(segment.first().location().x()) * static_cast(segment.second().location().y()) - - static_cast(segment.second().location().x()) * static_cast(segment.first().location().y()); + void fix_direction() noexcept { + if (is_cw() == is_outer()) { + reverse(); } - - return sum; } - bool is_cw() const { - return sum() <= 0; + void reset() { + m_inner.clear(); + m_outer_ring = nullptr; + std::for_each(m_segments.begin(), m_segments.end(), [](NodeRefSegment* segment) { + segment->mark_direction_not_done(); + }); } - int64_t area() const { - return std::abs(sum()) / 2; + void get_ways(std::set& ways) const { + for (const auto& segment : m_segments) { + ways.insert(segment->way()); + } } - void swap_segments(ProtoRing& other) { - using std::swap; - swap(m_segments, other.m_segments); + void join_forward(ProtoRing& other) { + for (NodeRefSegment* segment : other.m_segments) { + add_segment_back(segment); + } } - void add_inner_ring(ProtoRing* ring) { - m_inner.push_back(ring); - } - - const std::vector& inner_rings() const { - return m_inner; + void join_backward(ProtoRing& other) { + for (auto it = other.m_segments.rbegin(); it != other.m_segments.rend(); ++it) { + (*it)->reverse(); + add_segment_back(*it); + } } void print(std::ostream& out) const { out << "["; - bool first = true; + if (!m_segments.empty()) { + out << m_segments.front()->start().ref(); + } for (const auto& segment : m_segments) { - if (first) { - out << segment.first().ref(); - } - out << ',' << segment.second().ref(); - first = false; + out << ',' << segment->stop().ref(); } - out << "]"; - } - - void reverse() { - std::for_each(m_segments.begin(), m_segments.end(), [](NodeRefSegment& segment) { - segment.swap_locations(); - }); - std::reverse(m_segments.begin(), m_segments.end()); - } - - /** - * Merge other ring to end of this ring. - */ - void merge_ring(const ProtoRing& other, bool debug) { - if (debug) { - std::cerr << " MERGE rings "; - print(std::cerr); - std::cerr << " to "; - other.print(std::cerr); - std::cerr << "\n"; - } - m_segments.insert(m_segments.end(), other.m_segments.begin(), other.m_segments.end()); - if (debug) { - std::cerr << " result ring: "; - print(std::cerr); - std::cerr << "\n"; - } - } - - void merge_ring_reverse(const ProtoRing& other, bool debug) { - if (debug) { - std::cerr << " MERGE rings (reverse) "; - print(std::cerr); - std::cerr << " to "; - other.print(std::cerr); - std::cerr << "\n"; - } - size_t n = m_segments.size(); - m_segments.resize(n + other.m_segments.size()); - std::transform(other.m_segments.rbegin(), other.m_segments.rend(), m_segments.begin() + static_cast(n), [](NodeRefSegment segment) { - segment.swap_locations(); - return segment; - }); - if (debug) { - std::cerr << " result ring: "; - print(std::cerr); - std::cerr << "\n"; - } - } - - const NodeRef& min_node() const { - auto it = std::min_element(m_segments.begin(), m_segments.end()); - if (location_less()(it->first(), it->second())) { - return it->first(); - } else { - return it->second(); - } - } - - bool is_in(ProtoRing* outer) { - osmium::Location testpoint = segments().front().first().location(); - bool is_in = false; - - for (size_t i = 0, j = outer->segments().size()-1; i < outer->segments().size(); j = i++) { - if (((outer->segments()[i].first().location().y() > testpoint.y()) != (outer->segments()[j].first().location().y() > testpoint.y())) && - (testpoint.x() < (outer->segments()[j].first().location().x() - outer->segments()[i].first().location().x()) * (testpoint.y() - outer->segments()[i].first().location().y()) / (outer->segments()[j].first().location().y() - outer->segments()[i].first().location().y()) + outer->segments()[i].first().location().x()) ) { - is_in = !is_in; - } - } - - return is_in; - } - - void get_ways(std::set& ways) { - for (const auto& segment : m_segments) { - ways.insert(segment.way()); - } - } - - bool contains(const NodeRefSegment& segment) const { - for (const auto& s : m_segments) { - if (s == segment || (s.first() == segment.second() && s.second() == segment.first())) { - return true; - } - } - return false; + out << "]-" << (is_outer() ? "OUTER" : "INNER"); } }; // class ProtoRing diff --git a/include/osmium/area/detail/segment_list.hpp b/include/osmium/area/detail/segment_list.hpp index 05e0cd8d4..a4361e0e1 100644 --- a/include/osmium/area/detail/segment_list.hpp +++ b/include/osmium/area/detail/segment_list.hpp @@ -35,12 +35,16 @@ DEALINGS IN THE SOFTWARE. #include #include +#include +#include #include +#include +#include #include -#include #include -#include +#include +#include #include #include #include @@ -52,6 +56,24 @@ namespace osmium { namespace detail { + /** + * Iterate over all relation members and the vector of ways at the + * same time and call given function with the relation member and + * way as parameter. This takes into account that there might be + * non-way members in the relation. + */ + template + inline void for_each_member(const osmium::Relation& relation, const std::vector& ways, F&& func) { + auto way_it = ways.cbegin(); + for (const osmium::RelationMember& member : relation.members()) { + if (member.type() == osmium::item_type::way) { + assert(way_it != ways.cend()); + func(member, **way_it); + ++way_it; + } + } + } + /** * This is a helper class for the area assembler. It models * a list of segments. @@ -64,6 +86,51 @@ namespace osmium { bool m_debug; + static role_type parse_role(const char* role) noexcept { + if (role[0] == '\0') { + return role_type::empty; + } else if (!std::strcmp(role, "outer")) { + return role_type::outer; + } else if (!std::strcmp(role, "inner")) { + return role_type::inner; + } + return role_type::unknown; + } + + /** + * Calculate the number of segments in all the ways together. + */ + static size_t get_num_segments(const std::vector& members) noexcept { + return std::accumulate(members.cbegin(), members.cend(), 0, [](size_t sum, const osmium::Way* way) { + if (way->nodes().empty()) { + return sum; + } else { + return sum + way->nodes().size() - 1; + } + }); + } + + uint32_t extract_segments_from_way_impl(osmium::area::ProblemReporter* problem_reporter, const osmium::Way& way, role_type role) { + uint32_t duplicate_nodes = 0; + + osmium::NodeRef previous_nr; + for (const osmium::NodeRef& nr : way.nodes()) { + if (previous_nr.location()) { + if (previous_nr.location() != nr.location()) { + m_segments.emplace_back(previous_nr, nr, role, &way); + } else { + ++duplicate_nodes; + if (problem_reporter) { + problem_reporter->report_duplicate_node(previous_nr.ref(), nr.ref(), nr.location()); + } + } + } + previous_nr = nr; + } + + return duplicate_nodes; + } + public: explicit SegmentList(bool debug) noexcept : @@ -84,12 +151,31 @@ namespace osmium { return m_segments.size(); } + /// Is the segment list empty? bool empty() const noexcept { return m_segments.empty(); } - typedef slist_type::const_iterator const_iterator; - typedef slist_type::iterator iterator; + using const_iterator = slist_type::const_iterator; + using iterator = slist_type::iterator; + + NodeRefSegment& front() { + return m_segments.front(); + } + + NodeRefSegment& back() { + return m_segments.back(); + } + + const NodeRefSegment& operator[](size_t n) const noexcept { + assert(n < m_segments.size()); + return m_segments[n]; + } + + NodeRefSegment& operator[](size_t n) noexcept { + assert(n < m_segments.size()); + return m_segments[n]; + } iterator begin() noexcept { return m_segments.begin(); @@ -115,11 +201,6 @@ namespace osmium { m_debug = debug; } - /// Clear the list of segments. All segments are removed. - void clear() { - m_segments.clear(); - } - /// Sort the list of segments. void sort() { std::sort(m_segments.begin(), m_segments.end()); @@ -128,32 +209,37 @@ namespace osmium { /** * Extract segments from given way and add them to the list. * - * Segments connecting two nodes with the same location (ie same - * node or different node with same location) are removed. - * - * XXX should two nodes with same location be reported? + * Segments connecting two nodes with the same location (ie + * same node or different nodes with same location) are + * removed after reporting the duplicate node. */ - void extract_segments_from_way(const osmium::Way& way, const char* role) { - osmium::NodeRef last_nr; - for (const osmium::NodeRef& nr : way.nodes()) { - if (last_nr.location() && last_nr.location() != nr.location()) { - m_segments.emplace_back(last_nr, nr, role, &way); - } - last_nr = nr; + uint32_t extract_segments_from_way(osmium::area::ProblemReporter* problem_reporter, const osmium::Way& way) { + if (way.nodes().empty()) { + return 0; } + m_segments.reserve(way.nodes().size() - 1); + return extract_segments_from_way_impl(problem_reporter, way, role_type::outer); } /** * Extract all segments from all ways that make up this * multipolygon relation and add them to the list. */ - void extract_segments_from_ways(const osmium::Relation& relation, const std::vector& members, const osmium::memory::Buffer& in_buffer) { - auto member_it = relation.members().begin(); - for (size_t offset : members) { - const osmium::Way& way = in_buffer.get(offset); - extract_segments_from_way(way, member_it->role()); - ++member_it; + uint32_t extract_segments_from_ways(osmium::area::ProblemReporter* problem_reporter, const osmium::Relation& relation, const std::vector& members) { + assert(relation.members().size() >= members.size()); + + const size_t num_segments = get_num_segments(members); + if (problem_reporter) { + problem_reporter->set_nodes(num_segments); } + m_segments.reserve(num_segments); + + uint32_t duplicate_nodes = 0; + for_each_member(relation, members, [this, &problem_reporter, &duplicate_nodes](const osmium::RelationMember& member, const osmium::Way& way) { + duplicate_nodes += extract_segments_from_way_impl(problem_reporter, way, parse_role(member.role())); + }); + + return duplicate_nodes; } /** @@ -162,17 +248,35 @@ namespace osmium { * same segment. So if there are three, for instance, two will * be removed and one will be left. */ - void erase_duplicate_segments() { + uint32_t erase_duplicate_segments(osmium::area::ProblemReporter* problem_reporter) { + uint32_t duplicate_segments = 0; + while (true) { auto it = std::adjacent_find(m_segments.begin(), m_segments.end()); if (it == m_segments.end()) { - return; + break; } if (m_debug) { std::cerr << " erase duplicate segment: " << *it << "\n"; } + + // Only count and report duplicate segments if they + // belong to the same way or if they don't both have + // the role "inner". Those cases are definitely wrong. + // If the duplicate segments belong to different + // "inner" ways, they could be touching inner rings + // which are perfectly okay. Note that for this check + // the role has to be correct in the member data. + if (it->way() == std::next(it)->way() || !it->role_inner() || !std::next(it)->role_inner()) { + ++duplicate_segments; + if (problem_reporter) { + problem_reporter->report_duplicate_segment(it->first(), it->second()); + } + } m_segments.erase(it, it+2); } + + return duplicate_segments; } /** @@ -182,14 +286,14 @@ namespace osmium { * reported to this object. * @returns true if there are intersections. */ - bool find_intersections(osmium::area::ProblemReporter* problem_reporter) const { + uint32_t find_intersections(osmium::area::ProblemReporter* problem_reporter) const { if (m_segments.empty()) { - return false; + return 0; } - bool found_intersections = false; + uint32_t found_intersections = 0; - for (auto it1 = m_segments.begin(); it1 != m_segments.end()-1; ++it1) { + for (auto it1 = m_segments.cbegin(); it1 != m_segments.cend()-1; ++it1) { const NodeRefSegment& s1 = *it1; for (auto it2 = it1+1; it2 != m_segments.end(); ++it2) { const NodeRefSegment& s2 = *it2; @@ -203,12 +307,13 @@ namespace osmium { if (y_range_overlap(s1, s2)) { osmium::Location intersection = calculate_intersection(s1, s2); if (intersection) { - found_intersections = true; + ++found_intersections; if (m_debug) { std::cerr << " segments " << s1 << " and " << s2 << " intersecting at " << intersection << "\n"; } if (problem_reporter) { - problem_reporter->report_intersection(s1.way()->id(), s1.first().location(), s1.second().location(), s2.way()->id(), s2.first().location(), s2.second().location(), intersection); + problem_reporter->report_intersection(s1.way()->id(), s1.first().location(), s1.second().location(), + s2.way()->id(), s2.first().location(), s2.second().location(), intersection); } } } diff --git a/include/osmium/area/detail/vector.hpp b/include/osmium/area/detail/vector.hpp new file mode 100644 index 000000000..44983cc75 --- /dev/null +++ b/include/osmium/area/detail/vector.hpp @@ -0,0 +1,121 @@ +#ifndef OSMIUM_AREA_DETAIL_VECTOR_HPP +#define OSMIUM_AREA_DETAIL_VECTOR_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include +#include + +#include +#include + +namespace osmium { + + namespace area { + + namespace detail { + + /** + * This helper class models a 2D vector in the mathematical sense. + * It uses 64 bit integers internally which has enough precision + * for most operations with inputs based on 32 bit locations. + */ + struct vec { + + int64_t x; + int64_t y; + + constexpr vec(int64_t a, int64_t b) noexcept : + x(a), + y(b) { + } + + constexpr explicit vec(const osmium::Location& l) noexcept : + x(l.x()), + y(l.y()) { + } + + constexpr explicit vec(const osmium::NodeRef& nr) noexcept : + x(nr.x()), + y(nr.y()) { + } + + }; // struct vec + + // addition + constexpr inline vec operator+(const vec& a, const vec& b) noexcept { + return vec{a.x + b.x, a.y + b.y}; + } + + // subtraction + constexpr inline vec operator-(const vec& a, const vec& b) noexcept { + return vec{a.x - b.x, a.y - b.y}; + } + + // cross product + constexpr inline int64_t operator*(const vec& a, const vec& b) noexcept { + return a.x * b.y - a.y * b.x; + } + + // scale vector + constexpr inline vec operator*(double s, const vec& v) noexcept { + return vec{int64_t(s * v.x), int64_t(s * v.y)}; + } + + // scale vector + constexpr inline vec operator*(const vec& v, double s) noexcept { + return vec{int64_t(s * v.x), int64_t(s * v.y)}; + } + + // equality + constexpr inline bool operator==(const vec& a, const vec& b) noexcept { + return a.x == b.x && a.y == b.y; + } + + // inequality + constexpr inline bool operator!=(const vec& a, const vec& b) noexcept { + return !(a == b); + } + + template + inline std::basic_ostream& operator<<(std::basic_ostream& out, const vec& v) { + return out << '(' << v.x << ',' << v.y << ')'; + } + + } // namespace detail + + } // namespace area + +} // namespace osmium + +#endif // OSMIUM_AREA_DETAIL_VECTOR_HPP diff --git a/include/osmium/area/multipolygon_collector.hpp b/include/osmium/area/multipolygon_collector.hpp index 81a28b24b..8b370521b 100644 --- a/include/osmium/area/multipolygon_collector.hpp +++ b/include/osmium/area/multipolygon_collector.hpp @@ -34,11 +34,11 @@ DEALINGS IN THE SOFTWARE. */ #include -#include #include #include #include +#include #include #include #include @@ -47,7 +47,6 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include namespace osmium { @@ -74,13 +73,15 @@ namespace osmium { template class MultipolygonCollector : public osmium::relations::Collector, false, true, false> { - typedef typename osmium::relations::Collector, false, true, false> collector_type; + using collector_type = osmium::relations::Collector, false, true, false>; - typedef typename TAssembler::config_type assembler_config_type; + using assembler_config_type = typename TAssembler::config_type; const assembler_config_type m_assembler_config; osmium::memory::Buffer m_output_buffer; + osmium::area::area_stats m_stats; + static constexpr size_t initial_output_buffer_size = 1024 * 1024; static constexpr size_t max_buffer_size_for_flush = 100 * 1024; @@ -107,6 +108,10 @@ namespace osmium { m_output_buffer(initial_output_buffer_size, osmium::memory::Buffer::auto_grow::yes) { } + const osmium::area::area_stats& stats() const noexcept { + return m_stats; + } + /** * We are interested in all relations tagged with type=multipolygon * or type=boundary. @@ -121,7 +126,7 @@ namespace osmium { return false; } - if ((!strcmp(type, "multipolygon")) || (!strcmp(type, "boundary"))) { + if ((!std::strcmp(type, "multipolygon")) || (!std::strcmp(type, "boundary"))) { return true; } @@ -155,26 +160,32 @@ namespace osmium { // way is closed and has enough nodes, build simple multipolygon TAssembler assembler(m_assembler_config); assembler(way, m_output_buffer); + m_stats += assembler.stats(); possibly_flush_output_buffer(); } - } catch (osmium::invalid_location&) { + } catch (const osmium::invalid_location&) { // XXX ignore } } void complete_relation(osmium::relations::RelationMeta& relation_meta) { const osmium::Relation& relation = this->get_relation(relation_meta); - std::vector offsets; + const osmium::memory::Buffer& buffer = this->members_buffer(); + + std::vector ways; for (const auto& member : relation.members()) { if (member.ref() != 0) { - offsets.push_back(this->get_offset(member.type(), member.ref())); + const size_t offset = this->get_offset(member.type(), member.ref()); + ways.push_back(&buffer.get(offset)); } } + try { TAssembler assembler(m_assembler_config); - assembler(relation, offsets, this->members_buffer(), m_output_buffer); + assembler(relation, ways, m_output_buffer); + m_stats += assembler.stats(); possibly_flush_output_buffer(); - } catch (osmium::invalid_location&) { + } catch (const osmium::invalid_location&) { // XXX ignore } } diff --git a/include/osmium/area/problem_reporter.hpp b/include/osmium/area/problem_reporter.hpp index 1dde85ba9..6c562314e 100644 --- a/include/osmium/area/problem_reporter.hpp +++ b/include/osmium/area/problem_reporter.hpp @@ -33,12 +33,17 @@ DEALINGS IN THE SOFTWARE. */ +#include + #include #include #include namespace osmium { + class NodeRef; + class Way; + namespace area { /** @@ -62,6 +67,9 @@ namespace osmium { // ID of the relation/way we are currently working on osmium::object_id_type m_object_id; + // Number of nodes in the area + size_t m_nodes; + public: ProblemReporter() = default; @@ -79,6 +87,10 @@ namespace osmium { m_object_id = object_id; } + void set_nodes(size_t nodes) noexcept { + m_nodes = nodes; + } + // Disable "unused-parameter" warning, so that the compiler will not complain. // We can't remove the parameter names, because then doxygen will complain. #pragma GCC diagnostic push @@ -87,13 +99,23 @@ namespace osmium { /** * Report a duplicate node, ie. two nodes with the same location. * - * @param node_id1 ID of the first node. - * @param node_id2 ID of the second node. - * @param location Location of both nodes. + * @param node_id1 ID of the first node. + * @param node_id2 ID of the second node. + * @param location Location of both nodes. */ virtual void report_duplicate_node(osmium::object_id_type node_id1, osmium::object_id_type node_id2, osmium::Location location) { } + /** + * Report a node/location where rings touch. This is often wrong, + * but not necessarily so. + * + * @param node_id ID of the node. + * @param location Location of the node. + */ + virtual void report_touching_ring(osmium::object_id_type node_id, osmium::Location location) { + } + /** * Report an intersection between two segments. * @@ -109,21 +131,33 @@ namespace osmium { osmium::object_id_type way2_id, osmium::Location way2_seg_start, osmium::Location way2_seg_end, osmium::Location intersection) { } + /** + * Report a duplicate segments. Two or more segments are directly + * on top of each other. This can be a problem, if there is a + * spike for instance, or it could be okay, if there are touching + * inner rings. + * + * @param nr1 NodeRef of one end of the segment. + * @param nr2 NodeRef of the other end of the segment. + */ + virtual void report_duplicate_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) { + } + /** * Report an open ring. * - * @param end1 Location of the first open end. - * @param end2 Location of the second open end. + * @param nr NodeRef of one end of the ring. + * @param way Optional pointer to way the end node is in. */ - virtual void report_ring_not_closed(osmium::Location end1, osmium::Location end2) { + virtual void report_ring_not_closed(const osmium::NodeRef& nr, const osmium::Way* way = nullptr) { } /** * Report a segment that should have role "outer", but has a different role. * - * @param way_id ID of the way this segment is in. - * @param seg_start Start of the segment with the wrong role. - * @param seg_end End of the segment with the wrong role. + * @param way_id ID of the way this segment is in. + * @param seg_start Start of the segment with the wrong role. + * @param seg_end End of the segment with the wrong role. */ virtual void report_role_should_be_outer(osmium::object_id_type way_id, osmium::Location seg_start, osmium::Location seg_end) { } @@ -138,6 +172,32 @@ namespace osmium { virtual void report_role_should_be_inner(osmium::object_id_type way_id, osmium::Location seg_start, osmium::Location seg_end) { } + /** + * Report a way that is in multiple rings. + * + * @param way The way. + */ + virtual void report_way_in_multiple_rings(const osmium::Way& way) { + } + + /** + * Report a way with role inner that has the same tags as the + * relation or outer ways. + * + * @param way The way. + */ + virtual void report_inner_with_same_tags(const osmium::Way& way) { + } + + /** + * In addition to reporting specific problems, this is used to + * report all ways belonging to areas having problems. + * + * @param way The way + */ + virtual void report_way(const osmium::Way& way) { + } + #pragma GCC diagnostic pop }; // class ProblemReporter diff --git a/include/osmium/area/problem_reporter_exception.hpp b/include/osmium/area/problem_reporter_exception.hpp index abadd9653..009a5f4ba 100644 --- a/include/osmium/area/problem_reporter_exception.hpp +++ b/include/osmium/area/problem_reporter_exception.hpp @@ -42,6 +42,9 @@ DEALINGS IN THE SOFTWARE. namespace osmium { + class NodeRef; + class Way; + namespace area { class ProblemReporterException : public ProblemReporterStream { @@ -62,6 +65,12 @@ namespace osmium { throw std::runtime_error(m_sstream.str()); } + void report_touching_ring(osmium::object_id_type node_id, osmium::Location location) override { + m_sstream.str(); + ProblemReporterStream::report_touching_ring(node_id, location); + throw std::runtime_error(m_sstream.str()); + } + void report_intersection(osmium::object_id_type way1_id, osmium::Location way1_seg_start, osmium::Location way1_seg_end, osmium::object_id_type way2_id, osmium::Location way2_seg_start, osmium::Location way2_seg_end, osmium::Location intersection) override { m_sstream.str(); @@ -69,9 +78,15 @@ namespace osmium { throw std::runtime_error(m_sstream.str()); } - void report_ring_not_closed(osmium::Location end1, osmium::Location end2) override { + void report_duplicate_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) override { m_sstream.str(); - ProblemReporterStream::report_ring_not_closed(end1, end2); + ProblemReporterStream::report_duplicate_segment(nr1, nr2); + throw std::runtime_error(m_sstream.str()); + } + + void report_ring_not_closed(const osmium::NodeRef& nr, const osmium::Way* way = nullptr) override { + m_sstream.str(); + ProblemReporterStream::report_ring_not_closed(nr, way); throw std::runtime_error(m_sstream.str()); } @@ -87,6 +102,18 @@ namespace osmium { throw std::runtime_error(m_sstream.str()); } + void report_way_in_multiple_rings(const osmium::Way& way) override { + m_sstream.str(); + ProblemReporterStream::report_way_in_multiple_rings(way); + throw std::runtime_error(m_sstream.str()); + } + + void report_inner_with_same_tags(const osmium::Way& way) override { + m_sstream.str(); + ProblemReporterStream::report_inner_with_same_tags(way); + throw std::runtime_error(m_sstream.str()); + } + }; // class ProblemReporterException } // namespace area diff --git a/include/osmium/area/problem_reporter_ogr.hpp b/include/osmium/area/problem_reporter_ogr.hpp index d58fe559c..0889a5809 100644 --- a/include/osmium/area/problem_reporter_ogr.hpp +++ b/include/osmium/area/problem_reporter_ogr.hpp @@ -49,8 +49,12 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include #include +#include +#include #include +#include namespace osmium { @@ -66,26 +70,34 @@ namespace osmium { gdalcpp::Layer m_layer_perror; gdalcpp::Layer m_layer_lerror; + gdalcpp::Layer m_layer_ways; + + void set_object(gdalcpp::Feature& feature) { + const char t[2] = { osmium::item_type_to_char(m_object_type), '\0' }; + feature.set_field("obj_type", t); + feature.set_field("obj_id", int32_t(m_object_id)); + feature.set_field("nodes", int32_t(m_nodes)); + } void write_point(const char* problem_type, osmium::object_id_type id1, osmium::object_id_type id2, osmium::Location location) { gdalcpp::Feature feature(m_layer_perror, m_ogr_factory.create_point(location)); - feature.set_field("id1", static_cast(id1)); - feature.set_field("id2", static_cast(id2)); - feature.set_field("problem_type", problem_type); + set_object(feature); + feature.set_field("id1", double(id1)); + feature.set_field("id2", double(id2)); + feature.set_field("problem", problem_type); feature.add_to_layer(); } void write_line(const char* problem_type, osmium::object_id_type id1, osmium::object_id_type id2, osmium::Location loc1, osmium::Location loc2) { - std::unique_ptr ogr_point1 = m_ogr_factory.create_point(loc1); - std::unique_ptr ogr_point2 = m_ogr_factory.create_point(loc2); - std::unique_ptr ogr_linestring = std::unique_ptr(new OGRLineString()); - ogr_linestring->addPoint(ogr_point1.get()); - ogr_linestring->addPoint(ogr_point2.get()); + auto ogr_linestring = std::unique_ptr{new OGRLineString{}}; + ogr_linestring->addPoint(loc1.lon(), loc1.lat()); + ogr_linestring->addPoint(loc2.lon(), loc2.lat()); gdalcpp::Feature feature(m_layer_lerror, std::move(ogr_linestring)); + set_object(feature); feature.set_field("id1", static_cast(id1)); feature.set_field("id2", static_cast(id2)); - feature.set_field("problem_type", problem_type); + feature.set_field("problem", problem_type); feature.add_to_layer(); } @@ -93,15 +105,36 @@ namespace osmium { explicit ProblemReporterOGR(gdalcpp::Dataset& dataset) : m_layer_perror(dataset, "perrors", wkbPoint), - m_layer_lerror(dataset, "lerrors", wkbLineString) { + m_layer_lerror(dataset, "lerrors", wkbLineString), + m_layer_ways(dataset, "ways", wkbLineString) { - m_layer_perror.add_field("id1", OFTReal, 10); - m_layer_perror.add_field("id2", OFTReal, 10); - m_layer_perror.add_field("problem_type", OFTString, 30); + // 64bit integers are not supported in GDAL < 2, so we + // are using a workaround here in fields where we expect + // node IDs, we use real numbers. + m_layer_perror + .add_field("obj_type", OFTString, 1) + .add_field("obj_id", OFTInteger, 10) + .add_field("nodes", OFTInteger, 8) + .add_field("id1", OFTReal, 12, 1) + .add_field("id2", OFTReal, 12, 1) + .add_field("problem", OFTString, 30) + ; - m_layer_lerror.add_field("id1", OFTReal, 10); - m_layer_lerror.add_field("id2", OFTReal, 10); - m_layer_lerror.add_field("problem_type", OFTString, 30); + m_layer_lerror + .add_field("obj_type", OFTString, 1) + .add_field("obj_id", OFTInteger, 10) + .add_field("nodes", OFTInteger, 8) + .add_field("id1", OFTReal, 12, 1) + .add_field("id2", OFTReal, 12, 1) + .add_field("problem", OFTString, 30) + ; + + m_layer_ways + .add_field("obj_type", OFTString, 1) + .add_field("obj_id", OFTInteger, 10) + .add_field("way_id", OFTInteger, 10) + .add_field("nodes", OFTInteger, 8) + ; } ~ProblemReporterOGR() override = default; @@ -110,24 +143,82 @@ namespace osmium { write_point("duplicate_node", node_id1, node_id2, location); } - void report_intersection(osmium::object_id_type way1_id, osmium::Location way1_seg_start, osmium::Location way1_seg_end, - osmium::object_id_type way2_id, osmium::Location way2_seg_start, osmium::Location way2_seg_end, osmium::Location intersection) override { - write_point("intersection", m_object_id, 0, intersection); - write_line("intersection", m_object_id, way1_id, way1_seg_start, way1_seg_end); - write_line("intersection", m_object_id, way2_id, way2_seg_start, way2_seg_end); + void report_touching_ring(osmium::object_id_type node_id, osmium::Location location) override { + write_point("touching_ring", node_id, 0, location); } - void report_ring_not_closed(osmium::Location end1, osmium::Location end2) override { - write_point("ring_not_closed", m_object_id, 0, end1); - write_point("ring_not_closed", m_object_id, 0, end2); + void report_intersection(osmium::object_id_type way1_id, osmium::Location way1_seg_start, osmium::Location way1_seg_end, + osmium::object_id_type way2_id, osmium::Location way2_seg_start, osmium::Location way2_seg_end, osmium::Location intersection) override { + write_point("intersection", way1_id, way2_id, intersection); + write_line("intersection", way1_id, way2_id, way1_seg_start, way1_seg_end); + write_line("intersection", way2_id, way1_id, way2_seg_start, way2_seg_end); + } + + void report_duplicate_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) override { + write_line("duplicate_segment", nr1.ref(), nr2.ref(), nr1.location(), nr2.location()); + } + + void report_ring_not_closed(const osmium::NodeRef& nr, const osmium::Way* way = nullptr) override { + write_point("ring_not_closed", nr.ref(), way ? way->id() : 0, nr.location()); } void report_role_should_be_outer(osmium::object_id_type way_id, osmium::Location seg_start, osmium::Location seg_end) override { - write_line("role_should_be_outer", m_object_id, way_id, seg_start, seg_end); + write_line("role_should_be_outer", way_id, 0, seg_start, seg_end); } void report_role_should_be_inner(osmium::object_id_type way_id, osmium::Location seg_start, osmium::Location seg_end) override { - write_line("role_should_be_inner", m_object_id, way_id, seg_start, seg_end); + write_line("role_should_be_inner", way_id, 0, seg_start, seg_end); + } + + void report_way_in_multiple_rings(const osmium::Way& way) override { + if (way.nodes().size() < 2) { + return; + } + try { + gdalcpp::Feature feature(m_layer_lerror, m_ogr_factory.create_linestring(way)); + set_object(feature); + feature.set_field("id1", int32_t(way.id())); + feature.set_field("id2", 0); + feature.set_field("problem", "way_in_multiple_rings"); + feature.add_to_layer(); + } catch (const osmium::geometry_error&) { + // XXX + } + } + + void report_inner_with_same_tags(const osmium::Way& way) override { + if (way.nodes().size() < 2) { + return; + } + try { + gdalcpp::Feature feature(m_layer_lerror, m_ogr_factory.create_linestring(way)); + set_object(feature); + feature.set_field("id1", int32_t(way.id())); + feature.set_field("id2", 0); + feature.set_field("problem", "inner_with_same_tags"); + feature.add_to_layer(); + } catch (const osmium::geometry_error&) { + // XXX + } + } + + void report_way(const osmium::Way& way) override { + if (way.nodes().empty()) { + return; + } + if (way.nodes().size() == 1) { + const auto& first_nr = way.nodes()[0]; + write_point("single_node_in_way", way.id(), first_nr.ref(), first_nr.location()); + return; + } + try { + gdalcpp::Feature feature(m_layer_ways, m_ogr_factory.create_linestring(way)); + set_object(feature); + feature.set_field("way_id", int32_t(way.id())); + feature.add_to_layer(); + } catch (const osmium::geometry_error&) { + // XXX + } } }; // class ProblemReporterOGR diff --git a/include/osmium/area/problem_reporter_stream.hpp b/include/osmium/area/problem_reporter_stream.hpp index ffd94a605..c601867b2 100644 --- a/include/osmium/area/problem_reporter_stream.hpp +++ b/include/osmium/area/problem_reporter_stream.hpp @@ -38,7 +38,9 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include #include +#include namespace osmium { @@ -57,7 +59,7 @@ namespace osmium { ~ProblemReporterStream() override = default; void header(const char* msg) { - *m_out << "DATA PROBLEM: " << msg << " on " << item_type_to_char(m_object_type) << m_object_id << ": "; + *m_out << "DATA PROBLEM: " << msg << " on " << item_type_to_char(m_object_type) << m_object_id << " (with " << m_nodes << " nodes): "; } void report_duplicate_node(osmium::object_id_type node_id1, osmium::object_id_type node_id2, osmium::Location location) override { @@ -65,6 +67,11 @@ namespace osmium { *m_out << "node_id1=" << node_id1 << " node_id2=" << node_id2 << " location=" << location << "\n"; } + void report_touching_ring(osmium::object_id_type node_id, osmium::Location location) override { + header("touching ring"); + *m_out << "node_id=" << node_id << " location=" << location << "\n"; + } + void report_intersection(osmium::object_id_type way1_id, osmium::Location way1_seg_start, osmium::Location way1_seg_end, osmium::object_id_type way2_id, osmium::Location way2_seg_start, osmium::Location way2_seg_end, osmium::Location intersection) override { header("intersection"); @@ -72,9 +79,19 @@ namespace osmium { << " way2_id=" << way2_id << " way2_seg_start=" << way2_seg_start << " way2_seg_end=" << way2_seg_end << " intersection=" << intersection << "\n"; } - void report_ring_not_closed(osmium::Location end1, osmium::Location end2) override { + void report_duplicate_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) override { + header("duplicate segment"); + *m_out << "node_id1=" << nr1.ref() << " location1=" << nr1.location() + << " node_id2=" << nr2.ref() << " location2=" << nr2.location() << "\n"; + } + + void report_ring_not_closed(const osmium::NodeRef& nr, const osmium::Way* way = nullptr) override { header("ring not closed"); - *m_out << "end1=" << end1 << " end2=" << end2 << "\n"; + *m_out << "node_id=" << nr.ref() << " location=" << nr.location(); + if (way) { + *m_out << " on way " << way->id(); + } + *m_out << "\n"; } void report_role_should_be_outer(osmium::object_id_type way_id, osmium::Location seg_start, osmium::Location seg_end) override { @@ -87,6 +104,16 @@ namespace osmium { *m_out << "way_id=" << way_id << " seg_start=" << seg_start << " seg_end=" << seg_end << "\n"; } + void report_way_in_multiple_rings(const osmium::Way& way) override { + header("way in multiple rings"); + *m_out << "way_id=" << way.id() << '\n'; + } + + void report_inner_with_same_tags(const osmium::Way& way) override { + header("inner way with same tags as relation or outer"); + *m_out << "way_id=" << way.id() << '\n'; + } + }; // class ProblemReporterStream } // namespace area diff --git a/include/osmium/area/stats.hpp b/include/osmium/area/stats.hpp new file mode 100644 index 000000000..79cc0026a --- /dev/null +++ b/include/osmium/area/stats.hpp @@ -0,0 +1,128 @@ +#ifndef OSMIUM_AREA_STATS_HPP +#define OSMIUM_AREA_STATS_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include +#include + +namespace osmium { + + namespace area { + + /** + * These statistics are generated by the area assembler code. They + * tell the user of the assembler a lot about the objects this area + * is made out of, what happened during the assembly, and what errors + * there were. + */ + struct area_stats { + uint64_t area_really_complex_case = 0; ///< Most difficult case with rings touching in multiple points + uint64_t area_simple_case = 0; ///< Simple case, no touching rings + uint64_t area_touching_rings_case = 0; ///< More difficult case with touching rings + uint64_t duplicate_nodes = 0; ///< Consecutive identical nodes or consecutive nodes with same location + uint64_t duplicate_segments = 0; ///< Segments duplicated (going back and forth) + uint64_t from_relations = 0; ///< Area created from multipolygon relation + uint64_t from_ways = 0; ///< Area created from way + uint64_t inner_rings = 0; ///< Number of inner rings + uint64_t inner_with_same_tags = 0; ///< Number of inner ways with same tags as area + uint64_t intersections = 0; ///< Number of intersections between segments + uint64_t member_ways = 0; ///< Number of ways in the area + uint64_t no_tags_on_relation = 0; ///< No tags on relation (old-style multipolygon with tags on outer ways) + uint64_t no_way_in_mp_relation = 0; ///< Multipolygon relation with no way members + uint64_t nodes = 0; ///< Number of nodes in the area + uint64_t open_rings = 0; ///< Number of open rings in the area + uint64_t outer_rings = 0; ///< Number of outer rings in the area + uint64_t short_ways = 0; ///< Number of ways with less than two nodes + uint64_t single_way_in_mp_relation = 0; ///< Multipolygon relation containing a single way + uint64_t touching_rings = 0; ///< Rings touching in a node + uint64_t ways_in_multiple_rings = 0; ///< Different segments of a way ended up in different rings + uint64_t wrong_role = 0; ///< Member has wrong role (not "outer", "inner", or empty) + + area_stats& operator+=(const area_stats& other) noexcept { + area_really_complex_case += other.area_really_complex_case; + area_simple_case += other.area_simple_case; + area_touching_rings_case += other.area_touching_rings_case; + duplicate_nodes += other.duplicate_nodes; + duplicate_segments += other.duplicate_segments; + from_relations += other.from_relations; + from_ways += other.from_ways; + inner_rings += other.inner_rings; + inner_with_same_tags += other.inner_with_same_tags; + intersections += other.intersections; + member_ways += other.member_ways; + no_tags_on_relation += other.no_tags_on_relation; + no_way_in_mp_relation += other.no_way_in_mp_relation; + nodes += other.nodes; + open_rings += other.open_rings; + outer_rings += other.outer_rings; + short_ways += other.short_ways; + single_way_in_mp_relation += other.single_way_in_mp_relation; + touching_rings += other.touching_rings; + ways_in_multiple_rings += other.ways_in_multiple_rings; + wrong_role += other.wrong_role; + return *this; + } + + }; // struct area_stats + + template + inline std::basic_ostream& operator<<(std::basic_ostream& out, const area_stats& s) { + return out << " area_really_complex_case=" << s.area_really_complex_case + << " area_simple_case=" << s.area_simple_case + << " area_touching_rings_case=" << s.area_touching_rings_case + << " duplicate_nodes=" << s.duplicate_nodes + << " duplicate_segments=" << s.duplicate_segments + << " from_relations=" << s.from_relations + << " from_ways=" << s.from_ways + << " inner_rings=" << s.inner_rings + << " inner_with_same_tags=" << s.inner_with_same_tags + << " intersections=" << s.intersections + << " member_ways=" << s.member_ways + << " no_tags_on_relation=" << s.no_tags_on_relation + << " no_way_in_mp_relation=" << s.no_way_in_mp_relation + << " nodes=" << s.nodes + << " open_rings=" << s.open_rings + << " outer_rings=" << s.outer_rings + << " short_ways=" << s.short_ways + << " single_way_in_mp_relation=" << s.single_way_in_mp_relation + << " touching_rings=" << s.touching_rings + << " ways_in_multiple_rings=" << s.ways_in_multiple_rings + << " wrong_role=" << s.wrong_role; + } + + } // namespace area + +} // namespace osmium + +#endif // OSMIUM_AREA_STATS_HPP diff --git a/include/osmium/builder/attr.hpp b/include/osmium/builder/attr.hpp index d9831c28f..2a5b69070 100644 --- a/include/osmium/builder/attr.hpp +++ b/include/osmium/builder/attr.hpp @@ -46,8 +46,15 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include namespace osmium { @@ -261,6 +268,34 @@ namespace osmium { }; // class member_type + class member_type_string { + + osmium::item_type m_type; + osmium::object_id_type m_ref; + std::string m_role; + + public: + + member_type_string(osmium::item_type type, osmium::object_id_type ref, std::string&& role) : + m_type(type), + m_ref(ref), + m_role(std::move(role)) { + } + + osmium::item_type type() const noexcept { + return m_type; + } + + osmium::object_id_type ref() const noexcept { + return m_ref; + } + + const char* role() const noexcept { + return m_role.c_str(); + } + + }; // class member_type_string + class comment_type { osmium::Timestamp m_date; diff --git a/include/osmium/builder/builder.hpp b/include/osmium/builder/builder.hpp index 869fe44e5..1b274ada5 100644 --- a/include/osmium/builder/builder.hpp +++ b/include/osmium/builder/builder.hpp @@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include #include #include @@ -101,7 +100,7 @@ namespace osmium { * */ void add_padding(bool self = false) { - auto padding = osmium::memory::align_bytes - (size() % osmium::memory::align_bytes); + const auto padding = osmium::memory::align_bytes - (size() % osmium::memory::align_bytes); if (padding != osmium::memory::align_bytes) { std::fill_n(m_buffer.reserve_space(padding), padding, 0); if (self) { diff --git a/include/osmium/builder/builder_helper.hpp b/include/osmium/builder/builder_helper.hpp index e1b7c52ff..5e0f2181d 100644 --- a/include/osmium/builder/builder_helper.hpp +++ b/include/osmium/builder/builder_helper.hpp @@ -56,7 +56,7 @@ namespace osmium { * Use osmium::builder::add_way_node_list() instead. */ OSMIUM_DEPRECATED inline const osmium::WayNodeList& build_way_node_list(osmium::memory::Buffer& buffer, const std::initializer_list& nodes) { - size_t pos = buffer.committed(); + const size_t pos = buffer.committed(); { osmium::builder::WayNodeListBuilder wnl_builder(buffer); for (const auto& node_ref : nodes) { @@ -72,7 +72,7 @@ namespace osmium { * Use osmium::builder::add_tag_list() instead. */ inline const osmium::TagList& build_tag_list(osmium::memory::Buffer& buffer, const std::initializer_list>& tags) { - size_t pos = buffer.committed(); + const size_t pos = buffer.committed(); { osmium::builder::TagListBuilder tl_builder(buffer); for (const auto& p : tags) { @@ -88,7 +88,7 @@ namespace osmium { * Use osmium::builder::add_tag_list() instead. */ inline const osmium::TagList& build_tag_list_from_map(osmium::memory::Buffer& buffer, const std::map& tags) { - size_t pos = buffer.committed(); + const size_t pos = buffer.committed(); { osmium::builder::TagListBuilder tl_builder(buffer); for (const auto& p : tags) { @@ -104,7 +104,7 @@ namespace osmium { * Use osmium::builder::add_tag_list() instead. */ inline const osmium::TagList& build_tag_list_from_func(osmium::memory::Buffer& buffer, std::function func) { - size_t pos = buffer.committed(); + const size_t pos = buffer.committed(); { osmium::builder::TagListBuilder tl_builder(buffer); func(tl_builder); diff --git a/include/osmium/builder/osm_object_builder.hpp b/include/osmium/builder/osm_object_builder.hpp index d4b54f8fc..e7a82988b 100644 --- a/include/osmium/builder/osm_object_builder.hpp +++ b/include/osmium/builder/osm_object_builder.hpp @@ -34,7 +34,6 @@ DEALINGS IN THE SOFTWARE. */ #include -#include #include #include #include @@ -44,16 +43,23 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include #include #include #include #include #include +#include +#include +#include +#include +#include +#include namespace osmium { + class Node; + namespace memory { class Buffer; } // namespace memory @@ -186,9 +192,9 @@ namespace osmium { }; // class NodeRefListBuilder - typedef NodeRefListBuilder WayNodeListBuilder; - typedef NodeRefListBuilder OuterRingBuilder; - typedef NodeRefListBuilder InnerRingBuilder; + using WayNodeListBuilder = NodeRefListBuilder; + using OuterRingBuilder = NodeRefListBuilder; + using InnerRingBuilder = NodeRefListBuilder; class RelationMemberListBuilder : public ObjectBuilder { @@ -353,8 +359,8 @@ namespace osmium { }; // class OSMObjectBuilder - typedef OSMObjectBuilder NodeBuilder; - typedef OSMObjectBuilder RelationBuilder; + using NodeBuilder = OSMObjectBuilder; + using RelationBuilder = OSMObjectBuilder; class WayBuilder : public OSMObjectBuilder { @@ -398,7 +404,7 @@ namespace osmium { }; // class AreaBuilder - typedef ObjectBuilder ChangesetBuilder; + using ChangesetBuilder = ObjectBuilder; } // namespace builder diff --git a/include/osmium/diff_iterator.hpp b/include/osmium/diff_iterator.hpp index 6e0ba4bbf..fc92b36ce 100644 --- a/include/osmium/diff_iterator.hpp +++ b/include/osmium/diff_iterator.hpp @@ -34,8 +34,10 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include #include +#include #include @@ -49,7 +51,7 @@ namespace osmium { * underlying OSMObjects. */ template - class DiffIterator : public std::iterator { + class DiffIterator { static_assert(std::is_base_of::value, "TBasicIterator::value_type must derive from osmium::OSMObject"); @@ -76,6 +78,12 @@ namespace osmium { public: + using iterator_category = std::input_iterator_tag; + using value_type = const osmium::DiffObject; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + DiffIterator(TBasicIterator begin, TBasicIterator end) : m_prev(begin), m_curr(begin), diff --git a/include/osmium/dynamic_handler.hpp b/include/osmium/dynamic_handler.hpp index e7c9900fa..01d15546b 100644 --- a/include/osmium/dynamic_handler.hpp +++ b/include/osmium/dynamic_handler.hpp @@ -36,11 +36,16 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include namespace osmium { + class Node; + class Way; + class Relation; + class Area; + class Changeset; + namespace handler { namespace detail { @@ -143,7 +148,7 @@ auto _name_##_dispatch(THandler& handler, const osmium::_type_& object, long) -> class DynamicHandler : public osmium::handler::Handler { - typedef std::unique_ptr impl_ptr; + using impl_ptr = std::unique_ptr; impl_ptr m_impl; public: diff --git a/include/osmium/experimental/flex_reader.hpp b/include/osmium/experimental/flex_reader.hpp index 8059ea32a..0a2a66837 100644 --- a/include/osmium/experimental/flex_reader.hpp +++ b/include/osmium/experimental/flex_reader.hpp @@ -34,11 +34,12 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include #include #include -#include +#include // IWYU pragma: keep #include #include #include diff --git a/include/osmium/geom/factory.hpp b/include/osmium/geom/factory.hpp index 394228eb9..14c51dfdf 100644 --- a/include/osmium/geom/factory.hpp +++ b/include/osmium/geom/factory.hpp @@ -46,6 +46,8 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include +#include #include namespace osmium { @@ -148,7 +150,7 @@ namespace osmium { /** * Add all points of an outer or inner ring to a multipolygon. */ - void add_points(const osmium::OuterRing& nodes) { + void add_points(const osmium::NodeRefList& nodes) { osmium::Location last_location; for (const osmium::NodeRef& node_ref : nodes) { if (last_location != node_ref.location()) { @@ -169,7 +171,7 @@ namespace osmium { template explicit GeometryFactory(TArgs&&... args) : m_projection(), - m_impl(std::forward(args)...) { + m_impl(m_projection.epsg(), std::forward(args)...) { } /** @@ -179,15 +181,16 @@ namespace osmium { template explicit GeometryFactory(TProjection&& projection, TArgs&&... args) : m_projection(std::move(projection)), - m_impl(std::forward(args)...) { + m_impl(m_projection.epsg(), std::forward(args)...) { } - typedef TProjection projection_type; - typedef typename TGeomImpl::point_type point_type; - typedef typename TGeomImpl::linestring_type linestring_type; - typedef typename TGeomImpl::polygon_type polygon_type; - typedef typename TGeomImpl::multipolygon_type multipolygon_type; - typedef typename TGeomImpl::ring_type ring_type; + using projection_type = TProjection; + + using point_type = typename TGeomImpl::point_type; + using linestring_type = typename TGeomImpl::linestring_type; + using polygon_type = typename TGeomImpl::polygon_type; + using multipolygon_type = typename TGeomImpl::multipolygon_type; + using ring_type = typename TGeomImpl::ring_type; int epsg() const { return m_projection.epsg(); @@ -280,13 +283,13 @@ namespace osmium { } if (num_points < 2) { - throw osmium::geometry_error("need at least two points for linestring"); + throw osmium::geometry_error{"need at least two points for linestring"}; } return linestring_finish(num_points); } - linestring_type create_linestring(const osmium::Way& way, use_nodes un=use_nodes::unique, direction dir=direction::forward) { + linestring_type create_linestring(const osmium::Way& way, use_nodes un=use_nodes::unique, direction dir = direction::forward) { try { return create_linestring(way.nodes(), un, dir); } catch (osmium::geometry_error& e) { @@ -354,13 +357,13 @@ namespace osmium { } if (num_points < 4) { - throw osmium::geometry_error("need at least four points for polygon"); + throw osmium::geometry_error{"need at least four points for polygon"}; } return polygon_finish(num_points); } - polygon_type create_polygon(const osmium::Way& way, use_nodes un=use_nodes::unique, direction dir=direction::forward) { + polygon_type create_polygon(const osmium::Way& way, use_nodes un=use_nodes::unique, direction dir = direction::forward) { try { return create_polygon(way.nodes(), un, dir); } catch (osmium::geometry_error& e) { @@ -378,8 +381,8 @@ namespace osmium { m_impl.multipolygon_start(); for (auto it = area.cbegin(); it != area.cend(); ++it) { - const osmium::OuterRing& ring = static_cast(*it); if (it->type() == osmium::item_type::outer_ring) { + auto& ring = static_cast(*it); if (num_polygons > 0) { m_impl.multipolygon_polygon_finish(); } @@ -390,6 +393,7 @@ namespace osmium { ++num_rings; ++num_polygons; } else if (it->type() == osmium::item_type::inner_ring) { + auto& ring = static_cast(*it); m_impl.multipolygon_inner_ring_start(); add_points(ring); m_impl.multipolygon_inner_ring_finish(); @@ -399,7 +403,7 @@ namespace osmium { // if there are no rings, this area is invalid if (num_rings == 0) { - throw osmium::geometry_error("area contains no rings"); + throw osmium::geometry_error{"invalid area"}; } m_impl.multipolygon_polygon_finish(); diff --git a/include/osmium/geom/geojson.hpp b/include/osmium/geom/geojson.hpp index 044a89f86..e9f722fa9 100644 --- a/include/osmium/geom/geojson.hpp +++ b/include/osmium/geom/geojson.hpp @@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include #include @@ -53,13 +54,13 @@ namespace osmium { public: - typedef std::string point_type; - typedef std::string linestring_type; - typedef std::string polygon_type; - typedef std::string multipolygon_type; - typedef std::string ring_type; + using point_type = std::string; + using linestring_type = std::string; + using polygon_type = std::string; + using multipolygon_type = std::string; + using ring_type = std::string; - GeoJSONFactoryImpl(int precision = 7) : + GeoJSONFactoryImpl(int /* srid */, int precision = 7) : m_precision(precision) { } diff --git a/include/osmium/geom/geos.hpp b/include/osmium/geom/geos.hpp index f9026e5a9..f406076a1 100644 --- a/include/osmium/geom/geos.hpp +++ b/include/osmium/geom/geos.hpp @@ -42,9 +42,14 @@ DEALINGS IN THE SOFTWARE. * @attention If you include this file, you'll need to link with `libgeos`. */ +#include +#include +#include +#include #include #include #include +#include #include #include @@ -59,11 +64,13 @@ DEALINGS IN THE SOFTWARE. #include #include +#include // MSVC doesn't support throw_with_nested yet #ifdef _MSC_VER # define THROW throw #else +# include # define THROW std::throw_with_nested #endif @@ -71,8 +78,8 @@ namespace osmium { struct geos_geometry_error : public geometry_error { - geos_geometry_error(const char* message) : - geometry_error(std::string("geometry creation failed in GEOS library: ") + message) { + explicit geos_geometry_error(const char* message) : + geometry_error(std::string{"geometry creation failed in GEOS library: "} + message) { } }; // struct geos_geometry_error @@ -93,19 +100,29 @@ namespace osmium { public: - typedef std::unique_ptr point_type; - typedef std::unique_ptr linestring_type; - typedef std::unique_ptr polygon_type; - typedef std::unique_ptr multipolygon_type; - typedef std::unique_ptr ring_type; + using point_type = std::unique_ptr; + using linestring_type = std::unique_ptr; + using polygon_type = std::unique_ptr; + using multipolygon_type = std::unique_ptr; + using ring_type = std::unique_ptr; - explicit GEOSFactoryImpl(geos::geom::GeometryFactory& geos_factory) : + explicit GEOSFactoryImpl(int /* srid */, geos::geom::GeometryFactory& geos_factory) : m_precision_model(nullptr), m_our_geos_factory(nullptr), m_geos_factory(&geos_factory) { } - explicit GEOSFactoryImpl(int srid = -1) : + /** + * @deprecated Do not set SRID explicitly. It will be set to the + * correct value automatically. + */ + OSMIUM_DEPRECATED explicit GEOSFactoryImpl(int /* srid */, int srid) : + m_precision_model(new geos::geom::PrecisionModel), + m_our_geos_factory(new geos::geom::GeometryFactory(m_precision_model.get(), srid)), + m_geos_factory(m_our_geos_factory.get()) { + } + + explicit GEOSFactoryImpl(int srid) : m_precision_model(new geos::geom::PrecisionModel), m_our_geos_factory(new geos::geom::GeometryFactory(m_precision_model.get(), srid)), m_geos_factory(m_our_geos_factory.get()) { @@ -116,7 +133,7 @@ namespace osmium { point_type make_point(const osmium::geom::Coordinates& xy) const { try { return point_type(m_geos_factory->createPoint(geos::geom::Coordinate(xy.x, xy.y))); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -126,7 +143,7 @@ namespace osmium { void linestring_start() { try { m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast(0), 2)); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -134,7 +151,7 @@ namespace osmium { void linestring_add_location(const osmium::geom::Coordinates& xy) { try { m_coordinate_sequence->add(geos::geom::Coordinate(xy.x, xy.y)); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -142,7 +159,7 @@ namespace osmium { linestring_type linestring_finish(size_t /* num_points */) { try { return linestring_type(m_geos_factory->createLineString(m_coordinate_sequence.release())); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -166,7 +183,7 @@ namespace osmium { }); m_polygons.emplace_back(m_geos_factory->createPolygon(m_rings[0].release(), inner_rings)); m_rings.clear(); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -174,7 +191,7 @@ namespace osmium { void multipolygon_outer_ring_start() { try { m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast(0), 2)); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -182,7 +199,7 @@ namespace osmium { void multipolygon_outer_ring_finish() { try { m_rings.emplace_back(m_geos_factory->createLinearRing(m_coordinate_sequence.release())); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -190,7 +207,7 @@ namespace osmium { void multipolygon_inner_ring_start() { try { m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast(0), 2)); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -198,7 +215,7 @@ namespace osmium { void multipolygon_inner_ring_finish() { try { m_rings.emplace_back(m_geos_factory->createLinearRing(m_coordinate_sequence.release())); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -206,7 +223,7 @@ namespace osmium { void multipolygon_add_location(const osmium::geom::Coordinates& xy) { try { m_coordinate_sequence->add(geos::geom::Coordinate(xy.x, xy.y)); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } @@ -219,7 +236,7 @@ namespace osmium { }); m_polygons.clear(); return multipolygon_type(m_geos_factory->createMultiPolygon(polygons)); - } catch (geos::util::GEOSException& e) { + } catch (const geos::util::GEOSException& e) { THROW(osmium::geos_geometry_error(e.what())); } } diff --git a/include/osmium/geom/haversine.hpp b/include/osmium/geom/haversine.hpp index 632bc1612..ef3f1ff78 100644 --- a/include/osmium/geom/haversine.hpp +++ b/include/osmium/geom/haversine.hpp @@ -56,7 +56,7 @@ namespace osmium { namespace haversine { /// @brief Earth's quadratic mean radius for WGS84 - constexpr double EARTH_RADIUS_IN_METERS = 6372797.560856; + constexpr const double EARTH_RADIUS_IN_METERS = 6372797.560856; /** * Calculate distance in meters between two sets of coordinates. diff --git a/include/osmium/geom/ogr.hpp b/include/osmium/geom/ogr.hpp index b8e9ef3ab..a91fbe5c5 100644 --- a/include/osmium/geom/ogr.hpp +++ b/include/osmium/geom/ogr.hpp @@ -62,33 +62,34 @@ namespace osmium { public: - typedef std::unique_ptr point_type; - typedef std::unique_ptr linestring_type; - typedef std::unique_ptr polygon_type; - typedef std::unique_ptr multipolygon_type; - typedef std::unique_ptr ring_type; + using point_type = std::unique_ptr; + using linestring_type = std::unique_ptr; + using polygon_type = std::unique_ptr; + using multipolygon_type = std::unique_ptr; + using ring_type = std::unique_ptr; private: - linestring_type m_linestring; - multipolygon_type m_multipolygon; - polygon_type m_polygon; - ring_type m_ring; + linestring_type m_linestring{nullptr}; + multipolygon_type m_multipolygon{nullptr}; + polygon_type m_polygon{nullptr}; + ring_type m_ring{nullptr}; public: - OGRFactoryImpl() = default; + explicit OGRFactoryImpl(int /* srid */) { + } /* Point */ point_type make_point(const osmium::geom::Coordinates& xy) const { - return point_type(new OGRPoint(xy.x, xy.y)); + return point_type{new OGRPoint{xy.x, xy.y}}; } /* LineString */ void linestring_start() { - m_linestring = std::unique_ptr(new OGRLineString()); + m_linestring.reset(new OGRLineString{}); } void linestring_add_location(const osmium::geom::Coordinates& xy) { @@ -97,13 +98,14 @@ namespace osmium { } linestring_type linestring_finish(size_t /* num_points */) { + assert(!!m_linestring); return std::move(m_linestring); } /* Polygon */ void polygon_start() { - m_ring = std::unique_ptr(new OGRLinearRing()); + m_ring.reset(new OGRLinearRing{}); } void polygon_add_location(const osmium::geom::Coordinates& xy) { @@ -112,7 +114,7 @@ namespace osmium { } polygon_type polygon_finish(size_t /* num_points */) { - std::unique_ptr polygon = std::unique_ptr(new OGRPolygon()); + auto polygon = std::unique_ptr{new OGRPolygon{}}; polygon->addRingDirectly(m_ring.release()); return polygon; } @@ -120,11 +122,11 @@ namespace osmium { /* MultiPolygon */ void multipolygon_start() { - m_multipolygon.reset(new OGRMultiPolygon()); + m_multipolygon.reset(new OGRMultiPolygon{}); } void multipolygon_polygon_start() { - m_polygon.reset(new OGRPolygon()); + m_polygon.reset(new OGRPolygon{}); } void multipolygon_polygon_finish() { @@ -134,7 +136,7 @@ namespace osmium { } void multipolygon_outer_ring_start() { - m_ring.reset(new OGRLinearRing()); + m_ring.reset(new OGRLinearRing{}); } void multipolygon_outer_ring_finish() { @@ -144,7 +146,7 @@ namespace osmium { } void multipolygon_inner_ring_start() { - m_ring.reset(new OGRLinearRing()); + m_ring.reset(new OGRLinearRing{}); } void multipolygon_inner_ring_finish() { diff --git a/include/osmium/geom/projection.hpp b/include/osmium/geom/projection.hpp index 42d95b27e..eaa9b53f3 100644 --- a/include/osmium/geom/projection.hpp +++ b/include/osmium/geom/projection.hpp @@ -70,15 +70,19 @@ namespace osmium { public: - CRS(const std::string& crs) : + explicit CRS(const std::string& crs) : m_crs(pj_init_plus(crs.c_str()), ProjCRSDeleter()) { if (!m_crs) { - throw osmium::projection_error(std::string("creation of CRS failed: ") + pj_strerrno(*pj_get_errno_ref())); + throw osmium::projection_error(std::string{"creation of CRS failed: "} + pj_strerrno(*pj_get_errno_ref())); } } - CRS(int epsg) : - CRS(std::string("+init=epsg:") + std::to_string(epsg)) { + explicit CRS(const char* crs) : + CRS(std::string{crs}) { + } + + explicit CRS(int epsg) : + CRS(std::string{"+init=epsg:"} + std::to_string(epsg)) { } /** @@ -127,13 +131,19 @@ namespace osmium { public: - Projection(const std::string& proj_string) : + explicit Projection(const std::string& proj_string) : m_epsg(-1), m_proj_string(proj_string), m_crs_user(proj_string) { } - Projection(int epsg) : + explicit Projection(const char* proj_string) : + m_epsg(-1), + m_proj_string(proj_string), + m_crs_user(proj_string) { + } + + explicit Projection(int epsg) : m_epsg(epsg), m_proj_string(std::string("+init=epsg:") + std::to_string(epsg)), m_crs_user(epsg) { diff --git a/include/osmium/geom/rapid_geojson.hpp b/include/osmium/geom/rapid_geojson.hpp index 87e13110f..7817389c9 100644 --- a/include/osmium/geom/rapid_geojson.hpp +++ b/include/osmium/geom/rapid_geojson.hpp @@ -33,6 +33,8 @@ DEALINGS IN THE SOFTWARE. */ +#include + #include #include @@ -53,13 +55,13 @@ namespace osmium { public: - typedef void point_type; - typedef void linestring_type; - typedef void polygon_type; - typedef void multipolygon_type; - typedef void ring_type; + using point_type = void; + using linestring_type = void; + using polygon_type = void; + using multipolygon_type = void; + using ring_type = void; - RapidGeoJSONFactoryImpl(TWriter& writer) : + RapidGeoJSONFactoryImpl(int /* srid */, TWriter& writer) : m_writer(&writer) { } diff --git a/include/osmium/geom/tile.hpp b/include/osmium/geom/tile.hpp index e35c2ee59..672ae5416 100644 --- a/include/osmium/geom/tile.hpp +++ b/include/osmium/geom/tile.hpp @@ -33,9 +33,12 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include +#include namespace osmium { @@ -57,24 +60,64 @@ namespace osmium { */ struct Tile { + /// x coordinate uint32_t x; + + /// y coordinate uint32_t y; + + /// Zoom level uint32_t z; + /** + * Create a tile with the given zoom level and x any y tile + * coordinates. + * + * The values are not checked for validity. + * + * @pre @code zoom <= 30 && x < 2^zoom && y < 2^zoom @endcode + */ explicit Tile(uint32_t zoom, uint32_t tx, uint32_t ty) noexcept : x(tx), y(ty), z(zoom) { + assert(zoom <= 30u); + assert(x < (1u << zoom)); + assert(y < (1u << zoom)); } + /** + * Create a tile with the given zoom level that contains the given + * location. + * + * The values are not checked for validity. + * + * @pre @code location.valid() && zoom <= 30 @endcode + */ explicit Tile(uint32_t zoom, const osmium::Location& location) : z(zoom) { - osmium::geom::Coordinates c = lonlat_to_mercator(location); + assert(zoom <= 30u); + assert(location.valid()); + const osmium::geom::Coordinates c = lonlat_to_mercator(location); const int32_t n = 1 << zoom; const double scale = detail::max_coordinate_epsg3857 * 2 / n; x = uint32_t(detail::restrict_to_range(int32_t((c.x + detail::max_coordinate_epsg3857) / scale), 0, n-1)); y = uint32_t(detail::restrict_to_range(int32_t((detail::max_coordinate_epsg3857 - c.y) / scale), 0, n-1)); } + /** + * Check whether this tile is valid. For a tile to be valid the + * zoom level must be between 0 and 30 and the coordinates must + * each be between 0 and 2^zoom-1. + */ + bool valid() const noexcept { + if (z > 30) { + return false; + } + const uint32_t max = 1 << z; + return x < max && y < max; + } + }; // struct Tile + /// Tiles are equal if all their attributes are equal. inline bool operator==(const Tile& a, const Tile& b) { return a.z == b.z && a.x == b.x && a.y == b.y; } diff --git a/include/osmium/geom/util.hpp b/include/osmium/geom/util.hpp index fa9c8bc36..b5682f92f 100644 --- a/include/osmium/geom/util.hpp +++ b/include/osmium/geom/util.hpp @@ -44,11 +44,11 @@ namespace osmium { */ struct projection_error : public std::runtime_error { - projection_error(const std::string& what) : + explicit projection_error(const std::string& what) : std::runtime_error(what) { } - projection_error(const char* what) : + explicit projection_error(const char* what) : std::runtime_error(what) { } diff --git a/include/osmium/geom/wkb.hpp b/include/osmium/geom/wkb.hpp index 19c12165e..d6b779879 100644 --- a/include/osmium/geom/wkb.hpp +++ b/include/osmium/geom/wkb.hpp @@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -60,14 +61,13 @@ namespace osmium { template inline void str_push(std::string& str, T data) { - size_t size = str.size(); - str.resize(size + sizeof(T)); - std::copy_n(reinterpret_cast(&data), sizeof(T), &str[size]); + str.append(reinterpret_cast(&data), sizeof(T)); } inline std::string convert_to_hex(const std::string& str) { static const char* lookup_hex = "0123456789ABCDEF"; std::string out; + out.reserve(str.size() * 2); for (char c : str) { out += lookup_hex[(c >> 4) & 0xf]; @@ -79,9 +79,6 @@ namespace osmium { class WKBFactoryImpl { - /// OSM data always uses SRID 4326 (WGS84). - static constexpr uint32_t srid = 4326; - /** * Type of WKB geometry. * These definitions are from @@ -112,6 +109,7 @@ namespace osmium { std::string m_data; uint32_t m_points {0}; + int m_srid; wkb_type m_wkb_type; out_type m_out_type; @@ -130,11 +128,11 @@ namespace osmium { #endif if (m_wkb_type == wkb_type::ewkb) { str_push(str, type | wkbSRID); - str_push(str, srid); + str_push(str, m_srid); } else { str_push(str, type); } - size_t offset = str.size(); + const size_t offset = str.size(); if (add_length) { str_push(str, static_cast(0)); } @@ -142,18 +140,20 @@ namespace osmium { } void set_size(const size_t offset, const size_t size) { - *reinterpret_cast(&m_data[offset]) = static_cast_with_assert(size); + uint32_t s = static_cast_with_assert(size); + std::copy_n(reinterpret_cast(&s), sizeof(uint32_t), &m_data[offset]); } public: - typedef std::string point_type; - typedef std::string linestring_type; - typedef std::string polygon_type; - typedef std::string multipolygon_type; - typedef std::string ring_type; + using point_type = std::string; + using linestring_type = std::string; + using polygon_type = std::string; + using multipolygon_type = std::string; + using ring_type = std::string; - explicit WKBFactoryImpl(wkb_type wtype = wkb_type::wkb, out_type otype = out_type::binary) : + explicit WKBFactoryImpl(int srid, wkb_type wtype = wkb_type::wkb, out_type otype = out_type::binary) : + m_srid(srid), m_wkb_type(wtype), m_out_type(otype) { } diff --git a/include/osmium/geom/wkt.hpp b/include/osmium/geom/wkt.hpp index ae73b9604..e360f712b 100644 --- a/include/osmium/geom/wkt.hpp +++ b/include/osmium/geom/wkt.hpp @@ -45,29 +45,44 @@ namespace osmium { namespace geom { + enum class wkt_type : bool { + wkt = false, + ewkt = true + }; // enum class wkt_type + namespace detail { class WKTFactoryImpl { + std::string m_srid_prefix; std::string m_str; int m_precision; + wkt_type m_wkt_type; public: - typedef std::string point_type; - typedef std::string linestring_type; - typedef std::string polygon_type; - typedef std::string multipolygon_type; - typedef std::string ring_type; + using point_type = std::string; + using linestring_type = std::string; + using polygon_type = std::string; + using multipolygon_type = std::string; + using ring_type = std::string; - WKTFactoryImpl(int precision = 7) : - m_precision(precision) { + WKTFactoryImpl(int srid, int precision = 7, wkt_type wtype = wkt_type::wkt) : + m_srid_prefix(), + m_precision(precision), + m_wkt_type(wtype) { + if (m_wkt_type == wkt_type::ewkt) { + m_srid_prefix = "SRID="; + m_srid_prefix += std::to_string(srid); + m_srid_prefix += ';'; + } } /* Point */ point_type make_point(const osmium::geom::Coordinates& xy) const { - std::string str {"POINT"}; + std::string str {m_srid_prefix}; + str += "POINT"; xy.append_to_string(str, '(', ' ', ')', m_precision); return str; } @@ -75,7 +90,8 @@ namespace osmium { /* LineString */ void linestring_start() { - m_str = "LINESTRING("; + m_str = m_srid_prefix; + m_str += "LINESTRING("; } void linestring_add_location(const osmium::geom::Coordinates& xy) { @@ -97,7 +113,8 @@ namespace osmium { /* MultiPolygon */ void multipolygon_start() { - m_str = "MULTIPOLYGON("; + m_str = m_srid_prefix; + m_str += "MULTIPOLYGON("; } void multipolygon_polygon_start() { diff --git a/include/osmium/handler.hpp b/include/osmium/handler.hpp index 3620d6546..1263910d4 100644 --- a/include/osmium/handler.hpp +++ b/include/osmium/handler.hpp @@ -33,56 +33,82 @@ DEALINGS IN THE SOFTWARE. */ -#include - namespace osmium { + class Area; + class Changeset; + class ChangesetDiscussion; + class InnerRing; + class Node; + class OSMObject; + class OuterRing; + class Relation; + class RelationMemberList; + class TagList; + class Way; + class WayNodeList; + /** * @brief Osmium handlers provide callbacks for OSM objects */ namespace handler { + /** + * Handler base class. Never used directly. Derive your own class from + * this class and "overwrite" the functions. Your functions must be + * named the same, but don't have to be const or noexcept or take + * their argument as const. + * + * Usually you will overwrite the node(), way(), and relation() + * functions. If your program supports multipolygons, also the area() + * function. You can also use the osm_object() function which is + * called for all OSM objects (nodes, ways, relations, and areas) + * right before each of their specific callbacks is called. + * + * If you are working with changesets, implement the changeset() + * function. + */ class Handler { public: - void osm_object(const osmium::OSMObject&) const { + void osm_object(const osmium::OSMObject&) const noexcept { } - void node(const osmium::Node&) const { + void node(const osmium::Node&) const noexcept { } - void way(const osmium::Way&) const { + void way(const osmium::Way&) const noexcept { } - void relation(const osmium::Relation&) const { + void relation(const osmium::Relation&) const noexcept { } - void area(const osmium::Area&) const { + void area(const osmium::Area&) const noexcept { } - void changeset(const osmium::Changeset&) const { + void changeset(const osmium::Changeset&) const noexcept { } - void tag_list(const osmium::TagList&) const { + void tag_list(const osmium::TagList&) const noexcept { } - void way_node_list(const osmium::WayNodeList&) const { + void way_node_list(const osmium::WayNodeList&) const noexcept { } - void relation_member_list(const osmium::RelationMemberList&) const { + void relation_member_list(const osmium::RelationMemberList&) const noexcept { } - void outer_ring(const osmium::OuterRing&) const { + void outer_ring(const osmium::OuterRing&) const noexcept { } - void inner_ring(const osmium::InnerRing&) const { + void inner_ring(const osmium::InnerRing&) const noexcept { } - void changeset_discussion(const osmium::ChangesetDiscussion&) const { + void changeset_discussion(const osmium::ChangesetDiscussion&) const noexcept { } - void flush() const { + void flush() const noexcept { } }; // class Handler diff --git a/include/osmium/handler/chain.hpp b/include/osmium/handler/chain.hpp index e59eb05b9..109e3537a 100644 --- a/include/osmium/handler/chain.hpp +++ b/include/osmium/handler/chain.hpp @@ -67,7 +67,7 @@ namespace osmium { template class ChainHandler : public osmium::handler::Handler { - typedef std::tuple handlers_type; + using handlers_type = std::tuple; handlers_type m_handlers; template diff --git a/include/osmium/handler/check_order.hpp b/include/osmium/handler/check_order.hpp index 143794bd5..a9b6834a4 100644 --- a/include/osmium/handler/check_order.hpp +++ b/include/osmium/handler/check_order.hpp @@ -51,11 +51,11 @@ namespace osmium { */ struct out_of_order_error : public std::runtime_error { - out_of_order_error(const std::string& what) : + explicit out_of_order_error(const std::string& what) : std::runtime_error(what) { } - out_of_order_error(const char* what) : + explicit out_of_order_error(const char* what) : std::runtime_error(what) { } diff --git a/include/osmium/handler/disk_store.hpp b/include/osmium/handler/disk_store.hpp index b8ab229a3..4e955734a 100644 --- a/include/osmium/handler/disk_store.hpp +++ b/include/osmium/handler/disk_store.hpp @@ -57,7 +57,7 @@ namespace osmium { */ class DiskStore : public osmium::handler::Handler { - typedef osmium::index::map::Map offset_index_type; + using offset_index_type = osmium::index::map::Map; size_t m_offset = 0; int m_data_fd; diff --git a/include/osmium/handler/node_locations_for_ways.hpp b/include/osmium/handler/node_locations_for_ways.hpp index f62a4db7c..a490f9e28 100644 --- a/include/osmium/handler/node_locations_for_ways.hpp +++ b/include/osmium/handler/node_locations_for_ways.hpp @@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include @@ -50,7 +51,7 @@ namespace osmium { namespace handler { - typedef osmium::index::map::Dummy dummy_type; + using dummy_type = osmium::index::map::Dummy; /** * Handler to retrieve locations from nodes and add them to ways. @@ -69,8 +70,8 @@ namespace osmium { public: - typedef TStoragePosIDs index_pos_type; - typedef TStorageNegIDs index_neg_type; + using index_pos_type = TStoragePosIDs; + using index_neg_type = TStorageNegIDs; private: @@ -80,6 +81,8 @@ namespace osmium { /// Object that handles the actual storage of the node locations (with negative IDs). TStorageNegIDs& m_storage_neg; + osmium::unsigned_object_id_type m_last_id{0}; + bool m_ignore_errors {false}; bool m_must_sort {false}; @@ -115,7 +118,11 @@ namespace osmium { * Store the location of the node in the storage. */ void node(const osmium::Node& node) { - m_must_sort = true; + if (node.positive_id() < m_last_id) { + m_must_sort = true; + } + m_last_id = node.positive_id(); + const osmium::object_id_type id = node.id(); if (id >= 0) { m_storage_pos.set(static_cast( id), node.location()); @@ -144,6 +151,7 @@ namespace osmium { m_storage_pos.sort(); m_storage_neg.sort(); m_must_sort = false; + m_last_id = std::numeric_limits::max(); } bool error = false; for (auto& node_ref : way.nodes()) { @@ -152,7 +160,7 @@ namespace osmium { if (!node_ref.location()) { error = true; } - } catch (osmium::not_found&) { + } catch (const osmium::not_found&) { error = true; } } diff --git a/include/osmium/handler/object_relations.hpp b/include/osmium/handler/object_relations.hpp index 4afcf6a6d..279365d08 100644 --- a/include/osmium/handler/object_relations.hpp +++ b/include/osmium/handler/object_relations.hpp @@ -52,7 +52,7 @@ namespace osmium { */ class ObjectRelations : public osmium::handler::Handler { - typedef osmium::index::multimap::Multimap index_type; + using index_type = osmium::index::multimap::Multimap; index_type& m_index_n2w; index_type& m_index_n2r; diff --git a/include/osmium/index/detail/create_map_with_fd.hpp b/include/osmium/index/detail/create_map_with_fd.hpp index 640b3c693..72b326a1b 100644 --- a/include/osmium/index/detail/create_map_with_fd.hpp +++ b/include/osmium/index/detail/create_map_with_fd.hpp @@ -54,9 +54,9 @@ namespace osmium { } assert(config.size() > 1); const std::string& filename = config[1]; - int fd = ::open(filename.c_str(), O_CREAT | O_RDWR, 0644); + const int fd = ::open(filename.c_str(), O_CREAT | O_RDWR, 0644); if (fd == -1) { - throw std::runtime_error(std::string("can't open file '") + filename + "': " + strerror(errno)); + throw std::runtime_error(std::string("can't open file '") + filename + "': " + std::strerror(errno)); } return new T(fd); } diff --git a/include/osmium/index/detail/mmap_vector_base.hpp b/include/osmium/index/detail/mmap_vector_base.hpp index aadeef0b5..8f52e9849 100644 --- a/include/osmium/index/detail/mmap_vector_base.hpp +++ b/include/osmium/index/detail/mmap_vector_base.hpp @@ -33,10 +33,13 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include #include #include // IWYU pragma: keep #include +#include #include namespace osmium { @@ -63,22 +66,26 @@ namespace osmium { mmap_vector_base(int fd, size_t capacity, size_t size = 0) : m_size(size), m_mapping(capacity, osmium::util::MemoryMapping::mapping_mode::write_shared, fd) { + assert(size <= capacity); + std::fill(data() + size, data() + capacity, osmium::index::empty_value()); + shrink_to_fit(); } explicit mmap_vector_base(size_t capacity = mmap_vector_size_increment) : m_size(0), m_mapping(capacity) { + std::fill_n(data(), capacity, osmium::index::empty_value()); } ~mmap_vector_base() noexcept = default; - typedef T value_type; - typedef T& reference; - typedef const T& const_reference; - typedef T* pointer; - typedef const T* const_pointer; - typedef T* iterator; - typedef const T* const_iterator; + using value_type = T; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = value_type*; + using const_iterator = const value_type*; void close() { m_mapping.unmap(); @@ -105,6 +112,7 @@ namespace osmium { } T& operator[](size_t n) { + assert(n < m_size); return data()[n]; } @@ -120,20 +128,21 @@ namespace osmium { } void shrink_to_fit() { - // XXX do something here + while (m_size > 0 && data()[m_size - 1] == osmium::index::empty_value()) { + --m_size; + } } void push_back(const T& value) { - if (m_size >= capacity()) { - resize(m_size+1); - } - data()[m_size] = value; - ++m_size; + resize(m_size+1); + data()[m_size-1] = value; } void reserve(size_t new_capacity) { if (new_capacity > capacity()) { + const size_t old_capacity = capacity(); m_mapping.resize(new_capacity); + std::fill(data() + old_capacity, data() + new_capacity, osmium::index::empty_value()); } } @@ -141,9 +150,6 @@ namespace osmium { if (new_size > capacity()) { reserve(new_size + osmium::detail::mmap_vector_size_increment); } - if (new_size > size()) { - new (data() + size()) T[new_size - size()]; - } m_size = new_size; } diff --git a/include/osmium/index/detail/mmap_vector_file.hpp b/include/osmium/index/detail/mmap_vector_file.hpp index 666a409d4..9e72f5a8c 100644 --- a/include/osmium/index/detail/mmap_vector_file.hpp +++ b/include/osmium/index/detail/mmap_vector_file.hpp @@ -33,6 +33,11 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include +#include +#include + #include #include #include @@ -48,6 +53,16 @@ namespace osmium { template class mmap_vector_file : public mmap_vector_base { + size_t filesize(int fd) const { + const size_t size = osmium::util::file_size(fd); + + if (size % sizeof(T) != 0) { + throw std::runtime_error("Index file has wrong size (must be multiple of " + std::to_string(sizeof(T)) + ")."); + } + + return size / sizeof(T); + } + public: mmap_vector_file() : @@ -59,8 +74,8 @@ namespace osmium { explicit mmap_vector_file(int fd) : mmap_vector_base( fd, - osmium::util::file_size(fd) / sizeof(T), - osmium::util::file_size(fd) / sizeof(T)) { + std::max(osmium::detail::mmap_vector_size_increment, filesize(fd)), + filesize(fd)) { } ~mmap_vector_file() noexcept = default; diff --git a/include/osmium/index/detail/vector_map.hpp b/include/osmium/index/detail/vector_map.hpp index 0fa59dd48..ac87c2fc9 100644 --- a/include/osmium/index/detail/vector_map.hpp +++ b/include/osmium/index/detail/vector_map.hpp @@ -55,10 +55,10 @@ namespace osmium { public: - typedef TValue element_type; - typedef TVector vector_type; - typedef typename vector_type::iterator iterator; - typedef typename vector_type::const_iterator const_iterator; + using element_type = TValue; + using vector_type = TVector; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; VectorBasedDenseMap() : m_vector() { @@ -88,7 +88,7 @@ namespace osmium { not_found_error(id); } return value; - } catch (std::out_of_range&) { + } catch (const std::out_of_range&) { not_found_error(id); } } @@ -146,10 +146,10 @@ namespace osmium { public: - typedef typename std::pair element_type; - typedef TVector vector_type; - typedef typename vector_type::iterator iterator; - typedef typename vector_type::const_iterator const_iterator; + using element_type = typename std::pair; + using vector_type = TVector; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; private: diff --git a/include/osmium/index/detail/vector_multimap.hpp b/include/osmium/index/detail/vector_multimap.hpp index 35a4cc182..f859f5b45 100644 --- a/include/osmium/index/detail/vector_multimap.hpp +++ b/include/osmium/index/detail/vector_multimap.hpp @@ -52,10 +52,10 @@ namespace osmium { public: - typedef typename std::pair element_type; - typedef TVector vector_type; - typedef typename vector_type::iterator iterator; - typedef typename vector_type::const_iterator const_iterator; + using element_type = typename std::pair; + using vector_type = TVector; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; private: @@ -127,7 +127,7 @@ namespace osmium { } void remove(const TId id, const TValue value) { - auto r = get_all(id); + const auto r = get_all(id); for (auto it = r.first; it != r.second; ++it) { if (it->second == value) { it->second = 0; diff --git a/include/osmium/index/index.hpp b/include/osmium/index/index.hpp index 37d022d1b..c3deeadf8 100644 --- a/include/osmium/index/index.hpp +++ b/include/osmium/index/index.hpp @@ -49,11 +49,11 @@ namespace osmium { */ struct not_found : public std::runtime_error { - not_found(const std::string& what) : + explicit not_found(const std::string& what) : std::runtime_error(what) { } - not_found(const char* what) : + explicit not_found(const char* what) : std::runtime_error(what) { } diff --git a/include/osmium/index/map.hpp b/include/osmium/index/map.hpp index f90a895d1..1d2d5aa34 100644 --- a/include/osmium/index/map.hpp +++ b/include/osmium/index/map.hpp @@ -98,10 +98,10 @@ namespace osmium { public: /// The "key" type, usually osmium::unsigned_object_id_type. - typedef TId key_type; + using key_type = TId; /// The "value" type, usually a Location or size_t. - typedef TValue value_type; + using value_type = TValue; Map() = default; @@ -171,10 +171,10 @@ namespace osmium { public: - typedef TId id_type; - typedef TValue value_type; - typedef osmium::index::map::Map map_type; - typedef std::function&)> create_map_func; + using id_type = TId; + using value_type = TValue; + using map_type = osmium::index::map::Map; + using create_map_func = std::function&)>; private: @@ -207,7 +207,7 @@ namespace osmium { } bool has_map_type(const std::string& map_type_name) const { - return m_callbacks.count(map_type_name); + return m_callbacks.count(map_type_name) != 0; } std::vector map_types() const { diff --git a/include/osmium/index/map/sparse_mem_map.hpp b/include/osmium/index/map/sparse_mem_map.hpp index 41351c865..1e3c58cc6 100644 --- a/include/osmium/index/map/sparse_mem_map.hpp +++ b/include/osmium/index/map/sparse_mem_map.hpp @@ -37,7 +37,6 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include #include #include @@ -98,7 +97,7 @@ namespace osmium { } void dump_as_list(const int fd) final { - typedef typename std::map::value_type t; + using t = typename std::map::value_type; std::vector v; v.reserve(m_elements.size()); std::copy(m_elements.cbegin(), m_elements.cend(), std::back_inserter(v)); diff --git a/include/osmium/index/multimap.hpp b/include/osmium/index/multimap.hpp index de6aa1eb4..9361d6a76 100644 --- a/include/osmium/index/multimap.hpp +++ b/include/osmium/index/multimap.hpp @@ -52,7 +52,7 @@ namespace osmium { static_assert(std::is_integral::value && std::is_unsigned::value, "TId template parameter for class Multimap must be unsigned integral type"); - typedef typename std::pair element_type; + using element_type = typename std::pair; Multimap(const Multimap&) = delete; Multimap& operator=(const Multimap&) = delete; @@ -65,10 +65,10 @@ namespace osmium { public: /// The "key" type, usually osmium::unsigned_object_id_type. - typedef TId key_type; + using key_type = TId; /// The "value" type, usually a Location or size_t. - typedef TValue value_type; + using value_type = TValue; Multimap() = default; @@ -77,7 +77,7 @@ namespace osmium { /// Set the field with id to value. virtual void set(const TId id, const TValue value) = 0; - typedef element_type* iterator; + using iterator = element_type*; // virtual std::pair get_all(const TId id) const = 0; diff --git a/include/osmium/index/multimap/hybrid.hpp b/include/osmium/index/multimap/hybrid.hpp index ba9430232..06a5f6e7b 100644 --- a/include/osmium/index/multimap/hybrid.hpp +++ b/include/osmium/index/multimap/hybrid.hpp @@ -50,10 +50,10 @@ namespace osmium { template class HybridIterator { - typedef SparseMemArray main_map_type; - typedef SparseMemMultimap extra_map_type; + using main_map_type = SparseMemArray; + using extra_map_type = SparseMemMultimap; - typedef typename std::pair element_type; + using element_type = typename std::pair; typename main_map_type::iterator m_begin_main; typename main_map_type::iterator m_end_main; @@ -120,16 +120,16 @@ namespace osmium { template class Hybrid : public Multimap { - typedef SparseMemArray main_map_type; - typedef SparseMemMultimap extra_map_type; + using main_map_type = SparseMemArray; + using extra_map_type = SparseMemMultimap; main_map_type m_main; extra_map_type m_extra; public: - typedef HybridIterator iterator; - typedef const HybridIterator const_iterator; + using iterator = HybridIterator; + using const_iterator = const HybridIterator; Hybrid() : m_main(), diff --git a/include/osmium/index/multimap/sparse_mem_multimap.hpp b/include/osmium/index/multimap/sparse_mem_multimap.hpp index 0859fca21..7a67cc7cc 100644 --- a/include/osmium/index/multimap/sparse_mem_multimap.hpp +++ b/include/osmium/index/multimap/sparse_mem_multimap.hpp @@ -63,12 +63,11 @@ namespace osmium { public: - typedef typename std::multimap collection_type; - typedef typename collection_type::iterator iterator; - typedef typename collection_type::const_iterator const_iterator; - typedef typename collection_type::value_type value_type; - - typedef typename std::pair element_type; + using collection_type = typename std::multimap; + using iterator = typename collection_type::iterator; + using const_iterator = typename collection_type::const_iterator; + using value_type = typename collection_type::value_type; + using element_type = typename std::pair; private: diff --git a/include/osmium/io/any_input.hpp b/include/osmium/io/any_input.hpp index dd8445132..e4617e8b5 100644 --- a/include/osmium/io/any_input.hpp +++ b/include/osmium/io/any_input.hpp @@ -45,8 +45,9 @@ DEALINGS IN THE SOFTWARE. #include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export -#include // IWYU pragma: export #endif // OSMIUM_IO_ANY_INPUT_HPP diff --git a/include/osmium/io/bzip2_compression.hpp b/include/osmium/io/bzip2_compression.hpp index e5cad0be3..63b4cadf9 100644 --- a/include/osmium/io/bzip2_compression.hpp +++ b/include/osmium/io/bzip2_compression.hpp @@ -43,10 +43,9 @@ DEALINGS IN THE SOFTWARE. */ #include -#include #include -#include #include +#include #include @@ -55,6 +54,7 @@ DEALINGS IN THE SOFTWARE. #endif #include +#include #include #include #include @@ -109,7 +109,7 @@ namespace osmium { explicit Bzip2Compressor(int fd, fsync sync) : Compressor(sync), - m_file(fdopen(dup(fd), "wb")), + m_file(fdopen(::dup(fd), "wb")), m_bzerror(BZ_OK), m_bzfile(::BZ2_bzWriteOpen(&m_bzerror, m_file, 6, 0, 0)) { if (!m_bzfile) { @@ -165,7 +165,7 @@ namespace osmium { explicit Bzip2Decompressor(int fd) : Decompressor(), - m_file(fdopen(dup(fd), "rb")), + m_file(fdopen(::dup(fd), "rb")), m_bzerror(BZ_OK), m_bzfile(::BZ2_bzReadOpen(&m_bzerror, m_file, 0, 0, nullptr, 0)) { if (!m_bzfile) { @@ -215,6 +215,8 @@ namespace osmium { buffer.resize(static_cast(nread)); } + set_offset(size_t(ftell(m_file))); + return buffer; } diff --git a/include/osmium/io/compression.hpp b/include/osmium/io/compression.hpp index 7d95661bd..e7f93bd5e 100644 --- a/include/osmium/io/compression.hpp +++ b/include/osmium/io/compression.hpp @@ -33,11 +33,12 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include #include #include -#include #include #include #include @@ -54,6 +55,7 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include namespace osmium { @@ -86,6 +88,9 @@ namespace osmium { class Decompressor { + std::atomic m_file_size {0}; + std::atomic m_offset {0}; + public: static constexpr unsigned int input_buffer_size = 1024 * 1024; @@ -105,6 +110,22 @@ namespace osmium { virtual void close() = 0; + size_t file_size() const noexcept { + return m_file_size; + } + + void set_file_size(size_t size) noexcept { + m_file_size = size; + } + + size_t offset() const noexcept { + return m_offset; + } + + void set_offset(size_t offset) noexcept { + m_offset = offset; + } + }; // class Decompressor /** @@ -118,16 +139,16 @@ namespace osmium { public: - typedef std::function create_compressor_type; - typedef std::function create_decompressor_type_fd; - typedef std::function create_decompressor_type_buffer; + using create_compressor_type = std::function; + using create_decompressor_type_fd = std::function; + using create_decompressor_type_buffer = std::function; private: - typedef std::map> compression_map_type; + using compression_map_type = std::map>; compression_map_type m_callbacks; @@ -182,7 +203,9 @@ namespace osmium { auto it = m_callbacks.find(compression); if (it != m_callbacks.end()) { - return std::unique_ptr(std::get<1>(it->second)(fd)); + auto p = std::unique_ptr(std::get<1>(it->second)(fd)); + p->set_file_size(osmium::util::file_size(fd)); + return p; } error(compression); @@ -241,6 +264,7 @@ namespace osmium { int m_fd; const char *m_buffer; size_t m_buffer_size; + size_t m_offset = 0; public: @@ -284,6 +308,9 @@ namespace osmium { buffer.resize(std::string::size_type(nread)); } + m_offset += buffer.size(); + set_offset(m_offset); + return buffer; } diff --git a/include/osmium/io/detail/debug_output_format.hpp b/include/osmium/io/detail/debug_output_format.hpp index 3f7537ea6..dc5323ad3 100644 --- a/include/osmium/io/detail/debug_output_format.hpp +++ b/include/osmium/io/detail/debug_output_format.hpp @@ -34,29 +34,35 @@ DEALINGS IN THE SOFTWARE. */ #include -#include -#include -#include -#include +#include +#include #include #include #include -#include #include +#include + #include +#include +#include +#include #include +#include #include -#include +#include #include #include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -66,8 +72,6 @@ namespace osmium { namespace io { - class File; - namespace detail { constexpr const char* color_bold = "\x1b[1m"; @@ -80,6 +84,10 @@ namespace osmium { constexpr const char* color_magenta = "\x1b[35m"; constexpr const char* color_cyan = "\x1b[36m"; constexpr const char* color_white = "\x1b[37m"; + + constexpr const char* color_backg_red = "\x1b[41m"; + constexpr const char* color_backg_green = "\x1b[42m"; + constexpr const char* color_reset = "\x1b[0m"; struct debug_output_options { @@ -90,6 +98,11 @@ namespace osmium { /// Output with ANSI colors? bool use_color; + /// Add CRC32 checksum to each object? + bool add_crc32; + + /// Write in form of a diff file? + bool format_as_diff; }; /** @@ -102,16 +115,47 @@ namespace osmium { const char* m_utf8_prefix = ""; const char* m_utf8_suffix = ""; + char m_diff_char = '\0'; + void append_encoded_string(const char* data) { append_debug_encoded_string(*m_out, data, m_utf8_prefix, m_utf8_suffix); } + template + void output_formatted(const char* format, TArgs&&... args) { + append_printf_formatted_string(*m_out, format, std::forward(args)...); + } + void write_color(const char* color) { if (m_options.use_color) { *m_out += color; } } + void write_diff() { + if (!m_diff_char) { + return; + } + if (m_options.use_color) { + if (m_diff_char == '-') { + *m_out += color_backg_red; + *m_out += color_white; + *m_out += color_bold; + *m_out += '-'; + *m_out += color_reset; + return; + } else if (m_diff_char == '+') { + *m_out += color_backg_green; + *m_out += color_white; + *m_out += color_bold; + *m_out += '+'; + *m_out += color_reset; + return; + } + } + *m_out += m_diff_char; + } + void write_string(const char* string) { *m_out += '"'; write_color(color_blue); @@ -121,6 +165,7 @@ namespace osmium { } void write_object_type(const char* object_type, bool visible = true) { + write_diff(); if (visible) { write_color(color_bold); } else { @@ -132,6 +177,7 @@ namespace osmium { } void write_fieldname(const char* name) { + write_diff(); *m_out += " "; write_color(color_cyan); *m_out += name; @@ -161,7 +207,9 @@ namespace osmium { void write_timestamp(const osmium::Timestamp& timestamp) { if (timestamp.valid()) { *m_out += timestamp.to_iso(); - output_formatted(" (%d)", timestamp.seconds_since_epoch()); + *m_out += " ("; + output_int(timestamp.seconds_since_epoch()); + *m_out += ')'; } else { write_error("NOT SET"); } @@ -169,53 +217,63 @@ namespace osmium { } void write_meta(const osmium::OSMObject& object) { - output_formatted("%" PRId64 "\n", object.id()); + output_int(object.id()); + *m_out += '\n'; if (m_options.add_metadata) { write_fieldname("version"); - output_formatted(" %d", object.version()); + *m_out += " "; + output_int(object.version()); if (object.visible()) { *m_out += " visible\n"; } else { write_error(" deleted\n"); } write_fieldname("changeset"); - output_formatted("%d\n", object.changeset()); + output_int(object.changeset()); + *m_out += '\n'; write_fieldname("timestamp"); write_timestamp(object.timestamp()); write_fieldname("user"); - output_formatted(" %d ", object.uid()); + *m_out += " "; + output_int(object.uid()); + *m_out += ' '; write_string(object.user()); *m_out += '\n'; } } void write_tags(const osmium::TagList& tags, const char* padding="") { - if (!tags.empty()) { - write_fieldname("tags"); - *m_out += padding; - output_formatted(" %d\n", tags.size()); + if (tags.empty()) { + return; + } + write_fieldname("tags"); + *m_out += padding; + *m_out += " "; + output_int(tags.size()); + *m_out += '\n'; - osmium::max_op max; - for (const auto& tag : tags) { - max.update(std::strlen(tag.key())); - } - for (const auto& tag : tags) { - *m_out += " "; - write_string(tag.key()); - auto spacing = max() - std::strlen(tag.key()); - while (spacing--) { - *m_out += " "; - } - *m_out += " = "; - write_string(tag.value()); - *m_out += '\n'; + osmium::max_op max; + for (const auto& tag : tags) { + max.update(std::strlen(tag.key())); + } + for (const auto& tag : tags) { + write_diff(); + *m_out += " "; + write_string(tag.key()); + auto spacing = max() - std::strlen(tag.key()); + while (spacing--) { + *m_out += " "; } + *m_out += " = "; + write_string(tag.value()); + *m_out += '\n'; } } void write_location(const osmium::Location& location) { write_fieldname("lon/lat"); - output_formatted(" %.7f,%.7f", location.lon_without_check(), location.lat_without_check()); + *m_out += " "; + location.as_string_without_check(std::back_inserter(*m_out)); if (!location.valid()) { write_error(" INVALID LOCATION!"); } @@ -230,13 +288,30 @@ namespace osmium { } const auto& bl = box.bottom_left(); const auto& tr = box.top_right(); - output_formatted("%.7f,%.7f %.7f,%.7f", bl.lon_without_check(), bl.lat_without_check(), tr.lon_without_check(), tr.lat_without_check()); + bl.as_string(std::back_inserter(*m_out)); + *m_out += ' '; + tr.as_string(std::back_inserter(*m_out)); if (!box.valid()) { write_error(" INVALID BOX!"); } *m_out += '\n'; } + template + void write_crc32(const T& object) { + write_fieldname("crc32"); + osmium::CRC crc32; + crc32.update(object); + output_formatted(" %x\n", crc32().checksum()); + } + + void write_crc32(const osmium::Changeset& object) { + write_fieldname("crc32"); + osmium::CRC crc32; + crc32.update(object); + output_formatted(" %x\n", crc32().checksum()); + } + public: DebugOutputBlock(osmium::memory::Buffer&& buffer, const debug_output_options& options) : @@ -265,6 +340,8 @@ namespace osmium { } void node(const osmium::Node& node) { + m_diff_char = m_options.format_as_diff ? node.diff_as_char() : '\0'; + write_object_type("node", node.visible()); write_meta(node); @@ -274,17 +351,24 @@ namespace osmium { write_tags(node.tags()); + if (m_options.add_crc32) { + write_crc32(node); + } + *m_out += '\n'; } void way(const osmium::Way& way) { + m_diff_char = m_options.format_as_diff ? way.diff_as_char() : '\0'; + write_object_type("way", way.visible()); write_meta(way); write_tags(way.tags()); write_fieldname("nodes"); - output_formatted(" %d", way.nodes().size()); + *m_out += " "; + output_int(way.nodes().size()); if (way.nodes().size() < 2) { write_error(" LESS THAN 2 NODES!\n"); } else if (way.nodes().size() > 2000) { @@ -295,32 +379,45 @@ namespace osmium { *m_out += " (open)\n"; } - int width = int(log10(way.nodes().size())) + 1; + const int width = int(std::log10(way.nodes().size())) + 1; int n = 0; for (const auto& node_ref : way.nodes()) { + write_diff(); write_counter(width, n++); output_formatted("%10" PRId64, node_ref.ref()); if (node_ref.location().valid()) { - output_formatted(" (%.7f,%.7f)", node_ref.location().lon_without_check(), node_ref.location().lat_without_check()); + *m_out += " ("; + node_ref.location().as_string(std::back_inserter(*m_out)); + *m_out += ')'; } *m_out += '\n'; } + if (m_options.add_crc32) { + write_crc32(way); + } + *m_out += '\n'; } void relation(const osmium::Relation& relation) { static const char* short_typename[] = { "node", "way ", "rel " }; + + m_diff_char = m_options.format_as_diff ? relation.diff_as_char() : '\0'; + write_object_type("relation", relation.visible()); write_meta(relation); write_tags(relation.tags()); write_fieldname("members"); - output_formatted(" %d\n", relation.members().size()); + *m_out += " "; + output_int(relation.members().size()); + *m_out += '\n'; - int width = int(log10(relation.members().size())) + 1; + const int width = int(std::log10(relation.members().size())) + 1; int n = 0; for (const auto& member : relation.members()) { + write_diff(); write_counter(width, n++); *m_out += short_typename[item_type_to_nwr_index(member.type())]; output_formatted(" %10" PRId64 " ", member.ref()); @@ -328,15 +425,20 @@ namespace osmium { *m_out += '\n'; } + if (m_options.add_crc32) { + write_crc32(relation); + } + *m_out += '\n'; } void changeset(const osmium::Changeset& changeset) { write_object_type("changeset"); - output_formatted("%d\n", changeset.id()); + output_int(changeset.id()); + *m_out += '\n'; write_fieldname("num changes"); - output_formatted("%d", changeset.num_changes()); + output_int(changeset.num_changes()); if (changeset.num_changes() == 0) { write_error(" NO CHANGES!"); } @@ -355,7 +457,9 @@ namespace osmium { } write_fieldname("user"); - output_formatted(" %d ", changeset.uid()); + *m_out += " "; + output_int(changeset.uid()); + *m_out += ' '; write_string(changeset.user()); *m_out += '\n'; @@ -364,9 +468,11 @@ namespace osmium { if (changeset.num_comments() > 0) { write_fieldname("comments"); - output_formatted(" %d\n", changeset.num_comments()); + *m_out += " "; + output_int(changeset.num_comments()); + *m_out += '\n'; - int width = int(log10(changeset.num_comments())) + 1; + const int width = int(std::log10(changeset.num_comments())) + 1; int n = 0; for (const auto& comment : changeset.discussion()) { write_counter(width, n++); @@ -376,7 +482,8 @@ namespace osmium { output_formatted(" %*s", width, ""); write_comment_field("user"); - output_formatted("%d ", comment.uid()); + output_int(comment.uid()); + *m_out += ' '; write_string(comment.user()); output_formatted("\n %*s", width, ""); @@ -386,6 +493,10 @@ namespace osmium { } } + if (m_options.add_crc32) { + write_crc32(changeset); + } + *m_out += '\n'; } @@ -412,8 +523,10 @@ namespace osmium { DebugOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) : OutputFormat(output_queue), m_options() { - m_options.add_metadata = file.is_not_false("add_metadata"); - m_options.use_color = file.is_true("color"); + m_options.add_metadata = file.is_not_false("add_metadata"); + m_options.use_color = file.is_true("color"); + m_options.add_crc32 = file.is_true("add_crc32"); + m_options.format_as_diff = file.is_true("diff"); } DebugOutputFormat(const DebugOutputFormat&) = delete; @@ -422,6 +535,10 @@ namespace osmium { ~DebugOutputFormat() noexcept final = default; void write_header(const osmium::io::Header& header) final { + if (m_options.format_as_diff) { + return; + } + std::string out; if (m_options.use_color) { @@ -439,9 +556,9 @@ namespace osmium { out += '\n'; for (const auto& box : header.boxes()) { out += " "; - box.bottom_left().as_string(std::back_inserter(out), ','); - out += " "; - box.top_right().as_string(std::back_inserter(out), ','); + box.bottom_left().as_string(std::back_inserter(out)); + out += ' '; + box.top_right().as_string(std::back_inserter(out)); out += '\n'; } write_fieldname(out, "options"); diff --git a/include/osmium/io/detail/input_format.hpp b/include/osmium/io/detail/input_format.hpp index 0d7885131..98081e3c9 100644 --- a/include/osmium/io/detail/input_format.hpp +++ b/include/osmium/io/detail/input_format.hpp @@ -38,11 +38,11 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include #include #include #include +#include #include #include #include @@ -154,18 +154,14 @@ namespace osmium { public: - typedef std::function< - std::unique_ptr( - future_string_queue_type&, - future_buffer_queue_type&, - std::promise& header_promise, - osmium::osm_entity_bits::type read_which_entities - ) - > create_parser_type; + using create_parser_type = std::function(future_string_queue_type&, + future_buffer_queue_type&, + std::promise& header_promise, + osmium::osm_entity_bits::type read_which_entities)>; private: - typedef std::map map_type; + using map_type = std::map; map_type m_callbacks; diff --git a/include/osmium/io/detail/o5m_input_format.hpp b/include/osmium/io/detail/o5m_input_format.hpp index 7293cfa73..d6428790a 100644 --- a/include/osmium/io/detail/o5m_input_format.hpp +++ b/include/osmium/io/detail/o5m_input_format.hpp @@ -42,9 +42,9 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #include -#include #include #include #include @@ -52,19 +52,26 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include #include #include #include #include +#include #include +#include +#include #include +#include #include #include #include namespace osmium { + namespace builder { + class Builder; + } // namespace builder + /** * Exception thrown when the o5m deocder failed. The exception contains * (if available) information about the place where the error happened @@ -529,7 +536,7 @@ namespace osmium { uint64_t length = 0; try { length = protozero::decode_varint(&m_data, m_end); - } catch (protozero::end_of_buffer_exception&) { + } catch (const protozero::end_of_buffer_exception&) { throw o5m_error("premature end of file"); } diff --git a/include/osmium/io/detail/opl_input_format.hpp b/include/osmium/io/detail/opl_input_format.hpp new file mode 100644 index 000000000..15a31f38a --- /dev/null +++ b/include/osmium/io/detail/opl_input_format.hpp @@ -0,0 +1,156 @@ +#ifndef OSMIUM_IO_DETAIL_OPL_INPUT_FORMAT_HPP +#define OSMIUM_IO_DETAIL_OPL_INPUT_FORMAT_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace osmium { + + namespace io { + + namespace detail { + + class OPLParser : public Parser { + + osmium::memory::Buffer m_buffer{1024*1024}; + const char* m_data = nullptr; + uint64_t m_line_count = 0; + + void maybe_flush() { + if (m_buffer.committed() > 800*1024) { + osmium::memory::Buffer buffer{1024*1024}; + using std::swap; + swap(m_buffer, buffer); + send_to_output_queue(std::move(buffer)); + + } + } + + void parse_line() { + if (opl_parse_line(m_line_count, m_data, m_buffer, read_types())) { + maybe_flush(); + } + ++m_line_count; + } + + public: + + OPLParser(future_string_queue_type& input_queue, + future_buffer_queue_type& output_queue, + std::promise& header_promise, + osmium::osm_entity_bits::type read_types) : + Parser(input_queue, output_queue, header_promise, read_types) { + set_header_value(osmium::io::Header{}); + } + + ~OPLParser() noexcept final = default; + + void run() final { + osmium::thread::set_thread_name("_osmium_opl_in"); + + std::string rest; + while (!input_done()) { + std::string input = get_input(); + std::string::size_type ppos = 0; + + if (!rest.empty()) { + ppos = input.find('\n'); + if (ppos == std::string::npos) { + rest.append(input); + continue; + } + rest.append(input.substr(0, ppos)); + m_data = rest.data(); + parse_line(); + rest.clear(); + } + + std::string::size_type pos = input.find('\n', ppos); + while (pos != std::string::npos) { + m_data = &input[ppos]; + input[pos] = '\0'; + parse_line(); + ppos = pos + 1; + if (ppos >= input.size()) { + break; + } + pos = input.find('\n', ppos); + } + rest = input.substr(ppos); + } + + if (m_buffer.committed() > 0) { + send_to_output_queue(std::move(m_buffer)); + } + } + + }; // class OPLParser + + // we want the register_parser() function to run, setting + // the variable is only a side-effect, it will never be used + const bool registered_opl_parser = ParserFactory::instance().register_parser( + file_format::opl, + [](future_string_queue_type& input_queue, + future_buffer_queue_type& output_queue, + std::promise& header_promise, + osmium::osm_entity_bits::type read_which_entities) { + return std::unique_ptr(new OPLParser(input_queue, output_queue, header_promise, read_which_entities)); + }); + + // dummy function to silence the unused variable warning from above + inline bool get_registered_opl_parser() noexcept { + return registered_opl_parser; + } + + } // namespace detail + + } // namespace io + +} // namespace osmium + + +#endif // OSMIUM_IO_DETAIL_OPL_INPUT_FORMAT_HPP diff --git a/include/osmium/io/detail/opl_output_format.hpp b/include/osmium/io/detail/opl_output_format.hpp index 3fedf7e9c..acdfe29af 100644 --- a/include/osmium/io/detail/opl_output_format.hpp +++ b/include/osmium/io/detail/opl_output_format.hpp @@ -33,26 +33,26 @@ DEALINGS IN THE SOFTWARE. */ -#include -#include #include -#include -#include #include #include #include -#include #include #include +#include +#include +#include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -65,8 +65,6 @@ namespace osmium { namespace io { - class File; - namespace detail { struct opl_output_options { @@ -74,6 +72,12 @@ namespace osmium { /// Should metadata of objects be added? bool add_metadata; + /// Should node locations be added to ways? + bool locations_on_ways; + + /// Write in form of a diff file? + bool format_as_diff; + }; /** @@ -87,38 +91,71 @@ namespace osmium { osmium::io::detail::append_utf8_encoded_string(*m_out, data); } - void write_meta(const osmium::OSMObject& object) { - output_formatted("%" PRId64, object.id()); - if (m_options.add_metadata) { - output_formatted(" v%d d", object.version()); - *m_out += (object.visible() ? 'V' : 'D'); - output_formatted(" c%d t", object.changeset()); - *m_out += object.timestamp().to_iso(); - output_formatted(" i%d u", object.uid()); - append_encoded_string(object.user()); - } + void write_field_int(char c, int64_t value) { + *m_out += c; + output_int(value); + } + + void write_field_timestamp(char c, const osmium::Timestamp& timestamp) { + *m_out += c; + *m_out += timestamp.to_iso(); + } + + void write_tags(const osmium::TagList& tags) { *m_out += " T"; - bool first = true; - for (const auto& tag : object.tags()) { - if (first) { - first = false; - } else { - *m_out += ','; - } - append_encoded_string(tag.key()); + + if (tags.empty()) { + return; + } + + auto it = tags.begin(); + append_encoded_string(it->key()); + *m_out += '='; + append_encoded_string(it->value()); + + for (++it; it != tags.end(); ++it) { + *m_out += ','; + append_encoded_string(it->key()); *m_out += '='; - append_encoded_string(tag.value()); + append_encoded_string(it->value()); } } + void write_meta(const osmium::OSMObject& object) { + output_int(object.id()); + if (m_options.add_metadata) { + *m_out += ' '; + write_field_int('v', object.version()); + *m_out += " d"; + *m_out += (object.visible() ? 'V' : 'D'); + *m_out += ' '; + write_field_int('c', object.changeset()); + *m_out += ' '; + write_field_timestamp('t', object.timestamp()); + *m_out += ' '; + write_field_int('i', object.uid()); + *m_out += " u"; + append_encoded_string(object.user()); + } + write_tags(object.tags()); + } + void write_location(const osmium::Location& location, const char x, const char y) { + *m_out += ' '; + *m_out += x; if (location) { - output_formatted(" %c%.7f %c%.7f", x, location.lon_without_check(), y, location.lat_without_check()); - } else { - *m_out += ' '; - *m_out += x; - *m_out += ' '; - *m_out += y; + osmium::detail::append_location_coordinate_to_string(std::back_inserter(*m_out), location.x()); + } + *m_out += ' '; + *m_out += y; + if (location) { + osmium::detail::append_location_coordinate_to_string(std::back_inserter(*m_out), location.y()); + } + } + + void write_diff(const osmium::OSMObject& object) { + if (m_options.format_as_diff) { + *m_out += object.diff_as_char(); } } @@ -148,70 +185,93 @@ namespace osmium { } void node(const osmium::Node& node) { + write_diff(node); *m_out += 'n'; write_meta(node); write_location(node.location(), 'x', 'y'); *m_out += '\n'; } + void write_field_ref(const osmium::NodeRef& node_ref) { + write_field_int('n', node_ref.ref()); + *m_out += 'x'; + if (node_ref.location()) { + node_ref.location().as_string(std::back_inserter(*m_out), 'y'); + } else { + *m_out += 'y'; + } + } + void way(const osmium::Way& way) { + write_diff(way); *m_out += 'w'; write_meta(way); *m_out += " N"; - bool first = true; - for (const auto& node_ref : way.nodes()) { - if (first) { - first = false; + + if (!way.nodes().empty()) { + auto it = way.nodes().begin(); + if (m_options.locations_on_ways) { + write_field_ref(*it); + for (++it; it != way.nodes().end(); ++it) { + *m_out += ','; + write_field_ref(*it); + } } else { - *m_out += ','; + write_field_int('n', it->ref()); + for (++it; it != way.nodes().end(); ++it) { + *m_out += ','; + write_field_int('n', it->ref()); + } } - output_formatted("n%" PRId64, node_ref.ref()); } + *m_out += '\n'; } + void relation_member(const osmium::RelationMember& member) { + *m_out += item_type_to_char(member.type()); + output_int(member.ref()); + *m_out += '@'; + append_encoded_string(member.role()); + } + void relation(const osmium::Relation& relation) { + write_diff(relation); *m_out += 'r'; write_meta(relation); *m_out += " M"; - bool first = true; - for (const auto& member : relation.members()) { - if (first) { - first = false; - } else { + + if (!relation.members().empty()) { + auto it = relation.members().begin(); + relation_member(*it); + for (++it; it != relation.members().end(); ++it) { *m_out += ','; + relation_member(*it); } - *m_out += item_type_to_char(member.type()); - output_formatted("%" PRId64 "@", member.ref()); - append_encoded_string(member.role()); } + *m_out += '\n'; } void changeset(const osmium::Changeset& changeset) { - output_formatted("c%d k%d s", changeset.id(), changeset.num_changes()); - *m_out += changeset.created_at().to_iso(); - *m_out += " e"; - *m_out += changeset.closed_at().to_iso(); - output_formatted(" d%d i%d u", changeset.num_comments(), changeset.uid()); + write_field_int('c', changeset.id()); + *m_out += ' '; + write_field_int('k', changeset.num_changes()); + *m_out += ' '; + write_field_timestamp('s', changeset.created_at()); + *m_out += ' '; + write_field_timestamp('e', changeset.closed_at()); + *m_out += ' '; + write_field_int('d', changeset.num_comments()); + *m_out += ' '; + write_field_int('i', changeset.uid()); + *m_out += " u"; append_encoded_string(changeset.user()); write_location(changeset.bounds().bottom_left(), 'x', 'y'); write_location(changeset.bounds().top_right(), 'X', 'Y'); - *m_out += " T"; - bool first = true; - for (const auto& tag : changeset.tags()) { - if (first) { - first = false; - } else { - *m_out += ','; - } - append_encoded_string(tag.key()); - *m_out += '='; - append_encoded_string(tag.value()); - } - + write_tags(changeset.tags()); *m_out += '\n'; } @@ -226,7 +286,9 @@ namespace osmium { OPLOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) : OutputFormat(output_queue), m_options() { - m_options.add_metadata = file.is_not_false("add_metadata"); + m_options.add_metadata = file.is_not_false("add_metadata"); + m_options.locations_on_ways = file.is_true("locations_on_ways"); + m_options.format_as_diff = file.is_true("diff"); } OPLOutputFormat(const OPLOutputFormat&) = delete; diff --git a/include/osmium/io/detail/opl_parser_functions.hpp b/include/osmium/io/detail/opl_parser_functions.hpp new file mode 100644 index 000000000..97c59275a --- /dev/null +++ b/include/osmium/io/detail/opl_parser_functions.hpp @@ -0,0 +1,747 @@ +#ifndef OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP +#define OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace osmium { + + namespace builder { + class Builder; + } // namespace builder + + /** + * Exception thrown when there was a problem with parsing the OPL format + * of a file. + */ + struct opl_error : public io_error { + + uint64_t line = 0; + uint64_t column = 0; + const char* data; + std::string msg; + + explicit opl_error(const std::string& what, const char* d = nullptr) : + io_error(std::string("OPL error: ") + what), + data(d), + msg("OPL error: ") { + msg.append(what); + } + + explicit opl_error(const char* what, const char* d = nullptr) : + io_error(std::string("OPL error: ") + what), + data(d), + msg("OPL error: ") { + msg.append(what); + } + + void set_pos(uint64_t l, uint64_t col) { + line = l; + column = col; + msg.append(" on line "); + msg.append(std::to_string(line)); + msg.append(" column "); + msg.append(std::to_string(column)); + } + + const char* what() const noexcept override { + return msg.c_str(); + } + + }; // struct opl_error + + namespace io { + + namespace detail { + + /** + * Consume consecutive space and tab characters. There must be + * at least one. + */ + inline void opl_parse_space(const char** s) { + if (**s != ' ' && **s != '\t') { + throw opl_error{"expected space or tab character", *s}; + } + do { + ++*s; + } while (**s == ' ' || **s == '\t'); + } + + /** + * Check whether s points to something else than the end of the + * string or a space or tab. + */ + inline bool opl_non_empty(const char *s) { + return *s != '\0' && *s != ' ' && *s != '\t'; + } + + /** + * Skip to the next space or tab character or the end of the + * string. + */ + inline const char* opl_skip_section(const char** s) noexcept { + while (opl_non_empty(*s)) { + ++*s; + } + return *s; + } + + /** + * Parse OPL-escaped strings with hex code with a '%' at the end. + * Appends resulting unicode character to the result string. + * + * Returns a pointer to next character that needs to be consumed. + */ + inline void opl_parse_escaped(const char** data, std::string& result) { + const char* s = *data; + uint32_t value = 0; + const int max_length = sizeof(value) * 2 /* hex chars per byte */; + int length = 0; + while (++length <= max_length) { + if (*s == '\0') { + throw opl_error{"eol", s}; + } + if (*s == '%') { + ++s; + utf8::utf32to8(&value, &value + 1, std::back_inserter(result)); + *data = s; + return; + } + value <<= 4; + if (*s >= '0' && *s <= '9') { + value += *s - '0'; + } else if (*s >= 'a' && *s <= 'f') { + value += *s - 'a' + 10; + } else if (*s >= 'A' && *s <= 'F') { + value += *s - 'A' + 10; + } else { + throw opl_error{"not a hex char", s}; + } + ++s; + } + throw opl_error{"hex escape too long", s}; + } + + /** + * Parse a string up to end of string or next space, tab, comma, or + * equal sign. + * + * Appends characters to the result string. + * + * Returns a pointer to next character that needs to be consumed. + */ + inline void opl_parse_string(const char** data, std::string& result) { + const char* s = *data; + while (true) { + if (*s == '\0' || *s == ' ' || *s == '\t' || *s == ',' || *s == '=') { + break; + } else if (*s == '%') { + ++s; + opl_parse_escaped(&s, result); + } else { + result += *s; + ++s; + } + } + *data = s; + } + + // Arbitrary limit how long integers can get + constexpr const int max_int_len = 16; + + template + inline T opl_parse_int(const char** s) { + if (**s == '\0') { + throw opl_error{"expected integer", *s}; + } + const bool negative = (**s == '-'); + if (negative) { + ++*s; + } + + int64_t value = 0; + + int n = max_int_len; + while (**s >= '0' && **s <= '9') { + if (--n == 0) { + throw opl_error{"integer too long", *s}; + } + value *= 10; + value += **s - '0'; + ++*s; + } + + if (n == max_int_len) { + throw opl_error{"expected integer", *s}; + } + + if (negative) { + value = -value; + if (value < std::numeric_limits::min()) { + throw opl_error{"integer too long", *s}; + } + } else { + if (value > std::numeric_limits::max()) { + throw opl_error{"integer too long", *s}; + } + } + + return T(value); + } + + inline osmium::object_id_type opl_parse_id(const char** s) { + return opl_parse_int(s); + } + + inline osmium::changeset_id_type opl_parse_changeset_id(const char** s) { + return opl_parse_int(s); + } + + inline osmium::object_version_type opl_parse_version(const char** s) { + return opl_parse_int(s); + } + + inline bool opl_parse_visible(const char** data) { + if (**data == 'V') { + ++*data; + return true; + } + if (**data == 'D') { + ++*data; + return false; + } + throw opl_error{"invalid visible flag", *data}; + } + + inline osmium::user_id_type opl_parse_uid(const char** s) { + return opl_parse_int(s); + } + + inline osmium::Timestamp opl_parse_timestamp(const char** s) { + try { + if (**s == '\0' || **s == ' ' || **s == '\t') { + return osmium::Timestamp{}; + } + osmium::Timestamp timestamp{*s}; + *s += 20; + return timestamp; + } catch (const std::invalid_argument&) { + throw opl_error{"can not parse timestamp", *s}; + } + } + + /** + * Check if data points to given character and consume it. + * Throw error otherwise. + */ + inline void opl_parse_char(const char** data, char c) { + if (**data == c) { + ++*data; + return; + } + std::string msg = "expected '"; + msg += c; + msg += "'"; + throw opl_error{msg, *data}; + } + + /** + * Parse a list of tags in the format 'key=value,key=value,...' + * + * Tags will be added to the buffer using a TagListBuilder. + */ + inline void opl_parse_tags(const char* s, osmium::memory::Buffer& buffer, osmium::builder::Builder* parent_builder = nullptr) { + osmium::builder::TagListBuilder builder{buffer, parent_builder}; + std::string key; + std::string value; + while (true) { + opl_parse_string(&s, key); + opl_parse_char(&s, '='); + opl_parse_string(&s, value); + builder.add_tag(key, value); + if (*s == ' ' || *s == '\t' || *s == '\0') { + break; + } + opl_parse_char(&s, ','); + key.clear(); + value.clear(); + } + } + + /** + * Parse a number of nodes in the format "nID,nID,nID..." + * + * Nodes will be added to the buffer using a WayNodeListBuilder. + */ + inline void opl_parse_way_nodes(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::WayBuilder* parent_builder = nullptr) { + if (s == e) { + return; + } + osmium::builder::WayNodeListBuilder builder{buffer, parent_builder}; + + while (s < e) { + opl_parse_char(&s, 'n'); + if (s == e) { + throw opl_error{"expected integer", s}; + } + + const osmium::object_id_type ref = opl_parse_id(&s); + if (s == e) { + builder.add_node_ref(ref); + return; + } + + osmium::Location location; + if (*s == 'x') { + ++s; + location.set_lon_partial(&s); + if (*s == 'y') { + ++s; + location.set_lat_partial(&s); + } + } + + builder.add_node_ref(ref, location); + + if (s == e) { + return; + } + + opl_parse_char(&s, ','); + } + } + + inline void opl_parse_node(const char** data, osmium::memory::Buffer& buffer) { + osmium::builder::NodeBuilder builder{buffer}; + osmium::Node& node = builder.object(); + + node.set_id(opl_parse_id(data)); + + const char* tags_begin = nullptr; + + std::string user; + osmium::Location location; + while (**data) { + opl_parse_space(data); + const char c = **data; + if (c == '\0') { + break; + } + ++(*data); + switch (c) { + case 'v': + node.set_version(opl_parse_version(data)); + break; + case 'd': + node.set_visible(opl_parse_visible(data)); + break; + case 'c': + node.set_changeset(opl_parse_changeset_id(data)); + break; + case 't': + node.set_timestamp(opl_parse_timestamp(data)); + break; + case 'i': + node.set_uid(opl_parse_uid(data)); + break; + case 'u': + opl_parse_string(data, user); + break; + case 'T': + if (opl_non_empty(*data)) { + tags_begin = *data; + opl_skip_section(data); + } + break; + case 'x': + if (opl_non_empty(*data)) { + location.set_lon_partial(data); + } + break; + case 'y': + if (opl_non_empty(*data)) { + location.set_lat_partial(data); + } + break; + default: + --(*data); + throw opl_error{"unknown attribute", *data}; + } + } + + if (location.valid()) { + node.set_location(location); + } + + builder.add_user(user); + + if (tags_begin) { + opl_parse_tags(tags_begin, buffer, &builder); + } + + buffer.commit(); + } + + inline void opl_parse_way(const char** data, osmium::memory::Buffer& buffer) { + osmium::builder::WayBuilder builder{buffer}; + osmium::Way& way = builder.object(); + + way.set_id(opl_parse_id(data)); + + const char* tags_begin = nullptr; + + const char* nodes_begin = nullptr; + const char* nodes_end = nullptr; + + std::string user; + while (**data) { + opl_parse_space(data); + const char c = **data; + if (c == '\0') { + break; + } + ++(*data); + switch (c) { + case 'v': + way.set_version(opl_parse_version(data)); + break; + case 'd': + way.set_visible(opl_parse_visible(data)); + break; + case 'c': + way.set_changeset(opl_parse_changeset_id(data)); + break; + case 't': + way.set_timestamp(opl_parse_timestamp(data)); + break; + case 'i': + way.set_uid(opl_parse_uid(data)); + break; + case 'u': + opl_parse_string(data, user); + break; + case 'T': + if (opl_non_empty(*data)) { + tags_begin = *data; + opl_skip_section(data); + } + break; + case 'N': + nodes_begin = *data; + nodes_end = opl_skip_section(data); + break; + default: + --(*data); + throw opl_error{"unknown attribute", *data}; + } + } + + builder.add_user(user); + + if (tags_begin) { + opl_parse_tags(tags_begin, buffer, &builder); + } + + opl_parse_way_nodes(nodes_begin, nodes_end, buffer, &builder); + + buffer.commit(); + } + + inline void opl_parse_relation_members(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::RelationBuilder* parent_builder = nullptr) { + if (s == e) { + return; + } + osmium::builder::RelationMemberListBuilder builder{buffer, parent_builder}; + + while (s < e) { + osmium::item_type type = osmium::char_to_item_type(*s); + if (type != osmium::item_type::node && + type != osmium::item_type::way && + type != osmium::item_type::relation) { + throw opl_error{"unknown object type", s}; + } + ++s; + + if (s == e) { + throw opl_error{"expected integer", s}; + } + osmium::object_id_type ref = opl_parse_id(&s); + opl_parse_char(&s, '@'); + if (s == e) { + builder.add_member(type, ref, ""); + return; + } + std::string role; + opl_parse_string(&s, role); + builder.add_member(type, ref, role); + + if (s == e) { + return; + } + opl_parse_char(&s, ','); + } + } + + inline void opl_parse_relation(const char** data, osmium::memory::Buffer& buffer) { + osmium::builder::RelationBuilder builder{buffer}; + osmium::Relation& relation = builder.object(); + + relation.set_id(opl_parse_id(data)); + + const char* tags_begin = nullptr; + + const char* members_begin = nullptr; + const char* members_end = nullptr; + + std::string user; + while (**data) { + opl_parse_space(data); + const char c = **data; + if (c == '\0') { + break; + } + ++(*data); + switch (c) { + case 'v': + relation.set_version(opl_parse_version(data)); + break; + case 'd': + relation.set_visible(opl_parse_visible(data)); + break; + case 'c': + relation.set_changeset(opl_parse_changeset_id(data)); + break; + case 't': + relation.set_timestamp(opl_parse_timestamp(data)); + break; + case 'i': + relation.set_uid(opl_parse_uid(data)); + break; + case 'u': + opl_parse_string(data, user); + break; + case 'T': + if (opl_non_empty(*data)) { + tags_begin = *data; + opl_skip_section(data); + } + break; + case 'M': + members_begin = *data; + members_end = opl_skip_section(data); + break; + default: + --(*data); + throw opl_error{"unknown attribute", *data}; + } + } + + builder.add_user(user); + + if (tags_begin) { + opl_parse_tags(tags_begin, buffer, &builder); + } + + if (members_begin != members_end) { + opl_parse_relation_members(members_begin, members_end, buffer, &builder); + } + + buffer.commit(); + } + + inline void opl_parse_changeset(const char** data, osmium::memory::Buffer& buffer) { + osmium::builder::ChangesetBuilder builder{buffer}; + osmium::Changeset& changeset = builder.object(); + + changeset.set_id(opl_parse_changeset_id(data)); + + const char* tags_begin = nullptr; + + osmium::Location location1; + osmium::Location location2; + std::string user; + while (**data) { + opl_parse_space(data); + const char c = **data; + if (c == '\0') { + break; + } + ++(*data); + switch (c) { + case 'k': + changeset.set_num_changes(opl_parse_int(data)); + break; + case 's': + changeset.set_created_at(opl_parse_timestamp(data)); + break; + case 'e': + changeset.set_closed_at(opl_parse_timestamp(data)); + break; + case 'd': + changeset.set_num_comments(opl_parse_int(data)); + break; + case 'i': + changeset.set_uid(opl_parse_uid(data)); + break; + case 'u': + opl_parse_string(data, user); + break; + case 'x': + if (opl_non_empty(*data)) { + location1.set_lon_partial(data); + } + break; + case 'y': + if (opl_non_empty(*data)) { + location1.set_lat_partial(data); + } + break; + case 'X': + if (opl_non_empty(*data)) { + location2.set_lon_partial(data); + } + break; + case 'Y': + if (opl_non_empty(*data)) { + location2.set_lat_partial(data); + } + break; + case 'T': + if (opl_non_empty(*data)) { + tags_begin = *data; + opl_skip_section(data); + } + break; + default: + --(*data); + throw opl_error{"unknown attribute", *data}; + } + + } + + if (location1.valid() && location2.valid()) { + changeset.bounds().extend(location1); + changeset.bounds().extend(location2); + } + + builder.add_user(user); + + if (tags_begin) { + opl_parse_tags(tags_begin, buffer, &builder); + } + + buffer.commit(); + } + + inline bool opl_parse_line(uint64_t line_count, + const char* data, + osmium::memory::Buffer& buffer, + osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all) { + const char* start_of_line = data; + try { + switch (*data) { + case '\0': + // ignore empty lines + break; + case '#': + // ignore lines starting with # + break; + case 'n': + if (read_types & osmium::osm_entity_bits::node) { + ++data; + opl_parse_node(&data, buffer); + return true; + } + break; + case 'w': + if (read_types & osmium::osm_entity_bits::way) { + ++data; + opl_parse_way(&data, buffer); + return true; + } + break; + case 'r': + if (read_types & osmium::osm_entity_bits::relation) { + ++data; + opl_parse_relation(&data, buffer); + return true; + } + break; + case 'c': + if (read_types & osmium::osm_entity_bits::changeset) { + ++data; + opl_parse_changeset(&data, buffer); + return true; + } + break; + default: + throw opl_error{"unknown type", data}; + } + } catch (opl_error& e) { + e.set_pos(line_count, e.data ? e.data - start_of_line : 0); + throw; + } + + return false; + } + + } // namespace detail + + } // namespace io + +} // namespace osmium + + +#endif // OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP diff --git a/include/osmium/io/detail/output_format.hpp b/include/osmium/io/detail/output_format.hpp index 34ca82f24..0fe49158b 100644 --- a/include/osmium/io/detail/output_format.hpp +++ b/include/osmium/io/detail/output_format.hpp @@ -33,16 +33,16 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include -#include #include #include #include #include -#include +#include #include #include #include @@ -70,9 +70,28 @@ namespace osmium { m_out(std::make_shared()) { } - template - void output_formatted(const char* format, TArgs&&... args) { - append_printf_formatted_string(*m_out, format, std::forward(args)...); + // Simple function to convert integer to string. This is much + // faster than using sprintf, but could be further optimized. + // See https://github.com/miloyip/itoa-benchmark . + void output_int(int64_t value) { + if (value < 0) { + *m_out += '-'; + value = -value; + } + + char temp[20]; + char *t = temp; + do { + *t++ = char(value % 10) + '0'; + value /= 10; + } while (value > 0); + + const auto old_size = m_out->size(); + m_out->resize(old_size + (t - temp)); + char* data = &(*m_out)[old_size]; + do { + *data++ += *--t; + } while (t != temp); } }; // class OutputBlock; @@ -133,11 +152,11 @@ namespace osmium { public: - typedef std::function create_output_type; + using create_output_type = std::function; private: - typedef std::map map_type; + using map_type = std::map; map_type m_callbacks; diff --git a/include/osmium/io/detail/pbf.hpp b/include/osmium/io/detail/pbf.hpp index 88c499326..e23f8b9bf 100644 --- a/include/osmium/io/detail/pbf.hpp +++ b/include/osmium/io/detail/pbf.hpp @@ -78,7 +78,7 @@ namespace osmium { // between representation as double and as int const int64_t lonlat_resolution = 1000 * 1000 * 1000; - const int64_t resolution_convert = lonlat_resolution / osmium::Location::coordinate_precision; + const int64_t resolution_convert = lonlat_resolution / osmium::detail::coordinate_precision; } // namespace detail diff --git a/include/osmium/io/detail/pbf_decoder.hpp b/include/osmium/io/detail/pbf_decoder.hpp index 43de1c18e..5df191db6 100644 --- a/include/osmium/io/detail/pbf_decoder.hpp +++ b/include/osmium/io/detail/pbf_decoder.hpp @@ -33,10 +33,8 @@ DEALINGS IN THE SOFTWARE. */ -#include #include #include -#include #include #include #include @@ -44,35 +42,47 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #include +#include #include #include // IWYU pragma: export #include #include #include +#include +#include +#include +#include #include #include +#include +#include +#include #include -#include -#include +#include #include #include namespace osmium { + namespace builder { + class Builder; + } // namespace builder + namespace io { namespace detail { - using ptr_len_type = std::pair; + using protozero::data_view; using osm_string_len_type = std::pair; class PBFPrimitiveBlockDecoder { - static constexpr size_t initial_buffer_size = 2 * 1024 * 1024; + static constexpr const size_t initial_buffer_size = 2 * 1024 * 1024; - ptr_len_type m_data; + data_view m_data; std::vector m_stringtable; int64_t m_lon_offset = 0; @@ -84,18 +94,18 @@ namespace osmium { osmium::memory::Buffer m_buffer { initial_buffer_size }; - void decode_stringtable(const ptr_len_type& data) { + void decode_stringtable(const data_view& data) { if (!m_stringtable.empty()) { throw osmium::pbf_error("more than one stringtable in pbf file"); } protozero::pbf_message pbf_string_table(data); while (pbf_string_table.next(OSMFormat::StringTable::repeated_bytes_s)) { - auto str_len = pbf_string_table.get_data(); - if (str_len.second > osmium::max_osm_string_length) { + const auto str_view = pbf_string_table.get_view(); + if (str_view.size() > osmium::max_osm_string_length) { throw osmium::pbf_error("overlong string in string table"); } - m_stringtable.emplace_back(str_len.first, osmium::string_size_type(str_len.second)); + m_stringtable.emplace_back(str_view.data(), osmium::string_size_type(str_view.size())); } } @@ -104,7 +114,7 @@ namespace osmium { while (pbf_primitive_block.next()) { switch (pbf_primitive_block.tag()) { case OSMFormat::PrimitiveBlock::required_StringTable_stringtable: - decode_stringtable(pbf_primitive_block.get_data()); + decode_stringtable(pbf_primitive_block.get_view()); break; case OSMFormat::PrimitiveBlock::optional_int32_granularity: m_granularity = pbf_primitive_block.get_int32(); @@ -132,28 +142,28 @@ namespace osmium { switch (pbf_primitive_group.tag()) { case OSMFormat::PrimitiveGroup::repeated_Node_nodes: if (m_read_types & osmium::osm_entity_bits::node) { - decode_node(pbf_primitive_group.get_data()); + decode_node(pbf_primitive_group.get_view()); } else { pbf_primitive_group.skip(); } break; case OSMFormat::PrimitiveGroup::optional_DenseNodes_dense: if (m_read_types & osmium::osm_entity_bits::node) { - decode_dense_nodes(pbf_primitive_group.get_data()); + decode_dense_nodes(pbf_primitive_group.get_view()); } else { pbf_primitive_group.skip(); } break; case OSMFormat::PrimitiveGroup::repeated_Way_ways: if (m_read_types & osmium::osm_entity_bits::way) { - decode_way(pbf_primitive_group.get_data()); + decode_way(pbf_primitive_group.get_view()); } else { pbf_primitive_group.skip(); } break; case OSMFormat::PrimitiveGroup::repeated_Relation_relations: if (m_read_types & osmium::osm_entity_bits::relation) { - decode_relation(pbf_primitive_group.get_data()); + decode_relation(pbf_primitive_group.get_view()); } else { pbf_primitive_group.skip(); } @@ -165,7 +175,7 @@ namespace osmium { } } - osm_string_len_type decode_info(const ptr_len_type& data, osmium::OSMObject& object) { + osm_string_len_type decode_info(const data_view& data, osmium::OSMObject& object) { osm_string_len_type user = std::make_pair("", 0); protozero::pbf_message pbf_info(data); @@ -173,7 +183,7 @@ namespace osmium { switch (pbf_info.tag()) { case OSMFormat::Info::optional_int32_version: { - auto version = pbf_info.get_int32(); + const auto version = pbf_info.get_int32(); if (version < 0) { throw osmium::pbf_error("object version must not be negative"); } @@ -185,7 +195,7 @@ namespace osmium { break; case OSMFormat::Info::optional_int64_changeset: { - auto changeset_id = pbf_info.get_int64(); + const auto changeset_id = pbf_info.get_int64(); if (changeset_id < 0) { throw osmium::pbf_error("object changeset_id must not be negative"); } @@ -209,15 +219,15 @@ namespace osmium { return user; } - using kv_type = std::pair; + using kv_type = protozero::iterator_range; void build_tag_list(osmium::builder::Builder& builder, const kv_type& keys, const kv_type& vals) { - if (keys.first != keys.second) { + if (!keys.empty()) { osmium::builder::TagListBuilder tl_builder(m_buffer, &builder); - auto kit = keys.first; - auto vit = vals.first; - while (kit != keys.second) { - if (vit == vals.second) { + auto kit = keys.begin(); + auto vit = vals.begin(); + while (kit != keys.end()) { + if (vit == vals.end()) { // this is against the spec, must have same number of elements throw osmium::pbf_error("PBF format error"); } @@ -232,7 +242,7 @@ namespace osmium { return int32_t((c * m_granularity + m_lon_offset) / resolution_convert); } - void decode_node(const ptr_len_type& data) { + void decode_node(const data_view& data) { osmium::builder::NodeBuilder builder(m_buffer); osmium::Node& node = builder.object(); @@ -256,7 +266,7 @@ namespace osmium { vals = pbf_node.get_packed_uint32(); break; case OSMFormat::Node::optional_Info_info: - user = decode_info(pbf_node.get_data(), builder.object()); + user = decode_info(pbf_node.get_view(), builder.object()); break; case OSMFormat::Node::required_sint64_lat: lat = pbf_node.get_sint64(); @@ -287,12 +297,14 @@ namespace osmium { m_buffer.commit(); } - void decode_way(const ptr_len_type& data) { + void decode_way(const data_view& data) { osmium::builder::WayBuilder builder(m_buffer); kv_type keys; kv_type vals; - std::pair refs; + protozero::iterator_range refs; + protozero::iterator_range lats; + protozero::iterator_range lons; osm_string_len_type user = { "", 0 }; @@ -309,11 +321,17 @@ namespace osmium { vals = pbf_way.get_packed_uint32(); break; case OSMFormat::Way::optional_Info_info: - user = decode_info(pbf_way.get_data(), builder.object()); + user = decode_info(pbf_way.get_view(), builder.object()); break; case OSMFormat::Way::packed_sint64_refs: refs = pbf_way.get_packed_sint64(); break; + case OSMFormat::Way::packed_sint64_lat: + lats = pbf_way.get_packed_sint64(); + break; + case OSMFormat::Way::packed_sint64_lon: + lons = pbf_way.get_packed_sint64(); + break; default: pbf_way.skip(); } @@ -321,11 +339,26 @@ namespace osmium { builder.add_user(user.first, user.second); - if (refs.first != refs.second) { + if (!refs.empty()) { osmium::builder::WayNodeListBuilder wnl_builder(m_buffer, &builder); osmium::util::DeltaDecode ref; - while (refs.first != refs.second) { - wnl_builder.add_node_ref(ref.update(*refs.first++)); + if (lats.empty()) { + for (const auto& ref_value : refs) { + wnl_builder.add_node_ref(ref.update(ref_value)); + } + } else { + osmium::util::DeltaDecode lon; + osmium::util::DeltaDecode lat; + while (!refs.empty() && !lons.empty() && !lats.empty()) { + wnl_builder.add_node_ref( + ref.update(refs.front()), + osmium::Location{convert_pbf_coordinate(lon.update(lons.front())), + convert_pbf_coordinate(lat.update(lats.front()))} + ); + refs.drop_front(); + lons.drop_front(); + lats.drop_front(); + } } } @@ -334,14 +367,14 @@ namespace osmium { m_buffer.commit(); } - void decode_relation(const ptr_len_type& data) { + void decode_relation(const data_view& data) { osmium::builder::RelationBuilder builder(m_buffer); kv_type keys; kv_type vals; - std::pair roles; - std::pair refs; - std::pair types; + protozero::iterator_range roles; + protozero::iterator_range refs; + protozero::iterator_range types; osm_string_len_type user = { "", 0 }; @@ -358,7 +391,7 @@ namespace osmium { vals = pbf_relation.get_packed_uint32(); break; case OSMFormat::Relation::optional_Info_info: - user = decode_info(pbf_relation.get_data(), builder.object()); + user = decode_info(pbf_relation.get_view(), builder.object()); break; case OSMFormat::Relation::packed_int32_roles_sid: roles = pbf_relation.get_packed_int32(); @@ -376,21 +409,24 @@ namespace osmium { builder.add_user(user.first, user.second); - if (refs.first != refs.second) { + if (!refs.empty()) { osmium::builder::RelationMemberListBuilder rml_builder(m_buffer, &builder); osmium::util::DeltaDecode ref; - while (roles.first != roles.second && refs.first != refs.second && types.first != types.second) { - const auto& r = m_stringtable.at(*roles.first++); - int type = *types.first++; + while (!roles.empty() && !refs.empty() && !types.empty()) { + const auto& r = m_stringtable.at(roles.front()); + const int type = types.front(); if (type < 0 || type > 2) { throw osmium::pbf_error("unknown relation member type"); } rml_builder.add_member( osmium::item_type(type + 1), - ref.update(*refs.first++), + ref.update(refs.front()), r.first, r.second ); + roles.drop_front(); + refs.drop_front(); + types.drop_front(); } } @@ -399,22 +435,22 @@ namespace osmium { m_buffer.commit(); } - void decode_dense_nodes(const ptr_len_type& data) { + void decode_dense_nodes(const data_view& data) { bool has_info = false; bool has_visibles = false; - std::pair ids; - std::pair lats; - std::pair lons; + protozero::iterator_range ids; + protozero::iterator_range lats; + protozero::iterator_range lons; - std::pair tags; + protozero::iterator_range tags; - std::pair versions; - std::pair timestamps; - std::pair changesets; - std::pair uids; - std::pair user_sids; - std::pair visibles; + protozero::iterator_range versions; + protozero::iterator_range timestamps; + protozero::iterator_range changesets; + protozero::iterator_range uids; + protozero::iterator_range user_sids; + protozero::iterator_range visibles; protozero::pbf_message pbf_dense_nodes(data); while (pbf_dense_nodes.next()) { @@ -475,11 +511,11 @@ namespace osmium { osmium::util::DeltaDecode dense_changeset; osmium::util::DeltaDecode dense_timestamp; - auto tag_it = tags.first; + auto tag_it = tags.begin(); - while (ids.first != ids.second) { - if (lons.first == lons.second || - lats.first == lats.second) { + while (!ids.empty()) { + if (lons.empty() || + lats.empty()) { // this is against the spec, must have same number of elements throw osmium::pbf_error("PBF format error"); } @@ -489,43 +525,50 @@ namespace osmium { osmium::builder::NodeBuilder builder(m_buffer); osmium::Node& node = builder.object(); - node.set_id(dense_id.update(*ids.first++)); + node.set_id(dense_id.update(ids.front())); + ids.drop_front(); if (has_info) { - if (versions.first == versions.second || - changesets.first == changesets.second || - timestamps.first == timestamps.second || - uids.first == uids.second || - user_sids.first == user_sids.second) { + if (versions.empty() || + changesets.empty() || + timestamps.empty() || + uids.empty() || + user_sids.empty()) { // this is against the spec, must have same number of elements throw osmium::pbf_error("PBF format error"); } - auto version = *versions.first++; + const auto version = versions.front(); + versions.drop_front(); if (version < 0) { throw osmium::pbf_error("object version must not be negative"); } node.set_version(static_cast(version)); - auto changeset_id = dense_changeset.update(*changesets.first++); + const auto changeset_id = dense_changeset.update(changesets.front()); + changesets.drop_front(); if (changeset_id < 0) { throw osmium::pbf_error("object changeset_id must not be negative"); } node.set_changeset(static_cast(changeset_id)); - node.set_timestamp(dense_timestamp.update(*timestamps.first++) * m_date_factor / 1000); - node.set_uid_from_signed(static_cast(dense_uid.update(*uids.first++))); + node.set_timestamp(dense_timestamp.update(timestamps.front()) * m_date_factor / 1000); + timestamps.drop_front(); + node.set_uid_from_signed(static_cast(dense_uid.update(uids.front()))); + uids.drop_front(); if (has_visibles) { - if (visibles.first == visibles.second) { + if (visibles.empty()) { // this is against the spec, must have same number of elements throw osmium::pbf_error("PBF format error"); } - visible = (*visibles.first++) != 0; + visible = (visibles.front() != 0); + visibles.drop_front(); } node.set_visible(visible); - const auto& u = m_stringtable.at(dense_user_sid.update(*user_sids.first++)); + const auto& u = m_stringtable.at(dense_user_sid.update(user_sids.front())); + user_sids.drop_front(); builder.add_user(u.first, u.second); } else { builder.add_user(""); @@ -533,8 +576,10 @@ namespace osmium { // even if the node isn't visible, there's still a record // of its lat/lon in the dense arrays. - const auto lon = dense_longitude.update(*lons.first++); - const auto lat = dense_latitude.update(*lats.first++); + const auto lon = dense_longitude.update(lons.front()); + lons.drop_front(); + const auto lat = dense_latitude.update(lats.front()); + lats.drop_front(); if (visible) { builder.object().set_location(osmium::Location( convert_pbf_coordinate(lon), @@ -542,18 +587,18 @@ namespace osmium { )); } - if (tag_it != tags.second) { + if (tag_it != tags.end()) { osmium::builder::TagListBuilder tl_builder(m_buffer, &builder); - while (tag_it != tags.second && *tag_it != 0) { + while (tag_it != tags.end() && *tag_it != 0) { const auto& k = m_stringtable.at(*tag_it++); - if (tag_it == tags.second) { + if (tag_it == tags.end()) { throw osmium::pbf_error("PBF format error"); // this is against the spec, keys/vals must come in pairs } const auto& v = m_stringtable.at(*tag_it++); tl_builder.add_tag(k.first, k.second, v.first, v.second); } - if (tag_it != tags.second) { + if (tag_it != tags.end()) { ++tag_it; } } @@ -565,7 +610,7 @@ namespace osmium { public: - PBFPrimitiveBlockDecoder(const ptr_len_type& data, osmium::osm_entity_bits::type read_types) : + PBFPrimitiveBlockDecoder(const data_view& data, osmium::osm_entity_bits::type read_types) : m_data(data), m_read_types(read_types) { } @@ -582,7 +627,7 @@ namespace osmium { try { decode_primitive_block_metadata(); decode_primitive_block_data(); - } catch (std::out_of_range&) { + } catch (const std::out_of_range&) { throw osmium::pbf_error("string id out of range"); } @@ -591,17 +636,17 @@ namespace osmium { }; // class PBFPrimitiveBlockDecoder - inline ptr_len_type decode_blob(const std::string& blob_data, std::string& output) { + inline data_view decode_blob(const std::string& blob_data, std::string& output) { int32_t raw_size = 0; - std::pair zlib_data = {nullptr, 0}; + protozero::data_view zlib_data; protozero::pbf_message pbf_blob(blob_data); while (pbf_blob.next()) { switch (pbf_blob.tag()) { case FileFormat::Blob::optional_bytes_raw: { - auto data_len = pbf_blob.get_data(); - if (data_len.second > max_uncompressed_blob_size) { + auto data_len = pbf_blob.get_view(); + if (data_len.size() > max_uncompressed_blob_size) { throw osmium::pbf_error("illegal blob size"); } return data_len; @@ -613,7 +658,7 @@ namespace osmium { } break; case FileFormat::Blob::optional_bytes_zlib_data: - zlib_data = pbf_blob.get_data(); + zlib_data = pbf_blob.get_view(); break; case FileFormat::Blob::optional_bytes_lzma_data: throw osmium::pbf_error("lzma blobs not implemented"); @@ -622,10 +667,10 @@ namespace osmium { } } - if (zlib_data.second != 0 && raw_size != 0) { + if (zlib_data.size() != 0 && raw_size != 0) { return osmium::io::detail::zlib_uncompress_string( - zlib_data.first, - static_cast(zlib_data.second), + zlib_data.data(), + static_cast(zlib_data.size()), static_cast(raw_size), output ); @@ -634,7 +679,7 @@ namespace osmium { throw osmium::pbf_error("blob contains no data"); } - inline osmium::Box decode_header_bbox(const ptr_len_type& data) { + inline osmium::Box decode_header_bbox(const data_view& data) { int64_t left = std::numeric_limits::max(); int64_t right = std::numeric_limits::max(); int64_t top = std::numeric_limits::max(); @@ -674,7 +719,7 @@ namespace osmium { return box; } - inline osmium::io::Header decode_header_block(const ptr_len_type& data) { + inline osmium::io::Header decode_header_block(const data_view& data) { osmium::io::Header header; int i = 0; @@ -682,20 +727,20 @@ namespace osmium { while (pbf_header_block.next()) { switch (pbf_header_block.tag()) { case OSMFormat::HeaderBlock::optional_HeaderBBox_bbox: - header.add_box(decode_header_bbox(pbf_header_block.get_data())); + header.add_box(decode_header_bbox(pbf_header_block.get_view())); break; case OSMFormat::HeaderBlock::repeated_string_required_features: { - auto feature = pbf_header_block.get_data(); - if (!strncmp("OsmSchema-V0.6", feature.first, feature.second)) { + auto feature = pbf_header_block.get_view(); + if (!std::strncmp("OsmSchema-V0.6", feature.data(), feature.size())) { // intentionally left blank - } else if (!strncmp("DenseNodes", feature.first, feature.second)) { + } else if (!std::strncmp("DenseNodes", feature.data(), feature.size())) { header.set("pbf_dense_nodes", true); - } else if (!strncmp("HistoricalInformation", feature.first, feature.second)) { + } else if (!std::strncmp("HistoricalInformation", feature.data(), feature.size())) { header.set_has_multiple_object_versions(true); } else { std::string msg("required feature not supported: "); - msg.append(feature.first, feature.second); + msg.append(feature.data(), feature.size()); throw osmium::pbf_error(msg); } } @@ -708,7 +753,7 @@ namespace osmium { break; case OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp: { - auto timestamp = osmium::Timestamp(pbf_header_block.get_int64()).to_iso(); + const auto timestamp = osmium::Timestamp(pbf_header_block.get_int64()).to_iso(); header.set("osmosis_replication_timestamp", timestamp); header.set("timestamp", timestamp); } diff --git a/include/osmium/io/detail/pbf_input_format.hpp b/include/osmium/io/detail/pbf_input_format.hpp index 0c6a9ef54..1253447f3 100644 --- a/include/osmium/io/detail/pbf_input_format.hpp +++ b/include/osmium/io/detail/pbf_input_format.hpp @@ -38,25 +38,22 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include #include -#include #include -#include #include #include +#include #include #include // IWYU pragma: export #include #include -#include -#include +#include #include -#include +#include #include -#include -#include #include #include #include @@ -80,7 +77,7 @@ namespace osmium { */ std::string read_from_input_queue(size_t size) { while (m_input_buffer.size() < size) { - std::string new_data = get_input(); + const std::string new_data = get_input(); if (input_done()) { throw osmium::pbf_error("truncated data (EOF encountered)"); } @@ -106,7 +103,7 @@ namespace osmium { try { const std::string input_data = read_from_input_queue(sizeof(size_in_network_byte_order)); size_in_network_byte_order = *reinterpret_cast(input_data.data()); - } catch (osmium::pbf_error&) { + } catch (const osmium::pbf_error&) { return 0; // EOF } @@ -123,13 +120,13 @@ namespace osmium { * type. Return the size of the following Blob. */ size_t decode_blob_header(protozero::pbf_message&& pbf_blob_header, const char* expected_type) { - std::pair blob_header_type; + protozero::data_view blob_header_type; size_t blob_header_datasize = 0; while (pbf_blob_header.next()) { switch (pbf_blob_header.tag()) { case FileFormat::BlobHeader::required_string_type: - blob_header_type = pbf_blob_header.get_data(); + blob_header_type = pbf_blob_header.get_view(); break; case FileFormat::BlobHeader::required_int32_datasize: blob_header_datasize = pbf_blob_header.get_int32(); @@ -143,7 +140,7 @@ namespace osmium { throw osmium::pbf_error("PBF format error: BlobHeader.datasize missing or zero."); } - if (strncmp(expected_type, blob_header_type.first, blob_header_type.second)) { + if (std::strncmp(expected_type, blob_header_type.data(), blob_header_type.size())) { throw osmium::pbf_error("blob does not have expected type (OSMHeader in first blob, OSMData in following blobs)"); } diff --git a/include/osmium/io/detail/pbf_output_format.hpp b/include/osmium/io/detail/pbf_output_format.hpp index 878d7b4ec..43aa8cc4d 100644 --- a/include/osmium/io/detail/pbf_output_format.hpp +++ b/include/osmium/io/detail/pbf_output_format.hpp @@ -41,30 +41,34 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include #include #include +#include +#include #include #include #include // IWYU pragma: export #include +#include #include #include #include #include #include #include -#include +#include #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -101,6 +105,9 @@ namespace osmium { /// Should the visible flag be added to all OSM objects? bool add_visible_flag; + /// Should node locations be added to ways? + bool locations_on_ways; + }; /** @@ -483,6 +490,7 @@ namespace osmium { m_options.add_metadata = file.is_not_false("pbf_add_metadata") && file.is_not_false("add_metadata"); m_options.add_historical_information_flag = file.has_multiple_object_versions(); m_options.add_visible_flag = file.has_multiple_object_versions(); + m_options.locations_on_ways = file.is_true("locations_on_ways"); } PBFOutputFormat(const PBFOutputFormat&) = delete; @@ -514,20 +522,24 @@ namespace osmium { pbf_header_block.add_string(OSMFormat::HeaderBlock::repeated_string_required_features, "HistoricalInformation"); } + if (m_options.locations_on_ways) { + pbf_header_block.add_string(OSMFormat::HeaderBlock::repeated_string_optional_features, "LocationsOnWays"); + } + pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_writingprogram, header.get("generator")); - std::string osmosis_replication_timestamp = header.get("osmosis_replication_timestamp"); + const std::string osmosis_replication_timestamp = header.get("osmosis_replication_timestamp"); if (!osmosis_replication_timestamp.empty()) { osmium::Timestamp ts(osmosis_replication_timestamp.c_str()); pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp, uint32_t(ts)); } - std::string osmosis_replication_sequence_number = header.get("osmosis_replication_sequence_number"); + const std::string osmosis_replication_sequence_number = header.get("osmosis_replication_sequence_number"); if (!osmosis_replication_sequence_number.empty()) { pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_sequence_number, std::atoll(osmosis_replication_sequence_number.c_str())); } - std::string osmosis_replication_base_url = header.get("osmosis_replication_base_url"); + const std::string osmosis_replication_base_url = header.get("osmosis_replication_base_url"); if (!osmosis_replication_base_url.empty()) { pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_osmosis_replication_base_url, osmosis_replication_base_url); } @@ -571,15 +583,30 @@ namespace osmium { pbf_way.add_int64(OSMFormat::Way::required_int64_id, way.id()); add_meta(way, pbf_way); - static auto map_node_ref = [](osmium::NodeRefList::const_iterator node_ref) noexcept -> osmium::object_id_type { - return node_ref->ref(); - }; - typedef osmium::util::DeltaEncodeIterator it_type; + { + osmium::util::DeltaEncode delta_id; + protozero::packed_field_sint64 field{pbf_way, protozero::pbf_tag_type(OSMFormat::Way::packed_sint64_refs)}; + for (const auto& node_ref : way.nodes()) { + field.add_element(delta_id.update(node_ref.ref())); + } + } - const auto& nodes = way.nodes(); - it_type first { nodes.cbegin(), nodes.cend(), map_node_ref }; - it_type last { nodes.cend(), nodes.cend(), map_node_ref }; - pbf_way.add_packed_sint64(OSMFormat::Way::packed_sint64_refs, first, last); + if (m_options.locations_on_ways) { + { + osmium::util::DeltaEncode delta_id; + protozero::packed_field_sint64 field{pbf_way, protozero::pbf_tag_type(OSMFormat::Way::packed_sint64_lon)}; + for (const auto& node_ref : way.nodes()) { + field.add_element(delta_id.update(lonlat2int(node_ref.location().lon_without_check()))); + } + } + { + osmium::util::DeltaEncode delta_id; + protozero::packed_field_sint64 field{pbf_way, protozero::pbf_tag_type(OSMFormat::Way::packed_sint64_lat)}; + for (const auto& node_ref : way.nodes()) { + field.add_element(delta_id.update(lonlat2int(node_ref.location().lat_without_check()))); + } + } + } } void relation(const osmium::Relation& relation) { @@ -596,14 +623,13 @@ namespace osmium { } } - static auto map_member_ref = [](osmium::RelationMemberList::const_iterator member) noexcept -> osmium::object_id_type { - return member->ref(); - }; - typedef osmium::util::DeltaEncodeIterator it_type; - const auto& members = relation.members(); - it_type first { members.cbegin(), members.cend(), map_member_ref }; - it_type last { members.cend(), members.cend(), map_member_ref }; - pbf_relation.add_packed_sint64(OSMFormat::Relation::packed_sint64_memids, first, last); + { + osmium::util::DeltaEncode delta_id; + protozero::packed_field_sint64 field{pbf_relation, protozero::pbf_tag_type(OSMFormat::Relation::packed_sint64_memids)}; + for (const auto& member : relation.members()) { + field.add_element(delta_id.update(member.ref())); + } + } { protozero::packed_field_int32 field{pbf_relation, protozero::pbf_tag_type(OSMFormat::Relation::packed_MemberType_types)}; diff --git a/include/osmium/io/detail/protobuf_tags.hpp b/include/osmium/io/detail/protobuf_tags.hpp index bdaabbaec..3eb790240 100644 --- a/include/osmium/io/detail/protobuf_tags.hpp +++ b/include/osmium/io/detail/protobuf_tags.hpp @@ -146,7 +146,9 @@ namespace osmium { packed_uint32_keys = 2, packed_uint32_vals = 3, optional_Info_info = 4, - packed_sint64_refs = 8 + packed_sint64_refs = 8, + packed_sint64_lat = 9, + packed_sint64_lon = 10 }; enum class Relation : protozero::pbf_tag_type { diff --git a/include/osmium/io/detail/queue_util.hpp b/include/osmium/io/detail/queue_util.hpp index af8454411..bc4702000 100644 --- a/include/osmium/io/detail/queue_util.hpp +++ b/include/osmium/io/detail/queue_util.hpp @@ -47,6 +47,9 @@ namespace osmium { namespace detail { + template + using future_queue_type = osmium::thread::Queue>; + /** * This type of queue contains buffers with OSM data in them. * The "end of file" is marked by an invalid Buffer. @@ -54,7 +57,7 @@ namespace osmium { * transport exceptions. The future also helps with keeping the * data in order. */ - using future_buffer_queue_type = osmium::thread::Queue>; + using future_buffer_queue_type = future_queue_type; /** * This type of queue contains OSM file data in the form it is @@ -71,24 +74,24 @@ namespace osmium { * transport exceptions. The future also helps with keeping the * data in order. */ - using future_string_queue_type = osmium::thread::Queue>; + using future_string_queue_type = future_queue_type; template - inline void add_to_queue(osmium::thread::Queue>& queue, T&& data) { + inline void add_to_queue(future_queue_type& queue, T&& data) { std::promise promise; queue.push(promise.get_future()); promise.set_value(std::forward(data)); } template - inline void add_to_queue(osmium::thread::Queue>& queue, std::exception_ptr&& exception) { + inline void add_to_queue(future_queue_type& queue, std::exception_ptr&& exception) { std::promise promise; queue.push(promise.get_future()); promise.set_exception(std::move(exception)); } template - inline void add_end_of_data_to_queue(osmium::thread::Queue>& queue) { + inline void add_end_of_data_to_queue(future_queue_type& queue) { add_to_queue(queue, T{}); } @@ -103,14 +106,12 @@ namespace osmium { template class queue_wrapper { - using queue_type = osmium::thread::Queue>; - - queue_type& m_queue; + future_queue_type& m_queue; bool m_has_reached_end_of_data; public: - explicit queue_wrapper(queue_type& queue) : + explicit queue_wrapper(future_queue_type& queue) : m_queue(queue), m_has_reached_end_of_data(false) { } diff --git a/include/osmium/io/detail/read_write.hpp b/include/osmium/io/detail/read_write.hpp index 769e2b39a..a086e5b78 100644 --- a/include/osmium/io/detail/read_write.hpp +++ b/include/osmium/io/detail/read_write.hpp @@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include #include #include @@ -84,7 +83,7 @@ namespace osmium { #ifdef _WIN32 flags |= O_BINARY; #endif - int fd = ::open(filename.c_str(), flags, 0666); + const int fd = ::open(filename.c_str(), flags, 0666); if (fd < 0) { throw std::system_error(errno, std::system_category(), std::string("Open failed for '") + filename + "'"); } @@ -108,7 +107,7 @@ namespace osmium { #ifdef _WIN32 flags |= O_BINARY; #endif - int fd = ::open(filename.c_str(), flags); + const int fd = ::open(filename.c_str(), flags); if (fd < 0) { throw std::system_error(errno, std::system_category(), std::string("Open failed for '") + filename + "'"); } @@ -133,7 +132,7 @@ namespace osmium { if (write_count > max_write) { write_count = max_write; } - auto length = ::write(fd, output_buffer + offset, static_cast(write_count)); + const auto length = ::write(fd, output_buffer + offset, static_cast(write_count)); if (length < 0) { throw std::system_error(errno, std::system_category(), "Write failed"); } diff --git a/include/osmium/io/detail/string_table.hpp b/include/osmium/io/detail/string_table.hpp index 55c622681..f1ddc8715 100644 --- a/include/osmium/io/detail/string_table.hpp +++ b/include/osmium/io/detail/string_table.hpp @@ -34,13 +34,14 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include -#include #include #include #include -#include #include +#include +#include #include @@ -69,8 +70,8 @@ namespace osmium { std::list m_chunks; void add_chunk() { - m_chunks.push_front(std::string()); - m_chunks.front().reserve(m_chunk_size); + m_chunks.emplace_back(); + m_chunks.back().reserve(m_chunk_size); } public: @@ -82,6 +83,7 @@ namespace osmium { } void clear() noexcept { + assert(!m_chunks.empty()); m_chunks.erase(std::next(m_chunks.begin()), m_chunks.end()); m_chunks.front().clear(); } @@ -93,31 +95,38 @@ namespace osmium { * allocated. */ const char* add(const char* string) { - size_t len = std::strlen(string) + 1; + const size_t len = std::strlen(string) + 1; assert(len <= m_chunk_size); - size_t chunk_len = m_chunks.front().size(); - if (chunk_len + len > m_chunks.front().capacity()) { + size_t chunk_len = m_chunks.back().size(); + if (chunk_len + len > m_chunks.back().capacity()) { add_chunk(); chunk_len = 0; } - m_chunks.front().append(string); - m_chunks.front().append(1, '\0'); + m_chunks.back().append(string); + m_chunks.back().append(1, '\0'); - return m_chunks.front().c_str() + chunk_len; + return m_chunks.back().c_str() + chunk_len; } - class const_iterator : public std::iterator { + class const_iterator { + + using it_type = std::list::const_iterator; - typedef std::list::const_iterator it_type; it_type m_it; const it_type m_last; const char* m_pos; public: + using iterator_category = std::forward_iterator_tag; + using value_type = const char*; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + const_iterator(it_type it, it_type last) : m_it(it), m_last(last), @@ -126,7 +135,7 @@ namespace osmium { const_iterator& operator++() { assert(m_it != m_last); - auto last_pos = m_it->c_str() + m_it->size(); + const auto last_pos = m_it->c_str() + m_it->size(); while (m_pos != last_pos && *m_pos) ++m_pos; if (m_pos != last_pos) ++m_pos; if (m_pos == last_pos) { @@ -184,18 +193,33 @@ namespace osmium { } size_t get_used_bytes_in_last_chunk() const noexcept { - return m_chunks.front().size(); + return m_chunks.back().size(); } }; // class StringStore - struct StrComp { + struct str_equal { - bool operator()(const char* lhs, const char* rhs) const { - return strcmp(lhs, rhs) < 0; + bool operator()(const char* lhs, const char* rhs) const noexcept { + return lhs == rhs || std::strcmp(lhs, rhs) == 0; } - }; // struct StrComp + }; // struct str_equal + + struct djb2_hash { + + size_t operator()(const char* str) const noexcept { + size_t hash = 5381; + int c; + + while ((c = *str++)) { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + + return hash; + } + + }; // struct djb2_hash class StringTable { @@ -206,14 +230,23 @@ namespace osmium { // Blob. static constexpr const uint32_t max_entries = max_uncompressed_blob_size; + // There is one string table per PBF primitive block. Most of + // them are really small, because most blocks are full of nodes + // with no tags. But string tables can get really large for + // ways with many tags or for large relations. + // The chosen size is enough so that 99% of all string tables + // in typical OSM files will only need a single memory + // allocation. + static constexpr const size_t default_stringtable_chunk_size = 100 * 1024; + StringStore m_strings; - std::map m_index; + std::unordered_map m_index; uint32_t m_size; public: - StringTable() : - m_strings(1024 * 1024), + explicit StringTable(size_t size = default_stringtable_chunk_size) : + m_strings(size), m_index(), m_size(0) { m_strings.add(""); @@ -231,7 +264,7 @@ namespace osmium { } uint32_t add(const char* s) { - auto f = m_index.find(s); + const auto f = m_index.find(s); if (f != m_index.end()) { return uint32_t(f->second); } diff --git a/include/osmium/io/detail/string_util.hpp b/include/osmium/io/detail/string_util.hpp index f80088e63..0334b0e98 100644 --- a/include/osmium/io/detail/string_util.hpp +++ b/include/osmium/io/detail/string_util.hpp @@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #include #include #include @@ -100,36 +101,57 @@ namespace osmium { static const size_t max_size = 0; #endif - size_t old_size = out.size(); + const size_t old_size = out.size(); - int len = string_snprintf(out, - old_size, - max_size, - format, - std::forward(args)...); + const int len = string_snprintf(out, + old_size, + max_size, + format, + std::forward(args)...); assert(len > 0); if (size_t(len) >= max_size) { #ifndef NDEBUG - int len2 = + const int len2 = #endif - string_snprintf(out, - old_size, - size_t(len) + 1, - format, - std::forward(args)...); + string_snprintf(out, + old_size, + size_t(len) + 1, + format, + std::forward(args)...); assert(len2 == len); } out.resize(old_size + size_t(len)); } + // Write out the value with exactly two hex digits. + inline void append_2_hex_digits(std::string& out, uint32_t value, const char* const hex_digits) { + out += hex_digits[(value >> 4) & 0xf]; + out += hex_digits[ value & 0xf]; + } + + // Write out the value with four or more hex digits. + inline void append_min_4_hex_digits(std::string& out, uint32_t value, const char* const hex_digits) { + auto + v = value & 0xf0000000; if (v) out += hex_digits[v >> 28]; + v = value & 0x0f000000; if (v) out += hex_digits[v >> 24]; + v = value & 0x00f00000; if (v) out += hex_digits[v >> 20]; + v = value & 0x000f0000; if (v) out += hex_digits[v >> 16]; + + out += hex_digits[(value >> 12) & 0xf]; + out += hex_digits[(value >> 8) & 0xf]; + out += hex_digits[(value >> 4) & 0xf]; + out += hex_digits[ value & 0xf]; + } + inline void append_utf8_encoded_string(std::string& out, const char* data) { + static const char* lookup_hex = "0123456789abcdef"; const char* end = data + std::strlen(data); while (data != end) { const char* last = data; - uint32_t c = utf8::next(data, end); + const uint32_t c = utf8::next(data, end); // This is a list of Unicode code points that we let // through instead of escaping them. It is incomplete @@ -148,9 +170,9 @@ namespace osmium { } else { out += '%'; if (c <= 0xff) { - append_printf_formatted_string(out, "%02x", c); + append_2_hex_digits(out, c, lookup_hex); } else { - append_printf_formatted_string(out, "%04x", c); + append_min_4_hex_digits(out, c, lookup_hex); } out += '%'; } @@ -174,6 +196,7 @@ namespace osmium { } inline void append_debug_encoded_string(std::string& out, const char* data, const char* prefix, const char* suffix) { + static const char* lookup_hex = "0123456789ABCDEF"; const char* end = data + std::strlen(data); while (data != end) { @@ -194,7 +217,9 @@ namespace osmium { out.append(last, data); } else { out.append(prefix); - append_printf_formatted_string(out, "", c); + out.append(""); out.append(suffix); } } diff --git a/include/osmium/io/detail/write_thread.hpp b/include/osmium/io/detail/write_thread.hpp index 796048638..85ef811a7 100644 --- a/include/osmium/io/detail/write_thread.hpp +++ b/include/osmium/io/detail/write_thread.hpp @@ -36,6 +36,7 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include #include #include diff --git a/include/osmium/io/detail/xml_input_format.hpp b/include/osmium/io/detail/xml_input_format.hpp index 8cb5efe8a..d8c57d86e 100644 --- a/include/osmium/io/detail/xml_input_format.hpp +++ b/include/osmium/io/detail/xml_input_format.hpp @@ -34,10 +34,7 @@ DEALINGS IN THE SOFTWARE. */ #include -#include -#include #include -#include #include #include #include @@ -53,15 +50,19 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include #include +#include #include #include #include +#include +#include #include +#include +#include #include #include -#include +#include #include #include @@ -192,6 +193,17 @@ namespace osmium { static_cast(data)->characters(text, len); } + // This handler is called when there are any XML entities + // declared in the OSM file. Entities are normally not used, + // but they can be misused. See + // https://en.wikipedia.org/wiki/Billion_laughs + // The handler will just throw an error. + static void entity_declaration_handler(void*, + const XML_Char*, int, const XML_Char*, int, const XML_Char*, + const XML_Char*, const XML_Char*, const XML_Char*) { + throw osmium::xml_error("XML entities are not supported"); + } + public: explicit ExpatXMLParser(T* callback_object) : @@ -202,6 +214,7 @@ namespace osmium { XML_SetUserData(m_parser, callback_object); XML_SetElementHandler(m_parser, start_element_wrapper, end_element_wrapper); XML_SetCharacterDataHandler(m_parser, character_data_wrapper); + XML_SetEntityDeclHandler(m_parser, entity_declaration_handler); } ExpatXMLParser(const ExpatXMLParser&) = delete; @@ -240,11 +253,11 @@ namespace osmium { osmium::Location location; check_attributes(attrs, [&location, &user, &object](const XML_Char* name, const XML_Char* value) { - if (!strcmp(name, "lon")) { - location.set_lon(std::atof(value)); // XXX doesn't detect garbage after the number - } else if (!strcmp(name, "lat")) { - location.set_lat(std::atof(value)); // XXX doesn't detect garbage after the number - } else if (!strcmp(name, "user")) { + if (!std::strcmp(name, "lon")) { + location.set_lon(value); + } else if (!std::strcmp(name, "lat")) { + location.set_lat(value); + } else if (!std::strcmp(name, "user")) { user = value; } else { object.set_attribute(name, value); @@ -265,15 +278,15 @@ namespace osmium { osmium::Location min; osmium::Location max; check_attributes(attrs, [&min, &max, &user, &new_changeset](const XML_Char* name, const XML_Char* value) { - if (!strcmp(name, "min_lon")) { - min.set_lon(atof(value)); - } else if (!strcmp(name, "min_lat")) { - min.set_lat(atof(value)); - } else if (!strcmp(name, "max_lon")) { - max.set_lon(atof(value)); - } else if (!strcmp(name, "max_lat")) { - max.set_lat(atof(value)); - } else if (!strcmp(name, "user")) { + if (!std::strcmp(name, "min_lon")) { + min.set_lon(value); + } else if (!std::strcmp(name, "min_lat")) { + min.set_lat(value); + } else if (!std::strcmp(name, "max_lon")) { + max.set_lon(value); + } else if (!std::strcmp(name, "max_lat")) { + max.set_lat(value); + } else if (!std::strcmp(name, "user")) { user = value; } else { new_changeset.set_attribute(name, value); @@ -309,17 +322,17 @@ namespace osmium { void start_element(const XML_Char* element, const XML_Char** attrs) { switch (m_context) { case context::root: - if (!strcmp(element, "osm") || !strcmp(element, "osmChange")) { - if (!strcmp(element, "osmChange")) { + if (!std::strcmp(element, "osm") || !std::strcmp(element, "osmChange")) { + if (!std::strcmp(element, "osmChange")) { m_header.set_has_multiple_object_versions(true); } check_attributes(attrs, [this](const XML_Char* name, const XML_Char* value) { - if (!strcmp(name, "version")) { + if (!std::strcmp(name, "version")) { m_header.set("version", value); - if (strcmp(value, "0.6")) { + if (std::strcmp(value, "0.6")) { throw osmium::format_version_error(value); } - } else if (!strcmp(name, "generator")) { + } else if (!std::strcmp(name, "generator")) { m_header.set("generator", value); } }); @@ -333,7 +346,7 @@ namespace osmium { break; case context::top: assert(!m_tl_builder); - if (!strcmp(element, "node")) { + if (!std::strcmp(element, "node")) { mark_header_as_done(); if (read_types() & osmium::osm_entity_bits::node) { m_node_builder = std::unique_ptr(new osmium::builder::NodeBuilder(m_buffer)); @@ -342,7 +355,7 @@ namespace osmium { } else { m_context = context::ignored_node; } - } else if (!strcmp(element, "way")) { + } else if (!std::strcmp(element, "way")) { mark_header_as_done(); if (read_types() & osmium::osm_entity_bits::way) { m_way_builder = std::unique_ptr(new osmium::builder::WayBuilder(m_buffer)); @@ -351,7 +364,7 @@ namespace osmium { } else { m_context = context::ignored_way; } - } else if (!strcmp(element, "relation")) { + } else if (!std::strcmp(element, "relation")) { mark_header_as_done(); if (read_types() & osmium::osm_entity_bits::relation) { m_relation_builder = std::unique_ptr(new osmium::builder::RelationBuilder(m_buffer)); @@ -360,7 +373,7 @@ namespace osmium { } else { m_context = context::ignored_relation; } - } else if (!strcmp(element, "changeset")) { + } else if (!std::strcmp(element, "changeset")) { mark_header_as_done(); if (read_types() & osmium::osm_entity_bits::changeset) { m_changeset_builder = std::unique_ptr(new osmium::builder::ChangesetBuilder(m_buffer)); @@ -369,50 +382,56 @@ namespace osmium { } else { m_context = context::ignored_changeset; } - } else if (!strcmp(element, "bounds")) { + } else if (!std::strcmp(element, "bounds")) { osmium::Location min; osmium::Location max; check_attributes(attrs, [&min, &max](const XML_Char* name, const XML_Char* value) { - if (!strcmp(name, "minlon")) { - min.set_lon(atof(value)); - } else if (!strcmp(name, "minlat")) { - min.set_lat(atof(value)); - } else if (!strcmp(name, "maxlon")) { - max.set_lon(atof(value)); - } else if (!strcmp(name, "maxlat")) { - max.set_lat(atof(value)); + if (!std::strcmp(name, "minlon")) { + min.set_lon(value); + } else if (!std::strcmp(name, "minlat")) { + min.set_lat(value); + } else if (!std::strcmp(name, "maxlon")) { + max.set_lon(value); + } else if (!std::strcmp(name, "maxlat")) { + max.set_lat(value); } }); osmium::Box box; box.extend(min).extend(max); m_header.add_box(box); - } else if (!strcmp(element, "delete")) { + } else if (!std::strcmp(element, "delete")) { m_in_delete_section = true; } break; case context::node: m_last_context = context::node; m_context = context::in_object; - if (!strcmp(element, "tag")) { + if (!std::strcmp(element, "tag")) { get_tag(m_node_builder.get(), attrs); } break; case context::way: m_last_context = context::way; m_context = context::in_object; - if (!strcmp(element, "nd")) { + if (!std::strcmp(element, "nd")) { m_tl_builder.reset(); if (!m_wnl_builder) { m_wnl_builder = std::unique_ptr(new osmium::builder::WayNodeListBuilder(m_buffer, m_way_builder.get())); } - check_attributes(attrs, [this](const XML_Char* name, const XML_Char* value) { - if (!strcmp(name, "ref")) { - m_wnl_builder->add_node_ref(osmium::string_to_object_id(value)); + NodeRef nr; + check_attributes(attrs, [this, &nr](const XML_Char* name, const XML_Char* value) { + if (!std::strcmp(name, "ref")) { + nr.set_ref(osmium::string_to_object_id(value)); + } else if (!std::strcmp(name, "lon")) { + nr.location().set_lon(value); + } else if (!std::strcmp(name, "lat")) { + nr.location().set_lat(value); } }); - } else if (!strcmp(element, "tag")) { + m_wnl_builder->add_node_ref(nr); + } else if (!std::strcmp(element, "tag")) { m_wnl_builder.reset(); get_tag(m_way_builder.get(), attrs); } @@ -420,7 +439,7 @@ namespace osmium { case context::relation: m_last_context = context::relation; m_context = context::in_object; - if (!strcmp(element, "member")) { + if (!std::strcmp(element, "member")) { m_tl_builder.reset(); if (!m_rml_builder) { @@ -431,11 +450,11 @@ namespace osmium { object_id_type ref = 0; const char* role = ""; check_attributes(attrs, [&type, &ref, &role](const XML_Char* name, const XML_Char* value) { - if (!strcmp(name, "type")) { + if (!std::strcmp(name, "type")) { type = char_to_item_type(value[0]); - } else if (!strcmp(name, "ref")) { + } else if (!std::strcmp(name, "ref")) { ref = osmium::string_to_object_id(value); - } else if (!strcmp(name, "role")) { + } else if (!std::strcmp(name, "role")) { role = static_cast(value); } }); @@ -446,37 +465,37 @@ namespace osmium { throw osmium::xml_error("Missing ref on relation member"); } m_rml_builder->add_member(type, ref, role); - } else if (!strcmp(element, "tag")) { + } else if (!std::strcmp(element, "tag")) { m_rml_builder.reset(); get_tag(m_relation_builder.get(), attrs); } break; case context::changeset: m_last_context = context::changeset; - if (!strcmp(element, "discussion")) { + if (!std::strcmp(element, "discussion")) { m_context = context::discussion; m_tl_builder.reset(); if (!m_changeset_discussion_builder) { m_changeset_discussion_builder = std::unique_ptr(new osmium::builder::ChangesetDiscussionBuilder(m_buffer, m_changeset_builder.get())); } - } else if (!strcmp(element, "tag")) { + } else if (!std::strcmp(element, "tag")) { m_context = context::in_object; m_changeset_discussion_builder.reset(); get_tag(m_changeset_builder.get(), attrs); } break; case context::discussion: - if (!strcmp(element, "comment")) { + if (!std::strcmp(element, "comment")) { m_context = context::comment; osmium::Timestamp date; osmium::user_id_type uid = 0; const char* user = ""; check_attributes(attrs, [&date, &uid, &user](const XML_Char* name, const XML_Char* value) { - if (!strcmp(name, "date")) { + if (!std::strcmp(name, "date")) { date = osmium::Timestamp(value); - } else if (!strcmp(name, "uid")) { + } else if (!std::strcmp(name, "uid")) { uid = osmium::string_to_user_id(value); - } else if (!strcmp(name, "user")) { + } else if (!std::strcmp(name, "user")) { user = static_cast(value); } }); @@ -484,7 +503,7 @@ namespace osmium { } break; case context::comment: - if (!strcmp(element, "text")) { + if (!std::strcmp(element, "text")) { m_context = context::comment_text; } break; @@ -510,15 +529,15 @@ namespace osmium { assert(false); // should never be here break; case context::top: - if (!strcmp(element, "osm") || !strcmp(element, "osmChange")) { + if (!std::strcmp(element, "osm") || !std::strcmp(element, "osmChange")) { mark_header_as_done(); m_context = context::root; - } else if (!strcmp(element, "delete")) { + } else if (!std::strcmp(element, "delete")) { m_in_delete_section = false; } break; case context::node: - assert(!strcmp(element, "node")); + assert(!std::strcmp(element, "node")); m_tl_builder.reset(); m_node_builder.reset(); m_buffer.commit(); @@ -526,7 +545,7 @@ namespace osmium { flush_buffer(); break; case context::way: - assert(!strcmp(element, "way")); + assert(!std::strcmp(element, "way")); m_tl_builder.reset(); m_wnl_builder.reset(); m_way_builder.reset(); @@ -535,7 +554,7 @@ namespace osmium { flush_buffer(); break; case context::relation: - assert(!strcmp(element, "relation")); + assert(!std::strcmp(element, "relation")); m_tl_builder.reset(); m_rml_builder.reset(); m_relation_builder.reset(); @@ -544,7 +563,7 @@ namespace osmium { flush_buffer(); break; case context::changeset: - assert(!strcmp(element, "changeset")); + assert(!std::strcmp(element, "changeset")); m_tl_builder.reset(); m_changeset_discussion_builder.reset(); m_changeset_builder.reset(); @@ -553,15 +572,15 @@ namespace osmium { flush_buffer(); break; case context::discussion: - assert(!strcmp(element, "discussion")); + assert(!std::strcmp(element, "discussion")); m_context = context::changeset; break; case context::comment: - assert(!strcmp(element, "comment")); + assert(!std::strcmp(element, "comment")); m_context = context::discussion; break; case context::comment_text: - assert(!strcmp(element, "text")); + assert(!std::strcmp(element, "text")); m_context = context::comment; m_changeset_discussion_builder->add_comment_text(m_comment_text); break; @@ -569,22 +588,22 @@ namespace osmium { m_context = m_last_context; break; case context::ignored_node: - if (!strcmp(element, "node")) { + if (!std::strcmp(element, "node")) { m_context = context::top; } break; case context::ignored_way: - if (!strcmp(element, "way")) { + if (!std::strcmp(element, "way")) { m_context = context::top; } break; case context::ignored_relation: - if (!strcmp(element, "relation")) { + if (!std::strcmp(element, "relation")) { m_context = context::top; } break; case context::ignored_changeset: - if (!strcmp(element, "changeset")) { + if (!std::strcmp(element, "changeset")) { m_context = context::top; } break; diff --git a/include/osmium/io/detail/xml_output_format.hpp b/include/osmium/io/detail/xml_output_format.hpp index 09bd6b3ce..3f47b0fb1 100644 --- a/include/osmium/io/detail/xml_output_format.hpp +++ b/include/osmium/io/detail/xml_output_format.hpp @@ -34,27 +34,26 @@ DEALINGS IN THE SOFTWARE. */ #include -#include -#include -#include -#include #include #include #include -#include #include +#include #include +#include +#include #include #include #include #include -#include +#include #include #include #include #include #include +#include #include #include #include @@ -86,8 +85,26 @@ namespace osmium { */ bool use_change_ops; + /// Should node locations be added to ways? + bool locations_on_ways; }; + namespace detail { + + inline void append_lat_lon_attributes(std::string& out, const char* lat, const char* lon, const osmium::Location& location) { + out += ' '; + out += lat; + out += "=\""; + osmium::detail::append_location_coordinate_to_string(std::back_inserter(out), location.y()); + out += "\" "; + out += lon; + out += "=\""; + osmium::detail::append_location_coordinate_to_string(std::back_inserter(out), location.x()); + out += "\""; + } + + } // namespace detail + class XMLOutputBlock : public OutputBlock { // operation (create, modify, delete) for osc files @@ -116,12 +133,21 @@ namespace osmium { write_spaces(prefix_spaces()); } + template + void write_attribute(const char* name, T value) { + *m_out += ' '; + *m_out += name; + *m_out += "=\""; + output_int(value); + *m_out += '"'; + } + void write_meta(const osmium::OSMObject& object) { - output_formatted(" id=\"%" PRId64 "\"", object.id()); + write_attribute("id", object.id()); if (m_options.add_metadata) { if (object.version()) { - output_formatted(" version=\"%d\"", object.version()); + write_attribute("version", object.version()); } if (object.timestamp()) { @@ -131,13 +157,14 @@ namespace osmium { } if (!object.user_is_anonymous()) { - output_formatted(" uid=\"%d\" user=\"", object.uid()); + write_attribute("uid", object.uid()); + *m_out += " user=\""; append_xml_encoded_string(*m_out, object.user()); *m_out += "\""; } if (object.changeset()) { - output_formatted(" changeset=\"%d\"", object.changeset()); + write_attribute("changeset", object.changeset()); } if (m_options.add_visible_flag) { @@ -162,8 +189,11 @@ namespace osmium { } void write_discussion(const osmium::ChangesetDiscussion& comments) { + *m_out += " \n"; for (const auto& comment : comments) { - output_formatted(" \n", node_ref.ref()); + if (m_options.locations_on_ways) { + for (const auto& node_ref : way.nodes()) { + write_prefix(); + *m_out += " \n"; } @@ -332,7 +374,7 @@ namespace osmium { void changeset(const osmium::Changeset& changeset) { *m_out += " 0) { - *m_out += " \n"; + if (!changeset.discussion().empty()) { write_discussion(changeset.discussion()); } @@ -394,9 +434,10 @@ namespace osmium { XMLOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) : OutputFormat(output_queue), m_options() { - m_options.add_metadata = file.is_not_false("add_metadata"); - m_options.use_change_ops = file.is_true("xml_change_format"); - m_options.add_visible_flag = (file.has_multiple_object_versions() || file.is_true("force_visible_flag")) && !m_options.use_change_ops; + m_options.add_metadata = file.is_not_false("add_metadata"); + m_options.use_change_ops = file.is_true("xml_change_format"); + m_options.add_visible_flag = (file.has_multiple_object_versions() || file.is_true("force_visible_flag")) && !m_options.use_change_ops; + m_options.locations_on_ways = file.is_true("locations_on_ways"); } XMLOutputFormat(const XMLOutputFormat&) = delete; @@ -425,10 +466,9 @@ namespace osmium { for (const auto& box : header.boxes()) { out += " \n", box.top_right().lat()); + detail::append_lat_lon_attributes(out, "minlat", "minlon", box.bottom_left()); + detail::append_lat_lon_attributes(out, "maxlat", "maxlon", box.top_right()); + out += "/>\n"; } send_to_output_queue(std::move(out)); diff --git a/include/osmium/io/detail/zlib.hpp b/include/osmium/io/detail/zlib.hpp index 2b6dddb94..15ece7c5a 100644 --- a/include/osmium/io/detail/zlib.hpp +++ b/include/osmium/io/detail/zlib.hpp @@ -33,12 +33,12 @@ DEALINGS IN THE SOFTWARE. */ -#include -#include #include #include +#include + #include #include @@ -62,7 +62,7 @@ namespace osmium { std::string output(output_size, '\0'); - auto result = ::compress( + const auto result = ::compress( reinterpret_cast(const_cast(output.data())), &output_size, reinterpret_cast(input.data()), @@ -89,10 +89,10 @@ namespace osmium { * @param output Uncompressed result data. * @returns Pointer and size to incompressed data. */ - inline std::pair zlib_uncompress_string(const char* input, unsigned long input_size, unsigned long raw_size, std::string& output) { + inline protozero::data_view zlib_uncompress_string(const char* input, unsigned long input_size, unsigned long raw_size, std::string& output) { output.resize(raw_size); - auto result = ::uncompress( + const auto result = ::uncompress( reinterpret_cast(&*output.begin()), &raw_size, reinterpret_cast(input), @@ -103,7 +103,7 @@ namespace osmium { throw io_error(std::string("failed to uncompress data: ") + zError(result)); } - return std::make_pair(output.data(), output.size()); + return protozero::data_view{output.data(), output.size()}; } } // namespace detail diff --git a/include/osmium/io/file.hpp b/include/osmium/io/file.hpp index 56fc8d513..812c49429 100644 --- a/include/osmium/io/file.hpp +++ b/include/osmium/io/file.hpp @@ -34,7 +34,6 @@ DEALINGS IN THE SOFTWARE. */ #include -#include #include #include #include @@ -43,7 +42,6 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include namespace osmium { @@ -115,7 +113,7 @@ namespace osmium { } // if filename is a URL, default to XML format - std::string protocol = m_filename.substr(0, m_filename.find_first_of(':')); + const std::string protocol = m_filename.substr(0, m_filename.find_first_of(':')); if (protocol == "http" || protocol == "https") { m_file_format = file_format::xml; } @@ -174,7 +172,7 @@ namespace osmium { } for (auto& option : options) { - size_t pos = option.find_first_of('='); + const size_t pos = option.find_first_of('='); if (pos == std::string::npos) { set(option, true); } else { diff --git a/include/osmium/io/gzip_compression.hpp b/include/osmium/io/gzip_compression.hpp index e6ca010de..5e3e2331a 100644 --- a/include/osmium/io/gzip_compression.hpp +++ b/include/osmium/io/gzip_compression.hpp @@ -42,15 +42,20 @@ DEALINGS IN THE SOFTWARE. * @attention If you include this file, you'll need to link with `libz`. */ -#include +#include #include +#ifndef _MSC_VER +# include +#endif + #include #include #include #include #include +#include #include #include #include @@ -102,7 +107,7 @@ namespace osmium { explicit GzipCompressor(int fd, fsync sync) : Compressor(sync), - m_fd(dup(fd)), + m_fd(::dup(fd)), m_gzfile(::gzdopen(fd, "w")) { if (!m_gzfile) { detail::throw_gzip_error(m_gzfile, "write initialization failed"); @@ -171,6 +176,9 @@ namespace osmium { detail::throw_gzip_error(m_gzfile, "read failed"); } buffer.resize(static_cast(nread)); +#if ZLIB_VERNUM >= 0x1240 + set_offset(size_t(::gzoffset(m_gzfile))); +#endif return buffer; } diff --git a/include/osmium/io/input_iterator.hpp b/include/osmium/io/input_iterator.hpp index 8be975930..4cde92f51 100644 --- a/include/osmium/io/input_iterator.hpp +++ b/include/osmium/io/input_iterator.hpp @@ -41,7 +41,6 @@ DEALINGS IN THE SOFTWARE. #include #include -#include namespace osmium { @@ -57,7 +56,7 @@ namespace osmium { static_assert(std::is_base_of::value, "TItem must derive from osmium::buffer::Item"); - typedef typename osmium::memory::Buffer::t_iterator item_iterator; + using item_iterator = typename osmium::memory::Buffer::t_iterator; TSource* m_source; std::shared_ptr m_buffer; @@ -69,20 +68,20 @@ namespace osmium { if (!m_buffer || !*m_buffer) { // end of input m_source = nullptr; m_buffer.reset(); - m_iter = item_iterator(); + m_iter = item_iterator{}; return; } - m_iter = m_buffer->begin(); - } while (m_iter == m_buffer->end()); + m_iter = m_buffer->select().begin(); + } while (m_iter == m_buffer->select().end()); } public: - typedef std::input_iterator_tag iterator_category; - typedef TItem value_type; - typedef ptrdiff_t difference_type; - typedef TItem* pointer; - typedef TItem& reference; + using iterator_category = std::input_iterator_tag; + using value_type = TItem; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; explicit InputIterator(TSource& source) : m_source(&source) { @@ -99,7 +98,7 @@ namespace osmium { assert(m_buffer); assert(m_iter); ++m_iter; - if (m_iter == m_buffer->end()) { + if (m_iter == m_buffer->select().end()) { update_buffer(); } return *this; diff --git a/include/osmium/io/opl_input.hpp b/include/osmium/io/opl_input.hpp new file mode 100644 index 000000000..ee9e447d5 --- /dev/null +++ b/include/osmium/io/opl_input.hpp @@ -0,0 +1,46 @@ +#ifndef OSMIUM_IO_OPL_INPUT_HPP +#define OSMIUM_IO_OPL_INPUT_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +/** + * @file + * + * Include this file if you want to read OSM OPL files. + * + */ + +#include // IWYU pragma: export +#include // IWYU pragma: export + +#endif // OSMIUM_IO_OPL_INPUT_HPP diff --git a/include/osmium/io/output_iterator.hpp b/include/osmium/io/output_iterator.hpp index ce050ebab..cf9291df0 100644 --- a/include/osmium/io/output_iterator.hpp +++ b/include/osmium/io/output_iterator.hpp @@ -35,10 +35,7 @@ DEALINGS IN THE SOFTWARE. #include #include -#include -#include -#include #include #include @@ -51,12 +48,18 @@ namespace osmium { namespace io { template - class OutputIterator : public std::iterator { + class OutputIterator { TDest* m_destination; public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + explicit OutputIterator(TDest& destination) : m_destination(&destination) { } diff --git a/include/osmium/io/overwrite.hpp b/include/osmium/io/overwrite.hpp index 5361698a5..bc6a5038c 100644 --- a/include/osmium/io/overwrite.hpp +++ b/include/osmium/io/overwrite.hpp @@ -34,6 +34,6 @@ DEALINGS IN THE SOFTWARE. */ #pragma message("Including overwrite.hpp is deprecated, #include instead.") -#include +#include // IWYU pragma: keep #endif // OSMIUM_IO_OVERWRITE_HPP diff --git a/include/osmium/io/reader.hpp b/include/osmium/io/reader.hpp index 7c60511af..12f97b8e6 100644 --- a/include/osmium/io/reader.hpp +++ b/include/osmium/io/reader.hpp @@ -62,11 +62,26 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include namespace osmium { namespace io { + namespace detail { + + inline size_t get_input_queue_size() noexcept { + const size_t n = osmium::config::get_max_queue_size("INPUT", 20); + return n > 2 ? n : 2; + } + + inline size_t get_osmdata_queue_size() noexcept { + const size_t n = osmium::config::get_max_queue_size("OSMDATA", 20); + return n > 2 ? n : 2; + } + + } // namespace detail + /** * This is the user-facing interface for reading OSM files. Instantiate * an object of this class with a file name or osmium::io::File object @@ -75,9 +90,6 @@ namespace osmium { */ class Reader { - static constexpr size_t max_input_queue_size = 20; // XXX - static constexpr size_t max_osmdata_queue_size = 20; // XXX - osmium::io::File m_file; osmium::osm_entity_bits::type m_read_which_entities; @@ -104,6 +116,8 @@ namespace osmium { osmium::thread::thread_handler m_thread; + size_t m_file_size; + // This function will run in a separate thread. static void parser_thread(const osmium::io::File& file, detail::future_string_queue_type& input_queue, @@ -111,8 +125,8 @@ namespace osmium { std::promise&& header_promise, osmium::osm_entity_bits::type read_which_entities) { std::promise promise = std::move(header_promise); - auto creator = detail::ParserFactory::instance().get_creator_function(file); - auto parser = creator(input_queue, osmdata_queue, promise, read_which_entities); + const auto creator = detail::ParserFactory::instance().get_creator_function(file); + const auto parser = creator(input_queue, osmdata_queue, promise, read_which_entities); parser->parse(); } @@ -133,7 +147,7 @@ namespace osmium { if (pipe(pipefd) < 0) { throw std::system_error(errno, std::system_category(), "opening pipe failed"); } - pid_t pid = fork(); + const pid_t pid = fork(); if (pid < 0) { throw std::system_error(errno, std::system_category(), "fork failed"); } @@ -202,16 +216,17 @@ namespace osmium { m_read_which_entities(read_which_entities), m_status(status::okay), m_childpid(0), - m_input_queue(max_input_queue_size, "raw_input"), + m_input_queue(detail::get_input_queue_size(), "raw_input"), m_decompressor(m_file.buffer() ? osmium::io::CompressionFactory::instance().create_decompressor(file.compression(), m_file.buffer(), m_file.buffer_size()) : osmium::io::CompressionFactory::instance().create_decompressor(file.compression(), open_input_file_or_url(m_file.filename(), &m_childpid))), m_read_thread_manager(*m_decompressor, m_input_queue), - m_osmdata_queue(max_osmdata_queue_size, "parser_results"), + m_osmdata_queue(detail::get_osmdata_queue_size(), "parser_results"), m_osmdata_queue_wrapper(m_osmdata_queue), m_header_future(), m_header(), - m_thread() { + m_thread(), + m_file_size(m_decompressor->file_size()) { std::promise header_promise; m_header_future = header_promise.get_future(); m_thread = osmium::thread::thread_handler{parser_thread, std::ref(m_file), std::ref(m_input_queue), std::ref(m_osmdata_queue), std::move(header_promise), read_which_entities}; @@ -263,7 +278,7 @@ namespace osmium { #ifndef _WIN32 if (m_childpid) { int status; - pid_t pid = ::waitpid(m_childpid, &status, 0); + const pid_t pid = ::waitpid(m_childpid, &status, 0); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" if (pid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { @@ -350,6 +365,32 @@ namespace osmium { return m_status == status::eof || m_status == status::closed; } + /** + * Get the size of the input file. Returns 0 if the file size + * is not available (for instance when reading from stdin). + */ + size_t file_size() const noexcept { + return m_file_size; + } + + /** + * Returns the current offset into the input file. Returns 0 if + * the offset is not available (for instance when reading from + * stdin). + * + * The offset can be used together with the result of file_size() + * to estimate how much of the file has been read. Note that due + * to buffering inside Osmium, this value will be larger than + * the amount of data actually available to the application. + * + * Do not call this function too often, certainly not for every + * object you are reading. Depending on the file type it might + * do an expensive system call. + */ + size_t offset() const noexcept { + return m_decompressor->offset(); + } + }; // class Reader /** diff --git a/include/osmium/io/writer.hpp b/include/osmium/io/writer.hpp index b389698fc..c12d31776 100644 --- a/include/osmium/io/writer.hpp +++ b/include/osmium/io/writer.hpp @@ -34,11 +34,13 @@ DEALINGS IN THE SOFTWARE. */ #include +#include +#include +#include #include +#include #include -#include #include -#include #include #include @@ -52,11 +54,25 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include namespace osmium { + namespace memory { + class Item; + } //namespace memory + namespace io { + namespace detail { + + inline size_t get_output_queue_size() noexcept { + size_t n = osmium::config::get_max_queue_size("OUTPUT", 20); + return n > 2 ? n : 2; + } + + } // namespace detail + /** * This is the user-facing interface for writing OSM files. Instantiate * an object of this class with a file name or osmium::io::File object @@ -194,7 +210,7 @@ namespace osmium { template explicit Writer(const osmium::io::File& file, TArgs&&... args) : m_file(file.check()), - m_output_queue(20, "raw_output"), // XXX + m_output_queue(detail::get_output_queue_size(), "raw_output"), m_output(osmium::io::detail::OutputFormatFactory::instance().create_output(m_file, m_output_queue)), m_buffer(), m_buffer_size(default_buffer_size), @@ -304,7 +320,7 @@ namespace osmium { } try { m_buffer.push_back(item); - } catch (osmium::buffer_is_full&) { + } catch (const osmium::buffer_is_full&) { do_flush(); m_buffer.push_back(item); } diff --git a/include/osmium/memory/buffer.hpp b/include/osmium/memory/buffer.hpp index 07a31fed9..8c246db02 100644 --- a/include/osmium/memory/buffer.hpp +++ b/include/osmium/memory/buffer.hpp @@ -517,6 +517,16 @@ namespace osmium { */ using const_iterator = t_const_iterator; + template + ItemIteratorRange select() { + return ItemIteratorRange{m_data, m_data + m_committed}; + } + + template + ItemIteratorRange select() const { + return ItemIteratorRange{m_data, m_data + m_committed}; + } + /** * Get iterator for iterating over all items of type T in the * buffer. diff --git a/include/osmium/memory/collection.hpp b/include/osmium/memory/collection.hpp index 17ace7025..2a2c04088 100644 --- a/include/osmium/memory/collection.hpp +++ b/include/osmium/memory/collection.hpp @@ -44,17 +44,23 @@ namespace osmium { namespace memory { template - class CollectionIterator : public std::iterator { + class CollectionIterator { // This data_type is either 'unsigned char*' or 'const unsigned char*' depending // on whether TMember is const. This allows this class to be used as an iterator and // as a const_iterator. - typedef typename std::conditional::value, const unsigned char*, unsigned char*>::type data_type; + using data_type = typename std::conditional::value, const unsigned char*, unsigned char*>::type; data_type m_data; public: + using iterator_category = std::forward_iterator_tag; + using value_type = TMember; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + CollectionIterator() noexcept : m_data(nullptr) { } @@ -112,9 +118,9 @@ namespace osmium { public: - typedef CollectionIterator iterator; - typedef CollectionIterator const_iterator; - typedef TMember value_type; + using iterator = CollectionIterator; + using const_iterator = CollectionIterator; + using value_type = TMember; static constexpr osmium::item_type itemtype = TCollectionItemType; diff --git a/include/osmium/memory/item.hpp b/include/osmium/memory/item.hpp index fd404ce93..2df33c7f5 100644 --- a/include/osmium/memory/item.hpp +++ b/include/osmium/memory/item.hpp @@ -33,8 +33,10 @@ DEALINGS IN THE SOFTWARE. */ +#include #include -#include + +#include namespace osmium { @@ -45,16 +47,21 @@ namespace osmium { class Builder; } // namespace builder + enum class diff_indicator_type { + none = 0, + left = 1, + right = 2, + both = 3 + }; // diff_indicator_type + namespace memory { - typedef uint32_t item_size_type; + using item_size_type = uint32_t; // align datastructures to this many bytes constexpr item_size_type align_bytes = 8; - template - inline T padded_length(T length) noexcept { - static_assert(std::is_integral::value && std::is_unsigned::value, "Template parameter must be unsigned integral type"); + inline std::size_t padded_length(std::size_t length) noexcept { return (length + align_bytes - 1) & ~(align_bytes - 1); } @@ -100,7 +107,8 @@ namespace osmium { item_size_type m_size; item_type m_type; uint16_t m_removed : 1; - uint16_t m_padding : 15; + uint16_t m_diff : 2; + uint16_t m_padding : 13; template friend class CollectionIterator; @@ -121,6 +129,7 @@ namespace osmium { m_size(size), m_type(type), m_removed(false), + m_diff(0), m_padding(0) { } @@ -150,7 +159,7 @@ namespace osmium { } item_size_type padded_size() const { - return padded_length(m_size); + return static_cast_with_assert(padded_length(m_size)); } item_type type() const noexcept { @@ -165,6 +174,19 @@ namespace osmium { m_removed = removed; } + diff_indicator_type diff() const noexcept { + return diff_indicator_type(m_diff); + } + + char diff_as_char() const noexcept { + static constexpr const char* diff_chars = "*-+ "; + return diff_chars[m_diff]; + } + + void set_diff(diff_indicator_type diff) noexcept { + m_diff = uint16_t(diff); + } + }; // class Item static_assert(sizeof(Item) == 8, "Class osmium::Item has wrong size!"); diff --git a/include/osmium/memory/item_iterator.hpp b/include/osmium/memory/item_iterator.hpp index 3886c9870..27ebc59f0 100644 --- a/include/osmium/memory/item_iterator.hpp +++ b/include/osmium/memory/item_iterator.hpp @@ -34,16 +34,29 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include #include #include -#include #include #include namespace osmium { + class Area; + class Changeset; + class InnerRing; + class Node; + class OSMEntity; + class OSMObject; + class OuterRing; + class Relation; + class RelationMemberList; + class TagList; + class Way; + class WayNodeList; + namespace memory { namespace detail { @@ -116,19 +129,19 @@ namespace osmium { } // namespace detail template - class ItemIterator : public std::iterator { + class ItemIterator { static_assert(std::is_base_of::value, "TMember must derive from osmium::memory::Item"); // This data_type is either 'unsigned char*' or 'const unsigned char*' depending // on whether TMember is const. This allows this class to be used as an iterator and // as a const_iterator. - typedef typename std::conditional::value, const unsigned char*, unsigned char*>::type data_type; + using data_type = typename std::conditional::value, const unsigned char*, unsigned char*>::type; data_type m_data; data_type m_end; - void advance_to_next_item_of_right_type() { + void advance_to_next_item_of_right_type() noexcept { while (m_data != m_end && !detail::type_is_compatible::type>(reinterpret_cast(m_data)->type())) { m_data = reinterpret_cast(m_data)->next(); @@ -137,23 +150,29 @@ namespace osmium { public: + using iterator_category = std::forward_iterator_tag; + using value_type = TMember; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + ItemIterator() noexcept : m_data(nullptr), m_end(nullptr) { } - ItemIterator(data_type data, data_type end) : + ItemIterator(data_type data, data_type end) noexcept : m_data(data), m_end(end) { advance_to_next_item_of_right_type(); } template - ItemIterator cast() const { + ItemIterator cast() const noexcept { return ItemIterator(m_data, m_end); } - ItemIterator& operator++() { + ItemIterator& operator++() noexcept { assert(m_data); assert(m_data != m_end); m_data = reinterpret_cast(m_data)->next(); @@ -166,45 +185,50 @@ namespace osmium { * types. Do not use this unless you know what you are * doing. */ - ItemIterator& advance_once() { + ItemIterator& advance_once() noexcept { assert(m_data); assert(m_data != m_end); m_data = reinterpret_cast(m_data)->next(); return *static_cast*>(this); } - ItemIterator operator++(int) { + ItemIterator operator++(int) noexcept { ItemIterator tmp(*this); operator++(); return tmp; } - bool operator==(const ItemIterator& rhs) const { + bool operator==(const ItemIterator& rhs) const noexcept { return m_data == rhs.m_data && m_end == rhs.m_end; } - bool operator!=(const ItemIterator& rhs) const { + bool operator!=(const ItemIterator& rhs) const noexcept { return !(*this == rhs); } - unsigned char* data() const { + data_type data() noexcept { assert(m_data); return m_data; } - TMember& operator*() const { + const unsigned char* data() const noexcept { + assert(m_data); + return m_data; + } + + TMember& operator*() const noexcept { assert(m_data); assert(m_data != m_end); return *reinterpret_cast(m_data); } - TMember* operator->() const { + TMember* operator->() const noexcept { assert(m_data); assert(m_data != m_end); return reinterpret_cast(m_data); } - explicit operator bool() const { + explicit operator bool() const noexcept { return (m_data != nullptr) && (m_data != m_end); } @@ -221,6 +245,77 @@ namespace osmium { return out; } + template + class ItemIteratorRange { + + static_assert(std::is_base_of::value, "Template parameter must derive from osmium::memory::Item"); + + // This data_type is either 'unsigned char*' or + // 'const unsigned char*' depending on whether T is const. + using data_type = typename std::conditional::value, const unsigned char*, unsigned char*>::type; + + data_type m_begin; + data_type m_end; + + public: + + using iterator = ItemIterator; + using const_iterator = ItemIterator; + + ItemIteratorRange(data_type first, data_type last) noexcept : + m_begin(first), + m_end(last) { + } + + iterator begin() noexcept { + return iterator{m_begin, m_end}; + } + + iterator end() noexcept { + return iterator{m_end, m_end}; + } + + const_iterator cbegin() const noexcept { + return const_iterator{m_begin, m_end}; + } + + const_iterator cend() const noexcept { + return const_iterator{m_end, m_end}; + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator end() const noexcept { + return cend(); + } + + /** + * Return the number of items in this range. + * + * Note that this methods has worst-case complexity O(n) with n + * being the number of items in the underlying range. + */ + size_t size() const { + if (m_begin == m_end) { + return 0; + } + return std::distance(cbegin(), cend()); + } + + /** + * Is this range empty? + * + * Note that this methods has worst-case complexity O(n) with n + * being the number of items in the underlying range. + */ + bool empty() const { + return size() == 0; + } + + }; // class ItemIteratorRange + } // namespace memory } // namespace osmium diff --git a/include/osmium/object_pointer_collection.hpp b/include/osmium/object_pointer_collection.hpp index 09a52934e..4db1c084d 100644 --- a/include/osmium/object_pointer_collection.hpp +++ b/include/osmium/object_pointer_collection.hpp @@ -40,11 +40,9 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include // IWYU pragma: no_forward_declare osmium::OSMObject -// IWYU pragma: no_forward_declare osmium::memory::Item namespace osmium { @@ -70,8 +68,8 @@ namespace osmium { public: - typedef boost::indirect_iterator::iterator, osmium::OSMObject> iterator; - typedef boost::indirect_iterator::const_iterator, const osmium::OSMObject> const_iterator; + using iterator = boost::indirect_iterator::iterator, osmium::OSMObject>; + using const_iterator = boost::indirect_iterator::const_iterator, const osmium::OSMObject>; ObjectPointerCollection() noexcept : m_objects() { diff --git a/include/osmium/opl.hpp b/include/osmium/opl.hpp new file mode 100644 index 000000000..5666fa0de --- /dev/null +++ b/include/osmium/opl.hpp @@ -0,0 +1,67 @@ +#ifndef OSMIUM_OPL_HPP +#define OSMIUM_OPL_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include +#include + +namespace osmium { + + /** + * Parses one line in OPL format. The line must not have a newline + * character at the end. Buffer.commit() is called automatically if the + * write succeeded. + * + * @param data Line must be in this zero-delimited string. + * @param buffer Result will be written to this buffer. + * + * @returns true if an entity was parsed, false otherwise (for instance + * when the line is empty). + * @throws osmium::opl_error If the parsing fails. + */ + inline bool opl_parse(const char* data, osmium::memory::Buffer& buffer) { + try { + const bool wrote_something = osmium::io::detail::opl_parse_line(0, data, buffer); + buffer.commit(); + return wrote_something; + } catch (const osmium::opl_error&) { + buffer.rollback(); + throw; + } + } + +} // namespace osmium + + +#endif // OSMIUM_OPL_HPP diff --git a/include/osmium/osm.hpp b/include/osmium/osm.hpp index 594db7572..fa8a92da4 100644 --- a/include/osmium/osm.hpp +++ b/include/osmium/osm.hpp @@ -33,11 +33,20 @@ DEALINGS IN THE SOFTWARE. */ -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export /** * @brief Namespace for everything in the Osmium library. diff --git a/include/osmium/osm/area.hpp b/include/osmium/osm/area.hpp index ee232f0bd..490fbe95b 100644 --- a/include/osmium/osm/area.hpp +++ b/include/osmium/osm/area.hpp @@ -35,14 +35,17 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #include #include #include +#include #include #include #include #include +#include namespace osmium { @@ -144,8 +147,8 @@ namespace osmium { * * @returns Pair (number outer rings, number inner rings) */ - std::pair num_rings() const { - std::pair counter { 0, 0 }; + std::pair num_rings() const { + std::pair counter { 0, 0 }; for (auto it = cbegin(); it != cend(); ++it) { switch (it->type()) { @@ -185,27 +188,51 @@ namespace osmium { } /** + * @deprecated Use inner_rings() instead. + * * Get iterator for iterating over all inner rings in a specified outer * ring. * * @param it Iterator specifying outer ring. * @returns Iterator to first inner ring in specified outer ring. */ - osmium::memory::ItemIterator inner_ring_cbegin(const osmium::memory::ItemIterator& it) const { + OSMIUM_DEPRECATED osmium::memory::ItemIterator inner_ring_cbegin(const osmium::memory::ItemIterator& it) const { return it.cast(); } /** + * @deprecated Use inner_rings() instead. + * * Get iterator for iterating over all inner rings in a specified outer * ring. * * @param it Iterator specifying outer ring. * @returns Iterator one past last inner ring in specified outer ring. */ - osmium::memory::ItemIterator inner_ring_cend(const osmium::memory::ItemIterator& it) const { + OSMIUM_DEPRECATED osmium::memory::ItemIterator inner_ring_cend(const osmium::memory::ItemIterator& it) const { return std::next(it).cast(); } + /** + * Return an iterator range for all outer rings. + * You can use the usual begin() and end() functions to iterate over + * all outer rings. + */ + osmium::memory::ItemIteratorRange outer_rings() const { + return subitems(); + } + + /** + * Return an iterator range for all inner rings in the given outer + * ring. + * You can use the usual begin() and end() functions to iterate over + * all outer rings. + */ + osmium::memory::ItemIteratorRange inner_rings(const osmium::OuterRing& outer) const { + osmium::memory::ItemIteratorRange outer_range{outer.data(), next()}; + return osmium::memory::ItemIteratorRange{outer_range.cbegin().data(), std::next(outer_range.cbegin()).data()}; + } + }; // class Area static_assert(sizeof(Area) % osmium::memory::align_bytes == 0, "Class osmium::Area has wrong size to be aligned properly!"); diff --git a/include/osmium/osm/box.hpp b/include/osmium/osm/box.hpp index 6fcf48d6c..52ca93dd1 100644 --- a/include/osmium/osm/box.hpp +++ b/include/osmium/osm/box.hpp @@ -36,7 +36,6 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include namespace osmium { diff --git a/include/osmium/osm/changeset.hpp b/include/osmium/osm/changeset.hpp index f59db4808..8a503caac 100644 --- a/include/osmium/osm/changeset.hpp +++ b/include/osmium/osm/changeset.hpp @@ -33,7 +33,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include #include @@ -131,7 +133,7 @@ namespace osmium { public: - typedef size_t size_type; + using size_type = size_t; ChangesetDiscussion() : osmium::memory::Collection() { @@ -185,6 +187,11 @@ namespace osmium { public: + // Dummy to avoid warning because of unused private fields. Do not use. + int32_t do_not_use() const noexcept { + return m_padding1 + m_padding2; + } + /// Get ID of this changeset changeset_id_type id() const noexcept { return m_id; @@ -369,23 +376,23 @@ namespace osmium { * @param value Value of the attribute */ void set_attribute(const char* attr, const char* value) { - if (!strcmp(attr, "id")) { + if (!std::strcmp(attr, "id")) { set_id(value); - } else if (!strcmp(attr, "num_changes")) { + } else if (!std::strcmp(attr, "num_changes")) { set_num_changes(value); - } else if (!strcmp(attr, "comments_count")) { + } else if (!std::strcmp(attr, "comments_count")) { set_num_comments(value); - } else if (!strcmp(attr, "created_at")) { + } else if (!std::strcmp(attr, "created_at")) { set_created_at(osmium::Timestamp(value)); - } else if (!strcmp(attr, "closed_at")) { + } else if (!std::strcmp(attr, "closed_at")) { set_closed_at(osmium::Timestamp(value)); - } else if (!strcmp(attr, "uid")) { + } else if (!std::strcmp(attr, "uid")) { set_uid(value); } } - typedef osmium::memory::CollectionIterator iterator; - typedef osmium::memory::CollectionIterator const_iterator; + using iterator = osmium::memory::CollectionIterator; + using const_iterator = osmium::memory::CollectionIterator; iterator begin() { return iterator(subitems_position()); diff --git a/include/osmium/osm/crc.hpp b/include/osmium/osm/crc.hpp index ff39996f5..2abeac4a1 100644 --- a/include/osmium/osm/crc.hpp +++ b/include/osmium/osm/crc.hpp @@ -36,11 +36,17 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #include +#include #include #include +#include #include +#include #include +#include +#include #include #include @@ -71,8 +77,8 @@ namespace osmium { # if defined(__GNUC__) || defined(__clang__) return __builtin_bswap64(value); # else - uint64_t val1 = byte_swap_32(value & 0xFFFFFFFF); - uint64_t val2 = byte_swap_32(value >> 32); + const uint64_t val1 = byte_swap_32(value & 0xFFFFFFFF); + const uint64_t val2 = byte_swap_32(value >> 32); return (val1 << 32) | val2; # endif } @@ -86,11 +92,11 @@ namespace osmium { public: - TCRC& operator()() { + TCRC& operator()() noexcept { return m_crc; } - const TCRC& operator()() const { + const TCRC& operator()() const noexcept { return m_crc; } @@ -106,7 +112,7 @@ namespace osmium { #if __BYTE_ORDER == __LITTLE_ENDIAN m_crc.process_bytes(&value, sizeof(uint16_t)); #else - uint16_t v = osmium::util::byte_swap_16(value); + const uint16_t v = osmium::util::byte_swap_16(value); m_crc.process_bytes(&v, sizeof(uint16_t)); #endif } @@ -115,7 +121,7 @@ namespace osmium { #if __BYTE_ORDER == __LITTLE_ENDIAN m_crc.process_bytes(&value, sizeof(uint32_t)); #else - uint32_t v = osmium::util::byte_swap_32(value); + const uint32_t v = osmium::util::byte_swap_32(value); m_crc.process_bytes(&v, sizeof(uint32_t)); #endif } @@ -124,7 +130,7 @@ namespace osmium { #if __BYTE_ORDER == __LITTLE_ENDIAN m_crc.process_bytes(&value, sizeof(uint64_t)); #else - uint64_t v = osmium::util::byte_swap_64(value); + const uint64_t v = osmium::util::byte_swap_64(value); m_crc.process_bytes(&v, sizeof(uint64_t)); #endif } @@ -151,6 +157,7 @@ namespace osmium { void update(const NodeRef& node_ref) { update_int64(node_ref.ref()); + update(node_ref.location()); } void update(const NodeRefList& node_refs) { @@ -205,10 +212,10 @@ namespace osmium { void update(const osmium::Area& area) { update(static_cast(area)); - for (auto it = area.cbegin(); it != area.cend(); ++it) { - if (it->type() == osmium::item_type::outer_ring || - it->type() == osmium::item_type::inner_ring) { - update(static_cast(*it)); + for (const auto& subitem : area) { + if (subitem.type() == osmium::item_type::outer_ring || + subitem.type() == osmium::item_type::inner_ring) { + update(static_cast(subitem)); } } } diff --git a/include/osmium/osm/diff_object.hpp b/include/osmium/osm/diff_object.hpp index 609ab7453..21cf139a8 100644 --- a/include/osmium/osm/diff_object.hpp +++ b/include/osmium/osm/diff_object.hpp @@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE. #include -#include #include #include #include @@ -43,6 +42,10 @@ DEALINGS IN THE SOFTWARE. namespace osmium { + class Node; + class Way; + class Relation; + /** * A DiffObject holds pointers to three OSMObjects, the current object, * the previous, and the next. They always have the same type (Node, Way, diff --git a/include/osmium/osm/entity_bits.hpp b/include/osmium/osm/entity_bits.hpp index 50b3e4cb0..b8e9ddba8 100644 --- a/include/osmium/osm/entity_bits.hpp +++ b/include/osmium/osm/entity_bits.hpp @@ -33,12 +33,15 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include + #include namespace osmium { /** - * @brief Bitfield for OSM entity types. + * @brief Bit field for OSM entity types. */ namespace osm_entity_bits { @@ -94,8 +97,19 @@ namespace osmium { return lhs; } + /** + * Get entity_bits from item_type. + * + * @pre item_type must be undefined, node, way, relation, area, or + * changeset. + */ inline type from_item_type(osmium::item_type item_type) noexcept { - return static_cast(0x1 << (static_cast(item_type) - 1)); + auto ut = static_cast::type>(item_type); + assert(ut <= 0x05); + if (ut == 0) { + return nothing; + } + return static_cast(0x1 << (ut - 1)); } } // namespace osm_entity_bits diff --git a/include/osmium/osm/location.hpp b/include/osmium/osm/location.hpp index 85f4b162a..c5da620cb 100644 --- a/include/osmium/osm/location.hpp +++ b/include/osmium/osm/location.hpp @@ -35,15 +35,14 @@ DEALINGS IN THE SOFTWARE. #include #include +#include +#include #include +#include +#include #include #include -#include - -#include -#include - namespace osmium { /** @@ -62,6 +61,184 @@ namespace osmium { }; // struct invalid_location + namespace detail { + + constexpr const int coordinate_precision = 10000000; + + // Convert string with a floating point number into integer suitable + // for use as coordinate in a Location. + inline int32_t string_to_location_coordinate(const char** data) { + const char* str = *data; + const char* full = str; + + int64_t result = 0; + int sign = 1; + + // one more than significant digits to allow rounding + int64_t scale = 8; + + // paranoia check for maximum number of digits + int max_digits = 10; + + // optional minus sign + if (*str == '-') { + sign = -1; + ++str; + } + + // there has to be at least one digit + if (*str >= '0' && *str <= '9') { + result = *str - '0'; + ++str; + } else { + goto error; + } + + // optional additional digits before decimal point + while (*str >= '0' && *str <= '9' && max_digits > 0) { + result = result * 10 + (*str - '0'); + ++str; + --max_digits; + } + + if (max_digits == 0) { + goto error; + } + + // optional decimal point + if (*str == '.') { + ++str; + + // read significant digits + for (; scale > 0 && *str >= '0' && *str <= '9'; --scale, ++str) { + result = result * 10 + (*str - '0'); + } + + // ignore non-significant digits + max_digits = 20; + while (*str >= '0' && *str <= '9' && max_digits > 0) { + ++str; + --max_digits; + } + + if (max_digits == 0) { + goto error; + } + } + + // optional exponent in scientific notation + if (*str == 'e' || *str == 'E') { + ++str; + + int esign = 1; + // optional minus sign + if (*str == '-') { + esign = -1; + ++str; + } + + int64_t eresult = 0; + + // there has to be at least one digit in exponent + if (*str >= '0' && *str <= '9') { + eresult = *str - '0'; + ++str; + } else { + goto error; + } + + // optional additional digits in exponent + max_digits = 5; + while (*str >= '0' && *str <= '9' && max_digits > 0) { + eresult = eresult * 10 + (*str - '0'); + ++str; + --max_digits; + } + + if (max_digits == 0) { + goto error; + } + + scale += eresult * esign; + } + + if (scale < 0) { + result = 0; + } else { + for (; scale > 0; --scale) { + result *= 10; + } + + result = (result + 5) / 10 * sign; + + if (result > std::numeric_limits::max() || + result < std::numeric_limits::min()) { + goto error; + } + } + + *data = str; + return static_cast(result); + + error: + + throw invalid_location{std::string{"wrong format for coordinate: '"} + full + "'"}; + } + + // Convert integer as used by location for coordinates into a string. + template + inline T append_location_coordinate_to_string(T iterator, int32_t value) { + // handle negative values + if (value < 0) { + *iterator++ = '-'; + value = -value; + } + + // write digits into temporary buffer + int32_t v = value; + char temp[10]; + char* t = temp; + do { + *t++ = char(v % 10) + '0'; + v /= 10; + } while (v != 0); + + while (t-temp < 7) { + *t++ = '0'; + } + + // write out digits before decimal point + if (value >= coordinate_precision) { + if (value >= 10 * coordinate_precision) { + if (value >= 100 * coordinate_precision) { + *iterator++ = *--t; + } + *iterator++ = *--t; + } + *iterator++ = *--t; + } else { + *iterator++ = '0'; + } + + // remove trailing zeros + const char* tn = temp; + while (tn < t && *tn == '0') { + ++tn; + } + + // decimal point + if (t != tn) { + *iterator++ = '.'; + while (t != tn) { + *iterator++ = *--t; + } + } + + return iterator; + } + + } // namespace detail + /** * Locations define a place on earth. * @@ -89,14 +266,12 @@ namespace osmium { // static constexpr int32_t undefined_coordinate = std::numeric_limits::max(); static constexpr int32_t undefined_coordinate = 2147483647; - static constexpr int coordinate_precision = 10000000; - static int32_t double_to_fix(const double c) noexcept { - return static_cast(std::round(c * coordinate_precision)); + return static_cast(std::round(c * detail::coordinate_precision)); } static constexpr double fix_to_double(const int32_t c) noexcept { - return static_cast(c) / coordinate_precision; + return static_cast(c) / detail::coordinate_precision; } /** @@ -154,10 +329,10 @@ namespace osmium { * usual bounds (-180<=lon<=180, -90<=lat<=90). */ constexpr bool valid() const noexcept { - return m_x >= -180 * coordinate_precision - && m_x <= 180 * coordinate_precision - && m_y >= -90 * coordinate_precision - && m_y <= 90 * coordinate_precision; + return m_x >= -180 * detail::coordinate_precision + && m_x <= 180 * detail::coordinate_precision + && m_y >= -90 * detail::coordinate_precision + && m_y <= 90 * detail::coordinate_precision; } constexpr int32_t x() const noexcept { @@ -226,11 +401,47 @@ namespace osmium { return *this; } + Location& set_lon(const char* str) { + const char** data = &str; + m_x = detail::string_to_location_coordinate(data); + if (**data != '\0') { + throw invalid_location{std::string{"characters after coordinate: '"} + *data + "'"}; + } + return *this; + } + + Location& set_lat(const char* str) { + const char** data = &str; + m_y = detail::string_to_location_coordinate(data); + if (**data != '\0') { + throw invalid_location{std::string{"characters after coordinate: '"} + *data + "'"}; + } + return *this; + } + + Location& set_lon_partial(const char** str) { + m_x = detail::string_to_location_coordinate(str); + return *this; + } + + Location& set_lat_partial(const char** str) { + m_y = detail::string_to_location_coordinate(str); + return *this; + } + template - T as_string(T iterator, const char separator) const { - iterator = osmium::util::double2string(iterator, lon(), 7); + T as_string_without_check(T iterator, const char separator = ',') const { + iterator = detail::append_location_coordinate_to_string(iterator, x()); *iterator++ = separator; - return osmium::util::double2string(iterator, lat(), 7); + return detail::append_location_coordinate_to_string(iterator, y()); + } + + template + T as_string(T iterator, const char separator = ',') const { + if (!valid()) { + throw osmium::invalid_location("invalid location"); + } + return as_string_without_check(iterator, separator); } }; // class Location @@ -273,13 +484,52 @@ namespace osmium { template inline std::basic_ostream& operator<<(std::basic_ostream& out, const osmium::Location& location) { if (location) { - out << '(' << location.lon() << ',' << location.lat() << ')'; + out << '('; + location.as_string(std::ostream_iterator(out), ','); + out << ')'; } else { out << "(undefined,undefined)"; } return out; } + namespace detail { + + template + inline size_t hash(const osmium::Location& location) noexcept { + return location.x() ^ location.y(); + } + + template <> + inline size_t hash<8>(const osmium::Location& location) noexcept { + size_t h = location.x(); + h <<= 32; + return h ^ location.y(); + } + + } // namespace detail + } // namespace osmium +namespace std { + +// This pragma is a workaround for a bug in an old libc implementation +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif + template <> + struct hash { + using argument_type = osmium::Location; + using result_type = size_t; + size_t operator()(const osmium::Location& location) const noexcept { + return osmium::detail::hash(location); + } + }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // namespace std + #endif // OSMIUM_OSM_LOCATION_HPP diff --git a/include/osmium/osm/node_ref_list.hpp b/include/osmium/osm/node_ref_list.hpp index 84edc0760..6cfdf2295 100644 --- a/include/osmium/osm/node_ref_list.hpp +++ b/include/osmium/osm/node_ref_list.hpp @@ -39,6 +39,7 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #include namespace osmium { @@ -66,7 +67,7 @@ namespace osmium { * Returns the number of NodeRefs in the collection. */ size_t size() const noexcept { - auto size_node_refs = byte_size() - sizeof(NodeRefList); + const auto size_node_refs = byte_size() - sizeof(NodeRefList); assert(size_node_refs % sizeof(NodeRef) == 0); return size_node_refs / sizeof(NodeRef); } diff --git a/include/osmium/osm/object.hpp b/include/osmium/osm/object.hpp index 6d1de6f67..caa6fbcdd 100644 --- a/include/osmium/osm/object.hpp +++ b/include/osmium/osm/object.hpp @@ -33,11 +33,10 @@ DEALINGS IN THE SOFTWARE. */ -#include -#include #include #include #include +#include #include #include @@ -49,6 +48,7 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include namespace osmium { @@ -172,9 +172,9 @@ namespace osmium { * @returns Reference to object to make calls chainable. */ OSMObject& set_visible(const char* visible) { - if (!strcmp("true", visible)) { + if (!std::strcmp("true", visible)) { set_visible(true); - } else if (!strcmp("false", visible)) { + } else if (!std::strcmp("false", visible)) { set_visible(false); } else { throw std::invalid_argument("Unknown value for visible attribute (allowed is 'true' or 'false')"); @@ -286,6 +286,20 @@ namespace osmium { return *this; } + /** + * Set the timestamp when this object last changed. + * + * @param timestamp Timestamp in ISO format. + * @returns Reference to object to make calls chainable. + */ + OSMObject& set_timestamp(const char* timestamp) { + m_timestamp = detail::parse_timestamp(timestamp); + if (timestamp[20] != '\0') { + throw std::invalid_argument{"can not parse timestamp"}; + } + return *this; + } + /// Get user name for this object. const char* user() const noexcept { return reinterpret_cast(data() + sizeof_object()); @@ -311,25 +325,28 @@ namespace osmium { * * @param attr Name of the attribute (must be one of "id", "version", "changeset", "timestamp", "uid", "visible") * @param value Value of the attribute + * @returns Reference to object to make calls chainable. */ - void set_attribute(const char* attr, const char* value) { - if (!strcmp(attr, "id")) { + OSMObject& set_attribute(const char* attr, const char* value) { + if (!std::strcmp(attr, "id")) { set_id(value); - } else if (!strcmp(attr, "version")) { + } else if (!std::strcmp(attr, "version")) { set_version(value); - } else if (!strcmp(attr, "changeset")) { + } else if (!std::strcmp(attr, "changeset")) { set_changeset(value); - } else if (!strcmp(attr, "timestamp")) { - set_timestamp(osmium::Timestamp(value)); - } else if (!strcmp(attr, "uid")) { + } else if (!std::strcmp(attr, "timestamp")) { + set_timestamp(value); + } else if (!std::strcmp(attr, "uid")) { set_uid(value); - } else if (!strcmp(attr, "visible")) { + } else if (!std::strcmp(attr, "visible")) { set_visible(value); } + + return *this; } - typedef osmium::memory::CollectionIterator iterator; - typedef osmium::memory::CollectionIterator const_iterator; + using iterator = osmium::memory::CollectionIterator; + using const_iterator = osmium::memory::CollectionIterator; iterator begin() { return iterator(subitems_position()); @@ -355,6 +372,26 @@ namespace osmium { return cend(); } + /** + * Get a range of subitems of a specific type. + * + * @tparam The type (must be derived from osmium::memory::Item. + */ + template + osmium::memory::ItemIteratorRange subitems() { + return osmium::memory::ItemIteratorRange{subitems_position(), next()}; + } + + /** + * Get a range of subitems of a specific type. + * + * @tparam The type (must be derived from osmium::memory::Item. + */ + template + osmium::memory::ItemIteratorRange subitems() const { + return osmium::memory::ItemIteratorRange{subitems_position(), next()}; + } + template using t_iterator = osmium::memory::ItemIterator; @@ -399,8 +436,8 @@ namespace osmium { * OSMObjects are equal if their type, id, and version are equal. */ inline bool operator==(const OSMObject& lhs, const OSMObject& rhs) noexcept { - return lhs.type() == rhs.type() && - lhs.id() == rhs.id() && + return lhs.type() == rhs.type() && + lhs.id() == rhs.id() && lhs.version() == rhs.version(); } @@ -409,16 +446,22 @@ namespace osmium { } /** - * OSMObjects can be ordered by type, id and version. - * Note that we use the absolute value of the id for a - * better ordering of objects with negative id. + * OSMObjects can be ordered by type, id, version, and timestamp. Usually + * ordering by timestamp is not necessary as there shouldn't be two + * objects with the same type, id, and version. But this can happen when + * creating diff files from extracts, so we take the timestamp into + * account here. + * + * Note that we use the absolute value of the id for a better ordering + * of objects with negative id. If the IDs have the same absolute value, + * the positive ID comes first. + * + * See object_order_type_id_reverse_version if you need a different + * ordering. */ inline bool operator<(const OSMObject& lhs, const OSMObject& rhs) noexcept { - if (lhs.type() != rhs.type()) { - return lhs.type() < rhs.type(); - } - return (lhs.id() == rhs.id() && lhs.version() < rhs.version()) || - lhs.positive_id() < rhs.positive_id(); + return const_tie(lhs.type(), lhs.positive_id(), lhs.id() < 0, lhs.version(), lhs.timestamp()) < + const_tie(rhs.type(), rhs.positive_id(), rhs.id() < 0, rhs.version(), rhs.timestamp()); } inline bool operator>(const OSMObject& lhs, const OSMObject& rhs) noexcept { diff --git a/include/osmium/osm/object_comparisons.hpp b/include/osmium/osm/object_comparisons.hpp index fe3529b4a..aa0241d26 100644 --- a/include/osmium/osm/object_comparisons.hpp +++ b/include/osmium/osm/object_comparisons.hpp @@ -33,12 +33,17 @@ DEALINGS IN THE SOFTWARE. */ +#include + #include +#include +#include namespace osmium { /** - * Function object class for comparing OSM objects for equality by type, id, and version. + * Function object class for comparing OSM objects for equality by type, + * id, and version. */ struct object_equal_type_id_version { @@ -53,8 +58,8 @@ namespace osmium { }; // struct object_equal_type_id_version /** - * Function object class for comparing OSM objects for equality by type and id, - * ignoring the version. + * Function object class for comparing OSM objects for equality by type + * and id, ignoring the version. */ struct object_equal_type_id { @@ -70,7 +75,8 @@ namespace osmium { }; // struct object_equal_type_id /** - * Function object class for ordering OSM objects by type, id, and version. + * Function object class for ordering OSM objects by type, id, version, + * and timestamp. */ struct object_order_type_id_version { @@ -85,18 +91,17 @@ namespace osmium { }; // struct object_order_type_id_version /** - * Function object class for ordering OSM objects by type, id, and reverse version, - * ie objects are ordered by type and id, but later versions of an object are - * ordered before earlier versions of the same object. + * Function object class for ordering OSM objects by type, id, and + * reverse version, timestamp. So objects are ordered by type and id, but + * later versions of an object are ordered before earlier versions of the + * same object. This is useful when the last version of an object needs + * to be used. */ struct object_order_type_id_reverse_version { bool operator()(const osmium::OSMObject& lhs, const osmium::OSMObject& rhs) const noexcept { - if (lhs.type() != rhs.type()) { - return lhs.type() < rhs.type(); - } - return (lhs.id() == rhs.id() && lhs.version() > rhs.version()) || - lhs.positive_id() < rhs.positive_id(); + return const_tie(lhs.type(), lhs.id() < 0, lhs.positive_id(), rhs.version(), rhs.timestamp()) < + const_tie(rhs.type(), rhs.id() < 0, rhs.positive_id(), lhs.version(), lhs.timestamp()); } bool operator()(const osmium::OSMObject* lhs, const osmium::OSMObject* rhs) const noexcept { diff --git a/include/osmium/osm/relation.hpp b/include/osmium/osm/relation.hpp index 9c4e69cc7..2aa9caaf1 100644 --- a/include/osmium/osm/relation.hpp +++ b/include/osmium/osm/relation.hpp @@ -33,13 +33,13 @@ DEALINGS IN THE SOFTWARE. */ -#include #include #include #include #include // IWYU pragma: keep #include +#include #include #include #include @@ -149,7 +149,7 @@ namespace osmium { public: - typedef size_t size_type; + using size_type = size_t; RelationMemberList() : osmium::memory::Collection() { diff --git a/include/osmium/osm/segment.hpp b/include/osmium/osm/segment.hpp index d35f97066..c36533e0c 100644 --- a/include/osmium/osm/segment.hpp +++ b/include/osmium/osm/segment.hpp @@ -37,7 +37,6 @@ DEALINGS IN THE SOFTWARE. #include #include -#include namespace osmium { diff --git a/include/osmium/osm/tag.hpp b/include/osmium/osm/tag.hpp index 3f1a29826..cd2a913b7 100644 --- a/include/osmium/osm/tag.hpp +++ b/include/osmium/osm/tag.hpp @@ -34,7 +34,7 @@ DEALINGS IN THE SOFTWARE. */ #include -#include +#include #include #include #include @@ -87,7 +87,7 @@ namespace osmium { }; // class Tag inline bool operator==(const Tag& a, const Tag& b) { - return !std::strcmp(a.key(), b.key()) && !strcmp(a.value(), b.value()); + return !std::strcmp(a.key(), b.key()) && !std::strcmp(a.value(), b.value()); } inline bool operator<(const Tag& a, const Tag& b) { @@ -104,32 +104,72 @@ namespace osmium { class TagList : public osmium::memory::Collection { + const_iterator find_key(const char* key) const noexcept { + return std::find_if(cbegin(), cend(), [key](const Tag& tag) { + return !std::strcmp(tag.key(), key); + }); + } + public: - typedef size_t size_type; + using size_type = size_t; TagList() : osmium::memory::Collection() { } + /** + * Returns the number of tags in this tag list. + */ size_type size() const noexcept { return static_cast(std::distance(begin(), end())); } + /** + * Get tag value for the given tag key. If the key is not set, returns + * the default_value. + * + * @pre @code key != nullptr @endcode + */ const char* get_value_by_key(const char* key, const char* default_value = nullptr) const noexcept { - auto result = std::find_if(cbegin(), cend(), [key](const Tag& tag) { - return !strcmp(tag.key(), key); - }); - if (result == cend()) { - return default_value; - } - return result->value(); + assert(key); + const auto result = find_key(key); + return result == cend() ? default_value : result->value(); } + /** + * Get tag value for the given tag key. If the key is not set, returns + * nullptr. + * + * @pre @code key != nullptr @endcode + */ const char* operator[](const char* key) const noexcept { return get_value_by_key(key); } + /** + * Returns true if the tag with the given key is in the tag list. + * + * @pre @code key != nullptr @endcode + */ + bool has_key(const char* key) const noexcept { + assert(key); + return find_key(key) != cend(); + } + + /** + * Returns true if the tag with the given key and value is in the + * tag list. + * + * @pre @code key != nullptr && value != nullptr @endcode + */ + bool has_tag(const char* key, const char* value) const noexcept { + assert(key); + assert(value); + const auto result = find_key(key); + return result != cend() && !std::strcmp(result->value(), value); + } + }; // class TagList static_assert(sizeof(TagList) % osmium::memory::align_bytes == 0, "Class osmium::TagList has wrong size to be aligned properly!"); diff --git a/include/osmium/osm/timestamp.hpp b/include/osmium/osm/timestamp.hpp index 613752e68..5f5243028 100644 --- a/include/osmium/osm/timestamp.hpp +++ b/include/osmium/osm/timestamp.hpp @@ -40,12 +40,71 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include #include #include // IWYU pragma: keep namespace osmium { + namespace detail { + + inline time_t parse_timestamp(const char* str) { + static const int mon_lengths[] = { + 31, 29, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 + }; + if (str[ 0] >= '0' && str[ 0] <= '9' && + str[ 1] >= '0' && str[ 1] <= '9' && + str[ 2] >= '0' && str[ 2] <= '9' && + str[ 3] >= '0' && str[ 3] <= '9' && + str[ 4] == '-' && + str[ 5] >= '0' && str[ 5] <= '9' && + str[ 6] >= '0' && str[ 6] <= '9' && + str[ 7] == '-' && + str[ 8] >= '0' && str[ 8] <= '9' && + str[ 9] >= '0' && str[ 9] <= '9' && + str[10] == 'T' && + str[11] >= '0' && str[11] <= '9' && + str[12] >= '0' && str[12] <= '9' && + str[13] == ':' && + str[14] >= '0' && str[14] <= '9' && + str[15] >= '0' && str[15] <= '9' && + str[16] == ':' && + str[17] >= '0' && str[17] <= '9' && + str[18] >= '0' && str[18] <= '9' && + str[19] == 'Z') { + struct tm tm; + tm.tm_year = (str[ 0] - '0') * 1000 + + (str[ 1] - '0') * 100 + + (str[ 2] - '0') * 10 + + (str[ 3] - '0') - 1900; + tm.tm_mon = (str[ 5] - '0') * 10 + (str[ 6] - '0') - 1; + tm.tm_mday = (str[ 8] - '0') * 10 + (str[ 9] - '0'); + tm.tm_hour = (str[11] - '0') * 10 + (str[12] - '0'); + tm.tm_min = (str[14] - '0') * 10 + (str[15] - '0'); + tm.tm_sec = (str[17] - '0') * 10 + (str[18] - '0'); + tm.tm_wday = 0; + tm.tm_yday = 0; + tm.tm_isdst = 0; + if (tm.tm_year >= 0 && + tm.tm_mon >= 0 && tm.tm_mon <= 11 && + tm.tm_mday >= 1 && tm.tm_mday <= mon_lengths[tm.tm_mon] && + tm.tm_hour >= 0 && tm.tm_hour <= 23 && + tm.tm_min >= 0 && tm.tm_min <= 59 && + tm.tm_sec >= 0 && tm.tm_sec <= 60) { +#ifndef _WIN32 + return timegm(&tm); +#else + return _mkgmtime(&tm); +#endif + } + } + throw std::invalid_argument{"can not parse timestamp"}; + } + + } // namespace detail + /** * A timestamp. Internal representation is an unsigned 32bit integer * holding seconds since epoch (1970-01-01T00:00:00Z), so this will @@ -56,7 +115,7 @@ namespace osmium { class Timestamp { // length of ISO timestamp string yyyy-mm-ddThh:mm:ssZ\0 - static constexpr int timestamp_length = 20 + 1; + static constexpr const int timestamp_length = 20 + 1; // The timestamp format for OSM timestamps in strftime(3) format. // This is the ISO-Format "yyyy-mm-ddThh:mm:ssZ". @@ -97,27 +156,7 @@ namespace osmium { * @throws std::invalid_argument if the timestamp can not be parsed. */ explicit Timestamp(const char* timestamp) { -#ifndef _WIN32 - struct tm tm { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (strptime(timestamp, timestamp_format(), &tm) == nullptr) { - throw std::invalid_argument("can't parse timestamp"); - } - m_timestamp = static_cast(timegm(&tm)); -#else - struct tm tm; - int n = sscanf(timestamp, "%4d-%2d-%2dT%2d:%2d:%2dZ", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); - if (n != 6) { - throw std::invalid_argument("can't parse timestamp"); - } - tm.tm_year -= 1900; - tm.tm_mon--; - tm.tm_wday = 0; - tm.tm_yday = 0; - tm.tm_isdst = 0; - m_timestamp = static_cast(_mkgmtime(&tm)); -#endif + m_timestamp = static_cast(detail::parse_timestamp(timestamp)); } /** diff --git a/include/osmium/osm/types.hpp b/include/osmium/osm/types.hpp index 984dd135c..ec46bb2cd 100644 --- a/include/osmium/osm/types.hpp +++ b/include/osmium/osm/types.hpp @@ -38,25 +38,25 @@ DEALINGS IN THE SOFTWARE. namespace osmium { /* - * The following typedefs are chosen so that they can represent all needed + * The following types are chosen so that they can represent all needed * numbers and still be reasonably space efficient. As the OSM database * needs 64 bit IDs for nodes, this size is used for all object IDs. */ - typedef int64_t object_id_type; ///< Type for OSM object (node, way, or relation) IDs. - typedef uint64_t unsigned_object_id_type; ///< Type for OSM object (node, way, or relation) IDs where we only allow positive IDs. - typedef uint32_t object_version_type; ///< Type for OSM object version number. - typedef uint32_t changeset_id_type; ///< Type for OSM changeset IDs. - typedef uint32_t user_id_type; ///< Type for OSM user IDs. - typedef int32_t signed_user_id_type; ///< Type for signed OSM user IDs. - typedef uint32_t num_changes_type; ///< Type for changeset num_changes. - typedef uint32_t num_comments_type; ///< Type for changeset num_comments. + using object_id_type = int64_t; ///< Type for OSM object (node, way, or relation) IDs. + using unsigned_object_id_type = uint64_t; ///< Type for OSM object (node, way, or relation) IDs where we only allow positive IDs. + using object_version_type = uint32_t; ///< Type for OSM object version number. + using changeset_id_type = uint32_t; ///< Type for OSM changeset IDs. + using user_id_type = uint32_t; ///< Type for OSM user IDs. + using signed_user_id_type = int32_t; ///< Type for signed OSM user IDs. + using num_changes_type = uint32_t; ///< Type for changeset num_changes. + using num_comments_type = uint32_t; ///< Type for changeset num_comments. /** * Size for strings in OSM data such as user names, tag keys, roles, etc. * In Osmium they can be up to 2^16 bytes long, but OSM usually has lower * defined limits. */ - typedef uint16_t string_size_type; + using string_size_type = uint16_t; // maximum of 256 characters of max 4 bytes each (in UTF-8 encoding) constexpr const int max_osm_string_length = 256 * 4; diff --git a/include/osmium/osm/types_from_string.hpp b/include/osmium/osm/types_from_string.hpp index aed064898..190dd2983 100644 --- a/include/osmium/osm/types_from_string.hpp +++ b/include/osmium/osm/types_from_string.hpp @@ -35,13 +35,14 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include #include +#include #include #include #include +#include #include #include @@ -50,7 +51,7 @@ namespace osmium { /** * Convert string with object id to object_id_type. * - * @pre input must not be nullptr. + * @pre @code input != nullptr @endcode * * @param input Input string. * @@ -70,23 +71,29 @@ namespace osmium { /** * Parse string with object type identifier followed by object id. This - * reads strings like "n1234" and "w10". + * reads strings like "n1234" and "w10". If there is no type prefix, + * the default_type is returned. * - * @pre input must not be nullptr. + * @pre @code input != nullptr @endcode + * @pre @code types != osmium::osm_entity_bits::nothing @endcode * * @param input Input string. * @param types Allowed types. Must not be osmium::osm_entity_bits::nothing. + * @param default_type Type used when there is no type prefix. * * @returns std::pair of type and id. * * @throws std::range_error if the value is out of range. */ - inline std::pair string_to_object_id(const char* input, osmium::osm_entity_bits::type types) { + inline std::pair + string_to_object_id(const char* input, + osmium::osm_entity_bits::type types, + osmium::item_type default_type = osmium::item_type::undefined) { assert(input); assert(types != osmium::osm_entity_bits::nothing); if (*input != '\0') { if (std::isdigit(*input)) { - return std::make_pair(osmium::item_type::undefined, string_to_object_id(input)); + return std::make_pair(default_type, string_to_object_id(input)); } osmium::item_type t = osmium::char_to_item_type(*input); if (osmium::osm_entity_bits::from_item_type(t) & types) { @@ -126,7 +133,7 @@ namespace osmium { } /** - * Convert string with object version to object_version_type. + * Convert string with changeset id to changeset_id_type. * * @pre input must not be nullptr. * diff --git a/include/osmium/osm/way.hpp b/include/osmium/osm/way.hpp index 3bc30b0c6..f6713fef4 100644 --- a/include/osmium/osm/way.hpp +++ b/include/osmium/osm/way.hpp @@ -33,12 +33,13 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include -#include -#include #include #include +#include namespace osmium { diff --git a/include/osmium/relations/collector.hpp b/include/osmium/relations/collector.hpp index 7d7d14d0b..b8455b4eb 100644 --- a/include/osmium/relations/collector.hpp +++ b/include/osmium/relations/collector.hpp @@ -39,13 +39,12 @@ DEALINGS IN THE SOFTWARE. #include #include #include -//#include +#include #include -#include #include #include -#include // IWYU pragma: keep +#include #include #include #include @@ -57,6 +56,9 @@ DEALINGS IN THE SOFTWARE. namespace osmium { + class Node; + class Way; + /** * @brief Code related to the assembly of OSM relations */ @@ -64,13 +66,6 @@ namespace osmium { namespace detail { - template - inline typename std::iterator_traits::difference_type count_not_removed(const R& range) { - return std::count_if(range.begin(), range.end(), [](MemberMeta& mm) { - return !mm.removed(); - }); - } - } // namespace detail /** @@ -113,7 +108,7 @@ namespace osmium { public: - HandlerPass1(TCollector& collector) noexcept : + explicit HandlerPass1(TCollector& collector) noexcept : m_collector(collector) { } @@ -136,7 +131,7 @@ namespace osmium { public: - HandlerPass2(TCollector& collector) noexcept : + explicit HandlerPass2(TCollector& collector) noexcept : m_collector(collector) { } @@ -193,14 +188,14 @@ namespace osmium { int m_count_complete = 0; - typedef std::function callback_func_type; + using callback_func_type = std::function; callback_func_type m_callback; static constexpr size_t initial_buffer_size = 1024 * 1024; iterator_range find_member_meta(osmium::item_type type, osmium::object_id_type id) { auto& mmv = member_meta(type); - return iterator_range{std::equal_range(mmv.begin(), mmv.end(), MemberMeta(id))}; + return make_range(std::equal_range(mmv.begin(), mmv.end(), MemberMeta(id))); } public: @@ -313,6 +308,7 @@ namespace osmium { } const osmium::Relation& get_relation(size_t offset) const { + assert(m_relations_buffer.committed() > offset); return m_relations_buffer.get(offset); } @@ -323,7 +319,15 @@ namespace osmium { return get_relation(relation_meta.relation_offset()); } + /** + * Get the relation from a member_meta. + */ + const osmium::Relation& get_relation(const MemberMeta& member_meta) const { + return get_relation(m_relations[member_meta.relation_pos()]); + } + osmium::OSMObject& get_member(size_t offset) const { + assert(m_members_buffer.committed() > offset); return m_members_buffer.get(offset); } @@ -360,7 +364,6 @@ namespace osmium { } else { m_relations_buffer.commit(); m_relations.push_back(std::move(relation_meta)); -// std::cerr << "added relation id=" << relation.id() << "\n"; } } @@ -369,15 +372,17 @@ namespace osmium { * search on them. */ void sort_member_meta() { -/* std::cerr << "relations: " << m_relations.size() << "\n"; - std::cerr << "node members: " << m_member_meta[0].size() << "\n"; - std::cerr << "way members: " << m_member_meta[1].size() << "\n"; - std::cerr << "relation members: " << m_member_meta[2].size() << "\n";*/ std::sort(m_member_meta[0].begin(), m_member_meta[0].end()); std::sort(m_member_meta[1].begin(), m_member_meta[1].end()); std::sort(m_member_meta[2].begin(), m_member_meta[2].end()); } + static typename iterator_range::iterator::difference_type count_not_removed(const iterator_range& range) { + return std::count_if(range.begin(), range.end(), [](MemberMeta& mm) { + return !mm.removed(); + }); + } + /** * Find this object in the member vectors and add it to all * relations that need it. @@ -388,7 +393,7 @@ namespace osmium { bool find_and_add_object(const osmium::OSMObject& object) { auto range = find_member_meta(object.type(), object.id()); - if (detail::count_not_removed(range) == 0) { + if (count_not_removed(range) == 0) { // nothing found return false; } @@ -409,9 +414,7 @@ namespace osmium { assert(member_meta.member_id() == object.id()); assert(member_meta.relation_pos() < m_relations.size()); RelationMeta& relation_meta = m_relations[member_meta.relation_pos()]; -// std::cerr << " => " << member_meta.member_pos() << " < " << get_relation(relation_meta).members().size() << " (id=" << get_relation(relation_meta).id() << ")\n"; assert(member_meta.member_pos() < get_relation(relation_meta).members().size()); -// std::cerr << " add way " << member_meta.member_id() << " to rel " << get_relation(relation_meta).id() << " at pos " << member_meta.member_pos() << "\n"; relation_meta.got_one_member(); if (relation_meta.has_all_members()) { const size_t relation_offset = member_meta.relation_pos(); @@ -429,17 +432,17 @@ namespace osmium { const osmium::Relation& relation = get_relation(relation_meta); for (const auto& member : relation.members()) { if (member.ref() != 0) { - auto range = find_member_meta(member.type(), member.ref()); + const auto range = find_member_meta(member.type(), member.ref()); assert(!range.empty()); // if this is the last time this object was needed // then mark it as removed - if (detail::count_not_removed(range) == 1) { + if (count_not_removed(range) == 1) { get_member(range.begin()->buffer_offset()).set_removed(true); } for (auto& member_meta : range) { - if (!member_meta.removed() && relation.id() == get_relation(member_meta.relation_pos()).id()) { + if (!member_meta.removed() && relation.id() == get_relation(member_meta).id()) { member_meta.remove(); break; } diff --git a/include/osmium/relations/detail/member_meta.hpp b/include/osmium/relations/detail/member_meta.hpp index f0e9c3625..b28dca1d0 100644 --- a/include/osmium/relations/detail/member_meta.hpp +++ b/include/osmium/relations/detail/member_meta.hpp @@ -33,10 +33,8 @@ DEALINGS IN THE SOFTWARE. */ -#include #include #include -#include #include @@ -132,7 +130,7 @@ namespace osmium { template inline std::basic_ostream& operator<<(std::basic_ostream& out, const MemberMeta& mm) { - out << "MemberMeta(member_id=" << mm.member_id() << " relation_pos=" << mm.relation_pos() << " member_pos=" << mm.member_pos() << " buffer_offset=" << mm.buffer_offset() << ")"; + out << "MemberMeta(member_id=" << mm.member_id() << " relation_pos=" << mm.relation_pos() << " member_pos=" << mm.member_pos() << " buffer_offset=" << mm.buffer_offset() << " removed=" << (mm.removed() ? "yes" : "no") << ")"; return out; } diff --git a/include/osmium/relations/detail/relation_meta.hpp b/include/osmium/relations/detail/relation_meta.hpp index 93aa41cf2..b71c5a5f7 100644 --- a/include/osmium/relations/detail/relation_meta.hpp +++ b/include/osmium/relations/detail/relation_meta.hpp @@ -117,8 +117,8 @@ namespace osmium { */ struct has_all_members { - typedef RelationMeta& argument_type; - typedef bool result_type; + using argument_type = RelationMeta&; + using result_type = bool; /** * @returns true if this relation is complete, false otherwise. diff --git a/include/osmium/tags/filter.hpp b/include/osmium/tags/filter.hpp index 407992e6d..27a836061 100644 --- a/include/osmium/tags/filter.hpp +++ b/include/osmium/tags/filter.hpp @@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -76,8 +77,8 @@ namespace osmium { template , typename TValueComp=match_value> class Filter { - typedef TKey key_type; - typedef typename std::conditional::value, bool, TValue>::type value_type; + using key_type = TKey; + using value_type = typename std::conditional::value, bool, TValue>::type; struct Rule { key_type key; @@ -106,10 +107,10 @@ namespace osmium { public: - typedef Filter filter_type; - typedef const osmium::Tag& argument_type; - typedef bool result_type; - typedef boost::filter_iterator iterator; + using filter_type = Filter; + using argument_type = const osmium::Tag&; + using result_type = bool; + using iterator = boost::filter_iterator; explicit Filter(bool default_result = false) : m_default_result(default_result) { @@ -151,9 +152,9 @@ namespace osmium { }; // class Filter - typedef Filter KeyValueFilter; - typedef Filter KeyFilter; - typedef Filter KeyPrefixFilter; + using KeyValueFilter = Filter; + using KeyFilter = Filter; + using KeyPrefixFilter = Filter; } // namespace tags diff --git a/include/osmium/tags/regex_filter.hpp b/include/osmium/tags/regex_filter.hpp index 8ea6d60c3..3df94dd0e 100644 --- a/include/osmium/tags/regex_filter.hpp +++ b/include/osmium/tags/regex_filter.hpp @@ -49,7 +49,7 @@ namespace osmium { } }; // struct match_value - typedef Filter RegexFilter; + using RegexFilter = Filter; } // namespace tags diff --git a/include/osmium/tags/taglist.hpp b/include/osmium/tags/taglist.hpp index b1f346fe1..d7862798c 100644 --- a/include/osmium/tags/taglist.hpp +++ b/include/osmium/tags/taglist.hpp @@ -34,7 +34,7 @@ DEALINGS IN THE SOFTWARE. */ #include -#include +#include // IWYU pragma: keep #include diff --git a/include/osmium/thread/pool.hpp b/include/osmium/thread/pool.hpp index 207f55514..613f2272c 100644 --- a/include/osmium/thread/pool.hpp +++ b/include/osmium/thread/pool.hpp @@ -34,9 +34,7 @@ DEALINGS IN THE SOFTWARE. */ #include -#include #include -#include #include #include #include @@ -66,7 +64,7 @@ namespace osmium { } if (num_threads < 0) { - num_threads += hardware_concurrency; + num_threads += int(hardware_concurrency); } if (num_threads < 1) { @@ -78,6 +76,11 @@ namespace osmium { return num_threads; } + inline size_t get_work_queue_size() noexcept { + const size_t n = osmium::config::get_max_queue_size("WORK", 10); + return n > 2 ? n : 2; + } + } // namespace detail /** @@ -118,13 +121,11 @@ namespace osmium { osmium::thread::set_thread_name("_osmium_worker"); while (true) { function_wrapper task; - m_work_queue.wait_and_pop_with_timeout(task); - if (task) { - if (task()) { - // The called tasks returns true only when the - // worker thread should shut down. - return; - } + m_work_queue.wait_and_pop(task); + if (task && task()) { + // The called tasks returns true only when the + // worker thread should shut down. + return; } } } @@ -160,10 +161,9 @@ namespace osmium { public: static constexpr int default_num_threads = 0; - static constexpr size_t max_work_queue_size = 10; static Pool& instance() { - static Pool pool(default_num_threads, max_work_queue_size); + static Pool pool(default_num_threads, detail::get_work_queue_size()); return pool; } @@ -176,7 +176,6 @@ namespace osmium { ~Pool() { shutdown_all_workers(); - m_work_queue.shutdown(); } size_t queue_size() const { @@ -190,7 +189,7 @@ namespace osmium { template std::future::type> submit(TFunction&& func) { - typedef typename std::result_of::type result_type; + using result_type = typename std::result_of::type; std::packaged_task task(std::forward(func)); std::future future_result(task.get_future()); diff --git a/include/osmium/thread/queue.hpp b/include/osmium/thread/queue.hpp index 771735883..6f4f7b1d9 100644 --- a/include/osmium/thread/queue.hpp +++ b/include/osmium/thread/queue.hpp @@ -33,7 +33,6 @@ DEALINGS IN THE SOFTWARE. */ -#include #include #include #include @@ -41,7 +40,12 @@ DEALINGS IN THE SOFTWARE. #include #include #include -#include // IWYU pragma: keep (for std::move) +#include // IWYU pragma: keep + +#ifdef OSMIUM_DEBUG_QUEUE_SIZE +# include +# include +#endif namespace osmium { @@ -69,8 +73,6 @@ namespace osmium { /// Used to signal readers when data is available in the queue. std::condition_variable m_data_available; - std::atomic m_done; - #ifdef OSMIUM_DEBUG_QUEUE_SIZE /// The largest size the queue has been so far. size_t m_largest_size; @@ -81,6 +83,16 @@ namespace osmium { /// The number of times the queue was full and a thread pushing /// to the queue was blocked. std::atomic m_full_counter; + + /** + * The number of times wait_and_pop(with_timeout)() was called + * on the queue. + */ + std::atomic m_pop_counter; + + /// The number of times the queue was full and a thread pushing + /// to the queue was blocked. + std::atomic m_empty_counter; #endif public: @@ -97,21 +109,21 @@ namespace osmium { m_name(name), m_mutex(), m_queue(), - m_data_available(), - m_done(false) + m_data_available() #ifdef OSMIUM_DEBUG_QUEUE_SIZE , m_largest_size(0), m_push_counter(0), - m_full_counter(0) + m_full_counter(0), + m_pop_counter(0), + m_empty_counter(0) #endif { } ~Queue() { - shutdown(); #ifdef OSMIUM_DEBUG_QUEUE_SIZE - std::cerr << "queue '" << m_name << "' with max_size=" << m_max_size << " had largest size " << m_largest_size << " and was full " << m_full_counter << " times in " << m_push_counter << " push() calls\n"; + std::cerr << "queue '" << m_name << "' with max_size=" << m_max_size << " had largest size " << m_largest_size << " and was full " << m_full_counter << " times in " << m_push_counter << " push() calls and was empty " << m_empty_counter << " times in " << m_pop_counter << " pop() calls\n"; #endif } @@ -141,15 +153,18 @@ namespace osmium { m_data_available.notify_one(); } - void shutdown() { - m_done = true; - m_data_available.notify_all(); - } - void wait_and_pop(T& value) { +#ifdef OSMIUM_DEBUG_QUEUE_SIZE + ++m_pop_counter; +#endif std::unique_lock lock(m_mutex); +#ifdef OSMIUM_DEBUG_QUEUE_SIZE + if (m_queue.empty()) { + ++m_empty_counter; + } +#endif m_data_available.wait(lock, [this] { - return !m_queue.empty() || m_done; + return !m_queue.empty(); }); if (!m_queue.empty()) { value = std::move(m_queue.front()); @@ -157,22 +172,15 @@ namespace osmium { } } - void wait_and_pop_with_timeout(T& value) { - std::unique_lock lock(m_mutex); - if (!m_data_available.wait_for(lock, std::chrono::seconds(1), [this] { - return !m_queue.empty() || m_done; - })) { - return; - } - if (!m_queue.empty()) { - value = std::move(m_queue.front()); - m_queue.pop(); - } - } - bool try_pop(T& value) { +#ifdef OSMIUM_DEBUG_QUEUE_SIZE + ++m_pop_counter; +#endif std::lock_guard lock(m_mutex); if (m_queue.empty()) { +#ifdef OSMIUM_DEBUG_QUEUE_SIZE + ++m_empty_counter; +#endif return false; } value = std::move(m_queue.front()); diff --git a/include/osmium/thread/sorted_queue.hpp b/include/osmium/thread/sorted_queue.hpp deleted file mode 100644 index 5478643c9..000000000 --- a/include/osmium/thread/sorted_queue.hpp +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef OSMIUM_THREAD_SORTED_QUEUE_HPP -#define OSMIUM_THREAD_SORTED_QUEUE_HPP - -/* - -This file is part of Osmium (http://osmcode.org/libosmium). - -Copyright 2013-2016 Jochen Topf and others (see README). - -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -*/ - -#include -#include -#include -#include - -namespace osmium { - - namespace thread { - - /** - * This implements a sorted queue. It is a bit like a priority - * queue. We have n worker threads pushing items into the queue - * and one thread pulling them out again "in order". The order - * is defined by the monotonically increasing "num" parameter - * to the push() method. The wait_and_pop() and try_pop() methods - * will only give out the next numbered item. This way several - * workers can work in their own time on different pieces of - * some incoming data, but it all gets serialized properly again - * after the workers have done their work. - */ - template - class SortedQueue { - - typedef typename std::deque::size_type size_type; - - mutable std::mutex m_mutex; - std::deque m_queue; - std::condition_variable m_data_available; - - size_type m_offset; - - // this method expects that we already have the lock - bool empty_intern() const { - return m_queue.front() == T(); - } - - public: - - SortedQueue() : - m_mutex(), - m_queue(1), - m_data_available(), - m_offset(0) { - } - - /** - * Push an item into the queue. - * - * @param value The item to push into the queue. - * @param num Number to describe ordering for the items. - * It must increase monotonically. - */ - void push(T value, size_type num) { - std::lock_guard lock(m_mutex); - - num -= m_offset; - if (m_queue.size() <= num + 1) { - m_queue.resize(num + 2); - } - m_queue[num] = std::move(value); - - m_data_available.notify_one(); - } - - /** - * Wait until the next item becomes available and make it - * available through value. - */ - void wait_and_pop(T& value) { - std::unique_lock lock(m_mutex); - - m_data_available.wait(lock, [this] { - return !empty_intern(); - }); - value = std::move(m_queue.front()); - m_queue.pop_front(); - ++m_offset; - } - - /** - * Get next item if it is available and return true. Or - * return false otherwise. - */ - bool try_pop(T& value) { - std::lock_guard lock(m_mutex); - - if (empty_intern()) { - return false; - } - value = std::move(m_queue.front()); - m_queue.pop_front(); - ++m_offset; - return true; - } - - /** - * The queue is empty. This means try_pop() would fail if called. - * It does not mean that there is nothing on the queue. Because - * the queue is sorted, it could mean that the next item in the - * queue is not available, but other items are. - */ - bool empty() const { - std::lock_guard lock(m_mutex); - - return empty_intern(); - } - - /** - * Returns the number of items in the queue, regardless of whether - * they can be accessed. If this is =0 it - * implies empty()==true, but not the other way around. - */ - size_t size() const { - std::lock_guard lock(m_mutex); - return m_queue.size(); - } - - }; // class SortedQueue - - } // namespace thread - -} // namespace osmium - -#endif // OSMIUM_THREAD_SORTED_QUEUE_HPP diff --git a/include/osmium/thread/util.hpp b/include/osmium/thread/util.hpp index 2ef331a2c..2eeb9997e 100644 --- a/include/osmium/thread/util.hpp +++ b/include/osmium/thread/util.hpp @@ -35,6 +35,8 @@ DEALINGS IN THE SOFTWARE. #include #include +#include +#include #ifdef __linux__ # include diff --git a/include/osmium/util/config.hpp b/include/osmium/util/config.hpp index c40512322..e0412350c 100644 --- a/include/osmium/util/config.hpp +++ b/include/osmium/util/config.hpp @@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #ifdef _MSC_VER # define strcasecmp _stricmp @@ -44,7 +45,7 @@ namespace osmium { namespace config { - inline int get_pool_threads() { + inline int get_pool_threads() noexcept { const char* env = getenv("OSMIUM_POOL_THREADS"); if (env) { return std::atoi(env); @@ -52,7 +53,7 @@ namespace osmium { return 0; } - inline bool use_pool_threads_for_pbf_parsing() { + inline bool use_pool_threads_for_pbf_parsing() noexcept { const char* env = getenv("OSMIUM_USE_POOL_THREADS_FOR_PBF_PARSING"); if (env) { if (!strcasecmp(env, "off") || @@ -65,6 +66,18 @@ namespace osmium { return true; } + inline size_t get_max_queue_size(const char* queue_name, size_t default_value) noexcept { + std::string name {"OSMIUM_MAX_"}; + name += queue_name; + name += "_QUEUE_SIZE"; + const char* env = getenv(name.c_str()); + if (env) { + auto value = std::atoi(env); + return value == 0 ? default_value : value; + } + return default_value; + } + } // namespace config } // namespace osmium diff --git a/include/osmium/util/delta.hpp b/include/osmium/util/delta.hpp index 34c4eb228..8894dd733 100644 --- a/include/osmium/util/delta.hpp +++ b/include/osmium/util/delta.hpp @@ -33,7 +33,7 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include #include #include @@ -118,55 +118,6 @@ namespace osmium { }; // class DeltaDecode - template - class DeltaEncodeIterator : public std::iterator { - - TBaseIterator m_it; - TBaseIterator m_end; - TTransform m_trans; - DeltaEncode m_value; - TDelta m_delta; - - public: - - using value_type = TValue; - using delta_type = TDelta; - - DeltaEncodeIterator(TBaseIterator first, TBaseIterator last, TTransform& trans) : - m_it(first), - m_end(last), - m_trans(trans), - m_value(m_it != m_end ? m_trans(m_it) : 0), - m_delta(static_cast_with_assert(m_value.value())) { - } - - DeltaEncodeIterator& operator++() { - if (++m_it != m_end) { - m_delta = m_value.update(m_trans(m_it)); - } - return *this; - } - - DeltaEncodeIterator operator++(int) { - DeltaEncodeIterator tmp(*this); - operator++(); - return tmp; - } - - TDelta operator*() { - return m_delta; - } - - bool operator==(const DeltaEncodeIterator& rhs) const { - return m_it == rhs.m_it && m_end == rhs.m_end; - } - - bool operator!=(const DeltaEncodeIterator& rhs) const { - return !(*this == rhs); - } - - }; // class DeltaEncodeIterator - } // namespace util } // namespace osmium diff --git a/include/osmium/util/double.hpp b/include/osmium/util/double.hpp index 1352c5fb1..9714bf634 100644 --- a/include/osmium/util/double.hpp +++ b/include/osmium/util/double.hpp @@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE. #include #include -#include #include #include #include @@ -44,7 +43,7 @@ namespace osmium { namespace util { - constexpr int max_double_length = 20; // should fit any double + constexpr const int max_double_length = 20; // should fit any double /** * Write double to iterator, removing superfluous '0' characters at diff --git a/include/osmium/util/file.hpp b/include/osmium/util/file.hpp index 86b93ff72..4c951e7e6 100644 --- a/include/osmium/util/file.hpp +++ b/include/osmium/util/file.hpp @@ -36,6 +36,7 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include #include #include #include @@ -47,9 +48,6 @@ DEALINGS IN THE SOFTWARE. #ifndef _MSC_VER # include -#else -// https://msdn.microsoft.com/en-us/library/whx354w1.aspx -# define ftruncate _chsize_s #endif #include @@ -70,7 +68,7 @@ namespace osmium { #ifdef _MSC_VER // Windows implementation // https://msdn.microsoft.com/en-us/library/dfbc2kec.aspx - auto size = ::_filelengthi64(fd); + const auto size = ::_filelengthi64(fd); if (size == -1L) { throw std::system_error(errno, std::system_category(), "_filelengthi64 failed"); } @@ -85,6 +83,44 @@ namespace osmium { #endif } + /** + * Get file size. + * This is a small wrapper around a system call. + * + * @param name File name + * @returns file size + * @throws std::system_error If system call failed + */ + inline size_t file_size(const char* name) { +#ifdef _MSC_VER + // Windows implementation + // https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx + struct _stat64 s; + if (::_stati64(name, &s) != 0) { + throw std::system_error(errno, std::system_category(), "_stati64 failed"); + } +#else + // Unix implementation + struct stat s; + if (::stat(name, &s) != 0) { + throw std::system_error(errno, std::system_category(), "stat failed"); + } +#endif + return size_t(s.st_size); + } + + /** + * Get file size. + * This is a small wrapper around a system call. + * + * @param name File name + * @returns file size + * @throws std::system_error If system call failed + */ + inline size_t file_size(const std::string& name) { + return file_size(name.c_str()); + } + /** * Resize file. * Small wrapper around ftruncate(2) system call. @@ -94,8 +130,13 @@ namespace osmium { * @throws std::system_error If ftruncate(2) call failed */ inline void resize_file(int fd, size_t new_size) { +#ifdef _WIN32 + // https://msdn.microsoft.com/en-us/library/whx354w1.aspx + if (::_chsize_s(fd, static_cast_with_assert<__int64>(new_size)) != 0) { +#else if (::ftruncate(fd, static_cast_with_assert(new_size)) != 0) { - throw std::system_error(errno, std::system_category(), "ftruncate failed"); +#endif + throw std::system_error(errno, std::system_category(), "resizing file failed"); } } @@ -114,6 +155,37 @@ namespace osmium { #endif } + /** + * Get current offset into file. + * + * @param fd Open file descriptor. + * @returns File offset or 0 if it is not available. + */ + inline size_t file_offset(int fd) { +#ifdef _MSC_VER + // https://msdn.microsoft.com/en-us/library/1yee101t.aspx + auto offset = _lseeki64(fd, 0, SEEK_CUR); +#else + auto offset = ::lseek(fd, 0, SEEK_CUR); +#endif + if (offset == -1) { + return 0; + } + return size_t(offset); + } + + /** + * Check whether the file descriptor refers to a TTY. + */ + inline bool isatty(int fd) { +#ifdef _MSC_VER + // https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx + return _isatty(fd) != 0; +#else + return ::isatty(fd) != 0; +#endif + } + } // namespace util } // namespace osmium diff --git a/include/osmium/util/iterator.hpp b/include/osmium/util/iterator.hpp index 4cef51950..42d23e830 100644 --- a/include/osmium/util/iterator.hpp +++ b/include/osmium/util/iterator.hpp @@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE. */ #include +#include #include namespace osmium { @@ -43,10 +44,10 @@ namespace osmium { using iterator = It; - iterator_range(P&& p) : + explicit iterator_range(P&& p) : P(std::forward

(p)) { } - +/* It begin() { return this->first; } @@ -54,7 +55,7 @@ namespace osmium { It end() { return this->second; } - +*/ It begin() const { return this->first; } @@ -67,7 +68,16 @@ namespace osmium { return begin() == end(); } - }; + }; // struct iterator_range + + /** + * Helper function to create iterator_range from std::pair. + */ + template + inline iterator_range make_range(P&& p) { + static_assert(std::is_same>::value, "make_range needs pair of iterators as argument"); + return iterator_range(std::forward

(p)); + } } // namespace osmium diff --git a/include/osmium/util/memory_mapping.hpp b/include/osmium/util/memory_mapping.hpp index 67e944e70..4fc8f019e 100644 --- a/include/osmium/util/memory_mapping.hpp +++ b/include/osmium/util/memory_mapping.hpp @@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE. #include #include +#include #include #include @@ -126,22 +127,18 @@ private: void make_invalid() noexcept; #ifdef _WIN32 - typedef DWORD flag_type; + using flag_type = DWORD; #else - typedef int flag_type; + using flag_type = int; #endif flag_type get_protection() const noexcept; flag_type get_flags() const noexcept; - // A zero-sized mapping is not allowed by the operating system. - // So if the user asks for a mapping of size 0, we map a full - // page instead. This way we don't have a special case in the rest - // of the code. - static size_t initial_size(size_t size) { + static size_t check_size(size_t size) { if (size == 0) { - return osmium::util::get_pagesize(); + throw std::runtime_error("Zero-sized mapping is not allowed."); } return size; } @@ -218,7 +215,7 @@ private: ~MemoryMapping() noexcept { try { unmap(); - } catch (std::system_error&) { + } catch (const std::system_error&) { // Ignore any exceptions because destructor must not throw. } } @@ -306,7 +303,7 @@ private: public: - AnonymousMemoryMapping(size_t size) : + explicit AnonymousMemoryMapping(size_t size) : MemoryMapping(size, mapping_mode::write_private) { } @@ -342,7 +339,7 @@ private: * @param size Number of objects of type T to be mapped * @throws std::system_error if the mapping fails */ - TypedMemoryMapping(size_t size) : + explicit TypedMemoryMapping(size_t size) : m_mapping(sizeof(T) * size, MemoryMapping::mapping_mode::write_private) { } @@ -491,7 +488,7 @@ private: public: - AnonymousTypedMemoryMapping(size_t size) : + explicit AnonymousTypedMemoryMapping(size_t size) : TypedMemoryMapping(size) { } @@ -550,7 +547,7 @@ inline int osmium::util::MemoryMapping::get_flags() const noexcept { } inline osmium::util::MemoryMapping::MemoryMapping(size_t size, mapping_mode mode, int fd, off_t offset) : - m_size(initial_size(size)), + m_size(check_size(size)), m_offset(offset), m_fd(resize_fd(fd)), m_mapping_mode(mode), @@ -689,7 +686,7 @@ inline void osmium::util::MemoryMapping::make_invalid() noexcept { } inline osmium::util::MemoryMapping::MemoryMapping(size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset) : - m_size(initial_size(size)), + m_size(check_size(size)), m_offset(offset), m_fd(resize_fd(fd)), m_mapping_mode(mode), diff --git a/include/osmium/util/misc.hpp b/include/osmium/util/misc.hpp new file mode 100644 index 000000000..8acecb560 --- /dev/null +++ b/include/osmium/util/misc.hpp @@ -0,0 +1,52 @@ +#ifndef OSMIUM_UTIL_MISC_HPP +#define OSMIUM_UTIL_MISC_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include + +namespace osmium { + + /** + * Like std::tie(), but takes its arguments as const references. Used + * as a helper function when sorting. + */ + template + inline std::tuple + const_tie(const Ts&... args) noexcept { + return std::tuple(args...); + } + +} // namespace osmium + +#endif // OSMIUM_UTIL_MISC_HPP diff --git a/include/osmium/util/options.hpp b/include/osmium/util/options.hpp index 9b00b4811..79818a96a 100644 --- a/include/osmium/util/options.hpp +++ b/include/osmium/util/options.hpp @@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -107,7 +108,7 @@ namespace osmium { * be set to "true". */ void set(std::string data) { - size_t pos = data.find_first_of('='); + const size_t pos = data.find_first_of('='); if (pos == std::string::npos) { m_options[data] = "true"; } else { @@ -122,7 +123,7 @@ namespace osmium { * empty string) is returned. */ std::string get(const std::string& key, const std::string& default_value="") const noexcept { - auto it = m_options.find(key); + const auto it = m_options.find(key); if (it == m_options.end()) { return default_value; } @@ -134,7 +135,7 @@ namespace osmium { * Will return false if the value is unset. */ bool is_true(const std::string& key) const noexcept { - std::string value = get(key); + const std::string value = get(key); return (value == "true" || value == "yes"); } @@ -143,7 +144,7 @@ namespace osmium { * Will return true if the value is unset. */ bool is_not_false(const std::string& key) const noexcept { - std::string value = get(key); + const std::string value = get(key); return !(value == "false" || value == "no"); } diff --git a/include/osmium/util/progress_bar.hpp b/include/osmium/util/progress_bar.hpp new file mode 100644 index 000000000..814aa2c0e --- /dev/null +++ b/include/osmium/util/progress_bar.hpp @@ -0,0 +1,179 @@ +#ifndef OSMIUM_UTIL_PROGRESS_BAR_HPP +#define OSMIUM_UTIL_PROGRESS_BAR_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include + +namespace osmium { + + /** + * Displays a progress bar on STDERR. Can be used together with the + * osmium::io::Reader class for instance. + */ + class ProgressBar { + + static const char* bar() noexcept { + return "======================================================================"; + } + + static const char* spc() noexcept { + return " "; + } + + static constexpr const size_t length = 70; + + // The max size is the file size if there is a single file and the + // sum of all file sizes if there are multiple files. It corresponds + // to 100%. + size_t m_max_size; + + // The sum of the file sizes already done. + size_t m_done_size = 0; + + // The currently read size in the current file. + size_t m_current_size = 0; + + // The percentage calculated when it was last displayed. Used to decide + // whether we need to update the display. Start setting is one that + // will always be different from any legal setting. + size_t m_prev_percent = 100 + 1; + + // Is the progress bar enabled at all? + bool m_enable; + + // Used to make sure we do cleanup in the destructor if it was not + // already done. + bool m_do_cleanup = true; + + void display() { + const size_t percent = 100 * (m_done_size + m_current_size) / m_max_size; + if (m_prev_percent == percent) { + return; + } + m_prev_percent = percent; + + const size_t num = size_t(percent * (length / 100.0)); + std::cerr << '['; + if (num >= length) { + std::cerr << bar(); + } else { + std::cerr << (bar() + length - num) << '>' << (spc() + num); + } + std::cerr << "] "; + if (percent < 10) { + std::cerr << ' '; + } + if (percent < 100) { + std::cerr << ' '; + } + std::cerr << percent << "% \r"; + } + + public: + + /** + * Initializes the progress bar. No output yet. + * + * @param max_size Max size equivalent to 100%. + * @param enable Set to false to disable (for instance if stderr is + * not a TTY). + */ + ProgressBar(size_t max_size, bool enable) noexcept : + m_max_size(max_size), + m_enable(max_size > 0 && enable) { + } + + ~ProgressBar() { + if (m_do_cleanup) { + try { + done(); + } catch (...) { + // Swallow any exceptions, because a destructor should + // not throw. + } + } + } + + /** + * Call this function to update the progress bar. Actual update will + * only happen if the percentage changed from the last time this + * function was called. + * + * @param current_size Current size. Used together with the max_size + * from constructor to calculate the percentage. + */ + void update(size_t current_size) { + if (!m_enable) { + return; + } + + m_current_size = current_size; + + display(); + } + + /** + * If you are reading multiple files, call this function after each + * file is finished. + * + * @param file_size The size of the file just finished. + */ + void file_done(size_t file_size) { + if (m_enable) { + m_done_size += file_size; + m_current_size = 0; + display(); + } + } + + /** + * Call this at the end. Will update the progress bar to 100% and + * print a final line feed. If this is not called explicitly the + * destructor will also call this. + */ + void done() { + m_do_cleanup = false; + if (m_enable) { + m_done_size = m_max_size; + m_current_size = 0; + display(); + std::cerr << '\n'; + } + } + + }; // class ProgressBar + +} // namespace osmium + +#endif // OSMIUM_UTIL_PROGRESS_BAR_HPP diff --git a/include/osmium/util/string.hpp b/include/osmium/util/string.hpp index 1198288c0..2cdb9830e 100644 --- a/include/osmium/util/string.hpp +++ b/include/osmium/util/string.hpp @@ -33,9 +33,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include -#include namespace osmium { diff --git a/include/osmium/util/timer.hpp b/include/osmium/util/timer.hpp new file mode 100644 index 000000000..8cae80add --- /dev/null +++ b/include/osmium/util/timer.hpp @@ -0,0 +1,98 @@ +#ifndef OSMIUM_UTIL_TIMER_HPP +#define OSMIUM_UTIL_TIMER_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#include + +#ifdef OSMIUM_WITH_TIMER + +#include + +namespace osmium { + + class Timer { + + using clock = std::chrono::high_resolution_clock; + std::chrono::time_point m_start; + std::chrono::time_point m_stop; + + public: + + Timer() : + m_start(clock::now()) { + } + + void start() { + m_start = clock::now(); + } + + void stop() { + m_stop = clock::now(); + } + + int64_t elapsed_microseconds() const { + return std::chrono::duration_cast(m_stop - m_start).count(); + } + + }; + +} // namespace osmium + +#else + +namespace osmium { + + class Timer { + + public: + + Timer() = default; + + void start() { + } + + void stop() { + } + + int64_t elapsed_microseconds() const { + return 0; + } + + }; + +} // namespace osmium + +#endif + +#endif // OSMIUM_UTIL_TIMER_HPP diff --git a/include/osmium/util/verbose_output.hpp b/include/osmium/util/verbose_output.hpp index c7677a436..f85d2654e 100644 --- a/include/osmium/util/verbose_output.hpp +++ b/include/osmium/util/verbose_output.hpp @@ -33,11 +33,11 @@ DEALINGS IN THE SOFTWARE. */ -#include - +#include #include #include #include +#include namespace osmium { @@ -75,9 +75,9 @@ namespace osmium { */ void start_line() { if (m_newline) { - time_t elapsed = runtime(); + const time_t elapsed = runtime(); - char old_fill = std::cerr.fill(); + const char old_fill = std::cerr.fill(); std::cerr << '[' << std::setw(2) << (elapsed / 60) << ':' << std::setw(2) << std::setfill('0') << (elapsed % 60) << "] "; std::cerr.fill(old_fill); diff --git a/include/osmium/version.hpp b/include/osmium/version.hpp new file mode 100644 index 000000000..6f3b0a368 --- /dev/null +++ b/include/osmium/version.hpp @@ -0,0 +1,42 @@ +#ifndef OSMIUM_VERSION_HPP +#define OSMIUM_VERSION_HPP + +/* + +This file is part of Osmium (http://osmcode.org/libosmium). + +Copyright 2013-2016 Jochen Topf and others (see README). + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +*/ + +#define LIBOSMIUM_VERSION_MAJOR 2 +#define LIBOSMIUM_VERSION_MINOR 9 +#define LIBOSMIUM_VERSION_PATCH 0 + +#define LIBOSMIUM_VERSION_STRING "2.9.0" + +#endif // OSMIUM_VERSION_HPP diff --git a/include/protozero/byteswap.hpp b/include/protozero/byteswap.hpp index a018c1c17..06ba6ded9 100644 --- a/include/protozero/byteswap.hpp +++ b/include/protozero/byteswap.hpp @@ -28,7 +28,7 @@ namespace protozero { * be specialized to actually work. */ template -inline void byteswap(const char* /*data*/, char* /*result*/) { +inline void byteswap(const char* /*data*/, char* /*result*/) noexcept { static_assert(N == 1, "Can only swap 4 or 8 byte values"); } @@ -36,7 +36,7 @@ inline void byteswap(const char* /*data*/, char* /*result*/) { * Swap 4 byte value (int32_t, uint32_t, float) between endianness formats. */ template <> -inline void byteswap<4>(const char* data, char* result) { +inline void byteswap<4>(const char* data, char* result) noexcept { #ifdef PROTOZERO_USE_BUILTIN_BSWAP *reinterpret_cast(result) = __builtin_bswap32(*reinterpret_cast(data)); #else @@ -51,7 +51,7 @@ inline void byteswap<4>(const char* data, char* result) { * Swap 8 byte value (int64_t, uint64_t, double) between endianness formats. */ template <> -inline void byteswap<8>(const char* data, char* result) { +inline void byteswap<8>(const char* data, char* result) noexcept { #ifdef PROTOZERO_USE_BUILTIN_BSWAP *reinterpret_cast(result) = __builtin_bswap64(*reinterpret_cast(data)); #else diff --git a/include/protozero/exception.hpp b/include/protozero/exception.hpp index 5c7ab5478..ca4340e90 100644 --- a/include/protozero/exception.hpp +++ b/include/protozero/exception.hpp @@ -29,7 +29,7 @@ namespace protozero { */ struct exception : std::exception { /// Returns the explanatory string. - const char *what() const noexcept override { return "pbf exception"; } + const char* what() const noexcept override { return "pbf exception"; } }; /** @@ -38,7 +38,7 @@ struct exception : std::exception { */ struct varint_too_long_exception : exception { /// Returns the explanatory string. - const char *what() const noexcept override { return "varint too long exception"; } + const char* what() const noexcept override { return "varint too long exception"; } }; /** @@ -47,7 +47,7 @@ struct varint_too_long_exception : exception { */ struct unknown_pbf_wire_type_exception : exception { /// Returns the explanatory string. - const char *what() const noexcept override { return "unknown pbf field type exception"; } + const char* what() const noexcept override { return "unknown pbf field type exception"; } }; /** @@ -60,7 +60,7 @@ struct unknown_pbf_wire_type_exception : exception { */ struct end_of_buffer_exception : exception { /// Returns the explanatory string. - const char *what() const noexcept override { return "end of buffer exception"; } + const char* what() const noexcept override { return "end of buffer exception"; } }; } // end namespace protozero diff --git a/include/protozero/iterators.hpp b/include/protozero/iterators.hpp new file mode 100644 index 000000000..00ba91935 --- /dev/null +++ b/include/protozero/iterators.hpp @@ -0,0 +1,373 @@ +#ifndef PROTOZERO_ITERATORS_HPP +#define PROTOZERO_ITERATORS_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file iterators.hpp + * + * @brief Contains the iterators for access to packed repeated fields. + */ + +#include +#include +#include + +#include +#include + +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN +# include +#endif + +namespace protozero { + +namespace detail { + + // Copy N bytes from src to dest on little endian machines, on big + // endian swap the bytes in the process. + template + inline void copy_or_byteswap(const char* src, void* dest) noexcept { +#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN + std::memcpy(dest, src, N); +#else + byteswap(src, reinterpret_cast(dest)); +#endif + } + +} // end namespace detail + +/** + * A range of iterators based on std::pair. Created from beginning and + * end iterators. Used as a return type from some pbf_reader methods + * that is easy to use with range-based for loops. + */ +template > +class iterator_range : +#ifdef PROTOZERO_STRICT_API + protected +#else + public +#endif + P { + +public: + + /// The type of the iterators in this range. + using iterator = T; + + /// The value type of the underlying iterator. + using value_type = typename std::iterator_traits::value_type; + + /** + * Default constructor. Create empty iterator_range. + */ + constexpr iterator_range() : + P(iterator{}, iterator{}) { + } + + /** + * Create iterator range from two iterators. + * + * @param first_iterator Iterator to beginning or range. + * @param last_iterator Iterator to end or range. + */ + constexpr iterator_range(iterator&& first_iterator, iterator&& last_iterator) : + P(std::forward(first_iterator), + std::forward(last_iterator)) { + } + + /// Return iterator to beginning of range. + constexpr iterator begin() const noexcept { + return this->first; + } + + /// Return iterator to end of range. + constexpr iterator end() const noexcept { + return this->second; + } + + /// Return iterator to beginning of range. + constexpr iterator cbegin() const noexcept { + return this->first; + } + + /// Return iterator to end of range. + constexpr iterator cend() const noexcept { + return this->second; + } + + /// Return true if this range is empty. + constexpr std::size_t empty() const noexcept { + return begin() == end(); + } + + /** + * Get element at the beginning of the range. + * + * @pre Range must not be empty. + */ + value_type front() const { + protozero_assert(!empty()); + return *(this->first); + } + + /** + * Advance beginning of range by one. + * + * @pre Range must not be empty. + */ + void drop_front() { + protozero_assert(!empty()); + ++this->first; + } + + /** + * Swap the contents of this range with the other. + * + * @param other Other range to swap data with. + */ + void swap(iterator_range& other) noexcept { + using std::swap; + swap(this->first, other.first); + swap(this->second, other.second); + } + +}; // struct iterator_range + +/** + * Swap two iterator_ranges. + * + * @param lhs First range. + * @param rhs Second range. + */ +template +inline void swap(iterator_range& lhs, iterator_range& rhs) noexcept { + lhs.swap(rhs); +} + +#ifdef PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED + +template +using const_fixed_iterator = const T*; + +/** + * Create iterator_range from char pointers to beginning and end of range. + * + * @param first Beginning of range. + * @param last End of range. + */ +template +inline iterator_range> create_fixed_iterator_range(const char* first, const char* last) { + return iterator_range>{reinterpret_cast(first), + reinterpret_cast(last)}; +} + +#else + +/** + * A forward iterator used for accessing packed repeated fields of fixed + * length (fixed32, sfixed32, float, double). + */ +template +class const_fixed_iterator { + + /// Pointer to current iterator position + const char* m_data; + + /// Pointer to end iterator position + const char* m_end; + +public: + + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + const_fixed_iterator() noexcept : + m_data(nullptr), + m_end(nullptr) { + } + + const_fixed_iterator(const char* data, const char* end) noexcept : + m_data(data), + m_end(end) { + } + + const_fixed_iterator(const const_fixed_iterator&) noexcept = default; + const_fixed_iterator(const_fixed_iterator&&) noexcept = default; + + const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default; + const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default; + + ~const_fixed_iterator() noexcept = default; + + value_type operator*() const { + value_type result; + detail::copy_or_byteswap(m_data , &result); + return result; + } + + const_fixed_iterator& operator++() { + m_data += sizeof(value_type); + return *this; + } + + const_fixed_iterator operator++(int) { + const const_fixed_iterator tmp(*this); + ++(*this); + return tmp; + } + + bool operator==(const const_fixed_iterator& rhs) const noexcept { + return m_data == rhs.m_data && m_end == rhs.m_end; + } + + bool operator!=(const const_fixed_iterator& rhs) const noexcept { + return !(*this == rhs); + } + +}; // class const_fixed_iterator + +/** + * Create iterator_range from char pointers to beginning and end of range. + * + * @param first Beginning of range. + * @param last End of range. + */ +template +inline iterator_range> create_fixed_iterator_range(const char* first, const char* last) { + return iterator_range>{const_fixed_iterator(first, last), + const_fixed_iterator(last, last)}; +} + +#endif + +/** + * A forward iterator used for accessing packed repeated varint fields + * (int32, uint32, int64, uint64, bool, enum). + */ +template +class const_varint_iterator { + +protected: + + /// Pointer to current iterator position + const char* m_data; + + /// Pointer to end iterator position + const char* m_end; + +public: + + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + const_varint_iterator() noexcept : + m_data(nullptr), + m_end(nullptr) { + } + + const_varint_iterator(const char* data, const char* end) noexcept : + m_data(data), + m_end(end) { + } + + const_varint_iterator(const const_varint_iterator&) noexcept = default; + const_varint_iterator(const_varint_iterator&&) noexcept = default; + + const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default; + const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default; + + ~const_varint_iterator() noexcept = default; + + value_type operator*() const { + const char* d = m_data; // will be thrown away + return static_cast(decode_varint(&d, m_end)); + } + + const_varint_iterator& operator++() { + skip_varint(&m_data, m_end); + return *this; + } + + const_varint_iterator operator++(int) { + const const_varint_iterator tmp(*this); + ++(*this); + return tmp; + } + + bool operator==(const const_varint_iterator& rhs) const noexcept { + return m_data == rhs.m_data && m_end == rhs.m_end; + } + + bool operator!=(const const_varint_iterator& rhs) const noexcept { + return !(*this == rhs); + } + +}; // class const_varint_iterator + +/** + * A forward iterator used for accessing packed repeated svarint fields + * (sint32, sint64). + */ +template +class const_svarint_iterator : public const_varint_iterator { + +public: + + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + const_svarint_iterator() noexcept : + const_varint_iterator() { + } + + const_svarint_iterator(const char* data, const char* end) noexcept : + const_varint_iterator(data, end) { + } + + const_svarint_iterator(const const_svarint_iterator&) = default; + const_svarint_iterator(const_svarint_iterator&&) = default; + + const_svarint_iterator& operator=(const const_svarint_iterator&) = default; + const_svarint_iterator& operator=(const_svarint_iterator&&) = default; + + ~const_svarint_iterator() = default; + + value_type operator*() const { + const char* d = this->m_data; // will be thrown away + return static_cast(decode_zigzag64(decode_varint(&d, this->m_end))); + } + + const_svarint_iterator& operator++() { + skip_varint(&this->m_data, this->m_end); + return *this; + } + + const_svarint_iterator operator++(int) { + const const_svarint_iterator tmp(*this); + ++(*this); + return tmp; + } + +}; // class const_svarint_iterator + +} // end namespace protozero + +#endif // PROTOZERO_ITERATORS_HPP diff --git a/include/protozero/pbf_builder.hpp b/include/protozero/pbf_builder.hpp index 548f4cecb..39af53f3f 100644 --- a/include/protozero/pbf_builder.hpp +++ b/include/protozero/pbf_builder.hpp @@ -57,7 +57,7 @@ public: /// @cond INTERNAL #define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \ - inline void add_##name(T tag, type value) { \ + void add_##name(T tag, type value) { \ pbf_writer::add_##name(pbf_tag_type(tag), value); \ } @@ -79,38 +79,38 @@ public: #undef PROTOZERO_WRITER_WRAP_ADD_SCALAR /// @endcond - inline void add_bytes(T tag, const char* value, std::size_t size) { + void add_bytes(T tag, const char* value, std::size_t size) { pbf_writer::add_bytes(pbf_tag_type(tag), value, size); } - inline void add_bytes(T tag, const std::string& value) { + void add_bytes(T tag, const std::string& value) { pbf_writer::add_bytes(pbf_tag_type(tag), value); } - inline void add_string(T tag, const char* value, std::size_t size) { + void add_string(T tag, const char* value, std::size_t size) { pbf_writer::add_string(pbf_tag_type(tag), value, size); } - inline void add_string(T tag, const std::string& value) { + void add_string(T tag, const std::string& value) { pbf_writer::add_string(pbf_tag_type(tag), value); } - inline void add_string(T tag, const char* value) { + void add_string(T tag, const char* value) { pbf_writer::add_string(pbf_tag_type(tag), value); } - inline void add_message(T tag, const char* value, std::size_t size) { + void add_message(T tag, const char* value, std::size_t size) { pbf_writer::add_message(pbf_tag_type(tag), value, size); } - inline void add_message(T tag, const std::string& value) { + void add_message(T tag, const std::string& value) { pbf_writer::add_message(pbf_tag_type(tag), value); } /// @cond INTERNAL #define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \ template \ - inline void add_packed_##name(T tag, InputIterator first, InputIterator last) { \ + void add_packed_##name(T tag, InputIterator first, InputIterator last) { \ pbf_writer::add_packed_##name(pbf_tag_type(tag), first, last); \ } @@ -132,7 +132,7 @@ public: #undef PROTOZERO_WRITER_WRAP_ADD_PACKED /// @endcond -}; +}; // class pbf_builder } // end namespace protozero diff --git a/include/protozero/pbf_message.hpp b/include/protozero/pbf_message.hpp index 45f01c149..055773408 100644 --- a/include/protozero/pbf_message.hpp +++ b/include/protozero/pbf_message.hpp @@ -13,7 +13,7 @@ documentation. /** * @file pbf_message.hpp * - * @brief Contains the pbf_message class. + * @brief Contains the pbf_message template class. */ #include @@ -75,19 +75,19 @@ public: pbf_reader(std::forward(args)...) { } - inline bool next() { + bool next() { return pbf_reader::next(); } - inline bool next(T tag) { + bool next(T tag) { return pbf_reader::next(pbf_tag_type(tag)); } - inline T tag() const noexcept { + T tag() const noexcept { return T(pbf_reader::tag()); } -}; +}; // class pbf_message } // end namespace protozero diff --git a/include/protozero/pbf_reader.hpp b/include/protozero/pbf_reader.hpp index 58b388455..2f4054d2d 100644 --- a/include/protozero/pbf_reader.hpp +++ b/include/protozero/pbf_reader.hpp @@ -18,13 +18,12 @@ documentation. #include #include -#include -#include #include #include #include #include +#include #include #include @@ -55,16 +54,16 @@ namespace protozero { * * All methods of the pbf_reader class except get_bytes() and get_string() * provide the strong exception guarantee, ie they either succeed or do not - * change the pbf_reader object they are called on. Use the get_data() method + * change the pbf_reader object they are called on. Use the get_view() method * instead of get_bytes() or get_string(), if you need this guarantee. */ class pbf_reader { // A pointer to the next unread data. - const char *m_data = nullptr; + const char* m_data = nullptr; // A pointer to one past the end of data. - const char *m_end = nullptr; + const char* m_end = nullptr; // The wire type of the current field. pbf_wire_type m_wire_type = pbf_wire_type::unknown; @@ -72,128 +71,82 @@ class pbf_reader { // The tag of the current field. pbf_tag_type m_tag = 0; - // Copy N bytes from src to dest on little endian machines, on big endian - // swap the bytes in the process. - template - static void copy_or_byteswap(const char* src, void* dest) noexcept { -#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN - memcpy(dest, src, N); -#else - byteswap(src, reinterpret_cast(dest)); -#endif - } - template - inline T get_fixed() { + T get_fixed() { T result; skip_bytes(sizeof(T)); - copy_or_byteswap(m_data - sizeof(T), &result); + detail::copy_or_byteswap(m_data - sizeof(T), &result); return result; } -#ifdef PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED - template - using const_fixed_iterator = const T*; - - template - inline std::pair, const_fixed_iterator> create_fixed_iterator_pair(const char* first, const char* last) { - return std::make_pair(reinterpret_cast(first), - reinterpret_cast(last)); - } - -#else - - template - class const_fixed_iterator : public std::iterator { - - const char* m_data; - const char* m_end; - - public: - - const_fixed_iterator() noexcept : - m_data(nullptr), - m_end(nullptr) { - } - - const_fixed_iterator(const char *data, const char* end) noexcept : - m_data(data), - m_end(end) { - } - - const_fixed_iterator(const const_fixed_iterator&) noexcept = default; - const_fixed_iterator(const_fixed_iterator&&) noexcept = default; - - const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default; - const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default; - - ~const_fixed_iterator() noexcept = default; - - T operator*() { - T result; - copy_or_byteswap(m_data , &result); - return result; - } - - const_fixed_iterator& operator++() { - m_data += sizeof(T); - return *this; - } - - const_fixed_iterator operator++(int) { - const const_fixed_iterator tmp(*this); - ++(*this); - return tmp; - } - - bool operator==(const const_fixed_iterator& rhs) const noexcept { - return m_data == rhs.m_data && m_end == rhs.m_end; - } - - bool operator!=(const const_fixed_iterator& rhs) const noexcept { - return !(*this == rhs); - } - - }; // class const_fixed_iterator - - template - inline std::pair, const_fixed_iterator> create_fixed_iterator_pair(const char* first, const char* last) { - return std::make_pair(const_fixed_iterator(first, last), - const_fixed_iterator(last, last)); - } - -#endif - - template - inline std::pair, const_fixed_iterator> packed_fixed() { + iterator_range> packed_fixed() { protozero_assert(tag() != 0 && "call next() before accessing field value"); - auto len = get_len_and_skip(); + const auto len = get_len_and_skip(); protozero_assert(len % sizeof(T) == 0); - return create_fixed_iterator_pair(m_data-len, m_data); + return create_fixed_iterator_range(m_data - len, m_data); } - template inline T get_varint(); - template inline T get_svarint(); + template + T get_varint() { + return static_cast(decode_varint(&m_data, m_end)); + } - inline pbf_length_type get_length() { return get_varint(); } + template + T get_svarint() { + protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint"); + return static_cast(decode_zigzag64(decode_varint(&m_data, m_end))); + } - inline void skip_bytes(pbf_length_type len); + pbf_length_type get_length() { + return get_varint(); + } - inline pbf_length_type get_len_and_skip(); + void skip_bytes(pbf_length_type len) { + if (m_data + len > m_end) { + throw end_of_buffer_exception(); + } + m_data += len; + + // In debug builds reset the tag to zero so that we can detect (some) + // wrong code. +#ifndef NDEBUG + m_tag = 0; +#endif + } + + pbf_length_type get_len_and_skip() { + const auto len = get_length(); + skip_bytes(len); + return len; + } + + template + iterator_range get_packed() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + const auto len = get_len_and_skip(); + return iterator_range{T{m_data - len, m_data}, + T{m_data, m_data}}; + } public: /** - * Construct a pbf_reader message from a data pointer and a length. The pointer - * will be stored inside the pbf_reader object, no data is copied. So you must - * make sure the buffer stays valid as long as the pbf_reader object is used. + * Construct a pbf_reader message from a data_view. The pointer from the + * data_view will be stored inside the pbf_reader object, no data is + * copied. So you must* make sure the view stays valid as long as the + * pbf_reader object is used. * * The buffer must contain a complete protobuf message. * * @post There is no current field. */ - inline pbf_reader(const char *data, std::size_t length) noexcept; + explicit pbf_reader(const data_view& view) noexcept + : m_data(view.data()), + m_end(view.data() + view.size()), + m_wire_type(pbf_wire_type::unknown), + m_tag(0) { + } /** * Construct a pbf_reader message from a data pointer and a length. The pointer @@ -204,7 +157,28 @@ public: * * @post There is no current field. */ - inline pbf_reader(std::pair data) noexcept; + pbf_reader(const char* data, std::size_t length) noexcept + : m_data(data), + m_end(data + length), + m_wire_type(pbf_wire_type::unknown), + m_tag(0) { + } + + /** + * Construct a pbf_reader message from a data pointer and a length. The pointer + * will be stored inside the pbf_reader object, no data is copied. So you must + * make sure the buffer stays valid as long as the pbf_reader object is used. + * + * The buffer must contain a complete protobuf message. + * + * @post There is no current field. + */ + pbf_reader(std::pair data) noexcept + : m_data(data.first), + m_end(data.first + data.second), + m_wire_type(pbf_wire_type::unknown), + m_tag(0) { + } /** * Construct a pbf_reader message from a std::string. A pointer to the string @@ -216,33 +190,53 @@ public: * * @post There is no current field. */ - inline pbf_reader(const std::string& data) noexcept; + pbf_reader(const std::string& data) noexcept + : m_data(data.data()), + m_end(data.data() + data.size()), + m_wire_type(pbf_wire_type::unknown), + m_tag(0) { + } /** * pbf_reader can be default constructed and behaves like it has an empty * buffer. */ - inline pbf_reader() noexcept = default; + pbf_reader() noexcept = default; /// pbf_reader messages can be copied trivially. - inline pbf_reader(const pbf_reader&) noexcept = default; + pbf_reader(const pbf_reader&) noexcept = default; /// pbf_reader messages can be moved trivially. - inline pbf_reader(pbf_reader&&) noexcept = default; + pbf_reader(pbf_reader&&) noexcept = default; /// pbf_reader messages can be copied trivially. - inline pbf_reader& operator=(const pbf_reader& other) noexcept = default; + pbf_reader& operator=(const pbf_reader& other) noexcept = default; /// pbf_reader messages can be moved trivially. - inline pbf_reader& operator=(pbf_reader&& other) noexcept = default; + pbf_reader& operator=(pbf_reader&& other) noexcept = default; - inline ~pbf_reader() = default; + ~pbf_reader() = default; + + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(pbf_reader& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_end, other.m_end); + swap(m_wire_type, other.m_wire_type); + swap(m_tag, other.m_tag); + } /** * In a boolean context the pbf_reader class evaluates to `true` if there are * still fields available and to `false` if the last field has been read. */ - inline operator bool() const noexcept; + operator bool() const noexcept { + return m_data < m_end; + } /** * Return the length in bytes of the current message. If you have @@ -272,7 +266,31 @@ public: * @pre There must be no current field. * @post If it returns `true` there is a current field now. */ - inline bool next(); + bool next() { + if (m_data == m_end) { + return false; + } + + const auto value = get_varint(); + m_tag = pbf_tag_type(value >> 3); + + // tags 0 and 19000 to 19999 are not allowed as per + // https://developers.google.com/protocol-buffers/docs/proto + protozero_assert(((m_tag > 0 && m_tag < 19000) || (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range"); + + m_wire_type = pbf_wire_type(value & 0x07); + switch (m_wire_type) { + case pbf_wire_type::varint: + case pbf_wire_type::fixed64: + case pbf_wire_type::length_delimited: + case pbf_wire_type::fixed32: + break; + default: + throw unknown_pbf_wire_type_exception(); + } + + return true; + } /** * Set next field with given tag in the message as the current field. @@ -299,7 +317,16 @@ public: * @pre There must be no current field. * @post If it returns `true` there is a current field now with the given tag. */ - inline bool next(pbf_tag_type tag); + bool next(pbf_tag_type tag) { + while (next()) { + if (m_tag == tag) { + return true; + } else { + skip(); + } + } + return false; + } /** * The tag of the current field. The tag is the field number from the @@ -310,7 +337,9 @@ public: * @returns tag of the current field. * @pre There must be a current field (ie. next() must have returned `true`). */ - inline pbf_tag_type tag() const noexcept; + pbf_tag_type tag() const noexcept { + return m_tag; + } /** * Get the wire type of the current field. The wire types are: @@ -327,7 +356,9 @@ public: * @returns wire type of the current field. * @pre There must be a current field (ie. next() must have returned `true`). */ - inline pbf_wire_type wire_type() const noexcept; + pbf_wire_type wire_type() const noexcept { + return m_wire_type; + } /** * Check the wire type of the current field. @@ -335,7 +366,9 @@ public: * @returns `true` if the current field has the given wire type. * @pre There must be a current field (ie. next() must have returned `true`). */ - inline bool has_wire_type(pbf_wire_type type) const noexcept; + bool has_wire_type(pbf_wire_type type) const noexcept { + return wire_type() == type; + } /** * Consume the current field. @@ -343,7 +376,25 @@ public: * @pre There must be a current field (ie. next() must have returned `true`). * @post The current field was consumed and there is no current field now. */ - inline void skip(); + void skip() { + protozero_assert(tag() != 0 && "call next() before calling skip()"); + switch (wire_type()) { + case pbf_wire_type::varint: + skip_varint(&m_data, m_end); + break; + case pbf_wire_type::fixed64: + skip_bytes(8); + break; + case pbf_wire_type::length_delimited: + skip_bytes(get_length()); + break; + case pbf_wire_type::fixed32: + skip_bytes(4); + break; + default: + protozero_assert(false && "can not be here because next() should have thrown already"); + } + } ///@{ /** @@ -357,7 +408,13 @@ public: * @pre The current field must be of type "bool". * @post The current field was consumed and there is no current field now. */ - inline bool get_bool(); + bool get_bool() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint"); + skip_bytes(1); + return m_data[-1] != 0; // -1 okay because we incremented m_data the line before + } /** * Consume and return value of current "enum" field. @@ -366,7 +423,7 @@ public: * @pre The current field must be of type "enum". * @post The current field was consumed and there is no current field now. */ - inline int32_t get_enum() { + int32_t get_enum() { protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); return get_varint(); } @@ -378,7 +435,7 @@ public: * @pre The current field must be of type "int32". * @post The current field was consumed and there is no current field now. */ - inline int32_t get_int32() { + int32_t get_int32() { protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); return get_varint(); } @@ -390,7 +447,7 @@ public: * @pre The current field must be of type "sint32". * @post The current field was consumed and there is no current field now. */ - inline int32_t get_sint32() { + int32_t get_sint32() { protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); return get_svarint(); } @@ -402,7 +459,7 @@ public: * @pre The current field must be of type "uint32". * @post The current field was consumed and there is no current field now. */ - inline uint32_t get_uint32() { + uint32_t get_uint32() { protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); return get_varint(); } @@ -414,7 +471,7 @@ public: * @pre The current field must be of type "int64". * @post The current field was consumed and there is no current field now. */ - inline int64_t get_int64() { + int64_t get_int64() { protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); return get_varint(); } @@ -426,7 +483,7 @@ public: * @pre The current field must be of type "sint64". * @post The current field was consumed and there is no current field now. */ - inline int64_t get_sint64() { + int64_t get_sint64() { protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); return get_svarint(); } @@ -438,7 +495,7 @@ public: * @pre The current field must be of type "uint64". * @post The current field was consumed and there is no current field now. */ - inline uint64_t get_uint64() { + uint64_t get_uint64() { protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); return get_varint(); } @@ -450,7 +507,11 @@ public: * @pre The current field must be of type "fixed32". * @post The current field was consumed and there is no current field now. */ - inline uint32_t get_fixed32(); + uint32_t get_fixed32() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); + return get_fixed(); + } /** * Consume and return value of current "sfixed32" field. @@ -459,7 +520,11 @@ public: * @pre The current field must be of type "sfixed32". * @post The current field was consumed and there is no current field now. */ - inline int32_t get_sfixed32(); + int32_t get_sfixed32() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); + return get_fixed(); + } /** * Consume and return value of current "fixed64" field. @@ -468,7 +533,11 @@ public: * @pre The current field must be of type "fixed64". * @post The current field was consumed and there is no current field now. */ - inline uint64_t get_fixed64(); + uint64_t get_fixed64() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); + return get_fixed(); + } /** * Consume and return value of current "sfixed64" field. @@ -477,7 +546,11 @@ public: * @pre The current field must be of type "sfixed64". * @post The current field was consumed and there is no current field now. */ - inline int64_t get_sfixed64(); + int64_t get_sfixed64() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); + return get_fixed(); + } /** * Consume and return value of current "float" field. @@ -486,7 +559,11 @@ public: * @pre The current field must be of type "float". * @post The current field was consumed and there is no current field now. */ - inline float get_float(); + float get_float() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); + return get_fixed(); + } /** * Consume and return value of current "double" field. @@ -495,8 +572,29 @@ public: * @pre The current field must be of type "double". * @post The current field was consumed and there is no current field now. */ - inline double get_double(); + double get_double() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); + return get_fixed(); + } + /** + * Consume and return value of current "bytes", "string", or "message" + * field. + * + * @returns A data_view object. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "bytes", "string", or "message". + * @post The current field was consumed and there is no current field now. + */ + data_view get_view() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message"); + const auto len = get_len_and_skip(); + return data_view{m_data-len, len}; + } + +#ifndef PROTOZERO_STRICT_API /** * Consume and return value of current "bytes" or "string" field. * @@ -505,7 +603,13 @@ public: * @pre The current field must be of type "bytes" or "string". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_data(); + std::pair get_data() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message"); + const auto len = get_len_and_skip(); + return std::make_pair(m_data-len, len); + } +#endif /** * Consume and return value of current "bytes" field. @@ -514,7 +618,9 @@ public: * @pre The current field must be of type "bytes". * @post The current field was consumed and there is no current field now. */ - inline std::string get_bytes(); + std::string get_bytes() { + return std::string(get_view()); + } /** * Consume and return value of current "string" field. @@ -523,7 +629,9 @@ public: * @pre The current field must be of type "string". * @post The current field was consumed and there is no current field now. */ - inline std::string get_string(); + std::string get_string() { + return std::string(get_view()); + } /** * Consume and return value of current "message" field. @@ -532,136 +640,35 @@ public: * @pre The current field must be of type "message". * @post The current field was consumed and there is no current field now. */ - inline pbf_reader get_message() { - return pbf_reader(get_data()); + pbf_reader get_message() { + return pbf_reader(get_view()); } ///@} -private: - - template - class const_varint_iterator : public std::iterator { - - protected: - - const char* m_data; - const char* m_end; - - public: - - const_varint_iterator() noexcept : - m_data(nullptr), - m_end(nullptr) { - } - - const_varint_iterator(const char *data, const char* end) noexcept : - m_data(data), - m_end(end) { - } - - const_varint_iterator(const const_varint_iterator&) noexcept = default; - const_varint_iterator(const_varint_iterator&&) noexcept = default; - - const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default; - const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default; - - ~const_varint_iterator() noexcept = default; - - T operator*() { - const char* d = m_data; // will be thrown away - return static_cast(decode_varint(&d, m_end)); - } - - const_varint_iterator& operator++() { - // Ignore the result, we call decode_varint() just for the - // side-effect of updating m_data. - decode_varint(&m_data, m_end); - return *this; - } - - const_varint_iterator operator++(int) { - const const_varint_iterator tmp(*this); - ++(*this); - return tmp; - } - - bool operator==(const const_varint_iterator& rhs) const noexcept { - return m_data == rhs.m_data && m_end == rhs.m_end; - } - - bool operator!=(const const_varint_iterator& rhs) const noexcept { - return !(*this == rhs); - } - - }; // class const_varint_iterator - - template - class const_svarint_iterator : public const_varint_iterator { - - public: - - const_svarint_iterator() noexcept : - const_varint_iterator() { - } - - const_svarint_iterator(const char *data, const char* end) noexcept : - const_varint_iterator(data, end) { - } - - const_svarint_iterator(const const_svarint_iterator&) = default; - const_svarint_iterator(const_svarint_iterator&&) = default; - - const_svarint_iterator& operator=(const const_svarint_iterator&) = default; - const_svarint_iterator& operator=(const_svarint_iterator&&) = default; - - ~const_svarint_iterator() = default; - - T operator*() { - const char* d = this->m_data; // will be thrown away - return static_cast(decode_zigzag64(decode_varint(&d, this->m_end))); - } - - const_svarint_iterator& operator++() { - // Ignore the result, we call decode_varint() just for the - // side-effect of updating m_data. - decode_varint(&this->m_data, this->m_end); - return *this; - } - - const_svarint_iterator operator++(int) { - const const_svarint_iterator tmp(*this); - ++(*this); - return tmp; - } - - }; // class const_svarint_iterator - -public: - /// Forward iterator for iterating over bool (int32 varint) values. - typedef const_varint_iterator< int32_t> const_bool_iterator; + using const_bool_iterator = const_varint_iterator< int32_t>; /// Forward iterator for iterating over enum (int32 varint) values. - typedef const_varint_iterator< int32_t> const_enum_iterator; + using const_enum_iterator = const_varint_iterator< int32_t>; /// Forward iterator for iterating over int32 (varint) values. - typedef const_varint_iterator< int32_t> const_int32_iterator; + using const_int32_iterator = const_varint_iterator< int32_t>; /// Forward iterator for iterating over sint32 (varint) values. - typedef const_svarint_iterator const_sint32_iterator; + using const_sint32_iterator = const_svarint_iterator; /// Forward iterator for iterating over uint32 (varint) values. - typedef const_varint_iterator const_uint32_iterator; + using const_uint32_iterator = const_varint_iterator; /// Forward iterator for iterating over int64 (varint) values. - typedef const_varint_iterator< int64_t> const_int64_iterator; + using const_int64_iterator = const_varint_iterator< int64_t>; /// Forward iterator for iterating over sint64 (varint) values. - typedef const_svarint_iterator const_sint64_iterator; + using const_sint64_iterator = const_svarint_iterator; /// Forward iterator for iterating over uint64 (varint) values. - typedef const_varint_iterator const_uint64_iterator; + using const_uint64_iterator = const_varint_iterator; ///@{ /** @@ -677,7 +684,9 @@ public: * @pre The current field must be of type "repeated packed bool". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_bool(); + iterator_range get_packed_bool() { + return get_packed(); + } /** * Consume current "repeated packed enum" field. @@ -688,7 +697,9 @@ public: * @pre The current field must be of type "repeated packed enum". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_enum(); + iterator_range get_packed_enum() { + return get_packed(); + } /** * Consume current "repeated packed int32" field. @@ -699,7 +710,9 @@ public: * @pre The current field must be of type "repeated packed int32". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_int32(); + iterator_range get_packed_int32() { + return get_packed(); + } /** * Consume current "repeated packed sint32" field. @@ -710,7 +723,9 @@ public: * @pre The current field must be of type "repeated packed sint32". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_sint32(); + iterator_range get_packed_sint32() { + return get_packed(); + } /** * Consume current "repeated packed uint32" field. @@ -721,7 +736,9 @@ public: * @pre The current field must be of type "repeated packed uint32". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_uint32(); + iterator_range get_packed_uint32() { + return get_packed(); + } /** * Consume current "repeated packed int64" field. @@ -732,7 +749,9 @@ public: * @pre The current field must be of type "repeated packed int64". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_int64(); + iterator_range get_packed_int64() { + return get_packed(); + } /** * Consume current "repeated packed sint64" field. @@ -743,7 +762,9 @@ public: * @pre The current field must be of type "repeated packed sint64". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_sint64(); + iterator_range get_packed_sint64() { + return get_packed(); + } /** * Consume current "repeated packed uint64" field. @@ -754,7 +775,9 @@ public: * @pre The current field must be of type "repeated packed uint64". * @post The current field was consumed and there is no current field now. */ - inline std::pair get_packed_uint64(); + iterator_range get_packed_uint64() { + return get_packed(); + } /** * Consume current "repeated packed fixed32" field. @@ -765,7 +788,7 @@ public: * @pre The current field must be of type "repeated packed fixed32". * @post The current field was consumed and there is no current field now. */ - inline auto get_packed_fixed32() -> decltype(packed_fixed()) { + auto get_packed_fixed32() -> decltype(packed_fixed()) { return packed_fixed(); } @@ -778,7 +801,7 @@ public: * @pre The current field must be of type "repeated packed sfixed32". * @post The current field was consumed and there is no current field now. */ - inline auto get_packed_sfixed32() -> decltype(packed_fixed()) { + auto get_packed_sfixed32() -> decltype(packed_fixed()) { return packed_fixed(); } @@ -791,7 +814,7 @@ public: * @pre The current field must be of type "repeated packed fixed64". * @post The current field was consumed and there is no current field now. */ - inline auto get_packed_fixed64() -> decltype(packed_fixed()) { + auto get_packed_fixed64() -> decltype(packed_fixed()) { return packed_fixed(); } @@ -804,7 +827,7 @@ public: * @pre The current field must be of type "repeated packed sfixed64". * @post The current field was consumed and there is no current field now. */ - inline auto get_packed_sfixed64() -> decltype(packed_fixed()) { + auto get_packed_sfixed64() -> decltype(packed_fixed()) { return packed_fixed(); } @@ -817,7 +840,7 @@ public: * @pre The current field must be of type "repeated packed float". * @post The current field was consumed and there is no current field now. */ - inline auto get_packed_float() -> decltype(packed_fixed()) { + auto get_packed_float() -> decltype(packed_fixed()) { return packed_fixed(); } @@ -830,7 +853,7 @@ public: * @pre The current field must be of type "repeated packed double". * @post The current field was consumed and there is no current field now. */ - inline auto get_packed_double() -> decltype(packed_fixed()) { + auto get_packed_double() -> decltype(packed_fixed()) { return packed_fixed(); } @@ -838,238 +861,14 @@ public: }; // class pbf_reader -pbf_reader::pbf_reader(const char *data, std::size_t length) noexcept - : m_data(data), - m_end(data + length), - m_wire_type(pbf_wire_type::unknown), - m_tag(0) { -} - -pbf_reader::pbf_reader(std::pair data) noexcept - : m_data(data.first), - m_end(data.first + data.second), - m_wire_type(pbf_wire_type::unknown), - m_tag(0) { -} - -pbf_reader::pbf_reader(const std::string& data) noexcept - : m_data(data.data()), - m_end(data.data() + data.size()), - m_wire_type(pbf_wire_type::unknown), - m_tag(0) { -} - -pbf_reader::operator bool() const noexcept { - return m_data < m_end; -} - -bool pbf_reader::next() { - if (m_data == m_end) { - return false; - } - - auto value = get_varint(); - m_tag = value >> 3; - - // tags 0 and 19000 to 19999 are not allowed as per - // https://developers.google.com/protocol-buffers/docs/proto - protozero_assert(((m_tag > 0 && m_tag < 19000) || (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range"); - - m_wire_type = pbf_wire_type(value & 0x07); - switch (m_wire_type) { - case pbf_wire_type::varint: - case pbf_wire_type::fixed64: - case pbf_wire_type::length_delimited: - case pbf_wire_type::fixed32: - break; - default: - throw unknown_pbf_wire_type_exception(); - } - - return true; -} - -bool pbf_reader::next(pbf_tag_type requested_tag) { - while (next()) { - if (m_tag == requested_tag) { - return true; - } else { - skip(); - } - } - return false; -} - -pbf_tag_type pbf_reader::tag() const noexcept { - return m_tag; -} - -pbf_wire_type pbf_reader::wire_type() const noexcept { - return m_wire_type; -} - -bool pbf_reader::has_wire_type(pbf_wire_type type) const noexcept { - return wire_type() == type; -} - -void pbf_reader::skip_bytes(pbf_length_type len) { - if (m_data + len > m_end) { - throw end_of_buffer_exception(); - } - m_data += len; - -// In debug builds reset the tag to zero so that we can detect (some) -// wrong code. -#ifndef NDEBUG - m_tag = 0; -#endif -} - -void pbf_reader::skip() { - protozero_assert(tag() != 0 && "call next() before calling skip()"); - switch (wire_type()) { - case pbf_wire_type::varint: - (void)get_uint32(); // called for the side-effect of skipping value - break; - case pbf_wire_type::fixed64: - skip_bytes(8); - break; - case pbf_wire_type::length_delimited: - skip_bytes(get_length()); - break; - case pbf_wire_type::fixed32: - skip_bytes(4); - break; - default: - protozero_assert(false && "can not be here because next() should have thrown already"); - } -} - -pbf_length_type pbf_reader::get_len_and_skip() { - auto len = get_length(); - skip_bytes(len); - return len; -} - -template -T pbf_reader::get_varint() { - return static_cast(decode_varint(&m_data, m_end)); -} - -template -T pbf_reader::get_svarint() { - protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint"); - return static_cast(decode_zigzag64(decode_varint(&m_data, m_end))); -} - -uint32_t pbf_reader::get_fixed32() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); - return get_fixed(); -} - -int32_t pbf_reader::get_sfixed32() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); - return get_fixed(); -} - -uint64_t pbf_reader::get_fixed64() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); - return get_fixed(); -} - -int64_t pbf_reader::get_sfixed64() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); - return get_fixed(); -} - -float pbf_reader::get_float() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); - return get_fixed(); -} - -double pbf_reader::get_double() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); - return get_fixed(); -} - -bool pbf_reader::get_bool() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); - protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint"); - skip_bytes(1); - return m_data[-1] != 0; // -1 okay because we incremented m_data the line before -} - -std::pair pbf_reader::get_data() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message"); - auto len = get_len_and_skip(); - return std::make_pair(m_data-len, len); -} - -std::string pbf_reader::get_bytes() { - auto d = get_data(); - return std::string(d.first, d.second); -} - -std::string pbf_reader::get_string() { - return get_bytes(); -} - -std::pair pbf_reader::get_packed_bool() { - return get_packed_int32(); -} - -std::pair pbf_reader::get_packed_enum() { - return get_packed_int32(); -} - -std::pair pbf_reader::get_packed_int32() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - auto len = get_len_and_skip(); - return std::make_pair(pbf_reader::const_int32_iterator(m_data-len, m_data), - pbf_reader::const_int32_iterator(m_data, m_data)); -} - -std::pair pbf_reader::get_packed_uint32() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - auto len = get_len_and_skip(); - return std::make_pair(pbf_reader::const_uint32_iterator(m_data-len, m_data), - pbf_reader::const_uint32_iterator(m_data, m_data)); -} - -std::pair pbf_reader::get_packed_sint32() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - auto len = get_len_and_skip(); - return std::make_pair(pbf_reader::const_sint32_iterator(m_data-len, m_data), - pbf_reader::const_sint32_iterator(m_data, m_data)); -} - -std::pair pbf_reader::get_packed_int64() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - auto len = get_len_and_skip(); - return std::make_pair(pbf_reader::const_int64_iterator(m_data-len, m_data), - pbf_reader::const_int64_iterator(m_data, m_data)); -} - -std::pair pbf_reader::get_packed_uint64() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - auto len = get_len_and_skip(); - return std::make_pair(pbf_reader::const_uint64_iterator(m_data-len, m_data), - pbf_reader::const_uint64_iterator(m_data, m_data)); -} - -std::pair pbf_reader::get_packed_sint64() { - protozero_assert(tag() != 0 && "call next() before accessing field value"); - auto len = get_len_and_skip(); - return std::make_pair(pbf_reader::const_sint64_iterator(m_data-len, m_data), - pbf_reader::const_sint64_iterator(m_data, m_data)); +/** + * Swap two pbf_reader objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline void swap(pbf_reader& lhs, pbf_reader& rhs) noexcept { + lhs.swap(rhs); } } // end namespace protozero diff --git a/include/protozero/pbf_writer.hpp b/include/protozero/pbf_writer.hpp index 422e14772..bce1c3ffc 100644 --- a/include/protozero/pbf_writer.hpp +++ b/include/protozero/pbf_writer.hpp @@ -22,6 +22,7 @@ documentation. #include #include #include +#include #include #include @@ -68,38 +69,38 @@ class pbf_writer { // parent to the position where the data of the submessage is written to. std::size_t m_pos = 0; - inline void add_varint(uint64_t value) { + void add_varint(uint64_t value) { protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); protozero_assert(m_data); write_varint(std::back_inserter(*m_data), value); } - inline void add_field(pbf_tag_type tag, pbf_wire_type type) { + void add_field(pbf_tag_type tag, pbf_wire_type type) { protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1 << 29) - 1))) && "tag out of range"); - uint32_t b = (tag << 3) | uint32_t(type); + const uint32_t b = (tag << 3) | uint32_t(type); add_varint(b); } - inline void add_tagged_varint(pbf_tag_type tag, uint64_t value) { + void add_tagged_varint(pbf_tag_type tag, uint64_t value) { add_field(tag, pbf_wire_type::varint); add_varint(value); } template - inline void add_fixed(T value) { + void add_fixed(T value) { protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); protozero_assert(m_data); #if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN m_data->append(reinterpret_cast(&value), sizeof(T)); #else - auto size = m_data->size(); + const auto size = m_data->size(); m_data->resize(size + sizeof(T)); byteswap(reinterpret_cast(&value), const_cast(m_data->data() + size)); #endif } template - inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) { + void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) { if (first == last) { return; } @@ -112,12 +113,12 @@ class pbf_writer { } template - inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) { + void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) { if (first == last) { return; } - auto length = std::distance(first, last); + const auto length = std::distance(first, last); add_length_varint(tag, sizeof(T) * pbf_length_type(length)); reserve(sizeof(T) * std::size_t(length)); @@ -127,7 +128,7 @@ class pbf_writer { } template - inline void add_packed_varint(pbf_tag_type tag, It first, It last) { + void add_packed_varint(pbf_tag_type tag, It first, It last) { if (first == last) { return; } @@ -140,7 +141,7 @@ class pbf_writer { } template - inline void add_packed_svarint(pbf_tag_type tag, It first, It last) { + void add_packed_svarint(pbf_tag_type tag, It first, It last) { if (first == last) { return; } @@ -155,14 +156,18 @@ class pbf_writer { // The number of bytes to reserve for the varint holding the length of // a length-delimited field. The length has to fit into pbf_length_type, // and a varint needs 8 bit for every 7 bit. - static const int reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1; + enum constant_reserve_bytes : int { + reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1 + }; // If m_rollpack_pos is set to this special value, it means that when // the submessage is closed, nothing needs to be done, because the length // of the submessage has already been written correctly. - static const std::size_t size_is_known = std::numeric_limits::max(); + enum constant_size_is_known : std::size_t { + size_is_known = std::numeric_limits::max() + }; - inline void open_submessage(pbf_tag_type tag, std::size_t size) { + void open_submessage(pbf_tag_type tag, std::size_t size) { protozero_assert(m_pos == 0); protozero_assert(m_data); if (size == 0) { @@ -177,7 +182,7 @@ class pbf_writer { m_pos = m_data->size(); } - inline void rollback_submessage() { + void rollback_submessage() { protozero_assert(m_pos != 0); protozero_assert(m_rollback_pos != size_is_known); protozero_assert(m_data); @@ -185,20 +190,20 @@ class pbf_writer { m_pos = 0; } - inline void commit_submessage() { + void commit_submessage() { protozero_assert(m_pos != 0); protozero_assert(m_rollback_pos != size_is_known); protozero_assert(m_data); - auto length = pbf_length_type(m_data->size() - m_pos); + const auto length = pbf_length_type(m_data->size() - m_pos); protozero_assert(m_data->size() >= m_pos - reserve_bytes); - auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length); + const auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length); m_data->erase(m_data->begin() + long(m_pos) - reserve_bytes + n, m_data->begin() + long(m_pos)); m_pos = 0; } - inline void close_submessage() { + void close_submessage() { protozero_assert(m_data); if (m_pos == 0 || m_rollback_pos == size_is_known) { return; @@ -210,7 +215,7 @@ class pbf_writer { } } - inline void add_length_varint(pbf_tag_type tag, pbf_length_type length) { + void add_length_varint(pbf_tag_type tag, pbf_length_type length) { add_field(tag, pbf_wire_type::length_delimited); add_varint(length); } @@ -222,7 +227,7 @@ public: * stores a reference to that string and adds all data to it. The string * doesn't have to be empty. The pbf_writer will just append data. */ - inline explicit pbf_writer(std::string& data) noexcept : + explicit pbf_writer(std::string& data) noexcept : m_data(&data), m_parent_writer(nullptr), m_pos(0) { @@ -232,7 +237,7 @@ public: * Create a writer without a data store. In this form the writer can not * be used! */ - inline pbf_writer() noexcept : + pbf_writer() noexcept : m_data(nullptr), m_parent_writer(nullptr), m_pos(0) { @@ -248,7 +253,7 @@ public: * Setting this allows some optimizations but is only possible in * a few very specific cases. */ - inline pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) : + pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) : m_data(parent_writer.m_data), m_parent_writer(&parent_writer), m_pos(0) { @@ -262,17 +267,30 @@ public: pbf_writer& operator=(const pbf_writer&) noexcept = default; /// A pbf_writer object can be moved - inline pbf_writer(pbf_writer&&) noexcept = default; + pbf_writer(pbf_writer&&) noexcept = default; /// A pbf_writer object can be moved - inline pbf_writer& operator=(pbf_writer&&) noexcept = default; + pbf_writer& operator=(pbf_writer&&) noexcept = default; - inline ~pbf_writer() { + ~pbf_writer() { if (m_parent_writer) { m_parent_writer->close_submessage(); } } + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(pbf_writer& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_parent_writer, other.m_parent_writer); + swap(m_rollback_pos, other.m_rollback_pos); + swap(m_pos, other.m_pos); + } + /** * Reserve size bytes in the underlying message store in addition to * whatever the message store already holds. So unlike @@ -286,7 +304,14 @@ public: m_data->reserve(m_data->size() + size); } - inline void rollback() { + /** + * Cancel writing of this submessage. The complete submessage will be + * removed as if it was never created and no fields were added. + * + * @pre Must be a pbf_writer of a submessage, ie one opened with the + * pbf_writer constructor taking a parent message. + */ + void rollback() { protozero_assert(m_parent_writer && "you can't call rollback() on a pbf_writer without a parent"); protozero_assert(m_pos == 0 && "you can't call rollback() on a pbf_writer that has an open nested submessage"); m_parent_writer->rollback_submessage(); @@ -304,7 +329,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_bool(pbf_tag_type tag, bool value) { + void add_bool(pbf_tag_type tag, bool value) { add_field(tag, pbf_wire_type::varint); protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); protozero_assert(m_data); @@ -317,7 +342,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_enum(pbf_tag_type tag, int32_t value) { + void add_enum(pbf_tag_type tag, int32_t value) { add_tagged_varint(tag, uint64_t(value)); } @@ -327,7 +352,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_int32(pbf_tag_type tag, int32_t value) { + void add_int32(pbf_tag_type tag, int32_t value) { add_tagged_varint(tag, uint64_t(value)); } @@ -337,7 +362,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_sint32(pbf_tag_type tag, int32_t value) { + void add_sint32(pbf_tag_type tag, int32_t value) { add_tagged_varint(tag, encode_zigzag32(value)); } @@ -347,7 +372,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_uint32(pbf_tag_type tag, uint32_t value) { + void add_uint32(pbf_tag_type tag, uint32_t value) { add_tagged_varint(tag, value); } @@ -357,7 +382,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_int64(pbf_tag_type tag, int64_t value) { + void add_int64(pbf_tag_type tag, int64_t value) { add_tagged_varint(tag, uint64_t(value)); } @@ -367,7 +392,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_sint64(pbf_tag_type tag, int64_t value) { + void add_sint64(pbf_tag_type tag, int64_t value) { add_tagged_varint(tag, encode_zigzag64(value)); } @@ -377,7 +402,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_uint64(pbf_tag_type tag, uint64_t value) { + void add_uint64(pbf_tag_type tag, uint64_t value) { add_tagged_varint(tag, value); } @@ -387,7 +412,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_fixed32(pbf_tag_type tag, uint32_t value) { + void add_fixed32(pbf_tag_type tag, uint32_t value) { add_field(tag, pbf_wire_type::fixed32); add_fixed(value); } @@ -398,7 +423,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_sfixed32(pbf_tag_type tag, int32_t value) { + void add_sfixed32(pbf_tag_type tag, int32_t value) { add_field(tag, pbf_wire_type::fixed32); add_fixed(value); } @@ -409,7 +434,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_fixed64(pbf_tag_type tag, uint64_t value) { + void add_fixed64(pbf_tag_type tag, uint64_t value) { add_field(tag, pbf_wire_type::fixed64); add_fixed(value); } @@ -420,7 +445,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_sfixed64(pbf_tag_type tag, int64_t value) { + void add_sfixed64(pbf_tag_type tag, int64_t value) { add_field(tag, pbf_wire_type::fixed64); add_fixed(value); } @@ -431,7 +456,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_float(pbf_tag_type tag, float value) { + void add_float(pbf_tag_type tag, float value) { add_field(tag, pbf_wire_type::fixed32); add_fixed(value); } @@ -442,7 +467,7 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_double(pbf_tag_type tag, double value) { + void add_double(pbf_tag_type tag, double value) { add_field(tag, pbf_wire_type::fixed64); add_fixed(value); } @@ -454,7 +479,7 @@ public: * @param value Pointer to value to be written * @param size Number of bytes to be written */ - inline void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) { + void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) { protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); protozero_assert(m_data); protozero_assert(size <= std::numeric_limits::max()); @@ -468,7 +493,17 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_bytes(pbf_tag_type tag, const std::string& value) { + void add_bytes(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_bytes(pbf_tag_type tag, const std::string& value) { add_bytes(tag, value.data(), value.size()); } @@ -479,7 +514,7 @@ public: * @param value Pointer to value to be written * @param size Number of bytes to be written */ - inline void add_string(pbf_tag_type tag, const char* value, std::size_t size) { + void add_string(pbf_tag_type tag, const char* value, std::size_t size) { add_bytes(tag, value, size); } @@ -489,7 +524,17 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written */ - inline void add_string(pbf_tag_type tag, const std::string& value) { + void add_string(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "string" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_string(pbf_tag_type tag, const std::string& value) { add_bytes(tag, value.data(), value.size()); } @@ -500,7 +545,7 @@ public: * @param tag Tag (field number) of the field * @param value Pointer to value to be written */ - inline void add_string(pbf_tag_type tag, const char* value) { + void add_string(pbf_tag_type tag, const char* value) { add_bytes(tag, value, std::strlen(value)); } @@ -511,7 +556,7 @@ public: * @param value Pointer to message to be written * @param size Length of the message */ - inline void add_message(pbf_tag_type tag, const char* value, std::size_t size) { + void add_message(pbf_tag_type tag, const char* value, std::size_t size) { add_bytes(tag, value, size); } @@ -521,7 +566,17 @@ public: * @param tag Tag (field number) of the field * @param value Value to be written. The value must be a complete message. */ - inline void add_message(pbf_tag_type tag, const std::string& value) { + void add_message(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "message" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(pbf_tag_type tag, const std::string& value) { add_bytes(tag, value.data(), value.size()); } @@ -535,126 +590,126 @@ public: /** * Add "repeated packed bool" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to bool. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_varint(tag, first, last); } /** * Add "repeated packed enum" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to int32_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_varint(tag, first, last); } /** * Add "repeated packed int32" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to int32_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_varint(tag, first, last); } /** * Add "repeated packed sint32" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to int32_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_svarint(tag, first, last); } /** * Add "repeated packed uint32" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to uint32_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_varint(tag, first, last); } /** * Add "repeated packed int64" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to int64_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_varint(tag, first, last); } /** * Add "repeated packed sint64" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to int64_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_svarint(tag, first, last); } /** * Add "repeated packed uint64" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to uint64_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_varint(tag, first, last); } /** * Add "repeated packed fixed32" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to uint32_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_fixed(tag, first, last, typename std::iterator_traits::iterator_category()); } @@ -662,14 +717,14 @@ public: /** * Add "repeated packed sfixed32" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to int32_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_fixed(tag, first, last, typename std::iterator_traits::iterator_category()); } @@ -677,14 +732,14 @@ public: /** * Add "repeated packed fixed64" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to uint64_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_fixed(tag, first, last, typename std::iterator_traits::iterator_category()); } @@ -692,14 +747,14 @@ public: /** * Add "repeated packed sfixed64" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to int64_t. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_fixed(tag, first, last, typename std::iterator_traits::iterator_category()); } @@ -707,14 +762,14 @@ public: /** * Add "repeated packed float" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to float. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_fixed(tag, first, last, typename std::iterator_traits::iterator_category()); } @@ -722,14 +777,14 @@ public: /** * Add "repeated packed double" field to data. * - * @tparam InputIterator An type satisfying the InputIterator concept. + * @tparam InputIterator A type satisfying the InputIterator concept. * Dereferencing the iterator must yield a type assignable to double. * @param tag Tag (field number) of the field * @param first Iterator pointing to the beginning of the data * @param last Iterator pointing one past the end of data */ template - inline void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) { + void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) { add_packed_fixed(tag, first, last, typename std::iterator_traits::iterator_category()); } @@ -742,6 +797,16 @@ public: }; // class pbf_writer +/** + * Swap two pbf_writer objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline void swap(pbf_writer& lhs, pbf_writer& rhs) noexcept { + lhs.swap(rhs); +} + namespace detail { class packed_field { @@ -817,19 +882,46 @@ namespace detail { } // end namespace detail +/// Class for generating packed repeated bool fields. using packed_field_bool = detail::packed_field_varint; + +/// Class for generating packed repeated enum fields. using packed_field_enum = detail::packed_field_varint; + +/// Class for generating packed repeated int32 fields. using packed_field_int32 = detail::packed_field_varint; + +/// Class for generating packed repeated sint32 fields. using packed_field_sint32 = detail::packed_field_svarint; + +/// Class for generating packed repeated uint32 fields. using packed_field_uint32 = detail::packed_field_varint; + +/// Class for generating packed repeated int64 fields. using packed_field_int64 = detail::packed_field_varint; + +/// Class for generating packed repeated sint64 fields. using packed_field_sint64 = detail::packed_field_svarint; + +/// Class for generating packed repeated uint64 fields. using packed_field_uint64 = detail::packed_field_varint; + +/// Class for generating packed repeated fixed32 fields. using packed_field_fixed32 = detail::packed_field_fixed; + +/// Class for generating packed repeated sfixed32 fields. using packed_field_sfixed32 = detail::packed_field_fixed; + +/// Class for generating packed repeated fixed64 fields. using packed_field_fixed64 = detail::packed_field_fixed; + +/// Class for generating packed repeated sfixed64 fields. using packed_field_sfixed64 = detail::packed_field_fixed; + +/// Class for generating packed repeated float fields. using packed_field_float = detail::packed_field_fixed; + +/// Class for generating packed repeated double fields. using packed_field_double = detail::packed_field_fixed; } // end namespace protozero diff --git a/include/protozero/types.hpp b/include/protozero/types.hpp index 6856b3dde..8b046387e 100644 --- a/include/protozero/types.hpp +++ b/include/protozero/types.hpp @@ -16,33 +16,173 @@ documentation. * @brief Contains the declaration of low-level types used in the pbf format. */ +#include #include +#include +#include +#include + +#include namespace protozero { - /** - * The type used for field tags (field numbers). - */ - typedef uint32_t pbf_tag_type; +/** + * The type used for field tags (field numbers). + */ +using pbf_tag_type = uint32_t; + +/** + * The type used to encode type information. + * See the table on + * https://developers.google.com/protocol-buffers/docs/encoding + */ +enum class pbf_wire_type : uint32_t { + varint = 0, // int32/64, uint32/64, sint32/64, bool, enum + fixed64 = 1, // fixed64, sfixed64, double + length_delimited = 2, // string, bytes, embedded messages, + // packed repeated fields + fixed32 = 5, // fixed32, sfixed32, float + unknown = 99 // used for default setting in this library +}; + +/** + * The type used for length values, such as the length of a field. + */ +using pbf_length_type = uint32_t; + +#ifdef PROTOZERO_USE_VIEW +using data_view = PROTOZERO_USE_VIEW; +#else + +/** + * Holds a pointer to some data and a length. + * + * This class is supposed to be compatible with the std::string_view + * that will be available in C++17. + */ +class data_view { + + const char* m_data; + std::size_t m_size; + +public: /** - * The type used to encode type information. - * See the table on - * https://developers.google.com/protocol-buffers/docs/encoding + * Default constructor. Construct an empty data_view. */ - enum class pbf_wire_type : uint32_t { - varint = 0, // int32/64, uint32/64, sint32/64, bool, enum - fixed64 = 1, // fixed64, sfixed64, double - length_delimited = 2, // string, bytes, embedded messages, - // packed repeated fields - fixed32 = 5, // fixed32, sfixed32, float - unknown = 99 // used for default setting in this library - }; + constexpr data_view() noexcept + : m_data(nullptr), + m_size(0) { + } /** - * The type used for length values, such as the length of a field. + * Create data_view from pointer and size. + * + * @param data Pointer to the data. + * @param size Length of the data. */ - typedef uint32_t pbf_length_type; + constexpr data_view(const char* data, std::size_t size) noexcept + : m_data(data), + m_size(size) { + } + + /** + * Create data_view from string. + * + * @param str String with the data. + */ + data_view(const std::string& str) noexcept + : m_data(str.data()), + m_size(str.size()) { + } + + /** + * Create data_view from zero-terminated string. + * + * @param data Pointer to the data. + */ + data_view(const char* data) noexcept + : m_data(data), + m_size(std::strlen(data)) { + } + + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(data_view& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_size, other.m_size); + } + + /// Return pointer to data. + constexpr const char* data() const noexcept { + return m_data; + } + + /// Return length of data in bytes. + constexpr std::size_t size() const noexcept { + return m_size; + } + + /** + * Convert data view to string. + * + * @pre Must not be default constructed data_view. + */ + std::string to_string() const { + protozero_assert(m_data); + return std::string{m_data, m_size}; + } + + /** + * Convert data view to string. + * + * @pre Must not be default constructed data_view. + */ + explicit operator std::string() const { + protozero_assert(m_data); + return std::string{m_data, m_size}; + } + +}; // class data_view + +/** + * Swap two data_view objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline void swap(data_view& lhs, data_view& rhs) noexcept { + lhs.swap(rhs); +} + +/** + * Two data_view instances are equal if they have the same size and the + * same content. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator==(data_view& lhs, data_view& rhs) noexcept { + return lhs.size() == rhs.size() && !std::strcmp(lhs.data(), rhs.data()); +} + +/** + * Two data_view instances are not equal if they have different sizes or the + * content differs. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator!=(data_view& lhs, data_view& rhs) noexcept { + return !(lhs == rhs); +} + +#endif + } // end namespace protozero diff --git a/include/protozero/varint.hpp b/include/protozero/varint.hpp index 4242df951..f6142d1c1 100644 --- a/include/protozero/varint.hpp +++ b/include/protozero/varint.hpp @@ -23,13 +23,54 @@ documentation. namespace protozero { /** - * The maximum length of a 64bit varint. + * The maximum length of a 64 bit varint. */ constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1; -// from https://github.com/facebook/folly/blob/master/folly/Varint.h +namespace detail { + + // from https://github.com/facebook/folly/blob/master/folly/Varint.h + inline uint64_t decode_varint_impl(const char** data, const char* end) { + const int8_t* begin = reinterpret_cast(*data); + const int8_t* iend = reinterpret_cast(end); + const int8_t* p = begin; + uint64_t val = 0; + + if (iend - begin >= max_varint_length) { // fast path + do { + int64_t b; + b = *p++; val = uint64_t((b & 0x7f) ); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 7); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break; + b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break; + throw varint_too_long_exception(); + } while (false); + } else { + int shift = 0; + while (p != iend && *p < 0) { + val |= uint64_t(*p++ & 0x7f) << shift; + shift += 7; + } + if (p == iend) { + throw end_of_buffer_exception(); + } + val |= uint64_t(*p++) << shift; + } + + *data = reinterpret_cast(p); + return val; + } + +} // end namespace detail + /** - * Decode a 64bit varint. + * Decode a 64 bit varint. * * Strong exception guarantee: if there is an exception the data pointer will * not be changed. @@ -39,54 +80,68 @@ constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1; * @param[in] end Pointer one past the end of the input data. * @returns The decoded integer * @throws varint_too_long_exception if the varint is longer then the maximum - * length that would fit in a 64bit int. Usually this means your data + * length that would fit in a 64 bit int. Usually this means your data * is corrupted or you are trying to read something as a varint that * isn't. * @throws end_of_buffer_exception if the *end* of the buffer was reached * before the end of the varint. */ inline uint64_t decode_varint(const char** data, const char* end) { - const int8_t* begin = reinterpret_cast(*data); - const int8_t* iend = reinterpret_cast(end); - const int8_t* p = begin; - uint64_t val = 0; - - if (iend - begin >= max_varint_length) { // fast path - do { - int64_t b; - b = *p++; val = uint64_t((b & 0x7f) ); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 7); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break; - throw varint_too_long_exception(); - } while (false); - } else { - int shift = 0; - while (p != iend && *p < 0) { - val |= uint64_t(*p++ & 0x7f) << shift; - shift += 7; - } - if (p == iend) { - throw end_of_buffer_exception(); - } - val |= uint64_t(*p++) << shift; + // If this is a one-byte varint, decode it here. + if (end != *data && ((**data & 0x80) == 0)) { + uint64_t val = uint64_t(**data); + ++(*data); + return val; } - - *data = reinterpret_cast(p); - return val; + // If this varint is more than one byte, defer to complete implementation. + return detail::decode_varint_impl(data, end); } /** - * Varint-encode a 64bit integer. + * Skip over a varint. + * + * Strong exception guarantee: if there is an exception the data pointer will + * not be changed. + * + * @param[in,out] data Pointer to pointer to the input data. After the function + * returns this will point to the next data to be read. + * @param[in] end Pointer one past the end of the input data. + * @throws end_of_buffer_exception if the *end* of the buffer was reached + * before the end of the varint. */ -template -inline int write_varint(OutputIterator data, uint64_t value) { +inline void skip_varint(const char** data, const char* end) { + const int8_t* begin = reinterpret_cast(*data); + const int8_t* iend = reinterpret_cast(end); + const int8_t* p = begin; + + while (p != iend && *p < 0) { + ++p; + } + + if (p >= begin + max_varint_length) { + throw varint_too_long_exception(); + } + + if (p == iend) { + throw end_of_buffer_exception(); + } + + ++p; + + *data = reinterpret_cast(p); +} + +/** + * Varint encode a 64 bit integer. + * + * @tparam T An output iterator type. + * @param data Output iterator the varint encoded value will be written to + * byte by byte. + * @param value The integer that will be encoded. + * @throws Any exception thrown by increment or dereference operator on data. + */ +template +inline int write_varint(T data, uint64_t value) { int n=1; while (value >= 0x80) { diff --git a/include/protozero/version.hpp b/include/protozero/version.hpp index 7b60e2eda..127e64972 100644 --- a/include/protozero/version.hpp +++ b/include/protozero/version.hpp @@ -10,13 +10,26 @@ documentation. *****************************************************************************/ -#define PROTOZERO_VERSION_MAJOR 1 -#define PROTOZERO_VERSION_MINOR 3 -#define PROTOZERO_VERSION_PATCH 0 +/** + * @file version.hpp + * + * @brief Contains macros defining the protozero version. + */ +/// The major version number +#define PROTOZERO_VERSION_MAJOR 1 + +/// The minor version number +#define PROTOZERO_VERSION_MINOR 4 + +/// The patch number +#define PROTOZERO_VERSION_PATCH 2 + +/// The complete version number #define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH) -#define PROTOZERO_VERSION_STRING "1.3.0" +/// Version number as string +#define PROTOZERO_VERSION_STRING "1.4.2" #endif // PROTOZERO_VERSION_HPP diff --git a/osmium.imp b/osmium.imp index c45794d08..77c5c37aa 100644 --- a/osmium.imp +++ b/osmium.imp @@ -2,10 +2,15 @@ # # Configuration for Include-What-You-Use tool # -# https://code.google.com/p/include-what-you-use/ +# http://include-what-you-use.org/ # #----------------------------------------------------------------------------- [ { "include": ["", "private", "", "public"] }, - { "include": ["", "public", "", "public"] } + { "include": ["", "private", "", "public"] }, + { "include": ["", "public", "", "public"] }, + { "include": ['"utf8/checked.h"', "private", "", "public"] }, + { "include": ['"utf8/unchecked.h"', "private", "", "public"] }, + { "include": ["", "public", "", "public"] }, + { "include": ["", "public", "", "public"] } ] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cea67ecd8..6230cde23 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -85,6 +85,40 @@ function(add_unit_test _tgroup _tname) endif() endfunction() + +#----------------------------------------------------------------------------- +# +# Prepare some variables so querying it for tests work properly. +# +#----------------------------------------------------------------------------- + +if(NOT GEOS_FOUND) + set(GEOS_FOUND FALSE) +endif() + +if(NOT PROJ_FOUND) + set(PROJ_FOUND FALSE) +endif() + +if(NOT SPARSEHASH_FOUND) + set(SPARSEHASH_FOUND FALSE) +endif() + +if(NOT BZIP2_FOUND) + set(BZIP2_FOUND FALSE) +endif() + +if(NOT Threads_FOUND) + set(Threads_FOUND FALSE) +endif() + +if(GEOS_FOUND AND PROJ_FOUND) + set(GEOS_AND_PROJ_FOUND TRUE) +else() + set(GEOS_AND_PROJ_FOUND FALSE) +endif() + + #----------------------------------------------------------------------------- # # Add all tests. @@ -93,6 +127,7 @@ endfunction() add_unit_test(area test_area_id) add_unit_test(area test_node_ref_segment) +add_unit_test(basic test_area) add_unit_test(basic test_box) add_unit_test(basic test_changeset) add_unit_test(basic test_crc) @@ -112,11 +147,6 @@ add_unit_test(buffer test_buffer_purge) add_unit_test(builder test_attr) -if(GEOS_FOUND AND PROJ_FOUND) - set(GEOS_AND_PROJ_FOUND TRUE) -else() - set(GEOS_AND_PROJ_FOUND FALSE) -endif() add_unit_test(geom test_factory_with_projection ENABLE_IF ${GEOS_AND_PROJ_FOUND} LIBS ${GEOS_LIBRARY} ${PROJ_LIBRARY}) @@ -129,17 +159,19 @@ add_unit_test(geom test_geos_wkb ENABLE_IF ${GEOS_FOUND} LIBS ${GEOS_LIBRARY}) add_unit_test(geom test_mercator) add_unit_test(geom test_ogr ENABLE_IF ${GDAL_FOUND} LIBS ${GDAL_LIBRARY}) add_unit_test(geom test_projection ENABLE_IF ${PROJ_FOUND} LIBS ${PROJ_LIBRARY}) -add_unit_test(geom test_tile) +add_unit_test(geom test_tile ENABLE_IF ${GEOS_FOUND}) add_unit_test(geom test_wkb) add_unit_test(geom test_wkt) add_unit_test(index test_id_to_location ENABLE_IF ${SPARSEHASH_FOUND}) +add_unit_test(index test_file_based_index) add_unit_test(io test_bzip2 ENABLE_IF ${BZIP2_FOUND} LIBS ${BZIP2_LIBRARIES}) add_unit_test(io test_file_formats) add_unit_test(io test_reader LIBS "${OSMIUM_XML_LIBRARIES};${OSMIUM_PBF_LIBRARIES}") add_unit_test(io test_reader_with_mock_decompression ENABLE_IF ${Threads_FOUND} LIBS ${OSMIUM_XML_LIBRARIES}) add_unit_test(io test_reader_with_mock_parser ENABLE_IF ${Threads_FOUND} LIBS ${CMAKE_THREAD_LIBS_INIT}) +add_unit_test(io test_opl_parser) add_unit_test(io test_output_utils) add_unit_test(io test_output_iterator ENABLE_IF ${Threads_FOUND} LIBS ${CMAKE_THREAD_LIBS_INIT}) add_unit_test(io test_string_table) diff --git a/test/README b/test/README index 8195824b4..868b533f2 100644 --- a/test/README +++ b/test/README @@ -3,11 +3,3 @@ Osmium uses Catch (https://github.com/philsquared/Catch/) for its unit tests. Only one header file is needed (catch.hpp) which can be downloaded from http://builds.catch-lib.net/ and put into the include directory. -Osmium needs a few changes to catch.hpp, they were patched in. To be able to -compare with the original version, it is stored in include/catch_orig.hpp. - -Changes are: -* Disable more warnings in GCC -* CATCH_CONFIG_CPP11_NULLPTR must be set for MSVC -* Problem with test running in loop: https://github.com/philsquared/Catch/issues/271 - diff --git a/test/data-tests/include/common.hpp b/test/data-tests/include/common.hpp index a6fd3df61..4dad07d32 100644 --- a/test/data-tests/include/common.hpp +++ b/test/data-tests/include/common.hpp @@ -10,9 +10,9 @@ #include #include -typedef osmium::index::map::Dummy index_neg_type; -typedef osmium::index::map::SparseMemArray index_pos_type; -typedef osmium::handler::NodeLocationsForWays location_handler_type; +using index_neg_type = osmium::index::map::Dummy; +using index_pos_type = osmium::index::map::SparseMemArray; +using location_handler_type = osmium::handler::NodeLocationsForWays; #include "check_basics_handler.hpp" #include "check_wkt_handler.hpp" diff --git a/test/data-tests/testdata-multipolygon.cpp b/test/data-tests/testdata-multipolygon.cpp index cf4fc521a..6d0328c8d 100644 --- a/test/data-tests/testdata-multipolygon.cpp +++ b/test/data-tests/testdata-multipolygon.cpp @@ -1,7 +1,9 @@ +#include #include #include #include +#include #include @@ -17,21 +19,20 @@ #include #include -typedef osmium::index::map::SparseMemArray index_type; - -typedef osmium::handler::NodeLocationsForWays location_handler_type; +using index_type = osmium::index::map::SparseMemArray; +using location_handler_type = osmium::handler::NodeLocationsForWays; struct less_charptr { - bool operator()(const char* a, const char* b) const { + bool operator()(const char* a, const char* b) const noexcept { return std::strcmp(a, b) < 0; } }; // less_charptr -typedef std::map tagmap_type; +using tagmap_type = std::map; -inline tagmap_type create_map(const osmium::TagList& taglist) { +tagmap_type create_map(const osmium::TagList& taglist) { tagmap_type map; for (auto& tag : taglist) { @@ -52,7 +53,7 @@ class TestHandler : public osmium::handler::Handler { std::ofstream m_out; - bool m_first_out {true}; + bool m_first_out{true}; public: @@ -77,7 +78,7 @@ public: } void node(const osmium::Node& node) { - gdalcpp::Feature feature(m_layer_point, m_ogr_factory.create_point(node)); + gdalcpp::Feature feature{m_layer_point, m_ogr_factory.create_point(node)}; feature.set_field("id", static_cast(node.id())); feature.set_field("type", node.tags().get_value_by_key("type")); feature.add_to_layer(); @@ -85,11 +86,11 @@ public: void way(const osmium::Way& way) { try { - gdalcpp::Feature feature(m_layer_lines, m_ogr_factory.create_linestring(way)); + gdalcpp::Feature feature{m_layer_lines, m_ogr_factory.create_linestring(way)}; feature.set_field("id", static_cast(way.id())); feature.set_field("type", way.tags().get_value_by_key("type")); feature.add_to_layer(); - } catch (osmium::geometry_error&) { + } catch (const osmium::geometry_error&) { std::cerr << "Ignoring illegal geometry for way " << way.id() << ".\n"; } } @@ -103,10 +104,10 @@ public: } m_out << "{\n \"test_id\": " << (area.orig_id() / 1000) << ",\n \"area_id\": " << area.id() << ",\n \"from_id\": " << area.orig_id() << ",\n \"from_type\": \"" << (area.from_way() ? "way" : "relation") << "\",\n \"wkt\": \""; try { - std::string wkt = m_wkt_factory.create_multipolygon(area); + const std::string wkt = m_wkt_factory.create_multipolygon(area); m_out << wkt << "\",\n \"tags\": {"; - auto tagmap = create_map(area.tags()); + const auto tagmap = create_map(area.tags()); bool first = true; for (auto& tag : tagmap) { if (first) { @@ -117,11 +118,11 @@ public: m_out << '"' << tag.first << "\": \"" << tag.second << '"'; } m_out << "}\n}"; - } catch (osmium::geometry_error&) { + } catch (const osmium::geometry_error&) { m_out << "INVALID\"\n}"; } try { - gdalcpp::Feature feature(m_layer_mpoly, m_ogr_factory.create_multipolygon(area)); + gdalcpp::Feature feature{m_layer_mpoly, m_ogr_factory.create_multipolygon(area)}; feature.set_field("id", static_cast(area.orig_id())); std::string from_type; @@ -132,7 +133,7 @@ public: } feature.set_field("from_type", from_type.c_str()); feature.add_to_layer(); - } catch (osmium::geometry_error&) { + } catch (const osmium::geometry_error&) { std::cerr << "Ignoring illegal geometry for area " << area.id() << " created from " << (area.from_way() ? "way" : "relation") << " with id=" << area.orig_id() << ".\n"; } } @@ -144,35 +145,38 @@ public: int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " INFILE\n"; - exit(1); + std::exit(1); } - std::string output_format("SQLite"); - std::string input_filename(argv[1]); - std::string output_filename("multipolygon.db"); + const std::string output_format{"SQLite"}; + const std::string input_filename{argv[1]}; + const std::string output_filename{"multipolygon.db"}; CPLSetConfigOption("OGR_SQLITE_SYNCHRONOUS", "FALSE"); - gdalcpp::Dataset dataset{output_format, output_filename, gdalcpp::SRS{}, { "SPATIALITE=TRUE" }}; + gdalcpp::Dataset dataset{output_format, output_filename, gdalcpp::SRS{}, {"SPATIALITE=TRUE"}}; - osmium::area::ProblemReporterOGR problem_reporter(dataset); - osmium::area::Assembler::config_type assembler_config(&problem_reporter); - assembler_config.enable_debug_output(); - osmium::area::MultipolygonCollector collector(assembler_config); + osmium::area::ProblemReporterOGR problem_reporter{dataset}; + osmium::area::Assembler::config_type assembler_config; + assembler_config.problem_reporter = &problem_reporter; + assembler_config.check_roles = true; + assembler_config.create_empty_areas = true; + assembler_config.debug_level = 2; + osmium::area::MultipolygonCollector collector{assembler_config}; std::cerr << "Pass 1...\n"; - osmium::io::Reader reader1(input_filename); + osmium::io::Reader reader1{input_filename}; collector.read_relations(reader1); reader1.close(); std::cerr << "Pass 1 done\n"; index_type index; - location_handler_type location_handler(index); + location_handler_type location_handler{index}; location_handler.ignore_errors(); - TestHandler test_handler(dataset); + TestHandler test_handler{dataset}; std::cerr << "Pass 2...\n"; - osmium::io::Reader reader2(input_filename); + osmium::io::Reader reader2{input_filename}; osmium::apply(reader2, location_handler, test_handler, collector.handler([&test_handler](const osmium::memory::Buffer& area_buffer) { osmium::apply(area_buffer, test_handler); })); diff --git a/test/data-tests/testdata-overview.cpp b/test/data-tests/testdata-overview.cpp index 43d672dd1..4994f23ef 100644 --- a/test/data-tests/testdata-overview.cpp +++ b/test/data-tests/testdata-overview.cpp @@ -1,6 +1,7 @@ /* The code in this file is released into the Public Domain. */ #include +#include #include @@ -12,8 +13,8 @@ #include #include -typedef osmium::index::map::SparseMemArray index_type; -typedef osmium::handler::NodeLocationsForWays location_handler_type; +using index_type = osmium::index::map::SparseMemArray; +using location_handler_type = osmium::handler::NodeLocationsForWays; class TestOverviewHandler : public osmium::handler::Handler { @@ -64,7 +65,7 @@ public: } feature.add_to_layer(); - } catch (osmium::geometry_error&) { + } catch (const osmium::geometry_error&) { std::cerr << "Ignoring illegal geometry for way " << way.id() << ".\n"; } } @@ -76,24 +77,24 @@ public: int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " INFILE\n"; - exit(1); + std::exit(1); } - std::string output_format("SQLite"); - std::string input_filename(argv[1]); - std::string output_filename("testdata-overview.db"); + const std::string output_format{"SQLite"}; + const std::string input_filename{argv[1]}; + const std::string output_filename{"testdata-overview.db"}; ::unlink(output_filename.c_str()); CPLSetConfigOption("OGR_SQLITE_SYNCHRONOUS", "FALSE"); - gdalcpp::Dataset dataset(output_format, output_filename, gdalcpp::SRS{}, { "SPATIALITE=TRUE" }); + gdalcpp::Dataset dataset{output_format, output_filename, gdalcpp::SRS{}, {"SPATIALITE=TRUE"}}; - osmium::io::Reader reader(input_filename); + osmium::io::Reader reader{input_filename}; index_type index; - location_handler_type location_handler(index); + location_handler_type location_handler{index}; location_handler.ignore_errors(); - TestOverviewHandler handler(dataset); + TestOverviewHandler handler{dataset}; osmium::apply(reader, location_handler, handler); reader.close(); diff --git a/test/data-tests/testdata-testcases.cpp b/test/data-tests/testdata-testcases.cpp index 0ea7fc859..d05c28327 100644 --- a/test/data-tests/testdata-testcases.cpp +++ b/test/data-tests/testdata-testcases.cpp @@ -15,11 +15,9 @@ int main(int argc, char* argv[]) { std::cerr << "Running tests from '" << dirname << "' (from TESTCASES_DIR environment variable)\n"; } else { std::cerr << "Please set TESTCASES_DIR environment variable.\n"; - exit(1); + std::exit(1); } - int result = Catch::Session().run(argc, argv); - - return result; + return Catch::Session().run(argc, argv); } diff --git a/test/data-tests/testdata-xml.cpp b/test/data-tests/testdata-xml.cpp index b5a0e9028..0d2739a3a 100644 --- a/test/data-tests/testdata-xml.cpp +++ b/test/data-tests/testdata-xml.cpp @@ -5,7 +5,9 @@ #include #include +#include #include +#include #include #include @@ -14,14 +16,14 @@ #include std::string S_(const char* s) { - return std::string(s); + return std::string{s}; } std::string filename(const char* test_id, const char* suffix = "osm") { const char* testdir = getenv("TESTDIR"); if (!testdir) { std::cerr << "You have to set TESTDIR environment variable before running testdata-xml\n"; - exit(2); + std::exit(2); } std::string f; @@ -47,11 +49,11 @@ struct header_buffer_type { // file contents fit into small buffers. std::string read_file(const char* test_id) { - int fd = osmium::io::detail::open_for_reading(filename(test_id)); + const int fd = osmium::io::detail::open_for_reading(filename(test_id)); assert(fd >= 0); std::string input(10000, '\0'); - auto n = ::read(fd, reinterpret_cast(const_cast(input.data())), 10000); + const auto n = ::read(fd, reinterpret_cast(const_cast(input.data())), 10000); assert(n >= 0); input.resize(static_cast(n)); @@ -61,10 +63,10 @@ std::string read_file(const char* test_id) { } std::string read_gz_file(const char* test_id, const char* suffix) { - int fd = osmium::io::detail::open_for_reading(filename(test_id, suffix)); + const int fd = osmium::io::detail::open_for_reading(filename(test_id, suffix)); assert(fd >= 0); - osmium::io::GzipDecompressor gzip_decompressor(fd); + osmium::io::GzipDecompressor gzip_decompressor{fd}; std::string input = gzip_decompressor.read(); gzip_decompressor.close(); @@ -81,7 +83,7 @@ header_buffer_type parse_xml(std::string input) { osmium::io::detail::add_to_queue(input_queue, std::move(input)); osmium::io::detail::add_to_queue(input_queue, std::string{}); - osmium::io::detail::XMLParser parser(input_queue, output_queue, header_promise, osmium::osm_entity_bits::all); + osmium::io::detail::XMLParser parser{input_queue, output_queue, header_promise, osmium::osm_entity_bits::all}; parser.parse(); header_buffer_type result; @@ -117,9 +119,9 @@ TEST_CASE("Reading OSM XML 100") { } SECTION("Using Reader") { - osmium::io::Reader reader(filename("100-correct_but_no_data")); + osmium::io::Reader reader{filename("100-correct_but_no_data")}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; REQUIRE(header.get("generator") == "testdata"); osmium::memory::Buffer buffer = reader.read(); @@ -129,9 +131,9 @@ TEST_CASE("Reading OSM XML 100") { } SECTION("Using Reader asking for header only") { - osmium::io::Reader reader(filename("100-correct_but_no_data"), osmium::osm_entity_bits::nothing); + osmium::io::Reader reader{filename("100-correct_but_no_data"), osmium::osm_entity_bits::nothing}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; REQUIRE(header.get("generator") == "testdata"); reader.close(); } @@ -146,15 +148,15 @@ TEST_CASE("Reading OSM XML 101") { REQUIRE_THROWS_AS(read_xml("101-missing_version"), osmium::format_version_error); try { read_xml("101-missing_version"); - } catch (osmium::format_version_error& e) { + } catch (const osmium::format_version_error& e) { REQUIRE(e.version.empty()); } } SECTION("Using Reader") { REQUIRE_THROWS_AS({ - osmium::io::Reader reader(filename("101-missing_version")); - osmium::io::Header header = reader.header(); + osmium::io::Reader reader{filename("101-missing_version")}; + const osmium::io::Header header{reader.header()}; osmium::memory::Buffer buffer = reader.read(); reader.close(); }, osmium::format_version_error); @@ -170,16 +172,16 @@ TEST_CASE("Reading OSM XML 102") { REQUIRE_THROWS_AS(read_xml("102-wrong_version"), osmium::format_version_error); try { read_xml("102-wrong_version"); - } catch (osmium::format_version_error& e) { + } catch (const osmium::format_version_error& e) { REQUIRE(e.version == "0.1"); } } SECTION("Using Reader") { REQUIRE_THROWS_AS({ - osmium::io::Reader reader(filename("102-wrong_version")); + osmium::io::Reader reader{filename("102-wrong_version")}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; osmium::memory::Buffer buffer = reader.read(); reader.close(); }, osmium::format_version_error); @@ -195,15 +197,15 @@ TEST_CASE("Reading OSM XML 103") { REQUIRE_THROWS_AS(read_xml("103-old_version"), osmium::format_version_error); try { read_xml("103-old_version"); - } catch (osmium::format_version_error& e) { + } catch (const osmium::format_version_error& e) { REQUIRE(e.version == "0.5"); } } SECTION("Using Reader") { REQUIRE_THROWS_AS({ - osmium::io::Reader reader(filename("103-old_version")); - osmium::io::Header header = reader.header(); + osmium::io::Reader reader{filename("103-old_version")}; + const osmium::io::Header header{reader.header()}; osmium::memory::Buffer buffer = reader.read(); reader.close(); }, osmium::format_version_error); @@ -219,7 +221,7 @@ TEST_CASE("Reading OSM XML 104") { REQUIRE_THROWS_AS(read_xml("104-empty_file"), osmium::xml_error); try { read_xml("104-empty_file"); - } catch (osmium::xml_error& e) { + } catch (const osmium::xml_error& e) { REQUIRE(e.line == 1); REQUIRE(e.column == 0); } @@ -227,8 +229,8 @@ TEST_CASE("Reading OSM XML 104") { SECTION("Using Reader") { REQUIRE_THROWS_AS({ - osmium::io::Reader reader(filename("104-empty_file")); - osmium::io::Header header = reader.header(); + osmium::io::Reader reader{filename("104-empty_file")}; + const osmium::io::Header header{reader.header()}; osmium::memory::Buffer buffer = reader.read(); reader.close(); }, osmium::xml_error); @@ -245,8 +247,8 @@ TEST_CASE("Reading OSM XML 105") { SECTION("Using Reader") { REQUIRE_THROWS_AS({ - osmium::io::Reader reader(filename("105-incomplete_xml_file")); - osmium::io::Header header = reader.header(); + osmium::io::Reader reader{filename("105-incomplete_xml_file")}; + const osmium::io::Header header{reader.header()}; osmium::memory::Buffer buffer = reader.read(); reader.close(); }, osmium::xml_error); @@ -270,9 +272,9 @@ TEST_CASE("Reading OSM XML 120") { } SECTION("Using Reader") { - osmium::io::Reader reader(filename("120-correct_gzip_file_without_data", "osm.gz")); + osmium::io::Reader reader{filename("120-correct_gzip_file_without_data", "osm.gz")}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; REQUIRE(header.get("generator") == "testdata"); osmium::memory::Buffer buffer = reader.read(); @@ -296,8 +298,8 @@ TEST_CASE("Reading OSM XML 121") { SECTION("Using Reader") { // can throw osmium::gzip_error or osmium::xml_error REQUIRE_THROWS({ - osmium::io::Reader reader(filename("121-truncated_gzip_file", "osm.gz")); - osmium::io::Header header = reader.header(); + osmium::io::Reader reader{filename("121-truncated_gzip_file", "osm.gz")}; + const osmium::io::Header header{reader.header()}; osmium::memory::Buffer buffer = reader.read(); reader.close(); }); @@ -317,8 +319,8 @@ TEST_CASE("Reading OSM XML 122") { SECTION("Using Reader") { REQUIRE_THROWS_AS({ - osmium::io::Reader reader(filename("122-no_osm_element")); - osmium::io::Header header = reader.header(); + osmium::io::Reader reader{filename("122-no_osm_element")}; + const osmium::io::Header header{reader.header()}; osmium::memory::Buffer buffer = reader.read(); reader.close(); }, osmium::xml_error); @@ -331,19 +333,19 @@ TEST_CASE("Reading OSM XML 122") { TEST_CASE("Reading OSM XML 140") { SECTION("Using Reader") { - osmium::io::Reader reader(filename("140-unicode")); + osmium::io::Reader reader{filename("140-unicode")}; osmium::memory::Buffer buffer = reader.read(); reader.close(); int count = 0; - for (auto it = buffer.begin(); it != buffer.end(); ++it) { + for (const auto& node : buffer.select()) { ++count; - REQUIRE(it->id() == count); - const osmium::TagList& t = it->tags(); + REQUIRE(node.id() == count); + const osmium::TagList& t = node.tags(); const char* uc = t["unicode_char"]; - auto len = atoi(t["unicode_utf8_length"]); + const auto len = atoi(t["unicode_utf8_length"]); REQUIRE(len == strlen(uc)); REQUIRE(S_(uc) == t["unicode_xml"]); @@ -382,7 +384,7 @@ TEST_CASE("Reading OSM XML 140") { TEST_CASE("Reading OSM XML 141") { SECTION("Using Reader") { - osmium::io::Reader reader(filename("141-entities")); + osmium::io::Reader reader{filename("141-entities")}; osmium::memory::Buffer buffer = reader.read(); reader.close(); REQUIRE(buffer.committed() > 0); @@ -406,40 +408,40 @@ TEST_CASE("Reading OSM XML 141") { TEST_CASE("Reading OSM XML 142") { SECTION("Using Reader to read nodes") { - osmium::io::Reader reader(filename("142-whitespace")); + osmium::io::Reader reader{filename("142-whitespace")}; osmium::memory::Buffer buffer = reader.read(); reader.close(); int count = 0; - for (auto it = buffer.begin(); it != buffer.end(); ++it) { + for (const auto& node : buffer.select()) { ++count; - REQUIRE(it->id() == count); - REQUIRE(it->tags().size() == 1); - const osmium::Tag& tag = *(it->tags().begin()); + REQUIRE(node.id() == count); + REQUIRE(node.tags().size() == 1); + const osmium::Tag& tag = *(node.tags().begin()); switch (count) { case 1: - REQUIRE(S_(it->user()) == "user name"); + REQUIRE(S_(node.user()) == "user name"); REQUIRE(S_(tag.key()) == "key with space"); REQUIRE(S_(tag.value()) == "value with space"); break; case 2: - REQUIRE(S_(it->user()) == "line\nfeed"); + REQUIRE(S_(node.user()) == "line\nfeed"); REQUIRE(S_(tag.key()) == "key with\nlinefeed"); REQUIRE(S_(tag.value()) == "value with\nlinefeed"); break; case 3: - REQUIRE(S_(it->user()) == "carriage\rreturn"); + REQUIRE(S_(node.user()) == "carriage\rreturn"); REQUIRE(S_(tag.key()) == "key with\rcarriage\rreturn"); REQUIRE(S_(tag.value()) == "value with\rcarriage\rreturn"); break; case 4: - REQUIRE(S_(it->user()) == "tab\tulator"); + REQUIRE(S_(node.user()) == "tab\tulator"); REQUIRE(S_(tag.key()) == "key with\ttab"); REQUIRE(S_(tag.value()) == "value with\ttab"); break; case 5: - REQUIRE(S_(it->user()) == "unencoded linefeed"); + REQUIRE(S_(node.user()) == "unencoded linefeed"); REQUIRE(S_(tag.key()) == "key with unencoded linefeed"); REQUIRE(S_(tag.value()) == "value with unencoded linefeed"); break; @@ -451,12 +453,12 @@ TEST_CASE("Reading OSM XML 142") { } SECTION("Using Reader to read relation") { - osmium::io::Reader reader(filename("142-whitespace")); + osmium::io::Reader reader{filename("142-whitespace")}; osmium::memory::Buffer buffer = reader.read(); reader.close(); - auto it = buffer.begin(); - REQUIRE(it != buffer.end()); + auto it = buffer.select().begin(); + REQUIRE(it != buffer.select().end()); REQUIRE(it->id() == 21); const auto& members = it->members(); REQUIRE(members.size() == 5); @@ -505,9 +507,9 @@ TEST_CASE("Reading OSM XML 200") { } SECTION("Using Reader") { - osmium::io::Reader reader(filename("200-nodes")); + osmium::io::Reader reader{filename("200-nodes")}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; REQUIRE(header.get("generator") == "testdata"); osmium::memory::Buffer buffer = reader.read(); @@ -519,9 +521,9 @@ TEST_CASE("Reading OSM XML 200") { } SECTION("Using Reader asking for nodes") { - osmium::io::Reader reader(filename("200-nodes"), osmium::osm_entity_bits::node); + osmium::io::Reader reader{filename("200-nodes"), osmium::osm_entity_bits::node}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; REQUIRE(header.get("generator") == "testdata"); osmium::memory::Buffer buffer = reader.read(); @@ -533,9 +535,9 @@ TEST_CASE("Reading OSM XML 200") { } SECTION("Using Reader asking for header only") { - osmium::io::Reader reader(filename("200-nodes"), osmium::osm_entity_bits::nothing); + osmium::io::Reader reader{filename("200-nodes"), osmium::osm_entity_bits::nothing}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; REQUIRE(header.get("generator") == "testdata"); REQUIRE_THROWS({ @@ -546,9 +548,9 @@ TEST_CASE("Reading OSM XML 200") { } SECTION("Using Reader asking for ways") { - osmium::io::Reader reader(filename("200-nodes"), osmium::osm_entity_bits::way); + osmium::io::Reader reader{filename("200-nodes"), osmium::osm_entity_bits::way}; - osmium::io::Header header = reader.header(); + const osmium::io::Header header{reader.header()}; REQUIRE(header.get("generator") == "testdata"); osmium::memory::Buffer buffer = reader.read(); diff --git a/test/include/catch.hpp b/test/include/catch.hpp index f04214990..879fc5b1d 100644 --- a/test/include/catch.hpp +++ b/test/include/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v1.3.3 - * Generated: 2016-01-22 07:51:36.661106 + * Catch v1.5.6 + * Generated: 2016-06-09 19:20:41.460328 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -62,7 +62,11 @@ #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif #define INTERNAL_CATCH_STRINGIFY2( expr ) #expr #define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) @@ -89,7 +93,7 @@ // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? - +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too @@ -102,6 +106,18 @@ // All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 +#ifdef __cplusplus + +# if __cplusplus >= 201103L +# define CATCH_CPP11_OR_GREATER +# endif + +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER +# endif + +#endif + #ifdef __clang__ # if __has_feature(cxx_nullptr) @@ -112,6 +128,10 @@ # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif +# if defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# endif + #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// @@ -136,9 +156,13 @@ // GCC #ifdef __GNUC__ -#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -#endif +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) +# endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below @@ -173,13 +197,20 @@ #endif +// Use __COUNTER__ if the compiler supports it +#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ + ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \ + ( defined __clang__ && __clang_major__ >= 3 ) + +#define CATCH_INTERNAL_CONFIG_COUNTER + +#endif + //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 -#if defined(__cplusplus) && __cplusplus >= 201103L - -# define CATCH_CPP11_OR_GREATER +#if defined(CATCH_CPP11_OR_GREATER) # if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR @@ -246,6 +277,13 @@ #if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif // noexcept support: #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) @@ -672,24 +710,28 @@ void registerTestCaseFunction #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + struct TestName : ClassName{ \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ @@ -697,24 +739,28 @@ void registerTestCaseFunction #else /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ + static void TestName(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void TestName() #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + struct TestCaseName : ClassName{ \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + void TestCaseName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ @@ -1287,37 +1333,37 @@ namespace Internal { template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { - return opCast( lhs ) == opCast( rhs ); + return bool( opCast( lhs ) == opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) != opCast( rhs ); + return bool( opCast( lhs ) != opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) < opCast( rhs ); + return bool( opCast( lhs ) < opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) > opCast( rhs ); + return bool( opCast( lhs ) > opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) >= opCast( rhs ); + return bool( opCast( lhs ) >= opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) <= opCast( rhs ); + return bool( opCast( lhs ) <= opCast( rhs ) ); } }; @@ -2020,13 +2066,14 @@ namespace Catch { do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ ( __catchResult <= expr ).endExpression(); \ } \ catch( ... ) { \ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + } while( Catch::isTrue( false && !!(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ @@ -2563,10 +2610,12 @@ namespace Catch { } /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED @@ -3331,6 +3380,11 @@ namespace Catch { InLexicographicalOrder, InRandomOrder }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; class TestSpec; @@ -3350,7 +3404,7 @@ namespace Catch { virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; - virtual bool forceColour() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; }; } @@ -3404,7 +3458,7 @@ namespace Catch { }; class DebugOutStream : public IStream { - std::auto_ptr m_streamBuf; + CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; mutable std::ostream m_os; public: DebugOutStream(); @@ -3439,14 +3493,14 @@ namespace Catch { noThrow( false ), showHelp( false ), showInvisibles( false ), - forceColour( false ), filenamesAsTags( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), showDurations( ShowDurations::DefaultForReporter ), - runOrder( RunTests::InDeclarationOrder ) + runOrder( RunTests::InDeclarationOrder ), + useColour( UseColour::Auto ) {} bool listTests; @@ -3459,7 +3513,6 @@ namespace Catch { bool noThrow; bool showHelp; bool showInvisibles; - bool forceColour; bool filenamesAsTags; int abortAfter; @@ -3469,6 +3522,7 @@ namespace Catch { WarnAbout::What warnings; ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; + UseColour::YesOrNo useColour; std::string outputFilename; std::string name; @@ -3534,7 +3588,7 @@ namespace Catch { virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } virtual unsigned int rngSeed() const { return m_data.rngSeed; } - virtual bool forceColour() const { return m_data.forceColour; } + virtual UseColour::YesOrNo useColour() const { return m_data.useColour; } private: @@ -3552,7 +3606,7 @@ namespace Catch { } ConfigData m_data; - std::auto_ptr m_stream; + CATCH_AUTO_PTR( IStream const ) m_stream; TestSpec m_testSpec; }; @@ -3572,6 +3626,8 @@ namespace Catch { #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h +// Version 0.0.2.4 + // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) @@ -3596,6 +3652,7 @@ namespace Catch { #include #include #include +#include // Use optional outer namespace #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE @@ -3730,15 +3787,165 @@ namespace Tbc { #endif // TBC_TEXT_FORMAT_H_INCLUDED // ----------- end of #include from tbc_text_format.h ----------- -// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h +// ........... back in clara.h #undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE +// ----------- #included from clara_compilers.h ----------- + +#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED +#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? +// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + +// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 + +#ifdef __clang__ + +#if __has_feature(cxx_nullptr) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if __has_feature(cxx_noexcept) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(__cplusplus) && __cplusplus >= 201103L + +#define CLARA_CPP11_OR_GREATER + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) +#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE +#endif +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NULLPTR +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_UNIQUE_PTR +#endif + +// noexcept support: +#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) +#define CLARA_NOEXCEPT noexcept +# define CLARA_NOEXCEPT_IS(x) noexcept(x) +#else +#define CLARA_NOEXCEPT throw() +# define CLARA_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CLARA_CONFIG_CPP11_NULLPTR +#define CLARA_NULL nullptr +#else +#define CLARA_NULL NULL +#endif + +// override support +#ifdef CLARA_CONFIG_CPP11_OVERRIDE +#define CLARA_OVERRIDE override +#else +#define CLARA_OVERRIDE +#endif + +// unique_ptr support +#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR +# define CLARA_AUTO_PTR( T ) std::unique_ptr +#else +# define CLARA_AUTO_PTR( T ) std::auto_ptr +#endif + +#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// ----------- end of #include from clara_compilers.h ----------- +// ........... back in clara.h + #include -#include #include #include +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CLARA_PLATFORM_WINDOWS +#endif + // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE @@ -3797,23 +4004,15 @@ namespace Clara { else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } - inline void convertInto( bool _source, bool& _dest ) { - _dest = _source; - } - template - inline void convertInto( bool, T& ) { - throw std::runtime_error( "Invalid conversion" ); - } template struct IArgFunction { virtual ~IArgFunction() {} -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS +#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; -# endif +#endif virtual void set( ConfigT& config, std::string const& value ) const = 0; - virtual void setFlag( ConfigT& config ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; @@ -3821,11 +4020,11 @@ namespace Clara { template class BoundArgFunction { public: - BoundArgFunction() : functionObj( CATCH_NULL ) {} + BoundArgFunction() : functionObj( CLARA_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CATCH_NULL ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CATCH_NULL; + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; delete functionObj; functionObj = newFunctionObj; return *this; @@ -3835,13 +4034,10 @@ namespace Clara { void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } - void setFlag( ConfigT& config ) const { - functionObj->setFlag( config ); - } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { - return functionObj != CATCH_NULL; + return functionObj != CLARA_NULL; } private: IArgFunction* functionObj; @@ -3850,7 +4046,6 @@ namespace Clara { template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} - virtual void setFlag( C& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; @@ -3861,9 +4056,6 @@ namespace Clara { virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } - virtual void setFlag( C& p ) const { - convertInto( true, p.*member ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; @@ -3876,11 +4068,6 @@ namespace Clara { convertInto( stringValue, value ); (p.*member)( value ); } - virtual void setFlag( C& p ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - (p.*member)( value ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); @@ -3894,9 +4081,6 @@ namespace Clara { if( value ) (p.*member)(); } - virtual void setFlag( C& p ) const { - (p.*member)(); - } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); @@ -3911,9 +4095,6 @@ namespace Clara { if( value ) function( obj ); } - virtual void setFlag( C& p ) const { - function( p ); - } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); @@ -3927,11 +4108,6 @@ namespace Clara { convertInto( stringValue, value ); function( obj, value ); } - virtual void setFlag( C& obj ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - function( obj, value ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); @@ -3939,8 +4115,20 @@ namespace Clara { } // namespace Detail - struct Parser { - Parser() : separators( " \t=:" ) {} + inline std::vector argsToVector( int argc, char const* const* const argv ) { + std::vector args( static_cast( argc ) ); + for( std::size_t i = 0; i < static_cast( argc ); ++i ) + args[i] = argv[i]; + + return args; + } + + class Parser { + enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; + Mode mode; + std::size_t from; + bool inQuotes; + public: struct Token { enum Type { Positional, ShortOpt, LongOpt }; @@ -3949,38 +4137,75 @@ namespace Clara { std::string data; }; - void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + Parser() : mode( None ), from( 0 ), inQuotes( false ){} + + void parseIntoTokens( std::vector const& args, std::vector& tokens ) { const std::string doubleDash = "--"; - for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) - parseIntoTokens( argv[i] , tokens); + for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) + parseIntoTokens( args[i], tokens); } - void parseIntoTokens( std::string arg, std::vector& tokens ) const { - while( !arg.empty() ) { - Parser::Token token( Parser::Token::Positional, arg ); - arg = ""; - if( token.data[0] == '-' ) { - if( token.data.size() > 1 && token.data[1] == '-' ) { - token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); - } - else { - token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); - if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { - arg = "-" + token.data.substr( 1 ); - token.data = token.data.substr( 0, 1 ); - } - } - } - if( token.type != Parser::Token::Positional ) { - std::size_t pos = token.data.find_first_of( separators ); - if( pos != std::string::npos ) { - arg = token.data.substr( pos+1 ); - token.data = token.data.substr( 0, pos ); - } - } - tokens.push_back( token ); + + void parseIntoTokens( std::string const& arg, std::vector& tokens ) { + for( std::size_t i = 0; i <= arg.size(); ++i ) { + char c = arg[i]; + if( c == '"' ) + inQuotes = !inQuotes; + mode = handleMode( i, c, arg, tokens ); } } - std::string separators; + Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + switch( mode ) { + case None: return handleNone( i, c ); + case MaybeShortOpt: return handleMaybeShortOpt( i, c ); + case ShortOpt: + case LongOpt: + case SlashOpt: return handleOpt( i, c, arg, tokens ); + case Positional: return handlePositional( i, c, arg, tokens ); + default: throw std::logic_error( "Unknown mode" ); + } + } + + Mode handleNone( std::size_t i, char c ) { + if( inQuotes ) { + from = i; + return Positional; + } + switch( c ) { + case '-': return MaybeShortOpt; +#ifdef CLARA_PLATFORM_WINDOWS + case '/': from = i+1; return SlashOpt; +#endif + default: from = i; return Positional; + } + } + Mode handleMaybeShortOpt( std::size_t i, char c ) { + switch( c ) { + case '-': from = i+1; return LongOpt; + default: from = i; return ShortOpt; + } + } + Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) + return mode; + + std::string optName = arg.substr( from, i-from ); + if( mode == ShortOpt ) + for( std::size_t j = 0; j < optName.size(); ++j ) + tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); + else if( mode == SlashOpt && optName.size() == 1 ) + tokens.push_back( Token( Token::ShortOpt, optName ) ); + else + tokens.push_back( Token( Token::LongOpt, optName ) ); + return None; + } + Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) + return mode; + + std::string data = arg.substr( from, i-from ); + tokens.push_back( Token( Token::Positional, data ) ); + return None; + } }; template @@ -4059,7 +4284,7 @@ namespace Clara { } }; - typedef CATCH_AUTO_PTR( Arg ) ArgAutoPtr; + typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { @@ -4135,8 +4360,8 @@ namespace Clara { m_arg->description = description; return *this; } - ArgBuilder& detail( std::string const& _detail ) { - m_arg->detail = _detail; + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; return *this; } @@ -4219,14 +4444,14 @@ namespace Clara { maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { - Detail::Text usageText( it->commands(), Detail::TextAttributes() + Detail::Text usage( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); - for( std::size_t i = 0; i < (std::max)( usageText.size(), desc.size() ); ++i ) { - std::string usageCol = i < usageText.size() ? usageText[i] : ""; + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) @@ -4283,21 +4508,21 @@ namespace Clara { return oss.str(); } - ConfigT parse( int argc, char const * const * argv ) const { + ConfigT parse( std::vector const& args ) const { ConfigT config; - parseInto( argc, argv, config ); + parseInto( args, config ); return config; } - std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { - std::string processName = argv[0]; + std::vector parseInto( std::vector const& args, ConfigT& config ) const { + std::string processName = args[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; - parser.parseIntoTokens( argc, argv, tokens ); + parser.parseIntoTokens( args, tokens ); return populate( tokens, config ); } @@ -4328,7 +4553,7 @@ namespace Clara { arg.boundField.set( config, tokens[++i].data ); } else { - arg.boundField.setFlag( config ); + arg.boundField.set( config, "true" ); } break; } @@ -4471,6 +4696,21 @@ namespace Catch { ? ShowDurations::Always : ShowDurations::Never; } + inline void setUseColour( ConfigData& config, std::string const& value ) { + std::string mode = toLower( value ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); + } + inline void forceColour( ConfigData& config ) { + config.useColour = UseColour::Yes; + } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) @@ -4557,7 +4797,7 @@ namespace Catch { cli["-d"]["--durations"] .describe( "show test durations" ) - .bind( &setShowDurations, "yes/no" ); + .bind( &setShowDurations, "yes|no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) @@ -4585,8 +4825,12 @@ namespace Catch { .bind( &setRngSeed, "'time'|number" ); cli["--force-colour"] - .describe( "force colourised output" ) - .bind( &ConfigData::forceColour ); + .describe( "force colourised output (deprecated)" ) + .bind( &forceColour ); + + cli["--use-colour"] + .describe( "should output be colourised" ) + .bind( &setUseColour, "yes|no" ); return cli; } @@ -5017,6 +5261,8 @@ namespace Catch bool aborting; }; + class MultipleReporters; + struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); @@ -5044,6 +5290,8 @@ namespace Catch virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } }; struct IReporterFactory : IShared { @@ -5261,6 +5509,10 @@ namespace TestCaseTracking { virtual void addChild( Ptr const& child ) = 0; virtual ITracker* findChild( std::string const& name ) = 0; virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; }; class TrackerContext { @@ -5385,6 +5637,10 @@ namespace TestCaseTracking { m_parent->openChild(); } } + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } + virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } + void open() { m_runState = Executing; moveToThis(); @@ -5448,13 +5704,16 @@ namespace TestCaseTracking { {} virtual ~SectionTracker(); + virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } + static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { SectionTracker* section = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { - section = dynamic_cast( childTracker ); - assert( section ); + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = static_cast( childTracker ); } else { section = new SectionTracker( name, ctx, ¤tTracker ); @@ -5479,13 +5738,16 @@ namespace TestCaseTracking { {} virtual ~IndexTracker(); + virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } + static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { IndexTracker* tracker = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { - tracker = dynamic_cast( childTracker ); - assert( tracker ); + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = static_cast( childTracker ); } else { tracker = new IndexTracker( name, ctx, ¤tTracker, size ); @@ -5692,6 +5954,11 @@ namespace Catch { while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); + if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, @@ -6083,10 +6350,10 @@ namespace Catch { Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } - int applyCommandLine( int argc, char const* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); - m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); m_config.reset(); @@ -6110,7 +6377,7 @@ namespace Catch { m_config.reset(); } - int run( int argc, char const* const argv[] ) { + int run( int argc, char const* const* const argv ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) @@ -6180,13 +6447,31 @@ namespace Catch { #include #include +#ifdef CATCH_CPP14_OR_GREATER +#include +#endif + namespace Catch { - struct LexSort { - bool operator() (TestCase i,TestCase j) const { return (i + static void shuffle( V& vector ) { + RandomNumberGenerator rng; +#ifdef CATCH_CPP14_OR_GREATER + std::shuffle( vector.begin(), vector.end(), rng ); +#else + std::random_shuffle( vector.begin(), vector.end(), rng ); +#endif + } }; inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { @@ -6195,14 +6480,12 @@ namespace Catch { switch( config.runOrder() ) { case RunTests::InLexicographicalOrder: - std::sort( sorted.begin(), sorted.end(), LexSort() ); + std::sort( sorted.begin(), sorted.end() ); break; case RunTests::InRandomOrder: { seedRng( config ); - - RandomNumberGenerator rng; - std::random_shuffle( sorted.begin(), sorted.end(), rng ); + RandomNumberGenerator::shuffle( sorted ); } break; case RunTests::InDeclarationOrder: @@ -6221,13 +6504,15 @@ namespace Catch { it != itEnd; ++it ) { std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); - if( !prev.second ){ - Catch::cerr() - << Colour( Colour::Red ) - << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; - exit(1); + if( !prev.second ) { + std::ostringstream ss; + + ss << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + + throw std::runtime_error(ss.str()); } } } @@ -6815,7 +7100,18 @@ namespace { IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; - return &s_instance; + + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = !isDebuggerActive() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); } } // end anon namespace @@ -6866,7 +7162,14 @@ namespace { IColourImpl* platformColourInstance() { Ptr config = getCurrentContext().getConfig(); - return (config && config->forceColour()) || isatty(STDOUT_FILENO) + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance(); } @@ -6891,9 +7194,7 @@ namespace Catch { Colour::~Colour(){ if( !m_moved ) use( None ); } void Colour::use( Code _colourCode ) { - static IColourImpl* impl = isDebuggerActive() - ? NoColourImpl::instance() - : platformColourInstance(); + static IColourImpl* impl = platformColourInstance(); impl->use( _colourCode ); } @@ -7270,7 +7571,7 @@ namespace Catch { return os; } - Version libraryVersion( 1, 3, 3, "", 0 ); + Version libraryVersion( 1, 5, 6, "", 0 ); } @@ -8249,13 +8550,18 @@ public: // IStreamingReporter ++it ) (*it)->skipTest( testInfo ); } + + virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { + return this; + } + }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { Ptr resultingReporter; if( existingReporter ) { - MultipleReporters* multi = dynamic_cast( existingReporter.get() ); + MultipleReporters* multi = existingReporter->tryAsMulti(); if( !multi ) { multi = new MultipleReporters; resultingReporter = Ptr( multi ); @@ -8435,7 +8741,7 @@ namespace Catch { virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} - virtual bool assertionEnded( AssertionStats const& assertionStats ) { + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); @@ -9566,7 +9872,7 @@ namespace Catch { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } - else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " diff --git a/test/t/area/test_node_ref_segment.cpp b/test/t/area/test_node_ref_segment.cpp index 3261c24e1..5e33c2bae 100644 --- a/test/t/area/test_node_ref_segment.cpp +++ b/test/t/area/test_node_ref_segment.cpp @@ -20,93 +20,111 @@ TEST_CASE("NodeRefSegmentClass") { osmium::NodeRef nr3(3, { 1.2, 3.6 }); osmium::NodeRef nr4(4, { 1.2, 3.7 }); - NodeRefSegment s1(nr1, nr2, nullptr, nullptr); + NodeRefSegment s1(nr1, nr2); REQUIRE(s1.first().ref() == 1); REQUIRE(s1.second().ref() == 2); - NodeRefSegment s2(nr2, nr3, nullptr, nullptr); + NodeRefSegment s2(nr2, nr3); REQUIRE(s2.first().ref() == 3); REQUIRE(s2.second().ref() == 2); - NodeRefSegment s3(nr3, nr4, nullptr, nullptr); + NodeRefSegment s3(nr3, nr4); REQUIRE(s3.first().ref() == 3); REQUIRE(s3.second().ref() == 4); } SECTION("intersection") { - NodeRefSegment s1({ 1, {0.0, 0.0}}, { 2, {2.0, 2.0}}, nullptr, nullptr); - NodeRefSegment s2({ 3, {0.0, 2.0}}, { 4, {2.0, 0.0}}, nullptr, nullptr); - NodeRefSegment s3({ 5, {2.0, 0.0}}, { 6, {4.0, 2.0}}, nullptr, nullptr); - NodeRefSegment s4({ 7, {1.0, 0.0}}, { 8, {3.0, 2.0}}, nullptr, nullptr); - NodeRefSegment s5({ 9, {0.0, 4.0}}, {10, {4.0, 0.0}}, nullptr, nullptr); - NodeRefSegment s6({11, {0.0, 0.0}}, {12, {1.0, 1.0}}, nullptr, nullptr); - NodeRefSegment s7({13, {1.0, 1.0}}, {14, {3.0, 3.0}}, nullptr, nullptr); + NodeRefSegment s1({ 1, {0.0, 0.0}}, { 2, {2.0, 2.0}}); + NodeRefSegment s2({ 3, {0.0, 2.0}}, { 4, {2.0, 0.0}}); + NodeRefSegment s3({ 5, {2.0, 0.0}}, { 6, {4.0, 2.0}}); + NodeRefSegment s4({ 7, {1.0, 0.0}}, { 8, {3.0, 2.0}}); + NodeRefSegment s5({ 9, {0.0, 4.0}}, {10, {4.0, 0.0}}); + NodeRefSegment s6({11, {0.0, 0.0}}, {12, {1.0, 1.0}}); + NodeRefSegment s7({13, {1.0, 1.0}}, {14, {3.0, 3.0}}); REQUIRE(calculate_intersection(s1, s2) == osmium::Location(1.0, 1.0)); + REQUIRE(calculate_intersection(s2, s1) == osmium::Location(1.0, 1.0)); + REQUIRE(calculate_intersection(s1, s3) == osmium::Location()); + REQUIRE(calculate_intersection(s3, s1) == osmium::Location()); + REQUIRE(calculate_intersection(s2, s3) == osmium::Location()); + REQUIRE(calculate_intersection(s3, s2) == osmium::Location()); + REQUIRE(calculate_intersection(s1, s4) == osmium::Location()); + REQUIRE(calculate_intersection(s4, s1) == osmium::Location()); + REQUIRE(calculate_intersection(s1, s5) == osmium::Location(2.0, 2.0)); + REQUIRE(calculate_intersection(s5, s1) == osmium::Location(2.0, 2.0)); + + REQUIRE(calculate_intersection(s1, s6) == osmium::Location(1.0, 1.0)); + REQUIRE(calculate_intersection(s6, s1) == osmium::Location(1.0, 1.0)); + + REQUIRE(calculate_intersection(s1, s7) == osmium::Location(1.0, 1.0)); + REQUIRE(calculate_intersection(s7, s1) == osmium::Location(1.0, 1.0)); + + REQUIRE(calculate_intersection(s6, s7) == osmium::Location()); + REQUIRE(calculate_intersection(s7, s6) == osmium::Location()); + } + + SECTION("intersection of collinear segments") { + NodeRefSegment s1({ 1, {0.0, 0.0}}, { 2, {2.0, 0.0}}); // *---* + NodeRefSegment s2({ 3, {2.0, 0.0}}, { 4, {4.0, 0.0}}); // *---* + NodeRefSegment s3({ 5, {0.0, 0.0}}, { 6, {1.0, 0.0}}); // *-* + NodeRefSegment s4({ 7, {1.0, 0.0}}, { 8, {2.0, 0.0}}); // *-* + NodeRefSegment s5({ 9, {1.0, 0.0}}, {10, {3.0, 0.0}}); // *---* + NodeRefSegment s6({11, {0.0, 0.0}}, {12, {4.0, 0.0}}); // *-------* + NodeRefSegment s7({13, {0.0, 0.0}}, {14, {5.0, 0.0}}); // *---------* + NodeRefSegment s8({13, {1.0, 0.0}}, {14, {5.0, 0.0}}); // *-------* + NodeRefSegment s9({13, {3.0, 0.0}}, {14, {4.0, 0.0}}); // *-* + REQUIRE(calculate_intersection(s1, s1) == osmium::Location()); - REQUIRE(calculate_intersection(s1, s6) == osmium::Location()); - REQUIRE(calculate_intersection(s1, s7) == osmium::Location()); + + REQUIRE(calculate_intersection(s1, s2) == osmium::Location()); + REQUIRE(calculate_intersection(s2, s1) == osmium::Location()); + + REQUIRE(calculate_intersection(s1, s3) == osmium::Location(1.0, 0.0)); + REQUIRE(calculate_intersection(s3, s1) == osmium::Location(1.0, 0.0)); + + REQUIRE(calculate_intersection(s1, s4) == osmium::Location(1.0, 0.0)); + REQUIRE(calculate_intersection(s4, s1) == osmium::Location(1.0, 0.0)); + + REQUIRE(calculate_intersection(s1, s5) == osmium::Location(1.0, 0.0)); + REQUIRE(calculate_intersection(s5, s1) == osmium::Location(1.0, 0.0)); + + REQUIRE(calculate_intersection(s1, s6) == osmium::Location(2.0, 0.0)); + REQUIRE(calculate_intersection(s6, s1) == osmium::Location(2.0, 0.0)); + + REQUIRE(calculate_intersection(s1, s7) == osmium::Location(2.0, 0.0)); + REQUIRE(calculate_intersection(s7, s1) == osmium::Location(2.0, 0.0)); + + REQUIRE(calculate_intersection(s1, s8) == osmium::Location(1.0, 0.0)); + REQUIRE(calculate_intersection(s8, s1) == osmium::Location(1.0, 0.0)); + + REQUIRE(calculate_intersection(s1, s9) == osmium::Location()); + REQUIRE(calculate_intersection(s9, s1) == osmium::Location()); + + REQUIRE(calculate_intersection(s5, s6) == osmium::Location(1.0, 0.0)); + REQUIRE(calculate_intersection(s6, s5) == osmium::Location(1.0, 0.0)); + + REQUIRE(calculate_intersection(s7, s8) == osmium::Location(1.0, 0.0)); + REQUIRE(calculate_intersection(s8, s7) == osmium::Location(1.0, 0.0)); } SECTION("intersection of very long segments") { - NodeRefSegment s1({ 1, {90.0, 90.0}}, { 2, {-90.0, -90.0}}, nullptr, nullptr); - NodeRefSegment s2({ 1, {-90.0, 90.0}}, { 2, {90.0, -90.0}}, nullptr, nullptr); + NodeRefSegment s1({ 1, {90.0, 90.0}}, { 2, {-90.0, -90.0}}); + NodeRefSegment s2({ 1, {-90.0, 90.0}}, { 2, {90.0, -90.0}}); REQUIRE(calculate_intersection(s1, s2) == osmium::Location(0.0, 0.0)); - NodeRefSegment s3({ 1, {-90.0, -90.0}}, { 2, {90.0, 90.0}}, nullptr, nullptr); - NodeRefSegment s4({ 1, {-90.0, 90.0}}, { 2, {90.0, -90.0}}, nullptr, nullptr); + NodeRefSegment s3({ 1, {-90.0, -90.0}}, { 2, {90.0, 90.0}}); + NodeRefSegment s4({ 1, {-90.0, 90.0}}, { 2, {90.0, -90.0}}); REQUIRE(calculate_intersection(s3, s4) == osmium::Location(0.0, 0.0)); - NodeRefSegment s5({ 1, {-90.0000001, -90.0}}, { 2, {90.0, 90.0}}, nullptr, nullptr); - NodeRefSegment s6({ 1, {-90.0, 90.0}}, { 2, {90.0, -90.0}}, nullptr, nullptr); + NodeRefSegment s5({ 1, {-90.00000001, -90.0}}, { 2, {90.0, 90.0}}); + NodeRefSegment s6({ 1, {-90.0, 90.0}}, { 2, {90.0, -90.0}}); REQUIRE(calculate_intersection(s5, s6) == osmium::Location(0.0, 0.0)); } - SECTION("to_left_of") { - osmium::Location loc { 2.0, 2.0 }; - - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {0.0, 4.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {4.0, 0.0}}, {1, {4.0, 4.0}}, nullptr, nullptr).to_left_of(loc) == false); - REQUIRE(NodeRefSegment({0, {1.0, 0.0}}, {1, {1.0, 4.0}}, nullptr, nullptr).to_left_of(loc)); - - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {1.0, 4.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {2.0, 4.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {3.0, 4.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {4.0, 4.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {4.0, 3.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {1.0, 3.0}}, {1, {2.0, 0.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {1.0, 3.0}}, {1, {3.0, 1.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {1.0, 3.0}}, {1, {3.0, 2.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {0.0, 2.0}}, {1, {2.0, 2.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {2.0, 0.0}}, {1, {2.0, 4.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {2.0, 0.0}}, {1, {2.0, 2.0}}, nullptr, nullptr).to_left_of(loc) == false); - REQUIRE(NodeRefSegment({0, {2.0, 2.0}}, {1, {2.0, 4.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {0.0, 1.0}}, nullptr, nullptr).to_left_of(loc) == false); - REQUIRE(NodeRefSegment({0, {1.0, 0.0}}, {1, {0.0, 1.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {1.0, 3.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 2.0}}, {1, {2.0, 0.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 2.0}}, {1, {3.0, 4.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {1.0, 0.0}}, {1, {1.0, 2.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 2.0}}, {1, {1.0, 2.0}}, nullptr, nullptr).to_left_of(loc) == false); - REQUIRE(NodeRefSegment({0, {0.0, 2.0}}, {1, {1.0, 4.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {0.0, 0.0}}, {1, {0.0, 2.0}}, nullptr, nullptr).to_left_of(loc)); - REQUIRE(NodeRefSegment({0, {0.0, 2.0}}, {1, {4.0, 4.0}}, nullptr, nullptr).to_left_of(loc) == false); - - REQUIRE(NodeRefSegment({0, {0.0, 1.0}}, {1, {2.0, 2.0}}, nullptr, nullptr).to_left_of(loc) == false); - REQUIRE(NodeRefSegment({0, {2.0, 2.0}}, {1, {4.0, 0.0}}, nullptr, nullptr).to_left_of(loc) == false); - } - SECTION("ordering") { osmium::NodeRef node_ref1(1, { 1.0, 3.0 }); osmium::NodeRef node_ref2(2, { 1.4, 2.9 }); @@ -127,3 +145,64 @@ TEST_CASE("NodeRefSegmentClass") { } +TEST_CASE("Ordering of NodeRefSegments") { + osmium::NodeRef nr0(0, { 0.0, 0.0 }); + osmium::NodeRef nr1(1, { 1.0, 0.0 }); + osmium::NodeRef nr2(2, { 0.0, 1.0 }); + osmium::NodeRef nr3(3, { 2.0, 0.0 }); + osmium::NodeRef nr4(4, { 0.0, 2.0 }); + osmium::NodeRef nr5(5, { 1.0, 1.0 }); + osmium::NodeRef nr6(6, { 2.0, 2.0 }); + osmium::NodeRef nr7(6, { 1.0, 2.0 }); + + NodeRefSegment s1(nr0, nr1); + NodeRefSegment s2(nr0, nr2); + NodeRefSegment s3(nr0, nr3); + NodeRefSegment s4(nr0, nr4); + NodeRefSegment s5(nr0, nr5); + NodeRefSegment s6(nr0, nr6); + NodeRefSegment s7(nr0, nr7); + + // s1 + REQUIRE_FALSE(s1 < s1); + REQUIRE(s2 < s1); + REQUIRE(s1 < s3); + REQUIRE(s4 < s1); + REQUIRE(s5 < s1); + REQUIRE(s6 < s1); + REQUIRE(s7 < s1); + + // s2 + REQUIRE_FALSE(s2 < s2); + REQUIRE(s2 < s3); + REQUIRE(s2 < s4); + REQUIRE(s2 < s5); + REQUIRE(s2 < s6); + REQUIRE(s2 < s7); + + // s3 + REQUIRE_FALSE(s3 < s3); + REQUIRE(s4 < s3); + REQUIRE(s5 < s3); + REQUIRE(s6 < s3); + REQUIRE(s7 < s3); + + // s4 + REQUIRE_FALSE(s4 < s4); + REQUIRE(s4 < s5); + REQUIRE(s4 < s6); + REQUIRE(s4 < s7); + + // s5 + REQUIRE_FALSE(s5 < s5); + REQUIRE(s5 < s6); + REQUIRE(s7 < s5); + + // s6 + REQUIRE_FALSE(s6 < s6); + REQUIRE(s7 < s6); + + // s7 + REQUIRE_FALSE(s7 < s7); +} + diff --git a/test/t/basic/test_area.cpp b/test/t/basic/test_area.cpp new file mode 100644 index 000000000..f2847dcc1 --- /dev/null +++ b/test/t/basic/test_area.cpp @@ -0,0 +1,78 @@ +#include "catch.hpp" + +#include + +#include +#include +#include + +using namespace osmium::builder::attr; + +TEST_CASE("Build area") { + osmium::memory::Buffer buffer(10000); + + osmium::builder::add_area(buffer, + _id(17), + _version(3), + _visible(), + _cid(333), + _uid(21), + _timestamp(time_t(123)), + _user("foo"), + _tag("landuse", "forest"), + _tag("name", "Sherwood Forest"), + _outer_ring({ + {1, {3.2, 4.2}}, + {2, {3.5, 4.7}}, + {3, {3.6, 4.9}}, + {1, {3.2, 4.2}} + }), + _inner_ring({ + {5, {1.0, 1.0}}, + {6, {8.0, 1.0}}, + {7, {8.0, 8.0}}, + {8, {1.0, 8.0}}, + {5, {1.0, 1.0}} + }) + ); + + const osmium::Area& area = buffer.get(0); + + REQUIRE(17 == area.id()); + REQUIRE(3 == area.version()); + REQUIRE(true == area.visible()); + REQUIRE(333 == area.changeset()); + REQUIRE(21 == area.uid()); + REQUIRE(std::string("foo") == area.user()); + REQUIRE(123 == uint32_t(area.timestamp())); + REQUIRE(2 == area.tags().size()); + + int inner = 0; + int outer = 0; + for (const auto& subitem : area) { + switch (subitem.type()) { + case osmium::item_type::outer_ring: { + const auto& ring = static_cast(subitem); + REQUIRE(ring.size() == 4); + ++outer; + } + break; + case osmium::item_type::inner_ring: { + const auto& ring = static_cast(subitem); + REQUIRE(ring.size() == 5); + ++inner; + } + break; + default: + break; + } + } + + REQUIRE(outer == 1); + REQUIRE(inner == 1); + + osmium::CRC crc32; + crc32.update(area); + REQUIRE(crc32().checksum() == 0x2b2b7fa0); +} + diff --git a/test/t/basic/test_entity_bits.cpp b/test/t/basic/test_entity_bits.cpp index f15068b1c..13de94b5f 100644 --- a/test/t/basic/test_entity_bits.cpp +++ b/test/t/basic/test_entity_bits.cpp @@ -21,6 +21,7 @@ TEST_CASE("entity_bits") { REQUIRE(! (entities & osmium::osm_entity_bits::way)); REQUIRE(entities == osmium::osm_entity_bits::node); + REQUIRE(osmium::osm_entity_bits::nothing == osmium::osm_entity_bits::from_item_type(osmium::item_type::undefined)); REQUIRE(osmium::osm_entity_bits::node == osmium::osm_entity_bits::from_item_type(osmium::item_type::node)); REQUIRE(osmium::osm_entity_bits::way == osmium::osm_entity_bits::from_item_type(osmium::item_type::way)); REQUIRE(osmium::osm_entity_bits::relation == osmium::osm_entity_bits::from_item_type(osmium::item_type::relation)); diff --git a/test/t/basic/test_location.cpp b/test/t/basic/test_location.cpp index 3fd8d155a..dc5b37872 100644 --- a/test/t/basic/test_location.cpp +++ b/test/t/basic/test_location.cpp @@ -137,7 +137,7 @@ TEST_CASE("Location") { } SECTION("output_defined") { - osmium::Location p(-3.2, 47.3); + osmium::Location p(-3.20, 47.30); std::stringstream out; out << p; REQUIRE(out.str() == "(-3.2,47.3)"); @@ -152,3 +152,221 @@ TEST_CASE("Location") { } +TEST_CASE("Location hash") { + if (sizeof(size_t) == 8) { + REQUIRE(std::hash{}({0, 0}) == 0); + REQUIRE(std::hash{}({0, 1}) == 1); + REQUIRE(std::hash{}({1, 0}) == 0x100000000); + REQUIRE(std::hash{}({1, 1}) == 0x100000001); + } else { + REQUIRE(std::hash{}({0, 0}) == 0); + REQUIRE(std::hash{}({0, 1}) == 1); + REQUIRE(std::hash{}({1, 0}) == 1); + REQUIRE(std::hash{}({1, 1}) == 0); + } +} + +#define CR(s, v, r) { \ + const char* strm = "-" s; \ + const char* strp = strm + 1; \ + REQUIRE(std::atof(strp) == Approx( v / 10000000.0)); \ + REQUIRE(std::atof(strm) == Approx(-v / 10000000.0)); \ + const char** data = &strp; \ + REQUIRE(osmium::detail::string_to_location_coordinate(data) == v); \ + REQUIRE(std::string{*data} == r); \ + data = &strm; \ + REQUIRE(osmium::detail::string_to_location_coordinate(data) == -v); \ + REQUIRE(std::string{*data} == r); \ + } + +#define C(s, v) CR(s, v, "") + +#define F(s) { \ + const char* strm = "-" s; \ + const char* strp = strm + 1; \ + const char** data = &strp; \ + REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location); \ + data = &strm; \ + REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location); \ + } + +TEST_CASE("Parsing coordinates from strings") { + F("x"); + F("."); + F("--"); + F(""); + F(" "); + F(" 123"); + + CR("123 ", 1230000000, " "); + CR("123x", 1230000000, "x"); + CR("1.2x", 12000000, "x"); + + C("0", 0); + + C("1", 10000000); + C("2", 20000000); + + C("9", 90000000); + C("10", 100000000); + C("11", 110000000); + + C("90", 900000000); + C("100", 1000000000); + C("101", 1010000000); + + C("00", 0); + C("01", 10000000); + C("001", 10000000); + C("0001", 10000000); + + F("1234"); + F("1234."); + F("12345678901234567890"); + + C("0.", 0); + C("0.0", 0); + C("1.", 10000000); + C("1.0", 10000000); + C("1.2", 12000000); + C("0.1", 1000000); + C("0.01", 100000); + C("0.001", 10000); + C("0.0001", 1000); + C("0.00001", 100); + C("0.000001", 10); + C("0.0000001", 1); + + C("1.1234567", 11234567); + C("1.12345670", 11234567); + C("1.12345674", 11234567); + C("1.123456751", 11234568); + C("1.12345679", 11234568); + C("1.12345680", 11234568); + C("1.12345681", 11234568); + + C("180.0000000", 1800000000); + C("180.0000001", 1800000001); + C("179.9999999", 1799999999); + C("179.99999999", 1800000000); + C("200.123", 2001230000); + + C("1e2", 1000000000); + C("1e1", 100000000); + C("1e0", 10000000); + C("1e-1", 1000000); + C("1e-2", 100000); + C("1e-3", 10000); + C("1e-4", 1000); + C("1e-5", 100); + C("1e-6", 10); + C("1e-7", 1); + + C("1.0e2", 1000000000); + C("1.1e1", 110000000); + C("0.1e1", 10000000); + C("1.2e0", 12000000); + C("1.9e-1", 1900000); + C("2.0e-2", 200000); + C("2.1e-3", 21000); + C("9.0e-4", 9000); + C("9.1e-5", 910); + C("1.0e-6", 10); + C("1.0e-7", 1); + C("1.4e-7", 1); + C("1.5e-7", 2); + C("1.9e-7", 2); + C("0.5e-7", 1); + C("0.1e-7", 0); + C("0.0e-7", 0); + C("1.9e-8", 0); + C("1.9e-9", 0); + C("1.9e-10", 0); + + F("e"); + F(" e"); + F(" 1.1e2"); + F("1.0e3"); + F("5e4"); + F("5.0e2"); + F("3e2"); + F("1e"); + F("0.5e"); + F("1e10"); + + CR("1e2 ", 1000000000, " "); + CR("1.1e2 ", 1100000000, " "); + CR("1.1e2x", 1100000000, "x"); + CR("1.1e2:", 1100000000, ":"); +} + +#undef C +#undef CR +#undef F + +#define CW(v, s) buffer.clear(); \ + osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), v); \ + CHECK(buffer == s); \ + buffer.clear(); \ + osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), -v); \ + CHECK(buffer == "-" s); + +TEST_CASE("Writing coordinates into string") { + std::string buffer; + + osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), 0); + CHECK(buffer == "0"); + + CW( 10000000, "1"); + CW( 90000000, "9"); + CW( 100000000, "10"); + CW(1000000000, "100"); + CW(2000000000, "200"); + + CW( 1000000, "0.1"); + CW( 100000, "0.01"); + CW( 10000, "0.001"); + CW( 1000, "0.0001"); + CW( 100, "0.00001"); + CW( 10, "0.000001"); + CW( 1, "0.0000001"); + + CW( 1230000, "0.123"); + CW( 9999999, "0.9999999"); + CW( 40101010, "4.010101"); + CW( 494561234, "49.4561234"); + CW(1799999999, "179.9999999"); +} + +#undef CW + +TEST_CASE("set lon/lat from string") { + osmium::Location loc; + loc.set_lon("1.2"); + loc.set_lat("3.4"); + REQUIRE(loc.lon() == Approx(1.2)); + REQUIRE(loc.lat() == Approx(3.4)); +} + +TEST_CASE("set lon/lat from string with trailing characters") { + osmium::Location loc; + REQUIRE_THROWS_AS({ + loc.set_lon("1.2x"); + }, osmium::invalid_location); + REQUIRE_THROWS_AS({ + loc.set_lat("3.4e1 "); + }, osmium::invalid_location); +} + +TEST_CASE("set lon/lat from string with trailing characters using partial") { + osmium::Location loc; + const char* x = "1.2x"; + const char* y = "3.4 "; + loc.set_lon_partial(&x); + loc.set_lat_partial(&y); + REQUIRE(loc.lon() == Approx(1.2)); + REQUIRE(loc.lat() == Approx(3.4)); + REQUIRE(*x == 'x'); + REQUIRE(*y == ' '); +} + diff --git a/test/t/basic/test_node.cpp b/test/t/basic/test_node.cpp index b7f4c6cce..e5dbe8a7c 100644 --- a/test/t/basic/test_node.cpp +++ b/test/t/basic/test_node.cpp @@ -9,7 +9,7 @@ using namespace osmium::builder::attr; TEST_CASE("Build node") { - osmium::memory::Buffer buffer(10000); + osmium::memory::Buffer buffer{10000}; osmium::builder::add_node(buffer, _id(17), @@ -36,7 +36,7 @@ TEST_CASE("Build node") { REQUIRE(false == node.deleted()); REQUIRE(333 == node.changeset()); REQUIRE(21 == node.uid()); - REQUIRE(std::string("foo") == node.user()); + REQUIRE(std::string{"foo"} == node.user()); REQUIRE(123 == uint32_t(node.timestamp())); REQUIRE(osmium::Location(3.5, 4.7) == node.location()); REQUIRE(2 == node.tags().size()); @@ -51,7 +51,7 @@ TEST_CASE("Build node") { } TEST_CASE("default values for node attributes") { - osmium::memory::Buffer buffer(10000); + osmium::memory::Buffer buffer{10000}; osmium::builder::add_node(buffer, _id(0)); @@ -62,22 +62,23 @@ TEST_CASE("default values for node attributes") { REQUIRE(true == node.visible()); REQUIRE(0 == node.changeset()); REQUIRE(0 == node.uid()); - REQUIRE(std::string("") == node.user()); + REQUIRE(std::string{} == node.user()); REQUIRE(0 == uint32_t(node.timestamp())); REQUIRE(osmium::Location() == node.location()); REQUIRE(0 == node.tags().size()); } TEST_CASE("set node attributes from strings") { - osmium::memory::Buffer buffer(10000); + osmium::memory::Buffer buffer{10000}; osmium::builder::add_node(buffer, _id(0)); osmium::Node& node = buffer.get(0); node.set_id("-17") .set_version("3") - .set_visible(true) + .set_visible("true") .set_changeset("333") + .set_timestamp("2014-03-17T16:23:08Z") .set_uid("21"); REQUIRE(-17l == node.id()); @@ -85,11 +86,52 @@ TEST_CASE("set node attributes from strings") { REQUIRE(3 == node.version()); REQUIRE(true == node.visible()); REQUIRE(333 == node.changeset()); + REQUIRE(std::string{"2014-03-17T16:23:08Z"} == node.timestamp().to_iso()); REQUIRE(21 == node.uid()); } +TEST_CASE("set node attributes from strings using set_attribute()") { + osmium::memory::Buffer buffer{10000}; + + osmium::builder::add_node(buffer, _id(0)); + + osmium::Node& node = buffer.get(0); + node.set_attribute("id", "-17") + .set_attribute("version", "3") + .set_attribute("visible", "true") + .set_attribute("changeset", "333") + .set_attribute("timestamp", "2014-03-17T16:23:08Z") + .set_attribute("uid", "21"); + + REQUIRE(-17l == node.id()); + REQUIRE(17ul == node.positive_id()); + REQUIRE(3 == node.version()); + REQUIRE(true == node.visible()); + REQUIRE(333 == node.changeset()); + REQUIRE(std::string{"2014-03-17T16:23:08Z"} == node.timestamp().to_iso()); + REQUIRE(21 == node.uid()); +} + +TEST_CASE("Setting attributes from bad data on strings should fail") { + osmium::memory::Buffer buffer{10000}; + + osmium::builder::add_node(buffer, _id(0)); + + osmium::Node& node = buffer.get(0); + REQUIRE_THROWS(node.set_id("bar")); + REQUIRE_THROWS(node.set_id("123x")); + REQUIRE_THROWS(node.set_version("123x")); + REQUIRE_THROWS(node.set_visible("foo")); + REQUIRE_THROWS(node.set_changeset("123x")); + REQUIRE_THROWS(node.set_changeset("NULL")); + REQUIRE_THROWS(node.set_timestamp("2014-03-17T16:23:08Zx")); + REQUIRE_THROWS(node.set_timestamp("2014-03-17T16:23:99Z")); + REQUIRE_THROWS(node.set_uid("123x")); + REQUIRE_THROWS(node.set_uid("anonymous")); +} + TEST_CASE("set large id") { - osmium::memory::Buffer buffer(10000); + osmium::memory::Buffer buffer{10000}; int64_t id = 3000000000l; osmium::builder::add_node(buffer, _id(id)); @@ -104,7 +146,7 @@ TEST_CASE("set large id") { } TEST_CASE("set tags on node") { - osmium::memory::Buffer buffer(10000); + osmium::memory::Buffer buffer{10000}; osmium::builder::add_node(buffer, _user("foo"), @@ -114,11 +156,11 @@ TEST_CASE("set tags on node") { const osmium::Node& node = buffer.get(0); REQUIRE(nullptr == node.tags().get_value_by_key("fail")); - REQUIRE(std::string("pub") == node.tags().get_value_by_key("amenity")); - REQUIRE(std::string("pub") == node.get_value_by_key("amenity")); + REQUIRE(std::string{"pub"} == node.tags().get_value_by_key("amenity")); + REQUIRE(std::string{"pub"} == node.get_value_by_key("amenity")); - REQUIRE(std::string("default") == node.tags().get_value_by_key("fail", "default")); - REQUIRE(std::string("pub") == node.tags().get_value_by_key("amenity", "default")); - REQUIRE(std::string("pub") == node.get_value_by_key("amenity", "default")); + REQUIRE(std::string{"default"} == node.tags().get_value_by_key("fail", "default")); + REQUIRE(std::string{"pub"} == node.tags().get_value_by_key("amenity", "default")); + REQUIRE(std::string{"pub"} == node.get_value_by_key("amenity", "default")); } diff --git a/test/t/basic/test_object_comparisons.cpp b/test/t/basic/test_object_comparisons.cpp index 461969fe9..42bcbff63 100644 --- a/test/t/basic/test_object_comparisons.cpp +++ b/test/t/basic/test_object_comparisons.cpp @@ -1,76 +1,80 @@ #include "catch.hpp" +#include +#include +#include + #include #include #include #include -TEST_CASE("Object_Comparisons") { +using namespace osmium::builder::attr; - using namespace osmium::builder::attr; +TEST_CASE("Node comparisons") { - SECTION("order") { - osmium::memory::Buffer buffer(10 * 1000); + osmium::memory::Buffer buffer(10 * 1000); + std::vector> nodes; - osmium::builder::add_node(buffer, _id(10), _version(1)); - osmium::builder::add_node(buffer, _id(15), _version(2)); + SECTION("nodes are ordered by id, version, and timestamp") { + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 0), _version(2), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 1), _version(2), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 10), _version(2), _timestamp("2016-01-01T00:01:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 10), _version(3), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 12), _version(2), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 12), _version(2), _timestamp("2016-01-01T00:01:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 15), _version(1), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id(10000000000ll), _version(2), _timestamp("2016-01-01T00:00:00Z")))); - auto it = buffer.begin(); - osmium::Node& node1 = static_cast(*it); - osmium::Node& node2 = static_cast(*(++it)); - - REQUIRE(node1 < node2); - REQUIRE_FALSE(node1 > node2); - node1.set_id(20); - node1.set_version(1); - node2.set_id(20); - node2.set_version(2); - REQUIRE(node1 < node2); - REQUIRE_FALSE(node1 > node2); - node1.set_id(-10); - node1.set_version(2); - node2.set_id(-15); - node2.set_version(1); - REQUIRE(node1 < node2); - REQUIRE_FALSE(node1 > node2); + REQUIRE(std::is_sorted(nodes.cbegin(), nodes.cend())); } - SECTION("order_types") { - osmium::memory::Buffer buffer(10 * 1000); + SECTION("equal nodes are not different") { + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id(1), _version(2), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id(1), _version(2), _timestamp("2016-01-01T00:00:00Z")))); - osmium::builder::add_node(buffer, _id(3), _version(3)); - osmium::builder::add_node(buffer, _id(3), _version(4)); - osmium::builder::add_node(buffer, _id(3), _version(4)); - osmium::builder::add_way(buffer, _id(2), _version(2)); - osmium::builder::add_relation(buffer, _id(1), _version(1)); + REQUIRE(nodes[0] == nodes[1]); + REQUIRE_FALSE(nodes[0] < nodes[1]); + REQUIRE_FALSE(nodes[0] > nodes[1]); + } - auto it = buffer.begin(); - const osmium::Node& node1 = static_cast(*it); - const osmium::Node& node2 = static_cast(*(++it)); - const osmium::Node& node3 = static_cast(*(++it)); - const osmium::Way& way = static_cast(*(++it)); - const osmium::Relation& relation = static_cast(*(++it)); + SECTION("IDs are ordered by absolute value") { + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 0)))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 1)))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( -1)))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 10)))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id(-10)))); - REQUIRE(node1 < node2); - REQUIRE(node2 < way); - REQUIRE_FALSE(node2 > way); - REQUIRE(way < relation); - REQUIRE(node1 < relation); + REQUIRE(std::is_sorted(nodes.cbegin(), nodes.cend())); + } - REQUIRE(osmium::object_order_type_id_version()(node1, node2)); - REQUIRE(osmium::object_order_type_id_reverse_version()(node2, node1)); - REQUIRE(osmium::object_order_type_id_version()(node1, way)); - REQUIRE(osmium::object_order_type_id_reverse_version()(node1, way)); + SECTION("reverse version ordering") { + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 0), _version(2), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 1), _version(2), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 10), _version(3), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 10), _version(2), _timestamp("2016-01-01T00:01:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 12), _version(2), _timestamp("2016-01-01T00:01:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 12), _version(2), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id( 15), _version(1), _timestamp("2016-01-01T00:00:00Z")))); + nodes.emplace_back(buffer.get(osmium::builder::add_node(buffer, _id(10000000000ll), _version(2), _timestamp("2016-01-01T00:00:00Z")))); - REQUIRE_FALSE(osmium::object_equal_type_id_version()(node1, node2)); - REQUIRE(osmium::object_equal_type_id_version()(node2, node3)); - - REQUIRE(osmium::object_equal_type_id()(node1, node2)); - REQUIRE(osmium::object_equal_type_id()(node2, node3)); - - REQUIRE_FALSE(osmium::object_equal_type_id_version()(node1, way)); - REQUIRE_FALSE(osmium::object_equal_type_id_version()(node1, relation)); - REQUIRE_FALSE(osmium::object_equal_type_id()(node1, relation)); + REQUIRE(std::is_sorted(nodes.cbegin(), nodes.cend(), osmium::object_order_type_id_reverse_version{})); } } + +TEST_CASE("Object comparisons") { + + osmium::memory::Buffer buffer(10 * 1000); + std::vector> objects; + + SECTION("types are ordered nodes, then ways, then relations") { + objects.emplace_back(buffer.get( osmium::builder::add_node( buffer, _id(3)))); + objects.emplace_back(buffer.get( osmium::builder::add_way( buffer, _id(2)))); + objects.emplace_back(buffer.get(osmium::builder::add_relation(buffer, _id(1)))); + + REQUIRE(std::is_sorted(objects.cbegin(), objects.cend())); + } + +} + diff --git a/test/t/basic/test_timestamp.cpp b/test/t/basic/test_timestamp.cpp index f80ffcf5f..561a705b5 100644 --- a/test/t/basic/test_timestamp.cpp +++ b/test/t/basic/test_timestamp.cpp @@ -75,3 +75,47 @@ TEST_CASE("Timestamp") { } } + +TEST_CASE("Valid timestamps") { + + std::vector test_cases = { + "1970-01-01T00:00:01Z", + "2000-01-01T00:00:00Z", + "2006-12-31T23:59:59Z", + "2030-12-31T23:59:59Z", + "2016-02-28T23:59:59Z", + "2016-03-31T23:59:59Z" + }; + + for (const auto& tc : test_cases) { + osmium::Timestamp t{tc}; + REQUIRE(tc == t.to_iso()); + } + +} + +TEST_CASE("Invalid timestamps") { + REQUIRE_THROWS_AS(osmium::Timestamp{""}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"x"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"xxxxxxxxxxxxxxxxxxxx"}, std::invalid_argument); + + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01x00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:00x"}, std::invalid_argument); + + REQUIRE_THROWS_AS(osmium::Timestamp{"2000x01-01T00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01x01T00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00x00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00x00Z"}, std::invalid_argument); + + REQUIRE_THROWS_AS(osmium::Timestamp{"0000-00-01T00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-00-01T00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-00T00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T24:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:60:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:61Z"}, std::invalid_argument); + + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-32T00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-02-30T00:00:00Z"}, std::invalid_argument); + REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-32T00:00:00Z"}, std::invalid_argument); +} + diff --git a/test/t/basic/test_types_from_string.cpp b/test/t/basic/test_types_from_string.cpp index 2481ae876..2866b8f74 100644 --- a/test/t/basic/test_types_from_string.cpp +++ b/test/t/basic/test_types_from_string.cpp @@ -31,9 +31,17 @@ TEST_CASE("set type and ID from string") { REQUIRE(r_2.first == osmium::item_type::relation); REQUIRE(r_2.second == -2); - auto x3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr); - REQUIRE(x3.first == osmium::item_type::undefined); - REQUIRE(x3.second == 3); + auto d3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr); + REQUIRE(d3.first == osmium::item_type::undefined); + REQUIRE(d3.second == 3); + + auto u3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr, osmium::item_type::undefined); + REQUIRE(u3.first == osmium::item_type::undefined); + REQUIRE(u3.second == 3); + + auto n3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr, osmium::item_type::node); + REQUIRE(n3.first == osmium::item_type::node); + REQUIRE(n3.second == 3); REQUIRE_THROWS_AS(osmium::string_to_object_id("", osmium::osm_entity_bits::nwr), std::range_error); REQUIRE_THROWS_AS(osmium::string_to_object_id("n", osmium::osm_entity_bits::nwr), std::range_error); diff --git a/test/t/basic/test_way.cpp b/test/t/basic/test_way.cpp index c4fa9d542..005ef3000 100644 --- a/test/t/basic/test_way.cpp +++ b/test/t/basic/test_way.cpp @@ -46,7 +46,7 @@ TEST_CASE("Build way") { osmium::CRC crc32; crc32.update(way); - REQUIRE(crc32().checksum() == 0x7676d0c2); + REQUIRE(crc32().checksum() == 0x65f6ba91); } TEST_CASE("build closed way") { diff --git a/test/t/builder/test_attr.cpp b/test/t/builder/test_attr.cpp index 258ae7a91..dedde77a7 100644 --- a/test/t/builder/test_attr.cpp +++ b/test/t/builder/test_attr.cpp @@ -67,14 +67,14 @@ TEST_CASE("create node using builders") { osmium::builder::add_node(buffer, _id(5), _visible(true)); osmium::builder::add_node(buffer, _id(6), _visible(false)); - auto it = buffer.cbegin(); + auto it = buffer.select().cbegin(); REQUIRE_FALSE(it++->visible()); REQUIRE_FALSE(it++->visible()); REQUIRE(it++->visible()); REQUIRE(it++->visible()); REQUIRE(it++->visible()); REQUIRE_FALSE(it++->visible()); - REQUIRE(it == buffer.cend()); + REQUIRE(it == buffer.select().cend()); } SECTION("order of attributes doesn't matter") { diff --git a/test/t/geom/test_geojson.cpp b/test/t/geom/test_geojson.cpp index a9e839822..725be7fb9 100644 --- a/test/t/geom/test_geojson.cpp +++ b/test/t/geom/test_geojson.cpp @@ -96,7 +96,7 @@ SECTION("area_1outer_0inner") { REQUIRE(!area.is_multipolygon()); REQUIRE(std::distance(area.cbegin(), area.cend()) == 2); - REQUIRE(std::distance(area.cbegin(), area.cend()) == area.num_rings().first); + REQUIRE(area.subitems().size() == area.num_rings().first); { std::string json {factory.create_multipolygon(area)}; @@ -112,8 +112,8 @@ SECTION("area_1outer_1inner") { REQUIRE(!area.is_multipolygon()); REQUIRE(std::distance(area.cbegin(), area.cend()) == 3); - REQUIRE(std::distance(area.cbegin(), area.cend()) == area.num_rings().first); - REQUIRE(std::distance(area.cbegin(), area.cend()) == area.num_rings().second); + REQUIRE(area.subitems().size() == area.num_rings().first); + REQUIRE(area.subitems().size() == area.num_rings().second); { std::string json {factory.create_multipolygon(area)}; @@ -129,24 +129,24 @@ SECTION("area_2outer_2inner") { REQUIRE(area.is_multipolygon()); REQUIRE(std::distance(area.cbegin(), area.cend()) == 5); - REQUIRE(std::distance(area.cbegin(), area.cend()) == area.num_rings().first); - REQUIRE(std::distance(area.cbegin(), area.cend()) == area.num_rings().second); + REQUIRE(area.subitems().size() == area.num_rings().first); + REQUIRE(area.subitems().size() == area.num_rings().second); int outer_ring=0; int inner_ring=0; - for (auto it_outer = area.cbegin(); it_outer != area.cend(); ++it_outer) { + for (const auto& outer : area.outer_rings()) { if (outer_ring == 0) { - REQUIRE(it_outer->front().ref() == 1); + REQUIRE(outer.front().ref() == 1); } else if (outer_ring == 1) { - REQUIRE(it_outer->front().ref() == 100); + REQUIRE(outer.front().ref() == 100); } else { REQUIRE(false); } - for (auto it_inner = area.inner_ring_cbegin(it_outer); it_inner != area.inner_ring_cend(it_outer); ++it_inner) { + for (const auto& inner : area.inner_rings(outer)) { if (outer_ring == 0 && inner_ring == 0) { - REQUIRE(it_inner->front().ref() == 5); + REQUIRE(inner.front().ref() == 5); } else if (outer_ring == 0 && inner_ring == 1) { - REQUIRE(it_inner->front().ref() == 10); + REQUIRE(inner.front().ref() == 10); } else { REQUIRE(false); } diff --git a/test/t/geom/test_geos.cpp b/test/t/geom/test_geos.cpp index 8bf11c93e..f74027c24 100644 --- a/test/t/geom/test_geos.cpp +++ b/test/t/geom/test_geos.cpp @@ -1,6 +1,7 @@ #include "catch.hpp" #include +#include #include "area_helper.hpp" #include "wnl_helper.hpp" @@ -11,16 +12,16 @@ TEST_CASE("GEOS geometry factory - create point") { std::unique_ptr point {factory.create_point(osmium::Location(3.2, 4.2))}; REQUIRE(3.2 == point->getX()); REQUIRE(4.2 == point->getY()); - REQUIRE(-1 == point->getSRID()); + REQUIRE(4326 == point->getSRID()); } -TEST_CASE("GEOS geometry factory - create point with non-default srid") { - osmium::geom::GEOSFactory<> factory(4326); +TEST_CASE("GEOS geometry factory - create point in web mercator") { + osmium::geom::GEOSFactory factory; std::unique_ptr point {factory.create_point(osmium::Location(3.2, 4.2))}; - REQUIRE(3.2 == point->getX()); - REQUIRE(4.2 == point->getY()); - REQUIRE(4326 == point->getSRID()); + REQUIRE(Approx(356222.3705384755l) == point->getX()); + REQUIRE(Approx(467961.143605213l) == point->getY()); + REQUIRE(3857 == point->getSRID()); } TEST_CASE("GEOS geometry factory - create point with externally created GEOS factory") { @@ -89,6 +90,7 @@ TEST_CASE("GEOS geometry factory - create area with one outer and no inner rings REQUIRE(1 == mp->getNumGeometries()); const geos::geom::Polygon* p0 = dynamic_cast(mp->getGeometryN(0)); + REQUIRE(p0); REQUIRE(0 == p0->getNumInteriorRing()); const geos::geom::LineString* l0e = p0->getExteriorRing(); @@ -108,6 +110,7 @@ TEST_CASE("GEOS geometry factory - create area with one outer and one inner ring REQUIRE(1 == mp->getNumGeometries()); const geos::geom::Polygon* p0 = dynamic_cast(mp->getGeometryN(0)); + REQUIRE(p0); REQUIRE(1 == p0->getNumInteriorRing()); const geos::geom::LineString* l0e = p0->getExteriorRing(); @@ -127,12 +130,14 @@ TEST_CASE("GEOS geometry factory - create area with two outer and two inner ring REQUIRE(2 == mp->getNumGeometries()); const geos::geom::Polygon* p0 = dynamic_cast(mp->getGeometryN(0)); + REQUIRE(p0); REQUIRE(2 == p0->getNumInteriorRing()); const geos::geom::LineString* l0e = p0->getExteriorRing(); REQUIRE(5 == l0e->getNumPoints()); const geos::geom::Polygon* p1 = dynamic_cast(mp->getGeometryN(1)); + REQUIRE(p1); REQUIRE(0 == p1->getNumInteriorRing()); const geos::geom::LineString* l1e = p1->getExteriorRing(); diff --git a/test/t/geom/test_ogr.cpp b/test/t/geom/test_ogr.cpp index 26d34c13a..3490c5759 100644 --- a/test/t/geom/test_ogr.cpp +++ b/test/t/geom/test_ogr.cpp @@ -68,6 +68,7 @@ SECTION("area_1outer_0inner") { REQUIRE(1 == mp->getNumGeometries()); const OGRPolygon* p0 = dynamic_cast(mp->getGeometryRef(0)); + REQUIRE(p0); REQUIRE(0 == p0->getNumInteriorRings()); const OGRLineString* l0e = p0->getExteriorRing(); @@ -86,6 +87,7 @@ SECTION("area_1outer_1inner") { REQUIRE(1 == mp->getNumGeometries()); const OGRPolygon* p0 = dynamic_cast(mp->getGeometryRef(0)); + REQUIRE(p0); REQUIRE(1 == p0->getNumInteriorRings()); const OGRLineString* l0e = p0->getExteriorRing(); @@ -105,12 +107,14 @@ SECTION("area_2outer_2inner") { REQUIRE(2 == mp->getNumGeometries()); const OGRPolygon* p0 = dynamic_cast(mp->getGeometryRef(0)); + REQUIRE(p0); REQUIRE(2 == p0->getNumInteriorRings()); const OGRLineString* l0e = p0->getExteriorRing(); REQUIRE(5 == l0e->getNumPoints()); const OGRPolygon* p1 = dynamic_cast(mp->getGeometryRef(1)); + REQUIRE(p1); REQUIRE(0 == p1->getNumInteriorRings()); const OGRLineString* l1e = p1->getExteriorRing(); diff --git a/test/t/geom/test_tile.cpp b/test/t/geom/test_tile.cpp index e80cb9604..5454fed79 100644 --- a/test/t/geom/test_tile.cpp +++ b/test/t/geom/test_tile.cpp @@ -8,86 +8,96 @@ #include "test_tile_data.hpp" -TEST_CASE("Tile") { +TEST_CASE("Tile from x0.0 y0.0 at zoom 0") { + osmium::Location l{0.0, 0.0}; - SECTION("x0.0 y0.0 zoom 0") { - osmium::Location l(0.0, 0.0); - - osmium::geom::Tile t(0, l); - - REQUIRE(t.x == 0); - REQUIRE(t.y == 0); - REQUIRE(t.z == 0); - } - - SECTION("x180.0 y90.0 zoom 0") { - osmium::Location l(180.0, 90.0); - - osmium::geom::Tile t(0, l); - - REQUIRE(t.x == 0); - REQUIRE(t.y == 0); - REQUIRE(t.z == 0); - } - - SECTION("x180.0 y90.0 zoom 4") { - osmium::Location l(180.0, 90.0); - - osmium::geom::Tile t(4, l); - - REQUIRE(t.x == (1 << 4) - 1); - REQUIRE(t.y == 0); - REQUIRE(t.z == 4); - } - - SECTION("x0.0 y0.0 zoom 4") { - osmium::Location l(0.0, 0.0); - - osmium::geom::Tile t(4, l); - - auto n = 1 << (4-1); - REQUIRE(t.x == n); - REQUIRE(t.y == n); - REQUIRE(t.z == 4); - } - - SECTION("equality") { - osmium::geom::Tile a(4, 3, 4); - osmium::geom::Tile b(4, 3, 4); - osmium::geom::Tile c(4, 4, 3); - REQUIRE(a == b); - REQUIRE(a != c); - REQUIRE(b != c); - } - - SECTION("order") { - osmium::geom::Tile a(2, 3, 4); - osmium::geom::Tile b(4, 3, 4); - osmium::geom::Tile c(4, 4, 3); - osmium::geom::Tile d(4, 4, 2); - REQUIRE(a < b); - REQUIRE(a < c); - REQUIRE(b < c); - REQUIRE(d < c); - } - - SECTION("tilelist") { - std::istringstream input_data(s); - while (input_data) { - double lon, lat; - uint32_t x, y, zoom; - input_data >> lon; - input_data >> lat; - input_data >> x; - input_data >> y; - input_data >> zoom; - - osmium::Location l(lon, lat); - osmium::geom::Tile t(zoom, l); - REQUIRE(t.x == x); - REQUIRE(t.y == y); - } - } + osmium::geom::Tile t{0, l}; + REQUIRE(t.x == 0); + REQUIRE(t.y == 0); + REQUIRE(t.z == 0); + REQUIRE(t.valid()); +} + +TEST_CASE("Tile from x180.0 y90.0 at zoom 0") { + osmium::Location l{180.0, 90.0}; + + osmium::geom::Tile t{0, l}; + + REQUIRE(t.x == 0); + REQUIRE(t.y == 0); + REQUIRE(t.z == 0); + REQUIRE(t.valid()); +} + +TEST_CASE("Tile from x180.0 y90.0 at zoom 4") { + osmium::Location l{180.0, 90.0}; + + osmium::geom::Tile t{4, l}; + + REQUIRE(t.x == (1 << 4) - 1); + REQUIRE(t.y == 0); + REQUIRE(t.z == 4); + REQUIRE(t.valid()); +} + +TEST_CASE("Tile from x0.0 y0.0 at zoom 4") { + osmium::Location l{0.0, 0.0}; + + osmium::geom::Tile t{4, l}; + + auto n = 1 << (4-1); + REQUIRE(t.x == n); + REQUIRE(t.y == n); + REQUIRE(t.z == 4); + REQUIRE(t.valid()); +} + +TEST_CASE("Tile from max values at zoom 4") { + osmium::geom::Tile t{4u, 15u, 15u}; + REQUIRE(t.valid()); +} + +TEST_CASE("Tile from max values at zoom 30") { + osmium::geom::Tile t{30u, (1u<<30) - 1, (1u<<30) - 1}; + REQUIRE(t.valid()); +} + +TEST_CASE("Tile equality") { + osmium::geom::Tile a{4, 3, 4}; + osmium::geom::Tile b{4, 3, 4}; + osmium::geom::Tile c{4, 4, 3}; + REQUIRE(a == b); + REQUIRE(a != c); + REQUIRE(b != c); +} + +TEST_CASE("Tile order") { + osmium::geom::Tile a{4, 3, 4}; + osmium::geom::Tile b{6, 3, 4}; + osmium::geom::Tile c{6, 4, 3}; + osmium::geom::Tile d{6, 4, 2}; + REQUIRE(a < b); + REQUIRE(a < c); + REQUIRE(b < c); + REQUIRE(d < c); +} + +TEST_CASE("Check a random list of tiles") { + std::istringstream input_data(s); + while (input_data) { + double lon, lat; + uint32_t x, y, zoom; + input_data >> lon; + input_data >> lat; + input_data >> x; + input_data >> y; + input_data >> zoom; + + osmium::Location l{lon, lat}; + osmium::geom::Tile t{zoom, l}; + REQUIRE(t.x == x); + REQUIRE(t.y == y); + } } diff --git a/test/t/geom/test_wkb.cpp b/test/t/geom/test_wkb.cpp index 49710cbc2..d4d922868 100644 --- a/test/t/geom/test_wkb.cpp +++ b/test/t/geom/test_wkb.cpp @@ -1,115 +1,135 @@ #include "catch.hpp" +#include #include #include "wnl_helper.hpp" #if __BYTE_ORDER == __LITTLE_ENDIAN -TEST_CASE("WKB_Geometry_byte_order_dependent") { +TEST_CASE("WKB geometry factory (byte-order-dependant), points") { + const osmium::Location loc{3.2, 4.2}; -SECTION("point") { - osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); + SECTION("point") { + osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); - std::string wkb {factory.create_point(osmium::Location(3.2, 4.2))}; - REQUIRE(std::string{"01010000009A99999999990940CDCCCCCCCCCC1040"} == wkb); -} - -SECTION("point_ewkb") { - osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex); - - std::string wkb {factory.create_point(osmium::Location(3.2, 4.2))}; - REQUIRE(std::string{"0101000020E61000009A99999999990940CDCCCCCCCCCC1040"} == wkb); -} - -SECTION("linestring") { - osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); - - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_okay(buffer); - - { - std::string wkb {factory.create_linestring(wnl)}; - REQUIRE(std::string{"0102000000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"} == wkb); + const std::string wkb{factory.create_point(loc)}; + REQUIRE(wkb == "01010000009A99999999990940CDCCCCCCCCCC1040"); } - { - std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)}; - REQUIRE(std::string{"010200000003000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040"} == wkb); + SECTION("point in web mercator") { + osmium::geom::WKBFactory factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); + + const std::string wkb{factory.create_point(loc)}; + REQUIRE(wkb == "010100000028706E7BF9BD1541B03E0D93E48F1C41"); } - { - std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; - REQUIRE(std::string{"0102000000040000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"} == wkb); + SECTION("point in ewkb") { + osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex); + + const std::string wkb{factory.create_point(loc)}; + REQUIRE(wkb == "0101000020E61000009A99999999990940CDCCCCCCCCCC1040"); } - { - std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; - REQUIRE(std::string{"010200000004000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040"} == wkb); + SECTION("point in ewkb in web mercator") { + osmium::geom::WKBFactory factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex); + + const std::string wkb{factory.create_point(loc)}; + REQUIRE(wkb == "0101000020110F000028706E7BF9BD1541B03E0D93E48F1C41"); } + } -SECTION("linestring_ewkb") { - osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex); +TEST_CASE("WKB geometry factory (byte-order-dependant)") { - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_okay(buffer); + osmium::memory::Buffer buffer{10000}; - std::string ewkb {factory.create_linestring(wnl)}; - REQUIRE(std::string{"0102000020E6100000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"} == ewkb); -} + SECTION("linestring") { + osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); + const auto& wnl = create_test_wnl_okay(buffer); -SECTION("linestring_with_two_same_locations") { - osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); + { + const std::string wkb{factory.create_linestring(wnl)}; + REQUIRE(wkb == "0102000000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"); + } - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_same_location(buffer); + { + const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)}; + REQUIRE(wkb == "010200000003000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040"); + } - REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); + { + const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; + REQUIRE(wkb == "0102000000040000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"); + } - { - std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; - REQUIRE(std::string{"0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240"} == wkb); + { + const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; + REQUIRE(wkb == "010200000004000000CDCCCCCCCCCC0C409A999999999913400000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC12409A99999999990940CDCCCCCCCCCC1040"); + } } - { - std::string wkb {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; - REQUIRE(std::string{"0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240"} == wkb); + SECTION("linestring as ewkb") { + osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::ewkb, osmium::geom::out_type::hex); + + const auto& wnl = create_test_wnl_okay(buffer); + + const std::string ewkb{factory.create_linestring(wnl)}; + REQUIRE(ewkb == "0102000020E6100000030000009A99999999990940CDCCCCCCCCCC10400000000000000C40CDCCCCCCCCCC1240CDCCCCCCCCCC0C409A99999999991340"); } -} -SECTION("linestring_with_undefined_location") { - osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); + SECTION("linestring with two same locations") { + osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_undefined_location(buffer); + const auto& wnl = create_test_wnl_same_location(buffer); - REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location); -} + SECTION("unique forwards (default)") { + REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); + } + + SECTION("unique backwards") { + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); + } + + SECTION("all forwards") { + const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; + REQUIRE(wkb == "0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240"); + } + + SECTION("all backwards") { + const std::string wkb{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; + REQUIRE(wkb == "0102000000020000000000000000000C40CDCCCCCCCCCC12400000000000000C40CDCCCCCCCCCC1240"); + } + } + + SECTION("linestring with undefined location") { + osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); + + const auto& wnl = create_test_wnl_undefined_location(buffer); + + REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location); + } } #endif -TEST_CASE("WKB_Geometry_byte_order_independent") { +TEST_CASE("WKB geometry (byte-order-independent)") { -SECTION("empty_point") { osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); - REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location); -} + SECTION("empty point") { + REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), osmium::invalid_location); + } -SECTION("empty_linestring") { - osmium::geom::WKBFactory<> factory(osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex); + SECTION("empty linestring") { + osmium::memory::Buffer buffer{10000}; + const auto& wnl = create_test_wnl_empty(buffer); - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_empty(buffer); - - REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error); - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error); -} + REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error); + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error); + } } diff --git a/test/t/geom/test_wkt.cpp b/test/t/geom/test_wkt.cpp index 55ccb4ada..f6913c40b 100644 --- a/test/t/geom/test_wkt.cpp +++ b/test/t/geom/test_wkt.cpp @@ -1,136 +1,133 @@ #include "catch.hpp" +#include #include #include "area_helper.hpp" #include "wnl_helper.hpp" -TEST_CASE("WKT_Geometry") { +TEST_CASE("WKT geometry for point") { -SECTION("point") { osmium::geom::WKTFactory<> factory; - std::string wkt {factory.create_point(osmium::Location(3.2, 4.2))}; - REQUIRE(std::string{"POINT(3.2 4.2)"} == wkt); + SECTION("point") { + const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})}; + REQUIRE(wkt == "POINT(3.2 4.2)"); + } + + SECTION("empty point") { + REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location); + } + } -SECTION("empty_point") { +TEST_CASE("WKT geometry for point in ekwt") { + osmium::geom::WKTFactory<> factory(7, osmium::geom::wkt_type::ewkt); + + const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})}; + REQUIRE(wkt == "SRID=4326;POINT(3.2 4.2)"); +} + +TEST_CASE("WKT geometry for point in ekwt in web mercator") { + osmium::geom::WKTFactory factory(2, osmium::geom::wkt_type::ewkt); + + const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})}; + REQUIRE(wkt == "SRID=3857;POINT(356222.37 467961.14)"); +} + +TEST_CASE("WKT geometry factory") { osmium::geom::WKTFactory<> factory; - REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location); -} + osmium::memory::Buffer buffer{10000}; -SECTION("linestring") { - osmium::geom::WKTFactory<> factory; + SECTION("linestring") { + const auto& wnl = create_test_wnl_okay(buffer); - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_okay(buffer); + SECTION("unique forwards (default)") { + const std::string wkt{factory.create_linestring(wnl)}; + REQUIRE(wkt == "LINESTRING(3.2 4.2,3.5 4.7,3.6 4.9)"); + } - { - std::string wkt {factory.create_linestring(wnl)}; - REQUIRE(std::string{"LINESTRING(3.2 4.2,3.5 4.7,3.6 4.9)"} == wkt); + SECTION("unique backwards") { + const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)}; + REQUIRE(wkt == "LINESTRING(3.6 4.9,3.5 4.7,3.2 4.2)"); + } + + SECTION("all forwards") { + const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; + REQUIRE(wkt == "LINESTRING(3.2 4.2,3.5 4.7,3.5 4.7,3.6 4.9)"); + } + + SECTION("all backwards") { + const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; + REQUIRE(wkt == "LINESTRING(3.6 4.9,3.5 4.7,3.5 4.7,3.2 4.2)"); + } } - { - std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)}; - REQUIRE(std::string{"LINESTRING(3.6 4.9,3.5 4.7,3.2 4.2)"} == wkt); + SECTION("empty linestring") { + const auto& wnl = create_test_wnl_empty(buffer); + + REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error); + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error); } - { - std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; - REQUIRE(std::string{"LINESTRING(3.2 4.2,3.5 4.7,3.5 4.7,3.6 4.9)"} == wkt); + SECTION("linestring with two same locations") { + const auto& wnl = create_test_wnl_same_location(buffer); + + SECTION("unique forwards") { + REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); + + try { + factory.create_linestring(wnl); + } catch (const osmium::geometry_error& e) { + REQUIRE(e.id() == 0); + REQUIRE(std::string(e.what()) == "need at least two points for linestring"); + } + } + + SECTION("unique backwards") { + REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); + } + + SECTION("all forwards") { + const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; + REQUIRE(wkt == "LINESTRING(3.5 4.7,3.5 4.7)"); + } + + SECTION("all backwards") { + const std::string wkt{factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; + REQUIRE(wkt == "LINESTRING(3.5 4.7,3.5 4.7)"); + } } - { - std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; - REQUIRE(std::string{"LINESTRING(3.6 4.9,3.5 4.7,3.5 4.7,3.2 4.2)"} == wkt); - } -} + SECTION("linestring with undefined location") { + const auto& wnl = create_test_wnl_undefined_location(buffer); -SECTION("empty_linestring") { - osmium::geom::WKTFactory<> factory; - - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_empty(buffer); - - REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error); - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error); -} - -SECTION("linestring_with_two_same_locations") { - osmium::geom::WKTFactory<> factory; - - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_same_location(buffer); - - REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error); - - try { - factory.create_linestring(wnl); - } catch (osmium::geometry_error& e) { - REQUIRE(e.id() == 0); - REQUIRE(std::string(e.what()) == "need at least two points for linestring"); + REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location); } - REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error); + SECTION("area with one outer and no inner rings") { + const osmium::Area& area = create_test_area_1outer_0inner(buffer); - { - std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all)}; - REQUIRE(std::string{"LINESTRING(3.5 4.7,3.5 4.7)"} == wkt); + const std::string wkt{factory.create_multipolygon(area)}; + REQUIRE(wkt == "MULTIPOLYGON(((3.2 4.2,3.5 4.7,3.6 4.9,3.2 4.2)))"); } - { - std::string wkt {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)}; - REQUIRE(std::string{"LINESTRING(3.5 4.7,3.5 4.7)"} == wkt); + SECTION("area with one outer and one inner ring") { + const osmium::Area& area = create_test_area_1outer_1inner(buffer); + + const std::string wkt{factory.create_multipolygon(area)}; + REQUIRE(wkt == "MULTIPOLYGON(((0.1 0.1,9.1 0.1,9.1 9.1,0.1 9.1,0.1 0.1),(1 1,8 1,8 8,1 8,1 1)))"); } -} -SECTION("linestring_with_undefined_location") { - osmium::geom::WKTFactory<> factory; + SECTION("area with two outer and two inner rings") { + const osmium::Area& area = create_test_area_2outer_2inner(buffer); - osmium::memory::Buffer buffer(10000); - auto &wnl = create_test_wnl_undefined_location(buffer); - - REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location); -} - -SECTION("area_1outer_0inner") { - osmium::geom::WKTFactory<> factory; - - osmium::memory::Buffer buffer(10000); - const osmium::Area& area = create_test_area_1outer_0inner(buffer); - - { - std::string wkt {factory.create_multipolygon(area)}; - REQUIRE(std::string{"MULTIPOLYGON(((3.2 4.2,3.5 4.7,3.6 4.9,3.2 4.2)))"} == wkt); + const std::string wkt{factory.create_multipolygon(area)}; + REQUIRE(wkt == "MULTIPOLYGON(((0.1 0.1,9.1 0.1,9.1 9.1,0.1 9.1,0.1 0.1),(1 1,4 1,4 4,1 4,1 1),(5 5,5 7,7 7,5 5)),((10 10,11 10,11 11,10 11,10 10)))"); } -} - -SECTION("area_1outer_1inner") { - osmium::geom::WKTFactory<> factory; - - osmium::memory::Buffer buffer(10000); - const osmium::Area& area = create_test_area_1outer_1inner(buffer); - - { - std::string wkt {factory.create_multipolygon(area)}; - REQUIRE(std::string{"MULTIPOLYGON(((0.1 0.1,9.1 0.1,9.1 9.1,0.1 9.1,0.1 0.1),(1 1,8 1,8 8,1 8,1 1)))"} == wkt); - } -} - -SECTION("area_2outer_2inner") { - osmium::geom::WKTFactory<> factory; - - osmium::memory::Buffer buffer(10000); - const osmium::Area& area = create_test_area_2outer_2inner(buffer); - - { - std::string wkt {factory.create_multipolygon(area)}; - REQUIRE(std::string{"MULTIPOLYGON(((0.1 0.1,9.1 0.1,9.1 9.1,0.1 9.1,0.1 0.1),(1 1,4 1,4 4,1 4,1 1),(5 5,5 7,7 7,5 5)),((10 10,11 10,11 11,10 11,10 10)))"} == wkt); - } -} } diff --git a/test/t/index/test_file_based_index.cpp b/test/t/index/test_file_based_index.cpp new file mode 100644 index 000000000..42cf57456 --- /dev/null +++ b/test/t/index/test_file_based_index.cpp @@ -0,0 +1,155 @@ + +#include "catch.hpp" + +#include +#include +#include +#include + +#include +#include + +#include + +TEST_CASE("File based index") { + + int fd = osmium::detail::create_tmp_file(); + + REQUIRE(osmium::util::file_size(fd) == 0); + + const osmium::unsigned_object_id_type id1 = 6; + const osmium::unsigned_object_id_type id2 = 3; + const osmium::Location loc1(1.2, 4.5); + const osmium::Location loc2(3.5, -7.2); + + SECTION("dense index") { + using index_type = osmium::index::map::DenseFileArray; + constexpr const size_t S = sizeof(index_type::element_type); + + { + index_type index(fd); + + REQUIRE(index.size() == 0); + + REQUIRE_THROWS_AS(index.get( 0), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 1), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 3), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 5), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 6), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 7), osmium::not_found); + REQUIRE_THROWS_AS(index.get(100), osmium::not_found); + + index.set(id1, loc1); + REQUIRE(index.size() == 7); + + index.set(id2, loc2); + REQUIRE(index.size() == 7); + + index.sort(); + + REQUIRE(loc1 == index.get(id1)); + REQUIRE(loc2 == index.get(id2)); + + REQUIRE_THROWS_AS(index.get( 0), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 1), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 5), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 7), osmium::not_found); + REQUIRE_THROWS_AS(index.get(100), osmium::not_found); + + REQUIRE(index.size() == 7); + REQUIRE(std::distance(index.cbegin(), index.cend()) == 7); + + REQUIRE(osmium::util::file_size(fd) >= (6 * S)); + } + + { + index_type index(fd); + REQUIRE(osmium::util::file_size(fd) >= (6 * S)); + + REQUIRE(index.size() == 7); + + REQUIRE(loc1 == index.get(id1)); + REQUIRE(loc2 == index.get(id2)); + + REQUIRE_THROWS_AS(index.get( 0), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 1), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 5), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 7), osmium::not_found); + REQUIRE_THROWS_AS(index.get(100), osmium::not_found); + + REQUIRE(index.size() == 7); + REQUIRE(std::distance(index.cbegin(), index.cend()) == 7); + + auto it = index.cbegin(); + REQUIRE(*it++ == osmium::Location{}); + REQUIRE(*it++ == osmium::Location{}); + REQUIRE(*it++ == osmium::Location{}); + REQUIRE(*it++ == loc2); + REQUIRE(*it++ == osmium::Location{}); + REQUIRE(*it++ == osmium::Location{}); + REQUIRE(*it++ == loc1); + REQUIRE(it++ == index.cend()); + } + } + + SECTION("sparse index") { + using index_type = osmium::index::map::SparseFileArray; + constexpr const size_t S = sizeof(index_type::element_type); + + { + index_type index(fd); + + REQUIRE(index.size() == 0); + + REQUIRE_THROWS_AS(index.get( 0), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 1), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 3), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 5), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 6), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 7), osmium::not_found); + REQUIRE_THROWS_AS(index.get(100), osmium::not_found); + + index.set(id1, loc1); + REQUIRE(index.size() == 1); + + index.set(id2, loc2); + REQUIRE(index.size() == 2); + + index.sort(); + + REQUIRE(loc1 == index.get(id1)); + REQUIRE(loc2 == index.get(id2)); + + REQUIRE_THROWS_AS(index.get( 0), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 1), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 5), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 7), osmium::not_found); + REQUIRE_THROWS_AS(index.get(100), osmium::not_found); + + REQUIRE(index.size() == 2); + REQUIRE(std::distance(index.cbegin(), index.cend()) == 2); + + REQUIRE(osmium::util::file_size(fd) >= (2 * S)); + } + + { + index_type index(fd); + REQUIRE(osmium::util::file_size(fd) >= (2 * S)); + + REQUIRE(index.size() == 2); + + REQUIRE(loc1 == index.get(id1)); + REQUIRE(loc2 == index.get(id2)); + + REQUIRE_THROWS_AS(index.get( 0), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 1), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 5), osmium::not_found); + REQUIRE_THROWS_AS(index.get( 7), osmium::not_found); + REQUIRE_THROWS_AS(index.get(100), osmium::not_found); + + REQUIRE(index.size() == 2); + REQUIRE(std::distance(index.cbegin(), index.cend()) == 2); + } + } +} + diff --git a/test/t/index/test_id_to_location.cpp b/test/t/index/test_id_to_location.cpp index 4aca238b6..810ef3be9 100644 --- a/test/t/index/test_id_to_location.cpp +++ b/test/t/index/test_id_to_location.cpp @@ -19,8 +19,8 @@ template void test_func_all(TIndex& index) { osmium::unsigned_object_id_type id1 = 12; osmium::unsigned_object_id_type id2 = 3; - osmium::Location loc1(1.2, 4.5); - osmium::Location loc2(3.5, -7.2); + osmium::Location loc1{1.2, 4.5}; + osmium::Location loc2{3.5, -7.2}; REQUIRE_THROWS_AS(index.get(id1), osmium::not_found); @@ -29,6 +29,8 @@ void test_func_all(TIndex& index) { index.sort(); + REQUIRE_THROWS_AS(index.get(0), osmium::not_found); + REQUIRE_THROWS_AS(index.get(1), osmium::not_found); REQUIRE_THROWS_AS(index.get(5), osmium::not_found); REQUIRE_THROWS_AS(index.get(100), osmium::not_found); } @@ -37,8 +39,8 @@ template void test_func_real(TIndex& index) { osmium::unsigned_object_id_type id1 = 12; osmium::unsigned_object_id_type id2 = 3; - osmium::Location loc1(1.2, 4.5); - osmium::Location loc2(3.5, -7.2); + osmium::Location loc1{1.2, 4.5}; + osmium::Location loc2{3.5, -7.2}; index.set(id1, loc1); index.set(id2, loc2); @@ -48,18 +50,26 @@ void test_func_real(TIndex& index) { REQUIRE(loc1 == index.get(id1)); REQUIRE(loc2 == index.get(id2)); + REQUIRE_THROWS_AS(index.get(0), osmium::not_found); + REQUIRE_THROWS_AS(index.get(1), osmium::not_found); REQUIRE_THROWS_AS(index.get(5), osmium::not_found); REQUIRE_THROWS_AS(index.get(100), osmium::not_found); index.clear(); REQUIRE_THROWS_AS(index.get(id1), osmium::not_found); + REQUIRE_THROWS_AS(index.get(id2), osmium::not_found); + + REQUIRE_THROWS_AS(index.get(0), osmium::not_found); + REQUIRE_THROWS_AS(index.get(1), osmium::not_found); + REQUIRE_THROWS_AS(index.get(5), osmium::not_found); + REQUIRE_THROWS_AS(index.get(100), osmium::not_found); } TEST_CASE("IdToLocation") { SECTION("Dummy") { - typedef osmium::index::map::Dummy index_type; + using index_type = osmium::index::map::Dummy; index_type index1; @@ -73,7 +83,7 @@ TEST_CASE("IdToLocation") { } SECTION("DenseMemArray") { - typedef osmium::index::map::DenseMemArray index_type; + using index_type = osmium::index::map::DenseMemArray; index_type index1; index1.reserve(1000); @@ -86,7 +96,7 @@ TEST_CASE("IdToLocation") { #ifdef __linux__ SECTION("DenseMmapArray") { - typedef osmium::index::map::DenseMmapArray index_type; + using index_type = osmium::index::map::DenseMmapArray; index_type index1; test_func_all(index1); @@ -99,7 +109,7 @@ TEST_CASE("IdToLocation") { #endif SECTION("DenseFileArray") { - typedef osmium::index::map::DenseFileArray index_type; + using index_type = osmium::index::map::DenseFileArray; index_type index1; test_func_all(index1); @@ -111,7 +121,7 @@ TEST_CASE("IdToLocation") { #ifdef OSMIUM_WITH_SPARSEHASH SECTION("SparseMemTable") { - typedef osmium::index::map::SparseMemTable index_type; + using index_type = osmium::index::map::SparseMemTable; index_type index1; test_func_all(index1); @@ -123,7 +133,7 @@ TEST_CASE("IdToLocation") { #endif SECTION("SparseMemMap") { - typedef osmium::index::map::SparseMemMap index_type; + using index_type = osmium::index::map::SparseMemMap; index_type index1; test_func_all(index1); @@ -133,7 +143,7 @@ TEST_CASE("IdToLocation") { } SECTION("SparseMemArray") { - typedef osmium::index::map::SparseMemArray index_type; + using index_type = osmium::index::map::SparseMemArray; index_type index1; @@ -149,10 +159,10 @@ TEST_CASE("IdToLocation") { } SECTION("Dynamic map choice") { - typedef osmium::index::map::Map map_type; + using map_type = osmium::index::map::Map; const auto& map_factory = osmium::index::MapFactory::instance(); - std::vector map_type_names = map_factory.map_types(); + const std::vector map_type_names = map_factory.map_types(); REQUIRE(map_type_names.size() >= 5); for (const auto& map_type_name : map_type_names) { diff --git a/test/t/io/test_opl_parser.cpp b/test/t/io/test_opl_parser.cpp new file mode 100644 index 000000000..9ad6eeb75 --- /dev/null +++ b/test/t/io/test_opl_parser.cpp @@ -0,0 +1,1075 @@ + +#include +#include + +#include "catch.hpp" + +#include +#include + +namespace oid = osmium::io::detail; + +TEST_CASE("Parse OPL: base exception") { + osmium::opl_error e{"foo"}; + REQUIRE(e.data == nullptr); + REQUIRE(e.line == 0); + REQUIRE(e.column == 0); + REQUIRE(e.msg == "OPL error: foo"); + REQUIRE(std::string{e.what()} == "OPL error: foo"); +} + +TEST_CASE("Parse OPL: exception with line and column") { + const char* d = "data"; + osmium::opl_error e{"bar", d}; + e.set_pos(17, 23); + REQUIRE(e.data == d); + REQUIRE(e.line == 17); + REQUIRE(e.column == 23); + REQUIRE(e.msg == "OPL error: bar on line 17 column 23"); + REQUIRE(std::string{e.what()} == "OPL error: bar on line 17 column 23"); +} + +TEST_CASE("Parse OPL: space") { + std::string d{"a b \t c"}; + + const char* s = d.data(); + REQUIRE_THROWS_AS({ + oid::opl_parse_space(&s); + }, osmium::opl_error); + + s = d.data() + 1; + oid::opl_parse_space(&s); + REQUIRE(*s == 'b'); + + REQUIRE_THROWS_AS({ + oid::opl_parse_space(&s); + }, osmium::opl_error); + + ++s; + oid::opl_parse_space(&s); + REQUIRE(*s == 'c'); +} + +TEST_CASE("Parse OPL: check for space") { + REQUIRE(oid::opl_non_empty("aaa")); + REQUIRE(oid::opl_non_empty("a b")); + REQUIRE_FALSE(oid::opl_non_empty(" ")); + REQUIRE_FALSE(oid::opl_non_empty(" x")); + REQUIRE_FALSE(oid::opl_non_empty("\tx")); + REQUIRE_FALSE(oid::opl_non_empty("")); +} + +TEST_CASE("Parse OPL: skip section") { + std::string d{"abcd efgh"}; + const char* skip1 = d.data() + 4; + const char* skip2 = d.data() + 9; + const char* s = d.data(); + REQUIRE(oid::opl_skip_section(&s) == skip1); + REQUIRE(s == skip1); + ++s; + REQUIRE(oid::opl_skip_section(&s) == skip2); + REQUIRE(s == skip2); +} + +TEST_CASE("Parse OPL: parse escaped") { + std::string result; + + SECTION("Empty string") { + const char* s = ""; + REQUIRE_THROWS_WITH({ + oid::opl_parse_escaped(&s, result); + }, "OPL error: eol"); + } + + SECTION("Illegal character for hex") { + const char* s = "x"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_escaped(&s, result); + }, "OPL error: not a hex char"); + } + + SECTION("Illegal character for hex after legal hex characters") { + const char* s = "0123x"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_escaped(&s, result); + }, "OPL error: not a hex char"); + } + + SECTION("Too long") { + const char* s = "123456780"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_escaped(&s, result); + }, "OPL error: hex escape too long"); + } + + SECTION("No data") { + const char* s = "%"; + const char* e = s + std::strlen(s); + oid::opl_parse_escaped(&s, result); + REQUIRE(result.size() == 1); + REQUIRE(result[0] == '\0'); + REQUIRE(s == e); + } + + SECTION("One hex char") { + const char* s = "9%"; + const char* e = s + std::strlen(s); + oid::opl_parse_escaped(&s, result); + REQUIRE(result.size() == 1); + REQUIRE(result == "\t"); + REQUIRE(s == e); + } + + SECTION("Two hex chars (lowercase)") { + const char* s = "3c%"; + const char* e = s + std::strlen(s); + oid::opl_parse_escaped(&s, result); + REQUIRE(result.size() == 1); + REQUIRE(result == "<"); + REQUIRE(s == e); + } + + SECTION("Two hex char (uppercase)") { + const char* s = "3C%"; + const char* e = s + std::strlen(s); + oid::opl_parse_escaped(&s, result); + REQUIRE(result.size() == 1); + REQUIRE(result == "<"); + REQUIRE(s == e); + } + + SECTION("Longer unicode characters") { + const char* s1 = "30dc%"; + oid::opl_parse_escaped(&s1, result); + result.append("_"); + const char* s2 = "1d11e%"; + oid::opl_parse_escaped(&s2, result); + result.append("_"); + const char* s3 = "1f6eb%"; + oid::opl_parse_escaped(&s3, result); + REQUIRE(result == u8"\u30dc_\U0001d11e_\U0001f6eb"); + } + + SECTION("Data after %") { + const char* s = "5a%abc"; + oid::opl_parse_escaped(&s, result); + REQUIRE(result.size() == 1); + REQUIRE(result == "Z"); + REQUIRE(std::string{s} == "abc"); + } + +} + +TEST_CASE("Parse OPL: parse string") { + std::string result; + + SECTION("empty string") { + const char* s = ""; + const char* e = s + std::strlen(s); + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 0); + REQUIRE(result == ""); + REQUIRE(s == e); + } + + SECTION("normal string") { + const char* s = "foo"; + const char* e = s + std::strlen(s); + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 3); + REQUIRE(result == "foo"); + REQUIRE(s == e); + } + + SECTION("string with space") { + const char* s = "foo bar"; + const char* e = s + 3; + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 3); + REQUIRE(result == "foo"); + REQUIRE(s == e); + } + + SECTION("string with tab") { + const char* s = "foo\tbar"; + const char* e = s + 3; + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 3); + REQUIRE(result == "foo"); + REQUIRE(s == e); + } + + SECTION("string with comma") { + const char* s = "foo,bar"; + const char* e = s + 3; + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 3); + REQUIRE(result == "foo"); + REQUIRE(s == e); + } + + SECTION("string with equal sign") { + const char* s = "foo=bar"; + const char* e = s + 3; + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 3); + REQUIRE(result == "foo"); + REQUIRE(s == e); + } + + SECTION("string with escaped characters") { + const char* s = "foo%3d%bar"; + const char* e = s + std::strlen(s); + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 7); + REQUIRE(result == "foo=bar"); + REQUIRE(s == e); + } + + SECTION("string with escaped characters at end") { + const char* s = "foo%3d%"; + const char* e = s + std::strlen(s); + oid::opl_parse_string(&s, result); + REQUIRE(result.size() == 4); + REQUIRE(result == "foo="); + REQUIRE(s == e); + } + + SECTION("string with invalid escaping") { + const char* s = "foo%"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_string(&s, result); + }, "OPL error: eol"); + } + + SECTION("string with invalid escaped characters") { + const char* s = "foo%x%"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_string(&s, result); + }, "OPL error: not a hex char"); + } + +} + +template +T test_parse_int(const char* s) { + auto r = oid::opl_parse_int(&s); + REQUIRE(*s == 'x'); + return r; +} + +TEST_CASE("Parse OPL: integer") { + REQUIRE(test_parse_int("0x") == 0); + REQUIRE(test_parse_int("-0x") == 0); + REQUIRE(test_parse_int("1x") == 1); + REQUIRE(test_parse_int("17x") == 17); + REQUIRE(test_parse_int("-1x") == -1); + REQUIRE(test_parse_int("1234567890123x") == 1234567890123); + REQUIRE(test_parse_int("-1234567890123x") == -1234567890123); + + REQUIRE_THROWS_WITH({ + test_parse_int(""); + }, "OPL error: expected integer"); + + REQUIRE_THROWS_WITH({ + test_parse_int("-x"); + }, "OPL error: expected integer"); + + REQUIRE_THROWS_WITH({ + test_parse_int(" 1"); + }, "OPL error: expected integer"); + + REQUIRE_THROWS_WITH({ + test_parse_int("x"); + }, "OPL error: expected integer"); + + REQUIRE_THROWS_WITH({ + test_parse_int("99999999999999999999999x"); + }, "OPL error: integer too long"); +} + +TEST_CASE("Parse OPL: int32_t") { + REQUIRE(test_parse_int("0x") == 0); + REQUIRE(test_parse_int("123x") == 123); + REQUIRE(test_parse_int("-123x") == -123); + + REQUIRE_THROWS_WITH({ + test_parse_int("12345678901x"); + }, "OPL error: integer too long"); + REQUIRE_THROWS_WITH({ + test_parse_int("-12345678901x"); + }, "OPL error: integer too long"); +} + +TEST_CASE("Parse OPL: uint32_t") { + REQUIRE(test_parse_int("0x") == 0); + REQUIRE(test_parse_int("123x") == 123); + + REQUIRE_THROWS_WITH({ + test_parse_int("-123x"); + }, "OPL error: integer too long"); + + REQUIRE_THROWS_WITH({ + test_parse_int("12345678901x"); + }, "OPL error: integer too long"); + + REQUIRE_THROWS_WITH({ + test_parse_int("-12345678901x"); + }, "OPL error: integer too long"); +} + +TEST_CASE("Parse OPL: visible flag") { + const char* data = "V"; + const char* e = data + std::strlen(data); + REQUIRE(oid::opl_parse_visible(&data)); + REQUIRE(e == data); + +} + +TEST_CASE("Parse OPL: deleted flag") { + const char* data = "D"; + const char* e = data + std::strlen(data); + REQUIRE_FALSE(oid::opl_parse_visible(&data)); + REQUIRE(e == data); +} + +TEST_CASE("Parse OPL: invalid visible flag") { + const char* data = "x"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_visible(&data); + }, "OPL error: invalid visible flag"); +} + +TEST_CASE("Parse OPL: timestamp (empty)") { + const char* data = ""; + const char* e = data + std::strlen(data); + REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{}); + REQUIRE(e == data); +} + +TEST_CASE("Parse OPL: timestamp (space)") { + const char* data = " "; + const char* e = data; + REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{}); + REQUIRE(e == data); +} + +TEST_CASE("Parse OPL: timestamp (tab)") { + const char* data = "\t"; + const char* e = data; + REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{}); + REQUIRE(e == data); +} + +TEST_CASE("Parse OPL: timestamp (invalid)") { + const char* data = "abc"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_timestamp(&data); + }, "OPL error: can not parse timestamp"); +} + +TEST_CASE("Parse OPL: timestamp (valid)") { + const char* data = "2016-03-04T17:28:03Z"; + const char* e = data + std::strlen(data); + REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{"2016-03-04T17:28:03Z"}); + REQUIRE(e == data); +} + +TEST_CASE("Parse OPL: valid timestamp with trailing data") { + const char* data = "2016-03-04T17:28:03Zxxx"; + REQUIRE(oid::opl_parse_timestamp(&data) == osmium::Timestamp{"2016-03-04T17:28:03Z"}); + REQUIRE(std::string{data} == "xxx"); +} + +TEST_CASE("Parse OPL: tags") { + osmium::memory::Buffer buffer{1024}; + + SECTION("Empty") { + const char* data = ""; + REQUIRE_THROWS_WITH({ + oid::opl_parse_tags(data, buffer); + }, "OPL error: expected '='"); + } + + SECTION("One tag") { + const char* data = "foo=bar"; + oid::opl_parse_tags(data, buffer); + const auto& taglist = buffer.get(0); + REQUIRE(taglist.size() == 1); + REQUIRE(std::string{taglist.begin()->key()} == "foo"); + REQUIRE(std::string{taglist.begin()->value()} == "bar"); + } + + SECTION("Empty key and value are allowed") { + const char* data = "="; + oid::opl_parse_tags(data, buffer); + const auto& taglist = buffer.get(0); + REQUIRE(taglist.size() == 1); + REQUIRE(std::string{taglist.begin()->key()} == ""); + REQUIRE(std::string{taglist.begin()->value()} == ""); + } + + SECTION("Multiple tags") { + const char* data = "highway=residential,oneway=yes,maxspeed=30"; + oid::opl_parse_tags(data, buffer); + const auto& taglist = buffer.get(0); + REQUIRE(taglist.size() == 3); + auto it = taglist.cbegin(); + REQUIRE(std::string{it->key()} == "highway"); + REQUIRE(std::string{it->value()} == "residential"); + ++it; + REQUIRE(std::string{it->key()} == "oneway"); + REQUIRE(std::string{it->value()} == "yes"); + ++it; + REQUIRE(std::string{it->key()} == "maxspeed"); + REQUIRE(std::string{it->value()} == "30"); + ++it; + REQUIRE(it == taglist.cend()); + } + + SECTION("No equal signs") { + const char* data = "a"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_tags(data, buffer); + }, "OPL error: expected '='"); + } + + SECTION("Two equal signs") { + const char* data = "a=b=c"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_tags(data, buffer); + }, "OPL error: expected ','"); + } + +} + +TEST_CASE("Parse OPL: nodes") { + osmium::memory::Buffer buffer{1024}; + + SECTION("Empty") { + const char* const s = ""; + oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() == 0); + } + + SECTION("Invalid format, missing n") { + const char* const s = "xyz"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer); + }, "OPL error: expected 'n'"); + } + + SECTION("Invalid format, missing ID") { + const char* const s = "nx"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer); + }, "OPL error: expected integer"); + } + + SECTION("Valid format: one node") { + const char* const s = "n123"; + oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& wnl = buffer.get(0); + REQUIRE(wnl.size() == 1); + REQUIRE(wnl.begin()->ref() == 123); + } + + SECTION("Valid format: two nodes") { + const char* const s = "n123,n456"; + oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& wnl = buffer.get(0); + REQUIRE(wnl.size() == 2); + auto it = wnl.begin(); + REQUIRE(it->ref() == 123); + ++it; + REQUIRE(it->ref() == 456); + ++it; + REQUIRE(it == wnl.end()); + } + + SECTION("Trailing comma") { + const char* const s = "n123,n456,"; + oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& wnl = buffer.get(0); + REQUIRE(wnl.size() == 2); + auto it = wnl.begin(); + REQUIRE(it->ref() == 123); + ++it; + REQUIRE(it->ref() == 456); + ++it; + REQUIRE(it == wnl.end()); + } + + SECTION("Way nodes with coordinates") { + const char* const s = "n123x1.2y3.4,n456x33y0.1"; + oid::opl_parse_way_nodes(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& wnl = buffer.get(0); + REQUIRE(wnl.size() == 2); + auto it = wnl.begin(); + REQUIRE(it->ref() == 123); + const osmium::Location loc1{1.2, 3.4}; + REQUIRE(it->location() == loc1); + ++it; + REQUIRE(it->ref() == 456); + const osmium::Location loc2{33.0, 0.1}; + REQUIRE(it->location() == loc2); + ++it; + REQUIRE(it == wnl.end()); + } + +} + +TEST_CASE("Parse OPL: members") { + osmium::memory::Buffer buffer{1024}; + + SECTION("Empty") { + const char* const s = ""; + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() == 0); + } + + SECTION("Invalid: unknown object type") { + const char* const s = "x123@foo"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + }, "OPL error: unknown object type"); + } + + SECTION("Invalid: illegal ref") { + const char* const s = "wx"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + }, "OPL error: expected integer"); + } + + SECTION("Invalid: missing @") { + const char* const s = "n123foo"; + REQUIRE_THROWS_WITH({ + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + }, "OPL error: expected '@'"); + } + + SECTION("Valid format: one member") { + const char* const s = "n123@foo"; + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& rml = buffer.get(0); + REQUIRE(rml.size() == 1); + auto it = rml.begin(); + REQUIRE(it->type() == osmium::item_type::node); + REQUIRE(it->ref() == 123); + REQUIRE(std::string{it->role()} == "foo"); + ++it; + REQUIRE(it == rml.end()); + } + + SECTION("Valid format: one member without role") { + const char* const s = "n123@"; + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& rml = buffer.get(0); + REQUIRE(rml.size() == 1); + auto it = rml.begin(); + REQUIRE(it->type() == osmium::item_type::node); + REQUIRE(it->ref() == 123); + REQUIRE(std::string{it->role()} == ""); + ++it; + REQUIRE(it == rml.end()); + } + + SECTION("Valid format: three members") { + const char* const s = "n123@,w456@abc,r78@type"; + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& rml = buffer.get(0); + REQUIRE(rml.size() == 3); + auto it = rml.begin(); + REQUIRE(it->type() == osmium::item_type::node); + REQUIRE(it->ref() == 123); + REQUIRE(std::string{it->role()} == ""); + ++it; + REQUIRE(it->type() == osmium::item_type::way); + REQUIRE(it->ref() == 456); + REQUIRE(std::string{it->role()} == "abc"); + ++it; + REQUIRE(it->type() == osmium::item_type::relation); + REQUIRE(it->ref() == 78); + REQUIRE(std::string{it->role()} == "type"); + ++it; + REQUIRE(it == rml.end()); + } + + SECTION("Trailing comma") { + const char* const s = "n123@,w456@abc,r78@type,"; + oid::opl_parse_relation_members(s, s + std::strlen(s), buffer); + REQUIRE(buffer.written() > 0); + const auto& rml = buffer.get(0); + REQUIRE(rml.size() == 3); + auto it = rml.begin(); + REQUIRE(it->type() == osmium::item_type::node); + REQUIRE(it->ref() == 123); + REQUIRE(std::string{it->role()} == ""); + ++it; + REQUIRE(it->type() == osmium::item_type::way); + REQUIRE(it->ref() == 456); + REQUIRE(std::string{it->role()} == "abc"); + ++it; + REQUIRE(it->type() == osmium::item_type::relation); + REQUIRE(it->ref() == 78); + REQUIRE(std::string{it->role()} == "type"); + ++it; + REQUIRE(it == rml.end()); + } + + +} + +TEST_CASE("Parse node") { + osmium::memory::Buffer buffer{1024}; + + SECTION("Node with id only") { + const char* s = "17"; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 17); + } + + SECTION("Node with trailing space") { + const char* s = "17 "; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 17); + } + + SECTION("Node with id and version") { + const char* s = "17 v23"; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 17); + REQUIRE(node.version() == 23); + } + + SECTION("Node with multiple spaces") { + const char* s = "17 v23"; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 17); + REQUIRE(node.version() == 23); + } + + SECTION("Node with tab instead of space") { + const char* s = "17\tv23"; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 17); + REQUIRE(node.version() == 23); + } + + SECTION("Full node (no tags)") { + const char* s = "125799 v6 dV c7711393 t2011-03-29T21:43:10Z i45445 uUScha T x8.7868047 y53.0749415"; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 125799); + REQUIRE(node.version() == 6); + REQUIRE(node.visible()); + REQUIRE(node.changeset() == 7711393); + REQUIRE(node.timestamp() == osmium::Timestamp{"2011-03-29T21:43:10Z"}); + REQUIRE(node.uid() == 45445); + REQUIRE(std::string{node.user()} == "UScha"); + osmium::Location loc{8.7868047, 53.0749415}; + REQUIRE(node.location() == loc); + REQUIRE(node.tags().empty()); + } + + SECTION("Node with tags)") { + const char* s = "123 v1 c456 Thighway=residential,oneway=true,name=High%20%Street"; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 123); + REQUIRE(node.version() == 1); + REQUIRE(node.changeset() == 456); + REQUIRE(node.tags().size() == 3); + + auto it = node.tags().cbegin(); + REQUIRE(std::string{it->key()} == "highway"); + REQUIRE(std::string{it->value()} == "residential"); + ++it; + REQUIRE(std::string{it->key()} == "oneway"); + REQUIRE(std::string{it->value()} == "true"); + ++it; + REQUIRE(std::string{it->key()} == "name"); + REQUIRE(std::string{it->value()} == "High Street"); + ++it; + REQUIRE(it == node.tags().cend()); + } + + SECTION("Order does not matter") { + const char* s = "125799 c7711393 dV v6 i45445 uUScha T t2011-03-29T21:43:10Z y53.0749415 x8.7868047"; + const char* e = s + std::strlen(s); + oid::opl_parse_node(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Node& node = buffer.get(0); + REQUIRE(node.id() == 125799); + REQUIRE(node.version() == 6); + REQUIRE(node.visible()); + REQUIRE(node.changeset() == 7711393); + REQUIRE(node.timestamp() == osmium::Timestamp{"2011-03-29T21:43:10Z"}); + REQUIRE(node.uid() == 45445); + REQUIRE(std::string{node.user()} == "UScha"); + osmium::Location loc{8.7868047, 53.0749415}; + REQUIRE(node.location() == loc); + REQUIRE(node.tags().empty()); + } + +} + +TEST_CASE("Parse way") { + + osmium::memory::Buffer buffer{1024}; + + SECTION("Way with id only") { + const char* s = "17"; + const char* e = s + std::strlen(s); + oid::opl_parse_way(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Way& way = buffer.get(0); + REQUIRE(way.id() == 17); + } + + SECTION("Complete way") { + const char* s = "78216 v12 dV c35895909 t2015-12-11T22:01:57Z i7412 umjulius Tdestination=Interlaken;%20%Kandersteg;%20%Zweisimmen,highway=motorway_link,name=Thun%20%Süd,oneway=yes,ref=17,surface=asphalt Nn1011242,n2569390773,n2569390769,n255308687,n2569390761,n255308689,n255308691,n1407526499,n255308692,n3888362655,n255308693,n255308694,n255308695,n255308686"; + const char* e = s + std::strlen(s); + oid::opl_parse_way(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Way& way = buffer.get(0); + REQUIRE(way.id() == 78216); + REQUIRE(way.version() == 12); + REQUIRE(way.visible()); + REQUIRE(way.changeset() == 35895909); + REQUIRE(way.timestamp() == osmium::Timestamp{"2015-12-11T22:01:57Z"}); + REQUIRE(way.uid() == 7412); + REQUIRE(std::string{way.user()} == "mjulius"); + REQUIRE(way.tags().size() == 6); + + auto it = way.tags().cbegin(); + REQUIRE(std::string{it->key()} == "destination"); + REQUIRE(std::string{it->value()} == "Interlaken; Kandersteg; Zweisimmen"); + ++it; + REQUIRE(std::string{it->key()} == "highway"); + REQUIRE(std::string{it->value()} == "motorway_link"); + ++it; + REQUIRE(std::string{it->key()} == "name"); + REQUIRE(std::string{it->value()} == "Thun Süd"); + ++it; + REQUIRE(std::string{it->key()} == "oneway"); + REQUIRE(std::string{it->value()} == "yes"); + ++it; + REQUIRE(std::string{it->key()} == "ref"); + REQUIRE(std::string{it->value()} == "17"); + ++it; + REQUIRE(std::string{it->key()} == "surface"); + REQUIRE(std::string{it->value()} == "asphalt"); + ++it; + REQUIRE(it == way.tags().cend()); + + REQUIRE(way.nodes().size() == 14); + std::vector ids = { + 1011242, 2569390773, 2569390769, 255308687, 2569390761, 255308689, + 255308691, 1407526499, 255308692, 3888362655, 255308693, 255308694, + 255308695, 255308686 + }; + REQUIRE(std::equal(way.nodes().cbegin(), way.nodes().cend(), ids.cbegin())); + } + +} + +TEST_CASE("Parse relation") { + + osmium::memory::Buffer buffer{1024}; + + SECTION("Relation with id only") { + const char* s = "17"; + const char* e = s + std::strlen(s); + oid::opl_parse_relation(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Relation& relation = buffer.get(0); + REQUIRE(relation.id() == 17); + } + + SECTION("Complete relation") { + const char* s = "1074 v45 dV c20048094 t2014-01-17T10:27:04Z i86566 uwisieb Ttype=multipolygon,landuse=forest Mw255722275@inner,w256126142@outer,w24402792@inner,w256950103@outer,w255722279@outer"; + const char* e = s + std::strlen(s); + oid::opl_parse_relation(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Relation& relation = buffer.get(0); + REQUIRE(relation.id() == 1074); + REQUIRE(relation.version() == 45); + REQUIRE(relation.visible()); + REQUIRE(relation.changeset() == 20048094); + REQUIRE(relation.timestamp() == osmium::Timestamp{"2014-01-17T10:27:04Z"}); + REQUIRE(relation.uid() == 86566); + REQUIRE(std::string{relation.user()} == "wisieb"); + REQUIRE(relation.tags().size() == 2); + + auto it = relation.tags().cbegin(); + REQUIRE(std::string{it->key()} == "type"); + REQUIRE(std::string{it->value()} == "multipolygon"); + ++it; + REQUIRE(std::string{it->key()} == "landuse"); + REQUIRE(std::string{it->value()} == "forest"); + ++it; + REQUIRE(it == relation.tags().cend()); + + REQUIRE(relation.members().size() == 5); + auto mit = relation.members().cbegin(); + REQUIRE(mit->type() == osmium::item_type::way); + REQUIRE(mit->ref() == 255722275); + REQUIRE(std::string{mit->role()} == "inner"); + ++mit; + REQUIRE(mit->type() == osmium::item_type::way); + REQUIRE(mit->ref() == 256126142); + REQUIRE(std::string{mit->role()} == "outer"); + ++mit; + ++mit; + ++mit; + ++mit; + REQUIRE(mit == relation.members().cend()); + } + +} + +TEST_CASE("Parse changeset") { + + osmium::memory::Buffer buffer{1024}; + + SECTION("Changeset with id only") { + const char* s = "17"; + const char* e = s + std::strlen(s); + oid::opl_parse_changeset(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Changeset& changeset = buffer.get(0); + REQUIRE(changeset.id() == 17); + } + + SECTION("Complete changeset") { + const char* s = "873494 k1 s2009-04-21T08:52:49Z e2009-04-21T09:52:49Z d0 i13093 uTiberiusNero x13.923302 y50.957069 X14.0337519 Y50.9824084 Tcreated_by=Potlatch%20%0.11"; + const char* e = s + std::strlen(s); + oid::opl_parse_changeset(&s, buffer); + REQUIRE(s == e); + REQUIRE(buffer.written() > 0); + const osmium::Changeset& changeset = buffer.get(0); + REQUIRE(changeset.id() == 873494); + REQUIRE(changeset.created_at() == osmium::Timestamp{"2009-04-21T08:52:49Z"}); + REQUIRE(changeset.closed_at() == osmium::Timestamp{"2009-04-21T09:52:49Z"}); + REQUIRE(changeset.num_changes() == 1); + REQUIRE(changeset.num_comments() == 0); + REQUIRE(changeset.uid() == 13093); + REQUIRE(std::string{changeset.user()} == "TiberiusNero"); + REQUIRE(changeset.tags().size() == 1); + + auto it = changeset.tags().cbegin(); + REQUIRE(std::string{it->key()} == "created_by"); + REQUIRE(std::string{it->value()} == "Potlatch 0.11"); + ++it; + REQUIRE(it == changeset.tags().cend()); + + osmium::Box box{13.923302, 50.957069, 14.0337519, 50.9824084}; + REQUIRE(box == changeset.bounds()); + } + +} + +TEST_CASE("Parse line") { + + osmium::memory::Buffer buffer{1024}; + + SECTION("Empty line") { + const char* s = ""; + REQUIRE_FALSE(oid::opl_parse_line(0, "", buffer)); + REQUIRE(buffer.written() == 0); + } + + SECTION("Comment line") { + REQUIRE_FALSE(oid::opl_parse_line(0, "# abc", buffer)); + REQUIRE(buffer.written() == 0); + } + + SECTION("Fail") { + REQUIRE_THROWS_WITH({ + oid::opl_parse_line(0, "X", buffer); + }, "OPL error: unknown type on line 0 column 0"); + REQUIRE(buffer.written() == 0); + } + + SECTION("New line at end not allowed") { + REQUIRE_THROWS_WITH({ + oid::opl_parse_line(0, "n12 v3\n", buffer); + }, "OPL error: expected space or tab character on line 0 column 6"); + } + + SECTION("Node, but not asking for nodes") { + REQUIRE_FALSE(oid::opl_parse_line(0, "n12 v1", buffer, osmium::osm_entity_bits::way)); + REQUIRE(buffer.written() == 0); + } + + SECTION("Node") { + REQUIRE(oid::opl_parse_line(0, "n12 v3", buffer)); + REQUIRE(buffer.written() > 0); + auto& item = buffer.get(0); + REQUIRE(item.type() == osmium::item_type::node); + } + + SECTION("Way") { + REQUIRE(oid::opl_parse_line(0, "w12 v3", buffer)); + REQUIRE(buffer.written() > 0); + auto& item = buffer.get(0); + REQUIRE(item.type() == osmium::item_type::way); + } + + SECTION("Relation") { + REQUIRE(oid::opl_parse_line(0, "r12 v3", buffer)); + REQUIRE(buffer.written() > 0); + auto& item = buffer.get(0); + REQUIRE(item.type() == osmium::item_type::relation); + } + + SECTION("Changeset") { + REQUIRE(oid::opl_parse_line(0, "c12", buffer)); + REQUIRE(buffer.written() > 0); + auto& item = buffer.get(0); + REQUIRE(item.type() == osmium::item_type::changeset); + } + +} + +TEST_CASE("Get context for errors") { + + osmium::memory::Buffer buffer{1024}; + + SECTION("Unknown object type") { + bool error = false; + try { + oid::opl_parse_line(0, "~~~", buffer); + } catch (const osmium::opl_error& e) { + error = true; + REQUIRE(e.line == 0); + REQUIRE(e.column == 0); + REQUIRE(std::string{e.data} == "~~~"); + } + REQUIRE(error); + } + + SECTION("Node id") { + bool error = false; + try { + oid::opl_parse_line(0, "n~~~", buffer); + } catch (const osmium::opl_error& e) { + error = true; + REQUIRE(e.line == 0); + REQUIRE(e.column == 1); + REQUIRE(std::string{e.data} == "~~~"); + } + REQUIRE(error); + } + + SECTION("Node expect space") { + bool error = false; + try { + oid::opl_parse_line(1, "n123~~~", buffer); + } catch (const osmium::opl_error& e) { + error = true; + REQUIRE(e.line == 1); + REQUIRE(e.column == 4); + REQUIRE(std::string{e.data} == "~~~"); + } + REQUIRE(error); + } + + SECTION("Node unknown attribute") { + bool error = false; + try { + oid::opl_parse_line(2, "n123 ~~~", buffer); + } catch (const osmium::opl_error& e) { + error = true; + REQUIRE(e.line == 2); + REQUIRE(e.column == 5); + REQUIRE(std::string{e.data} == "~~~"); + } + REQUIRE(error); + } + + SECTION("Node version not an int") { + bool error = false; + try { + oid::opl_parse_line(3, "n123 v~~~", buffer); + } catch (const osmium::opl_error& e) { + error = true; + REQUIRE(e.line == 3); + REQUIRE(e.column == 6); + REQUIRE(std::string{e.data} == "~~~"); + } + REQUIRE(error); + } + +} + +TEST_CASE("Parse line with external interface") { + + osmium::memory::Buffer buffer{1024}; + + SECTION("Node") { + REQUIRE(osmium::opl_parse("n12 v3", buffer)); + REQUIRE(buffer.committed() > 0); + REQUIRE(buffer.written() == buffer.committed()); + const auto& item = buffer.get(0); + REQUIRE(item.type() == osmium::item_type::node); + REQUIRE(static_cast(item).id() == 12); + } + + SECTION("Empty line") { + REQUIRE_FALSE(osmium::opl_parse("", buffer)); + REQUIRE(buffer.written() == 0); + REQUIRE(buffer.committed() == 0); + } + + SECTION("Failure") { + REQUIRE_THROWS_WITH({ + osmium::opl_parse("x", buffer); + }, "OPL error: unknown type on line 0 column 0"); + REQUIRE(buffer.written() == 0); + REQUIRE(buffer.committed() == 0); + } + +} + diff --git a/test/t/io/test_reader_with_mock_decompression.cpp b/test/t/io/test_reader_with_mock_decompression.cpp index 566295aff..63b8bd297 100644 --- a/test/t/io/test_reader_with_mock_decompression.cpp +++ b/test/t/io/test_reader_with_mock_decompression.cpp @@ -19,7 +19,7 @@ class MockDecompressor : public osmium::io::Decompressor { public: - MockDecompressor(const std::string& fail_in) : + explicit MockDecompressor(const std::string& fail_in) : Decompressor(), m_fail_in(fail_in) { if (m_fail_in == "constructor") { @@ -87,7 +87,7 @@ TEST_CASE("Test Reader using MockDecompressor") { try { osmium::io::Reader reader(with_data_dir("t/io/data.osm.gz")); REQUIRE(false); - } catch (std::runtime_error& e) { + } catch (const std::runtime_error& e) { REQUIRE(std::string{e.what()} == "error constructor"); } } @@ -99,7 +99,7 @@ TEST_CASE("Test Reader using MockDecompressor") { osmium::io::Reader reader(with_data_dir("t/io/data.osm.gz")); reader.read(); REQUIRE(false); - } catch (std::runtime_error& e) { + } catch (const std::runtime_error& e) { REQUIRE(std::string{e.what()} == "error first read"); } } @@ -112,7 +112,7 @@ TEST_CASE("Test Reader using MockDecompressor") { reader.read(); reader.read(); REQUIRE(false); - } catch (std::runtime_error& e) { + } catch (const std::runtime_error& e) { REQUIRE(std::string{e.what()} == "error second read"); } } @@ -127,7 +127,7 @@ TEST_CASE("Test Reader using MockDecompressor") { reader.read(); reader.close(); REQUIRE(false); - } catch (std::runtime_error& e) { + } catch (const std::runtime_error& e) { REQUIRE(std::string{e.what()} == "error close"); } } diff --git a/test/t/io/test_reader_with_mock_parser.cpp b/test/t/io/test_reader_with_mock_parser.cpp index c71847ce5..c5c997598 100644 --- a/test/t/io/test_reader_with_mock_parser.cpp +++ b/test/t/io/test_reader_with_mock_parser.cpp @@ -78,7 +78,7 @@ TEST_CASE("Test Reader using MockParser") { try { osmium::io::Reader reader(with_data_dir("t/io/data.osm")); reader.header(); - } catch (std::runtime_error& e) { + } catch (const std::runtime_error& e) { REQUIRE(std::string{e.what()} == "error in header"); } } @@ -89,7 +89,7 @@ TEST_CASE("Test Reader using MockParser") { reader.header(); try { reader.read(); - } catch (std::runtime_error& e) { + } catch (const std::runtime_error& e) { REQUIRE(std::string{e.what()} == "error in read"); } reader.close(); @@ -101,7 +101,7 @@ TEST_CASE("Test Reader using MockParser") { reader.header(); try { throw std::runtime_error("error in user code"); - } catch (std::runtime_error& e) { + } catch (const std::runtime_error& e) { REQUIRE(std::string{e.what()} == "error in user code"); } REQUIRE(reader.read()); diff --git a/test/t/io/test_string_table.cpp b/test/t/io/test_string_table.cpp index ab977e8bc..1e7624513 100644 --- a/test/t/io/test_string_table.cpp +++ b/test/t/io/test_string_table.cpp @@ -7,6 +7,8 @@ TEST_CASE("String store") { SECTION("empty") { REQUIRE(ss.begin() == ss.end()); + REQUIRE(ss.get_chunk_size() == 100); + REQUIRE(ss.get_chunk_count() == 1); } SECTION("add zero-length string") { @@ -17,6 +19,8 @@ TEST_CASE("String store") { REQUIRE(s1 == *it); REQUIRE(std::string(*it) == ""); REQUIRE(++it == ss.end()); + + REQUIRE(ss.get_chunk_count() == 1); } SECTION("add strings") { @@ -30,6 +34,9 @@ TEST_CASE("String store") { REQUIRE(s1 == *it++); REQUIRE(s2 == *it++); REQUIRE(it == ss.end()); + + ss.clear(); + REQUIRE(ss.begin() == ss.end()); } SECTION("add zero-length string and longer strings") { @@ -45,9 +52,9 @@ TEST_CASE("String store") { } SECTION("add many strings") { - for (const char* teststring : {"a", "abc", "abcd", "abcde"}) { + for (const char* teststring : {"", "a", "abc", "abcd", "abcde"}) { int i = 0; - for (; i < 100; ++i) { + for (; i < 200; ++i) { ss.add(teststring); } @@ -57,7 +64,9 @@ TEST_CASE("String store") { } REQUIRE(i == 0); + REQUIRE(ss.get_chunk_count() > 1); ss.clear(); + REQUIRE(ss.get_chunk_count() == 1); } } @@ -90,5 +99,32 @@ TEST_CASE("String table") { REQUIRE(st.size() == 1); } + SECTION("add empty string") { + REQUIRE(st.add("") == 1); + REQUIRE(st.size() == 2); + REQUIRE(st.add("") == 1); + REQUIRE(st.size() == 2); + } + +} + +TEST_CASE("lots of strings in string table so chunk overflows") { + osmium::io::detail::StringTable st{100}; + REQUIRE(st.size() == 1); + + const int n = 1000; + for (int i = 0; i < n; ++i) { + auto s = std::to_string(i); + st.add(s.c_str()); + } + + REQUIRE(st.size() == n + 1); + + auto it = st.begin(); + REQUIRE(std::string{} == *it++); + for (int i = 0; i < n; ++i) { + REQUIRE(atoi(*it++) == i); + } + REQUIRE(it == st.end()); } diff --git a/test/t/io/test_writer.cpp b/test/t/io/test_writer.cpp index b56dfeb13..d3c28368b 100644 --- a/test/t/io/test_writer.cpp +++ b/test/t/io/test_writer.cpp @@ -18,9 +18,9 @@ TEST_CASE("Writer") { osmium::memory::Buffer buffer = reader.read(); REQUIRE(buffer); REQUIRE(buffer.committed() > 0); - auto num = std::distance(buffer.cbegin(), buffer.cend()); + auto num = std::distance(buffer.select().cbegin(), buffer.select().cend()); REQUIRE(num > 0); - REQUIRE(buffer.cbegin()->id() == 1); + REQUIRE(buffer.select().cbegin()->id() == 1); std::string filename; @@ -81,9 +81,8 @@ TEST_CASE("Writer") { osmium::memory::Buffer buffer_check = reader_check.read(); REQUIRE(buffer_check); REQUIRE(buffer_check.committed() > 0); - REQUIRE(std::distance(buffer_check.cbegin(), buffer_check.cend()) == num); - REQUIRE(buffer_check.cbegin()->id() == 1); - + REQUIRE(buffer_check.select().size() == num); + REQUIRE(buffer_check.select().cbegin()->id() == 1); } SECTION("Interrupted writer after open") { diff --git a/test/t/io/test_writer_with_mock_compression.cpp b/test/t/io/test_writer_with_mock_compression.cpp index c2d3bbd49..a28d537fe 100644 --- a/test/t/io/test_writer_with_mock_compression.cpp +++ b/test/t/io/test_writer_with_mock_compression.cpp @@ -15,7 +15,7 @@ class MockCompressor : public osmium::io::Compressor { public: - MockCompressor(const std::string& fail_in) : + explicit MockCompressor(const std::string& fail_in) : Compressor(osmium::io::fsync::no), m_fail_in(fail_in) { if (m_fail_in == "constructor") { @@ -56,8 +56,7 @@ TEST_CASE("Write with mock compressor") { osmium::memory::Buffer buffer = reader.read(); REQUIRE(buffer); REQUIRE(buffer.committed() > 0); - auto num = std::distance(buffer.cbegin(), buffer.cend()); - REQUIRE(num > 0); + REQUIRE(buffer.select().size() > 0); SECTION("fail on construction") { diff --git a/test/t/io/test_writer_with_mock_encoder.cpp b/test/t/io/test_writer_with_mock_encoder.cpp index a43d59183..d059f6b87 100644 --- a/test/t/io/test_writer_with_mock_encoder.cpp +++ b/test/t/io/test_writer_with_mock_encoder.cpp @@ -62,8 +62,7 @@ TEST_CASE("Test Writer with MockOutputFormat") { osmium::memory::Buffer buffer = reader.read(); REQUIRE(buffer); REQUIRE(buffer.committed() > 0); - auto num = std::distance(buffer.cbegin(), buffer.cend()); - REQUIRE(num > 0); + REQUIRE(buffer.select().size() > 0); SECTION("error in header") { diff --git a/test/t/tags/test_filter.cpp b/test/t/tags/test_filter.cpp index fa21de1c5..260a4badb 100644 --- a/test/t/tags/test_filter.cpp +++ b/test/t/tags/test_filter.cpp @@ -24,7 +24,7 @@ void check_filter(const osmium::TagList& tag_list, const TFilter filter, const s } const osmium::TagList& make_tag_list(osmium::memory::Buffer& buffer, std::initializer_list> tags) { - auto pos = osmium::builder::add_tag_list(buffer, osmium::builder::attr::_tags(tags)); + const auto pos = osmium::builder::add_tag_list(buffer, osmium::builder::attr::_tags(tags)); return buffer.get(pos); } @@ -42,7 +42,7 @@ TEST_CASE("Filter") { { "source", "GPS" } // no match }); - std::vector results = { true, false, false }; + const std::vector results = { true, false, false }; check_filter(tag_list, filter, results); } @@ -84,7 +84,7 @@ TEST_CASE("Filter") { { "source", "GPS" } }); - std::vector results = {true, true, false}; + const std::vector results = {true, true, false}; check_filter(tag_list, filter, results); } diff --git a/test/t/tags/test_tag_list.cpp b/test/t/tags/test_tag_list.cpp index 470a20002..3bdf5e967 100644 --- a/test/t/tags/test_tag_list.cpp +++ b/test/t/tags/test_tag_list.cpp @@ -167,10 +167,16 @@ TEST_CASE("create tag list") { }); } - const osmium::TagList& tl = *buffer.begin(); + const osmium::TagList& tl = *buffer.select().cbegin(); REQUIRE(osmium::item_type::tag_list == tl.type()); REQUIRE(2 == tl.size()); + REQUIRE(tl.has_key("highway")); + REQUIRE_FALSE(tl.has_key("unknown")); + REQUIRE(tl.has_tag("highway", "primary")); + REQUIRE_FALSE(tl.has_tag("highway", "false")); + REQUIRE_FALSE(tl.has_tag("foo", "bar")); + auto it = tl.begin(); REQUIRE(std::string("highway") == it->key()); REQUIRE(std::string("primary") == it->value()); diff --git a/test/t/util/test_cast_with_assert.cpp b/test/t/util/test_cast_with_assert.cpp index 0231f30e1..044176eef 100644 --- a/test/t/util/test_cast_with_assert.cpp +++ b/test/t/util/test_cast_with_assert.cpp @@ -3,7 +3,7 @@ // Define assert() to throw this error. This enables the tests to check that // the assert() fails. struct assert_error : public std::runtime_error { - assert_error(const char* what_arg) : std::runtime_error(what_arg) { + explicit assert_error(const char* what_arg) : std::runtime_error(what_arg) { } }; #define assert(x) if (!(x)) { throw(assert_error(#x)); } diff --git a/test/t/util/test_delta.cpp b/test/t/util/test_delta.cpp index 667c9b443..27bd8be72 100644 --- a/test/t/util/test_delta.cpp +++ b/test/t/util/test_delta.cpp @@ -70,25 +70,3 @@ TEST_CASE("delta encode and decode") { } -TEST_CASE("delta encode iterator") { - std::vector data = { 4, 5, 13, 22, 12 }; - - auto l = [](std::vector::const_iterator it) -> int { - return *it; - }; - - typedef osmium::util::DeltaEncodeIterator::const_iterator, decltype(l), int> it_type; - it_type it(data.begin(), data.end(), l); - it_type end(data.end(), data.end(), l); - - REQUIRE(*it == 4); - ++it; - REQUIRE(*it++ == 1); - REQUIRE(*it == 8); - ++it; - REQUIRE(*it++ == 9); - REQUIRE(*it == -10); - ++it; - REQUIRE(it == end); -} - diff --git a/test/t/util/test_memory_mapping.cpp b/test/t/util/test_memory_mapping.cpp index 29893f7c7..647d2a05d 100644 --- a/test/t/util/test_memory_mapping.cpp +++ b/test/t/util/test_memory_mapping.cpp @@ -33,15 +33,10 @@ TEST_CASE("anonymous mapping") { mapping.unmap(); // second unmap is okay } - SECTION("memory mapping of zero length should work") { - osmium::util::MemoryMapping mapping(0, osmium::util::MemoryMapping::mapping_mode::write_private); - REQUIRE(mapping.get_addr() != nullptr); - - REQUIRE(mapping.size() == osmium::util::get_pagesize()); - - REQUIRE(!!mapping); - mapping.unmap(); - REQUIRE(!mapping); + SECTION("memory mapping of zero length should fail") { + REQUIRE_THROWS({ + osmium::util::MemoryMapping mapping(0, osmium::util::MemoryMapping::mapping_mode::write_private); + }); } SECTION("moving a memory mapping should work") {