Swift mkmapview get zoom level, width or radius of visible map area from latitude longitude delta - ios

I am in a confusion, on how to get a zoom level and radius of a visible area of the map (using mapkit mapview).
Here is what I am looking for (either of them or both, as needed) -
Zoom level, is to see at what level of the map is being shown to the users and with that information, I want to display the custom pins in the visible area. If zoom level is high, I need to show the actual logos of some commerce stores as pins. If zoom level is low, I need to show colored dots instead of logos.
As of now, I am using let updatedRadius = (mapView.camera.altitude)/1000 to get altitude of the camera, and if the updatedRadius value is > 25.0, I am showing colored dots. Below 25.0, I show the logos. I am doing this in regionDidChanged
Is this approach correct?
Radius, is to send it as a parameter to my REST API to fetch the list of places within that radius. When user zooms out on the map, visible area increases and so the REST API needs bigger radius to return the places covered in that area.
Ultimately, what should happen is, whenever user zooms out, then the radius changes. I need to send this changed radius to my REST to get an updated list.
What are latitude longtitude deltas, can we get radius/width of visible area using these values?
let latitudeDeltaVal = mapView.region.span.latitudeDelta
let longitudeDeltaVal = mapView.region.span.longitudeDelta
Can someone throw some light on what needs to be done please?

Since you need to call the api when the region changes you need to calculate the radius in mapView's delegate function, RegionDidChange.
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
let centralLocation = CLLocation(latitude: mapView.centerCoordinate.latitude, longitude: mapView.centerCoordinate.longitude)
self.centralLocationCoordinate = mapView.centerCoordinate
print("Radius - \(self.getRadius(centralLocation))")
}
func getRadius(centralLocation: CLLocation) -> Double{
let topCentralLat:Double = centralLocation.coordinate.latitude - mapView.region.span.latitudeDelta/2
let topCentralLocation = CLLocation(latitude: topCentralLat, longitude: centralLocation.coordinate.longitude)
let radius = centralLocation.distanceFromLocation(topCentralLocation)
return radius / 1000.0 // to convert radius to meters
}

To account for both landscape and portrait orientations, and/or situations where the map orientation is close to Northeast, Northwest, Southwest, Southeast, and to enclose the full screen up to the corners, one should consider both latitudeDelta and longitudeDelta:
func getRadius() -> Double{
let centralLocation = CLLocation(latitude: mapView.region.center.latitude, longitude: mapView.region.center.longitude)
let cornerOfMap = CLLocation(latitude: centralLocation.coordinate.latitude + mapView.region.span.latitudeDelta , longitude: centralLocation.coordinate.longitude + mapView.region.span.longitudeDelta)
let radius = centralLocation.distance(from: cornerOfMap)
return radius / 1000.0 // to convert radius to meters
}

Related

Get google map currently rotated angle by user in iOS

I am using google map in the application. When I do not rotate map everything works fine. But when I rotate map I am getting problem as shown in image. To solve that I need to get the google map currently rotated angle by the user. I need to get this so that I can place the marker back on the map at the same angle. Currently my map angle after rotation and my overlay seems to be different after placing plan image on map even when I am getting correct top left & bottom right corner coordinates.
My code
let topCoordinate = CLLocationCoordinate2D(latitude: topLattitude, longitude: topLongitude)//top
let bottomCoordinate = CLLocationCoordinate2D(latitude: bottomLattitude, longitude: bottomLongitude)//bottom
overlayBounds = GMSCoordinateBounds(coordinate: topCoordinate, coordinate:
bottomCoordinate)
let overlay = GMSGroundOverlay(bounds: overlayBounds, icon: planImage)
overlay.map = mapView
GMSMapViewDelegate
Bearing of the camera, in degrees clockwise from true north.
func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
print(position.bearing)
}
I have solved my problem by changing the overlay method as
let zoomLevel = (object["image_zoom_level"] as? NSString)?.doubleValue
let overlay = GMSGroundOverlay(position: centreCoordinate, icon: planImage, zoomLevel: CGFloat(zoomLevel!))
overlay.map = mapView
if let angle = (object["rotation_angle"] as? NSString)?.doubleValue{
overlay.bearing = angle
}
Instead of using Overlay bounds I have just used center coordinate and placed overlay image using image zoom level and rotation angle. By this, my image is placing in proper angle and at a proper place. This works fine if the Image size is correct as you want place and the center point is accurate and precise.

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?

User Current Location on specific position on MAP

MKMapView
CoreLocation
Swift3.0
I have a requirement like this:
Creating an application in which I am getting Lat and Long for various location from webservice . Ploting these locations on map with Custom AnnotationView . Apart from this, I'm showing User Current Location with Custom Image.
Here everything working fine.
Now, when user moves then User Current Location Pin changes its position so there is an important requirement for this, I need to make this Current Location position in such a way that it stick in the bottom of map (just 100 pixel) above from bottom.
I did Some RND and find out few useful links:
Centering MKMapView on spot N-pixels below pin
How can I center my mapview so that the selected pin is not in the middle of the map but in 1/3 of it?
set current location icon lower side in MKMapView
But this not working anymore.
Kindly suggest how can I set user Current Location pin in iOS map in such a way that it always at the bottom of Map (100 pixel above from Bottom)
Required Pin position:
Currently it is showing in other position.
Note - **Dont want Google Map for such functionality.
If you have been using something like this mapView.setUserTrackingMode(.Follow, animated: true) then user location will be always displayed in the center of the map, zoom/pan will be automatically adjusted whenever user location is updated.
You need to disable the UserTrackingMode and then manually adjust the map zoom/pan using func setVisibleMapRect(_ mapRect: MKMapRect, edgePadding insets: UIEdgeInsets, animated animate: Bool) each time whenever user location is changed.
I used following code in similar situation
let annotationPoint = MKMapPointForCoordinate(mapView.userLocation.coordinate);
var zoomRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 1, 1);
annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 1, 1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
mapView.setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(40, 50, 160, 50), animated: true)
p.s. edgePadding is your friend in this case.
Here is the trick to use to position the map in such a way that UserLocation pin gets adjusted automatically :
func setUserLocationOnLowerPositiononMap(coordinate: CLLocationCoordinate2D) {
var region = MKCoordinateRegion(center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
region.center = coordinate
self.map.setRegion(region, animated: true)
//self.map.moveCenterByOffSet(offSet: CGPoint(x: 0, y: -130), coordinate: coordinate)
self.map.moveCenterByOffSet(offSet: CGPoint(x: 0, y: SCREEN_HEIGHT - SCREEN_HEIGHT * 4/3 + 0 ), coordinate: coordinate)
}

Take an action if MapKit Map has zoomed into users location

I am using this to zoom into the users location at startup.
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
var locationArray = locations as NSArray
var locationObj = locationArray.lastObject as CLLocation
var coord = locationObj.coordinate
var newRegion = MKCoordinateRegion(center: coord, span: MKCoordinateSpanMake(spanX, spanY))
mapView.setRegion(newRegion, animated: true)
}
Now I'd like to take an action if the map has been zoomed into the users location. I've tried to use the mapDidChange delegate method, but this is fired a few times. Any thoughts how to do this the clever way? (iOS 8 / Swift)
Just In my opinion , you can compare newRegion's span values with map view's current span values in regionDidChangeAnimated. If span values are the same, you can secondly play with "mapview.region". May be getting visible mapView.region and comparing it with newRegion or getting centre coordinate of current visible region and comparing it with current location's coordinate can work.

Resources