Summary
Since c815b55 ("fixed bug in crs mismatch"), GeoDataFrame::receiveThread's CRS_REC handler resolves a CRS mismatch by silently overwriting with the last-received CRS (last-one-wins), rather than raising. The commit comment explains the intent — the constructor default is later replaced by the subsetting thread's mission+datum CRS — and that flow is fine today because all senders are mission code.
Why it's worth hardening
The consequence is that a genuine frame mislabel (e.g. a future sender attaching ITRF2014 metadata to ITRF2020 data, or a user-supplied CRS colliding with mission logic) can never fail loudly: the dataframe simply reports whichever CRS arrived last, and any downstream raster sampling (which trusts robj->getCRS() as the transform source, GdalRaster.cpp createTransform) silently uses it. Datum-level errors this could mask are dm-to-m scale (ITRF↔NAD83 ~1.5–2.3 m horizontal in the western US).
Suggestion
Keep the expected-replacement path (default → mission CRS) silent, but log a warning (or raise, behind a strict flag) when a non-default CRS is replaced by a different non-default CRS. That preserves current behavior for the legitimate flow while making a real mislabel observable.
Happy to provide more context — this came out of a broader verification of the ICESat-2 → 3DEP sampling datum chain (which we found to be exact; see also the coordinate-epoch issue filed separately).
Summary
Since c815b55 ("fixed bug in crs mismatch"),
GeoDataFrame::receiveThread's CRS_REC handler resolves a CRS mismatch by silently overwriting with the last-received CRS (last-one-wins), rather than raising. The commit comment explains the intent — the constructor default is later replaced by the subsetting thread's mission+datum CRS — and that flow is fine today because all senders are mission code.Why it's worth hardening
The consequence is that a genuine frame mislabel (e.g. a future sender attaching ITRF2014 metadata to ITRF2020 data, or a user-supplied CRS colliding with mission logic) can never fail loudly: the dataframe simply reports whichever CRS arrived last, and any downstream raster sampling (which trusts
robj->getCRS()as the transform source,GdalRaster.cppcreateTransform) silently uses it. Datum-level errors this could mask are dm-to-m scale (ITRF↔NAD83 ~1.5–2.3 m horizontal in the western US).Suggestion
Keep the expected-replacement path (default → mission CRS) silent, but log a warning (or raise, behind a strict flag) when a non-default CRS is replaced by a different non-default CRS. That preserves current behavior for the legitimate flow while making a real mislabel observable.
Happy to provide more context — this came out of a broader verification of the ICESat-2 → 3DEP sampling datum chain (which we found to be exact; see also the coordinate-epoch issue filed separately).