Geo CRS handling - #5
Conversation
|
Thank you for this! I know this is still a draft, but we should discuss whether we really want to store the CRS within the Point class. I personally do not think that this is a good idea. I know that this is only an additional 8-bit integer atm, but there are more than 256 projections, and even with only 8 bit a lot of our existing tools (for example petrimaps) which work on 16 bit or 32 bit geometries internally would suddenly need 12,5% (32bit) or 25% (16 bit) more memory. It would also allow strange things like lines and polygons which consist of points of different CRS (in the case of line and polygons, storing the same projection Maybe we could first clarify where we would actually need this? I think a sane approach would be to have the CRS as an optional parsing result (you already implemented this), to enable automatic conversion from input projections other than WGS84. There should of course also be a single internal reference projection (we currently use Web Mercator) to which the point is reprojected on parsng, not a re-projection of points on-the-fly on every access. |
Thanks for the review. I totally get your point. I did not know of the impact on the existing tools. I will remove the CRS type from the points.
I dont know the whole scope yet. The main idea would be to allow queries with CRS IRIs in front of geometries. Later ad-freiburg/qlever#2935
I also think that this sounds like a good idea. Do I understand it correctly that during parsing all geometries should be transformed to WebMercator? I previously thought CRS84 (LngLat) was used. |
Nice, thank you :)
It's a bit misleading to say what is used internally because it may mean different things, so my remark was a bit confusing, sorry. |
|
By the way: storing the original projection explicitly would be necessary for the folded points in QLever, I think. Otherwise we would loose the ability to output the original input WKT of a point. But this is something that should be done in QLever, not here. @ullingerc can give you better guidance on that :) |
|
@patrickbr Thanks for your comments. This is part of Yannik's bachelor thesis we talked about. I'll discuss details in a meeting with Yannik tomorrow and get back to you as soon as this PR is ready from my point of view. And I totally aggree that (a) we need to know the original CRS for some of the use cases, (b) the CRS should not be inside the Point class. |
ullingerc
left a comment
There was a problem hiding this comment.
Thanks for these changes.
A first pass on everything.
| PLACEHOLDER1 = 4, | ||
| PLACEHOLDER2 = 5, | ||
| PLACEHOLDER3 = 6, | ||
| PLACEHOLDER4 = 7 |
There was a problem hiding this comment.
What are these "placeholder"s for?
There was a problem hiding this comment.
I just left them as placeholder for later adding other systems/IRIs. Currently they have no use.
There was a problem hiding this comment.
Remove these placeholders please. They are not needed and the enum can be extended anytime in the future.
| if constexpr(InvocableWithExactReturnType<F, Point<T>, const Point<double>&>) { | ||
| // If the 'projFunc' does not take CRS types into account, transform to default 'CRS84'. | ||
| line.push_back(projFunc(convertToCRS84(util::geo::DPoint(x, y), sourceCRS))); | ||
| } else { | ||
| static_assert(InvocableWithExactReturnType<F, Point<T>, const Point<double>&, CRSType>); | ||
| line.push_back(projFunc(util::geo::DPoint(x, y), sourceCRS)); | ||
| } |
There was a problem hiding this comment.
This code is redundant and we should implemented only once. The important part is that this is properly templated s.t. inlining still works.
Something like
template <typename F, typename T>
decltype(auto) applyProj(F&& projFunc, T x, T y, const CRSType& sourceCRS) {
if constexpr (InvocableWithExactReturnType<F, Point<T>, const Point<double>&>) {
// projFunc doesn't take CRS into account, transform to default CRS84 first.
return projFunc(convertToCRS84(util::geo::DPoint(x, y), sourceCRS));
} else {
static_assert(InvocableWithExactReturnType<F, Point<T>, const Point<double>&, CRSType>);
return projFunc(util::geo::DPoint(x, y), sourceCRS);
}
}
// and then:
line.push_back(applyProj(projFunc, x, y, sourceCRS));Also you may explore adding [[always_inline]] to applyProj and the projection functions themselves.
There was a problem hiding this comment.
Heads up: please read @patrickbr's comments below before implementing. This should be done differently...
| project(util_standalone_test CXX) | ||
| enable_testing() | ||
| # ----- | ||
|
|
There was a problem hiding this comment.
This should not be part of the finalized PR. This was needed for me to enable testing.
| if constexpr(InvocableWithExactReturnType<F, Point<T>, const Point<double>&>) { | ||
| // If the 'projFunc' does not take CRS types into account, transform to default 'CRS84'. | ||
| line.push_back(projFunc(convertToCRS84(util::geo::DPoint(x, y), sourceCRS))); | ||
| } else { | ||
| static_assert(InvocableWithExactReturnType<F, Point<T>, const Point<double>&, CRSType>); | ||
| line.push_back(projFunc(util::geo::DPoint(x, y), sourceCRS)); | ||
| } |
There was a problem hiding this comment.
Note that there are many projects using this lib which are still on C++14 (or even C++11. So I would prefer it if we avoided std::is_invocable et al.
Could you give a motivation for supporting two types of projFunc? (one which accepts the source CRS, and one which doesnt?) I think it would be perfectly acceptable if projFunc is required to always accept a source CRS :)
There was a problem hiding this comment.
I was aware of c++17, but not of 14 or 11.
The only reason is not having to change petrimaps and possibly further callers. But I guess if we need c++11, changing petrimaps is our best guess before any crazy backports.
…d-freiburg#6's rewrite PR ad-freiburg#5 (CRS/projection support: CRSType, getCRSType, *FromWKTProj with CRS-aware overloads, projectToCRS/CRS84/WGS84/WebMerc, lngLat<->latLng swap) predates ad-freiburg#6's large distance-computation rewrite, which touched the same WKT-parsing machinery and split the monolithic GeoTest.cpp into per-feature files. Master's (ad-freiburg#6) conventions were kept wherever the two overlapped: - CMakeLists.txt: kept ad-freiburg#6's standalone-build setup over ad-freiburg#5's temporary local scaffold. - geo/Geo.{h,tpp}: kept ad-freiburg#6's F&& forwarding-reference convention for the std::string WKT-wrapper overloads; also fixed three latent naming bugs where ad-freiburg#6's own .tpp definitions had dropped the "Proj" suffix (multiLineFromWKT/multiPolygonFromWKT/collectionFromWKT instead of ...WKTProj), leaving ad-freiburg#5's correctly-named declarations undefined. - Replaced ad-freiburg#5's `if constexpr`/`std::is_invocable_r_v`-based dispatch (used to detect whether a projFunc takes just a Point or also a CRSType) with a C++11-compatible SFINAE overload pair, since ad-freiburg#6's CMakeLists.txt targets C++11 for downstream compatibility. - Fixed a pre-existing (and now newly-exposed) declaration/definition mismatch for the std::string overload of pointFromWKTProj, latent in ad-freiburg#6 itself, now surfaced as an ambiguous-call by aligning it to the same F&& convention used everywhere else. - tests/GeoTest.cpp: restored ad-freiburg#6's minimal split-runner file; moved ad-freiburg#5's new coverage (IRI/CRS-type detection, CRS conversions, and CRS-aware WKT parsing for every geometry type) into a new tests/GeoTestCRS.cpp, following ad-freiburg#6's one-file-per-feature split. Full standalone build and test suite verified passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Point, CRSType) Upstream (9ffed53) drops the dual-signature projFunc entirely: every WKT-parsing entry point now always passes the detected CRSType down to projFunc, and the CRS84-defaulting convenience wrappers (pointFromWKT, lineFromWKT, etc.) call projectToCRS84 explicitly themselves instead of relying on a dispatch shim. This also adds the missing CRSType-aware overloads for multiPointFromWKTProj/collectionFromWKTProj, matching the other geometry parsers. This makes the C++11 SFINAE dispatch helper (callProjFunc) added in the previous merge commit dead code, since there is no longer a single-argument projFunc signature to detect - removed it along with its two call sites in favor of upstream's direct calls. Full standalone build and test suite verified passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Crs merge pr5
ullingerc
left a comment
There was a problem hiding this comment.
Another round of reviews. But this looks quite nice already.
| PLACEHOLDER1 = 4, | ||
| PLACEHOLDER2 = 5, | ||
| PLACEHOLDER3 = 6, | ||
| PLACEHOLDER4 = 7 |
There was a problem hiding this comment.
Remove these placeholders please. They are not needed and the enum can be extended anytime in the future.
| template <typename T> | ||
| Point<T> lngLatToLatLng(Point<T> lngLat) { | ||
| return swapCoords<T>(lngLat.getX(), lngLat.getY()); | ||
| } | ||
|
|
||
| // _____________________________________________________________________________ | ||
| template <typename T> | ||
| Point<T> latLngToLngLat(Point<T> latLng) { | ||
| return swapCoords<T>(latLng.getX(), latLng.getY()); | ||
| } | ||
|
|
There was a problem hiding this comment.
These are exactly identical. I understand why they are there, but we should think about it if we really want them or use swapCoords directly at the call sites.
There was a problem hiding this comment.
I think there are arguments for keeping the functions and also for only using swapCoords. I would say that for some functions it is easier to follow them when having two seperate functions and not only swapping. Therefore one can always see the input CRS and output CRS. Especially when combined with for example ToWebMerc it is pretty important to know that you are in the correct CRS.
I would keep them for now, but I'm also open for just using swapCoords.
Sorry for that... on the positive side, the tests compile much faster now :) (Everything else should be irrelevant for this PR). |
No problem. I think it all works now :) |
| case WEB_MERCATOR: | ||
| return projectToWebMerc(p, baseCRS); | ||
| default: | ||
| throw std::runtime_error("Projection to unsupported CRS type."); |
There was a problem hiding this comment.
before we get to merging this, I would like to hear @patrickbr 's final word on this. Is throwing fine for all your call sites or do we need graceful error handling?
Transformations between different CRS (=coordinate reference system).