diff --git a/src/expire-tiles.cpp b/src/expire-tiles.cpp index 6aff88830..7ff7b2141 100644 --- a/src/expire-tiles.cpp +++ b/src/expire-tiles.cpp @@ -122,6 +122,16 @@ int expire_tiles::normalise_tile_x_coord(int x) return x; } +void expire_tiles::coords_to_tile(double lon, double lat, double *tilex, + double *tiley) +{ + auto const c = + projection->target_to_tile(osmium::geom::Coordinates{lon, lat}); + + *tilex = map_width * (0.5 + c.x / EARTH_CIRCUMFERENCE); + *tiley = map_width * (0.5 - c.y / EARTH_CIRCUMFERENCE); +} + /* * Expire tiles that a line crosses */ @@ -133,8 +143,8 @@ void expire_tiles::from_line(double lon_a, double lat_a, double lon_b, double tile_x_b; double tile_y_b; - projection->coords_to_tile(&tile_x_a, &tile_y_a, lon_a, lat_a, map_width); - projection->coords_to_tile(&tile_x_b, &tile_y_b, lon_b, lat_b, map_width); + coords_to_tile(lon_a, lat_a, &tile_x_a, &tile_y_a); + coords_to_tile(lon_b, lat_b, &tile_x_b, &tile_y_b); if (tile_x_a > tile_x_b) { /* We always want the line to go from left to right - swap the ends if it doesn't */ @@ -224,10 +234,10 @@ int expire_tiles::from_bbox(double min_lon, double min_lat, double max_lon, /* Convert the box's Mercator coordinates into tile coordinates */ double tmp_x; double tmp_y; - projection->coords_to_tile(&tmp_x, &tmp_y, min_lon, max_lat, map_width); + coords_to_tile(min_lon, max_lat, &tmp_x, &tmp_y); int min_tile_x = tmp_x - TILE_EXPIRY_LEEWAY; int min_tile_y = tmp_y - TILE_EXPIRY_LEEWAY; - projection->coords_to_tile(&tmp_x, &tmp_y, max_lon, min_lat, map_width); + coords_to_tile(max_lon, min_lat, &tmp_x, &tmp_y); int max_tile_x = tmp_x + TILE_EXPIRY_LEEWAY; int max_tile_y = tmp_y + TILE_EXPIRY_LEEWAY; if (min_tile_x < 0) { diff --git a/src/expire-tiles.hpp b/src/expire-tiles.hpp index 1942a4fac..262770238 100644 --- a/src/expire-tiles.hpp +++ b/src/expire-tiles.hpp @@ -142,6 +142,12 @@ struct expire_tiles static xy_coord_t quadkey_to_xy(uint64_t quadkey, uint32_t zoom); private: + + /** + * Converts from target coordinates to tile coordinates. + */ + void coords_to_tile(double lon, double lat, double *tilex, double *tiley); + /** * Expire a single tile. * diff --git a/src/middle.hpp b/src/middle.hpp index 438997647..7ba01e5e8 100644 --- a/src/middle.hpp +++ b/src/middle.hpp @@ -13,7 +13,6 @@ #include #include "osmtypes.hpp" -#include "reprojection.hpp" #include "thread-pool.hpp" /** diff --git a/src/reprojection-generic-none.cpp b/src/reprojection-generic-none.cpp index 30998ef32..4438be191 100644 --- a/src/reprojection-generic-none.cpp +++ b/src/reprojection-generic-none.cpp @@ -2,7 +2,7 @@ #include "reprojection.hpp" -std::shared_ptr reprojection::make_generic_projection(int srs) +std::shared_ptr reprojection::make_generic_projection(int) { throw std::runtime_error{"No generic projection library available."}; } diff --git a/src/reprojection-generic-proj4.cpp b/src/reprojection-generic-proj4.cpp index 62693f415..1a4442651 100644 --- a/src/reprojection-generic-proj4.cpp +++ b/src/reprojection-generic-proj4.cpp @@ -5,37 +5,32 @@ namespace { /** - * Generic projection based using proj library. + * Generic projection using proj library. */ class generic_reprojection_t : public reprojection { public: - explicit generic_reprojection_t(int srs) - : m_target_srs(srs), pj_target(srs), pj_source(PROJ_LATLONG), - pj_tile( - "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 " - "+y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs") + explicit generic_reprojection_t(int srs) : m_target_srs(srs), pj_target(srs) {} osmium::geom::Coordinates reproject(osmium::Location loc) const override { - using namespace osmium::geom; - return transform(pj_source, pj_target, - Coordinates{deg_to_rad(loc.lon_without_check()), - deg_to_rad(loc.lat_without_check())}); + double const lon = osmium::geom::deg_to_rad(loc.lon_without_check()); + double const lat = osmium::geom::deg_to_rad(loc.lat_without_check()); + + return osmium::geom::transform(pj_source, pj_target, + osmium::geom::Coordinates{lon, lat}); } - void target_to_tile(double *lat, double *lon) const override + osmium::geom::Coordinates + target_to_tile(osmium::geom::Coordinates coords) const override { - auto const c = transform(pj_target, pj_tile, - osmium::geom::Coordinates{*lon, *lat}); - - *lon = c.x; - *lat = c.y; + return osmium::geom::transform(pj_target, pj_tile, coords); } - int target_srs() const override { return m_target_srs; } - char const *target_desc() const override + int target_srs() const noexcept override { return m_target_srs; } + + char const *target_desc() const noexcept override { return pj_get_def(pj_target.get(), 0); } @@ -43,15 +38,17 @@ class generic_reprojection_t : public reprojection private: int m_target_srs; osmium::geom::CRS pj_target; - /** The projection of the source data. Always lat/lon (EPSG:4326). */ - osmium::geom::CRS pj_source; - /** The projection used for tiles. Currently this is fixed to be Spherical - * Mercator. You will usually have tiles in the same projection as used - * for PostGIS, but it is theoretically possible to have your PostGIS data - * in, say, lat/lon but still create tiles in Spherical Mercator. + /// The projection of the source data. Always lat/lon (EPSG:4326). + osmium::geom::CRS pj_source{PROJ_LATLONG}; + + /** + * The projection used for tiles. Currently this is fixed to be Spherical + * Mercator. You will usually have tiles in the same projection as used + * for PostGIS, but it is theoretically possible to have your PostGIS data + * in, say, lat/lon but still create tiles in Spherical Mercator. */ - osmium::geom::CRS pj_tile; + osmium::geom::CRS pj_tile{PROJ_SPHERE_MERC}; }; } // anonymous namespace diff --git a/src/reprojection.cpp b/src/reprojection.cpp index 560122372..a92f9f59d 100644 --- a/src/reprojection.cpp +++ b/src/reprojection.cpp @@ -1,53 +1,39 @@ -/* reprojection.c - * - * Convert OSM coordinates to another coordinate system for - * the database (usually convert lat/lon to Spherical Mercator - * so Mapnik doesn't have to). - */ - -#include -#include -#include - #include #include "reprojection.hpp" -/** must match expire.tiles.c */ -#define EARTH_CIRCUMFERENCE 40075016.68 - namespace { -void latlon2merc(double *lat, double *lon) +osmium::geom::Coordinates lonlat2merc(osmium::geom::Coordinates coords) { - if (*lat > 89.99) { - *lat = 89.99; - } else if (*lat < -89.99) { - *lat = -89.99; + if (coords.y > 89.99) { + coords.y = 89.99; + } else if (coords.y < -89.99) { + coords.y = -89.99; } - using namespace osmium::geom; - auto const coord = lonlat_to_mercator(Coordinates{*lon, *lat}); - *lon = coord.x; - *lat = coord.y; + return osmium::geom::lonlat_to_mercator(coords); } class latlon_reprojection_t : public reprojection { public: - osmium::geom::Coordinates reproject(osmium::Location loc) const override + osmium::geom::Coordinates reproject(osmium::Location loc) const + noexcept override { return osmium::geom::Coordinates{loc.lon_without_check(), loc.lat_without_check()}; } - void target_to_tile(double *lat, double *lon) const override + osmium::geom::Coordinates target_to_tile(osmium::geom::Coordinates c) const + noexcept override { - latlon2merc(lat, lon); + return lonlat2merc(c); } - int target_srs() const override { return PROJ_LATLONG; } - char const *target_desc() const override { return "Latlong"; } + int target_srs() const noexcept override { return PROJ_LATLONG; } + + char const *target_desc() const noexcept override { return "Latlong"; } }; class merc_reprojection_t : public reprojection @@ -55,20 +41,23 @@ class merc_reprojection_t : public reprojection public: osmium::geom::Coordinates reproject(osmium::Location loc) const override { - double lat = loc.lat_without_check(); - double lon = loc.lon_without_check(); - - latlon2merc(&lat, &lon); - - return osmium::geom::Coordinates{lon, lat}; + osmium::geom::Coordinates const coords{loc.lon_without_check(), + loc.lat_without_check()}; + return lonlat2merc(coords); } - void target_to_tile(double *, double *) const override - { /* nothing */ + osmium::geom::Coordinates target_to_tile(osmium::geom::Coordinates c) const + noexcept override + { + return c; } - int target_srs() const override { return PROJ_SPHERE_MERC; } - char const *target_desc() const override { return "Spherical Mercator"; } + int target_srs() const noexcept override { return PROJ_SPHERE_MERC; } + + char const *target_desc() const noexcept override + { + return "Spherical Mercator"; + } }; } // anonymous namespace @@ -80,16 +69,9 @@ std::shared_ptr reprojection::create_projection(int srs) return std::make_shared(); case PROJ_SPHERE_MERC: return std::make_shared(); + default: + break; } return make_generic_projection(srs); } - -void reprojection::coords_to_tile(double *tilex, double *tiley, double lon, - double lat, int map_width) -{ - target_to_tile(&lat, &lon); - - *tilex = map_width * (0.5 + lon / EARTH_CIRCUMFERENCE); - *tiley = map_width * (0.5 - lat / EARTH_CIRCUMFERENCE); -} diff --git a/src/reprojection.hpp b/src/reprojection.hpp index 1e37e419d..3ff6fea1c 100644 --- a/src/reprojection.hpp +++ b/src/reprojection.hpp @@ -1,10 +1,12 @@ #ifndef OSM2PGSQL_REPROJECTION_HPP #define OSM2PGSQL_REPROJECTION_HPP -/* - * Convert OSM latitude / longitude from degrees to mercator - * so that Mapnik does not have to project the data again +/** + * \file * + * This file is part of osm2pgsql (https://github.com/openstreetmap/osm2pgsql). + * + * It contains the reprojection class. */ #include @@ -18,6 +20,12 @@ enum Projection PROJ_SPHERE_MERC = 3857 }; +/** + * Virtual base class used for projecting OSM WGS84 coordinates into a + * different coordinate system. Most commonly this will be used to convert + * the coordinates into Spherical Mercator coordinates used in common web + * tiles. + */ class reprojection { public: @@ -38,25 +46,17 @@ class reprojection virtual osmium::geom::Coordinates reproject(osmium::Location loc) const = 0; /** - * Converts coordinates from target projection to tile projection (EPSG:3857) - * - * Do not confuse with coords_to_tile which does *not* calculate coordinates in the - * tile projection, but tile coordinates. + * Converts coordinates from target projection to tile projection + * (EPSG:3857) */ - virtual void target_to_tile(double *lat, double *lon) const = 0; + virtual osmium::geom::Coordinates + target_to_tile(osmium::geom::Coordinates) const = 0; - /** - * Converts from target coordinates to tile coordinates. - * - * The zoom level for the coordinates is explicitly given in the - * variable map_width. - */ - void coords_to_tile(double *tilex, double *tiley, double lon, double lat, - int map_width); - virtual int target_srs() const = 0; - virtual char const *target_desc() const = 0; + virtual int target_srs() const noexcept = 0; + + virtual char const *target_desc() const noexcept = 0; - bool target_latlon() const { return target_srs() == PROJ_LATLONG; } + bool target_latlon() const noexcept { return target_srs() == PROJ_LATLONG; } /** * Create a reprojection object with target srs `srs`. diff --git a/src/wkb.hpp b/src/wkb.hpp index 97baf439b..3f40ba7c5 100644 --- a/src/wkb.hpp +++ b/src/wkb.hpp @@ -253,7 +253,7 @@ class parser_t auto const x = read_data(); auto const y = read_data(); - return osmium::geom::Coordinates(x, y); + return osmium::geom::Coordinates{x, y}; } void skip_points(size_t num) { m_pos += sizeof(double) * 2 * num; } @@ -308,11 +308,9 @@ class parser_t double total = 0; - auto prev = read_point(); - proj->target_to_tile(&prev.y, &prev.x); + auto prev = proj->target_to_tile(read_point()); for (unsigned i = 1; i < num_pts; ++i) { - auto cur = read_point(); - proj->target_to_tile(&cur.y, &cur.x); + auto const cur = proj->target_to_tile(read_point()); total += prev.x * cur.y - cur.x * prev.y; prev = cur; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2e7cc14c9..f8b99b2a2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -62,6 +62,7 @@ set_test(test-parse-osmium LABELS NoDB) set_test(test-persistent-cache LABELS NoDB) set_test(test-pgsql) set_test(test-ram-cache LABELS NoDB) +set_test(test-reprojection LABELS NoDB) set_test(test-taginfo LABELS NoDB) set_test(test-util LABELS NoDB) set_test(test-wildcard-match LABELS NoDB) diff --git a/tests/test-options-projection.cpp b/tests/test-options-projection.cpp index 9719e4faa..9100c9394 100644 --- a/tests/test-options-projection.cpp +++ b/tests/test-options-projection.cpp @@ -9,11 +9,12 @@ static testing::db::import_t db; TEST_CASE("Projection setup") { - std::vector option_params = { - "osm2pgsql", "-S", OSM2PGSQLDATA_DIR "default.style", - "--number-processes", "1"}; + char const* const style_file = OSM2PGSQLDATA_DIR "default.style"; - char const *proj_name = nullptr; + std::vector option_params = {"osm2pgsql", "-S", style_file, + "--number-processes", "1"}; + + std::string proj_name; char const *srid = ""; SECTION("No options") @@ -29,7 +30,7 @@ TEST_CASE("Projection setup") srid = "4326"; } - SECTION("Mercartor option") + SECTION("Mercator option") { option_params.push_back("-m"); proj_name = "Spherical Mercator"; @@ -63,10 +64,10 @@ TEST_CASE("Projection setup") option_params.push_back("foo"); - options_t options((int)option_params.size(), (char **)option_params.data()); + options_t options{(int)option_params.size(), (char **)option_params.data()}; - if (proj_name) { - CHECK(strcmp(options.projection->target_desc(), proj_name) == 0); + if (!proj_name.empty()) { + CHECK(options.projection->target_desc() == proj_name); } db.run_import(options, "n1 Tamenity=bar x0 y0"); @@ -74,5 +75,5 @@ TEST_CASE("Projection setup") auto conn = db.connect(); CHECK(conn.require_scalar( - "SELECT find_srid('public', 'planet_osm_roads', 'way')") == srid); + "SELECT Find_SRID('public', 'planet_osm_roads', 'way')") == srid); } diff --git a/tests/test-reprojection.cpp b/tests/test-reprojection.cpp new file mode 100644 index 000000000..46c7e867a --- /dev/null +++ b/tests/test-reprojection.cpp @@ -0,0 +1,59 @@ +#include + +#include "reprojection.hpp" + +TEST_CASE("projection 4326", "[NoDB]") +{ + const osmium::Location loc{10.0, 53.0}; + int const srs = 4326; + + auto const reprojection = reprojection::create_projection(srs); + REQUIRE(reprojection->target_srs() == srs); + REQUIRE(reprojection->target_latlon()); + + auto const c = reprojection->reproject(loc); + REQUIRE(c.x == Approx(10.0)); + REQUIRE(c.y == Approx(53.0)); + + auto const ct = reprojection->target_to_tile(c); + REQUIRE(ct.x == Approx(1113194.91)); + REQUIRE(ct.y == Approx(6982997.92)); +} + +TEST_CASE("projection 3857", "[NoDB]") +{ + const osmium::Location loc{10.0, 53.0}; + int const srs = 3857; + + auto const reprojection = reprojection::create_projection(srs); + REQUIRE(reprojection->target_srs() == srs); + REQUIRE_FALSE(reprojection->target_latlon()); + + auto const c = reprojection->reproject(loc); + REQUIRE(c.x == Approx(1113194.91)); + REQUIRE(c.y == Approx(6982997.92)); + + auto const ct = reprojection->target_to_tile(c); + REQUIRE(ct.x == Approx(1113194.91)); + REQUIRE(ct.y == Approx(6982997.92)); +} + +#ifdef HAVE_GENERIC_PROJ +TEST_CASE("projection 5520", "[NoDB]") +{ + const osmium::Location loc{10.0, 53.0}; + int const srs = 5520; + + auto const reprojection = reprojection::create_projection(srs); + REQUIRE(reprojection->target_srs() == srs); + REQUIRE_FALSE(reprojection->target_latlon()); + + auto const c = reprojection->reproject(loc); + REQUIRE(c.x == Approx(1969644.93)); + REQUIRE(c.y == Approx(5897146.04)); + + auto const ct = reprojection->target_to_tile(c); + REQUIRE(ct.x == Approx(1113194.91)); + REQUIRE(ct.y == Approx(6982997.92)); +} +#endif