diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba1f0346f..2622af063 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(osm2pgsql_lib PRIVATE geom-functions.cpp geom-pole-of-inaccessibility.cpp geom.cpp + hex.cpp idlist.cpp input.cpp locator.cpp diff --git a/src/db-copy-mgr.hpp b/src/db-copy-mgr.hpp index f56251f30..7585e87de 100644 --- a/src/db-copy-mgr.hpp +++ b/src/db-copy-mgr.hpp @@ -15,7 +15,7 @@ #include #include "db-copy.hpp" -#include "util.hpp" +#include "hex.hpp" /** * Management class that fills and manages copy buffers. @@ -237,13 +237,7 @@ class db_copy_mgr_t */ void add_hex_geom(std::string const &wkb) { - char const *const lookup_hex = "0123456789ABCDEF"; - - for (auto c : wkb) { - unsigned int const num = static_cast(c); - m_current.buffer += lookup_hex[(num >> 4U) & 0xfU]; - m_current.buffer += lookup_hex[num & 0xfU]; - } + util::encode_hex(wkb, &m_current.buffer); m_current.buffer += '\t'; } diff --git a/src/gen/canvas.cpp b/src/gen/canvas.cpp index 0d6cf0d55..e0dd41a64 100644 --- a/src/gen/canvas.cpp +++ b/src/gen/canvas.cpp @@ -9,6 +9,7 @@ #include "canvas.hpp" +#include "hex.hpp" #include "raster.hpp" #include "tile.hpp" @@ -139,19 +140,3 @@ void canvas_t::merge(canvas_t const &other) { cv::bitwise_or(m_rast, other.m_rast, m_rast); } - -std::string to_hex(std::string const &in) -{ - std::string result; - result.reserve(in.size() * 2); - - char const *const lookup_hex = "0123456789ABCDEF"; - - for (const auto c : in) { - unsigned int const num = static_cast(c); - result += lookup_hex[(num >> 4U) & 0xfU]; - result += lookup_hex[num & 0xfU]; - } - - return result; -} diff --git a/src/gen/canvas.hpp b/src/gen/canvas.hpp index 3ea6a7f21..65bc0b913 100644 --- a/src/gen/canvas.hpp +++ b/src/gen/canvas.hpp @@ -77,6 +77,4 @@ class canvas_t image_type m_rast; }; // class canvas_t -std::string to_hex(std::string const &in); - #endif // OSM2PGSQL_CANVAS_HPP diff --git a/src/gen/gen-rivers.cpp b/src/gen/gen-rivers.cpp index 8c94510fc..54aaba8ef 100644 --- a/src/gen/gen-rivers.cpp +++ b/src/gen/gen-rivers.cpp @@ -10,6 +10,7 @@ #include "gen-rivers.hpp" #include "geom-functions.hpp" +#include "hex.hpp" #include "logging.hpp" #include "params.hpp" #include "pgsql.hpp" @@ -264,7 +265,7 @@ SELECT "{id_column}", "{width_column}", "{name_column}", "{geom_column}" if (!name.empty()) { names.emplace(id, name); } - auto const geom = ewkb_to_geom(decode_hex(result.get(i, 3))); + auto const geom = ewkb_to_geom(util::decode_hex(result.get(i, 3))); if (geom.is_linestring()) { auto const &ls = geom.get(); diff --git a/src/gen/gen-tile-builtup.cpp b/src/gen/gen-tile-builtup.cpp index e04cd5a37..4be8fe1ac 100644 --- a/src/gen/gen-tile-builtup.cpp +++ b/src/gen/gen-tile-builtup.cpp @@ -11,6 +11,7 @@ #include "canvas.hpp" #include "geom-functions.hpp" +#include "hex.hpp" #include "logging.hpp" #include "params.hpp" #include "pgsql.hpp" @@ -33,7 +34,7 @@ void save_image_to_table(pg_conn_t *connection, canvas_t const &canvas, std::string const &table, char const *variant, std::string const &table_prefix) { - auto const wkb = to_hex(canvas.to_wkb(tile, margin)); + auto const wkb = util::encode_hex(canvas.to_wkb(tile, margin)); connection->exec("INSERT INTO \"{}_{}_{}\" (zoom, x, y, rast)" " VALUES ({}, {}, {}, '{}')", @@ -61,7 +62,7 @@ void draw_from_db(double margin, canvas_list_t *canvas_list, pg_conn_t *conn, box.max_x(), box.max_y()); for (int n = 0; n < result.num_tuples(); ++n) { - auto const geom = ewkb_to_geom(decode_hex(result.get(n, 0))); + auto const geom = ewkb_to_geom(util::decode_hex(result.get(n, 0))); cc.canvas.draw(geom, tile); } } @@ -251,7 +252,7 @@ void gen_tile_builtup_t::process(tile_t const &tile) log_gen("Write geometries to destination table..."); timer(m_timer_write).start(); for (auto const &geom : geometries) { - auto const wkb = to_hex(geom_to_ewkb(geom)); + auto const wkb = util::encode_hex(geom_to_ewkb(geom)); if (m_has_area_column) { connection().exec_prepared("insert_geoms", wkb, tile.x(), tile.y(), geom::area(geom)); diff --git a/src/gen/gen-tile-raster.cpp b/src/gen/gen-tile-raster.cpp index ae34bc943..c0720ad63 100644 --- a/src/gen/gen-tile-raster.cpp +++ b/src/gen/gen-tile-raster.cpp @@ -10,6 +10,7 @@ #include "gen-tile-raster.hpp" #include "canvas.hpp" +#include "hex.hpp" #include "logging.hpp" #include "params.hpp" #include "pgsql.hpp" @@ -49,7 +50,7 @@ void draw_from_db(double margin, unsigned int image_extent, for (int n = 0; n < result.num_tuples(); ++n) { std::string param = result.get_value(n, 1); - auto const geom = ewkb_to_geom(decode_hex(result.get(n, 0))); + auto const geom = ewkb_to_geom(util::decode_hex(result.get(n, 0))); auto const [it, success] = canvas_list->try_emplace( std::move(param), image_extent, image_buffer); @@ -63,7 +64,7 @@ void save_image_to_table(pg_conn_t *connection, canvas_t const &canvas, std::string const ¶m, char const *variant, std::string const &table_prefix) { - auto const wkb = to_hex(canvas.to_wkb(tile, margin)); + auto const wkb = util::encode_hex(canvas.to_wkb(tile, margin)); connection->exec("INSERT INTO \"{}_{}\" (type, zoom, x, y, rast)" " VALUES ('{}', {}, {}, {}, '{}')", diff --git a/src/hex.cpp b/src/hex.cpp new file mode 100644 index 000000000..aec5bca2b --- /dev/null +++ b/src/hex.cpp @@ -0,0 +1,78 @@ +/** + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This file is part of osm2pgsql (https://osm2pgsql.org/). + * + * Copyright (C) 2006-2025 by the osm2pgsql developer community. + * For a full list of authors see the git log. + */ + +#include "hex.hpp" + +#include +#include +#include + +namespace util { + +void encode_hex(std::string const &in, std::string *out) +{ + assert(out); + + constexpr char const *const LOOKUP_HEX = "0123456789ABCDEF"; + + for (auto const c : in) { + unsigned int const num = static_cast(c); + (*out) += LOOKUP_HEX[(num >> 4U) & 0xfU]; + (*out) += LOOKUP_HEX[num & 0xfU]; + } +} + +std::string encode_hex(std::string const &in) +{ + std::string result; + result.reserve(in.size() * 2); + encode_hex(in, &result); + return result; +} + +namespace { + +constexpr std::array const HEX_TABLE = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, + + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +} // anonymous namespace + +unsigned char decode_hex_char(char c) noexcept +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + return HEX_TABLE[static_cast(static_cast(c))]; +} + +std::string decode_hex(std::string_view hex_string) +{ + if (hex_string.size() % 2 != 0) { + throw std::runtime_error{"Invalid wkb: Not a valid hex string"}; + } + + std::string wkb; + wkb.reserve(hex_string.size() / 2); + + // NOLINTNEXTLINE(llvm-qualified-auto, readability-qualified-auto) + for (auto hex = hex_string.begin(); hex != hex_string.end();) { + unsigned int const c = decode_hex_char(*hex++); + wkb += static_cast((c << 4U) | decode_hex_char(*hex++)); + } + + return wkb; +} + +} // namespace util diff --git a/src/hex.hpp b/src/hex.hpp new file mode 100644 index 000000000..a085e32fe --- /dev/null +++ b/src/hex.hpp @@ -0,0 +1,47 @@ +#ifndef OSM2PGSQL_HEX_HPP +#define OSM2PGSQL_HEX_HPP + +/** + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This file is part of osm2pgsql (https://osm2pgsql.org/). + * + * Copyright (C) 2006-2025 by the osm2pgsql developer community. + * For a full list of authors see the git log. + */ + +#include + +namespace util { + +/** + * Convert content of input string to hex and append to output. + * + * \param in The input data. + * \param out Pointer to output string. + */ +void encode_hex(std::string const &in, std::string *out); + +/** + * Convert content of input string to hex and return it. + * + * \param in The input data. + * \returns Hex encoded string. + */ +[[nodiscard]] std::string encode_hex(std::string const &in); + +/** + * Decode one hex character (0-9A-F or 0-9a-f) and return its value. + * Returns 0 for characters that are not hex characters. + */ +[[nodiscard]] unsigned char decode_hex_char(char c) noexcept; + +/** + * Decode a string of hex characters. Throws an exception if the input is not + * a valid hex encoding. + */ +[[nodiscard]] std::string decode_hex(std::string_view hex); + +} // namespace util + +#endif // OSM2PGSQL_HEX_HPP diff --git a/src/locator.cpp b/src/locator.cpp index 7b983751d..844eae364 100644 --- a/src/locator.cpp +++ b/src/locator.cpp @@ -12,6 +12,7 @@ #include "geom-boost-adaptor.hpp" #include "geom-box.hpp" #include "geom-functions.hpp" +#include "hex.hpp" #include "overloaded.hpp" #include "pgsql-capabilities.hpp" #include "pgsql.hpp" @@ -60,7 +61,7 @@ void locator_t::add_regions(pg_conn_t const &db_connection, for (int n = 0; n < result.num_tuples(); ++n) { std::string const name = result.get_value(n, 0); - auto geometry = ewkb_to_geom(decode_hex(result.get(n, 1))); + auto geometry = ewkb_to_geom(util::decode_hex(result.get(n, 1))); if (geometry.srid() == 4326) { add_region(name, geometry); diff --git a/src/wkb.cpp b/src/wkb.cpp index facae2ad4..07efec0fd 100644 --- a/src/wkb.cpp +++ b/src/wkb.cpp @@ -575,42 +575,3 @@ geom::geometry_t ewkb_to_geom(std::string_view wkb) return geom; } - -namespace { - -constexpr std::array const HEX_TABLE = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, - - 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -} // anonymous namespace - -unsigned char decode_hex_char(char c) noexcept -{ - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - return HEX_TABLE[static_cast(static_cast(c))]; -} - -std::string decode_hex(std::string_view hex_string) -{ - if (hex_string.size() % 2 != 0) { - throw std::runtime_error{"Invalid wkb: Not a valid hex string"}; - } - - std::string wkb; - wkb.reserve(hex_string.size() / 2); - - // NOLINTNEXTLINE(llvm-qualified-auto, readability-qualified-auto) - for (auto hex = hex_string.begin(); hex != hex_string.end();) { - unsigned int const c = decode_hex_char(*hex++); - wkb += static_cast((c << 4U) | decode_hex_char(*hex++)); - } - - return wkb; -} diff --git a/src/wkb.hpp b/src/wkb.hpp index cb38177e6..b43d4ed63 100644 --- a/src/wkb.hpp +++ b/src/wkb.hpp @@ -40,16 +40,4 @@ */ [[nodiscard]] geom::geometry_t ewkb_to_geom(std::string_view wkb); -/** - * Decode one hex character (0-9A-F or 0-9a-f) and return its value. - * Returns 0 for characters that are not hex characters. - */ -[[nodiscard]] unsigned char decode_hex_char(char c) noexcept; - -/** - * Decode a string of hex characters. Throws an exception if the input is not - * a valid hex encoding. - */ -[[nodiscard]] std::string decode_hex(std::string_view hex); - #endif // OSM2PGSQL_WKB_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 93fd0eded..4b946be34 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,6 +57,7 @@ set_test(test-geom-points LABELS NoDB) set_test(test-geom-pole-of-inaccessibility LABELS NoDB) set_test(test-geom-polygons LABELS NoDB) set_test(test-geom-transform LABELS NoDB) +set_test(test-hex LABELS NoDB) set_test(test-json-writer LABELS NoDB) set_test(test-locator LABELS NoDB) set_test(test-lua-utils LABELS NoDB) diff --git a/tests/test-hex.cpp b/tests/test-hex.cpp new file mode 100644 index 000000000..4b6df6a2a --- /dev/null +++ b/tests/test-hex.cpp @@ -0,0 +1,86 @@ +/** + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This file is part of osm2pgsql (https://osm2pgsql.org/). + * + * Copyright (C) 2006-2025 by the osm2pgsql developer community. + * For a full list of authors see the git log. + */ + +#include + +#include "hex.hpp" + +#include + +TEST_CASE("hex encode a string", "[NoDB]") +{ + std::string result; + util::encode_hex("ab~", &result); + REQUIRE(result.size() == 6); + REQUIRE(result == "61627E"); +} + +TEST_CASE("hex encode a string adding to existing string", "[NoDB]") +{ + std::string result{"0x"}; + util::encode_hex("\xca\xfe", &result); + REQUIRE(result.size() == 6); + REQUIRE(result == "0xCAFE"); +} + +TEST_CASE("hex encoding an empty string doesn't change output string", "[NoDB]") +{ + std::string result{"foo"}; + util::encode_hex("", &result); + REQUIRE(result == "foo"); +} + +TEST_CASE("wkb hex decode of valid and invalid hex characters") +{ + REQUIRE(util::decode_hex_char('0') == 0); + REQUIRE(util::decode_hex_char('9') == 9); + REQUIRE(util::decode_hex_char('a') == 0x0a); + REQUIRE(util::decode_hex_char('f') == 0x0f); + REQUIRE(util::decode_hex_char('A') == 0x0a); + REQUIRE(util::decode_hex_char('F') == 0x0f); + REQUIRE(util::decode_hex_char('#') == 0); + REQUIRE(util::decode_hex_char('@') == 0); + REQUIRE(util::decode_hex_char('g') == 0); + REQUIRE(util::decode_hex_char('G') == 0); + REQUIRE(util::decode_hex_char(0x7f) == 0); +} + +TEST_CASE("wkb hex decode of valid hex string") +{ + std::string const hex{"0001020F1099FF"}; + std::string const data = {0x00, + 0x01, + 0x02, + 0x0f, + 0x10, + static_cast(0x99), + static_cast(0xff)}; + + auto const result = util::decode_hex(hex); + REQUIRE(result.size() == hex.size() / 2); + REQUIRE(result == data); +} + +TEST_CASE("wkb hex decode of empty string is okay") +{ + std::string const hex{}; + REQUIRE(util::decode_hex(hex).empty()); +} + +TEST_CASE("wkb hex decode of string with odd number of characters fails") +{ + REQUIRE_THROWS(util::decode_hex("a")); + REQUIRE_THROWS(util::decode_hex("abc")); + REQUIRE_THROWS(util::decode_hex("00000")); +} + +TEST_CASE("hex encode and decode") { + std::string const str{"something somewhere"}; + REQUIRE(util::decode_hex(util::encode_hex(str)) == str); +} diff --git a/tests/test-wkb.cpp b/tests/test-wkb.cpp index 6e28e8bd0..1f1c796d4 100644 --- a/tests/test-wkb.cpp +++ b/tests/test-wkb.cpp @@ -189,46 +189,3 @@ TEST_CASE("wkb: geometrycollection", "[NoDB]") TEST_CASE("wkb: invalid", "[NoDB]") { REQUIRE_THROWS(ewkb_to_geom("INVALID")); } -TEST_CASE("wkb hex decode of valid and invalid hex characters") -{ - REQUIRE(decode_hex_char('0') == 0); - REQUIRE(decode_hex_char('9') == 9); - REQUIRE(decode_hex_char('a') == 0x0a); - REQUIRE(decode_hex_char('f') == 0x0f); - REQUIRE(decode_hex_char('A') == 0x0a); - REQUIRE(decode_hex_char('F') == 0x0f); - REQUIRE(decode_hex_char('#') == 0); - REQUIRE(decode_hex_char('@') == 0); - REQUIRE(decode_hex_char('g') == 0); - REQUIRE(decode_hex_char('G') == 0); - REQUIRE(decode_hex_char(0x7f) == 0); -} - -TEST_CASE("wkb hex decode of valid hex string") -{ - std::string const hex{"0001020F1099FF"}; - std::string const data = {0x00, - 0x01, - 0x02, - 0x0f, - 0x10, - static_cast(0x99), - static_cast(0xff)}; - - auto const result = decode_hex(hex); - REQUIRE(result.size() == hex.size() / 2); - REQUIRE(result == data); -} - -TEST_CASE("wkb hex decode of empty string is okay") -{ - std::string const hex{}; - REQUIRE(decode_hex(hex).empty()); -} - -TEST_CASE("wkb hex decode of string with odd number of characters fails") -{ - REQUIRE_THROWS(decode_hex("a")); - REQUIRE_THROWS(decode_hex("abc")); - REQUIRE_THROWS(decode_hex("00000")); -}