| 203 | } |
| 204 | |
| 205 | Result<GeoJsonObject> parseGeoJsonObject( |
| 206 | const rapidjson::Value::ConstObject& obj, |
| 207 | const std::function<bool(const std::string& type)>& expectedPredicate, |
| 208 | const std::string& expectedStr) { |
| 209 | const std::string& type = JsonHelpers::getStringOrDefault(obj, "type", ""); |
| 210 | if (type.empty()) { |
| 211 | return Result<GeoJsonObject>( |
| 212 | ErrorList::error("GeoJSON object missing required 'type' field.")); |
| 213 | } |
| 214 | |
| 215 | // Handle unexpected types before we go through the work of parsing something |
| 216 | // we won't use. |
| 217 | if (!expectedPredicate(type)) { |
| 218 | return Result<GeoJsonObject>( |
| 219 | ErrorList::error(fmt::format("{}, found {}.", expectedStr, type))); |
| 220 | } |
| 221 | |
| 222 | ErrorList errorList; |
| 223 | // Try reading the bounding box |
| 224 | std::optional<AxisAlignedBox> boundingBox; |
| 225 | const auto bboxMember = obj.FindMember("bbox"); |
| 226 | if (bboxMember != obj.MemberEnd()) { |
| 227 | Result<std::optional<AxisAlignedBox>> regionResult = |
| 228 | parseBoundingBox(bboxMember->value); |
| 229 | errorList.merge(std::move(regionResult.errors)); |
| 230 | if (regionResult.value) { |
| 231 | boundingBox = std::move(*regionResult.value); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | if (type == "Feature") { |
| 236 | // Feature has a geometry, properties, and an optional id |
| 237 | std::variant<std::monostate, std::string, int64_t> id = std::monostate(); |
| 238 | const auto& idMember = obj.FindMember("id"); |
| 239 | if (idMember != obj.MemberEnd()) { |
| 240 | if (idMember->value.IsInt64()) { |
| 241 | id = idMember->value.GetInt64(); |
| 242 | } else if (idMember->value.IsDouble()) { |
| 243 | // Store as string - hopefully people won't be using floating point IDs |
| 244 | // anyways. |
| 245 | id = std::to_string(idMember->value.GetDouble()); |
| 246 | } else if (idMember->value.IsString()) { |
| 247 | id = idMember->value.GetString(); |
| 248 | } else { |
| 249 | return Result<GeoJsonObject>(ErrorList::error( |
| 250 | "Feature 'id' member must be either a string or a number.")); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | std::unique_ptr<GeoJsonObject> geometry = nullptr; |
| 255 | const auto& geometryMember = obj.FindMember("geometry"); |
| 256 | if (geometryMember == obj.MemberEnd()) { |
| 257 | errorList.emplaceWarning("Feature must have a 'geometry' member."); |
| 258 | } else if (!geometryMember->value.IsNull()) { |
| 259 | if (!geometryMember->value.IsObject()) { |
| 260 | return Result<GeoJsonObject>(ErrorList::error( |
| 261 | "Feature 'geometry' member must be either an object or null.")); |
| 262 | } |
no test coverage detected