I'm using the iOS Mapbox SDK to create a MGLShapeCollectionFeature from a goejson FeatureCollection data that comes from a 3rd party API.
guard let feature = try? MGLShape(data: jsonData, encoding: String.Encoding.utf8.rawValue) as? MGLShapeCollectionFeature else {
print("Could not cast to specified MGLShapeCollectionFeature")
return
}
The problem is that the API sometimes returns an invalid geojson where a single Feature does not contain valid coordinates (see below) and initialising the MGLShape fails with a 'NSInvalidArgumentException', reason: 'A multipoint must have at least one vertex.' which is correct.
Is there a way to filter out and drop those invalid Features within a FeatureCollection other that parsing and fixing the geojson manually?
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"icaoId": "KBOS",
"airSigmetType": "AIRMET",
"hazard": "IFR"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
]
]
}
},
{
"type": "Feature",
"properties": {
"icaoId": "KSLC",
"airSigmetType": "AIRMET",
"hazard": "IFR"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-106.63,
49.06
],
[
-104.12,
48.95
],
[
-104.17,
44.8
],
[
-106.91,
46.38
],
[
-106.63,
49.06
]
]
]
}
}
]
}
A possible solution is to decode the JSON with Codable into structs, filter the empty items and encode the object back:
struct FeatureCollection : Codable {
let type : String
var features : [Feature]
}
struct Feature : Codable {
let type : String
let properties : Properties
let geometry : Geometry
}
struct Properties : Codable {
let icaoId, airSigmetType, hazard : String
}
struct Geometry : Codable {
let type : String
let coordinates : [[[Double]]]
}
do {
var result = try JSONDecoder().decode(FeatureCollection.self, from: jsonData)
let filteredFeatures = result.features.filter{$0.geometry.coordinates != [[]]}
result.features = filteredFeatures
let filteredData = try JSONEncoder().encode(result)
guard let feature = try? MGLShape(data: filteredData, encoding: String.Encoding.utf8.rawValue) as? MGLShapeCollectionFeature else {
print("Could not cast to specified MGLShapeCollectionFeature")
return
}
} catch {
print(error)
}
As you suggested, I did the filtering myself and wrote this extension on Data
extension Data {
func removeEmptyCoordinates() throws -> Data {
guard var geojson = try JSONSerialization.jsonObject(with: self, options: []) as? [String: Any] else {
return self
}
fix(geojson: &geojson,
processFeatureIf: NSPredicate(format: "geometry.type == 'Polygon'"),
keepFeatureIf: NSPredicate(format: "%K[0][SIZE] >= 2", "geometry.coordinates"))
return try JSONSerialization.data(withJSONObject: geojson, options: [])
}
private func fix(geojson: inout [String: Any], processFeatureIf: NSPredicate, keepFeatureIf: NSPredicate) {
guard let type = geojson["type"] as? String, type == "FeatureCollection" else {
// "Not a FeatureCollection"
return
}
// "No features to fix"
guard let features = geojson["features"] as? [[String: Any]] else { return }
let filtered = features.filter { feature in
if !processFeatureIf.evaluate(with: feature) {
// not processing
return true
}
return keepFeatureIf.evaluate(with: feature)
}
geojson["features"] = filtered
}
}