Open Jessers1 opened 1 year ago
The prepared geometry capability of GEOS would likely be a good fit for your use case. GEOSwift doesn't expose those APIs yet, but I'll consider this issue a feature request to add them!
Could you give https://github.com/GEOSwift/GEOSwift/pull/264 a try and let me know how it works?
The usage will be something like…
PreparedGeometry
by calling the new makePrepared()
method, and store those values somewhere alongside the original features/properties.PreparedGeometry
to test for containment.This is the first time I've tried prepared geometry, and it's a little different than the rest of GEOSwift in that the underly GEOS context escapes the scope of a single method call. I need to do a bit more testing & investigation to make sure that's not going to cause any problems, but in the mean time, please give it a try and let me know if you have any feedback.
I also wonder whether the STRtree APIs in GEOS might be an even better fit. I will explore that as well (though I'm out of time for today).
Thanks so much! I've implemented your pull request and at this stage (Fingers crossed) with test locations, it seems to work extremely fast and with accuracy however I am yet to test in a live location update sense (During a journey).
Implementation:
`
import Foundation
import MapKit
import GEOSwift
import CoreLocation
import Combine
class SpeedLimitStore: ObservableObject {
@Published var geoJSON: GeoJSON? = nil
var preparedGeometries: [PreparedGeometry] = []
var featureProperties: [[String: Any]] = []
init() {
loadSpeedLimits { features in
self.geoJSON = GeoJSON.featureCollection(FeatureCollection(features: features))
self.prepareGeometriesAndStoreProperties(features: features)
}
}
struct SpeedLimit {
let geometry: Geometry
let speedLimit: Int
}
func loadSpeedLimits(completion: @escaping ([Feature]) -> Void) {
guard let geoJSONURL = Bundle.main.url(forResource: "speed_limits", withExtension: "geojson") else {
print("GeoJSON file not found")
return
}
do {
let data = try Data(contentsOf: geoJSONURL)
let geoJSON = try JSONDecoder().decode(GeoJSON.self, from: data)
if case let .featureCollection(featureCollection) = geoJSON {
print("Loaded \(featureCollection.features.count) speed limit features")
completion(featureCollection.features)
} else {
print("Error: GeoJSON data is not a FeatureCollection")
}
} catch {
print("Error parsing GeoJSON data: \(error.localizedDescription)")
}
}
func prepareGeometriesAndStoreProperties(features: [Feature]) {
for feature in features {
guard let geometry = feature.geometry,
let properties = feature.properties else {
continue
}
do {
let preparedGeometry = try geometry.makePrepared()
preparedGeometries.append(preparedGeometry)
featureProperties.append(properties)
} catch {
print("Error preparing geometry: \(error)")
}
}
}
} `
And
`
func findSpeedLimit(location: CLLocationCoordinate2D, preparedGeometries: [PreparedGeometry], featureProperties: [[String: Any]]) -> Int? {
// Create a Point object from the location coordinates
let point = Point(x: location.longitude, y: location.latitude)
// Loop through the prepared geometries
for (index, preparedGeometry) in preparedGeometries.enumerated() {
do {
// Check if the prepared geometry contains the point
if try preparedGeometry.contains(point) {
// Check if the properties have a speedLimitZoneValue key
let properties = featureProperties[index]
if let speedLimit = properties["speedLimitZoneValue"] {
let speedLimitString = String(describing: speedLimit)
if let number = Int(speedLimitString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) {
return number
}
} else {
print("Feature \(index) has no 'speedLimitZoneValue' property")
}
}
} catch {
print("Error checking if prepared geometry contains point: \(error)")
}
}
return nil
}
`
As I said before at this stage it looks miles quicker for simple test locations however I will need to test it in a live updating context.
I will mention though that the bootup time of the program still is incredibly slow (and was even before the changes). Is this usual with the GeoSwift library. See my current loadspeedlimits function. I do call it in the App.swift file could that be the issue?
` @main struct SafeDrivingMapsApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var speedLimitStore = SpeedLimitStore()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(speedLimitStore)
}
}
}
`
Fixed the bootup time by loading the data asynchronously in the background. At this stage prepared geometry seems to be working perfectly.
Is there an efficient built in method for finding which features within a geojson file contains a point without resorting to inefficient linear search? I've got a very large file that I'm having to linear search every location update and it's inducing heaps of lag.