diff --git a/include/geopackage/NGen_SQLite.hpp b/include/geopackage/NGen_SQLite.hpp deleted file mode 100644 index 9aaf55d3a7..0000000000 --- a/include/geopackage/NGen_SQLite.hpp +++ /dev/null @@ -1,230 +0,0 @@ -#ifndef NGEN_GEOPACKAGE_SQLITE_H -#define NGEN_GEOPACKAGE_SQLITE_H - -#include -#include -#include -#include - -#include - -namespace geopackage { - -/** - * Deleter used to provide smart pointer support for sqlite3 structs. - */ -struct sqlite_deleter -{ - void operator()(sqlite3* db) { sqlite3_close_v2(db); } - void operator()(sqlite3_stmt* stmt) { sqlite3_finalize(stmt); } -}; - -/** - * Smart pointer (shared) type for sqlite3 prepared statements - */ -using stmt_t = std::unique_ptr; - -/** - * SQLite3 row iterator - * - * Provides a simple iterator-like implementation - * over rows of a SQLite3 query. - */ -class sqlite_iter -{ - private: - stmt_t stmt; - int iteration_step = -1; - bool iteration_finished = false; - - int column_count; - std::vector column_names; - - // vector of SQLITE data types, see: https://www.sqlite.org/datatype3.html - std::vector column_types; - - // returns the raw pointer to the sqlite statement - sqlite3_stmt* ptr() const noexcept; - - // checks if int is out of range, and throws error if so - void handle_get_index(int) const; - - public: - sqlite_iter(stmt_t stmt); - - /** - * Check if a row iterator is finished - * - * @return true if next() returned SQLITE_DONE - * @return false if there is more rows available - */ - bool done() const noexcept; - - /** - * Step into the next row of a SQLite query - * - * If the query is finished, next() acts idempotently, - * but will change done() to return true. - * @return sqlite_iter& returns itself - */ - sqlite_iter& next(); - - /** - * Restart an iteration to its initial state. - * next() must be called after calling this. - * - * @return sqlite_iter& returns itself - */ - sqlite_iter& restart(); - - /** - * Get the current row index for the iterator - * - * @return int the current row index, or -1 if next() hasn't been called - */ - int current_row() const noexcept; - - /** - * Get the number of columns within this iterator - * @return int number of columns in query - */ - int num_columns() const noexcept; - - /** - * Return the column index for a named column - * - * @param name column name to search for - * @return int index of given column name, or -1 if not found. - */ - int column_index(const std::string& name) const noexcept; - - /** - * Get a vector of column names - * - * @return const std::vector& column names as a vector of strings - */ - const std::vector& columns() const noexcept { return this->column_names; } - - /** - * Get a vector of column types - * - * See https://www.sqlite.org/datatype3.html for type values. The integers - * are the affinity for data types. - * @return const std::vector& column types as a vector of ints - */ - const std::vector& types() const noexcept { return this->column_types; } - - /** - * Get a column value from a row iterator by index - * - * @tparam T Type to parse value as, i.e. int - * @param col Column index to parse - * @return T value at column `col` - */ - template - T get(int col) const; - - /** - * Get a column value from a row iterator by name - * - * @tparam T Type to parse value as, i.e. int - * @return T value at the named column - */ - template - T get(const std::string&) const; -}; - -template -inline T sqlite_iter::get(const std::string& name) const -{ - const int index = this->column_index(name); - return this->get(index); -} - -/** - * Wrapper around a SQLite3 database - */ -class sqlite -{ - private: - /** - * Smart pointer (unique) type for sqlite3 database - */ - using sqlite_t = std::unique_ptr; - - sqlite_t conn = nullptr; - - public: - sqlite() = delete; - - /** - * Construct a new sqlite object from a path to database - * - * @param path File path to sqlite3 database - */ - sqlite(const std::string& path); - - sqlite(sqlite& db) = delete; - - sqlite& operator=(sqlite& db) = delete; - - /** - * Take ownership of a sqlite3 database - * - * @param db sqlite3 database object - */ - sqlite(sqlite&& db) = default; - - /** - * Move assignment operator - * - * @param db sqlite3 database object - * @return sqlite& reference to sqlite3 database - */ - sqlite& operator=(sqlite&& db) = default; - - /** - * Return the originating sqlite3 database pointer - * - * @return sqlite3* - */ - sqlite3* connection() const noexcept; - - /** - * Check if SQLite database contains a given table - * - * @param table name of table - * @return true if table does exist - * @return false if table does not exist - */ - bool has_table(const std::string& table) noexcept; - - /** - * Query the SQLite Database and get the result - * @param statement String query - * @return sqlite_iter SQLite row iterator - */ - sqlite_iter query(const std::string& statement); - - /** - * Query the SQLite Database with multiple boundable text parameters. - * - * @param statement String query with parameters - * @param binds text parameters to bind to statement - * @return sqlite_iter SQLite row iterator - */ - sqlite_iter query(const std::string& statement, const std::vector& binds); - - /** - * Query the SQLite Database with a bound statement and get the result - * @param statement String query with parameters - * @param params parameters to bind to statement - * @return sqlite_iter SQLite row iterator - */ - template - sqlite_iter query(const std::string& statement, T const&... params); -}; - -} // namespace geopackage - -#endif // NGEN_GEOPACKAGE_SQLITE_H diff --git a/include/geopackage/GeoPackage.hpp b/include/geopackage/geopackage.hpp similarity index 89% rename from include/geopackage/GeoPackage.hpp rename to include/geopackage/geopackage.hpp index 5a296b9516..09ab89a0f0 100644 --- a/include/geopackage/GeoPackage.hpp +++ b/include/geopackage/geopackage.hpp @@ -2,8 +2,9 @@ #define NGEN_GEOPACKAGE_H #include "FeatureCollection.hpp" -#include "NGen_SQLite.hpp" +#include "ngen_sqlite.hpp" +namespace ngen { namespace geopackage { /** @@ -15,7 +16,7 @@ namespace geopackage { * @return geojson::geometry GPKG WKB converted and projected to a boost geometry model */ geojson::geometry build_geometry( - const sqlite_iter& row, + const ngen::sqlite::database::iterator& row, const std::string& geom_col, std::vector& bounding_box ); @@ -28,7 +29,7 @@ geojson::geometry build_geometry( * @return geojson::PropertyMap PropertyMap of properties from the given row */ geojson::PropertyMap build_properties( - const sqlite_iter& row, + const ngen::sqlite::database::iterator& row, const std::string& geom_col ); @@ -40,7 +41,7 @@ geojson::PropertyMap build_properties( * @return geojson::Feature Feature containing geometry and properties from the given row */ geojson::Feature build_feature( - const sqlite_iter& row, + const ngen::sqlite::database::iterator& row, const std::string& id_col, const std::string& geom_col ); @@ -60,4 +61,5 @@ std::shared_ptr read( ); } // namespace geopackage +} // namespace ngen #endif // NGEN_GEOPACKAGE_H diff --git a/include/geopackage/ngen_sqlite.hpp b/include/geopackage/ngen_sqlite.hpp new file mode 100644 index 0000000000..9921f35ac1 --- /dev/null +++ b/include/geopackage/ngen_sqlite.hpp @@ -0,0 +1,181 @@ +#ifndef NGEN_GEOPACKAGE_SQLITE_H +#define NGEN_GEOPACKAGE_SQLITE_H + +#include +#include + +#include + +#include "span.hpp" +#include "traits.hpp" + +namespace ngen { +namespace sqlite { + +struct sqlite_error : public std::runtime_error +{ + using std::runtime_error::runtime_error; + + sqlite_error(const std::string& origin_func, int code, const std::string& extra = ""); +}; + +class database +{ + //! Deleter used to provide smart pointer support for sqlite3 structs. + struct deleter + { + void operator()(sqlite3* db) + { + sqlite3_close_v2(db); + } + + void operator()(sqlite3_stmt* stmt) + { + sqlite3_finalize(stmt); + } + }; + + using sqlite_t = std::unique_ptr; + using stmt_t = std::unique_ptr; + + public: + struct iterator + { + iterator() = delete; + + explicit iterator(stmt_t&& stmt); + + //! Check if a row iterator is finished + //! @return true if next() returned SQLITE_DONE + //! @return false if there is more rows available + bool done() const noexcept; + + //! Step into the next row of a SQLite query + //! + //! If the query is finished, next() acts idempotently, + //! but will change done() to return true. + //! @return sqlite_iter& returns itself + iterator& next(); + + //! Restart an iteration to its initial state. + //! next() must be called after calling this. + //! + //! @return sqlite_iter& returns itself + iterator& restart(); + + //! Get the current row index for the iterator + //! @return int the current row index, + //! or -1 if next() hasn't been called + int current_row() const noexcept; + + //! Get the number of columns within this iterator + //! @return int number of columns in query + int num_columns() const noexcept; + + //! Return the column index for a named column + //! + //! @param name column name to search for + //! @return int index of given column name, or -1 if not found. + int find(const std::string& name) const noexcept; + + //! Get a span of column names + //! @return column names as a span of strings + const boost::span columns() const noexcept; + + //! Get a span of column types + //! + //! See https://www.sqlite.org/datatype3.html for type affinities. + //! The integers are the affinity for data types. + //! @return column types as a span of ints + const boost::span types() const noexcept; + + + //! Get a column value from a row iterator by index + //! + //! @tparam T Type to parse value as, i.e. int + //! @param col Column index to parse + //! @return T value at column `col` + template + Tp get(int col) const; + + //! Get a column value from a row iterator by name + //! + //! @tparam T Type to parse value as, i.e. int + //! @param col Column name + //! @return T value at the named column + template + Tp get(const std::string& col) const + { return get(find(col)); } + + private: + + sqlite3_stmt* ptr_() const noexcept; + void handle_get_index_(int col) const; + + stmt_t stmt_; + + int step_ = -1; + int done_ = false; + int ncol_ = 0; + + std::vector names_; + std::vector types_; + }; // struct iterator + + // Prevent default construction and copy + // construction/assignment. + database() = delete; + database(const database&) = delete; + database& operator=(const database&) = delete; + + // Allow move operations + database(database&&) = default; + database& operator=(database&&) = default; + ~database() = default; + + //! Construct a new sqlite object from a path to database + //! @param path File path to sqlite3 database + explicit database(const std::string& path); + + + //! Return the originating sqlite3 database pointer + //! @return sqlite3* + sqlite3* connection() const noexcept; + + //! Check if SQLite database contains a given table + //! @param table name of table + //! @return true if table does exist + //! @return false if table does not exist + bool contains(const std::string& table); + + //! Query the SQLite Database and get the result + //! @param statement String query with parameters + //! @param binds text parameters to bind to statement + //! @return SQLite row iterator + iterator query(const std::string& statement, const boost::span binds = {}); + + //! Query the SQLite Database with a bound statement and get the result + //! @param statement String query with parameters + //! @param params parameters to bind to statement + //! @return SQLite row iterator + template< + typename... Ts, + std::enable_if_t< + ngen::traits::all_is_convertible::value, + bool + > = true + > + iterator query(const std::string& statement, const Ts&... params) + { + std::array binds = { params... }; + return query(statement, binds); + } + + private: + sqlite_t conn_ = nullptr; +}; + +} // namespace sqlite +} // namespace ngen + +#endif // NGEN_GEOPACKAGE_SQLITE_H diff --git a/include/geopackage/proj.hpp b/include/geopackage/proj.hpp new file mode 100644 index 0000000000..934671b06c --- /dev/null +++ b/include/geopackage/proj.hpp @@ -0,0 +1,32 @@ +#ifndef NGEN_GEOPACKAGE_PROJ_HPP +#define NGEN_GEOPACKAGE_PROJ_HPP + +#include +#include + +namespace ngen { +namespace srs { + +namespace bg = boost::geometry; + +struct epsg +{ + using srs_type = bg::srs::dpar::parameters; + + enum { + wgs84 = 4326, + conus_albers = 5070, + mercator = 3857 + }; + + static srs_type get(uint32_t srid); + + private: + using def_type = std::unordered_map; + static const def_type defs_; +}; + +} // namespace srs +} // namespace ngen + +#endif // NGEN_GEOPACKAGE_PROJ_HPP diff --git a/include/geopackage/WKB.hpp b/include/geopackage/wkb.hpp similarity index 98% rename from include/geopackage/WKB.hpp rename to include/geopackage/wkb.hpp index 57bb73a43e..2d176b7425 100644 --- a/include/geopackage/WKB.hpp +++ b/include/geopackage/wkb.hpp @@ -8,6 +8,7 @@ namespace bg = boost::geometry; +namespace ngen { namespace geopackage { /** @@ -53,8 +54,6 @@ struct wkb { */ static geometry read(const boost::span buffer); - static bg::srs::dpar::parameters<> get_prj(uint32_t srid); - private: /** * Read a WKB point into a cartesian model. @@ -112,5 +111,6 @@ struct wkb::wgs84 : public boost::static_visitor }; } // namespace geopackage +} // namespace ngen #endif // NGEN_GEOPACKAGE_WKB_H diff --git a/src/NGen.cpp b/src/NGen.cpp index b19213dc6a..8325a59265 100644 --- a/src/NGen.cpp +++ b/src/NGen.cpp @@ -10,7 +10,7 @@ #include #ifdef NGEN_WITH_SQLITE3 -#include +#include #endif #include "NGenConfig.h" @@ -256,7 +256,7 @@ int main(int argc, char *argv[]) { geojson::GeoJSON nexus_collection; if (boost::algorithm::ends_with(nexusDataFile, "gpkg")) { #ifdef NGEN_WITH_SQLITE3 - nexus_collection = geopackage::read(nexusDataFile, "nexus", nexus_subset_ids); + nexus_collection = ngen::geopackage::read(nexusDataFile, "nexus", nexus_subset_ids); #else throw std::runtime_error("SQLite3 support required to read GeoPackage files."); #endif @@ -269,7 +269,7 @@ int main(int argc, char *argv[]) { geojson::GeoJSON catchment_collection; if (boost::algorithm::ends_with(catchmentDataFile, "gpkg")) { #ifdef NGEN_WITH_SQLITE3 - catchment_collection = geopackage::read(catchmentDataFile, "divides", catchment_subset_ids); + catchment_collection = ngen::geopackage::read(catchmentDataFile, "divides", catchment_subset_ids); #else throw std::runtime_error("SQLite3 support required to read GeoPackage files."); #endif diff --git a/src/geopackage/CMakeLists.txt b/src/geopackage/CMakeLists.txt index 574c516368..7786671af3 100644 --- a/src/geopackage/CMakeLists.txt +++ b/src/geopackage/CMakeLists.txt @@ -1,21 +1,10 @@ -string(COMPARE EQUAL "${CMAKE_CXX_COMPILER_ID}" "IntelLLVM" _cmp) -if (NOT _cmp) - message(WARNING "[NGen::geopackage] wkb.cpp cannot be optimized with " - "${CMAKE_CXX_COMPILER_ID} due to a suspected optimizer/boost issue:\n" - "https://github.com/NOAA-OWP/ngen/issues/567.\n" - "Use IntelLLVM if optimization for this source file is required.") - # !! Required due to optimizer issue with either clang or - # !! boost::geometry::srs::transformation - set_source_files_properties(wkb.cpp PROPERTIES COMPILE_OPTIONS "-O0;-fno-sanitize=all") -endif() - -add_library(geopackage geometry.cpp +add_library(geopackage proj.cpp + geometry.cpp properties.cpp feature.cpp read.cpp wkb.cpp - sqlite/iterator.cpp - sqlite/database.cpp + ngen_sqlite.cpp ) add_library(NGen::geopackage ALIAS geopackage) target_include_directories(geopackage PUBLIC ${PROJECT_SOURCE_DIR}/include/geopackage) diff --git a/src/geopackage/feature.cpp b/src/geopackage/feature.cpp index d66c5dde4c..8b3f777274 100644 --- a/src/geopackage/feature.cpp +++ b/src/geopackage/feature.cpp @@ -1,4 +1,4 @@ -#include "GeoPackage.hpp" +#include "geopackage.hpp" // Points don't have a bounding box, so we can say its bbox is itself inline void build_point_bbox(const geojson::geometry& geom, std::vector& bbox) @@ -10,8 +10,8 @@ inline void build_point_bbox(const geojson::geometry& geom, std::vector& bbox[3] = pt.get<1>(); } -geojson::Feature geopackage::build_feature( - const sqlite_iter& row, +geojson::Feature ngen::geopackage::build_feature( + const ngen::sqlite::database::iterator& row, const std::string& id_col, const std::string& geom_col ) @@ -22,7 +22,7 @@ geojson::Feature geopackage::build_feature( geojson::geometry geometry = build_geometry(row, geom_col, bounding_box); // Convert variant type (0-based) to FeatureType - const auto wkb_type = geojson::FeatureType(geometry.which() + 1); + const auto wkb_type = static_cast(geometry.which() + 1); switch(wkb_type) { case geojson::FeatureType::Point: diff --git a/src/geopackage/geometry.cpp b/src/geopackage/geometry.cpp index c4dd188ce5..5b6fecf3af 100644 --- a/src/geopackage/geometry.cpp +++ b/src/geopackage/geometry.cpp @@ -1,9 +1,10 @@ -#include "GeoPackage.hpp" +#include "geopackage.hpp" #include "EndianCopy.hpp" -#include "WKB.hpp" +#include "wkb.hpp" +#include "proj.hpp" -geojson::geometry geopackage::build_geometry( - const sqlite_iter& row, +geojson::geometry ngen::geopackage::build_geometry( + const ngen::sqlite::database::iterator& row, const std::string& geom_col, std::vector& bounding_box ) @@ -23,11 +24,11 @@ geojson::geometry geopackage::build_geometry( index++; // Read srs_id - uint32_t srs_id; + uint32_t srs_id = 0; utils::copy_from(geometry_blob, index, srs_id, endian); - const auto epsg = wkb::get_prj(srs_id); - const bg::srs::transformation<> prj{epsg, wkb::get_prj(4326)}; + const auto epsg = ngen::srs::epsg::get(srs_id); + const bg::srs::transformation<> prj{epsg, ngen::srs::epsg::get(ngen::srs::epsg::wgs84)}; wkb::wgs84 pvisitor{srs_id, prj}; if (indicator > 0 & indicator < 5) { @@ -50,7 +51,7 @@ geojson::geometry geopackage::build_geometry( geojson::coordinate_t min_prj{}; // project the raw bounding box - if (srs_id == 4326) { + if (srs_id == srs::epsg::wgs84) { max_prj = geojson::coordinate_t{max.get<0>(), max.get<1>()}; min_prj = geojson::coordinate_t{min.get<0>(), min.get<1>()}; } else { diff --git a/src/geopackage/ngen_sqlite.cpp b/src/geopackage/ngen_sqlite.cpp new file mode 100644 index 0000000000..9f98288f81 --- /dev/null +++ b/src/geopackage/ngen_sqlite.cpp @@ -0,0 +1,236 @@ +#include "ngen_sqlite.hpp" + +#include +#include + +namespace ngen { +namespace sqlite { + +// ngen::sqlite::sqlite_error ================================================= + +//! error codes: https://www.sqlite.org/rescode.html +sqlite_error::sqlite_error(const std::string& origin_func, int code, const std::string& extra) + : std::runtime_error( + origin_func + " returned code " + std::to_string(code) + + " (msg: " + sqlite3_errstr(code) + ")" + + (extra.empty() ? "" : " " + extra) + ){}; + +const auto sqlite_not_started_error = sqlite_error{ + "sqlite iteration is has not started, get() is not callable " + "(call iterator::next() before)" +}; + +const auto sqlite_already_done_error = sqlite_error{ + "sqlite iteration is done, get() is not callable" +}; + +// ngen::sqlite::database::iterator =========================================== + +database::iterator::iterator(stmt_t&& stmt) + : stmt_(std::move(stmt)) +{ + next(); + ncol_ = sqlite3_column_count(ptr_()); + names_.reserve(ncol_); + types_.reserve(ncol_); + + for (int i = 0; i < ncol_; i++) { + names_.emplace_back(sqlite3_column_name(ptr_(), i)); + types_.emplace_back(sqlite3_column_type(ptr_(), i)); + } + + restart(); +}; + +auto database::iterator::ptr_() const noexcept -> sqlite3_stmt* +{ + return stmt_.get(); +} + +auto database::iterator::done() const noexcept -> bool +{ + return done_; +} + +auto database::iterator::next() -> iterator& +{ + if (!done_) { + const int code = sqlite3_step(ptr_()); + + if (code == SQLITE_DONE) { + done_ = true; + } else if( code == SQLITE_ROW){ + // Update column dtypes for the next row + // There are a couple ways to do this, each with some nuance: + // 1) use sqlite3_column_type upon construction of the iterator and inspect the first row + // then use those column types for all rows + // 2) use the sqlite3 table definition schema (i.e. PRAGMA table_info(...);) to get column types + // 3) check each row during iteration using sqlite3_column_type + // 1 & 2 may not produce consistent results because if the value in a column is NaN, + // then sqlite3_column_type will report that as a 5 or NULL type, even if the schema has a datatype + // Using 1, when the first row contains NaN but other rows have valid data, then all types are reported as NULL. + // Using 2, any NaN/NULL data will get interperted as the schema type + // Using 3 ensures we get a non-null type if there is valid data in the column in that row, and NULL when + // no data is present, with a small bit of additional iterator overhead. + for (int i = 0; i < ncol_ && i < types_.size(); i++) { + types_[i] = (sqlite3_column_type(ptr_(), i)); + } + } else { + throw sqlite_error{"sqlite3_step", code}; + } + + step_++; + } + + return *this; +} + +auto database::iterator::restart() -> iterator& +{ + sqlite3_reset(ptr_()); + step_ = -1; + done_ = false; + return *this; +} + +auto database::iterator::current_row() const noexcept -> int +{ + return step_; +} + +auto database::iterator::num_columns() const noexcept -> int +{ + return ncol_; +} + +auto database::iterator::find(const std::string& name) const noexcept -> int +{ + const auto pos = std::find(names_.begin(), names_.end(), name); + const auto dist = std::distance(names_.begin(), pos); + return dist >= names_.size() ? -1 : dist; +} + +auto database::iterator::columns() const noexcept + -> const boost::span +{ + return names_; +} + +auto database::iterator::types() const noexcept + -> const boost::span +{ + return types_; +} + +void database::iterator::handle_get_index_(int col) const +{ + if (done()) { + throw sqlite_already_done_error; + } + + if (current_row() == -1) { + throw sqlite_not_started_error; + } + + if (col < 0 || col >= ncol_) { + throw std::out_of_range( + "column " + std::to_string(col) + " out of range of " + + std::to_string(ncol_) + " columns" + ); + } +} + +template<> +auto database::iterator::get(int col) const + -> double +{ + handle_get_index_(col); + return sqlite3_column_double(ptr_(), col); +} + +template<> +auto database::iterator::get(int col) const + -> int +{ + handle_get_index_(col); + return sqlite3_column_int(ptr_(), col); +} + +template<> +auto database::iterator::get(int col) const + -> std::string +{ + handle_get_index_(col); + // TODO: this won't work with non-ASCII text + int size = sqlite3_column_bytes(ptr_(), col); + const unsigned char* ptr = sqlite3_column_text(ptr_(), col); + return { ptr, ptr + size }; +} + +template<> +auto database::iterator::get>(int col) const + -> std::vector +{ + handle_get_index_(col); + int size = sqlite3_column_bytes(ptr_(), col); + auto ptr = static_cast(sqlite3_column_blob(ptr_(), col)); + return {ptr, ptr + size}; +} + +// ngen::sqlite::database ===================================================== + +database::database(const std::string& path) +{ + sqlite3* conn = nullptr; + const int code = sqlite3_open_v2(path.c_str(), &conn, SQLITE_OPEN_READONLY, nullptr); + if (code != SQLITE_OK) { + throw sqlite_error{"sqlite3_open_v2", code}; + } + conn_ = sqlite_t{conn}; +} + +auto database::connection() const noexcept -> sqlite3* +{ + return conn_.get(); +} + +auto database::contains(const std::string& table) -> bool +{ + auto q = query("SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name=?)", table); + q.next(); + return q.get(0); +} + +auto database::query( + const std::string& statement, + const boost::span binds +) -> iterator +{ + sqlite3_stmt* stmt = nullptr; + const int code = sqlite3_prepare_v2( + connection(), + statement.c_str(), + statement.length() + 1, + &stmt, + nullptr + ); + + if (code != SQLITE_OK) { + throw sqlite_error{"sqlite3_prepare_v2", code}; + } + + if (!binds.empty()) { + for (int i = 0; i < binds.size(); i++) { + const int bind_code = sqlite3_bind_text(stmt, i + 1, binds[i].c_str(), -1, SQLITE_TRANSIENT); + if (bind_code != SQLITE_OK) { + throw sqlite_error{"sqlite3_bind_text", code}; + } + } + } + + return iterator{stmt_t{stmt}}; +} + +} // namespace sqlite +} // namespace ngen diff --git a/src/geopackage/proj.cpp b/src/geopackage/proj.cpp new file mode 100644 index 0000000000..8bd5746d34 --- /dev/null +++ b/src/geopackage/proj.cpp @@ -0,0 +1,23 @@ +#include "proj.hpp" + +namespace ngen { +namespace srs { + +const epsg::def_type epsg::defs_ = { + {4326, epsg::srs_type(bg::srs::dpar::proj_longlat)(bg::srs::dpar::ellps_wgs84)(bg::srs::dpar::datum_wgs84)(bg::srs::dpar::no_defs)}, + {5070, epsg::srs_type(bg::srs::dpar::proj_aea)(bg::srs::dpar::ellps_grs80)(bg::srs::dpar::towgs84, {0,0,0,0,0,0,0})(bg::srs::dpar::lat_0, 23)(bg::srs::dpar::lon_0, -96)(bg::srs::dpar::lat_1, 29.5)(bg::srs::dpar::lat_2, 45.5)(bg::srs::dpar::x_0, 0)(bg::srs::dpar::y_0, 0)}, + {3857, epsg::srs_type(bg::srs::dpar::proj_merc)(bg::srs::dpar::units_m)(bg::srs::dpar::no_defs)(bg::srs::dpar::a, 6378137)(bg::srs::dpar::b, 6378137)(bg::srs::dpar::lat_ts, 0)(bg::srs::dpar::lon_0, 0)(bg::srs::dpar::x_0, 0)(bg::srs::dpar::y_0, 0)(bg::srs::dpar::k, 1)} +}; + +auto epsg::get(uint32_t srid) -> srs_type +{ + + if (defs_.count(srid) == 0) { + throw std::runtime_error("SRID " + std::to_string(srid) + " is not supported. Project the input data to EPSG:5070 or EPSG:4326."); + } + + return defs_.at(srid); +} + +} // namespace srs +} // namespace ngen diff --git a/src/geopackage/properties.cpp b/src/geopackage/properties.cpp index a124ebd55b..4d9282da7c 100644 --- a/src/geopackage/properties.cpp +++ b/src/geopackage/properties.cpp @@ -1,24 +1,26 @@ -#include "GeoPackage.hpp" +#include "geopackage.hpp" #include "JSONProperty.hpp" -geojson::JSONProperty get_property(const geopackage::sqlite_iter& row, const std::string& name, int type) +geojson::JSONProperty get_property( + const ngen::sqlite::database::iterator& row, + const std::string& name, + int type +) { - if (type == SQLITE_INTEGER) { - auto val = row.get(name); - return geojson::JSONProperty(name, val); - } else if (type == SQLITE_FLOAT) { - auto val = row.get(name); - return geojson::JSONProperty(name, val); - } else if (type == SQLITE_TEXT) { - auto val = row.get(name); - return geojson::JSONProperty(name, val); - } else { - return geojson::JSONProperty(name, "null"); + switch(type) { + case SQLITE_INTEGER: + return { name, row.get(name)}; + case SQLITE_FLOAT: + return { name, row.get(name)}; + case SQLITE_TEXT: + return { name, row.get(name)}; + default: + return { name, "null" }; } } -geojson::PropertyMap geopackage::build_properties( - const sqlite_iter& row, +geojson::PropertyMap ngen::geopackage::build_properties( + const ngen::sqlite::database::iterator& row, const std::string& geom_col ) { diff --git a/src/geopackage/read.cpp b/src/geopackage/read.cpp index b263a3d4f7..f105234bad 100644 --- a/src/geopackage/read.cpp +++ b/src/geopackage/read.cpp @@ -1,19 +1,21 @@ -#include "GeoPackage.hpp" +#include "geopackage.hpp" #include #include void check_table_name(const std::string& table) { - if (boost::algorithm::starts_with(table, "sqlite_")) + if (boost::algorithm::starts_with(table, "sqlite_")) { throw std::runtime_error("table `" + table + "` is not queryable"); + } std::regex allowed("[^-A-Za-z0-9_ ]+"); - if (std::regex_match(table, allowed)) + if (std::regex_match(table, allowed)) { throw std::runtime_error("table `" + table + "` contains invalid characters"); + } } -std::shared_ptr geopackage::read( +std::shared_ptr ngen::geopackage::read( const std::string& gpkg_path, const std::string& layer = "", const std::vector& ids = {} @@ -22,10 +24,10 @@ std::shared_ptr geopackage::read( // Check for malicious/invalid layer input check_table_name(layer); - sqlite db(gpkg_path); + ngen::sqlite::database db{gpkg_path}; // Check if layer exists - if (!db.has_table(layer)) { + if (!db.contains(layer)) { // Since the layer doesn't exist, we need to output some additional // debug information with the error. In this case, we add ALL the tables // available in the GPKG, so that if the user sees this error, then it @@ -49,7 +51,7 @@ std::shared_ptr geopackage::read( if(layer == "divides"){ try { //TODO: A bit primitive. Actually introspect the schema somehow? https://www.sqlite.org/c3ref/funclist.html - sqlite_iter query_get_first_row = db.query("SELECT divide_id FROM " + layer + " LIMIT 1"); + auto query_get_first_row = db.query("SELECT divide_id FROM " + layer + " LIMIT 1"); id_column = "divide_id"; } catch (const std::exception& e){ @@ -77,13 +79,13 @@ std::shared_ptr geopackage::read( } // Get number of features - sqlite_iter query_get_layer_count = db.query("SELECT COUNT(*) FROM " + layer + joined_ids, ids); + auto query_get_layer_count = db.query("SELECT COUNT(*) FROM " + layer + joined_ids, ids); query_get_layer_count.next(); const int layer_feature_count = query_get_layer_count.get(0); #ifndef NGEN_QUIET // output debug info on what is read exactly - std::cout << "Reading " << layer_feature_count << " features in layer " << layer << " from "< geopackage::read( #endif // Get layer feature metadata (geometry column name + type) - sqlite_iter query_get_layer_geom_meta = db.query("SELECT column_name FROM gpkg_geometry_columns WHERE table_name = ?", layer); + auto query_get_layer_geom_meta = db.query("SELECT column_name FROM gpkg_geometry_columns WHERE table_name = ?", layer); query_get_layer_geom_meta.next(); const std::string layer_geometry_column = query_get_layer_geom_meta.get(0); // Get layer - sqlite_iter query_get_layer = db.query("SELECT * FROM " + layer + joined_ids, ids); + auto query_get_layer = db.query("SELECT * FROM " + layer + joined_ids, ids); query_get_layer.next(); // build features out of layer query diff --git a/src/geopackage/sqlite/database.cpp b/src/geopackage/sqlite/database.cpp deleted file mode 100644 index 41a7d418a4..0000000000 --- a/src/geopackage/sqlite/database.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "NGen_SQLite.hpp" - -using namespace geopackage; - -/** - * Get a runtime error based on a function and code. - * - * @param f String denoting the function where the error originated - * @param code sqlite3 result code - * @param extra additional messages to add to the end of the error - * @return std::runtime_error - */ -std::runtime_error sqlite_error(const std::string& f, int code, const std::string& extra = "") -{ - std::string errmsg = f + " returned code " - + std::to_string(code) - + " (msg: " - + std::string(sqlite3_errstr(code)) - + ")"; - - if (!extra.empty()) { - errmsg += " "; - errmsg += extra; - } - - return std::runtime_error(errmsg); -} - -sqlite::sqlite(const std::string& path) -{ - sqlite3* conn; - int code = sqlite3_open_v2(path.c_str(), &conn, SQLITE_OPEN_READONLY, NULL); - if (code != SQLITE_OK) { - throw sqlite_error("sqlite3_open_v2", code); - } - this->conn = sqlite_t(conn); -} - -sqlite3* sqlite::connection() const noexcept -{ - return this->conn.get(); -} - -bool sqlite::has_table(const std::string& table) noexcept -{ - auto q = this->query("SELECT EXISTS(SELECT 1 from sqlite_master WHERE type='table' AND name=?)", table); - q.next(); - return q.get(0); -}; - -sqlite_iter sqlite::query(const std::string& statement) -{ - sqlite3_stmt* stmt; - const auto cstmt = statement.c_str(); - const int code = sqlite3_prepare_v2(this->connection(), cstmt, statement.length() + 1, &stmt, NULL); - - if (code != SQLITE_OK) { - // something happened, can probably switch on result codes - // https://www.sqlite.org/rescode.html - throw sqlite_error("sqlite3_prepare_v2", code); - } - - return sqlite_iter(std::move(stmt_t(stmt))); -} - -sqlite_iter sqlite::query(const std::string& statement, const std::vector& binds) -{ - sqlite3_stmt* stmt; - const auto cstmt = statement.c_str(); - const int code = sqlite3_prepare_v2(this->connection(), cstmt, statement.length() + 1, &stmt, NULL); - - if (code != SQLITE_OK) { - throw sqlite_error("sqlite3_prepare_v2", code); - } - - if (!binds.empty()) { - for (size_t i = 0; i < binds.size(); i++) { - const int code = sqlite3_bind_text(stmt, i + 1, binds[i].c_str(), -1, SQLITE_TRANSIENT); - if (code != SQLITE_OK) { - throw sqlite_error("sqlite3_bind_text", code); - } - } - } - - return sqlite_iter(std::move(stmt_t(stmt))); -} - -template -inline sqlite_iter sqlite::query(const std::string& statement, T const&... params) -{ - sqlite3_stmt* stmt; - const auto cstmt = statement.c_str(); - const int code = sqlite3_prepare_v2(this->connection(), cstmt, statement.length() + 1, &stmt, NULL); - - if (code != SQLITE_OK) { - throw sqlite_error("sqlite3_prepare_v2", code); - } - - std::vector binds{ { params... } }; - for (size_t i = 0; i < binds.size(); i++) { - const int code = sqlite3_bind_text(stmt, i + 1, binds[i].c_str(), -1, SQLITE_TRANSIENT); - if (code != SQLITE_OK) { - throw sqlite_error("sqlite3_bind_text", code); - } - } - - return sqlite_iter(std::move(stmt_t(stmt))); -} diff --git a/src/geopackage/sqlite/iterator.cpp b/src/geopackage/sqlite/iterator.cpp deleted file mode 100644 index 7b224d6a2d..0000000000 --- a/src/geopackage/sqlite/iterator.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include -#include - -#include "NGen_SQLite.hpp" - -using namespace geopackage; - -// Defined in database.cpp -extern std::runtime_error sqlite_error(const std::string& f, int code, const std::string& extra = ""); - -/** - * Runtime error for iterations that haven't started - */ -const auto sqlite_get_notstarted_error = std::runtime_error( - "sqlite iteration is has not started, get() is not callable (call sqlite_iter::next() before)" -); - -/** - * Runtime error for iterations that are finished - */ -const auto sqlite_get_done_error = std::runtime_error( - "sqlite iteration is done, get() is not callable" -); - -sqlite_iter::sqlite_iter(stmt_t stmt) - : stmt(std::move(stmt)) -{ - // sqlite3_column_type requires the last result code to be - // SQLITE_ROW, so we need to iterate on the first row. - // TODO: need to test how this functions if the last result code - // was SQLITE_DONE. - this->next(); - this->column_count = sqlite3_column_count(this->ptr()); - this->column_names.reserve(this->column_count); - this->column_types.reserve(this->column_count); - - for (int i = 0; i < this->column_count; i++) { - this->column_names.emplace_back(sqlite3_column_name(this->ptr(), i)); - this->column_types.emplace_back(sqlite3_column_type(this->ptr(), i)); - } - - this->restart(); -} - -sqlite3_stmt* sqlite_iter::ptr() const noexcept -{ - return this->stmt.get(); -} - -void sqlite_iter::handle_get_index(int col) const -{ - if (this->done()) { - throw sqlite_get_done_error; - } - - if (this->current_row() == -1) { - throw sqlite_get_notstarted_error; - } - - if (col < 0 || col >= this->column_count) { - - throw std::out_of_range( - "column " + std::to_string(col) + " out of range of " + std::to_string(this->column_count) + " columns" - ); - } -} - -bool sqlite_iter::done() const noexcept -{ - return this->iteration_finished; -} - -sqlite_iter& sqlite_iter::next() -{ - if (!this->done()) { - const int returncode = sqlite3_step(this->ptr()); - if (returncode == SQLITE_DONE) { - this->iteration_finished = true; - } - else if( returncode == SQLITE_ROW){ - // Update column dtypes for the next row - // There are a couple ways to do this, each with some nuance: - // 1) use sqlite3_column_type upon construction of the iterator and inspect the first row - // then use those column types for all rows - // 2) use the sqlite3 table definition schema (i.e. PRAGMA table_info(...);) to get column types - // 3) check each row during iteration using sqlite3_column_type - // 1 & 2 may not produce consistent results because if the value in a column is NaN, - // then sqlite3_column_type will report that as a 5 or NULL type, even if the schema has a datatype - // Using 1, when the first row contains NaN but other rows have valid data, then all types are reported as NULL. - // Using 2, any NaN/NULL data will get interperted as the schema type - // Using 3 ensures we get a non-null type if there is valid data in the column in that row, and NULL when - // no data is present, with a small bit of additional iterator overhead. - for (int i = 0; i < this->column_count && i < column_types.size(); i++) { - this->column_types[i] = (sqlite3_column_type(this->ptr(), i)); - } - } else { - throw sqlite_error("sqlite3_step", returncode); - } - this->iteration_step++; - } - - return *this; -} - -sqlite_iter& sqlite_iter::restart() -{ - sqlite3_reset(this->ptr()); - this->iteration_step = -1; - this->iteration_finished = false; - return *this; -} - -int sqlite_iter::current_row() const noexcept -{ - return this->iteration_step; -} - -int sqlite_iter::num_columns() const noexcept -{ - return this->column_count; -} - -int sqlite_iter::column_index(const std::string& name) const noexcept -{ - const size_t pos = - std::distance(this->column_names.begin(), std::find(this->column_names.begin(), this->column_names.end(), name)); - - return pos >= this->column_names.size() ? -1 : pos; -} - -template<> -std::vector sqlite_iter::get>(int col) const -{ - this->handle_get_index(col); - int size = sqlite3_column_bytes(this->ptr(), col); - const uint8_t* ptr = static_cast(sqlite3_column_blob(this->ptr(), col)); - std::vector blob(ptr, ptr+size); - return blob; -} - -template<> -double sqlite_iter::get(int col) const -{ - this->handle_get_index(col); - return sqlite3_column_double(this->ptr(), col); -} - -template<> -int sqlite_iter::get(int col) const -{ - this->handle_get_index(col); - return sqlite3_column_int(this->ptr(), col); -} - -template<> -std::string sqlite_iter::get(int col) const -{ - this->handle_get_index(col); - // TODO: this won't work with non-ASCII text - int size = sqlite3_column_bytes(this->ptr(), col); - const unsigned char* ptr = sqlite3_column_text(this->ptr(), col); - return std::string(ptr, ptr+size); -} diff --git a/src/geopackage/wkb.cpp b/src/geopackage/wkb.cpp index c29a612dc6..48d8bad75e 100644 --- a/src/geopackage/wkb.cpp +++ b/src/geopackage/wkb.cpp @@ -1,6 +1,8 @@ -#include "WKB.hpp" +#include "wkb.hpp" +#include "proj.hpp" -using namespace geopackage; +namespace ngen { +namespace geopackage { enum wkb_geom_t { geometry = 0, @@ -196,51 +198,9 @@ typename wkb::geometry wkb::read(const boost::span buffer) // WKB Projection Visitor // ---------------------------------------------------------------------------- -bg::srs::dpar::parameters<> wkb::get_prj(uint32_t srid) { - /** - * EPSG 5070 and 3857 projection definitions for use with boost::geometry. - * - * @note these are required because boost 1.72.0 does not - * have an EPSG definition for 5070 or 3857 in boost::srs::epsg. - */ - const static auto epsg5070 = bg::srs::dpar::parameters<> - (bg::srs::dpar::proj_aea) - (bg::srs::dpar::ellps_grs80) - (bg::srs::dpar::towgs84, {0,0,0,0,0,0,0}) - (bg::srs::dpar::lat_0, 23) - (bg::srs::dpar::lon_0, -96) - (bg::srs::dpar::lat_1, 29.5) - (bg::srs::dpar::lat_2, 45.5) - (bg::srs::dpar::x_0, 0) - (bg::srs::dpar::y_0, 0); - - const static auto epsg3857 = bg::srs::dpar::parameters<> - (bg::srs::dpar::proj_merc) - (bg::srs::dpar::units_m) - (bg::srs::dpar::no_defs) - (bg::srs::dpar::a, 6378137) - (bg::srs::dpar::b, 6378137) - (bg::srs::dpar::lat_ts, 0) - (bg::srs::dpar::lon_0, 0) - (bg::srs::dpar::x_0, 0) - (bg::srs::dpar::y_0, 0) - (bg::srs::dpar::k, 1); - - switch(srid) { - case 5070: - return epsg5070; - case 3857: - return epsg3857; - default: - return bg::projections::detail::epsg_to_parameters(srid); - } -} - -// ---------------------------------------------------------------------------- - geojson::geometry wkb::wgs84::operator()(point_t& g) { - if (this->srs == 4326) { + if (this->srs == ngen::srs::epsg::wgs84) { return geojson::coordinate_t(g.get<0>(), g.get<1>()); } @@ -255,7 +215,7 @@ geojson::geometry wkb::wgs84::operator()(linestring_t& g) { geojson::linestring_t h; - if (this->srs == 4326) { + if (this->srs == ngen::srs::epsg::wgs84) { h.reserve(g.size()); for (auto&& gg : g) { h.emplace_back( @@ -275,7 +235,7 @@ geojson::geometry wkb::wgs84::operator()(polygon_t& g) { geojson::polygon_t h; - if(this->srs == 4326) { + if(this->srs == ngen::srs::epsg::wgs84) { h.outer().reserve(g.outer().size()); for (auto&& gg : g.outer()) { h.outer().emplace_back( @@ -308,7 +268,7 @@ geojson::geometry wkb::wgs84::operator()(multipoint_t& g) { geojson::multipoint_t h; - if (this->srs == 4326) { + if (this->srs == ngen::srs::epsg::wgs84) { h.reserve(g.size()); for (auto&& gg : g) { h.emplace_back( @@ -329,7 +289,7 @@ geojson::geometry wkb::wgs84::operator()(multilinestring_t& g) { geojson::multilinestring_t h; - if (this->srs == 4326) { + if (this->srs == ngen::srs::epsg::wgs84) { h.resize(g.size()); auto&& line_g = g.begin(); auto&& line_h = h.begin(); @@ -351,7 +311,7 @@ geojson::geometry wkb::wgs84::operator()(multipolygon_t& g) { geojson::multipolygon_t h; - if (this->srs == 4326) { + if (this->srs == ngen::srs::epsg::wgs84) { h.resize(g.size()); auto&& polygon_g = g.begin(); auto&& polygon_h = h.begin(); @@ -366,3 +326,6 @@ geojson::geometry wkb::wgs84::operator()(multipolygon_t& g) return h; } + +} // namespace geopackage +} // namespace ngen diff --git a/src/partitionGenerator.cpp b/src/partitionGenerator.cpp index 87061def4a..976abf7e9f 100644 --- a/src/partitionGenerator.cpp +++ b/src/partitionGenerator.cpp @@ -14,7 +14,7 @@ #include #ifdef NGEN_WITH_SQLITE3 -#include +#include #endif #include "core/Partition_Parser.hpp" @@ -429,7 +429,7 @@ int main(int argc, char* argv[]) if (boost::algorithm::ends_with(catchmentDataFile, "gpkg")) { #ifdef NGEN_WITH_SQLITE3 - catchment_collection = std::move( geopackage::read(catchmentDataFile, "divides", catchment_subset_ids) ); + catchment_collection = std::move( ngen::geopackage::read(catchmentDataFile, "divides", catchment_subset_ids) ); #else throw std::runtime_error("SQLite3 support required to read GeoPackage files."); #endif @@ -461,7 +461,7 @@ int main(int argc, char* argv[]) if (boost::algorithm::ends_with(nexusDataFile, "gpkg")) { #ifdef NGEN_WITH_SQLITE3 - global_nexus_collection = std::move( geopackage::read(nexusDataFile, "nexus", nexus_subset_ids) ); + global_nexus_collection = std::move( ngen::geopackage::read(nexusDataFile, "nexus", nexus_subset_ids) ); #else throw std::runtime_error("SQLite3 support required to read GeoPackage files."); #endif diff --git a/test/geopackage/GeoPackage_Test.cpp b/test/geopackage/GeoPackage_Test.cpp index 304bf9d559..5ed10a6096 100644 --- a/test/geopackage/GeoPackage_Test.cpp +++ b/test/geopackage/GeoPackage_Test.cpp @@ -1,7 +1,7 @@ #include #include -#include "GeoPackage.hpp" +#include "geopackage.hpp" #include "FileChecker.h" class GeoPackage_Test : public ::testing::Test @@ -38,7 +38,7 @@ class GeoPackage_Test : public ::testing::Test TEST_F(GeoPackage_Test, geopackage_read_test) { - const auto gpkg = geopackage::read(this->path, "test", {}); + const auto gpkg = ngen::geopackage::read(this->path, "test", {}); EXPECT_NE(gpkg->find("First"), -1); EXPECT_NE(gpkg->find("Second"), -1); const auto bbox = gpkg->get_bounding_box(); @@ -62,7 +62,7 @@ TEST_F(GeoPackage_Test, geopackage_read_test) TEST_F(GeoPackage_Test, geopackage_idsubset_test) { - const auto gpkg = geopackage::read(this->path, "test", { "First" }); + const auto gpkg = ngen::geopackage::read(this->path, "test", { "First" }); EXPECT_NE(gpkg->find("First"), -1); EXPECT_EQ(gpkg->find("Second"), -1); @@ -79,7 +79,7 @@ TEST_F(GeoPackage_Test, geopackage_idsubset_test) // are stored in EPSG:3857. When read in, they should convert to EPSG:4326. TEST_F(GeoPackage_Test, geopackage_projection_test) { - const auto gpkg = geopackage::read(this->path2, "example_3857", {}); + const auto gpkg = ngen::geopackage::read(this->path2, "example_3857", {}); EXPECT_NE(gpkg->find("First"), -1); EXPECT_NE(gpkg->find("Second"), -1); const auto bbox = gpkg->get_bounding_box(); diff --git a/test/geopackage/SQLite_Test.cpp b/test/geopackage/SQLite_Test.cpp index 726ac32979..8e1efdb58a 100644 --- a/test/geopackage/SQLite_Test.cpp +++ b/test/geopackage/SQLite_Test.cpp @@ -1,9 +1,9 @@ #include -#include "NGen_SQLite.hpp" -#include "FileChecker.h" +#include -using namespace geopackage; +#include "ngen_sqlite.hpp" +#include "FileChecker.h" class SQLite_Test : public ::testing::Test { @@ -29,15 +29,15 @@ class SQLite_Test : public ::testing::Test TEST_F(SQLite_Test, sqlite_access_test) { - sqlite db {this->path}; + ngen::sqlite::database db {this->path}; // user wants metadata - EXPECT_TRUE(db.has_table("gpkg_contents")); - EXPECT_FALSE(db.has_table("some_fake_table")); + EXPECT_TRUE(db.contains("gpkg_contents")); + EXPECT_FALSE(db.contains("some_fake_table")); } TEST_F(SQLite_Test, sqlite_query_test) { - sqlite db {this->path}; + ngen::sqlite::database db {this->path}; if (db.connection() == nullptr) { FAIL() << "database is not loaded"; @@ -45,10 +45,11 @@ TEST_F(SQLite_Test, sqlite_query_test) // user provides a query const std::string query = "SELECT * FROM gpkg_contents WHERE table_name = 'flowpaths' LIMIT 1"; - sqlite_iter iter = db.query(query); + ngen::sqlite::database::iterator iter = db.query(query); EXPECT_EQ(iter.num_columns(), 10); - EXPECT_EQ(iter.columns(), std::vector({ + + std::vector expected_columns = { "table_name", "data_type", "identifier", @@ -59,7 +60,11 @@ TEST_F(SQLite_Test, sqlite_query_test) "max_x", "max_y", "srs_id" - })); + }; + + for (size_t i = 0; i < expected_columns.size(); i++) { + EXPECT_EQ(iter.columns()[i], expected_columns[i]); + } // user iterates over row ASSERT_NO_THROW(iter.next()); diff --git a/test/geopackage/WKB_Test.cpp b/test/geopackage/WKB_Test.cpp index b60e6fd334..3c9f93c5ac 100644 --- a/test/geopackage/WKB_Test.cpp +++ b/test/geopackage/WKB_Test.cpp @@ -1,9 +1,9 @@ #include #include -#include +#include -using namespace geopackage; +using wkb = ngen::geopackage::wkb; class WKB_Test : public ::testing::Test {