Skip to content
Snippets Groups Projects
Forked from ogs / ogs
876 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
AsciiRasterInterface.cpp 10.43 KiB
/**
 * \file
 * \author Karsten Rink
 * \date 2014-09-10
 * \brief Implementation of the AsciiRasterInterface class.
 *
 * \copyright
 * Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org)
 *            Distributed under a Modified BSD License.
 *              See accompanying file LICENSE.txt or
 *              http://www.opengeosys.org/project/license
 */

#include "AsciiRasterInterface.h"

#include <fstream>
#include <tuple>

#include "BaseLib/FileTools.h"
#include "BaseLib/Logging.h"
#include "BaseLib/StringTools.h"
#include "GeoLib/Point.h"

namespace FileIO
{
GeoLib::Raster* AsciiRasterInterface::readRaster(std::string const& fname)
{
    std::string ext(BaseLib::getFileExtension(fname));
    std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
    if (ext == ".asc")
    {
        return getRasterFromASCFile(fname);
    }
    if (ext == ".grd")
    {
        return getRasterFromSurferFile(fname);
    }
    if (ext == ".xyz")
    {
        return getRasterFromXyzFile(fname);
    }
    return nullptr;
}

/// Reads a double replacing comma by point.
static double readDoubleFromStream(std::istream& in)
{
    std::string value;
    in >> value;
    return std::strtod(BaseLib::replaceString(",", ".", value).c_str(),
                       nullptr);
}

/// Reads the header of a Esri asc-file.
/// If the return value is empty, reading was not successful.
static std::optional<GeoLib::RasterHeader> readASCHeader(std::ifstream& in)
{
    GeoLib::RasterHeader header;

    std::string tag;
    std::string value;

    in >> tag;
    if (tag == "ncols")
    {
        in >> value;
        header.n_cols = atoi(value.c_str());
    }
    else
    {
        return {};
    }

    in >> tag;
    if (tag == "nrows")
    {
        in >> value;
        header.n_rows = atoi(value.c_str());
    }
    else
    {
        return {};
    }

    header.n_depth = 1;

    in >> tag;
    if (tag == "xllcorner" || tag == "xllcenter")
    {
        header.origin[0] = readDoubleFromStream(in);
    }
    else
    {
        return {};
    }

    in >> tag;
    if (tag == "yllcorner" || tag == "yllcenter")
    {
        header.origin[1] = readDoubleFromStream(in);
    }
    else
    {
        return {};
    }
    header.origin[2] = 0;

    in >> tag;
    if (tag == "cellsize")
    {
        header.cell_size = readDoubleFromStream(in);
    }
    else
    {
        return {};
    }

    in >> tag;
    if (tag == "NODATA_value" || tag == "nodata_value")
    {
        header.no_data = readDoubleFromStream(in);
    }
    else
    {
        return {};
    }

    return header;
}

GeoLib::Raster* AsciiRasterInterface::getRasterFromASCFile(
    std::string const& fname)
{
    std::ifstream in(fname.c_str());

    if (!in.is_open())
    {
        WARN("Raster::getRasterFromASCFile(): Could not open file {:s}.",
             fname);
        return nullptr;
    }

    auto const header = readASCHeader(in);
    if (!header)
    {
        WARN(
            "Raster::getRasterFromASCFile(): Could not read header of file "
            "{:s}",
            fname);
        return nullptr;
    }

    std::vector<double> values(header->n_cols * header->n_rows);
    // read the data into the double-array
    for (std::size_t j(0); j < header->n_rows; ++j)
    {
        const std::size_t idx((header->n_rows - j - 1) * header->n_cols);
        for (std::size_t i(0); i < header->n_cols; ++i)
        {
            values[idx + i] = readDoubleFromStream(in);
        }
    }

    return new GeoLib::Raster(*header, values.begin(), values.end());
}

/// Reads the header of a Surfer grd-file with minimum and maximum values.
/// If the return value is empty, reading was not successful.
static std::optional<std::tuple<GeoLib::RasterHeader, double, double>>
readSurferHeader(std::ifstream& in)
{
    std::string tag;

    in >> tag;

    if (tag != "DSAA")
    {
        ERR("Error in readSurferHeader() - No Surfer file.");
        return {};
    }

    GeoLib::RasterHeader header;
    in >> header.n_cols >> header.n_rows;
    double min, max;
    in >> min >> max;
    header.origin[0] = min;
    header.cell_size = (max - min) / static_cast<double>(header.n_cols);

    in >> min >> max;
    header.origin[1] = min;
    header.origin[2] = 0;

    if (ceil((max - min) / static_cast<double>(header.n_rows)) ==
        ceil(header.cell_size))
    {
        header.cell_size = ceil(header.cell_size);
    }
    else
    {
        ERR("Error in readSurferHeader() - Anisotropic cellsize detected.");
        return {};
    }
    header.n_depth = 1;
    header.no_data = -9999;
    in >> min >> max;

    return {{header, min, max}};
}

GeoLib::Raster* AsciiRasterInterface::getRasterFromSurferFile(
    std::string const& fname)
{
    std::ifstream in(fname.c_str());

    if (!in.is_open())
    {
        ERR("Raster::getRasterFromSurferFile() - Could not open file {:s}",
            fname);
        return nullptr;
    }

    auto const optional_header = readSurferHeader(in);
    if (!optional_header)
    {
        ERR("Raster::getRasterFromASCFile() - could not read header of file "
            "{:s}",
            fname);
        return nullptr;
    }

    auto const [header, min, max] = *optional_header;
    std::vector<double> values(header.n_cols * header.n_rows);
    // read the data into the double-array
    for (std::size_t j(0); j < header.n_rows; ++j)
    {
        const std::size_t idx(j * header.n_cols);
        for (std::size_t i(0); i < header.n_cols; ++i)
        {
            const double val = readDoubleFromStream(in);
            values[idx + i] = (val > max || val < min) ? header.no_data : val;
        }
    }

    return new GeoLib::Raster(header, values.begin(), values.end());
}

std::vector<std::string> readFile(std::istream& in)
{
    std::vector<std::string> lines;
    std::string line("");
    while (std::getline(in, line))
    {
        lines.emplace_back(line);
    }
    return lines;
}

std::optional<std::array<double, 3>> readCoordinates(std::string const& line)
{
    std::array<double, 3> coords;
    if (std::sscanf(line.c_str(), "%lf %lf %lf", &coords[0], &coords[1],
                    &coords[2]) == 3)
    {
        return coords;
    }
    else
    {
        ERR("Raster::getRasterFromXyzFile() - Unexpected file format:\n{:s}, line");
        return std::nullopt;
    }
}

GeoLib::Raster* AsciiRasterInterface::getRasterFromXyzFile(
    std::string const& fname)
{
    std::ifstream in(fname.c_str());
    if (!in.is_open())
    {
        ERR("Raster::getRasterFromXyzFile() - Could not open file {:s}", fname);
        return nullptr;
    }

    auto const string_lines = readFile(in);
    in.close();

    auto coords = readCoordinates(string_lines[0]);
    if (coords == std::nullopt)
    {
        return nullptr;
    }
    double x_min = (*coords)[0];
    double x_max = (*coords)[0];
    double y_min = (*coords)[1];
    double y_max = (*coords)[1];
    double cellsize = std::numeric_limits<double>::max();

    std::size_t const n_lines(string_lines.size());
    for (std::size_t i = 1; i < n_lines; ++i)
    {
        coords = readCoordinates(string_lines[i]);
        if (coords == std::nullopt)
        {
            return nullptr;
        }
        double const diff = (*coords)[0] - x_min;
        if (diff > 0)
        {
            cellsize = std::min(cellsize, diff);
        }
        x_min = std::min((*coords)[0], x_min);
        x_max = std::max((*coords)[0], x_max);
        y_min = std::min((*coords)[1], y_min);
        y_max = std::max((*coords)[1], y_max);
    }

    GeoLib::RasterHeader header;
    header.cell_size = cellsize;
    header.no_data = -9999;
    header.n_cols = static_cast<std::size_t>(((x_max - x_min) / cellsize) + 1);
    header.n_rows = static_cast<std::size_t>(((y_max - y_min) / cellsize) + 1);
    header.n_depth = 1;
    header.origin[0] = x_min;
    header.origin[1] = y_min;

    std::vector<double> values(header.n_cols * header.n_rows, -9999);
    for (std::size_t i = 0; i < n_lines; ++i)
    {
        coords = readCoordinates(string_lines[i]);
        std::size_t const idx = static_cast<std::size_t>(
            (header.n_cols * (((*coords)[1] - y_min) / cellsize)) +
            (((*coords)[0] - x_min) / cellsize));
        values[idx] = (*coords)[2];
    }
    return new GeoLib::Raster(header, values.begin(), values.end());
}

void AsciiRasterInterface::writeRasterAsASC(GeoLib::Raster const& raster,
                                            std::string const& file_name)
{
    GeoLib::RasterHeader header(raster.getHeader());
    MathLib::Point3d const& origin(header.origin);
    unsigned const nCols(header.n_cols);
    unsigned const nRows(header.n_rows);

    // write header
    std::ofstream out(file_name);
    out << "ncols " << nCols << "\n";
    out << "nrows " << nRows << "\n";
    auto const default_precision = out.precision();
    out.precision(std::numeric_limits<double>::digits10);
    out << "xllcorner " << origin[0] << "\n";
    out << "yllcorner " << origin[1] << "\n";
    out << "cellsize " << header.cell_size << "\n";
    out.precision(default_precision);
    out << "NODATA_value " << header.no_data << "\n";

    // write data
    for (unsigned row(0); row < nRows; ++row)
    {
        for (unsigned col(0); col < nCols - 1; ++col)
        {
            out << raster.data()[(nRows - row - 1) * nCols + col] << " ";
        }
        out << raster.data()[(nRows - row) * nCols - 1] << "\n";
    }
    out.close();
}

/// Checks if all raster files actually exist
static bool allRastersExist(std::vector<std::string> const& raster_paths)
{
    return std::all_of(raster_paths.begin(), raster_paths.end(),
                       [](std::string const& raster_path)
                       {
                           if (BaseLib::IsFileExisting(raster_path))
                           {
                               return true;
                           }
                           ERR("Opening raster file {} failed.", raster_path);
                           return false;
                       });
}

std::optional<std::vector<GeoLib::Raster const*>> readRasters(
    std::vector<std::string> const& raster_paths)
{
    if (!allRastersExist(raster_paths))
    {
        return std::nullopt;
    }

    std::vector<GeoLib::Raster const*> rasters;
    rasters.reserve(raster_paths.size());
    std::transform(raster_paths.begin(), raster_paths.end(),
                   std::back_inserter(rasters),
                   [](auto const& path)
                   { return FileIO::AsciiRasterInterface::readRaster(path); });
    return std::make_optional(rasters);
}
}  // end namespace FileIO