gadm cpp exports

This commit is contained in:
lovebird 2026-03-24 21:52:04 +01:00
parent 3f8073452c
commit 3a53b15274
15 changed files with 296 additions and 14 deletions

View File

@ -34,18 +34,22 @@ find_package(OpenMP)
add_subdirectory(packages/logger)
add_subdirectory(packages/json)
# Executable
add_executable(boundaries
src/main.cpp
# Library sources (everything except main.cpp)
set(GADM_LIB_SOURCES
src/gpkg_reader.cpp
src/geo_merge.cpp
src/ghs_enrich.cpp
)
target_include_directories(boundaries PRIVATE src)
set(GADM_PUBLIC_HEADERS
src/gpkg_reader.h
src/geo_merge.h
src/ghs_enrich.h
src/pip.h
src/types.h
)
target_link_libraries(boundaries PRIVATE
CLI11::CLI11
set(GADM_LIB_DEPS
GDAL::GDAL
GEOS::geos_c
PROJ::proj
@ -54,17 +58,42 @@ target_link_libraries(boundaries PRIVATE
json
)
# Static library
add_library(gadm_boundaries_static STATIC ${GADM_LIB_SOURCES})
target_include_directories(gadm_boundaries_static PUBLIC src)
target_link_libraries(gadm_boundaries_static PUBLIC ${GADM_LIB_DEPS})
if(OpenMP_CXX_FOUND)
target_link_libraries(boundaries PRIVATE OpenMP::OpenMP_CXX)
target_compile_definitions(boundaries PRIVATE HAS_OPENMP=1)
target_link_libraries(gadm_boundaries_static PUBLIC OpenMP::OpenMP_CXX)
target_compile_definitions(gadm_boundaries_static PUBLIC HAS_OPENMP=1)
endif()
# Compiler warnings
if(MSVC)
target_compile_options(boundaries PRIVATE /W4 /permissive-)
else()
target_compile_options(boundaries PRIVATE -Wall -Wextra -Wpedantic)
# Shared library
add_library(gadm_boundaries SHARED ${GADM_LIB_SOURCES})
target_include_directories(gadm_boundaries PUBLIC src)
target_link_libraries(gadm_boundaries PUBLIC ${GADM_LIB_DEPS})
if(OpenMP_CXX_FOUND)
target_link_libraries(gadm_boundaries PUBLIC OpenMP::OpenMP_CXX)
target_compile_definitions(gadm_boundaries PUBLIC HAS_OPENMP=1)
endif()
# Export all symbols on Windows (no __declspec needed)
set_target_properties(gadm_boundaries PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
# Executable (links against static lib)
add_executable(boundaries src/main.cpp)
target_link_libraries(boundaries PRIVATE
gadm_boundaries_static
CLI11::CLI11
)
# Compiler warnings (all targets)
set(_GADM_TARGETS boundaries gadm_boundaries_static gadm_boundaries)
foreach(_tgt IN LISTS _GADM_TARGETS)
if(MSVC)
target_compile_options(${_tgt} PRIVATE /W4 /permissive-)
else()
target_compile_options(${_tgt} PRIVATE -Wall -Wextra -Wpedantic)
endif()
endforeach()
# Copy data files needed at runtime
add_custom_command(TARGET boundaries POST_BUILD
@ -80,8 +109,10 @@ else()
set(DIST_DIR "${CMAKE_SOURCE_DIR}/../dist/linux-x64")
endif()
install(TARGETS boundaries
install(TARGETS boundaries gadm_boundaries_static gadm_boundaries
RUNTIME DESTINATION "${DIST_DIR}"
LIBRARY DESTINATION "${DIST_DIR}/lib"
ARCHIVE DESTINATION "${DIST_DIR}/lib"
)
# Post-build: copy binary so `npm run boundaries:cpp` works from dist
@ -93,6 +124,58 @@ add_custom_command(TARGET boundaries POST_BUILD
COMMENT "Copying boundaries to ${DIST_DIR}/"
)
# Post-build: copy public headers to dist/include/gadm/
add_custom_command(TARGET gadm_boundaries POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${DIST_DIR}/include/gadm"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/src/gpkg_reader.h" "${DIST_DIR}/include/gadm/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/src/geo_merge.h" "${DIST_DIR}/include/gadm/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/src/ghs_enrich.h" "${DIST_DIR}/include/gadm/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/src/pip.h" "${DIST_DIR}/include/gadm/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/src/types.h" "${DIST_DIR}/include/gadm/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/packages/logger/include/logger/logger.h"
"${DIST_DIR}/include/gadm/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/packages/json/include/json/json.h"
"${DIST_DIR}/include/gadm/"
COMMENT "Copying public headers to ${DIST_DIR}/include/gadm/"
)
# Post-build: copy static library to dist/lib/
add_custom_command(TARGET gadm_boundaries_static POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${DIST_DIR}/lib"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:gadm_boundaries_static>
"${DIST_DIR}/lib/"
COMMENT "Copying static library to ${DIST_DIR}/lib/"
)
# Post-build: copy shared library (.dll + import lib) to dist/
if(WIN32)
add_custom_command(TARGET gadm_boundaries POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${DIST_DIR}/lib"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:gadm_boundaries>
"${DIST_DIR}/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_LINKER_FILE:gadm_boundaries>
"${DIST_DIR}/lib/"
COMMENT "Copying shared library + import lib to ${DIST_DIR}/"
)
else()
add_custom_command(TARGET gadm_boundaries POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:gadm_boundaries>
"${DIST_DIR}/"
COMMENT "Copying shared library to ${DIST_DIR}/"
)
endif()
# Copy dependent DLLs (Windows) or shared libs + proj.db
if(WIN32)
add_custom_command(TARGET boundaries POST_BUILD

Binary file not shown.

BIN
dist/win-x64/gadm_boundaries.dll vendored Normal file

Binary file not shown.

BIN
dist/win-x64/gadm_boundaries.exp vendored Normal file

Binary file not shown.

BIN
dist/win-x64/gadm_boundaries.lib vendored Normal file

Binary file not shown.

BIN
dist/win-x64/gadm_boundaries_static.lib vendored Normal file

Binary file not shown.

14
dist/win-x64/include/gadm/geo_merge.h vendored Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <vector>
#include "types.h"
namespace geo_merge {
/// Merge multiple features that share the same GID code by unioning their geometries.
/// Uses GEOS GEOSUnaryUnion with GEOSMakeValid fallback.
std::vector<boundary::BoundaryFeature> merge(
const std::vector<boundary::BoundaryFeature>& features
);
} // namespace geo_merge

50
dist/win-x64/include/gadm/ghs_enrich.h vendored Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace ghs_enrich {
/// Enrichment results for a single boundary feature.
struct EnrichResult {
// Population grid (GHS-POP)
double ghsPopulation = 0;
double ghsPopMaxDensity = 0;
double ghsPopCenterLon = 0;
double ghsPopCenterLat = 0;
std::vector<std::array<double, 3>> ghsPopCenters; // [lon, lat, density]
// Built-up surface (GHS-BUILT-S)
double ghsBuiltWeight = 0;
double ghsBuiltMax = 0;
double ghsBuiltCenterLon = 0;
double ghsBuiltCenterLat = 0;
std::vector<std::array<double, 3>> ghsBuiltCenters; // [lon, lat, built]
bool hasPop = false;
bool hasBuilt = false;
};
/// Enrich a GeoJSON feature geometry with GHS raster statistics.
///
/// The TIFFs are global grids in Mollweide (EPSG:54009) at 100m resolution:
/// - GHS_POP_E2030_GLOBE_R2023A_54009_100_V1_0.tif (~38 GB)
/// - GHS_BUILT_S_E2030_GLOBE_R2023A_54009_100_V1_0.tif
///
/// GDAL reads these via windowed RasterIO — only the bbox-clipped
/// portion is loaded, so memory use stays bounded regardless of file size.
EnrichResult enrich_feature(
const std::vector<unsigned char> &wkb,
double minLon, double minLat, double maxLon, double maxLat,
const std::string &pop_tiff_path = "",
const std::string &built_tiff_path = "");
/// Convenience: enrich all features in a batch, returning enrichment data
/// indexed by feature position.
std::vector<EnrichResult> enrich_batch(
const std::vector<std::vector<unsigned char>> &wkbs,
const std::string &pop_tiff_path = "",
const std::string &built_tiff_path = "");
} // namespace ghs_enrich

28
dist/win-x64/include/gadm/gpkg_reader.h vendored Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <string>
#include <vector>
#include "types.h"
namespace gpkg_reader {
/// Read features from a GeoPackage file, filtered by country (GID_0) and admin level.
/// Groups features by GID_{level} and returns one BoundaryFeature per group
/// with its geometry as a GEOS handle.
/// Retrieve a list of distinct GID_{split_level} values for a given country code
std::vector<std::string> get_subregions(
const std::string& gpkg_path,
const std::string& country_code,
int split_level
);
/// Read features from a GeoPackage file, filtered by country (GID_0) and admin level.
/// Can also filter directly by sub-region if country_code is a dotted GADM ID (e.g. ESP.6_1).
std::vector<boundary::BoundaryFeature> read_features(
const std::string& gpkg_path,
const std::string& country_code,
int level,
double tolerance = 0.0
);
} // namespace gpkg_reader

23
dist/win-x64/include/gadm/json.h vendored Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <vector>
namespace json {
/// Parse a JSON string and return a pretty-printed version.
std::string prettify(const std::string &json_str);
/// Extract a string value by key from a JSON object (top-level only).
std::string get_string(const std::string &json_str, const std::string &key);
/// Extract an int value by key from a JSON object (top-level only).
int get_int(const std::string &json_str, const std::string &key);
/// Check if a JSON string is valid.
bool is_valid(const std::string &json_str);
/// Get all top-level keys from a JSON object.
std::vector<std::string> keys(const std::string &json_str);
} // namespace json

16
dist/win-x64/include/gadm/logger.h vendored Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <string>
namespace logger {
/// Initialize the default logger (call once at startup).
void init(const std::string &app_name = "polymech");
/// Log at various levels.
void info(const std::string &msg);
void warn(const std::string &msg);
void error(const std::string &msg);
void debug(const std::string &msg);
} // namespace logger

38
dist/win-x64/include/gadm/pip.h vendored Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <cstddef>
#include <vector>
namespace pip {
/// Ray-casting point-in-polygon test.
/// polygon is a flat array of [x0, y0, x1, y1, ...] coordinates.
/// Returns true if (px, py) is inside the polygon.
inline bool point_in_polygon(
double px, double py,
const double* polygon, size_t num_vertices
) {
bool inside = false;
for (size_t i = 0, j = num_vertices - 1; i < num_vertices; j = i++) {
double xi = polygon[i * 2];
double yi = polygon[i * 2 + 1];
double xj = polygon[j * 2];
double yj = polygon[j * 2 + 1];
if (((yi > py) != (yj > py)) &&
(px < (xj - xi) * (py - yi) / (yj - yi) + xi)) {
inside = !inside;
}
}
return inside;
}
/// Convenience overload for std::vector<double> ring.
inline bool point_in_polygon(
double px, double py,
const std::vector<double>& ring
) {
return point_in_polygon(px, py, ring.data(), ring.size() / 2);
}
} // namespace pip

30
dist/win-x64/include/gadm/types.h vendored Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace boundary {
/// A single administrative boundary feature (one GID code).
struct BoundaryFeature {
std::string code; // e.g. "DEU.1_1" (GID_{level})
std::string name; // e.g. "Bayern"
nlohmann::json geojson; // GeoJSON geometry object
std::vector<unsigned char> wkb;
double minX = 0, minY = 0, maxX = 0, maxY = 0;
// Raw GEOS geometry handle (opaque, managed by caller)
void* geos_geom = nullptr;
};
/// Result of a full boundary batch for one country+level.
struct BoundaryResult {
std::string country;
int level = 0;
int featureCount = 0;
std::vector<BoundaryFeature> features;
};
} // namespace boundary

BIN
dist/win-x64/lib/gadm_boundaries.lib vendored Normal file

Binary file not shown.

Binary file not shown.