static inline double Terrain_Slope_RiseRun(const Array2D<T> &elevations, const int x, const int y, const float zscale){
  const auto tsv = TerrainSetup(elevations,x,y,zscale);

  //See p. 18 of Horn (1981)
  double dzdx = ( (tsv.c+2*tsv.f+tsv.i) - (tsv.a+2*tsv.d+tsv.g) ) / 8 / elevations.getCellLengthX();
  double dzdy = ( (tsv.g+2*tsv.h+tsv.i) - (tsv.a+2*tsv.b+tsv.c) ) / 8 / elevations.getCellLengthY();

  //The above fits are surface to a 3x3 neighbour hood. This returns the slope
  //along the direction of maximum gradient.
  return sqrt(dzdx*dzdx+dzdy*dzdy);
}
static inline double Terrain_Aspect(const Array2D<T> &elevations, const int x, const int y, const float zscale){
  const auto tsv = TerrainSetup(elevations,x,y,zscale);

  //See p. 18 of Horn (1981)
  double dzdx       = ( (tsv.c+2*tsv.f+tsv.i) - (tsv.a+2*tsv.d+tsv.g) ) / 8 / elevations.getCellLengthX();
  double dzdy       = ( (tsv.g+2*tsv.h+tsv.i) - (tsv.a+2*tsv.b+tsv.c) ) / 8 / elevations.getCellLengthY();
  double the_aspect = 180.0/M_PI*atan2(dzdy,-dzdx);
  if(the_aspect<0)
    return 90-the_aspect;
  else if(the_aspect>90.0)
    return 360.0-the_aspect+90.0;
  else
    return 90.0-the_aspect;
}
static inline void TerrainProcessor(F func, const Array2D<T> &elevations, const float zscale, Array2D<float> &output){
  if(elevations.getCellLengthX()!=elevations.getCellLengthY())
    RDLOG_WARN<<"Cell X and Y dimensions are not equal!";

  output.resize(elevations);
  ProgressBar progress;

  progress.start(elevations.size());
  #pragma omp parallel for
  for(int y=0;y<elevations.height();y++){
    progress.update(y*elevations.width());
    for(int x=0;x<elevations.width();x++)
      if(elevations.isNoData(x,y))
        output(x,y) = output.noData();
      else
        output(x,y) = func(elevations,x,y,zscale);
  }
  RDLOG_TIME_USE<<"Wall-time = "<<progress.stop();
}