Skip to content

Commit 354f9ad

Browse files
authored
Merge pull request #1331 from joto/proj6-support
Work-in-progress: Add support for projection using PROJ library >6.1
2 parents ace7d22 + 552e4bc commit 354f9ad

11 files changed

Lines changed: 247 additions & 27 deletions

.github/actions/build-and-test/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ runs:
2222
else
2323
CMAKE_OPTIONS="$CMAKE_OPTIONS -DWITH_LUAJIT=${LUAJIT_OPTION}"
2424
fi
25+
if [ -n "$USE_PROJ_LIB" ]; then
26+
CMAKE_OPTIONS="$CMAKE_OPTIONS -DUSE_PROJ_LIB=$USE_PROJ_LIB"
27+
fi
2528
if [ -n "$CPP_VERSION" ]; then
2629
CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_CXX_STANDARD=$CPP_VERSION"
2730
fi

.github/workflows/ci.yml

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
PGHOST: /tmp
3737

3838

39-
ubuntu-pg93-gcc5:
39+
ubuntu16-pg93-gcc5:
4040
runs-on: ubuntu-16.04
4141

4242
env:
@@ -53,7 +53,7 @@ jobs:
5353
- uses: ./.github/actions/ubuntu-prerequisites
5454
- uses: ./.github/actions/build-and-test
5555

56-
ubuntu-pg94-clang6:
56+
ubuntu16-pg94-clang6:
5757
runs-on: ubuntu-16.04
5858

5959
env:
@@ -70,7 +70,7 @@ jobs:
7070
- uses: ./.github/actions/ubuntu-prerequisites
7171
- uses: ./.github/actions/build-and-test
7272

73-
ubuntu-pg95-gcc7-jit:
73+
ubuntu18-pg95-gcc7-jit:
7474
runs-on: ubuntu-18.04
7575

7676
env:
@@ -87,7 +87,7 @@ jobs:
8787
- uses: ./.github/actions/ubuntu-prerequisites
8888
- uses: ./.github/actions/build-and-test
8989

90-
ubuntu-pg96-clang8-jit:
90+
ubuntu18-pg96-clang8-jit:
9191
runs-on: ubuntu-18.04
9292

9393
env:
@@ -104,7 +104,7 @@ jobs:
104104
- uses: ./.github/actions/ubuntu-prerequisites
105105
- uses: ./.github/actions/build-and-test
106106

107-
ubuntu-pg10-gcc9:
107+
ubuntu18-pg10-gcc9:
108108
runs-on: ubuntu-18.04
109109

110110
env:
@@ -122,7 +122,7 @@ jobs:
122122
- uses: ./.github/actions/build-and-test
123123

124124

125-
ubuntu-pg11-clang9:
125+
ubuntu18-pg11-clang9:
126126
runs-on: ubuntu-18.04
127127

128128
env:
@@ -139,7 +139,7 @@ jobs:
139139
- uses: ./.github/actions/ubuntu-prerequisites
140140
- uses: ./.github/actions/build-and-test
141141

142-
ubuntu-pg12-gcc10-jit:
142+
ubuntu20-pg12-gcc10-jit:
143143
runs-on: ubuntu-20.04
144144

145145
env:
@@ -157,9 +157,8 @@ jobs:
157157
- uses: ./.github/actions/build-and-test
158158

159159

160-
ubuntu-pg13-clang10-jit:
160+
ubuntu20-pg13-clang10-jit:
161161
runs-on: ubuntu-20.04
162-
continue-on-error: true
163162

164163
env:
165164
CC: clang-10
@@ -175,7 +174,43 @@ jobs:
175174
- uses: ./.github/actions/ubuntu-prerequisites
176175
- uses: ./.github/actions/build-and-test
177176

