Calculate map region when drive in circle - ios

While driving I record the different coordinates during the drive. If I drive between two points that is generally in a straight line, then the map span of the following code works well but if I drive around the block then the map span doesn't show the route. I can generally see that since I am using the first and last coordinate this would be why. The region is then put into a snapshot to generate an image of the map. What I'm struggling with is being able to use all coordinates to calculate the needed span.
//calculate region
let span = MKCoordinateSpan.init(latitudeDelta: fabs(startCoord.latitude - endCoord.latitude) * 1.6, longitudeDelta: fabs(endCoord.longitude - startCoord.longitude) * 1.6)
let resd = CLLocationCoordinate2D.init(latitude: startCoord.latitude - (startCoord.latitude - endCoord.latitude) * 0.5, longitude: startCoord.longitude + (endCoord.longitude - startCoord.longitude) * 0.5)
let options = MKMapSnapshotter.Options()
options.scale = UIScreen.main.scale
options.region = MKCoordinateRegion.init(center: resd, span: span);
options.size = CGSize(width: 600, height: 450) //map size
options.showsBuildings = true
options.showsPointsOfInterest = true
I found this bit of code on another question
func regionFor(coordinates coords: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
var r = MKMapRect.null
for i in 0 ..< coords.count {
let p = MKMapPoint(coords[i])
r = r.union(MKMapRect(x: p.x, y: p.y, width: 0, height: 0))
}
return MKCoordinateRegion(r)
}
But I couldn't get it to work to show the circle either. Anyone have an idea of how I could do this?

A region span is a rectangle. Finding a bounding rectangle is a simpler problem than finding a circle that fully encloses a set of points.
Just set up max and min latitude and longitude variables that start with values that are out of range in the opposite direction, loop through your points, and when a points co-ord is >max or <min, replace that value. When you're done, use the min-max latitude and min-max longitude values to define your region span.

Related

how to detect difference between 2 points on the map and consider the zoom?

I am using a map with some annotations, I want to group the nearest annotations in annotation group to not overlap in the design in case of user zoom out, but my problem is to how to know the distance between annotations and this distance should change when zooming in and out.
the distance should be between points with x and y formate note meters
So my question is to how to catch the difference between 2 points on the map and consider the zoom
// convert location to cLLocation
let cLLocation1 = CLLocation(latitude: post1Location?.lat ?? 0, longitude: post1Location?.lng ?? 0)
let cLLocation2 = CLLocation(latitude: post2Location?.lat ?? 0, longitude: post2Location?.lng ?? 0)
// this is return the dinsactence in metres but i don't need that
let distance = cLLocation1.distance(from: cLLocation2)
let annotaionPoint1 = MKMapPoint(cLLocation1.coordinate)
let annotaionPoint2 = MKMapPoint(cLLocation2.coordinate)
let xDistance = max(annotaionPoint1.x, annotaionPoint2.x) - min(annotaionPoint1.x, annotaionPoint2.x)
let yDistance = max(annotaionPoint1.y, annotaionPoint2.y) - min(annotaionPoint1.y, annotaionPoint2.y)
this is working but zoom in and zoom out no effect so I need zoom to make change
if min(xDistance, yDistance) <= 32 {
/// action
}

HEREMaps iOS: Zoom to specific point

I need to zoom NMAMapView to specific point inside map bounds. Let's say we have some marker as subview of this map with position pinPoint: CGPoint.
I tried to use transformCenter property of NMAMapView like this:
let transformCenter = CGPoint(x: pinPoint.x / mapView.bounds.width, y: pinPoint.y / mapView.bounds.height) // x and y are in range [0, 1]
mapView.transformCenter = transformCenter
mapView.set(zoomLevel: mapView.zoomLevel + 1, animation: .linear)
But this code zooms map to its frame center, not to pinPoint.
I also tried to zoom map to needed point with code below:
guard let coordinates = mapView.geoCoordinates(from: pinPoint) else {
return
}
mapView.set(coordinates: coordinates, to: pinPoint, animation: .linear, zoomLevel: mapView.zoomLevel + 1)
But this also doesn't work correctly: after zooming, geo coordinates below marker were changed after each zoom step.
Please try to with this code, this code is just for your reference, You need to modify as per your requirements.
// create geo coordinate
let geoCoordCenter = NMAGeoCoordinates(latitude: 49.260327, longitude: -123.115025)
// set map view with geo center
self.mapView.set(geoCenter: geoCoordCenter, animation:NMAMapAnimation.none)
// set zoom level
self.mapView.zoomLevel = 13.2
Please check more example GitHub.

