diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 91b0235..6f25271 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -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 + $ + "${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 + $ + "${DIST_DIR}/" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + "${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 + $ + "${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 diff --git a/dist/win-x64/boundaries.exe b/dist/win-x64/boundaries.exe index f91b3ca..3a8759b 100644 Binary files a/dist/win-x64/boundaries.exe and b/dist/win-x64/boundaries.exe differ diff --git a/dist/win-x64/gadm_boundaries.dll b/dist/win-x64/gadm_boundaries.dll new file mode 100644 index 0000000..0cec102 Binary files /dev/null and b/dist/win-x64/gadm_boundaries.dll differ diff --git a/dist/win-x64/gadm_boundaries.exp b/dist/win-x64/gadm_boundaries.exp new file mode 100644 index 0000000..bcfb11a Binary files /dev/null and b/dist/win-x64/gadm_boundaries.exp differ diff --git a/dist/win-x64/gadm_boundaries.lib b/dist/win-x64/gadm_boundaries.lib new file mode 100644 index 0000000..7a0e956 Binary files /dev/null and b/dist/win-x64/gadm_boundaries.lib differ diff --git a/dist/win-x64/gadm_boundaries_static.lib b/dist/win-x64/gadm_boundaries_static.lib new file mode 100644 index 0000000..dcfc182 Binary files /dev/null and b/dist/win-x64/gadm_boundaries_static.lib differ diff --git a/dist/win-x64/include/gadm/geo_merge.h b/dist/win-x64/include/gadm/geo_merge.h new file mode 100644 index 0000000..8ca2bb4 --- /dev/null +++ b/dist/win-x64/include/gadm/geo_merge.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#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 merge( + const std::vector& features +); + +} // namespace geo_merge diff --git a/dist/win-x64/include/gadm/ghs_enrich.h b/dist/win-x64/include/gadm/ghs_enrich.h new file mode 100644 index 0000000..84466b1 --- /dev/null +++ b/dist/win-x64/include/gadm/ghs_enrich.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +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> ghsPopCenters; // [lon, lat, density] + + // Built-up surface (GHS-BUILT-S) + double ghsBuiltWeight = 0; + double ghsBuiltMax = 0; + double ghsBuiltCenterLon = 0; + double ghsBuiltCenterLat = 0; + std::vector> 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 (~3–8 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 &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 enrich_batch( + const std::vector> &wkbs, + const std::string &pop_tiff_path = "", + const std::string &built_tiff_path = ""); + +} // namespace ghs_enrich diff --git a/dist/win-x64/include/gadm/gpkg_reader.h b/dist/win-x64/include/gadm/gpkg_reader.h new file mode 100644 index 0000000..223216f --- /dev/null +++ b/dist/win-x64/include/gadm/gpkg_reader.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#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 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 read_features( + const std::string& gpkg_path, + const std::string& country_code, + int level, + double tolerance = 0.0 +); + +} // namespace gpkg_reader diff --git a/dist/win-x64/include/gadm/json.h b/dist/win-x64/include/gadm/json.h new file mode 100644 index 0000000..30ce3e4 --- /dev/null +++ b/dist/win-x64/include/gadm/json.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +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 keys(const std::string &json_str); + +} // namespace json diff --git a/dist/win-x64/include/gadm/logger.h b/dist/win-x64/include/gadm/logger.h new file mode 100644 index 0000000..6cfffb1 --- /dev/null +++ b/dist/win-x64/include/gadm/logger.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +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 diff --git a/dist/win-x64/include/gadm/pip.h b/dist/win-x64/include/gadm/pip.h new file mode 100644 index 0000000..b792909 --- /dev/null +++ b/dist/win-x64/include/gadm/pip.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +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 ring. +inline bool point_in_polygon( + double px, double py, + const std::vector& ring +) { + return point_in_polygon(px, py, ring.data(), ring.size() / 2); +} + +} // namespace pip diff --git a/dist/win-x64/include/gadm/types.h b/dist/win-x64/include/gadm/types.h new file mode 100644 index 0000000..efc41f4 --- /dev/null +++ b/dist/win-x64/include/gadm/types.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +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 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 features; +}; + +} // namespace boundary diff --git a/dist/win-x64/lib/gadm_boundaries.lib b/dist/win-x64/lib/gadm_boundaries.lib new file mode 100644 index 0000000..7a0e956 Binary files /dev/null and b/dist/win-x64/lib/gadm_boundaries.lib differ diff --git a/dist/win-x64/lib/gadm_boundaries_static.lib b/dist/win-x64/lib/gadm_boundaries_static.lib new file mode 100644 index 0000000..dcfc182 Binary files /dev/null and b/dist/win-x64/lib/gadm_boundaries_static.lib differ