update to tin library

This commit is contained in:
张壹 2021-10-14 21:38:45 +08:00
parent f06d009807
commit 0b52bae14e
15 changed files with 528 additions and 714 deletions

2
.gitignore vendored
View File

@ -32,3 +32,5 @@
*.app
.DS_Store
backup/
build/

14
CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.15.2)
#
project(LibTIN VERSION 1.0 LANGUAGES CXX)
#
include(CMakePackageConfigHelpers)
message(STATUS "Platform: " ${CMAKE_HOST_SYSTEM_NAME})
# CMake WindowsC:/Program\ Files/${Project_Name} Linux/Unix/usr/local
message(STATUS "Install prefix: " ${CMAKE_INSTALL_PREFIX})
# CMake
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
#
add_subdirectory(src/)

15
LibTINConfig.cmake.in Normal file
View File

@ -0,0 +1,15 @@
@PACKAGE_INIT@
set(@PROJECT_NAME@_Version "@PROJECT_VERSION@")
set_and_check(@PROJECT_NAME@_INSTALL_PREFIX "${PACKAGE_PREFIX_DIR}")
set_and_check(@PROJECT_NAME@_INC_DIR "${PACKAGE_PREFIX_DIR}/include")
set_and_check(@PROJECT_NAME@_INCULDE_DIR "${PACKAGE_PREFIX_DIR}/include")
set_and_check(@PROJECT_NAME@_LIB_DIR "${PACKAGE_PREFIX_DIR}/lib")
set_and_check(@PROJECT_NAME@_LIBRARY_DIR "${PACKAGE_PREFIX_DIR}/lib")
set(@PROJECT_NAME@_LIB tin)
set(@PROJECT_NAME@_LIBRARY tin)
set(@PROJECT_NAME@_FOUND 1)
# include target information
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")

View File

146
demo.cpp
View File

@ -1,146 +0,0 @@
#include "tin.h"
#include "iostream"
#include "fstream"
#include "iomanip"
#include "gctl/utility.h"
#define GRID_FILE "grid-file|grid_file"
#define GRID_PARA "grid-para|grid_para"
#define MAXI_ERR "maxi-error|maxi_error"
#define LOG_FILE "log-file|log_file"
#define NEIGH_FILE "neighbor-file|neighbor_file|neigh-file|neigh_file"
#define SINGLE_Z "single-z"
int main(int argc, char const *argv[])
{
if (argc < 2)
{
std::cerr << "Usage: ./tin <config-file>\n";
std::cerr << "Options:\n";
std::cerr << "grid-file = <filename>\n";
std::cerr << "grid-para = <xmin>/<dx>/<xmax>/<ymin>/<dy>/<ymax>\n";
std::cerr << "maxi-error = <value > 0>\n";
std::cerr << "log-file = <filename> (optional)\n";
std::cerr << "neighbor-file = <filename> (optional)\n";
std::cerr << "single-z = yes|no\n";
return -1;
}
gctl::getoption gopt(argv[1]);
gopt.show_options();
// read dem grid
double xmin, xmax, ymin, ymax, dx, dy;
gctl::parse_string_to_value(gopt.get_value(GRID_PARA), '/', xmin, dx, xmax, ymin, dy, ymax);
int xnum = round((xmax - xmin)/dx) + 1;
int ynum = round((ymax - ymin)/dy) + 1;
std::vector<double> topo(xnum*ynum);
double tmp_d;
std::ifstream infile(gopt.get_value(GRID_FILE));
if (gopt.get_value(SINGLE_Z, false) != "NullValue")
{
for (int i = 0; i < xnum*ynum; ++i)
{
infile >> topo[i];
}
infile.close();
}
else
{
for (int i = 0; i < xnum*ynum; ++i)
{
infile >> tmp_d >> tmp_d >> topo[i];
}
infile.close();
}
double maxi_err = std::atof(gopt.get_value(MAXI_ERR).c_str());
std::vector<double> err_records;
std::vector<vertex2dc*> tin_vert;
std::vector<triangle*> tin_ele;
dem2tin(topo, xmin, xmax, ymin, ymax, dx, dy, tin_vert, tin_ele, maxi_err, &err_records);
// Write a Gmsh's .msh file
std::ofstream outfile(gopt.get_value(GRID_FILE)+".msh");
outfile << "$MeshFormat" << std::endl << "2.2 0 8" << std::endl << "$EndMeshFormat "<<std::endl;
outfile << "$Nodes" << std::endl << tin_vert.size() << std::endl;
for (int i = 0; i < tin_vert.size(); i++)
{
outfile << tin_vert[i]->id + 1 << " " << std::setprecision(16)
<< tin_vert[i]->x << " " << tin_vert[i]->y << " " << tin_vert[i]->elev << std::endl;
}
outfile<<"$EndNodes"<<std::endl;
outfile << "$Elements" << std::endl << tin_ele.size() <<std::endl;
for (int i = 0; i < tin_ele.size(); i++)
{
outfile << i + 1 << " 2 0";
for (int j = 0; j < 3; j++)
{
outfile << " " << tin_ele[i]->vert[j]->id + 1;
}
outfile << std::endl;
}
outfile << "$EndElements"<< std::endl;
outfile<<"$NodeData"<<std::endl;
outfile<<1<<std::endl
<<"\"Topography (m)\"" <<std::endl
<< 1 <<std::endl<< 0.0 <<std::endl
<< 3 <<std::endl<< 0<<std::endl
<< 1 <<std::endl<< tin_vert.size() <<std::endl;
for (int i = 0; i < tin_vert.size(); i++)
{
outfile << tin_vert[i]->id + 1 << " " << std::setprecision(16) << tin_vert[i]->elev << std::endl;
}
outfile << "$EndNodeData" << std::endl;
outfile.close();
// write a neighbor file
std::string neigh_name = gopt.get_value(NEIGH_FILE, false);
if (neigh_name != "NullValue")
{
outfile.open(neigh_name+".neigh");
outfile << tin_ele.size() << std::endl;
for (int i = 0; i < tin_ele.size(); i++)
{
outfile << i + 1;
for (int j = 0; j < 3; j++)
{
if (tin_ele[i]->neigh[j] != nullptr)
{
outfile << " " << tin_ele[i]->neigh[j]->id + 1;
}
else outfile << " -1";
}
outfile << std::endl;
}
outfile.close();
}
// Write a log file
std::string log_name = gopt.get_value(LOG_FILE, false);
if (log_name != "NullValue")
{
std::ofstream logfile(log_name+".log");
logfile << "# Insertion Maxi-Error\n";
for (int i = 0; i < err_records.size(); ++i)
{
logfile << i+1 << " " << err_records[i] << std::endl;
}
logfile.close();
}
// Destroy memories allocated by the dem2tin function
for (int i = 0; i < tin_vert.size(); ++i)
{
delete tin_vert[i];
}
for (int i = 0; i < tin_ele.size(); ++i)
{
delete tin_ele[i];
}
return 0;
}