Calculate bearing in MKMapView gives wrong value while crossing 180 meridian

I need to draw lines to demonstrate transportation of goods on apple maps. To clarify start- and end-point, I draw a little arrowhead on the destination side.The arrowhead is drawn separately but it is reversed in one case.
>-->-->-->-
instead of
<--<--<--<-
I am using MKMapView and MKPolyline to draw lines. I am using MKOverlay to add direction arrows. The steps I follow are,
calculate bearing of
Source : CLLocationCoordinate2D(latitude: -33.8392932, longitude: 151.21519799999999)
Destination: CLLocationCoordinate2D(latitude: 39.645516999999998, longitude: -104.598724)
using the following function
open static func getDirectionOf( _ supplyLineWithCoordinates: [CLLocationCoordinate2D]) -> CGFloat {
guard let sourceCoordniate = supplyLineWithCoordinates.first,
let destinationCoordniate = supplyLineWithCoordinates.last else {
fatalError("Coordinates of supply line not found")
}
let sourcePoint: MKMapPoint = MKMapPointForCoordinate(sourceCoordniate)
let destinationPoint: MKMapPoint = MKMapPointForCoordinate(destinationCoordniate)
let x: Double = destinationPoint.x - sourcePoint.x
let y: Double = destinationPoint.y - sourcePoint.y
var arrowDirection = CGFloat(fmod(atan2(y, x), 360.0))
if arrowDirection < 0.0 {
arrowDirection += 2 * .pi
}
return arrowDirection
}
Rotate the arrow image and add it as the map overlay. The directions are calculated correctly in most of the cases, however, when I select the line shown below the direction is displayed 180 opposite. It starts from Sydney, Australia and ends in Denver, US
When trying to display the region with this two locations in mapView.setVisibleMapRect these region is not displayed, mapview tries to display region starting from Sydney (Australia) to Denver(US) through Asia and Europe, while it should display the map area I have attached above. If you have suggestions for optimisation, feel free to mention it.
I think this might be the reason, the direction should be calculated along the red line but it being calculated along the green line. Both lines are drawn by connecting same location coordinates in map. Any known workaround for this?
I solved it in a dirty way by converting coordinate to CGPoint and then calculating bearing between Points.
let destinationPoint = mapView.convert(destination, toPointTo: nil)
let sourcePoint = mapView.convert(source, toPointTo: nil)
let bearing = atan2(sourcePoint.y - destinationPoint.y, sourcePoint.x - destinationPoint.x) - .pi
Caution: This calculation will go wrong when map is rotated

MKMapView - how to calculate radius consistent with set-up

I draw a circle on an MKMapView. But how do I calculate the radius of this circle such that it equals the value of the radius used to draw the circle in the first place?
func setArea(_ center:CLLocationCoordinate2D, radius:CLLocationDistance) {
let area = MKCircle(center:center, radius:radius / LocationViewController.kRadiusInset)
mapView.setVisibleMapRect(mapView.mapRectThatFits(area.boundingMapRect), animated:false)
}
I've tried the following but it returns a value very slightly larger than radius passed to setArea. It calculates the distance from the center of the map to the left-hand edge.
// getRadius
let distance = middle.distance(from: edge)
let middle = CLLocation(latitude: mapView.region.center.latitude, longitude: mapView.region.center.longitude)
let edge = CLLocation(latitude: mapView.region.center.latitude, longitude: mapView.centerCoordinate.longitude - (mapView.region.span.longitudeDelta * 0.5))
let distance = middle.distance(from: edge)
If I pass in '4000' metres to setArea() and then afterwards calculate the map's radius (say, within mapViewDidFinishRenderingMap() I get 4010.61219348448
Why the discrepancy?
I replaced the use of a MKCircle with the following:
let region = MKCoordinateRegionMakeWithDistance(center, radius * 2, radius * 2)
mapView.region = region
This is less inaccurate. With radius == 4000 fed into this I get 4000.00048143541 after I set my MKMapView's region. This difference will creep into the user-interface but not nearly as quickly as my first approach.
Can anyone get closer?

