I study this app in swiftUI which should allow a farmer to track his position while sowing.
The code take the GPS location of the user and for each location received, i am calculating the coordinate at certain distance and bearing, exactly at 90 degree and 270 degree from the course of the user. (distance is the size of the seeder divided by 2) I save this calculate coordinate in an arrayLocationOffset and this coordinate will be used for draw a polygon to allow the farmer understand what it has already been sown.
On the didUpdateLocations func I'm running the following code:
func registraPosizione (loc: [CLLocation], dimensioneInKM: Double){
let diminKM = dimensioneInKM/1000 // size of half seeder transform in km ..
// to be use to calc the offset
let lastPos = loc.last// last updated position
if lastPos != nil {
let coord = CLLocationCoordinate2D(latitude: (lastPos?.coordinate.latitude)!, longitude: (lastPos?.coordinate.longitude)!)
arrayLocations.append(coord) // add location to array location : loccoord2d
// calc offset 90 and 270 for the polygon
createCoordinatePoly(course: lastPos!.course, dimensioneInKM: diminKM)
}
}
here how I calculate the offset coordinate:
func coord270(coord: CLLocationCoordinate2D, course: Double, dimensionKM: Double) -> CLLocationCoordinate2D{
let correctBrg = Angle(degrees: course).radians + Angle(degrees: 270).radians
let brginDeg = Angle(radians: correctBrg)
let brgCorrect = convertToPositiveAngle(brginDeg).degrees. // if close to 360..
let lat1 = Angle(degrees: coord.latitude).radians
let lon1 = Angle(degrees: coord.longitude).radians
let R = 6378.1 //#Radius of the Earth
let d = Double(dimensionKM) //#Distance in km
let lat270 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(brgCorrect))
let lon270 = lon1 + atan((sin(brgCorrect)*sin(d/R)*cos(lat1))/(cos(d/R)-sin(lat1)*sin(lat270)))
let finalLat270 = Angle(radians: lat270).degrees
let finalLon270 = Angle(radians: lon270).degrees
let coordOff = CLLocationCoordinate2D(latitude: finalLat270, longitude: finalLon270)
debugPrint("creato offstet a 270 \(brgCorrect) ---\(finalLat270) \(finalLon270)")
return coordOff
}
func coord90(coord: CLLocationCoordinate2D, course: Double, dimensionKM: Double) -> CLLocationCoordinate2D{
let correctBrg = Angle(degrees: course).radians + Angle(degrees: 90).radians
let brginDeg = Angle(radians: correctBrg)
let brgCorrect = convertToPositiveAngle(brginDeg).degrees // if close to 360..
let lat1 = Angle(degrees: coord.latitude).radians
let lon1 = Angle(degrees: coord.longitude).radians
let R = 6378.1 //#Radius of the Earth
let d = Double(dimensionKM) //#Distance in km
let lat90 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(brgCorrect))
let lon90 = lon1 + atan((sin(brgCorrect)*sin(d/R)*cos(lat1))/(cos(d/R)-sin(lat1)*sin(lat90)))
let finalLat90 = Angle(radians: lat90).degrees
let finalLon90 = Angle(radians: lon90).degrees
let coordOff = CLLocationCoordinate2D(latitude: finalLat90, longitude: finalLon90)
debugPrint("creato offstet 90 \(brgCorrect) ---\(finalLat90) \(finalLon90)")
return coordOff
}
and here the func used to generate the array contain all coordinates to draw the polygon over the map in the correct order.
func createCoordinatePoly(course: Double, dimensioneInKM:Double) {
arrayLocationOffset = []
let polypoint = arrayLocations.count
debugPrint("Course now is \(course)")
DispatchQueue.global().sync {
for i in 0..<polypoint {
let coord = arrayLocations[i]
// calc LH
if course <= 0 {
// wait that course have value
let lhCoord = coord270(coord: coord, course: 360, dimensionKM: dimensioneInKM)
arrayLocationOffset.append(lhCoord)
} else {
let lhCoord = coord270(coord: coord, course: course, dimensionKM: dimensioneInKM)
arrayLocationOffset.append(lhCoord)
}
}
var reversIndex = polypoint-1
for _ in 0..<polypoint {
let coord = arrayLocations[reversIndex]
// calc RH
if course <= 0 {
let RhCoord = coord90(coord: coord, course: 360, dimensionKM: dimensioneInKM)
arrayLocationOffset.append(RhCoord)
reversIndex -= 1
} else {
let RhCoord = coord90(coord: coord, course: course, dimensionKM: dimensioneInKM)
arrayLocationOffset.append(RhCoord)
reversIndex -= 1
}
}
}
}
and final I have UIViewRepresentable to display the MapView
struct MapViewNew: UIViewRepresentable {
var receivedCoordinate : [CLLocationCoordinate2D]
let arrayOffset : [CLLocationCoordinate2D]
var myMap = MKMapView(frame: .zero)
func makeUIView(context: Context) -> some MKMapView {
myMap.showsUserLocation = true
myMap.delegate = context.coordinator
myMap.mapType = .satellite
myMap.userTrackingMode = .follow
return myMap
}
func updateUIView(_ uiView: UIViewType, context: Context) {
creaLinea(from: uiView) // center line
creaPoly(from: uiView) // polygon representing the seeder
}
func makeCoordinator() -> MapViewCoordinator {
MapViewCoordinator(self)
}
private func creaLinea(from mapView: MKMapView) {
let poly = MKPolyline(coordinates: receivedCoordinate, count: receivedCoordinate.count)
mapView.addOverlay(poly)
}
func creaPoly(from mapView : MKMapView){
// every time a new set of offset coordinate is calc,
// i delate the old polygon and add the new on
let over = mapView.overlays
for overlay in over {
if overlay is MKPolygon {
mapView.removeOverlay(overlay)
}
}
// creat new polygon
let poligon = MKPolygon(coordinates: arrayOffset, count: arrayOffset.count)
mapView.addOverlay(poligon)
}
//MARK : COORDINATOR CLASS
class MapViewCoordinator: NSObject, MKMapViewDelegate {
private let map: MapViewNew
init(_ control: MapViewNew) {
self.map = control
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolyline {
let render = MKPolylineRenderer(overlay: overlay)
render.strokeColor = .blue
render.lineWidth = 2
return render
} else if overlay is MKPolygon {
let render = MKPolygonRenderer(overlay: overlay as! MKPolygon)
render.strokeColor = .red
render.fillColor = UIColor.black.withAlphaComponent(0.5)
render.lineWidth = 1.0
return render
}
return MKOverlayRenderer()
}
}
}
the result is pretty much ok(see pict, only 3 coordinate manually add on the simulator)..
my question is:
is there any better way I could use to draw that area representing the job done by the seeder? or this the only way... can I improve something? or something different I can use to draw that shape along the path of the seeder?
thanks in advance .. looks for your help.. still very beginner in coding...