diff options
Diffstat (limited to 'src/transformations/defmodel.cpp')
| -rw-r--r-- | src/transformations/defmodel.cpp | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/src/transformations/defmodel.cpp b/src/transformations/defmodel.cpp new file mode 100644 index 00000000..3d0f2a58 --- /dev/null +++ b/src/transformations/defmodel.cpp @@ -0,0 +1,451 @@ +/****************************************************************************** + * Project: PROJ + * Purpose: Functionality related to deformation model + * Author: Even Rouault, <even.rouault at spatialys.com> + * + ****************************************************************************** + * Copyright (c) 2020, Even Rouault, <even.rouault at spatialys.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + *****************************************************************************/ + +#define PJ_LIB__ +#define PROJ_COMPILATION + +#include "defmodel.hpp" +#include "filemanager.hpp" +#include "grids.hpp" +#include "proj_internal.h" + +#include <assert.h> + +#include <map> +#include <memory> +#include <utility> + +PROJ_HEAD(defmodel, "Deformation model"); + +using namespace DeformationModel; + +namespace { + +struct Grid : public GridPrototype { + PJ_CONTEXT *ctx; + const NS_PROJ::GenericShiftGrid *realGrid; + mutable bool checkedHorizontal = false; + mutable bool checkedVertical = false; + mutable int sampleX = 0; + mutable int sampleY = 1; + mutable int sampleZ = 2; + + Grid(PJ_CONTEXT *ctxIn, const NS_PROJ::GenericShiftGrid *realGridIn) + : ctx(ctxIn), realGrid(realGridIn) { + minx = realGridIn->extentAndRes().west; + miny = realGridIn->extentAndRes().south; + resx = realGridIn->extentAndRes().resX; + resy = realGridIn->extentAndRes().resY; + width = realGridIn->width(); + height = realGridIn->height(); + } + + bool checkHorizontal(const std::string &expectedUnit) const { + if (!checkedHorizontal) { + const auto samplesPerPixel = realGrid->samplesPerPixel(); + if (samplesPerPixel < 2) { + pj_log(ctx, PJ_LOG_ERROR, + "defmodel: grid %s has not enough samples", + realGrid->name().c_str()); + return false; + } + bool foundDescX = false; + bool foundDescY = false; + bool foundDesc = false; + for (int i = 0; i < samplesPerPixel; i++) { + const auto desc = realGrid->description(i); + if (desc == "east_offset") { + sampleX = i; + foundDescX = true; + } else if (desc == "north_offset") { + sampleY = i; + foundDescY = true; + } + if (!desc.empty()) { + foundDesc = true; + } + } + if (foundDesc && (!foundDescX || !foundDescY)) { + pj_log(ctx, PJ_LOG_ERROR, + "defmodel: grid %s : Found band description, " + "but not the ones expected", + realGrid->name().c_str()); + return false; + } + const auto unit = realGrid->unit(sampleX); + if (!unit.empty() && unit != expectedUnit) { + pj_log(ctx, PJ_LOG_ERROR, "defmodel: grid %s : Only unit=%s " + "currently handled for this mode", + realGrid->name().c_str(), expectedUnit.c_str()); + return false; + } + checkedHorizontal = true; + } + return true; + } + + bool getLonLatOffset(int ix, int iy, double &lonOffsetRadian, + double &latOffsetRadian) const { + if (!checkHorizontal(STR_DEGREE)) { + return false; + } + float lonOffsetDeg; + float latOffsetDeg; + if (!realGrid->valueAt(ix, iy, sampleX, lonOffsetDeg) || + !realGrid->valueAt(ix, iy, sampleY, latOffsetDeg)) { + return false; + } + lonOffsetRadian = lonOffsetDeg * DEG_TO_RAD; + latOffsetRadian = latOffsetDeg * DEG_TO_RAD; + return true; + } + + bool getZOffset(int ix, int iy, double &zOffset) const { + if (!checkedVertical) { + const auto samplesPerPixel = realGrid->samplesPerPixel(); + if (samplesPerPixel == 1) { + sampleZ = 0; + } else if (samplesPerPixel < 3) { + pj_log(ctx, PJ_LOG_ERROR, + "defmodel: grid %s has not enough samples", + realGrid->name().c_str()); + return false; + } + bool foundDesc = false; + bool foundDescZ = false; + for (int i = 0; i < samplesPerPixel; i++) { + const auto desc = realGrid->description(i); + if (desc == "vertical_offset") { + sampleZ = i; + foundDescZ = true; + } + if (!desc.empty()) { + foundDesc = true; + } + } + if (foundDesc && !foundDescZ) { + pj_log(ctx, PJ_LOG_ERROR, + "defmodel: grid %s : Found band description, " + "but not the ones expected", + realGrid->name().c_str()); + return false; + } + const auto unit = realGrid->unit(sampleZ); + if (!unit.empty() && unit != STR_METRE) { + pj_log(ctx, PJ_LOG_ERROR, + "defmodel: grid %s : Only unit=metre currently " + "handled for this mode", + realGrid->name().c_str()); + return false; + } + checkedVertical = true; + } + float zOffsetFloat = 0.0f; + const bool ret = realGrid->valueAt(ix, iy, sampleZ, zOffsetFloat); + zOffset = zOffsetFloat; + return ret; + } + + bool getEastingNorthingOffset(int ix, int iy, double &eastingOffset, + double &northingOffset) const { + if (!checkHorizontal(STR_METRE)) { + return false; + } + float eastingOffsetFloat = 0.0f; + float northingOffsetFloat = 0.0f; + const bool ret = + realGrid->valueAt(ix, iy, sampleX, eastingOffsetFloat) && + realGrid->valueAt(ix, iy, sampleY, northingOffsetFloat); + eastingOffset = eastingOffsetFloat; + northingOffset = northingOffsetFloat; + return ret; + } + + bool getLonLatZOffset(int ix, int iy, double &lonOffsetRadian, + double &latOffsetRadian, double &zOffset) const { + return getLonLatOffset(ix, iy, lonOffsetRadian, latOffsetRadian) && + getZOffset(ix, iy, zOffset); + } + + bool getEastingNorthingZOffset(int ix, int iy, double &eastingOffset, + double &northingOffset, + double &zOffset) const { + return getEastingNorthingOffset(ix, iy, eastingOffset, + northingOffset) && + getZOffset(ix, iy, zOffset); + } + +#ifdef DEBUG_DEFMODEL + std::string name() const { return realGrid->name(); } +#endif + + private: + Grid(const Grid &) = delete; + Grid &operator=(const Grid &) = delete; +}; + +struct GridSet : public GridSetPrototype<Grid> { + + PJ_CONTEXT *ctx; + std::unique_ptr<NS_PROJ::GenericShiftGridSet> realGridSet; + std::map<const NS_PROJ::GenericShiftGrid *, std::unique_ptr<Grid>> + mapGrids{}; + + GridSet(PJ_CONTEXT *ctxIn, + std::unique_ptr<NS_PROJ::GenericShiftGridSet> &&realGridSetIn) + : ctx(ctxIn), realGridSet(std::move(realGridSetIn)) {} + + const Grid *gridAt(double x, double y) { + const NS_PROJ::GenericShiftGrid *realGrid = realGridSet->gridAt(x, y); + if (!realGrid) { + return nullptr; + } + auto iter = mapGrids.find(realGrid); + if (iter == mapGrids.end()) { + iter = mapGrids + .insert(std::pair<const NS_PROJ::GenericShiftGrid *, + std::unique_ptr<Grid>>( + realGrid, + std::unique_ptr<Grid>(new Grid(ctx, realGrid)))) + .first; + } + return iter->second.get(); + } + + private: + GridSet(const GridSet &) = delete; + GridSet &operator=(const GridSet &) = delete; +}; + +struct EvaluatorIface : public EvaluatorIfacePrototype<Grid, GridSet> { + + PJ_CONTEXT *ctx; + PJ *cart; + + EvaluatorIface(PJ_CONTEXT *ctxIn, PJ *cartIn) : ctx(ctxIn), cart(cartIn) {} + + ~EvaluatorIface() { + if (cart) + cart->destructor(cart, 0); + } + + std::unique_ptr<GridSet> open(const std::string &filename) { + auto realGridSet = NS_PROJ::GenericShiftGridSet::open(ctx, filename); + if (!realGridSet) { + pj_log(ctx, PJ_LOG_ERROR, "defmodel: cannot open %s", + filename.c_str()); + return nullptr; + } + return std::unique_ptr<GridSet>( + new GridSet(ctx, std::move(realGridSet))); + } + + bool isGeographicCRS(const std::string &crsDef) { + PJ *P = proj_create(ctx, crsDef.c_str()); + if (P == nullptr) { + return true; // reasonable default value + } + const auto type = proj_get_type(P); + bool ret = (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || + type == PJ_TYPE_GEOGRAPHIC_3D_CRS); + proj_destroy(P); + return ret; + } + + void geographicToGeocentric(double lam, double phi, double height, double a, + double b, double /*es*/, double &X, double &Y, + double &Z) { + (void)a; + (void)b; + assert(cart->a == a); + assert(cart->b == b); + PJ_LPZ lpz; + lpz.lam = lam; + lpz.phi = phi; + lpz.z = height; + PJ_XYZ xyz = cart->fwd3d(lpz, cart); + X = xyz.x; + Y = xyz.y; + Z = xyz.z; + } + + void geocentricToGeographic(double X, double Y, double Z, double a, + double b, double /*es*/, double &lam, + double &phi, double &height) { + (void)a; + (void)b; + assert(cart->a == a); + assert(cart->b == b); + PJ_XYZ xyz; + xyz.x = X; + xyz.y = Y; + xyz.z = Z; + PJ_LPZ lpz = cart->inv3d(xyz, cart); + lam = lpz.lam; + phi = lpz.phi; + height = lpz.z; + } + +#ifdef DEBUG_DEFMODEL + void log(const std::string &msg) { + pj_log(ctx, PJ_LOG_TRACE, "%s", msg.c_str()); + } +#endif + + private: + EvaluatorIface(const EvaluatorIface &) = delete; + EvaluatorIface &operator=(const EvaluatorIface &) = delete; +}; + +struct defmodelData { + std::unique_ptr<Evaluator<Grid, GridSet, EvaluatorIface>> evaluator{}; + EvaluatorIface evaluatorIface; + + explicit defmodelData(PJ_CONTEXT *ctx, PJ *cart) + : evaluatorIface(ctx, cart) {} + + defmodelData(const defmodelData &) = delete; + defmodelData &operator=(const defmodelData &) = delete; +}; + +} // namespace + +static PJ *destructor(PJ *P, int errlev) { + if (nullptr == P) + return nullptr; + + auto Q = static_cast<struct defmodelData *>(P->opaque); + delete Q; + P->opaque = nullptr; + + return pj_default_destructor(P, errlev); +} + +static PJ_COORD forward_4d(PJ_COORD in, PJ *P) { + auto *Q = (struct defmodelData *)P->opaque; + + PJ_COORD out; + out.xyzt.t = in.xyzt.t; + + if (!Q->evaluator->forward(Q->evaluatorIface, in.xyzt.x, in.xyzt.y, + in.xyzt.z, in.xyzt.t, out.xyzt.x, out.xyzt.y, + out.xyzt.z)) { + return proj_coord_error(); + } + + return out; +} + +static PJ_COORD reverse_4d(PJ_COORD in, PJ *P) { + auto *Q = (struct defmodelData *)P->opaque; + + PJ_COORD out; + out.xyzt.t = in.xyzt.t; + + if (!Q->evaluator->inverse(Q->evaluatorIface, in.xyzt.x, in.xyzt.y, + in.xyzt.z, in.xyzt.t, out.xyzt.x, out.xyzt.y, + out.xyzt.z)) { + return proj_coord_error(); + } + + return out; +} + +// Function called by proj_assign_context() when a new context is assigned to +// an existing PJ object. Mostly to deal with objects being passed between +// threads. +static void reassign_context(PJ *P, PJ_CONTEXT *ctx) { + auto *Q = (struct defmodelData *)P->opaque; + if (Q->evaluatorIface.ctx != ctx) { + Q->evaluator->clearGridCache(); + Q->evaluatorIface.ctx = ctx; + } +} + +PJ *TRANSFORMATION(defmodel, 1) { + // Pass a dummy ellipsoid definition that will be overridden just afterwards + auto cart = proj_create(P->ctx, "+proj=cart +a=1"); + if (cart == nullptr) + return destructor(P, ENOMEM); + + /* inherit ellipsoid definition from P to Q->cart */ + pj_inherit_ellipsoid_def(P, cart); + + auto Q = new defmodelData(P->ctx, cart); + P->opaque = (void *)Q; + P->destructor = destructor; + P->reassign_context = reassign_context; + + const char *model = pj_param(P->ctx, P->params, "smodel").s; + if (!model) { + proj_log_error(P, "defmodel: +model= should be specified."); + return destructor(P, PJD_ERR_NO_ARGS); + } + + auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, model); + if (nullptr == file) { + proj_log_error(P, "defmodel: Cannot open %s", model); + return destructor(P, PJD_ERR_INVALID_ARG); + } + file->seek(0, SEEK_END); + unsigned long long size = file->tell(); + // Arbitrary threshold to avoid ingesting an arbitrarily large JSON file, + // that could be a denial of service risk. 10 MB should be sufficiently + // large for any valid use ! + if (size > 10 * 1024 * 1024) { + proj_log_error(P, "defmodel: File %s too large", model); + return destructor(P, PJD_ERR_INVALID_ARG); + } + file->seek(0); + std::string jsonStr; + jsonStr.resize(static_cast<size_t>(size)); + if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) { + proj_log_error(P, "defmodel: Cannot read %s", model); + return destructor(P, PJD_ERR_INVALID_ARG); + } + + try { + Q->evaluator.reset(new Evaluator<Grid, GridSet, EvaluatorIface>( + MasterFile::parse(jsonStr), Q->evaluatorIface, P->a, P->b)); + } catch (const std::exception &e) { + proj_log_error(P, "defmodel: invalid model: %s", e.what()); + return destructor(P, PJD_ERR_INVALID_ARG); + } + + P->fwd4d = forward_4d; + P->inv4d = reverse_4d; + + if (Q->evaluator->isGeographicCRS()) { + P->left = PJ_IO_UNITS_RADIANS; + P->right = PJ_IO_UNITS_RADIANS; + } else { + P->left = PJ_IO_UNITS_PROJECTED; + P->right = PJ_IO_UNITS_PROJECTED; + } + + return P; +} |
