I'm working on an application that tracks movement in Swift, for traveling from point A: locationOne, to point B: locationTwo. The MapView should display both points on the map, centered in between them.
I implemented a function to determine the center location as per this link, though I had to modify it to function in Swift. The function is called findCenterPoint.
Then, I set the mapView's region to an MKCoordinateRegion. This region is created like so: let region = MKCoordinateRegionMakeWithDistance(center, 2*distanceOne, 2*distanceTwo), and then the mapView's region is set like this: mapView.setRegion(region, animated: false)
I multiply the distance by 2 so that we have a margin on the sides of the two locations (the annotation for locationOne, and locationTwo: the user location)
Here's the problem: If the two points are very far away, i.e. New York and somewhere in Australia (let's just say general Australia) the app can't display both points, because they don't fit on the mapView. So instead, only one pin is visible, because the other one is off the screen.
Screenshots of problem
I need both of those points to show up without scrolling around the map.
I also have a degreesToRadians function which is used in the code below.
TL;DR: My app's mapView isn't big enough to fit and display two far away points on the map, and it is already zoomed out to the max.
Here's the actual code:
var center = findCenterPoint(firstLocation.coordinate, locTwo: placemark.coordinate)
let earthRadius: Double = 6371000
distance = degreesToRadians(placemark.coordinate.latitude - firstLocation.coordinate.latitude)
lonDistance = degreesToRadians(placemark.coordinate.longitude - firstLocation.coordinate.longitude)
let region = MKCoordinateRegionMakeWithDistance(center, earthRadius*distance, earthRadius*lonDistance)
mapView.setRegion(region, animated: false)
Related
I am currently making an app with a map that should focus on a certain location only. I would like the user to not be able to zoom out or pan out of this area so they can keep their focus on the image overlay that i have put over this area.
In order to get the app to start off from the location that i want and not some random map, I used a tutorial from Ray Wenderlich: https://www.raywenderlich.com/425-mapkit-tutorial-overlay-views
How would I acoomplish my task based on the code that is written in the tutorial above? I have completed the tutorial, so I am looking for help in adding any code and identifying where and what kind of code to put.
I found other tutorials on this topic unhelpful because they were for other map types like Google maps or MapBox. The apple website about MapKit and MaximumZ does not help me very much either.
I am a beginner in XCode and Swift, and have only had little bit of experience in Python previously. I was hoping limiting the zoom and user access to parts of the maps would be easier...
override func viewDidLoad() {
super.viewDidLoad()
let latDelta = park.overlayTopLeftCoordinate.latitude -
park.overlayBottomRightCoordinate.latitude
// Think of a span as a tv size, measure from one corner to another
let span = MKCoordinateSpanMake(fabs(latDelta), 0.0)
let region = MKCoordinateRegionMake(park.midCoordinate, span)
mapView.region = region
}
This is what I have so far for getting the app to startup on the location that I want, using a rectangle that bounds the area that I am looking to restrict the user to.
The MKMapView has a delegate MKMapViewDelegate. This protocol has a function called:
func mapViewDidChangeVisibleRegion(_:)
This method is called whenever the user scrolls or zooms the map. In this method you can specify the behavior of the map. You can, for instance set a specific region that you want the map to zoom into and specify the maximum level of zoom allowed.
In the function mapViewDidChangeVisibleRegion(_:) you can then check to what latitudeDelta and longitudeDelta the map can zoom into. If the delta's go below or above a certain level you can lock the zooming by setting the region with something like this:
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
if mapView.region.span.latitudeDelta < 0.4 {
regionSpan = MKCoordinateSpan(latitudeDelta: 0.4, longitudeDelta: 0.5)
let mapRegion = MKCoordinateRegion(center: self.centerCoordinate, span: self.regionSpan)
self.trackMapView.setRegion(mapRegion, animated: true)
}
}
I using Apple's MapKit and MKMapView to show a location on screen. The function I am using is:
func displayLocation() {
locationMap.setRegion(MKCoordinateRegion(center: CLLocationCoordinate2DMake(userConnected.siteConnectedLat!, userConnected.siteConnectedLong!), span: MKCoordinateSpanMake(0.05, 0.05)), animated: true)
let locationPin = CLLocationCoordinate2D(latitude: userConnected.siteConnectedLat!, longitude: userConnected.siteConnectedLong!)
let annotation = MKPointAnnotation()
annotation.coordinate = locationPin
locationMap.addAnnotation(annotation)
locationMap.showAnnotations([annotation], animated: true)
}
Is is then called in the viewDidLoad method of the View Controller. Apple's documentation says that the span should change the zoom but even changing the span is putting minimum effect on the map. I want it to be zoomed out enough so that we can clearly see like 3-4 European countries, i.e, a significant amount of zoom-out.
This is what my map looks like in simulator:
This is how it looks like after I press enlarge:
You need to set the region of the map in viewDidAppear, not viewDidLoad. When viewDidLoad is called, the map view has just been loaded - the map hasn't been rendered yet, so you can't set its region.
Another thing to change is the span. (0.05, 0.05) seems too small a span to show 3-4 countries. You should try something bigger, like (5, 5). Remember that these numbers represent in degrees the width and height of the map region.
I have a MKMapView that I configure with:
static let STARTING_MAP_RANGE: Double = 1000 // meters
. . .
let region: MKCoordinateRegion = MKCoordinateRegionMakeWithDistance(location,
MapViewController.STARTING_MAP_RANGE,
MapViewController.STARTING_MAP_RANGE)
mapView.setRegion(region, animated: true)
I add an annotation for the current location and it all looks fine. When I add an annotation for other points in the visible region, the MKMapView zooms in to the minimum area needed to show all the annotations.
The weird thing is that I tried to figure out where this was happening by printing out the bottom left and top right latitude and longitude like this:
private func printMapRegion(caller: String)
{
let mapRect = mapView.visibleMapRect;
let bottomLeft = MKCoordinateForMapPoint(MKMapPointMake(mapRect.origin.x, MKMapRectGetMaxY(mapRect)))
let topRight = MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMaxX(mapRect), mapRect.origin.y))
print("\(caller): (\(bottomLeft.latitude),\(bottomLeft.longitude)) -- (\(topRight.latitude),\(topRight.longitude))")
}
When I run this before and after setting the annotations, I get identical values, despite seeing the map zoom on the screen (both in the simulator and my iPhone).
I added a refresh button to reset the map. It works as far as zooming the map out, but it also reports that the bottom left and top right coordinates are the same before and after zooming.
Is there something wrong with my understanding of visibleMapRect?
I found that the reason for the zooming is that I was adding the annotations like this:
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(annotations)
mapView.showAnnotations(mapView.annotations, animated: true)
Removing the call to showAnnotations eliminated the problem. I'm still curious as to why visibleMapRect is reporting the same bounding coordinates after the visible zoom.
I am using mapbox map in my iOS app. I added annotation in mapbox map by using this class MGLPointAnnotation.
Now the problem is i am unable to change its location. This is how i changing annotation location. But it is not moving. I need to remove first old one and again need to add new annotation but i don't want to do this.Please let me know how can i do this.
car.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lng)
I want the movement of annotation when location changed of annotation with smooth animation.
Also when there is a change in location i'm changing center of my mapbox map with camera. Let me know i am doing right or not.
// Optionally set a starting point.
bMbMapView.setCenter(cord, zoomLevel: 7, direction: bearing, animated: true)
bMbMapView.setDirection(bearing, animated: true)
// Create a camera that rotates around the same center point, rotating 180°.
// `fromDistance:` is meters above mean sea level that an eye would have to be in order to see what the map view is showing.
let camera = MGLMapCamera(lookingAtCenter: cord, fromDistance: 1000, pitch: 7, heading: 90)
// Animate the camera movement over 5 seconds.
bMbMapView.setCamera(camera, withDuration: 1, animationTimingFunction: CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))
A MGLAnnotationView is a UIView, so you should be able to animate changes with Core Animation: https://github.com/mapbox/mapbox-gl-native/issues/8378#issuecomment-287910768
Another option is to use a style source and layer, then update the source using a timer. This would likely be less smooth than using an animated annotation view.
I'm querying my server for a list of locations that are closest to the user based on his observable portion of the mapview.
I'm not entirely sure on how to do this, but should I send the center lat and long of the mapview's visible region and then search for results based on nearest radius?
I know that MKMapView has two properties called region and visibleMapRect. Should I use one of those, and if so, which one is more appropriate based on my question?
EDIT
I'm looking to implement functionality that's identical to the apple maps and Yelp app when you search for nearby locations and it shows you what's relevant based off the visible portion of the map view.
EDIT 2
I have seen a lot of people break up the visible portion of their mapview into quadrants, most commonly NW,NE,SW and SE. However, I'm still not entirely sure why theyr'e doing this. I would like to know the best way to query the back end, which contains a lat and long for each location, to find the locations which exist on the mapview.
I have looked everywhere on stackoverflow and I found a similar question here. However, it doesn't answer my question and there's no mention of visibleMapRect because it's over 5 years old.
Any help is tremendously appreciated.
So you would need to provide your server call with these 2 coord bottomleft and topright 4 points all together.
if lat of location > bottomleft.lat and if lat of location < topright.lat
if long of location > bottomleft.long and if long of location < topright.long
iOS Code would look like this the make server call with these four variables as parameters
fun viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
}
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// Using region
var span: MKCoordinateSpan = mapView.region.span
var center: CLLocationCoordinate2D = mapView.region.center
// This is the farthest Lat point to the left
var farthestLeft = center.latitude - span.latitudeDelta * 0.5
// This is the farthest Lat point to the Right
var farthestRight = center.latitude + span.latitudeDelta * 0.5
// This is the farthest Long point in the Upward direction
var farthestTop = center.longitude - span.longitudeDelta * 0.5
// This is the farthest Long point in the Downward direction
var farthestBottom = center.longitude + span.longitudeDelta * 0.5
var SWCoord = MKCoordinateForMapPoint(farthestBottom, farthestLeft)
var NECoord = MKCoordinateForMapPoint(farthestTop, farthestRight)
// Using visibleMapRect
var mapRect = mapView.visibleMapRect
// This is the top right Coordinate
var NECoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMaxX(mapRect), y: mapRect.origin.y)
// This is the bottom left Coordinate
var SWCoord = getCoordinateFromMapRectanglePoint(mapRect.origin.x, y: MKMapRectGetMaxY(mapRect))
// Not needed but could be useful
// var NWCoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMinX(mapRect), y: mapRect.origin.y)
// var SECoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMaxX(mapRect), y: MKMapRectGetMaxY(mapRect))
}
func getCoordinateFromMapRectanglePoint(x: Double, y: Double) -> CLLocationCoordinate2D {
var mapPoint = MKMapPointMake(x, y)
return MKCoordinateForMapPoint(mapPoint)
}
If you have anymore questions just comment
EDIT 2 I have seen a lot of people break up the visible portion of their mapview into quadrants, most commonly NW,NE,SW and SE.
However, I'm still not entirely sure why theyr'e doing this. I would
like to know the best way to query the back end, which contains a lat
and long for each location, to find the locations which exist on the
mapview.
I'm not sure if this is what you're referring to but they could be using quadtrees as a way to efficiently search the locations of a huge amount of coordinates.
This post from Thoughtbot demonstrates how to use quadtrees to display thousands of place coordinates as clusters on the map and it includes gifs that very succinctly explain how it works:
A quad tree is a data structure comprising nodes which store a bucket of points and a bounding box. Any point which is contained within the node’s bounding box is added to its bucket. Once the bucket gets filled up, the node splits itself into four nodes, each with a bounding box corresponding to a quadrant of its parents bounding box. All points which would have gone into the parent’s bucket now go into one of its children’s buckets.
Source: How To Efficiently Display Large Amounts of Data on iOS Maps