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++
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;
|
||
|
|
}
|