#include "gadm_reader/gadm_reader.h" #include #include #include #include namespace gadm { // ── Helpers ───────────────────────────────────────────────────────────────── std::string country_code(const std::string& gid) { auto dot = gid.find('.'); return (dot != std::string::npos) ? gid.substr(0, dot) : gid; } int infer_level(const std::string& gid) { // Count dots: "ABW" → 0, "AFG.1_1" → 1, "AFG.1.1_1" → 2 int dots = 0; for (char c : gid) { if (c == '.') dots++; } return dots; } static std::string read_file(const std::string& path) { std::ifstream ifs(path, std::ios::binary); if (!ifs.is_open()) return ""; std::ostringstream oss; oss << ifs.rdbuf(); return oss.str(); } /// Parse a coord array [lon, lat] → geo::Coord static geo::Coord parse_coord(const rapidjson::Value& arr) { if (arr.IsArray() && arr.Size() >= 2) { return {arr[0].GetDouble(), arr[1].GetDouble()}; } return {}; } /// Parse a ring array [[lon,lat], [lon,lat], ...] → vector static std::vector parse_ring(const rapidjson::Value& arr) { std::vector ring; if (!arr.IsArray()) return ring; ring.reserve(arr.Size()); for (rapidjson::SizeType i = 0; i < arr.Size(); ++i) { ring.push_back(parse_coord(arr[i])); } return ring; } /// Parse weighted centers [[lon, lat, weight], ...] static std::vector> parse_weighted_centers( const rapidjson::Value& arr) { std::vector> centers; if (!arr.IsArray()) return centers; centers.reserve(arr.Size()); for (rapidjson::SizeType i = 0; i < arr.Size(); ++i) { const auto& c = arr[i]; if (c.IsArray() && c.Size() >= 3) { centers.push_back({c[0].GetDouble(), c[1].GetDouble(), c[2].GetDouble()}); } } return centers; } /// Get a double from properties, with fallback static double get_double(const rapidjson::Value& props, const char* key, double fallback = 0.0) { if (props.HasMember(key) && props[key].IsNumber()) { return props[key].GetDouble(); } return fallback; } /// Get a bool from properties, with fallback static bool get_bool(const rapidjson::Value& props, const char* key, bool fallback = true) { if (props.HasMember(key) && props[key].IsBool()) { return props[key].GetBool(); } return fallback; } /// Get a string from properties, checking GID_0, GID_1, GID_2, etc. static std::string get_gid(const rapidjson::Value& props) { // Try GID_5 down to GID_0, return the most specific one found for (int lvl = 5; lvl >= 0; --lvl) { std::string key = "GID_" + std::to_string(lvl); if (props.HasMember(key.c_str()) && props[key.c_str()].IsString()) { return props[key.c_str()].GetString(); } } return ""; } /// Get the name (NAME_0, NAME_1, ... NAME_5) static std::string get_name(const rapidjson::Value& props) { for (int lvl = 5; lvl >= 0; --lvl) { std::string key = "NAME_" + std::to_string(lvl); if (props.HasMember(key.c_str()) && props[key.c_str()].IsString()) { return props[key.c_str()].GetString(); } } return ""; } /// Parse a single GeoJSON Feature object into a gadm::Feature static Feature parse_feature(const rapidjson::Value& feat) { Feature f; // Properties if (feat.HasMember("properties") && feat["properties"].IsObject()) { const auto& props = feat["properties"]; f.gid = get_gid(props); f.name = get_name(props); f.level = infer_level(f.gid); f.ghsPopulation = get_double(props, "ghsPopulation"); f.ghsBuiltWeight = get_double(props, "ghsBuiltWeight"); f.ghsPopMaxDensity = get_double(props, "ghsPopMaxDensity"); f.ghsBuiltMax = get_double(props, "ghsBuiltMax"); f.isOuter = get_bool(props, "isOuter"); if (props.HasMember("ghsPopCenter") && props["ghsPopCenter"].IsArray()) { f.ghsPopCenter = parse_coord(props["ghsPopCenter"]); } if (props.HasMember("ghsBuiltCenter") && props["ghsBuiltCenter"].IsArray()) { f.ghsBuiltCenter = parse_coord(props["ghsBuiltCenter"]); } if (props.HasMember("ghsPopCenters") && props["ghsPopCenters"].IsArray()) { f.ghsPopCenters = parse_weighted_centers(props["ghsPopCenters"]); } if (props.HasMember("ghsBuiltCenters") && props["ghsBuiltCenters"].IsArray()) { f.ghsBuiltCenters = parse_weighted_centers(props["ghsBuiltCenters"]); } } // Geometry if (feat.HasMember("geometry") && feat["geometry"].IsObject()) { const auto& geom = feat["geometry"]; std::string gtype; if (geom.HasMember("type") && geom["type"].IsString()) { gtype = geom["type"].GetString(); } if (geom.HasMember("coordinates") && geom["coordinates"].IsArray()) { const auto& coords = geom["coordinates"]; if (gtype == "Polygon") { // coordinates: [ [ring], [hole], ... ] for (rapidjson::SizeType r = 0; r < coords.Size(); ++r) { f.rings.push_back(parse_ring(coords[r])); } } else if (gtype == "MultiPolygon") { // coordinates: [ [ [ring], [hole] ], [ [ring] ], ... ] for (rapidjson::SizeType p = 0; p < coords.Size(); ++p) { if (coords[p].IsArray()) { for (rapidjson::SizeType r = 0; r < coords[p].Size(); ++r) { f.rings.push_back(parse_ring(coords[p][r])); } } } } } } // Compute bbox and area from first ring (outer boundary) if (!f.rings.empty() && !f.rings[0].empty()) { f.bbox = geo::bbox(f.rings[0]); f.areaSqKm = geo::area_sq_km(f.rings[0]); } return f; } // ── Public API ────────────────────────────────────────────────────────────── BoundaryResult load_boundary_file(const std::string& filepath) { BoundaryResult result; std::string json = read_file(filepath); if (json.empty()) { result.error = "Failed to read file: " + filepath; return result; } rapidjson::Document doc; doc.Parse(json.c_str()); if (doc.HasParseError()) { result.error = "JSON parse error in: " + filepath; return result; } // Expect a FeatureCollection if (!doc.HasMember("features") || !doc["features"].IsArray()) { result.error = "Missing 'features' array in: " + filepath; return result; } const auto& features = doc["features"]; result.features.reserve(features.Size()); for (rapidjson::SizeType i = 0; i < features.Size(); ++i) { result.features.push_back(parse_feature(features[i])); } return result; } BoundaryResult load_boundary(const std::string& gid, int targetLevel, const std::string& cacheDir) { // Try: cacheDir/boundary_{gid}_{level}.json std::string path = cacheDir + "/boundary_" + gid + "_" + std::to_string(targetLevel) + ".json"; auto result = load_boundary_file(path); if (result.error.empty()) return result; // Fallback: cacheDir/boundary_{countryCode}_{level}.json std::string cc = country_code(gid); if (cc != gid) { path = cacheDir + "/boundary_" + cc + "_" + std::to_string(targetLevel) + ".json"; result = load_boundary_file(path); if (result.error.empty()) return result; } // Both failed result.error = "No boundary file found for gid=" + gid + " level=" + std::to_string(targetLevel) + " in " + cacheDir; return result; } } // namespace gadm