178-
ubuntu-pg12-gcc10-release:
177+
ubuntu20-pg13-clang10-proj6:
178+
runs-on: ubuntu-20.04
179+
180+
env:
181+
CC: clang-10
182+
CXX: clang++-10
183+
LUA_VERSION: 5.3
184+
LUAJIT_OPTION: OFF
185+
POSTGRESQL_VERSION: 13
186+
POSTGIS_VERSION: 3
187+
CPPVERSION: 14
188+
USE_PROJ_LIB: 6
189+
190+
steps:
191+
- uses: actions/checkout@v2
192+
- uses: ./.github/actions/ubuntu-prerequisites
193+
- uses: ./.github/actions/build-and-test
194+
195+
ubuntu20-pg13-clang10-noproj:
196+
runs-on: ubuntu-20.04
197+
198+
env:
199+
CC: clang-10
200+
CXX: clang++-10
201+
LUA_VERSION: 5.3
202+
LUAJIT_OPTION: OFF
203+
POSTGRESQL_VERSION: 13
204+
POSTGIS_VERSION: 3
205+
CPPVERSION: 14
206+
USE_PROJ_LIB: off
207+
208+
steps:
209+
- uses: actions/checkout@v2
210+
- uses: ./.github/actions/ubuntu-prerequisites
211+
- uses: ./.github/actions/build-and-test
212+
213+
ubuntu20-pg12-gcc10-release:
179214
runs-on: ubuntu-20.04
180215

181216
env:

CMakeLists.txt

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ option(EXTERNAL_LIBOSMIUM "Do not use the bundled libosmium" OFF)
5252
option(EXTERNAL_PROTOZERO "Do not use the bundled protozero" OFF)
5353
option(EXTERNAL_FMT "Do not use the bundled fmt" OFF)
5454

55+
set(USE_PROJ_LIB "auto" CACHE STRING "Which version of PROJ API to use: ('4', '6', 'off', or 'auto')")
56+
5557
if (NOT WIN32 AND NOT APPLE)
5658
# No need for this path, just a workaround to make cmake work on all systems.
5759
# Without this we need the PostgreSQL server libraries installed.
@@ -202,22 +204,45 @@ find_package(Threads)
202204
203205
set(LIBS ${Boost_LIBRARIES} ${PostgreSQL_LIBRARY} ${OSMIUM_LIBRARIES})
204206
205-
find_path(PROJ4_INCLUDE_DIR proj_api.h)
206-
find_library(PROJ_LIBRARY NAMES proj)
207-
if (PROJ4_INCLUDE_DIR AND PROJ_LIBRARY)
208-
message(STATUS "Found Proj4 ${PROJ_LIBRARY}")
209-
set(HAVE_GENERIC_PROJ 1)
210-
set(HAVE_PROJ4 1)
211-
list(APPEND LIBS ${PROJ_LIBRARY})
212-
include_directories(SYSTEM ${PROJ_INCLUDE_DIR})
207+
if (USE_PROJ_LIB STREQUAL "off")
208+
message(STATUS "Proj library disabled (because USE_PROJ_LIB is set to 'off').")
213209
else()
214-
message(STATUS "Proj library not found.")
215-
message(STATUS " Only Mercartor and WGS84 projections will be available.")
210+
find_path(PROJ4_INCLUDE_DIR proj_api.h)
211+
if (PROJ4_INCLUDE_DIR AND NOT USE_PROJ_LIB STREQUAL "6")
212+
message(STATUS "Found proj_api.h")
213+
find_library(PROJ_LIBRARY NAMES proj)
214+
message(STATUS "Found Proj [API 4] ${PROJ_LIBRARY}")
215+
set(HAVE_GENERIC_PROJ 1)
216+
set(HAVE_PROJ4 1)
217+
list(APPEND LIBS ${PROJ_LIBRARY})
218+
include_directories(SYSTEM ${PROJ4_INCLUDE_DIR})
219+
elseif (NOT USE_PROJ_LIB STREQUAL "4")
220+
find_path(PROJ6_INCLUDE_DIR proj.h)
221+
find_library(PROJ_LIBRARY NAMES proj)
222+
if (PROJ_LIBRARY)
223+
message(STATUS "Found Proj [API 6] ${PROJ_LIBRARY}")
224+
set(HAVE_GENERIC_PROJ 1)
225+
set(HAVE_PROJ6 1)
226+
list(APPEND LIBS ${PROJ_LIBRARY})
227+
include_directories(SYSTEM ${PROJ6_INCLUDE_DIR})
228+
else()
229+
message(STATUS "Proj library not found.")
230+
message(STATUS " Only Mercartor and WGS84 projections will be available.")
231+
endif()
232+
endif()
233+
endif()
234+
235+
if (USE_PROJ_LIB STREQUAL "4" AND NOT HAVE_PROJ4)
236+
message(FATAL_ERROR "USE_PROJ_LIB was set to '4', but PROJ version 4 API not found")
237+
endif()
238+
239+
if (USE_PROJ_LIB STREQUAL "6" AND NOT HAVE_PROJ6)
240+
message(FATAL_ERROR "USE_PROJ_LIB was set to '6', but PROJ version 4 API not found")
216241
endif()
217242
218243
if (LUAJIT_FOUND)
219244
list(APPEND LIBS ${LUAJIT_LIBRARIES})
220-
elseif(LUA_FOUND)
245+
elseif (LUA_FOUND)
221246
list(APPEND LIBS ${LUA_LIBRARIES})
222247
endif()
223248

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,23 @@ Also available is the new flex output. It is much more flexible than the other
183183
outputs. IT IS CURRENTLY EXPERIMENTAL AND SUBJECT TO CHANGE. The flex output is
184184
only available if you have compiled osm2pgsql with Lua support.
185185

