You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

444 lines
13 KiB
C++

1 month ago
/*------------------------------------------------------------------------------
* Copyright (c) 2023 by Bai Bing (seread@163.com)
* See COPYING file for copying and redistribution conditions.
*
* Alians IT Studio.
*----------------------------------------------------------------------------*/
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <execution>
#include <filesystem>
#include <functional>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <random>
#include <regex>
#include <set>
#include <stdexcept>
#include <thread>
#include <vector>
#include <SurfaceGrid.h>
#include "params.h"
#include "version.h"
// Target area
std::vector<ais::Point> build_area_by_concerns_points(double xMin, double yMin, double xMax, double yMax)
{
std::vector<ais::Point> points;
points.push_back(ais::Point(xMin, yMin));
points.push_back(ais::Point(xMin, yMax));
points.push_back(ais::Point(xMax, yMin));
points.push_back(ais::Point(xMax, yMax));
points.push_back(ais::Point(xMin, yMin));
return points;
}
std::vector<ais::Point> load_area_concerns(const char *filename)
{
std::vector<ais::Point> points;
std::ifstream ifs(filename);
if (!ifs)
{
std::cout << "Failed to opening file " << filename << std::endl;
std::cout << "Use sample points' coordinates value to established target area." << std::endl;
return points;
}
std::string line;
while (std::getline(ifs, line))
{
// skip version number and object name
if (line == "Version 2030" || line.find("Pline") != std::string::npos)
continue;
std::istringstream iss(line);
std::vector<std::string> tokens;
std::string token;
while (std::getline(iss, token, ','))
{
tokens.push_back(token);
}
if (!tokens.empty())
points.push_back(ais::Point(std::stod(tokens[0]), std::stod(tokens[1])));
}
return points;
}
using axisRange = std::pair<double, double>;
template <class PC>
std::pair<axisRange, axisRange> get_actually_area_concerns(const std::vector<PC> &points)
{
auto x = ais::getAxisValues(points, ais::Axis3DType::X);
auto y = ais::getAxisValues(points, ais::Axis3DType::Y);
auto xMinMax = ais::getMinMax(x);
auto yMinMax = ais::getMinMax(y);
return {xMinMax, yMinMax};
}
std::vector<ais::PointXYZ> drop_duplicated_points(std::vector<ais::PointXYZ> &points)
{
std::vector<ais::PointXYZ> result;
std::sort(points.begin(), points.end());
result.reserve(points.size());
for (auto &p : points)
{
if (std::find(std::execution::par, result.begin(), result.end(), p) == result.end())
result.push_back(p);
}
return result;
}
void dump_to_grd(ais::GridInterpolator &gi, const char *filename)
{
std::ofstream outfile;
outfile.open(filename, std::ios::out);
// header
outfile << "DSAA" << std::endl;
outfile << gi.header_info().c_str();
// data
outfile << gi.data_info().c_str();
outfile.close();
}
// Sample Points
std::vector<ais::PointXYZ> mock_sample_points(size_t count, std::vector<ais::Point> &areaConcerns)
{
auto x = ais::getAxisValues(areaConcerns, ais::Axis3DType::X);
auto y = ais::getAxisValues(areaConcerns, ais::Axis3DType::Y);
auto [xMin, xMax] = ais::getMinMax(x);
auto [yMin, yMax] = ais::getMinMax(y);
std::vector<ais::PointXYZ> points;
srand(time(nullptr));
for (int i = 0; i < count; i++)
{
double x = std::rand() / double(RAND_MAX) * (xMax - xMin) + xMin; // min~max
double y = std::rand() / double(RAND_MAX) * (yMax - yMin) + yMin; // min~max
double z = std::rand() / double(RAND_MAX) * count / 5; // 0.00 ~ count/5
ais::PointXYZ point(x, y, z);
points.push_back(point);
}
// write points to randoms.csv
std::ofstream outfile;
outfile.open("randoms.csv", std::ios::out);
outfile << "x,y,z" << std::endl;
for (auto &p : points)
{
outfile << p.x << "," << p.y << "," << p.z << std::endl;
}
outfile.close();
return points;
}
std::vector<ais::PointXYZ> load_sample_points(const char *filename)
{
std::vector<ais::PointXYZ> points;
std::ifstream infile;
infile.open(filename, std::ios::in);
if (!infile.is_open())
{
std::cout << "Failed to open sample points file: " << filename << std::endl;
exit(-1);
}
std::string line;
// show process
std::cout << "|";
while (std::getline(infile, line))
{
if (line[0] == '#' || line.empty())
continue;
ais::PointXYZ p(line.c_str());
if (!p.isNan())
{
points.push_back(p);
if (points.size() > 0 && (points.size() % 10000 == 0))
{
if(points.size() % 100000 == 0)
std::cout << "*";
else if (points.size() % 50000 == 0)
std::cout << "+";
else
std::cout << "-";
}
}
}
std::cout << "|" << std::endl;
std::cout << "Total points: " << points.size() << std::endl;
return points;
}
static ais::Settings settings;
void parse_args(int argc, char **argv)
{
// parse params
char c;
int optIndex = 0;
std::set<std::string> onParams = {"on", "1", "true", "yes"};
while (-1 != (c = ais::getopt_long_2(argc, argv, ais::shortopts, ais::longopts, &optIndex, &ais::optarg2)))
{
switch (c)
{
case 's':
settings.randomsFilename = ais::optarg2;
break;
case 't':
settings.concernPointsFilename = ais::optarg2;
break;
case 'c':
{
int count = atoi(ais::optarg2);
// only use mock random points when specified this option
settings.useMockRandoms = true;
settings.mockRandomCount = count ? count : 200; // default is 200
break;
}
case 'b':
settings.breaklinesFilename = ais::optarg2;
break;
case 'a':
settings.faultsFilename = ais::optarg2;
break;
case 'x':
settings.xCount = atoi(ais::optarg2);
break;
case 'y':
settings.yCount = atoi(ais::optarg2);
break;
case 'X':
settings.targetXGridSize = atof(ais::optarg2);
break;
case 'Y':
settings.targetYGridSize = atof(ais::optarg2);
break;
case 'i':
settings.maxIteration = atoi(ais::optarg2);
break;
case 'r':
settings.residual = atof(ais::optarg2);
break;
case 'f':
settings.fillValue = atof(ais::optarg2);
break;
case 'e':
settings.estimateFactor = atoi(ais::optarg2);
break;
case 'w':
{
// corner weight should between 4~256
int v = atoi(ais::optarg2);
if (v < 4)
v = 4;
if (v > 256)
v = 256;
settings.cornerWeight = v;
break;
}
case 'm':
{
std::string value = ais::optarg2;
value = ais::tolower(value);
settings.useMultiThread = onParams.find(value.c_str()) != onParams.end();
break;
}
case 'o':
{
settings.outputFilename = ais::optarg2;
if (settings.outputFilename.empty())
settings.outputFilename = "output.grd";
break;
}
case 'l':
{
// 0 add fault point with out any constrict, default is 0
// 1-4 add fault point with different constrict level, 1-less 4-more
auto faultEdgeLevel = (ais::optarg2 != NULL) ? atoi(ais::optarg2) : 0;
if (faultEdgeLevel < 0 || faultEdgeLevel > 4)
{
std::cout << "Invalid param, the fault edge level must between 0 and 4, default is 0" << std::endl;
exit(-1);
}
settings.faultEdgeLevel = faultEdgeLevel;
break;
}
case '?':
case 'h':
ais::usage(argv[0]);
exit(EXIT_SUCCESS);
case 'v':
std::cout << ais::VERSION << std::endl;
exit(EXIT_SUCCESS);
}
}
}
int main(int argc, char **argv)
{
auto pwd = std::filesystem::current_path();
std::cout << pwd << std::endl;
parse_args(argc, argv);
// specialized area points to limited target grid area
std::vector<ais::Point> areaPoints;
if (!settings.concernPointsFilename.empty())
{
areaPoints = load_area_concerns(settings.concernPointsFilename.c_str());
}
// load or mock simple points
std::vector<ais::PointXYZ> samplePoints;
if (settings.useMockRandoms)
{
std::cout << "Mock random points (" << settings.mockRandomCount << " points) in area (0, 0)~(10, 10)";
areaPoints = build_area_by_concerns_points(0, 0, 10.0, 10.0);
samplePoints = mock_sample_points(settings.mockRandomCount, areaPoints);
}
else
{
if (settings.randomsFilename.empty())
{
settings.randomsFilename = "data.csv";
}
std::cout << "Loading sample points from " << settings.randomsFilename << std::endl;
samplePoints = load_sample_points(settings.randomsFilename.c_str());
}
// build area points with actual points range or specific area
auto [xMinMax, yMinMax] = areaPoints.empty() ? get_actually_area_concerns(samplePoints) : get_actually_area_concerns(areaPoints);
areaPoints = build_area_by_concerns_points(xMinMax.first, yMinMax.first, xMinMax.second, yMinMax.second);
// drop duplicated and out-of-area points, remove drop_duplicated_points due to performance problem
// samplePoints = drop_duplicated_points(samplePoints);
ais::filter_points(areaPoints, samplePoints);
// adjust the target grid size by the specified grid size
if (!std::isnan(settings.targetXGridSize) && settings.targetXGridSize != 0.0)
{
size_t gridCount = std::ceil((xMinMax.second - xMinMax.first) / settings.targetXGridSize);
if (gridCount < 4)
{
std::cout << "X grid size is too big, can't build a valid grid" << std::endl;
exit(-1);
}
if (settings.xCount != gridCount + 1)
{
xMinMax.second = xMinMax.first + gridCount * settings.targetXGridSize;
areaPoints = build_area_by_concerns_points(xMinMax.first, yMinMax.first, xMinMax.second, yMinMax.second);
settings.xCount = gridCount + 1;
}
}
if (!std::isnan(settings.targetYGridSize) && settings.targetYGridSize != 0.0)
{
size_t gridCount = std::ceil((yMinMax.second - yMinMax.first) / settings.targetYGridSize);
if (gridCount < 4)
{
std::cout << "Y grid size is too big, can't build a valid grid" << std::endl;
exit(-1);
}
if (settings.yCount != gridCount + 1)
{
yMinMax.second = yMinMax.first + gridCount * settings.targetYGridSize;
areaPoints = build_area_by_concerns_points(xMinMax.first, yMinMax.first, xMinMax.second, yMinMax.second);
settings.yCount = gridCount + 1;
}
}
// target matrix, point count = grid count + 1
ais::Shape targetShape = {settings.yCount, settings.xCount};
std::shared_ptr<ais::FMatrix> target = std::make_shared<ais::FMatrix>(targetShape);
// fill target matrix with nan values for next iteration
target->zeros();
// load breaklines from file
std::vector<ais::BreakLine> breaklines;
if (settings.breaklinesFilename.length() > 0)
{
ais::BreakLineFile blf(settings.breaklinesFilename.c_str());
for (auto &kv : blf.lines)
{
breaklines.emplace_back(kv.second);
}
}
// load faults from file
std::vector<ais::Fault> faults;
if (settings.faultsFilename.length() > 0)
{
ais::FaultFile ff(settings.faultsFilename.c_str());
for (auto &kv : ff.faults)
{
faults.emplace_back(kv.second);
}
}
// build Minimum-Curvature interpolation grid
ais::GridInterpolator gi(samplePoints, areaPoints, target, breaklines, faults);
gi.update_grid_params(settings.params());
std::cout << gi.params_info();
std::cout << gi.dump_data_statistics();
// start grid intersection from working thread
auto gs = [&gi]()
{ gi.start(); };
std::thread tWorker(gs);
// start output from other thread
auto gOutput = [&gi]()
{
do
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string msg = gi.get_progress_msg();
if (!msg.empty())
{
std::cout << msg;
}
} while (!gi.is_end());
};
std::thread tOutput(gOutput);
tWorker.join();
tOutput.join();
std::cout << "Interpolated result: " << std::endl;
std::cout << "---------------------------------------" << std::endl;
std::cout << gi.report();
std::cout << "---------------------------------------" << std::endl;
std::cout << "Output file: " << settings.outputFilename << std::endl;
dump_to_grd(gi, settings.outputFilename.c_str());
return 0;
}