diff --git a/gdal/GDALmake.opt.in b/gdal/GDALmake.opt.in index 70e155e65e2d..fd8e8f374da1 100644 --- a/gdal/GDALmake.opt.in +++ b/gdal/GDALmake.opt.in @@ -446,6 +446,7 @@ LIBLZMA_SETTING = @LIBLZMA_SETTING@ WEBP_SETTING = @WEBP_SETTING@ ZSTD_SETTING = @ZSTD_SETTING@ TILEDB_SETTING = @TILEDB_SETTING@ +RDB_SETTING = @RDB_SETTING@ # # DDS via Crunch Support. diff --git a/gdal/configure b/gdal/configure index d10698646992..4fea1e213c43 100755 --- a/gdal/configure +++ b/gdal/configure @@ -636,6 +636,7 @@ HAVE_OPENSSL_CRYPTO USE_ONLY_CRYPTODLL_ALG HAVE_CRYPTOPP HAVE_ARMADILLO +RDB_SETTING RASDAMAN_LIB RASDAMAN_INC RASDAMAN_ENABLED @@ -1216,6 +1217,7 @@ with_mdb with_jvm_lib with_jvm_lib_add_rpath with_rasdaman +with_rdb with_armadillo with_cryptopp with_crypto @@ -2223,6 +2225,7 @@ Optional Packages: --with-jvm-lib=ARG ARG is dlopen or points to Java libjvm path --with-jvm-lib-add-rpath Add the libjvm path to the RPATH (no by default) --with-rasdaman=DIR Include rasdaman support (DIR is rasdaman's install dir). + --with-rdb=ARG Include RDB support (ARG=no, yes or RDB SDK root path) --with-armadillo=ARG Include Armadillo support for faster TPS transform computation (ARG=yes/no/path to armadillo install root) [default=no] --with-cryptopp=ARG Include cryptopp support (ARG=yes, no or path) --with-crypto=ARG Include crypto (from openssl) support (ARG=yes, no or path) @@ -41520,6 +41523,129 @@ RASDAMAN_LIB=$RASDAMAN_LIB +# Check whether --with-rdb was given. +if test "${with_rdb+set}" = set; then : + withval=$with_rdb; +fi + + +RDB_SETTING=no + +if test "$with_rdb" = "yes" -o "$with_rdb" = "" ; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rdb_library_name in -lrdb" >&5 +$as_echo_n "checking for rdb_library_name in -lrdb... " >&6; } +if ${ac_cv_lib_rdb_rdb_library_name+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrdb $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char rdb_library_name (); +int +main () +{ +return rdb_library_name (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_rdb_rdb_library_name=yes +else + ac_cv_lib_rdb_rdb_library_name=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rdb_rdb_library_name" >&5 +$as_echo "$ac_cv_lib_rdb_rdb_library_name" >&6; } +if test "x$ac_cv_lib_rdb_rdb_library_name" = xyes; then : + RDB_SETTING=yes +else + RDB_SETTING=no +fi + + + if test "$RDB_SETTING" = "yes" ; then + LIBS="-lrdb $LIBS" + else + echo "librdb not found - RDB support disabled" + fi + +elif test "$with_rdb" != "no" -a "$with_rdb" != ""; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rdb_library_name in -lrdb" >&5 +$as_echo_n "checking for rdb_library_name in -lrdb... " >&6; } +if ${ac_cv_lib_rdb_rdb_library_name+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrdb -L$with_rdb/library -lrdb $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char rdb_library_name (); +int +main () +{ +return rdb_library_name (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_rdb_rdb_library_name=yes +else + ac_cv_lib_rdb_rdb_library_name=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rdb_rdb_library_name" >&5 +$as_echo "$ac_cv_lib_rdb_rdb_library_name" >&6; } +if test "x$ac_cv_lib_rdb_rdb_library_name" = xyes; then : + RDB_SETTING=yes +else + RDB_SETTING=no +fi + + + if test "$RDB_SETTING" = "yes" ; then + LIBS="-L$with_rdb/library -lrdb $LIBS" + EXTRA_INCLUDES="-I$with_rdb/interface/cpp/ -I$with_rdb/interface/c/ $EXTRA_INCLUDES" + else + echo "librdb not found - RDB support disabled" + fi + +fi + +RDB_SETTING=$RDB_SETTING + + +if test "$RDB_SETTING" != "no" ; then + OPT_GDAL_FORMATS="rdb $OPT_GDAL_FORMATS" +fi + + + + # Check whether --with-armadillo was given. if test "${with_armadillo+set}" = set; then : withval=$with_armadillo; @@ -44388,6 +44514,9 @@ echo " SDE support: ${SDE_ENABLED}" echo " Rasdaman support: ${RASDAMAN_ENABLED}" +echo " RDB support: ${RDB_SETTING}" + + echo " DODS support: ${HAVE_DODS}" diff --git a/gdal/configure.ac b/gdal/configure.ac index 60aafc0fe64d..81ea8f7f69fe 100644 --- a/gdal/configure.ac +++ b/gdal/configure.ac @@ -5653,6 +5653,44 @@ AC_SUBST(RASDAMAN_ENABLED,$RASDAMAN_ENABLED) AC_SUBST(RASDAMAN_INC, $RASDAMAN_INC) AC_SUBST(RASDAMAN_LIB, $RASDAMAN_LIB) +dnl --------------------------------------------------------------------------- +dnl RDB support +dnl --------------------------------------------------------------------------- + +AC_ARG_WITH(rdb,[ --with-rdb[=ARG] Include RDB support (ARG=no, yes or RDB SDK root path)],,) + +RDB_SETTING=no + +if test "$with_rdb" = "yes" -o "$with_rdb" = "" ; then + + AC_CHECK_LIB(rdb,rdb_library_name,RDB_SETTING=yes,RDB_SETTING=no,) + + if test "$RDB_SETTING" = "yes" ; then + LIBS="-lrdb $LIBS" + else + echo "librdb not found - RDB support disabled" + fi + +elif test "$with_rdb" != "no" -a "$with_rdb" != ""; then + + AC_CHECK_LIB(rdb,rdb_library_name,RDB_SETTING=yes,RDB_SETTING=no,-L$with_rdb/library -lrdb) + + if test "$RDB_SETTING" = "yes" ; then + LIBS="-L$with_rdb/library -lrdb $LIBS" + EXTRA_INCLUDES="-I$with_rdb/interface/cpp/ -I$with_rdb/interface/c/ $EXTRA_INCLUDES" + else + echo "librdb not found - RDB support disabled" + fi + +fi + +AC_SUBST(RDB_SETTING,$RDB_SETTING) + +if test "$RDB_SETTING" != "no" ; then + OPT_GDAL_FORMATS="rdb $OPT_GDAL_FORMATS" +fi + + dnl --------------------------------------------------------------------------- dnl Armadillo support dnl --------------------------------------------------------------------------- @@ -6009,6 +6047,7 @@ LOC_MSG([ OCI support: ${HAVE_OCI}]) LOC_MSG([ GEORASTER support: ${HAVE_GEORASTER}]) LOC_MSG([ SDE support: ${SDE_ENABLED}]) LOC_MSG([ Rasdaman support: ${RASDAMAN_ENABLED}]) +LOC_MSG([ RDB support: ${RDB_SETTING}]) LOC_MSG([ DODS support: ${HAVE_DODS}]) LOC_MSG([ SQLite support: ${HAVE_SQLITE}]) LOC_MSG([ PCRE support: ${HAVE_PCRE}]) diff --git a/gdal/doc/source/drivers/raster/index.rst b/gdal/doc/source/drivers/raster/index.rst index a690f4f9d3a5..7e110d50ef59 100644 --- a/gdal/doc/source/drivers/raster/index.rst +++ b/gdal/doc/source/drivers/raster/index.rst @@ -137,6 +137,7 @@ Raster drivers rasterlite2 r rda + rdb rik rmf roi_pac diff --git a/gdal/doc/source/drivers/raster/rdb.rst b/gdal/doc/source/drivers/raster/rdb.rst new file mode 100644 index 000000000000..5947b9a8dfd3 --- /dev/null +++ b/gdal/doc/source/drivers/raster/rdb.rst @@ -0,0 +1,78 @@ +.. _raster.rdb: + +================================================================================ +RDB - *RIEGL* Database +================================================================================ + +.. shortname:: RDB + +.. versionadded:: 3.1 + +GDAL can read \*.mpx files in the RDB format, the in-house format used by `RIEGL Laser Measurement Systems GmbH `__ through the RDB library. + +The driver relies on the RDB library, which can be downloaded `here `__. The minimum version required of the rdblib is 2.2.0. + +Driver capabilities +------------------- + +.. supports_georeferencing:: + +Provided Bands +------------------- + +All attributes stored in the RDB, but the coordinates, are provided in bands. Vector attributes are split up into multiple bands. +The attributes are currently mapped as follows: + ++----------------------------+-------------------------+ +| RDB attribute | GDAL Band | ++============================+=========================+ +| riegl.surface_normal[0], | Band 1 | +| | | +| riegl.surface_normal[1], | Band 2 | +| | | +| riegl.surface_normal[2] | Band 3 | ++----------------------------+-------------------------+ +| riegl.reflectance | Band 4 | ++----------------------------+-------------------------+ +| riegl.amplitude | Band 5 | ++----------------------------+-------------------------+ +| riegl.deviation | Band 6 | ++----------------------------+-------------------------+ +| riegl.point_count | Band 7 | ++----------------------------+-------------------------+ +| riegl.pca_thickness | Band 8 | ++----------------------------+-------------------------+ +| riegl.std_dev | Band 9 | ++----------------------------+-------------------------+ +| riegl.height_center | Band 10 | ++----------------------------+-------------------------+ +| riegl.height_mean | Band 11 | ++----------------------------+-------------------------+ +| riegl.height_min | Band 12 | ++----------------------------+-------------------------+ +| riegl.height_max | Band 13 | ++----------------------------+-------------------------+ +| pixel_linear_sums[0] | Band 14 | +| | | +| pixel_linear_sums[1] | Band 15 | +| | | +| pixel_linear_sums[2] | Band 16 | ++----------------------------+-------------------------+ +| pixel_square_sums[0] | Band 17 | +| | | +| pixel_square_sums[1] | Band 18 | +| | | +| pixel_square_sums[2] | Band 19 | +| | | +| pixel_square_sums[3] | Band 20 | +| | | +| pixel_square_sums[4] | Band 21 | +| | | +| pixel_square_sums[5] | Band 22 | ++----------------------------+-------------------------+ +| riegl.voxel_count | Band 23 | ++----------------------------+-------------------------+ +| riegl.id | Band 24 | ++----------------------------+-------------------------+ +| riegl.point_count_grid_cell| Band 25 | ++----------------------------+-------------------------+ diff --git a/gdal/frmts/gdalallregister.cpp b/gdal/frmts/gdalallregister.cpp index 63d16c9974b0..37fb9a2a82ed 100644 --- a/gdal/frmts/gdalallregister.cpp +++ b/gdal/frmts/gdalallregister.cpp @@ -404,6 +404,9 @@ void CPL_STDCALL GDALAllRegister() GDALRegister_TileDB(); #endif +#ifdef FRMT_rdb + GDALRegister_RDB(); +#endif /* -------------------------------------------------------------------- */ /* Put raw formats at the end of the list. These drivers support */ /* various ASCII-header labeled formats, so the driver could be */ diff --git a/gdal/frmts/makefile.vc b/gdal/frmts/makefile.vc index 9aeb4029a7c9..91863660d97e 100644 --- a/gdal/frmts/makefile.vc +++ b/gdal/frmts/makefile.vc @@ -207,6 +207,9 @@ EXTRAFLAGS = $(EXTRAFLAGS) -DFRMT_jpegls EXTRAFLAGS = $(EXTRAFLAGS) -DFRMT_tiledb !ENDIF +!IFDEF RDB_ENABLED +EXTRAFLAGS = $(EXTRAFLAGS) -DFRMT_rdb +!ENDIF default: o\gdalallregister.obj subdirs diff --git a/gdal/frmts/rdb/GNUmakefile b/gdal/frmts/rdb/GNUmakefile new file mode 100644 index 000000000000..5f00466e587c --- /dev/null +++ b/gdal/frmts/rdb/GNUmakefile @@ -0,0 +1,13 @@ + +include ../../GDALmake.opt + +OBJ = rdbdataset.o + +CPPFLAGS := -I../../ogr/ogrsf_frmts/geojson $(JSON_INCLUDE) $(CPPFLAGS) + +default: $(OBJ:.o=.$(OBJ_EXT)) + +clean: + rm -f *.o $(O_OBJ) + +install-obj: $(O_OBJ:.o=.$(OBJ_EXT)) diff --git a/gdal/frmts/rdb/makefile.vc b/gdal/frmts/rdb/makefile.vc new file mode 100644 index 000000000000..11d288c6e87b --- /dev/null +++ b/gdal/frmts/rdb/makefile.vc @@ -0,0 +1,15 @@ + +OBJ = ./rdbdataset.obj + +EXTRAFLAGS = -I..\..\ogr\ogrsf_frmts\geojson -I../../ogr/ogrsf_frmts/geojson/libjson $(RDB_CFLAGS) + +GDAL_ROOT = ..\.. + +!INCLUDE $(GDAL_ROOT)\nmake.opt + +default: $(OBJ) + xcopy /D /Y *.obj ..\o + +clean: + -del *.obj + diff --git a/gdal/frmts/rdb/rdbdataset.cpp b/gdal/frmts/rdb/rdbdataset.cpp new file mode 100644 index 000000000000..5ed15c79a674 --- /dev/null +++ b/gdal/frmts/rdb/rdbdataset.cpp @@ -0,0 +1,788 @@ +/****************************************************************************** + * $Id$ + * + * Project: RIEGL RDB 2 driver + * Purpose: Add support for reading *.mpx RDB 2 files. + * Author: RIEGL Laser Measurement Systems GmbH (support@riegl.com) + * + ****************************************************************************** + * Copyright (c) 2019, RIEGL Laser Measurement Systems GmbH (support@riegl.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. + ****************************************************************************/ + +#include "ogrgeojsonreader.h" + +#include "rdbdataset.hpp" + +#include +#include + +namespace rdb +{ +void RDBOverview::addRDBNode(RDBNode &oRDBNode, double dfXMin, double dfYMin, + double dfXMax, double dfYMax) +{ + adfMinimum[0] = std::min(adfMinimum[0], dfXMin); + adfMaximum[0] = std::max(adfMaximum[0], dfXMax); + + adfMinimum[1] = std::min(adfMinimum[1], dfYMin); + adfMaximum[1] = std::max(adfMaximum[1], dfYMax); + + aoRDBNodes.push_back(oRDBNode); +} +void RDBOverview::setTileSize(double dfTileSizeIn) +{ + dfTileSize = dfTileSizeIn; + dfPixelSize = dfTileSize / 256.0; +} + +template struct CPLMallocGuard +{ + T *const pData = nullptr; + explicit CPLMallocGuard(std::size_t count) + : pData(static_cast(CPLMalloc(sizeof(T) * count))) + { + } + ~CPLMallocGuard() { CPLFree(pData); } +}; + +template class RDBRasterBandInternal; + +template class RDBRasterBandInternal : public RDBRasterBand +{ + std::vector>> aoOverviewBands; + std::vector aoVRTRasterBand; + + public: + RDBRasterBandInternal( + RDBDataset *poDSIn, const std::string &osAttributeNameIn, + const riegl::rdb::pointcloud::PointAttribute &oPointAttributeIn, + int nBandIn, GDALDataType eDataTypeIn, int nLevelIn) + : RDBRasterBand(poDSIn, osAttributeNameIn, oPointAttributeIn, nBandIn, + eDataTypeIn, nLevelIn) + { + poDS = poDSIn; + nBand = nBandIn; + + eDataType = eDataTypeIn; + eAccess = poDSIn->eAccess; + + auto &oRDBOverview = poDSIn->aoRDBOverviews[nLevelIn]; + + nRasterXSize = static_cast( + (oRDBOverview.adfMaximum[0] - oRDBOverview.adfMinimum[0]) * 256); + nRasterYSize = static_cast( + (oRDBOverview.adfMaximum[1] - oRDBOverview.adfMinimum[1]) * 256); + + nBlockXSize = 256; + nBlockYSize = 256; + } + + ~RDBRasterBandInternal() {} + RDBRasterBandInternal( + RDBDataset *poDSIn, const std::string &osAttributeNameIn, + const riegl::rdb::pointcloud::PointAttribute &oPointAttributeIn, + int nBandIn, GDALDataType eDataTypeIn, int nLevelIn, int nNumerOfLayers) + : RDBRasterBandInternal(poDSIn, osAttributeNameIn, oPointAttributeIn, + nBandIn, eDataTypeIn, nLevelIn) + { + aoOverviewBands.resize(nNumerOfLayers); + poDSIn->apoVRTDataset.resize(nNumerOfLayers); + + for(int i = nNumerOfLayers - 2; i >= 0; i--) + { + aoOverviewBands[i].reset(new RDBRasterBandInternal( + poDSIn, osAttributeNameIn, oPointAttributeIn, nBandIn, + eDataTypeIn, i)); + RDBOverview &oRDBOverview = poDSIn->aoRDBOverviews[i]; + + int nDatasetXSize = static_cast(std::round( + (poDSIn->dfXMax - poDSIn->dfXMin) / oRDBOverview.dfPixelSize)); + + int nDatasetYSize = static_cast(std::round( + (poDSIn->dfYMax - poDSIn->dfYMin) / oRDBOverview.dfPixelSize)); + + if(!poDSIn->apoVRTDataset[i]) + { + poDSIn->apoVRTDataset[i].reset( + new VRTDataset(nDatasetXSize, nDatasetYSize)); + } + + VRTAddBand(poDSIn->apoVRTDataset[i].get(), eDataType, nullptr); + + VRTSourcedRasterBand *hVRTBand(dynamic_cast( + poDSIn->apoVRTDataset[i]->GetRasterBand(nBandIn))); + + int bSuccess = FALSE; + double dfNoDataValue = GetNoDataValue(&bSuccess); + if(bSuccess == FALSE) + { + dfNoDataValue = VRT_NODATA_UNSET; + } + + hVRTBand->AddSimpleSource( + aoOverviewBands[i].get(), + (poDSIn->dfXMin - + oRDBOverview.adfMinimum[0] * oRDBOverview.dfTileSize) / + (oRDBOverview.dfPixelSize), + (poDSIn->dfYMin - + oRDBOverview.adfMinimum[1] * oRDBOverview.dfTileSize) / + (oRDBOverview.dfPixelSize), + nDatasetXSize, nDatasetYSize, 0, 0, nDatasetXSize, + nDatasetYSize, "average", dfNoDataValue); + + aoVRTRasterBand.push_back(hVRTBand); + } + poDS = poDSIn; + nBand = nBandIn; + + eDataType = eDataTypeIn; + eAccess = poDSIn->eAccess; + nRasterXSize = poDSIn->nRasterXSize; + nRasterYSize = poDSIn->nRasterYSize; + nBlockXSize = 256; + nBlockYSize = 256; + } + + double GetNoDataValue(int *pbSuccess) override + { + double dfInvalidValue = RDBRasterBand::GetNoDataValue(pbSuccess); + + if(pbSuccess != nullptr && *pbSuccess == TRUE) + { + return dfInvalidValue; + } + else + { + if(oPointAttribute.maximumValue < std::numeric_limits::max()) + { + if(pbSuccess != nullptr) + { + *pbSuccess = TRUE; + } + return std::numeric_limits::max(); + } + else if(oPointAttribute.minimumValue > + std::numeric_limits::lowest()) + { + if(pbSuccess != nullptr) + { + *pbSuccess = TRUE; + } + return std::numeric_limits::lowest(); + } + // Another no data value could be any value that is actually not in + // the data but in the range of specified rdb attribute. However, + // this could maybe be a problem when combining multiple files. + + // Using always the maximum or minimum value in such cases might be + // at least consistend across multiple files. + if(pbSuccess != nullptr) + { + *pbSuccess = FALSE; + } + return 0.0; + } + } + virtual CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, + void *pImageIn) override + { + T *pImage = reinterpret_cast(pImageIn); + + constexpr std::size_t nTileSize = 256 * 256; + if(std::isnan(oPointAttribute.invalidValue)) + { + memset(pImageIn, 0, sizeof(T) * nTileSize); + } + else + { + for(std::size_t i = 0; i < nTileSize; i++) + { + pImage[i] = static_cast(oPointAttribute.invalidValue); + } + } + + try + { + RDBDataset *poRDBDs = dynamic_cast(poDS); + if(poRDBDs != nullptr) + { + auto &oRDBOverview = poRDBDs->aoRDBOverviews[nLevel]; + auto &aoRDBNodes = oRDBOverview.aoRDBNodes; + + auto pIt = std::find_if( + aoRDBNodes.begin(), aoRDBNodes.end(), + [&](const RDBNode &poRDBNode) { + return poRDBNode.nXBlockCoordinates == nBlockXOff && + poRDBNode.nYBlockCoordinates == nBlockYOff; + }); + + if(pIt != aoRDBNodes.end()) + { + using type = RDBCoordinatesPlusData; + CPLMallocGuard oData(pIt->nPointCount); + + uint32_t nPointsReturned = 0; + { + // is locking needed? + // std::lock_guard + // oGuard(poRDBDs->oLock); + auto oSelectQuery = + poRDBDs->oPointcloud.select(pIt->iID); + oSelectQuery.bindBuffer( + poRDBDs->oPointcloud.pointAttribute() + .primaryAttributeName(), + oData.pData[0].adfCoordinates[0], + static_cast(sizeof(type))); + + oSelectQuery.bindBuffer( + osAttributeName, oData.pData[0].data, + static_cast(sizeof(type))); + + nPointsReturned = oSelectQuery.next(pIt->nPointCount); + } + + if(nPointsReturned > 0) + { + double dfHalvePixel = oRDBOverview.dfPixelSize * 0.5; + + double dfTileMinX = + (std::floor((poRDBDs->dfXMin + dfHalvePixel) / + oRDBOverview.dfTileSize) + + nBlockXOff) * + oRDBOverview.dfTileSize; + double dfTileMinY = + (std::floor((poRDBDs->dfYMin + dfHalvePixel) / + oRDBOverview.dfTileSize) + + nBlockYOff) * + oRDBOverview.dfTileSize; + + for(uint32_t i = 0; i < nPointsReturned; i++) + { + int dfPixelX = static_cast( + std::floor((oData.pData[i].adfCoordinates[0] + + dfHalvePixel - dfTileMinX) / + oRDBOverview.dfPixelSize)); + + int dfPixelY = static_cast( + std::floor((oData.pData[i].adfCoordinates[1] + + dfHalvePixel - dfTileMinY) / + oRDBOverview.dfPixelSize)); + + pImage[dfPixelY * 256 + dfPixelX] = + oData.pData[i].data; + } + } + } + } + } + catch(const riegl::rdb::Error &oException) + { + CPLError(CE_Failure, CPLE_AppDefined, "RDB error: %s, %s", + oException.what(), oException.details()); + return CE_Failure; + } + catch(const std::exception &oException) + { + CPLError(CE_Failure, CPLE_AppDefined, "Error: %s", + oException.what()); + return CE_Failure; + } + catch(...) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Unknown error in IReadBlock."); + return CE_Failure; + } + return CE_None; + } + + virtual int GetOverviewCount() override + { + RDBDataset *poRDBDs = dynamic_cast(poDS); + if(poRDBDs == nullptr) + { + return 0; + } + return static_cast(aoVRTRasterBand.size()); + } + virtual GDALRasterBand *GetOverview(int i) override + { + return aoVRTRasterBand[i]; + } +}; + +RDBDataset::~RDBDataset() {} + +void RDBDataset::SetBandInternal( + RDBDataset *poDs, const std::string &osAttributeName, + const riegl::rdb::pointcloud::PointAttribute &oPointAttribute, + riegl::rdb::pointcloud::DataType eRDBDataType, int nLevel, + int nNumerOfLayers, int &nBandIndex) +{ + RDBRasterBand *poBand = nullptr; + // map riegl rdb datatype to gdal data type + switch(eRDBDataType) + { + case riegl::rdb::pointcloud::DataType::UINT8: + // Should I do ignore the other type? + case riegl::rdb::pointcloud::DataType::INT8: + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_Byte, + nLevel, nNumerOfLayers); + break; + case riegl::rdb::pointcloud::DataType::UINT16: + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_UInt16, + nLevel, nNumerOfLayers); + break; + case riegl::rdb::pointcloud::DataType::INT16: + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_Int16, + nLevel, nNumerOfLayers); + break; + case riegl::rdb::pointcloud::DataType::UINT32: + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_UInt32, + nLevel, nNumerOfLayers); + break; + case riegl::rdb::pointcloud::DataType::INT32: + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_Int32, + nLevel, nNumerOfLayers); + break; + case riegl::rdb::pointcloud::DataType::FLOAT32: + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_Float32, + nLevel, nNumerOfLayers); + break; + case riegl::rdb::pointcloud::DataType::FLOAT64: + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_Float64, + nLevel, nNumerOfLayers); + break; + default: + // for all reamining use double. e.g. u/int64_t + // an alternate option would be to check the data in the rdb and use the + // minimum required data type. could be a problem when working with + // multiple files. + poBand = new RDBRasterBandInternal( + poDs, osAttributeName, oPointAttribute, nBandIndex, GDT_Float64, + nLevel, nNumerOfLayers); + break; + } + + poDs->SetBand(nBandIndex, poBand); + nBandIndex++; +} + +void RDBDataset::addRDBNode(const riegl::rdb::pointcloud::GraphNode &oNode, + double dfTileSize, std::size_t nLevel) +{ + double adfNodeMinimum[2]; + double adfNodeMaximum[2]; + + oStatQuery.minimum(oNode.id, + oPointcloud.pointAttribute().primaryAttributeName(), + adfNodeMinimum[0]); + oStatQuery.maximum(oNode.id, + oPointcloud.pointAttribute().primaryAttributeName(), + adfNodeMaximum[0]); + + int dfXNodeMin = static_cast( + std::floor((adfNodeMinimum[0] + dfSizeOfPixel * 0.5) / dfTileSize)); + int dfYNodeMin = static_cast( + std::floor((adfNodeMinimum[1] + dfSizeOfPixel * 0.5) / dfTileSize)); + + int dfXNodeMax = static_cast( + std::ceil((adfNodeMaximum[0] + dfSizeOfPixel * 0.5) / dfTileSize)); + int dfYNodeMax = static_cast( + std::ceil((adfNodeMaximum[1] + dfSizeOfPixel * 0.5) / dfTileSize)); + + RDBNode oRDBNode; + + oRDBNode.iID = oNode.id; + oRDBNode.nPointCount = oNode.pointCountNode; + oRDBNode.nXBlockCoordinates = + dfXNodeMin - static_cast(std::floor( + (dfXMin + dfSizeOfPixel * 0.5) / dfTileSize)); + oRDBNode.nYBlockCoordinates = + dfYNodeMin - static_cast(std::floor( + (dfYMin + dfSizeOfPixel * 0.5) / dfTileSize)); + + if(aoRDBOverviews.size() <= nLevel) + { + aoRDBOverviews.resize(nLevel + 1); + } + aoRDBOverviews[nLevel].setTileSize(dfTileSize); + + aoRDBOverviews[nLevel].addRDBNode(oRDBNode, dfXNodeMin, dfYNodeMin, + dfXNodeMax, dfYNodeMax); +} + +double +RDBDataset::traverseRDBNodes(const riegl::rdb::pointcloud::GraphNode &oNode, + std::size_t nLevel) +{ + if(oNode.children.size() == 0) + { + addRDBNode(oNode, dfSizeOfTile, nLevel); + return dfSizeOfTile; + } + else + { + double dfSizeOfChildTile = 0.0; + for(auto &&oChild : oNode.children) + { + dfSizeOfChildTile = traverseRDBNodes(oChild, nLevel + 1); + } + if(dfSizeOfChildTile >= dfSizeOfTile && oNode.pointCountNode > 0) + { + double dfTileSizeCurrentLevel = dfSizeOfChildTile * 2.0; + + addRDBNode(oNode, dfTileSizeCurrentLevel, nLevel); + + return dfTileSizeCurrentLevel; + } + return 0.0; + } +} + +RDBDataset::RDBDataset(GDALOpenInfo *poOpenInfo) : oPointcloud(oContext) +{ + int nBandIndex = 1; + + riegl::rdb::pointcloud::OpenSettings oSettings(oContext); + oPointcloud.open(poOpenInfo->pszFilename, oSettings); + + oStatQuery = oPointcloud.stat(); + + std::string oPrimaryAttribute = + oPointcloud.pointAttribute().primaryAttributeName(); + + oStatQuery.minimum(1, oPrimaryAttribute, adfMinimumDs[0]); + oStatQuery.maximum(1, oPrimaryAttribute, adfMaximumDs[0]); + + dfResolution = + oPointcloud.pointAttribute().get(oPrimaryAttribute).resolution; + + nChunkSize = oPointcloud.management().getChunkSizeLOD(); + + std::string oPixelInfo = oPointcloud.metaData().get("riegl.pixel_info"); + + json_object *poObj = nullptr; + if(!OGRJSonParse(oPixelInfo.c_str(), &poObj, true)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "riegl.pixel_info is invalid JSon: %s", oPixelInfo.c_str()); + } + JsonObjectUniquePtr oObj(poObj); + + json_object *poPixelSize = CPL_json_object_object_get(poObj, "size"); + + if(poPixelSize != nullptr) + { + json_object *poSize0 = json_object_array_get_idx(poPixelSize, 0); + if(poSize0 != nullptr) + { + dfSizeOfPixel = json_object_get_double(poSize0); + } + } + + ReadGeoreferencing(); + + dfSizeOfTile = dfSizeOfPixel * 256; // 2^8 + + double dfHalvePixel = dfSizeOfPixel * 0.5; + dfXMin = std::floor((adfMinimumDs[0] + dfHalvePixel) / dfSizeOfTile) * + dfSizeOfTile; + dfYMin = std::floor((adfMinimumDs[1] + dfHalvePixel) / dfSizeOfTile) * + dfSizeOfTile; + + dfXMax = std::ceil((adfMaximumDs[0] + dfHalvePixel) / dfSizeOfTile) * + dfSizeOfTile; + dfYMax = std::ceil((adfMaximumDs[1] + dfHalvePixel) / dfSizeOfTile) * + dfSizeOfTile; + + nRasterXSize = static_cast((dfXMax - dfXMin) / dfSizeOfPixel); + nRasterYSize = static_cast((dfYMax - dfYMin) / dfSizeOfPixel); + + traverseRDBNodes(oStatQuery.index()); + + aoRDBOverviews.erase( + std::remove_if(aoRDBOverviews.begin(), aoRDBOverviews.end(), + [](const RDBOverview &oRDBOverView) { + return oRDBOverView.aoRDBNodes.empty(); + }), + aoRDBOverviews.end()); + + double dfLevelFactor = std::pow(2, aoRDBOverviews.size()); + nRasterXSize = static_cast(std::ceil(nRasterXSize / dfLevelFactor) * + dfLevelFactor); + nRasterYSize = static_cast(std::ceil(nRasterYSize / dfLevelFactor) * + dfLevelFactor); + + dfXMax = dfXMin + nRasterXSize * dfSizeOfPixel; + dfYMax = dfYMin + nRasterYSize * dfSizeOfPixel; + + riegl::rdb::pointcloud::PointAttributes &oPointAttribute = + oPointcloud.pointAttribute(); + + int nNumberOfLevels = static_cast(aoRDBOverviews.size()); + std::vector aoExistingPointAttributes = oPointAttribute.list(); + + for(auto &&osAttributeName : aoExistingPointAttributes) + { + riegl::rdb::pointcloud::PointAttribute oAttribute = + oPointAttribute.get(osAttributeName); + + if(osAttributeName == oPointAttribute.primaryAttributeName()) + { + continue; + } + if(oAttribute.length == 1) + { + SetBandInternal(this, osAttributeName, oAttribute, + oAttribute.dataType(), nNumberOfLevels - 1, + nNumberOfLevels, nBandIndex); + } + else + { + for(uint32_t i = 0; i < oAttribute.length; i++) + { + std::ostringstream oOss; + oOss << osAttributeName << '[' << i << ']'; + SetBandInternal(this, oOss.str(), oAttribute, + oAttribute.dataType(), nNumberOfLevels - 1, + nNumberOfLevels, nBandIndex); + } + } + } +} + +GDALDataset *RDBDataset::Open(GDALOpenInfo *poOpenInfo) +{ + if(!Identify(poOpenInfo)) + { + return nullptr; + } + + if(poOpenInfo->eAccess == GA_Update) + { + CPLError(CE_Failure, CPLE_NotSupported, + "The RDB driver does not support update access to existing " + "datasets."); + return nullptr; + } + + if(poOpenInfo->fpL == nullptr) + { + return nullptr; + } + try + { + std::unique_ptr poDS(new RDBDataset(poOpenInfo)); + // std::swap(poDS->fp, poOpenInfo->fpL); + // Initialize any PAM information. + poDS->SetDescription(poOpenInfo->pszFilename); + poDS->TryLoadXML(); + + return poDS.release(); + } + catch(const riegl::rdb::Error &oException) + { + CPLError(CE_Failure, CPLE_OpenFailed, "RDB error: %s, %s", + oException.what(), oException.details()); + return nullptr; + } + + catch(const std::exception &oException) + { + CPLError(CE_Failure, CPLE_OpenFailed, "Error: %s", oException.what()); + return nullptr; + } + catch(...) + { + CPLError(CE_Failure, CPLE_OpenFailed, "Unknown error in Open."); + return nullptr; + } + + return nullptr; +} +int RDBDataset::Identify(GDALOpenInfo *poOpenInfo) +{ + const char *psHeader = reinterpret_cast(poOpenInfo->pabyHeader); + if(poOpenInfo->nHeaderBytes < 32) + { + return FALSE; + } + + constexpr int kSizeOfRDBHeaderIdentifier = 32; + constexpr char szRDBHeaderIdentifier[kSizeOfRDBHeaderIdentifier] = + "RIEGL LMS RDB 2 POINTCLOUD FILE"; + + if(strncmp(psHeader, szRDBHeaderIdentifier, kSizeOfRDBHeaderIdentifier)) + { + // A more comprehensive test could be done by the library. + // Should file -> library incompatibilities handled in Identify or + // in the Open function? + return FALSE; + } + return TRUE; +} + +CPLErr RDBDataset::GetGeoTransform(double *padfTransform) +{ + padfTransform[0] = dfXMin; + padfTransform[1] = dfSizeOfPixel; + padfTransform[2] = 0; + + padfTransform[3] = dfYMin; + padfTransform[4] = 0; + padfTransform[5] = dfSizeOfPixel; + + return CE_None; +} + +const OGRSpatialReference *RDBDataset::GetSpatialRef() const +{ + return &oSpatialReference; +} + +void RDBDataset::ReadGeoreferencing() +{ + if(osWktString.empty()) + { + try + { + std::string oPixelInfo = + oPointcloud.metaData().get("riegl.geo_tag"); + + json_object *poObj = nullptr; + if(!OGRJSonParse(oPixelInfo.c_str(), &poObj, true)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "riegl.geo_tag is invalid JSon: %s", + oPixelInfo.c_str()); + } + JsonObjectUniquePtr oObj(poObj); + + json_object *poCrs = CPL_json_object_object_get(poObj, "crs"); + + if(poCrs != nullptr) + { + json_object *poWkt = CPL_json_object_object_get(poCrs, "wkt"); + + if(poWkt != nullptr) + { + osWktString = json_object_get_string(poWkt); + oSpatialReference.importFromWkt(osWktString.c_str()); + oSpatialReference.SetAxisMappingStrategy( + OAMS_TRADITIONAL_GIS_ORDER); + } + } + } + catch(const riegl::rdb::Error &oException) + { + CPLError(CE_Failure, CPLE_AppDefined, "RDB error: %s, %s", + oException.what(), oException.details()); + } + catch(const std::exception &oException) + { + CPLError(CE_Failure, CPLE_AppDefined, "Error: %s", + oException.what()); + } + catch(...) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Unknown error in IReadBlock."); + } + } +} + +RDBRasterBand::RDBRasterBand( + RDBDataset *poDSIn, const std::string &osAttributeNameIn, + const riegl::rdb::pointcloud::PointAttribute &oPointAttributeIn, + int nBandIn, GDALDataType eDataTypeIn, int nLevelIn) + : osAttributeName(osAttributeNameIn), oPointAttribute(oPointAttributeIn), + nLevel(nLevelIn) +{ + + osDescription.Printf("%s (%s)", oPointAttribute.title.c_str(), + osAttributeName.c_str()); + + poDS = poDSIn; + nBand = nBandIn; + + eDataType = eDataTypeIn; + eAccess = poDSIn->eAccess; + nRasterXSize = poDSIn->nRasterXSize; + nRasterYSize = poDSIn->nRasterYSize; + nBlockXSize = 256; + nBlockYSize = 256; +} + +double RDBRasterBand::GetNoDataValue(int *pbSuccess) +{ + if(!std::isnan(oPointAttribute.invalidValue)) + { + if(pbSuccess != nullptr) + { + *pbSuccess = TRUE; + } + return oPointAttribute.invalidValue; + } + else + { + if(pbSuccess != nullptr) + { + *pbSuccess = FALSE; + } + return 0.0; + } +} + +const char *RDBRasterBand::GetDescription() const +{ + return osDescription.c_str(); +} + +} // namespace rdb +void GDALRegister_RDB() +{ + if(!GDAL_CHECK_VERSION("RDB")) + return; + if(GDALGetDriverByName("RDB") != NULL) + return; + GDALDriver *poDriver = new GDALDriver(); + poDriver->SetDescription("RDB"); + poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); + poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "RIEGL RDB Map Pixel (.mpx)"); + // TODO: + // poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, + // ""); + poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "mpx"); + poDriver->pfnOpen = rdb::RDBDataset::Open; + poDriver->pfnIdentify = rdb::RDBDataset::Identify; + GetGDALDriverManager()->RegisterDriver(poDriver); +} + +// includes the cpp wrapper of the rdb library +#include diff --git a/gdal/frmts/rdb/rdbdataset.hpp b/gdal/frmts/rdb/rdbdataset.hpp new file mode 100644 index 000000000000..35d9965e62b7 --- /dev/null +++ b/gdal/frmts/rdb/rdbdataset.hpp @@ -0,0 +1,150 @@ +/****************************************************************************** + * $Id$ + * + * Project: RIEGL RDB 2 driver + * Purpose: Add support for reading *.mpx RDB 2 files. + * Author: RIEGL Laser Measurement Systems GmbH (support@riegl.com) + * + ****************************************************************************** + * Copyright (c) 2019, RIEGL Laser Measurement Systems GmbH (support@riegl.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. + ****************************************************************************/ + +#ifndef RDB_DATASET_INCLUDED +#define RDB_DATASET_INCLUDED + +#include "../frmts/vrt/vrtdataset.h" +#include "gdal_pam.h" + +#include + +#include +#include +#include +#include +namespace rdb +{ +class RDBRasterBand; + +struct RDBNode +{ + int nXBlockCoordinates = 0; + int nYBlockCoordinates = 0; + riegl::rdb::pointcloud::GraphNode::ID iID = 0; + uint64_t nPointCount = 0; +}; + +struct RDBOverview +{ + double dfTileSize = 0; + double dfPixelSize = 0; + double adfMinimum[2] = {std::numeric_limits::max(), + std::numeric_limits::max()}; + double adfMaximum[2] = {std::numeric_limits::lowest(), + std::numeric_limits::lowest()}; + std::vector aoRDBNodes; + void addRDBNode(RDBNode &oRDBNode, double dfXMin, double dfYMin, + double dfXMax, double dfYMax); + void setTileSize(double dfTileSizeIn); +}; + +template struct RDBCoordinatesPlusData +{ + double adfCoordinates[2]; + T data; +}; + +class RDBDataset : public GDALPamDataset +{ + friend class RDBRasterBand; + template friend class RDBRasterBandInternal; + + // is locking needed? + // std::mutex oLock; + FILE *fp = nullptr; + riegl::rdb::Context oContext; + riegl::rdb::Pointcloud oPointcloud; + riegl::rdb::pointcloud::QueryStat oStatQuery; + + OGRSpatialReference oSpatialReference; + + double dfResolution = 0; + int nChunkSize = 0; + double dfSizeOfTile; + double dfSizeOfPixel; + CPLString osWktString; + + std::vector aoRDBOverviews; + std::vector> apoVRTDataset; + + double dfXMin; + double dfYMin; + + double dfXMax; + double dfYMax; + + double adfMinimumDs[2] = {}; + double adfMaximumDs[2] = {}; + + public: + explicit RDBDataset(GDALOpenInfo *poOpenInfo); + ~RDBDataset(); + + static GDALDataset *Open(GDALOpenInfo *poOpenInfo); + static int Identify(GDALOpenInfo *poOpenInfo); + + CPLErr GetGeoTransform(double *padfTransform) override; + const OGRSpatialReference *GetSpatialRef() const override; + + protected: + static void SetBandInternal( + RDBDataset *poDs, const std::string &osAttributeName, + const riegl::rdb::pointcloud::PointAttribute &oPointAttribute, + riegl::rdb::pointcloud::DataType eRDBDataType, int nLevel, + int nNumberOfLevels, int &nBandIndex); + void addRDBNode(const riegl::rdb::pointcloud::GraphNode &oNode, + double dfTileSize, std::size_t nLeve); + double traverseRDBNodes(const riegl::rdb::pointcloud::GraphNode &oNode, + std::size_t nLevel = 0); + + void ReadGeoreferencing(); +}; + +class RDBRasterBand : public GDALPamRasterBand +{ + protected: + CPLString osAttributeName; + CPLString osDescription; + riegl::rdb::pointcloud::PointAttribute oPointAttribute; + int nLevel; + + public: + RDBRasterBand( + RDBDataset *poDSIn, const std::string &osAttributeName, + const riegl::rdb::pointcloud::PointAttribute &oPointAttributeIn, + int nBandIn, GDALDataType eDataTypeIn, int nLevelIn); + + virtual double GetNoDataValue(int *pbSuccess = nullptr) override; + virtual const char *GetDescription() const override; +}; +} // namespace rdb +void GDALRegister_RDB(); + +#endif // RDB_DATASET_INCLUDED \ No newline at end of file diff --git a/gdal/gcore/gdal_frmts.h b/gdal/gcore/gdal_frmts.h index 814a1c3947dd..f58f2c45e654 100644 --- a/gdal/gcore/gdal_frmts.h +++ b/gdal/gcore/gdal_frmts.h @@ -201,6 +201,7 @@ void CPL_DLL GDALRegister_IGNFHeightASCIIGrid(void); void CPL_DLL GDALRegister_TileDB(void); void CPL_DLL GDALRegister_DAAS(void); void CPL_DLL GDALRegister_COG(void); +void CPL_DLL GDALRegister_RDB(void); CPL_C_END #endif /* ndef GDAL_FRMTS_H_INCLUDED */ diff --git a/gdal/nmake.opt b/gdal/nmake.opt index ce4ce126199e..85941a9b68ea 100644 --- a/gdal/nmake.opt +++ b/gdal/nmake.opt @@ -690,6 +690,11 @@ OCI_INCLUDE = -I$(ORACLE_HOME)\oci\include #TILEDB_CFLAGS = -IC:/install-tiledb/dist/include #TILEDB_LIBS = C:/install-tiledb/dist/lib/libtiledb.lib +# Uncomment for RDB support +#RDB_ENABLED = YES +#RDB_CFLAGS = -IC:\rdb\rdblib-2.2.0-x86_64-windows\interface\cpp -IC:\rdb\rdblib-2.2.0-x86_64-windows\interface\c +#RDB_LIB = C:\rdb\rdblib-2.2.0-x86_64-windows\library\rdblib.lib + # Uncomment for WEBP support #WEBP_ENABLED = YES #WEBP_CFLAGS = -IE:/libwebp-0.1-windows/dev/Include @@ -997,4 +1002,5 @@ EXTERNAL_LIBS = $(OGDILIB) $(XERCES_LIB) $(EXPAT_LIB) $(OCI_LIB) $(PG_LIB) \ $(MRSID_LIDAR_LIB) $(LIBKML_LIBS) $(SOSI_LIBS) $(PDF_LIB_LINK) $(LZMA_LIBS) $(ZSTD_LIBS) \ $(LIBICONV_LIBRARY) $(WEBP_LIBS) $(TILEDB_LIBS) $(FGDB_LIB_LINK) $(FREEXL_LIBS) $(GTA_LIBS) \ $(INGRES_LIB) $(LIBXML2_LIB) $(PCRE_LIB) $(MONGODB_LIB_LINK) $(MONGODBV3_LIB_LINK) $(CRYPTOPP_LIB) $(OPENSSL_LIB) $(CHARLS_LIB) ws2_32.lib \ + $(RDB_LIB) \ kernel32.lib psapi.lib