186+
## Projection Support ##
187+
188+
Osm2pgsql has builtin support for the Latlong (WGS84, EPSG:4326) and the
189+
WebMercator (EPSG:3857) projection. If you need other projections you have to
190+
compile with the PROJ library.
191+
192+
Both the older API (PROJ version 4) and the newer API (PROJ version 6.1 and
193+
above) are supported. Usually the CMake configuration will find a suitable
194+
version and use it automatically, but you can set the `USE_PROJ_LIB` CMake
195+
cache variable to choose between the following behaviours:
196+
197+
* `4`: Look for PROJ library with API version 4. If it is not found, stop with error.
198+
* `6`: Look for PROJ library with API version 6. If it is not found, stop with error.
199+
* `off`: Build without PROJ library.
200+
* `auto`: Choose API 4 if available, otherwise API 6. If both are not available
201+
build without PROJ library. (This is the default.)
202+
186203
## LuaJIT support ##
187204

188205
To speed up Lua tag transformations, [LuaJIT](https://luajit.org/) can be optionally

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ if (HAVE_PROJ4)
6060
set_source_files_properties(reprojection-generic-proj4.cpp
6161
PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations)
6262
endif()
63+
elseif(HAVE_PROJ6)
64+
list(APPEND osm2pgsql_lib_SOURCES reprojection-generic-proj6.cpp)
6365
else()
6466
list(APPEND osm2pgsql_lib_SOURCES reprojection-generic-none.cpp)
6567
endif()

src/options.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ options_t::options_t(int argc, char *argv[]) : options_t()
576576
case 'V':
577577
fmt::print(stderr, "Compiled using the following library versions:\n");
578578
fmt::print(stderr, "Libosmium {}\n", LIBOSMIUM_VERSION_STRING);
579+
fmt::print(stderr, "Proj {}\n", get_proj_version());
579580
#ifdef HAVE_LUA
580581
fmt::print(stderr, "{}", LUA_RELEASE);
581582
#ifdef HAVE_LUAJIT

src/reprojection-generic-none.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ std::shared_ptr<reprojection> reprojection::make_generic_projection(int)
66
{
77
throw std::runtime_error{"No generic projection library available."};
88
}
9+
10+
std::string get_proj_version() { return "[disabled]"; }
11+

src/reprojection-generic-proj4.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "format.hpp"
12
#include "reprojection.hpp"
23

34
#include <osmium/geom/projection.hpp>
@@ -57,3 +58,6 @@ std::shared_ptr<reprojection> reprojection::make_generic_projection(int srs)
5758
{
5859
return std::make_shared<generic_reprojection_t>(srs);
5960
}
61+
62+
std::string get_proj_version() { return "[API 4] {}"_format(pj_get_release()); }
63+

src/reprojection-generic-proj6.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#include "format.hpp"
2+
#include "reprojection.hpp"
3+
4+
#include <osmium/geom/coordinates.hpp>
5+
#include <osmium/geom/util.hpp>
6+
7+
#include "proj.h"
8+
9+
namespace {
10+
11+
/**
12+
* Generic projection using proj library (version 6 and above).
13+
*/
14+
class generic_reprojection_t : public reprojection
15+
{
16+
public:
17+
explicit generic_reprojection_t(int srs)
18+
: m_target_srs(srs), m_context(proj_context_create())
19+
{
20+
assert(m_context);
21+
22+
m_transformation = create_transformation(PROJ_LATLONG, srs);
23+
24+
m_transformation.reset(proj_normalize_for_visualization(
25+
m_context.get(), m_transformation.get()));
26+
27+
if (!m_transformation) {
28+
throw std::runtime_error{
29+
"Invalid projection '{}': {}"_format(srs, errormsg())};
30+
}
31+
32+
m_transformation_tile = create_transformation(PROJ_SPHERE_MERC, srs);
33+
}
34+
35+
osmium::geom::Coordinates reproject(osmium::Location loc) const override
36+
{
37+
return transform(m_transformation.get(),
38+
osmium::geom::Coordinates{loc.lon_without_check(),
39+
loc.lat_without_check()});
40+
}
41+
42+
osmium::geom::Coordinates
43+
target_to_tile(osmium::geom::Coordinates coords) const override
44+
{
45+
return transform(m_transformation_tile.get(), coords);
46+
}
47+
48+
int target_srs() const noexcept override { return m_target_srs; }
49+
50+
char const *target_desc() const noexcept override { return ""; }
51+
52+
private:
53+
struct pj_context_deleter_t
54+
{
55+
void operator()(PJ_CONTEXT *ctx) const noexcept
56+
{
57+
proj_context_destroy(ctx);
58+
}
59+
};
60+
61+
struct pj_deleter_t
62+
{
63+
void operator()(PJ *p) const noexcept { proj_destroy(p); }
64+
};
65+
66+
char const *errormsg() const noexcept
67+
{
68+
return proj_errno_string(proj_context_errno(m_context.get()));
69+
}
70+
71+
std::unique_ptr<PJ, pj_deleter_t> create_transformation(int from,
72+
int to) const
73+
{
74+
std::string const source = "epsg:{}"_format(from);
75+
std::string const target = "epsg:{}"_format(to);
76+
77+
std::unique_ptr<PJ, pj_deleter_t> trans{proj_create_crs_to_crs(
78+
m_context.get(), source.c_str(), target.c_str(), nullptr)};
79+
80+
if (!trans) {
81+
throw std::runtime_error{
82+
"Invalid projection from {} to {}: {}"_format(from, to,
83+
errormsg())};
84+
}
85+
return trans;
86+
}
87+
88+
osmium::geom::Coordinates transform(PJ *transformation,
89+
osmium::geom::Coordinates coords) const
90+
noexcept
91+
{
92+
PJ_COORD c_in;
93+
c_in.lpzt.z = 0.0;
94+
c_in.lpzt.t = HUGE_VAL;
95+
c_in.lpzt.lam = osmium::geom::deg_to_rad(coords.x);
96+
c_in.lpzt.phi = osmium::geom::deg_to_rad(coords.y);
97+
98+
auto const c_out = proj_trans(transformation, PJ_FWD, c_in);
99+
100+
return osmium::geom::Coordinates{c_out.xy.x, c_out.xy.y};
101+
}
102+
103+
int m_target_srs;
104+
std::unique_ptr<PJ_CONTEXT, pj_context_deleter_t> m_context;
105+
std::unique_ptr<PJ, pj_deleter_t> m_transformation;
106+
107+
/**
108+
* The projection used for tiles. Currently this is fixed to be Spherical
109+
* Mercator. You will usually have tiles in the same projection as used
110+
* for PostGIS, but it is theoretically possible to have your PostGIS data
111+
* in, say, lat/lon but still create tiles in Spherical Mercator.
112+
*/
113+
std::unique_ptr<PJ, pj_deleter_t> m_transformation_tile;
114+
};
115+
116+
} // anonymous namespace
117+
118+
std::shared_ptr<reprojection> reprojection::make_generic_projection(int srs)
119+
{
120+
return std::make_shared<generic_reprojection_t>(srs);
121+
}
122+
123+
std::string get_proj_version()
124+
{
125+
return "[API 6] {}"_format(proj_info().version);
126+
}
127+

0 commit comments

Comments
 (0)