/***********************************************************************************/ /* Copyright 2009 WSL Institute for Snow and Avalanche Research SLF-DAVOS */ /***********************************************************************************/ /* This file is part of MeteoIO. MeteoIO is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. MeteoIO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with MeteoIO. If not, see . */ #include #include #include #include #include #include //for math constants /** * @file DEMObject.cc * @brief implementation of the DEMBoject class */ using namespace std; namespace mio { /** * @brief Default constructor. * Initializes all variables to 0, except lat/long which are initialized to IOUtils::nodata * @param i_algorithm specify the default algorithm to use for slope computation (default=DFLT) */ DEMObject::DEMObject(const slope_type& i_algorithm) : Grid2DObject(), slope(), azi(), curvature(), Nx(), Ny(), Nz(), min_altitude(Cst::dbl_max), min_slope(Cst::dbl_max), min_curvature(Cst::dbl_max), max_altitude(Cst::dbl_min), max_slope(Cst::dbl_min), max_curvature(Cst::dbl_min), CalculateSlope(&DEMObject::CalculateCorripio), update_flag(INT_MAX), dflt_algorithm(i_algorithm), slope_failures(0), curvature_failures(0) { setDefaultAlgorithm(i_algorithm); } /** * @brief Constructor that sets variables. * @param i_ncols number of colums in the grid2D * @param i_nrows number of rows in the grid2D * @param i_cellsize value for cellsize in grid2D * @param i_llcorner lower lower corner point * @param i_algorithm specify the default algorithm to use for slope computation (default=DFLT) */ DEMObject::DEMObject(const size_t& i_ncols, const size_t& i_nrows, const double& i_cellsize, const Coords& i_llcorner, const slope_type& i_algorithm) : Grid2DObject(i_ncols, i_nrows, i_cellsize, i_llcorner), slope(), azi(), curvature(), Nx(), Ny(), Nz(), min_altitude(Cst::dbl_max), min_slope(Cst::dbl_max), min_curvature(Cst::dbl_max), max_altitude(Cst::dbl_min), max_slope(Cst::dbl_min), max_curvature(Cst::dbl_min), CalculateSlope(&DEMObject::CalculateCorripio), update_flag(INT_MAX), dflt_algorithm(i_algorithm), slope_failures(0), curvature_failures(0) { setDefaultAlgorithm(i_algorithm); } /** * @brief Constructor that sets variables. * @param i_cellsize value for cellsize in grid2D * @param i_llcorner lower lower corner point * @param i_altitude grid2D of elevations * @param i_update also update slope/normals/curvatures and their min/max? (default=true) * @param i_algorithm specify the default algorithm to use for slope computation (default=DFLT) */ DEMObject::DEMObject(const double& i_cellsize, const Coords& i_llcorner, const Array2D& i_altitude, const bool& i_update, const slope_type& i_algorithm) : Grid2DObject(i_cellsize, i_llcorner, i_altitude), slope(), azi(), curvature(), Nx(), Ny(), Nz(), min_altitude(Cst::dbl_max), min_slope(Cst::dbl_max), min_curvature(Cst::dbl_max), max_altitude(Cst::dbl_min), max_slope(Cst::dbl_min), max_curvature(Cst::dbl_min), CalculateSlope(&DEMObject::CalculateCorripio), update_flag(INT_MAX), dflt_algorithm(i_algorithm), slope_failures(0), curvature_failures(0) { setDefaultAlgorithm(i_algorithm); if(i_update==false) { updateAllMinMax(); } else { update(i_algorithm); } } /** * @brief Constructor that sets variables from a Grid2DObject * @param i_dem grid contained in a Grid2DObject * @param i_update also update slope/normals/curvatures and their min/max? (default=true) * @param i_algorithm specify the default algorithm to use for slope computation (default=DFLT) */ DEMObject::DEMObject(const Grid2DObject& i_dem, const bool& i_update, const slope_type& i_algorithm) : Grid2DObject(i_dem.cellsize, i_dem.llcorner, i_dem.grid2D), slope(), azi(), curvature(), Nx(), Ny(), Nz(), min_altitude(Cst::dbl_max), min_slope(Cst::dbl_max), min_curvature(Cst::dbl_max), max_altitude(Cst::dbl_min), max_slope(Cst::dbl_min), max_curvature(Cst::dbl_min), CalculateSlope(&DEMObject::CalculateCorripio), update_flag(INT_MAX), dflt_algorithm(i_algorithm), slope_failures(0), curvature_failures(0) { setDefaultAlgorithm(i_algorithm); if(i_update==false) { updateAllMinMax(); } else { update(i_algorithm); } } /** * @brief Constructor that sets variables from a subset of another DEMObject, * given an origin (X,Y) (first index being 0) and a number of columns and rows * @param i_dem dem contained in a DEMDObject * @param i_nx X coordinate of the new origin * @param i_ny Y coordinate of the new origin * @param i_ncols number of columns for the subset dem * @param i_nrows number of rows for the subset dem * @param i_update also update slope/normals/curvatures and their min/max? (default=true) * @param i_algorithm specify the default algorithm to use for slope computation (default=DFLT) */ DEMObject::DEMObject(const DEMObject& i_dem, const size_t& i_nx, const size_t& i_ny, const size_t& i_ncols, const size_t& i_nrows, const bool& i_update, const slope_type& i_algorithm) : Grid2DObject(i_dem, i_nx,i_ny, i_ncols,i_nrows), slope(), azi(), curvature(), Nx(), Ny(), Nz(), min_altitude(Cst::dbl_max), min_slope(Cst::dbl_max), min_curvature(Cst::dbl_max), max_altitude(Cst::dbl_min), max_slope(Cst::dbl_min), max_curvature(Cst::dbl_min), CalculateSlope(&DEMObject::CalculateCorripio), update_flag(i_dem.update_flag), dflt_algorithm(i_algorithm), slope_failures(0), curvature_failures(0) { if ((i_ncols==0) || (i_nrows==0)) { throw InvalidArgumentException("requesting a subset of 0 columns or rows for DEMObject", AT); } //handling of the update properties setDefaultAlgorithm(i_algorithm); if(i_update==true) { //if the object is in automatic update, then we only process the arrays according to //the update_flag update(i_algorithm); } else { //if the object is NOT in automatic update, we manually copy all non-empty arrays //from the original set size_t nx, ny; i_dem.slope.size(nx, ny); if(nx>0 && ny>0) { slope.subset(i_dem.slope,i_nx,i_ny, i_ncols,i_nrows); } i_dem.azi.size(nx, ny); if(nx>0 && ny>0) { azi.subset(i_dem.azi,i_nx,i_ny, i_ncols,i_nrows); } i_dem.curvature.size(nx, ny); if(nx>0 && ny>0) { curvature.subset(i_dem.curvature,i_nx,i_ny, i_ncols,i_nrows); } i_dem.Nx.size(nx, ny); if(nx>0 && ny>0) { Nx.subset(i_dem.Nx,i_nx,i_ny, i_ncols,i_nrows); } i_dem.Ny.size(nx, ny); if(nx>0 && ny>0) { Ny.subset(i_dem.Ny,i_nx,i_ny, i_ncols,i_nrows); } i_dem.Nz.size(nx, ny); if(nx>0 && ny>0) { Nz.subset(i_dem.Nz,i_nx,i_ny, i_ncols,i_nrows); } updateAllMinMax(); } } /** * @brief Set the properties that will be calculated by the object when updating * The following properties can be turned on/off: slope/azimuth and/or normals, and/or curvatures. * Flags are combined using the binary "|" operator. * @param in_update_flag parameters to update */ void DEMObject::setUpdatePpt(const update_type& in_update_flag) { update_flag = in_update_flag; } /** * @brief Get the properties that will be calculated by the object when updating * @return combination of flags set with the binary "|" operator */ int DEMObject::getUpdatePpt() const { return update_flag; } /** * @brief Force the computation of the local slope, azimuth, normal vector and curvature. * It has to be called manually since it can require some time to compute. Without this call, * the above mentionned parameters are NOT up to date. * @param algorithm algorithm to use for computing slope, azimuth and normals */ void DEMObject::update(const slope_type& algorithm) { //This method recomputes the attributes that are not read as parameters //(such as slope, azimuth, normal vector) // Creating tables if(update_flag&SLOPE) { slope.resize(getNx(), getNy()); azi.resize(getNx(), getNy()); } if(update_flag&CURVATURE) { curvature.resize(getNx(), getNy()); } if(update_flag&NORMAL) { Nx.resize(getNx(), getNy()); Ny.resize(getNx(), getNy()); Nz.resize(getNx(), getNy()); } CalculateAziSlopeCurve(algorithm); updateAllMinMax(); } /** * @brief Force the computation of the local slope, azimuth, normal vector and curvature. * It has to be called manually since it can require some time to compute. Without this call, * the above mentionned parameters are NOT up to date. * @param algorithm algorithm to use for computing slope, azimuth and normals * it is either: * - HICK that uses the maximum downhill slope method (Dunn and Hickey, 1998) * - FLEMING uses a 4 neighbors algorithm (Fleming and Hoffer, 1979) * - CORRIPIO that uses the surface normal vector using the two triangle method given in Corripio (2002) * and the eight-neighbor algorithm of Horn (1981) for border cells. * - D8 uses CORRIPIO but discretizes the resulting azimuth to 8 cardinal directions and the slope is rounded to the nearest degree. Curvature and normals are left untouched. * * The azimuth is always computed using the Hodgson (1998) algorithm. */ void DEMObject::update(const std::string& algorithm) { //This method recomputes the attributes that are not read as parameters //(such as slope, azimuth, normal vector) slope_type type; if(algorithm.compare("HICK")==0) { type=HICK; } else if(algorithm.compare("FLEMING")==0) { type=FLEM; } else if(algorithm.compare("HORN")==0) { type=HORN; } else if(algorithm.compare("CORRIPIO")==0) { type=CORR; } else if(algorithm.compare("D8")==0) { type=D8; } else if(algorithm.compare("DEFAULT")==0) { type=DFLT; } else { throw InvalidArgumentException("Chosen slope algorithm " + algorithm + " not available", AT); } update(type); } /** * @brief Sets the default slope calculation algorithm * @param i_algorithm specify the default algorithm to use for slope computation */ void DEMObject::setDefaultAlgorithm(const slope_type& i_algorithm) { //This method MUST be called by each constructor! if(i_algorithm==DFLT) { dflt_algorithm = CORR; } else { dflt_algorithm = i_algorithm; } } /** * @brief Get the default slope calculation algorithm * @return default algorithm to use for slope computation */ int DEMObject::getDefaultAlgorithm() const { return dflt_algorithm; } /** * @brief Recomputes the min/max of altitude, slope and curvature * It return +/- std::numeric_limits\\:\:max() for a given parameter if its grid was empty/undefined */ void DEMObject::updateAllMinMax() { //updates the min/max parameters of all 2D tables if(update_flag&SLOPE) { min_slope = slope.getMin(); max_slope = slope.getMax(); } if(update_flag&CURVATURE) { min_curvature = curvature.getMin(); max_curvature = curvature.getMax(); } min_altitude = grid2D.getMin(); max_altitude = grid2D.getMax(); } /** * @brief Prints the list of points that have an elevation different than nodata but no slope or curvature * Such points can happen if they are surrounded by too many points whose elevation is nodata * If no such points exist, it prints nothing. */ void DEMObject::printFailures() { bool header=true; const size_t ncols = getNx(); const size_t nrows = getNy(); if(update_flag&SLOPE) { for ( size_t j = 0; j < nrows; j++ ) { for ( size_t i = 0; i < ncols; i++ ) { if((slope(i,j)==IOUtils::nodata) && (grid2D(i,j)!=IOUtils::nodata)) { if(header==true) { cerr << "[i] DEM slope could not be computed at the following points \n"; cerr << "[i]\tGrid Point\tElevation\tSlope\n"; header=false; } cerr << "[i]\t(" << i << "," << j << ")" << "\t\t" << grid2D(i,j) << "\t\t" << slope(i,j) << "\n"; } } } } if(update_flag&CURVATURE) { for ( size_t j = 0; j < nrows; j++ ) { for ( size_t i = 0; i < ncols; i++ ) { if((curvature(i,j)==IOUtils::nodata) && (grid2D(i,j)!=IOUtils::nodata)) { if(header==true) { cerr << "[i] DEM curvature could not be computed at the following points \n"; cerr << "[i]\tGrid Point\tElevation\tCurvature\n"; header=false; } cerr << "[i]\t(" << i << "," << j << ")" << "\t\t" << grid2D(i,j) << "\t\t" << curvature(i,j) << "\n"; } } } } if(header==false) { cerr << std::endl; } } /** * @brief Clean up the DEM Object * When computing the slope and curvature, it is possible to get points where the elevation is known * but where no slope/azimuth/normals/curvature could be computed. This method sets the elevation to nodata for such points, * so that latter use of the DEM would be simpler (simply test the elevation in order to know if the point can be used * and it guarantees that all other informations are available).If the slope/azimuth/normals/curvature tables were manually updated, this method will NOT perform any work (it requires the count of slopes/curvature failures to be greater than zero) * * IMPORTANT: calling this method DOES change the table of elevations! */ void DEMObject::sanitize() { if(slope_failures>0 || curvature_failures>0) { const size_t ncols = getNx(); const size_t nrows = getNy(); for ( size_t j = 0; j < nrows; j++ ) { for ( size_t i = 0; i < ncols; i++ ) { if(update_flag&SLOPE) { if((slope(i,j)==IOUtils::nodata) && (grid2D(i,j)!=IOUtils::nodata)) { grid2D(i,j) = IOUtils::nodata; } } if(update_flag&CURVATURE) { if((curvature(i,j)==IOUtils::nodata) && (grid2D(i,j)!=IOUtils::nodata)) { grid2D(i,j) = IOUtils::nodata; } } } } } } /** * @brief Computes the hillshade for the dem * This "fake illumination" method is used to better show the relief on maps. * @param elev elevation (in degrees) of the source of light * @param azimuth azimuth (in degrees) of the source of light * @return hillshade grid that containing the illumination * */ Grid2DObject DEMObject::getHillshade(const double& elev, const double& azimuth) const { if(slope.empty() || azi.empty()) throw InvalidArgumentException("Hillshade computation requires slope and azimuth!", AT); const double zenith_rad = (90.-elev)*Cst::to_rad; const double azimuth_rad = azimuth*Cst::to_rad; const size_t ncols = getNx(); const size_t nrows = getNy(); Grid2DObject hillshade(ncols, nrows, cellsize, llcorner); for ( size_t j = 0; j < nrows; j++ ) { for ( size_t i = 0; i < ncols; i++ ) { const double alt = grid2D(i,j); const double sl = slope(i,j); const double az = azi(i,j); if(alt!=IOUtils::nodata && sl!=IOUtils::nodata && az!=IOUtils::nodata) { const double sl_rad = sl*Cst::to_rad; const double tmp = cos(zenith_rad) * cos(sl_rad) + sin(zenith_rad) * sin(sl_rad) * cos(azimuth_rad-az*Cst::to_rad); hillshade(i,j) = (tmp>=0.)? tmp : 0.; } else hillshade(i,j) = IOUtils::nodata; } } return hillshade; } /** * @brief Computes the horizontal distance between two points in a metric grid * @param xcoord1 east coordinate of the first point * @param ycoord1 north coordinate of the first point * @param xcoord2 east coordinate of the second point * @param ycoord2 north coordinate of the second point * @return horizontal distance in meters * */ double DEMObject::horizontalDistance(const double& xcoord1, const double& ycoord1, const double& xcoord2, const double& ycoord2) { return sqrt( Optim::pow2(xcoord2-xcoord1) + Optim::pow2(ycoord2-ycoord1) ); } /** * @brief Computes the horizontal distance between two points in a metric grid * @param point1 first point (ie: origin) * @param point2 second point (ie: destination) * @return horizontal distance in meters * */ double DEMObject::horizontalDistance(Coords point1, const Coords& point2) { if(point1.isSameProj(point2)==false) { point1.copyProj(point2); } return horizontalDistance(point1.getEasting(), point1.getNorthing(), point2.getEasting(), point2.getNorthing() ); } /** * @brief Returns the distance *following the terrain* between two coordinates * @param point1 first point (ie: origin) * @param point2 second point (ie: destination) * @return distance following the terrain in meters * */ double DEMObject::terrainDistance(Coords point1, const Coords& point2) { std::vector vec_points; double distance=0.; size_t last_point=0; //point 0 is always the starting point //Checking that both points use the same projection is done in getPointsBetween() getPointsBetween(point1, point2, vec_points); if(vec_points.size()<=1) { return 0.; } for(size_t ii=1; ii& vec_points) { vec_points.clear(); if(ix1==ix2) { //special case of vertical alignement for(int iy=min(iy1,iy2); iy<=max(iy1,iy2); iy++) { GRID_POINT_2D pts; pts.ix = ix1; pts.iy = iy; vec_points.push_back(pts); } } else { //normal case //equation of the line between the two points const double a = ((double)(iy2-iy1)) / ((double)(ix2-ix1)); const double b = (double)iy1 - a * (double)ix1; for(int ix=ix1; ix<=ix2; ix++) { //extension of the line segment (ix, ix+1) along the Y axis int y1 = (int)floor( a*(double)ix+b ); //const int y2 = min( (int)floor( a*((double)ix+1)+b ) , iy2); int y2 = (int)floor( a*((double)ix+1)+b ); if(ix==ix2 && y1==iy2) { //we don't want to overshoot when reaching the target cell y2 = y1; } if(y1>y2) { //we want y10 && ix<(signed)getNx() && iy>0 && iy<(signed)getNy()) { vec_points.push_back(pts); } } } } } /** * @brief Returns a list of grid points that are on the straight line between two coordinates * @param point1 first point (ie: origin) * @param point2 second point (ie: destination) * @param vec_points vector of points that are in between * */ void DEMObject::getPointsBetween(Coords point1, Coords point2, std::vector& vec_points) { if(point1.isSameProj(point2)==false) { point1.copyProj(point2); } if(point1.getEasting() > point2.getEasting()) { //we want xcoord1& vec_points) { //equation of the line between for a point (x0,y0) and a bearing const double x0 = (point.getEasting() - llcorner.getEasting())/cellsize; const double y0 = (point.getNorthing() - llcorner.getNorthing())/cellsize; const double bear=fmod(bearing+360., 360.); //this should not be needed, but as safety... const double a = tan( IOUtils::bearing_to_angle(bear) ); //to get trigonometric angle const double b = y0 - a * x0; //looking which point is on the limit of the grid and not outside Coords pointlim; pointlim.copyProj(llcorner); //we use the same projection parameters as the DEM //define the boundaries according to the quadrant we are in double xlim, ylim; if(bear>=0. && bear<90.) { xlim = (double)(getNx()-1); ylim = (double)(getNy()-1); } else if (bear>=90. && bear<180.) { xlim = (double)(getNx()-1); ylim = 0.; } else if (bear>=180. && bear<270.) { xlim = 0.; ylim = 0.; } else { xlim = 0.; ylim = (double)(getNy()-1); } //calculate the two possible intersections between the bearing line and the boundaries const double y2 = a * xlim + b; const double x2 = (ylim - b) / (a + 1e-12); //Find out which point is the first intersect and take it as our destination point if(bear>=90. && bear<270.) { if (y2 >= ylim) pointlim.setXY((xlim*cellsize)+llcorner.getEasting(),(y2*cellsize)+llcorner.getNorthing() , IOUtils::nodata); else pointlim.setXY((x2*cellsize)+llcorner.getEasting(),(ylim*cellsize)+llcorner.getNorthing() , IOUtils::nodata); } else { if (y2 <= ylim) pointlim.setXY((xlim*cellsize)+llcorner.getEasting(),(y2*cellsize)+llcorner.getNorthing() , IOUtils::nodata); else pointlim.setXY((x2*cellsize)+llcorner.getEasting(),(ylim*cellsize)+llcorner.getNorthing() , IOUtils::nodata); } if(gridify(pointlim)==false) { std::ostringstream tmp; tmp << "[E] Wrong destination point calculated for bearing " << bearing; throw InvalidArgumentException(tmp.str(), AT); } getPointsBetween(point, pointlim, vec_points); //HACK BUG : for bearing=160 -> both start and end points are missing from the list!! } /** * @brief Returns the horizon from a given point looking toward a given bearing * @param point the origin point * @param bearing direction given by a compass bearing * @return angle above the horizontal (in deg) * */ double DEMObject::getHorizon(const Coords& point, const double& bearing) { std::vector vec_points; getPointsBetween(point, bearing, vec_points); //Starting point const int ix0 = (int)point.getGridI(); const int iy0 = (int)point.getGridJ(); const double height0 = grid2D(ix0,iy0); //going through every point and looking for the highest tangent (which is also the highest angle) double max_tangent = 0.; for (size_t ii=0; ii < vec_points.size(); ii++) { const int ix = (int)vec_points[ii].ix; const int iy = (int)vec_points[ii].iy; const double delta_height = grid2D(ix, iy) - height0; const double x_distance = (double)(ix - ix0) * cellsize; const double y_distance = (double)(iy - iy0) * cellsize; const double distance = sqrt(x_distance * x_distance + y_distance * y_distance); const double tangent = (delta_height / distance); if(tangent > max_tangent) max_tangent = tangent; } //returning the angle matching the highest tangent return ( atan(max_tangent)*Cst::to_deg ); } /** * @brief Returns the horizon from a given point looking 360 degrees around by increments * @param point the origin point * @param increment to the bearing between two angles * @param horizon vector of heights above a given angle * */ void DEMObject::getHorizon(const Coords& point, const double& increment, std::vector& horizon) { for(double bearing=0.0; bearing <360.; bearing += increment) { const double alpha = getHorizon(point, bearing * Cst::PI/180.); horizon.push_back(alpha); } } void DEMObject::CalculateAziSlopeCurve(slope_type algorithm) { //This computes the slope and the aspect at a given cell as well as the x and y components of the normal vector double A[4][4]; //table to store neigbouring heights: 3x3 matrix but we want to start at [1][1] //we use matrix notation: A[y][x] if(algorithm==DFLT) { algorithm = dflt_algorithm; } slope_failures = curvature_failures = 0; if(algorithm==HICK) { CalculateSlope = &DEMObject::CalculateHick; } else if(algorithm==HORN) { CalculateSlope = &DEMObject::CalculateHorn; } else if(algorithm==CORR) { CalculateSlope = &DEMObject::CalculateCorripio; } else if(algorithm==FLEM) { CalculateSlope = &DEMObject::CalculateFleming; } else if(algorithm==D8) { CalculateSlope = &DEMObject::CalculateHick; } else { throw InvalidArgumentException("Chosen slope algorithm not available", AT); } //Now, calculate the parameters using the previously defined function pointer for ( size_t j = 0; j < getNy(); j++ ) { for ( size_t i = 0; i < getNx(); i++ ) { if( grid2D(i,j) == IOUtils::nodata ) { if(update_flag&SLOPE) { slope(i,j) = azi(i,j) = IOUtils::nodata; } if(update_flag&CURVATURE) { curvature(i,j) = IOUtils::nodata; } if(update_flag&NORMAL) { Nx(i,j) = Ny(i,j) = Nz(i,j) = IOUtils::nodata; } } else { getNeighbours(i, j, A); double new_slope, new_Nx, new_Ny, new_Nz; (this->*CalculateSlope)(A, new_slope, new_Nx, new_Ny, new_Nz); const double new_azi = CalculateAspect(new_Nx, new_Ny, new_Nz, new_slope); const double new_curvature = getCurvature(A); if(update_flag&SLOPE) { slope(i,j) = new_slope; azi(i,j) = new_azi; } if(update_flag&CURVATURE) { curvature(i,j) = new_curvature; } if(update_flag&NORMAL) { Nx(i,j) = new_Nx; Ny(i,j) = new_Ny; Nz(i,j) = new_Nz; } } } } if((update_flag&SLOPE) && (algorithm==D8)) { //extra processing required: discretization for ( size_t j = 0; j < getNy(); j++ ) { for ( size_t i = 0; i < getNx(); i++ ) { //TODO: process flats by an extra algorithm if(azi(i,j)!=IOUtils::nodata) azi(i,j) = fmod(floor( (azi(i,j)+22.5)/45. )*45., 360.); if(slope(i,j)!=IOUtils::nodata) slope(i,j) = floor( slope(i,j)+0.5 ); } } } //Inform the user is some points have unexpectidly not been computed //(ie: there was an altitude but some parameters could not be computed) if(slope_failures>0 || curvature_failures>0) { cerr << "[W] DEMObject: " << slope_failures << " point(s) have an elevation but no slope, " << curvature_failures << " point(s) have an elevation but no curvature." << std::endl; } } // end of CalculateAziSlope double DEMObject::CalculateAspect(const double& o_Nx, const double& o_Ny, const double& o_Nz, const double& o_slope, const double no_slope) { //Calculates the aspect at a given point knowing its normal vector and slope //(direction of the normal pointing out of the surface, clockwise from north) //This azimuth calculation is similar to Hodgson (1998) //local_nodata is the value that we want to give to the aspect of points that don't have a slope //The value is a bearing (ie: deg, clockwise, 0=North) if(o_Nx==IOUtils::nodata || o_Ny==IOUtils::nodata || o_Nz==IOUtils::nodata || o_slope==IOUtils::nodata) { return IOUtils::nodata; } if ( o_slope > 0. ) { //there is some slope if ( o_Nx == 0. ) { //no E-W slope, so it is purely N-S const double aspect = (o_Ny < 0.)? 180. : 0.; return aspect; } else { //there is a E-W slope const double angle_deg = static_cast(Optim::round( atan2(o_Ny, o_Nx)*Cst::to_deg * 10.)) / 10.; //round angle to 0.1 const double aspect = fmod( 90. - angle_deg + 360., 360.); //convert angle to bearing return aspect; } } else { // if slope = 0 return (no_slope); // undefined or plain surface } } void DEMObject::CalculateHick(double A[4][4], double& o_slope, double& o_Nx, double& o_Ny, double& o_Nz) { //This calculates the surface normal vector using the steepest slope method (Dunn and Hickey, 1998): //the steepest slope found in the eight cells surrounding (i,j) is given to be the slope in (i,j) //Beware, sudden steps could happen const double smax = steepestGradient(A); //steepest local gradient if(smax==IOUtils::nodata) { o_slope = IOUtils::nodata; o_Nx = IOUtils::nodata; o_Ny = IOUtils::nodata; o_Nz = IOUtils::nodata; slope_failures++; } else { o_slope = atan(smax)*Cst::to_deg; //Nx and Ny: x and y components of the normal pointing OUT of the surface if ( smax > 0. ) { //ie: there is some slope double dx_sum, dy_sum; surfaceGradient(dx_sum, dy_sum, A); if(dx_sum==IOUtils::nodata || dy_sum==IOUtils::nodata) { o_Nx = IOUtils::nodata; o_Ny = IOUtils::nodata; o_Nz = IOUtils::nodata; slope_failures++; } else { o_Nx = -1.0 * dx_sum / (2. * cellsize); //Nx=-dz/dx o_Ny = -1.0 * dy_sum / (2. * cellsize); //Ny=-dz/dy o_Nz = 1.; //Nz=1 (normalized by definition of Nx and Ny) } } else { //ie: there is no slope o_Nx = 0.; o_Ny = 0.; o_Nz = 1.; } } } void DEMObject::CalculateFleming(double A[4][4], double& o_slope, double& o_Nx, double& o_Ny, double& o_Nz) { //This calculates the surface normal vector using method by Fleming and Hoffer (1979) if(A[2][1]!=IOUtils::nodata && A[2][3]!=IOUtils::nodata && A[3][2]!=IOUtils::nodata && A[1][2]!=IOUtils::nodata) { o_Nx = 0.5 * (A[2][1] - A[2][3]) / cellsize; o_Ny = 0.5 * (A[3][2] - A[1][2]) / cellsize; o_Nz = 1.; o_slope = atan( sqrt(o_Nx*o_Nx+o_Ny*o_Ny) ) * Cst::to_deg; } else { CalculateHick(A, o_slope, o_Nx, o_Ny, o_Nz); } } void DEMObject::CalculateHorn(double A[4][4], double& o_slope, double& o_Nx, double& o_Ny, double& o_Nz) { //This calculates the slope using the two eight neighbors method given in Horn (1981) //This is also the algorithm used by ArcGIS if ( A[1][1]!=IOUtils::nodata && A[1][2]!=IOUtils::nodata && A[1][3]!=IOUtils::nodata && A[2][1]!=IOUtils::nodata && A[2][2]!=IOUtils::nodata && A[2][3]!=IOUtils::nodata && A[3][1]!=IOUtils::nodata && A[3][2]!=IOUtils::nodata && A[3][3]!=IOUtils::nodata) { o_Nx = ((A[3][3]+2.*A[2][3]+A[1][3]) - (A[3][1]+2.*A[2][1]+A[1][1])) / (8.*cellsize); o_Ny = ((A[1][3]+2.*A[1][2]+A[1][1]) - (A[3][3]+2.*A[3][2]+A[3][1])) / (8.*cellsize); o_Nz = 1.; //There is no difference between slope = acos(n_z/|n|) and slope = atan(sqrt(sx*sx+sy*sy)) //slope = acos( (Nz / sqrt( Nx*Nx + Ny*Ny + Nz*Nz )) ); o_slope = atan( sqrt(o_Nx*o_Nx+o_Ny*o_Ny) ) * Cst::to_deg; } else { //steepest slope method (Dunn and Hickey, 1998) CalculateHick(A, o_slope, o_Nx, o_Ny, o_Nz); } } void DEMObject::CalculateCorripio(double A[4][4], double& o_slope, double& o_Nx, double& o_Ny, double& o_Nz) { //This calculates the surface normal vector using the two triangle method given in Corripio (2003) but cell centered instead of node centered (ie using a 3x3 grid instead of 2x2) if ( A[1][1]!=IOUtils::nodata && A[1][3]!=IOUtils::nodata && A[3][1]!=IOUtils::nodata && A[3][3]!=IOUtils::nodata) { // See Corripio (2003), knowing that here we normalize the result (divided by Nz=cellsize*cellsize) and that we are cell centered instead of node centered o_Nx = (A[3][1] + A[1][1] - A[3][3] - A[1][3]) / (2.*2.*cellsize); o_Ny = (A[3][1] - A[1][1] + A[3][3] - A[1][3]) / (2.*2.*cellsize); o_Nz = 1.; //There is no difference between slope = acos(n_z/|n|) and slope = atan(sqrt(sx*sx+sy*sy)) //slope = acos( (Nz / sqrt( Nx*Nx + Ny*Ny + Nz*Nz )) ); o_slope = atan( sqrt(o_Nx*o_Nx+o_Ny*o_Ny) ) * Cst::to_deg; } else { //steepest slope method (Dunn and Hickey, 1998) CalculateHick(A, o_slope, o_Nx, o_Ny, o_Nz); } } double DEMObject::getCurvature(double A[4][4]) { //This methode computes the curvature of a specific cell if(A[2][2]!=IOUtils::nodata) { const double Zwe = avgHeight(A[2][1], A[2][2], A[2][3]); const double Zsn = avgHeight(A[1][2], A[2][2], A[3][2]); const double Zswne = avgHeight(A[3][1], A[2][2], A[1][3]); const double Znwse = avgHeight(A[1][1], A[2][2], A[3][3]); const double sqrt2 = sqrt(2.); double sum=0.; size_t count=0; if(Zwe!=IOUtils::nodata) { sum += 0.5*(A[2][2]-Zwe); count++; } if(Zsn!=IOUtils::nodata) { sum += 0.5*(A[2][2]-Zsn); count++; } if(Zswne!=IOUtils::nodata) { sum += 0.5*(A[2][2]-Zswne)/sqrt2; count++; } if(Znwse!=IOUtils::nodata) { sum += 0.5*(A[2][2]-Znwse)/sqrt2; count++; } if(count != 0.) return 1./(double)count * sum; } curvature_failures++; return IOUtils::nodata; } double DEMObject::steepestGradient(double A[4][4]) { //best effort to calculate the local steepest gradient double smax=-1.; //maximum slope of all neighboring slopes const double sqrt2=sqrt(2.); //the weight of the 4 corner cells is increased by sqrt(2) if(A[2][2]!=IOUtils::nodata) { if(A[1][1]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[1][1])/(cellsize*sqrt2) ); if(A[1][2]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[1][2])/(cellsize) ); if(A[1][3]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[1][3])/(cellsize*sqrt2) ); if(A[2][1]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[2][1])/(cellsize) ); if(A[2][3]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[2][3])/(cellsize) ); if(A[3][1]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[3][1])/(cellsize*sqrt2) ); if(A[3][2]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[3][2])/(cellsize) ); if(A[3][3]!=IOUtils::nodata) smax = max( smax, fabs(A[2][2] - A[3][3])/(cellsize*sqrt2) ); } if(smax<0.) return IOUtils::nodata; return smax; } double DEMObject::lineGradient(const double& A1, const double& A2, const double& A3) { //best effort to calculate the local gradient if(A3!=IOUtils::nodata && A1!=IOUtils::nodata) { return A3 - A1; } else { if(A2!=IOUtils::nodata) { if(A3!=IOUtils::nodata) return (A3 - A2)*2.; if(A1!=IOUtils::nodata) return (A2 - A1)*2.; } } return IOUtils::nodata; } double DEMObject::fillMissingGradient(const double& delta1, const double& delta2) { //If a gradient could not be computed, try to fill it with some neighboring value if(delta1!=IOUtils::nodata && delta2!=IOUtils::nodata) { return 0.5*(delta1+delta2); } else { if(delta1!=IOUtils::nodata) return delta1; if(delta2!=IOUtils::nodata) return delta2; } return IOUtils::nodata; } void DEMObject::surfaceGradient(double& dx_sum, double& dy_sum, double A[4][4]) { //Compute the gradient for a given cell (i,j) accross its eight surrounding cells (Horn, 1981) double dx1 = lineGradient(A[3][1], A[3][2], A[3][3]); double dx2 = lineGradient(A[2][1], A[2][2], A[2][3]); double dx3 = lineGradient(A[1][1], A[1][2], A[1][3]); double dy1 = lineGradient(A[3][1], A[2][1], A[1][1]); double dy2 = lineGradient(A[3][2], A[2][2], A[1][2]); double dy3 = lineGradient(A[3][3], A[2][3], A[1][3]); //now trying to fill whatever could not be filled... if(dx1==IOUtils::nodata) dx1 = fillMissingGradient(dx2, dx3); if(dx2==IOUtils::nodata) dx2 = fillMissingGradient(dx1, dx3); if(dx3==IOUtils::nodata) dx3 = fillMissingGradient(dx1, dx2); if(dy1==IOUtils::nodata) dy1 = fillMissingGradient(dy2, dy3); if(dy2==IOUtils::nodata) dy2 = fillMissingGradient(dy1, dy3); if(dy3==IOUtils::nodata) dy3 = fillMissingGradient(dy1, dy2); if(dx1!=IOUtils::nodata && dy1!=IOUtils::nodata) { // principal axis twice to emphasize height difference in that direction dx_sum = (dx1 + 2.*dx2 + dx3) * 0.25; dy_sum = (dy1 + 2.*dy2 + dy3) * 0.25; } else { //if dx1==nodata, this also means that dx2==nodata and dx3==nodata //(otherwise, dx1 would have received a copy of either dx2 or dx3) dx_sum = IOUtils::nodata; dy_sum = IOUtils::nodata; } } double DEMObject::avgHeight(const double& z1, const double &z2, const double& z3) { //this safely computes the average height accross a vector if(z1!=IOUtils::nodata && z3!=IOUtils::nodata) { return 0.5*(z1+z3); } if(z1!=IOUtils::nodata && z2!=IOUtils::nodata) { return 0.5*(z1+z2); } if(z3!=IOUtils::nodata && z2!=IOUtils::nodata) { return 0.5*(z3+z2); } return IOUtils::nodata; } void DEMObject::getNeighbours(const size_t i, const size_t j, double A[4][4]) { //this fills a 3x3 table containing the neighboring values A[1][1] = safeGet((signed)i-1, (signed)j+1); A[1][2] = safeGet((signed)i, (signed)j+1); A[1][3] = safeGet((signed)i+1, (signed)j+1); A[2][1] = safeGet((signed)i-1, (signed)j); A[2][2] = safeGet((signed)i, (signed)j); A[2][3] = safeGet((signed)i+1, (signed)j); A[3][1] = safeGet((signed)i-1, (signed)j-1); A[3][2] = safeGet((signed)i, (signed)j-1); A[3][3] = safeGet((signed)i+1, (signed)j-1); } double DEMObject::safeGet(const int i, const int j) {//this function would allow reading the value of *any* point, //that is, even for coordinates outside of the grid (where it would return nodata) //this is to make implementing the slope/curvature computation easier for edges, holes, etc if(i<0 || i>=(signed)getNx()) { return IOUtils::nodata; } if(j<0 || j>=(signed)getNy()) { return IOUtils::nodata; } return grid2D((unsigned)i, (unsigned)j); } std::iostream& operator<<(std::iostream& os, const DEMObject& dem) { operator<<(os, *((Grid2DObject*)&dem)); os << dem.slope; os << dem.azi; os << dem.curvature; os << dem.Nx << dem.Ny << dem.Nz; os.write(reinterpret_cast(&dem.min_altitude), sizeof(dem.min_altitude)); os.write(reinterpret_cast(&dem.min_slope), sizeof(dem.min_slope)); os.write(reinterpret_cast(&dem.min_curvature), sizeof(dem.min_curvature)); os.write(reinterpret_cast(&dem.max_altitude), sizeof(dem.max_altitude)); os.write(reinterpret_cast(&dem.max_slope), sizeof(dem.max_slope)); os.write(reinterpret_cast(&dem.max_curvature), sizeof(dem.max_curvature)); os.write(reinterpret_cast(&dem.update_flag), sizeof(dem.update_flag)); os.write(reinterpret_cast(&dem.dflt_algorithm), sizeof(dem.dflt_algorithm)); os.write(reinterpret_cast(&dem.slope_failures), sizeof(dem.slope_failures)); os.write(reinterpret_cast(&dem.curvature_failures), sizeof(dem.curvature_failures)); return os; } std::iostream& operator>>(std::iostream& is, DEMObject& dem) { operator>>(is, *((Grid2DObject*)&dem)); is >> dem.slope; is >> dem.azi; is >> dem.curvature; is >> dem.Nx >> dem.Ny >> dem.Nz; is.read(reinterpret_cast(&dem.min_altitude), sizeof(dem.min_altitude)); is.read(reinterpret_cast(&dem.min_slope), sizeof(dem.min_slope)); is.read(reinterpret_cast(&dem.min_curvature), sizeof(dem.min_curvature)); is.read(reinterpret_cast(&dem.max_altitude), sizeof(dem.max_altitude)); is.read(reinterpret_cast(&dem.max_slope), sizeof(dem.max_slope)); is.read(reinterpret_cast(&dem.max_curvature), sizeof(dem.max_curvature)); is.read(reinterpret_cast(&dem.update_flag), sizeof(dem.update_flag)); is.read(reinterpret_cast(&dem.dflt_algorithm), sizeof(dem.dflt_algorithm)); is.read(reinterpret_cast(&dem.slope_failures), sizeof(dem.slope_failures)); is.read(reinterpret_cast(&dem.curvature_failures), sizeof(dem.curvature_failures)); return is; } DEMObject& DEMObject::operator=(const double& value) { grid2D = value; const size_t nx = getNx(), ny = getNy(); slope.resize(nx, ny, 0.); azi.resize(nx, ny, 0.); curvature.resize(nx, ny, 0.); Nx.resize(nx, ny, 0.); Ny.resize(nx, ny, 0.); Nz.resize(nx, ny, 0.); min_altitude = grid2D.getMin(); max_altitude = grid2D.getMax(); min_slope = 0.; max_slope = 0.; min_curvature = 0.; max_curvature = 0.; return *this; } DEMObject& DEMObject::operator+=(const double& rhs) { grid2D += rhs; updateAllMinMax(); return *this; } const DEMObject DEMObject::operator+(const double& rhs) { DEMObject result = *this; result.grid2D += rhs; result.updateAllMinMax(); return result; } DEMObject& DEMObject::operator+=(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); grid2D += rhs.grid2D; update(); return *this; } const DEMObject DEMObject::operator+(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); DEMObject result(*this); result.grid2D += rhs.grid2D; result.update(); return result; } DEMObject& DEMObject::operator-=(const double& rhs) { grid2D -= rhs; updateAllMinMax(); return *this; } const DEMObject DEMObject::operator-(const double& rhs) { DEMObject result(*this); result.grid2D -= rhs; result.updateAllMinMax(); return result; } DEMObject& DEMObject::operator-=(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); grid2D -= rhs.grid2D; update(); return *this; } const DEMObject DEMObject::operator-(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); DEMObject result(*this); result.grid2D -= rhs.grid2D; result.update(); return result; } DEMObject& DEMObject::operator*=(const double& rhs) { grid2D *= rhs; update(); return *this; } const DEMObject DEMObject::operator*(const double& rhs) { DEMObject result(*this); result.grid2D *= rhs; result.update(); return result; } DEMObject& DEMObject::operator*=(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); grid2D *= rhs.grid2D; update(); return *this; } const DEMObject DEMObject::operator*(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); DEMObject result(*this); result.grid2D *= rhs.grid2D; result.update(); return result; } DEMObject& DEMObject::operator/=(const double& rhs) { grid2D /= rhs; update(); return *this; } const DEMObject DEMObject::operator/(const double& rhs) { DEMObject result(*this); result.grid2D /= rhs; result.update(); return result; } DEMObject& DEMObject::operator/=(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); grid2D /= rhs.grid2D; update(); return *this; } const DEMObject DEMObject::operator/(const Grid2DObject& rhs) { if (!isSameGeolocalization(rhs)) throw InvalidArgumentException("[E] grids must have the same geolocalization in order to do arithmetic operations!", AT); DEMObject result(*this); result.grid2D /= rhs.grid2D; result.update(); return result; } bool DEMObject::operator==(const DEMObject& in) const { return (isSameGeolocalization(in) && grid2D==in.grid2D); } bool DEMObject::operator!=(const DEMObject& in) const { return !(*this==in); } } //end namespace