How to create an MKMapRect given two points, each specified with a latitude and longitude value?

I have a custom class that extends NSObject and implements the MKOverlay protocol. As a result, I need to implement the protocol's boundingMapRect property which is an MKMapRect. To create an MKMapRect I can of course use MKMapRectMake to make one. However, I don't know how to create an MKMapRect using that data I have which is two points, each specified by a latitude and longitude. MKMapRectMake's docs state:
MKMapRect MKMapRectMake(
double x,
double y,
double width,
double height
);
Parameters
x
The point along the east-west axis of the map projection to use for the origin.
y
The point along the north-south axis of the map projection to use for the origin.
width
The width of the rectangle (measured using map points).
height
The height of the rectangle (measured using map points).
Return Value
A map rectangle with the specified values.
The latitude and longitude values I have to spec out the MKMapRect are:
24.7433195, -124.7844079
49.3457868, -66.9513812
The target MKMapRect would therefore need to spec out an area that looks about like this:
So, to reiterate, how do I use my lat/lon values to create an MKMapRect that I can set as MKOverlay protocol's #property (nonatomic, readonly) MKMapRect boundingMapRect property?
This should do it:
// these are your two lat/long coordinates
CLLocationCoordinate2D coordinate1 = CLLocationCoordinate2DMake(lat1,long1);
CLLocationCoordinate2D coordinate2 = CLLocationCoordinate2DMake(lat2,long2);
// convert them to MKMapPoint
MKMapPoint p1 = MKMapPointForCoordinate (coordinate1);
MKMapPoint p2 = MKMapPointForCoordinate (coordinate2);
// and make a MKMapRect using mins and spans
MKMapRect mapRect = MKMapRectMake(fmin(p1.x,p2.x), fmin(p1.y,p2.y), fabs(p1.x-p2.x), fabs(p1.y-p2.y));
this uses the lesser of the two x and y coordinates for your start point, and calculates the x/y spans between the two points for the width and height.
For any number of coordinates, in Swift (4.2):
// Assuming `coordinates` is of type `[CLLocationCoordinate2D]`
let rects = coordinates.lazy.map { MKMapRect(origin: MKMapPoint($0), size: MKMapSize()) }
let fittingRect = rects.reduce(MKMapRect.null) { $0.union($1) }
As noted by #Abin Baby, this will not take wrap around into account (at +/-180 longitude & +/-90 latitude). The result will still be correct, but it will not be the smallest possible rectangle.
Based on Patrick's answer an extension on MKMapRect:
extension MKMapRect {
init(coordinates: [CLLocationCoordinate2D]) {
self = coordinates.map({ MKMapPointForCoordinate($0) }).map({ MKMapRect(origin: $0, size: MKMapSize(width: 0, height: 0)) }).reduce(MKMapRectNull, combine: MKMapRectUnion)
}
}
This is what worked for me.
No trouble even when crossing between +/-180 longitude and +/-90 latitude.
Swift 4.2
func makeRect(coordinates:[CLLocationCoordinate2D]) -> MKMapRect {
var rect = MKMapRect()
var coordinates = coordinates
if !coordinates.isEmpty {
let first = coordinates.removeFirst()
var top = first.latitude
var bottom = first.latitude
var left = first.longitude
var right = first.longitude
coordinates.forEach { coordinate in
top = max(top, coordinate.latitude)
bottom = min(bottom, coordinate.latitude)
left = min(left, coordinate.longitude)
right = max(right, coordinate.longitude)
}
let topLeft = MKMapPoint(CLLocationCoordinate2D(latitude:top, longitude:left))
let bottomRight = MKMapPoint(CLLocationCoordinate2D(latitude:bottom, longitude:right))
rect = MKMapRect(x:topLeft.x, y:topLeft.y,
width:bottomRight.x - topLeft.x, height:bottomRight.y - topLeft.y)
}
return rect
}

Resources