View File

@ -1,6 +0,0 @@
grid-file = topo
grid-para = 0/10/1000/0/10/1000
maxi-error = 1.0
log-file = job
neighbor-file = topo
single-z = yes

68
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,68 @@
#
aux_source_directory(lib LIBTIN_SRC)
#
#
# libcmake
add_library(tin SHARED ${LIBTIN_SRC})
#
add_library(tin_static STATIC ${LIBTIN_SRC})
#
set_target_properties(tin_static PROPERTIES OUTPUT_NAME "tin")
#
set_target_properties(tin PROPERTIES CLEAN_DIRECT_OUTPUT 1)
set_target_properties(tin_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
#
set_target_properties(tin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR})
#
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
#
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
set(CONFIG_FILE_PATH lib/cmake/${PROJECT_NAME})
configure_package_config_file(${PROJECT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in
${CMAKE_BINARY_DIR}/${PROJECT_NAME}Config.cmake
INSTALL_DESTINATION ${CONFIG_FILE_PATH}
NO_CHECK_REQUIRED_COMPONENTS_MACRO)
write_basic_package_version_file(${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
#
if(WIN32)
install(TARGETS tin DESTINATION lib)
install(TARGETS tin_static DESTINATION lib)
else()
install(TARGETS tin tin_static
EXPORT ${PROJECT_NAME}Targets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
install(EXPORT ${PROJECT_NAME}Targets
DESTINATION ${CONFIG_FILE_PATH})
install(FILES
${CMAKE_BINARY_DIR}/${PROJECT_NAME}Config.cmake
${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
DESTINATION ${CONFIG_FILE_PATH})
endif()
#
install(FILES lib/tin.h DESTINATION include/tin)
#
#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
#
macro(add_demo name)
#
add_executable(${name} demo/${name}.cpp)
# Windows
set_target_properties(${name} PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib)
#
target_link_libraries(${name} PUBLIC tin)
endmacro()
add_demo(demo)

94
src/demo/demo.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "../lib/tin.h"
#include "iostream"
#include "fstream"
#include "iomanip"
int main(int argc, char const *argv[])
{
// read dem grid
std::vector<double> topo(10201);
std::ifstream infile("data/topo");
for (int i = 0; i < 10201; ++i)
{
infile >> topo[i];
}
infile.close();
std::vector<double> err_records;
std::vector<vertex2dc*> tin_vert;
std::vector<triangle*> tin_ele;
dem2tin(topo, 0, 1000, 0, 1000, 10, 10, tin_vert, tin_ele, 1.0, &err_records);
// Write a log file
std::ofstream logfile("data/topo_TIN.log");
logfile << "# Insertion Maxi-Error\n";
for (int i = 0; i < err_records.size(); ++i)
{
logfile << i+1 << " " << err_records[i] << std::endl;
}
logfile.close();
// Write a Gmsh's .msh file
std::ofstream outfile("data/topo_TIN.msh");
outfile << "$MeshFormat" << std::endl << "2.2 0 8" << std::endl << "$EndMeshFormat "<<std::endl;
outfile << "$Nodes" << std::endl << tin_vert.size() << std::endl;
for (int i = 0; i < tin_vert.size(); i++)
{
outfile << tin_vert[i]->id + 1 << " " << std::setprecision(16)
<< tin_vert[i]->x << " " << tin_vert[i]->y << " " << tin_vert[i]->elev << std::endl;
}
outfile<<"$EndNodes"<<std::endl;
outfile << "$Elements" << std::endl << tin_ele.size() <<std::endl;
for (int i = 0; i < tin_ele.size(); i++)
{
outfile << i + 1 << " 2 0";
for (int j = 0; j < 3; j++)
{
outfile << " " << tin_ele[i]->vert[j]->id + 1;
}
outfile << std::endl;
}
outfile << "$EndElements"<< std::endl;
outfile<<"$NodeData"<<std::endl;
outfile<<1<<std::endl
<<"\"Topography (m)\"" <<std::endl
<< 1 <<std::endl<< 0.0 <<std::endl
<< 3 <<std::endl<< 0<<std::endl
<< 1 <<std::endl<< tin_vert.size() <<std::endl;
for (int i = 0; i < tin_vert.size(); i++)
{
outfile << tin_vert[i]->id + 1 << " " << std::setprecision(16) << tin_vert[i]->elev << std::endl;
}
outfile << "$EndNodeData" << std::endl;
outfile.close();
// write a neighbor file
outfile.open("data/topo_TIN.neigh");
outfile << tin_ele.size() << std::endl;
for (int i = 0; i < tin_ele.size(); i++)
{
outfile << i + 1;
for (int j = 0; j < 3; j++)
{
if (tin_ele[i]->neigh[j] != nullptr)
{
outfile << " " << tin_ele[i]->neigh[j]->id + 1;
}
else outfile << " -1";
}
outfile << std::endl;
}
outfile.close();
// Destroy memories allocated by the dem2tin function
for (int i = 0; i < tin_vert.size(); ++i)
{
delete tin_vert[i];
}
for (int i = 0; i < tin_ele.size(); ++i)
{
delete tin_ele[i];
}
return 0;
}

View File

@ -1,37 +1,56 @@
/**
* @defgroup TIN
* ___________ __
* /_ __/ _/ | / /
* / / / // |/ /
* / / _/ // /| /
* /_/ /___/_/ |_/
*
* @brief Generation of a Triangular Irregular Network (TIN) from a dense DEM grid.
* C++ library of the Triangular Irregular Network (TIN)
*
* @author Yi Zhang
* @date 2021-09-16
* Copyright (c) 2021-2031 Yi Zhang (zhangyiss@icloud.com)
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef _TIN_DELAUNAY_H
#define _TIN_DELAUNAY_H
#include "cmath"
#include "vector"
#include "algorithm"
#define ZERO 1e-5
#include "tin.h"
// Start vertex definition
struct vertex2dc
vertex2dc::vertex2dc()
{
unsigned int id; // index of the vertex
double x, y; // position of the vertex
double elev; // elevation at the vertex
x = y = elev = NAN;
id = 0;
}
vertex2dc() : x(NAN), y(NAN), elev(NAN), id(0) {}
vertex2dc(double inx, double iny, double inelev, unsigned int inid) {set(inx, iny, inelev, inid);}
void set(double inx, double iny, double inelev, unsigned int inid)
{
x = inx; y = iny; elev = inelev; id = inid;
return;
}
};
vertex2dc::vertex2dc(double inx, double iny, double inelev, unsigned int inid)
{
set(inx, iny, inelev, inid);
}
bool operator ==(const vertex2dc &a, const vertex2dc &b) // overload the == operator for vertex2dc type
void vertex2dc::set(double inx, double iny, double inelev, unsigned int inid)
{
x = inx; y = iny; elev = inelev; id = inid;
return;
}
// overload the == operator for vertex2dc type
bool operator ==(const vertex2dc &a, const vertex2dc &b)
{
if(fabs(a.x - b.x) <= ZERO && fabs(a.y - b.y) <= ZERO)
{
@ -40,7 +59,8 @@ bool operator ==(const vertex2dc &a, const vertex2dc &b) // overload the == oper
return false;
}
bool is_collinear(vertex2dc *a_ptr, vertex2dc *b_ptr, vertex2dc *c_ptr) // Test if the three points are on the same line
// Test if the three points are on the same line
bool is_collinear(vertex2dc *a_ptr, vertex2dc *b_ptr, vertex2dc *c_ptr)
{
// (y3y1)(x2x1)(y2y1)(x3x1)
if (fabs((c_ptr->y - a_ptr->y)*(b_ptr->x - a_ptr->x) - (b_ptr->y - a_ptr->y)*(c_ptr->x - a_ptr->x)) <= ZERO)
@ -50,7 +70,8 @@ bool is_collinear(vertex2dc *a_ptr, vertex2dc *b_ptr, vertex2dc *c_ptr) // Test
return false;
}
void circumcircle(vertex2dc *v0, vertex2dc *v1, vertex2dc *v2, double &cx, double &cy, double &cr) // calculate the circumcircle from three points
// calculate the circumcircle from three points
void circumcircle(vertex2dc *v0, vertex2dc *v1, vertex2dc *v2, double &cx, double &cy, double &cr)
{
double s = 0.5 / ((v1->x - v0->x) * (v2->y - v0->y) - (v1->y - v0->y) * (v2->x - v0->x));
double m = v1->x*v1->x - v0->x*v0->x + v1->y*v1->y - v0->y*v0->y;
@ -64,23 +85,23 @@ void circumcircle(vertex2dc *v0, vertex2dc *v1, vertex2dc *v2, double &cx, doubl
// End vertex definition
// Start DEM definition
struct triangle;
struct dem_point
dem_point::dem_point()
{
double x, y; // position of the DEM location
double elev; // elevation at the DEM location
double err; // error of the TIN with respect to the elevation
triangle *host;
x = y = elev = NAN;
err = 0.0;
host = nullptr;
}
dem_point() : x(NAN), y(NAN), elev(NAN), err(0.0), host(nullptr) {}
dem_point(double inx, double iny, double inelev) {set(inx, iny, inelev);}
void set(double inx, double iny, double inelev)
{
x = inx; y = iny; elev = inelev; err = 0.0; host = nullptr;
return;
}
};
dem_point::dem_point(double inx, double iny, double inelev)
{
set(inx, iny, inelev);
}
void dem_point::set(double inx, double iny, double inelev)
{
x = inx; y = iny; elev = inelev; err = 0.0; host = nullptr;
return;
}
bool compare_dem_point(dem_point *a, dem_point *b) // determination function for std::sort
{
@ -89,66 +110,59 @@ bool compare_dem_point(dem_point *a, dem_point *b) // determination function for
}
// End DEM definition
/* Start triangle definition
* v2
* /\
* / \
* n2 / \ n1
* / \
* /------------\
* v0 n0 v1
*/
struct triangle
// Start triangle definition
triangle::triangle()
{
int id;
vertex2dc *vert[3]; // vertex of the triangle
triangle *neigh[3]; // neighbors of the triangle
double cx, cy; // center of the triangle's circumcircle
double cr; // radius of the circumcircle
std::vector<dem_point*> hosted_dem;
vert[0] = vert[1] = vert[2] = nullptr;
neigh[0] = neigh[1] = neigh[2] = nullptr;
}
triangle() {vert[0] = vert[1] = vert[2] = nullptr; neigh[0] = neigh[1] = neigh[2] = nullptr;}
triangle(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr) {set(v0ptr, v1ptr, v2ptr);}
void set(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr)
{
vert[0] = v0ptr; vert[1] = v1ptr; vert[2] = v2ptr;
neigh[0] = neigh[1] = neigh[2] = nullptr;
circumcircle(vert[0], vert[1], vert[2], cx, cy, cr);
return;
}
triangle::triangle(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr)
{
set(v0ptr, v1ptr, v2ptr);
}
void set_neighbor(triangle *n0ptr, triangle *n1ptr, triangle *n2ptr)
{
neigh[0] = n0ptr; neigh[1] = n1ptr; neigh[2] = n2ptr;
return;
}
void triangle::set(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr)
{
vert[0] = v0ptr; vert[1] = v1ptr; vert[2] = v2ptr;
neigh[0] = neigh[1] = neigh[2] = nullptr;
circumcircle(vert[0], vert[1], vert[2], cx, cy, cr);
return;
}
bool bound_location(double inx, double iny) // Test if the location is inside the triangle
{
double l1x, l1y, l2x, l2y;
for (int i = 0; i < 3; i++)
{
l1x = vert[(i+1)%3]->x - vert[i]->x;
l1y = vert[(i+1)%3]->y - vert[i]->y;
l2x = inx - vert[i]->x;
l2y = iny - vert[i]->y;
void triangle::set_neighbor(triangle *n0ptr, triangle *n1ptr, triangle *n2ptr)
{
neigh[0] = n0ptr; neigh[1] = n1ptr; neigh[2] = n2ptr;
return;
}
if ((l1x*l2y - l1y*l2x) < 0) // This condition includes points on the triangle's edge
{
return false;
}
}
return true;
}
// Test if the location is inside the triangle
bool triangle::bound_location(double inx, double iny)
{
double l1x, l1y, l2x, l2y;
for (int i = 0; i < 3; i++)
{
l1x = vert[(i+1)%3]->x - vert[i]->x;
l1y = vert[(i+1)%3]->y - vert[i]->y;
l2x = inx - vert[i]->x;
l2y = iny - vert[i]->y;
double interpolate(double inx, double iny) // Interpolate the elevation of the given location inside the triangle
{
double a1 = 0.5 * ((vert[1]->x - inx)*(vert[2]->y - iny) - (vert[1]->y - iny)*(vert[2]->x - inx));
double a2 = 0.5 * ((vert[2]->x - inx)*(vert[0]->y - iny) - (vert[2]->y - iny)*(vert[0]->x - inx));
double a3 = 0.5 * ((vert[0]->x - inx)*(vert[1]->y - iny) - (vert[0]->y - iny)*(vert[1]->x - inx));
return (a1*vert[0]->elev + a2*vert[1]->elev + a3*vert[2]->elev)/(a1 + a2 + a3);
}
};
if ((l1x*l2y - l1y*l2x) < 0) // This condition includes points on the triangle's edge
{
return false;
}
}
return true;
}
// Interpolate the elevation of the given location inside the triangle
double triangle::interpolate(double inx, double iny)
{
double a1 = 0.5 * ((vert[1]->x - inx)*(vert[2]->y - iny) - (vert[1]->y - iny)*(vert[2]->x - inx));
double a2 = 0.5 * ((vert[2]->x - inx)*(vert[0]->y - iny) - (vert[2]->y - iny)*(vert[0]->x - inx));
double a3 = 0.5 * ((vert[0]->x - inx)*(vert[1]->y - iny) - (vert[0]->y - iny)*(vert[1]->x - inx));
return (a1*vert[0]->elev + a2*vert[1]->elev + a3*vert[2]->elev)/(a1 + a2 + a3);
}
/**
* @brief Flip neighboring triangles and their neighbors
@ -447,9 +461,8 @@ triangle *split_triangle(vertex2dc *v, triangle *t, triangle *new_t[4])
* @param[in] maxi_err Threshold to quit the algorithm. The default is 1e-0
* @param[in] err_records If this pointer is not NULL, record maximal error values after each insertion of vertex.
*/
void dem2tin(const std::vector<double> &dem, double xmin, double xmax, double ymin, double ymax,
double dx, double dy, std::vector<vertex2dc*> &out_verts, std::vector<triangle*> &out_tris,
double maxi_err = 1e-0, std::vector<double> *err_records = nullptr)
void dem2tin(const std::vector<double> &dem, double xmin, double xmax, double ymin, double ymax, double dx,
double dy, std::vector<vertex2dc*> &out_verts, std::vector<triangle*> &out_tris, double maxi_err, std::vector<double> *err_records)
{
if (!out_verts.empty()) out_verts.clear();
if (!out_tris.empty()) out_tris.clear();
@ -712,5 +725,3 @@ void dem2tin(const std::vector<double> &dem, double xmin, double xmax, double ym
}
return;
}
#endif // _TIN_DELAUNAY_H

223
src/lib/tin.h Normal file
View File

@ -0,0 +1,223 @@
/**
* ___________ __
* /_ __/ _/ | / /
* / / / // |/ /
* / / _/ // /| /
* /_/ /___/_/ |_/
*
* C++ library of the Triangular Irregular Network (TIN)
*
* Copyright (c) 2021-2031 Yi Zhang (zhangyiss@icloud.com)
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef _TIN_DELAUNAY_H
#define _TIN_DELAUNAY_H
#include "cmath"
#include "vector"
#include "algorithm"
#define ZERO 1e-5
// Start vertex definition
struct vertex2dc
{
unsigned int id; // index of the vertex
double x, y; // position of the vertex
double elev; // elevation at the vertex
vertex2dc();
/**
* @brief Construct a new vertex2dc object
*
* @param inx initial x coordinate
* @param iny initial y coordinate
* @param inelev initial elevation
* @param inid initial index
*/
vertex2dc(double inx, double iny, double inelev, unsigned int inid);
/**
* @brief Set a vertex2dc object
*
* @param inx initial x coordinate
* @param iny initial y coordinate
* @param inelev initial elevation
* @param inid initial index
*/
void set(double inx, double iny, double inelev, unsigned int inid);
};
/**
* @brief Compare two vertexes
*
* @param a vertex a
* @param b vertex b
* @return true the two vertexes are at the same location
* @return false the two vertexes are not at the same location
*/
bool operator ==(const vertex2dc &a, const vertex2dc &b);
/**
* @brief Test if the three points are on the same line
*
* @param a_ptr pointer of the vertex a
* @param b_ptr pointer of the vertex b
* @param c_ptr pointer of the vertex c
* @return true the three vertexes are on the same line
* @return false the three vertexes are not on the same line
*/
bool is_collinear(vertex2dc *a_ptr, vertex2dc *b_ptr, vertex2dc *c_ptr);
/**
* @brief Calculate the circumcircle from three points
*
* @param v0 pointer of the vertex v0
* @param v1 pointer of the vertex v1
* @param v2 pointer of the vertex v2
* @param cx x coordinate of the returned circumcircle
* @param cy y coordinate of the returned circumcircle
* @param cr squared radius of the returned circumcircle
*/
void circumcircle(vertex2dc *v0, vertex2dc *v1, vertex2dc *v2, double &cx, double &cy, double &cr);
// End vertex definition
// Start DEM definition
struct triangle;
struct dem_point
{
double x, y; // position of the DEM location
double elev; // elevation at the DEM location
double err; // error of the TIN with respect to the elevation
triangle *host;
dem_point();
/**
* @brief Construct a new DEM point object
*
* @param inx initial x coordinate
* @param iny initial y coordinate
* @param inelev initial elevation
*/
dem_point(double inx, double iny, double inelev);
/**
* @brief Set a DEM point object
*
* @param inx initial x coordinate
* @param iny initial y coordinate
* @param inelev initial elevation
*/
void set(double inx, double iny, double inelev);
};
// End DEM definition
/* Start triangle definition
* v2
* /\
* / \
* n2 / \ n1
* / \
* /------------\
* v0 n0 v1
*/
struct triangle
{
int id;
vertex2dc *vert[3]; // vertex of the triangle
triangle *neigh[3]; // neighbors of the triangle
double cx, cy; // center of the triangle's circumcircle
double cr; // radius of the circumcircle
std::vector<dem_point*> hosted_dem;
triangle();
/**
* @brief Construct a new triangle object
*
* @param v0ptr pointer of the vertex 0
* @param v1ptr pointer of the vertex 1
* @param v2ptr pointer of the vertex 2
*/
triangle(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr);
/**
* @brief Set a triangle object
*
* @param v0ptr pointer of the vertex 0
* @param v1ptr pointer of the vertex 1
* @param v2ptr pointer of the vertex 2
*/
void set(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr);
/**
* @brief Set neighbors of a triangle object
*
* @param n0ptr pointer of the neighboring vertex 0
* @param n1ptr pointer of the neighboring vertex 1
* @param n2ptr pointer of the neighboring vertex 2
*/
void set_neighbor(triangle *n0ptr, triangle *n1ptr, triangle *n2ptr);
/**
* @brief Test if the location is inside the triangle
*
* @param inx x coordinate of the input location
* @param iny y coordinate of the input location
* @return true the input location is inside the triangle
* @return false the input location is not inside the triangle
*/
bool bound_location(double inx, double iny);
/**
* @brief Interpolate the elevation of the given location inside the triangle
*
* @param inx x coordinate of the input location
* @param iny y coordinate of the input location
* @return double the interpolated elevation at the input location
*/
double interpolate(double inx, double iny);
};
// End triangle definition
/**
* @brief Generate the TIN from the DEM grid
*
* @param[in] dem Input DEM grid (Ordered from lower left corner to the upper right corner)
* @param[in] xmin The minimal coordinate of the DEM grid on the x-axis
* @param[in] xmax The maximal coordinate of the DEM grid on the x-axis
* @param[in] ymin The minimal coordinate of the DEM grid on the y-axis
* @param[in] ymax The maximal coordinate of the DEM grid on the y-axis
* @param[in] dx Data spacing of the DEM grid on the x-axis
* @param[in] dy Data spacing of the DEM grid on the y-axis
* @param out_verts The output vector of vertex's pointers. The user need to destroy the memories allocated by the function before destroy the vector
* @param out_tris The output vector of triangle's pointers. The user need to destroy the memories allocated by the function before destroy the vector
* @param[in] maxi_err Threshold to quit the algorithm. The default is 1e-0
* @param[in] err_records If this pointer is not NULL, record maximal error values after each insertion of vertex.
*/
void dem2tin(const std::vector<double> &dem, double xmin, double xmax, double ymin, double ymax,
double dx, double dy, std::vector<vertex2dc*> &out_verts, std::vector<triangle*> &out_tris,
double maxi_err = 1e-0, std::vector<double> *err_records = nullptr);
#endif // _TIN_DELAUNAY_H

BIN
tin

Binary file not shown.

View File

@ -1,461 +0,0 @@
/**
* @defgroup TIN
*
* @brief Generation of a Triangular Irregular Network (TIN) from a dense DEM grid.
*
* @author Yi Zhang
* @date 2021-09-16
*/
#ifndef _TIN_DELAUNAY_H
#define _TIN_DELAUNAY_H
#include "cmath"
#include "vector"
#include "algorithm"
#define ZERO 1e-5
// Start vertex definition
struct vertex2dc
{
unsigned int id; // index of the vertex
double x, y; // position of the vertex
double elev; // elevation at the vertex
vertex2dc() : x(NAN), y(NAN), elev(NAN), id(0) {}
vertex2dc(double inx, double iny, double inelev, unsigned int inid) {set(inx, iny, inelev, inid);}
void set(double inx, double iny, double inelev, unsigned int inid)
{
x = inx; y = iny; elev = inelev; id = inid;
return;
}
};
bool operator ==(const vertex2dc &a, const vertex2dc &b) // overload the == operator for vertex2dc type
{
if(fabs(a.x - b.x) <= ZERO && fabs(a.y - b.y) <= ZERO)
{
return true;
}
return false;
}
bool is_collinear(vertex2dc *a_ptr, vertex2dc *b_ptr, vertex2dc *c_ptr) // Test if the three points are on the same line
{
// (y3y1)(x2x1)(y2y1)(x3x1)
if (fabs((c_ptr->y - a_ptr->y)*(b_ptr->x - a_ptr->x) - (b_ptr->y - a_ptr->y)*(c_ptr->x - a_ptr->x)) <= ZERO)
{
return true;
}
return false;
}
// End vertex definition
// Start edge definition
struct edge
{
vertex2dc *vert[2]; // vertex of the edge
edge() {vert[0] = vert[1] = nullptr;}
edge(vertex2dc *v0ptr, vertex2dc *v1ptr) {set(v0ptr, v1ptr);}
void set(vertex2dc *v0ptr, vertex2dc *v1ptr)
{
vert[0] = v0ptr; vert[1] = v1ptr;
return;
}
};
bool operator ==(const edge &a, const edge &b) // overload the == operator for edge type
{
if((a.vert[0] == b.vert[0] && a.vert[1] == b.vert[1]) ||
(a.vert[0] == b.vert[1] && a.vert[1] == b.vert[0]))
{
return true;
}
return false;
}
// End edge definition
// Start triangle definition
struct dem_point;
struct triangle
{
vertex2dc *vert[3]; // vertex of the triangle
double cx, cy; // center of the triangle's circumcircle
double cr; // radius of the circumcircle
std::vector<dem_point*> circum_dem;
triangle() {vert[0] = vert[1] = vert[2] = nullptr;}
triangle(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr) {set(v0ptr, v1ptr, v2ptr);}
void set(vertex2dc *v0ptr, vertex2dc *v1ptr, vertex2dc *v2ptr)
{
vert[0] = v0ptr; vert[1] = v1ptr; vert[2] = v2ptr;
double s = 0.5 / ((vert[1]->x - vert[0]->x) * (vert[2]->y - vert[0]->y) - (vert[1]->y - vert[0]->y) * (vert[2]->x - vert[0]->x));
double m = vert[1]->x * vert[1]->x - vert[0]->x * vert[0]->x + vert[1]->y * vert[1]->y - vert[0]->y * vert[0]->y;
double u = vert[2]->x * vert[2]->x - vert[0]->x * vert[0]->x + vert[2]->y * vert[2]->y - vert[0]->y * vert[0]->y;
cx = ((vert[2]->y - vert[0]->y) * m + (vert[0]->y - vert[1]->y) * u) * s;
cy = ((vert[0]->x - vert[2]->x) * m + (vert[1]->x - vert[0]->x) * u) * s;
cr = (vert[0]->x - cx) * (vert[0]->x - cx) + (vert[0]->y - cy) * (vert[0]->y - cy); // not need to sqrt() here
return;
}
bool bound_location(double inx, double iny) // Test if the location is inside the triangle
{
double l1x, l1y, l2x, l2y;
for (int i = 0; i < 3; i++)
{
l1x = vert[(i+1)%3]->x - vert[i]->x;
l1y = vert[(i+1)%3]->y - vert[i]->y;
l2x = inx - vert[i]->x;
l2y = iny - vert[i]->y;
if ((l1x*l2y - l1y*l2x) < 0) // This condition includes points on the triangle's edge
{
return false;
}
}
return true;
}
double interpolate(double inx, double iny) // Interpolate the elevation of the given location inside the triangle
{
double a1 = 0.5 * ((vert[1]->x - inx)*(vert[2]->y - iny) - (vert[1]->y - iny)*(vert[2]->x - inx));
double a2 = 0.5 * ((vert[2]->x - inx)*(vert[0]->y - iny) - (vert[2]->y - iny)*(vert[0]->x - inx));
double a3 = 0.5 * ((vert[0]->x - inx)*(vert[1]->y - iny) - (vert[0]->y - iny)*(vert[1]->x - inx));
return (a1*vert[0]->elev + a2*vert[1]->elev + a3*vert[2]->elev)/(a1 + a2 + a3);
}
};
// End triangle definition
// Start DEM definition
struct dem_point
{
double x, y; // position of the DEM location
double elev; // elevation at the DEM location
double err; // error of the TIN with respect to the elevation
triangle *host; // host triangle of the DEM location
std::vector<triangle*> circum_host; // triangles which circumcircles include the location
dem_point() : x(NAN), y(NAN), elev(NAN), host(nullptr) {}
dem_point(double inx, double iny, double inelev) {set(inx, iny, inelev);}
void set(double inx, double iny, double inelev)
{
x = inx; y = iny; elev = inelev; host = nullptr;
return;
}
};
bool compare_dem_point(dem_point *a, dem_point *b)
{
if (a->err > b->err) return true;
return false;
}
// End DEM definition
/**
* @brief Generate the TIN from the DEM grid
*
* @param[in] dem Input DEM grid (Ordered from lower left corner to the upper right corner)
* @param[in] xmin The minimal coordinate of the DEM grid on the x-axis
* @param[in] xmax The maximal coordinate of the DEM grid on the x-axis
* @param[in] ymin The minimal coordinate of the DEM grid on the y-axis
* @param[in] ymax The maximal coordinate of the DEM grid on the y-axis
* @param[in] dx Data spacing of the DEM grid on the x-axis
* @param[in] dy Data spacing of the DEM grid on the y-axis
* @param out_verts The output vector of vertex's pointers. The user need to destroy the memories allocated by the function before destroy the vector
* @param out_tris The output vector of triangle's pointers. The user need to destroy the memories allocated by the function before destroy the vector
* @param[in] maxi_err Threshold to quit the algorithm. The default is 1e-0
* @param[in] err_records If this pointer is not NULL, record maximal error values after each insertion of vertex.
*/
void dem2tin(const std::vector<double> &dem, double xmin, double xmax, double ymin, double ymax,
double dx, double dy, std::vector<vertex2dc*> &out_verts, std::vector<triangle*> &out_tris,
double maxi_err = 1e-0, std::vector<double> *err_records = nullptr)
{
if (!out_verts.empty()) out_verts.clear();
if (!out_tris.empty()) out_tris.clear();
if (err_records != nullptr && !err_records->empty()) err_records->clear();
if (dx <= 0.0 || dy <= 0.0 || maxi_err <= 0.0) return;
if (xmin >= xmax || ymin >= ymax || (xmin + dx) > xmax || (ymin + dy) > ymax) return;
int xnum = round((xmax - xmin)/dx) + 1;
int ynum = round((ymax - ymin)/dy) + 1;
if (dem.size() != xnum*ynum) return;
// Prepare the DEM points
dem_point *tmp_dem = nullptr;;
std::vector<dem_point*> dem_grid(xnum*ynum);
std::vector<dem_point*>::iterator d_iter;
for (int i = 0; i < ynum; ++i)
{
for (int j = 0; j < xnum; ++j)
{
dem_grid[j + i*xnum] = new dem_point(xmin + dx*j, ymin + dy*i, dem[j + i*xnum]);
}
}
vertex2dc *tmp_vert = nullptr;
tmp_vert = new vertex2dc(xmin, ymin, dem_grid[0]->elev, out_verts.size()); // lower left corner
out_verts.push_back(tmp_vert);
d_iter = dem_grid.begin();
tmp_dem = *d_iter; delete tmp_dem;
dem_grid.erase(d_iter);
tmp_vert = new vertex2dc(xmax, ymin, dem_grid[xnum-2]->elev, out_verts.size()); // lower right corner. Note the first location is already erased
out_verts.push_back(tmp_vert);
d_iter = dem_grid.begin() + (xnum - 2);
tmp_dem = *d_iter; delete tmp_dem;
dem_grid.erase(d_iter);
tmp_vert = new vertex2dc(xmax, ymax, dem_grid[xnum*ynum-3]->elev, out_verts.size()); // upper right corner. Note the first two locations are already erased
out_verts.push_back(tmp_vert);
d_iter = dem_grid.begin() + (xnum*ynum - 3);
tmp_dem = *d_iter; delete tmp_dem;
dem_grid.erase(d_iter);
tmp_vert = new vertex2dc(xmin, ymax, dem_grid[xnum*(ynum-1) - 2]->elev, out_verts.size()); // upper left corner. Note the first two locations are already erased
out_verts.push_back(tmp_vert);
d_iter = dem_grid.begin() + (xnum*(ynum-1) - 2);
tmp_dem = *d_iter; delete tmp_dem;
dem_grid.erase(d_iter);
triangle *tmp_tri = nullptr;
std::vector<triangle*> cnst_tri, new_tri;
std::vector<triangle*>::iterator t_iter;
if (!is_collinear(out_verts[0], out_verts[1], out_verts[2])) // Do not create triangle if the vertexes are collinear
{
tmp_tri = new triangle(out_verts[0], out_verts[1], out_verts[2]); // order the vertex anti-clock wise
out_tris.push_back(tmp_tri);
}
if (!is_collinear(out_verts[0], out_verts[2], out_verts[3]))
{
tmp_tri = new triangle(out_verts[0], out_verts[2], out_verts[3]); // order the vertex anti-clock wise
out_tris.push_back(tmp_tri);
}
// Find host triangle for all DEM locations
for (int i = 0; i < dem_grid.size(); ++i)
{
for (int t = 0; t < out_tris.size(); ++t)
{
if (out_tris[t]->bound_location(dem_grid[i]->x, dem_grid[i]->y))
{
dem_grid[i]->host = out_tris[t];
break; // already found, no need to search more
}
}
}
// Find circum_host triangles for all DEM locations
double dist;
for (int i = 0; i < dem_grid.size(); ++i)
{
for (int t = 0; t < out_tris.size(); ++t)
{
dist = (out_tris[t]->cx - dem_grid[i]->x) * (out_tris[t]->cx - dem_grid[i]->x)
+ (out_tris[t]->cy - dem_grid[i]->y) * (out_tris[t]->cy - dem_grid[i]->y);
if ((dist - out_tris[t]->cr) <= ZERO) // Points on the circumcircle are also included
{
dem_grid[i]->circum_host.push_back(out_tris[t]);
out_tris[t]->circum_dem.push_back(dem_grid[i]);
// no beak here. There might be more than one triangle's circumcircle includes the DEM location
}
}
}
// loop all DEM data to find the location with maximal error
for (int i = 0; i < dem_grid.size(); ++i)
{
dem_grid[i]->err = fabs(dem_grid[i]->host->interpolate(dem_grid[i]->x, dem_grid[i]->y) - dem_grid[i]->elev);
}
// Sort dem_grid in the desceding order with respect to the error
std::sort(dem_grid.begin(), dem_grid.end(), compare_dem_point);
bool removed;
edge tmp_edge;
std::vector<edge> cnst_edge;
std::vector<edge>::iterator e_iter;
while (dem_grid[0]->err >= maxi_err) // quit til the threshold is meet
{
if (err_records != nullptr)
{
err_records->push_back(dem_grid[0]->err);
}
// create a new vertex
tmp_vert = new vertex2dc(dem_grid[0]->x, dem_grid[0]->y, dem_grid[0]->elev, out_verts.size());
out_verts.push_back(tmp_vert);
// Move triangles which circumcircles include the new vertex to the cnst_tri and remove it from out_tris
cnst_tri.clear();
for (int i = 0; i < dem_grid[0]->circum_host.size(); ++i)
{
cnst_tri.push_back(dem_grid[0]->circum_host[i]);
}
for (int c = 0; c < cnst_tri.size(); ++c)
{
for (t_iter = out_tris.begin(); t_iter != out_tris.end(); )
{
tmp_tri = *t_iter;
if (cnst_tri[c] == tmp_tri)
{
t_iter = out_tris.erase(t_iter);
break; // no need to search more
}
else t_iter++;
}
}
// remove cnst_tri from its circumed DEM's circum triangle list
for (int c = 0; c < cnst_tri.size(); ++c)
{
for (int i = 0; i < cnst_tri[c]->circum_dem.size(); ++i)
{
tmp_dem = cnst_tri[c]->circum_dem[i];
for (t_iter = tmp_dem->circum_host.begin(); t_iter != tmp_dem->circum_host.end(); )
{
if (cnst_tri[c] == *t_iter)
{
t_iter = tmp_dem->circum_host.erase(t_iter);
break;
}
else t_iter++;
}
}
}
// remove dem_grid[0] from its circumed triangle's circum DEM list
for (int c = 0; c < cnst_tri.size(); ++c)
{
for (d_iter = cnst_tri[c]->circum_dem.begin(); d_iter != cnst_tri[c]->circum_dem.end(); )
{
if (dem_grid[0] == *d_iter)
{
d_iter = cnst_tri[c]->circum_dem.erase(d_iter);
break;
}
else d_iter++;
}
}
// clear host and circumcircle triangles for the used DEM location
d_iter = dem_grid.begin();
tmp_dem = *d_iter; tmp_dem->circum_host.clear(); delete tmp_dem;
dem_grid.erase(d_iter);
// loop to remove duplicate edges
cnst_edge.clear();
for (int c = 0; c < cnst_tri.size(); ++c)
{
for (int e = 0; e < 3; ++e)
{
tmp_edge.set(cnst_tri[c]->vert[e], cnst_tri[c]->vert[(e+1)%3]);
removed = false;
for (e_iter = cnst_edge.begin(); e_iter != cnst_edge.end(); )
{
if (tmp_edge == *e_iter) // duplicate edge, remove from cnst_edge
{
e_iter = cnst_edge.erase(e_iter);
removed = true;
break; // no need to search more
}
else e_iter++;
}
if (!removed) // not a duplicate edge, add to the cnst_edge
{
cnst_edge.push_back(tmp_edge);
}
}
}
// construct new triangles and add to out_tris
new_tri.clear();
for (int c = 0; c < cnst_edge.size(); ++c)
{
if (!is_collinear(cnst_edge[c].vert[0], cnst_edge[c].vert[1], tmp_vert)) // Do not create triangle if the vertexes are collinear
{
tmp_tri = new triangle(cnst_edge[c].vert[0], cnst_edge[c].vert[1], tmp_vert); // order the vertex anti-clock wise
out_tris.push_back(tmp_tri);
new_tri.push_back(tmp_tri);
}
}
// loop all DEM data to update host triangles
for (int c = 0; c < cnst_tri.size(); ++c)
{
for (int i = 0; i < cnst_tri[c]->circum_dem.size(); ++i)
{
tmp_dem = cnst_tri[c]->circum_dem[i];
for (int n = 0; n < new_tri.size(); ++n) // search in newly created triangles to find new host
{
if (new_tri[n]->bound_location(tmp_dem->x, tmp_dem->y))
{
tmp_dem->host = new_tri[n];
tmp_dem->err = fabs(new_tri[n]->interpolate(tmp_dem->x, tmp_dem->y) - tmp_dem->elev);
break; // already found, no need to search more
}
}
}
}
// Find circum_host triangles for all DEM locations
// cnst_tri's circum area doesn't over cover new_tri's circum area
for (int i = 0; i < dem_grid.size(); ++i)
{
for (int n = 0; n < new_tri.size(); ++n) // search in newly created triangles to find new circumcircle triangles
{
dist = (new_tri[n]->cx - dem_grid[i]->x) * (new_tri[n]->cx - dem_grid[i]->x)
+ (new_tri[n]->cy - dem_grid[i]->y) * (new_tri[n]->cy - dem_grid[i]->y);
if ((dist - new_tri[n]->cr) <= ZERO) // Points on the circumcircle are also included
{
new_tri[n]->circum_dem.push_back(dem_grid[i]);
dem_grid[i]->circum_host.push_back(new_tri[n]);
// no beak here. There might be more than one triangle's circumcircle includes the DEM location
}
}
}
// destroy memories used by cnst_edge
for (int c = 0; c < cnst_tri.size(); ++c)
{
tmp_tri = cnst_tri[c];
tmp_tri->circum_dem.clear();
delete tmp_tri; tmp_tri = nullptr;
}
// Sort dem_grid in the desceding order with respect to the error
std::sort(dem_grid.begin(), dem_grid.end(), compare_dem_point);
}
if (err_records != nullptr)
{
err_records->push_back(dem_grid[0]->err);
}
// destroy remaining DEM data
for (int i = 0; i < dem_grid.size(); ++i)
{
tmp_dem = dem_grid[i];
delete tmp_dem; tmp_dem = nullptr;
}
return;
}
#endif // _TIN_DELAUNAY_H