How to install custom annotation on the map depending on the zoom - swift

1) I need to set a custom annotation, depending on the zoom on the map, they will be placed throughout the track on the map. (Arrows showing the direction).
my code
class customPin: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(pinTitle:String, pinSubTitle:String, location:CLLocationCoordinate2D) {
self.title = pinTitle
self.subtitle = pinSubTitle
self.coordinate = location
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "customannotation")
annotationView.image = UIImage(named:"mov_on_2")
annotationView.canShowCallout = true
return annotationView
}

The best way I have found to do this is to determine the distance of screen width shown by the map. You can do that using the mapView's span like so in the maps regionDidChangeAnimated:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// Get the span and the center coordinate of the mapview
let span = mapView.region.span
let center = mapView.centerCoordinate
// Create two points on the left and right side of the region
let rightSide = CLLocationCoordinate2D(latitude: center.latitude + (span.latitudeDelta / 2), longitude: center.longitude)
let leftSide = CLLocationCoordinate2D(latitude: center.latitude - (span.latitudeDelta / 2), longitude: center.longitude)
// Calculate the distance between these two points (don't forget to convert to meters!)
let distance = calculateDistanceBetween(firstLoc: leftSide, secondGeoPoint: rightSide) * 1609.34
// Switch case the distance to handle zooming logic
switch distance {
case _ where distance < 1000:
// Handle logic for if the on screen width distance is < 1000 meters
case _ where distance < 5000:
// Handle logic for if the on screen width distance is < 5000 meters
default:
// Handle logic for if the on screen width distance is > 5000 meters
}
}
Where the distance between any two GPS coordinates can be found using:
// Calculates the distance between two locations, returned in miles
func calculateDistanceBetween(firstLoc: CLLocationCoordinate2D, secondLoc: CLLocationCoordinate2D) -> Double {
// Convert lat's and long's into radians
let firstLatR = firstLoc.latitude * Double.pi / 180
let firstLongR = firstLoc.longitude * Double.pi / 180
let secondLatR = secondLoc.latitude * Double.pi / 180
let secondLongR = secondLoc.longitude * Double.pi / 180
// Calculate and return the distance
return 2 * 3963.1 * asin((pow(sin((secondLatR - firstLatR) / 2), 2) + cos(firstLatR) * cos(secondLatR) * pow(sin((secondLongR - firstLongR) / 2), 2)).squareRoot())
}
It is worth noting that the above equation assumes the earth is a perfect sphere. This should be more than accurate enough for your application.
The problem with finding a true "zoom" level for the map is that the map distance of on screen width (Distance of a degree longitude) changes depending on your latitude.

Related

can I calculate accurate distance between two latitude and longitude inside a house within 5 meters? [duplicate]

This question already exists:
I want to build something like that if a user enters in a room he recieve a notification in swift ios is it possible?
Closed 3 months ago.
I want to calculate distance between two coordinates within 5 meters or even within one meters is it possible
I have tried haversine formula but not getting the desired result
func calculateDistanceWithHaversin(crrLat: Double, crrLong: Double, desLat: Double = 23.1780068, desLong: Double = 75.7865060, radius: Double = 6367444.7) -> Double {
print("CrrLat \(crrLat) = CrrLong = \(crrLong)")
let haversin = { (angle: Double) -> Double in
return (1 - cos(angle))/2
}
let ahaversin = { (angle: Double) -> Double in
return 2 * asin(sqrt(angle))
}
// degree to radian
let dToR = { (angle: Double) -> Double in
return (angle / 360) * 2 * .pi
}
let lat1 = dToR(crrLat)
let lon1 = dToR(crrLong)
let lat2 = dToR(desLat)
let lon2 = dToR(desLong)
return radius * ahaversin(haversin(lat2 - lat1) + cos(lat1) * cos(lat2) * haversin(lon2 - lon1))
}
i have tried this also
func calculateDistance(crrLat: Double, crrLong: Double) {
let destinationLocation = CLLocation(latitude: 23.1780068, longitude: 75.7865060)
let currentLocation = CLLocation(latitude: crrLat, longitude: crrLong)
distance = currentLocation.distance(from: destinationLocation)
print(String(format: "The distance to my buddy is %.02f m", distance))
}
You can calculate distance with this Builtin Function provided by CoreLocation . The provided distance will be in meters
import CoreLocation
let locationOne = CLLocation(latitude: 37.899, longitude: 74.8989)
let locationTwo = CLLocation(latitude: 38.0900, longitude: 78.98898)
let distance = locationOne.distance(from: locationTwo)

How to display large amount of GMSPolylines without maxing out CPU usage?

I'm making an app that displays bus routes using the NextBus API and Google Maps. However, I'm having an issue with CPU usage that I think is being caused by the amount of GMSPolylines on the map. The route is displayed by an array of polylines made up of the points given by NextBus for a given route. When the polylines are added to the map and the GMSCamera is overviewing the entire route, the CPU on the simulator (iPhone X) maxes out at 100%. When zoomed in on a particular section of the route, however, the CPU usage goes down to ~2%.
Map Screenshot: https://i.imgur.com/jLmN26e.png
Performance: https://i.imgur.com/nUbIv5w.png
The NextBus API returns route information including the route of a specific bus path. Here's an small example of the data that I'm working with:
Route: {
"path": [Path]
}
Path: {
"points:" [Coordinate]
}
Coordinate: {
"lat": Float,
"lon": Float
}
And here's my method that creates the polylines from the data. All in all there are on average ~700 coordinates spread across ~28 polylines (each path object) for a route. Keep in mind I'm not displaying multiple routes on one page, I'm only displaying one at a time.
func buildRoute(routePath: [Path?]) -> [GMSPolyline] {
var polylines: [GMSPolyline] = []
for path in routePath {
let path = GMSMutablePath()
guard let coords = path?.points else {continue}
for coordinate in coords {
// Safely unwrap latitude strings and convert them to doubles.
guard let latStr = coordinate?.lat,
let lonStr = coordinate?.lon else {
continue
}
guard let latOne = Double(latStr),
let lonOne = Double(lonStr) else {
continue
}
// Create location coordinates.
let pointCoordinatie = CLLocationCoordinate2D(latitude: latOne, longitude: lonOne)
path.add(pointCoordinatie)
}
let line = GMSPolyline(path: path)
line.strokeWidth = 6
line.strokeColor = UIColor(red: 0/255, green: 104/255, blue: 139/255, alpha: 1.0)
polylines.append(line)
}
return polylines
}
Finally here is my method that adds the polylines to the map:
fileprivate func buildRoute(routeConfig: RouteConfig?) {
if let points = routeConfig?.route?.path {
let polylines = RouteBuiler.shared.buildRoute(routePath: points)
DispatchQueue.main.async {
// Remove polylines from map if there are any.
for line in self.currentRoute {
line.map = nil
}
// Set new current route and add it to the map.
self.currentRoute = polylines
for line in self.currentRoute {
line.map = self.mapView
}
}
}
}
Is there a problem with how I'm constructing the polylines? Or are there simply too many coordinates?
I ran into this exact problem. It is quite an odd bug -- when you go over a certain threshold of polylines, the CPU suddenly pegs to 100%.
I discovered that GMSPolygon does not have this problem. So I switched over all of GMSPolyline to GMSPolygon.
To get the correct stroke width, I am using the following code to create a polygon that traces the outline of a polyline at a given stroke width. My calculation requires the LASwift linear algebra library.
https://github.com/AlexanderTar/LASwift
import CoreLocation
import LASwift
import GoogleMaps
struct Segment {
let from: CLLocationCoordinate2D
let to: CLLocationCoordinate2D
}
enum RightLeft {
case right, left
}
// Offset the given path to the left or right by the given distance
func offsetPath(rightLeft: RightLeft, path: [CLLocationCoordinate2D], offset: Double) -> [CLLocationCoordinate2D] {
var offsetPoints = [CLLocationCoordinate2D]()
var prevSegment: Segment!
for i in 0..<path.count {
// Test if this is the last point
if i == path.count-1 {
if let to = prevSegment?.to {
offsetPoints.append(to)
}
continue
}
let from = path[i]
let to = path[i+1]
// Skip duplicate points
if from.latitude == to.latitude && from.longitude == to.longitude {
continue
}
// Calculate the miter corner for the offset point
let segmentAngle = -atan2(to.latitude - from.latitude, to.longitude - from.longitude)
let sinA = sin(segmentAngle)
let cosA = cos(segmentAngle)
let rotate =
Matrix([[cosA, -sinA, 0.0],
[sinA, cosA, 0.0],
[0.0, 0.0, 1.0]])
let translate =
Matrix([[1.0, 0.0, 0.0 ],
[0.0, 1.0, rightLeft == .left ? offset : -offset ],
[0.0, 0.0, 1.0]])
let mat = inv(rotate) * translate * rotate
let fromOff = mat * Matrix([[from.x], [from.y], [1.0]])
let toOff = mat * Matrix([[to.x], [to.y], [1.0]])
let offsetSegment = Segment(
from: CLLocationCoordinate2D(latitude: fromOff[1,0], longitude: fromOff[0,0]),
to: CLLocationCoordinate2D(latitude: toOff[1,0], longitude: toOff[0,0]))
if prevSegment == nil {
prevSegment = offsetSegment
offsetPoints.append(offsetSegment.from)
continue
}
// Calculate line intersection
guard let intersection = getLineIntersection(line0: prevSegment, line1: offsetSegment, segment: false) else {
prevSegment = offsetSegment
continue
}
prevSegment = offsetSegment
offsetPoints.append(intersection)
}
return offsetPoints
}
// Returns the intersection point if the line segments intersect, otherwise nil
func getLineIntersection(line0: Segment, line1: Segment, segment: Bool) -> CLLocationCoordinate2D? {
return getLineIntersection(p0: line0.from, p1: line0.to, p2: line1.from, p3: line1.to, segment: segment)
}
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
// Returns the intersection point if the line segments intersect, otherwise nil
func getLineIntersection(p0: CLLocationCoordinate2D, p1: CLLocationCoordinate2D, p2: CLLocationCoordinate2D, p3: CLLocationCoordinate2D, segment: Bool) -> CLLocationCoordinate2D? {
let s1x = p1.longitude - p0.longitude
let s1y = p1.latitude - p0.latitude
let s2x = p3.longitude - p2.longitude
let s2y = p3.latitude - p2.latitude
let numerator = (s2x * (p0.latitude - p2.latitude) - s2y * (p0.longitude - p2.longitude))
let denominator = (s1x * s2y - s2x * s1y)
if denominator == 0.0 {
return nil
}
let t = numerator / denominator
if segment {
let s = (s1y * (p0.longitude - p2.longitude) + s1x * (p0.latitude - p2.latitude)) / (s1x * s2y - s2x * s1y)
guard (s >= 0 && s <= 1 && t >= 0 && t <= 1) else {
return nil
}
}
return CLLocationCoordinate2D(latitude: p0.latitude + (t * s1y), longitude: p0.longitude + (t * s1x))
}
// The path from NextBus
let path: CLLocationCoordinate2D = pathFromNextBus()
// The desired width of the polyline
let strokeWidth: Double = desiredPolylineWidth()
let polygon: GMSPolygon
do {
let polygonPath = GMSMutablePath()
let w = strokeWidth / 2.0
for point in offsetPath(rightLeft: .left, path: route.offsetPath, offset: w) {
polygonPath.add(CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude))
}
for point in offsetPath(rightLeft: .right, path: route.offsetPath, offset: w).reversed() {
polygonPath.add(CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude))
}
polygon = GMSPolygon(path: polygonPath)
polygon.strokeWidth = 0.0
}

Get altitude from a specific given point - mapkit

I am trying to find out altitude from a given point, but I am not getting that with my iPad.
I have defined my altitude var like this:
var altitude: CLLocationDistance = 0.0
I have made this func:
func getAltitude(latitude: CLLocationDistance, longitude: CLLocationDistance) -> CLLocationDistance {
//Get altitude of touched point
locationManager.requestWhenInUseAuthorization()
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
let touchPointLocation = CLLocation(latitude: latitude, longitude: longitude)
let altitude = touchPointLocation.altitude
return altitude
}
For example when I touch the map I tried to get altitude like this, inside the longpress:
let touchPoint = gestureRecognizer.location(in: self.map)
let coord = map.convert(touchPoint, toCoordinateFrom: self.map) //now this coord is working ok. Its touched point coordinates
let altitude = getAltitude(latitude: coord.latitude, longitude: coord.longitude)
print(altitude) //I get 0.0 here :(
Why is this wrong? How can I do this?

Coding multiple annotations swift 2

I have created code that produces a geotargeting area represented by an annotation and MKCircle. The app notifies the user when they have entered and exited a region. Everything is working fine but I cannot figure out how to get the app to hold/display multiple regions (only one annotation/circle will show) here are a few snippets of my code:
override func viewDidLoad() {
super.viewDidLoad( )
//setup locationManager
locationManager.delegate = self
locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
//setup mapView
mapView.delegate = self
mapView.showsUserLocation = true
mapView.userTrackingMode = .Follow
//setup test data will need to link coredata to pass in (LocationLabel, radius, address)
setupData("Test1", radius: 100, Address: "735 Main Rd, Clemson")
setupData("Test2", radius: 100, Address: "821 Main Rd, Clemson")
setupData("Test3", radius: 100, Address: "720 Main Rd, Clemson")
}
func setupData( Label: String, radius: Double, Address: String ) {
// check if system can monitor regions
if CLLocationManager.isMonitoringAvailableForClass(CLCircularRegion.self) {
//region data need to put in its own class to read multiple regions
let title = Label
let regionRadius = radius // in meters
let address = Address // street, city, state zip
//takes in the address of a location and converts it into 2d coordinates (lat/long)
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks, error) in
if let placemarks = placemarks {
if placemarks.count != 0 {
let coordinates = placemarks.first!.location
let coordinate = coordinates?.coordinate
//setup region this will read an object with a saved coordinate and name
var region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: coordinate!.latitude,
longitude: coordinate!.longitude), radius: regionRadius, identifier: title)
self.locationManager.startMonitoringForRegion(region)
//setup annotation
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate!;
annotation.title = "\(title)";
self.mapView.addAnnotation(annotation)
//setup circle
let circle = MKCircle(centerCoordinate: coordinate!, radius: regionRadius)
self.mapView.addOverlay(circle)
}
else {
print("System can't track regions")
}
}
}
}
}
You have to implement the renderForOverlay function of MKMapViewDelegate to actually see those overlays that you have added. Try to increase your radius as well if you want it to see right away without zooming in.
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer
{
if let overlay = overlay as? MKCircle
{
let circleRenderer = MKCircleRenderer(circle: overlay)
circleRenderer.fillColor = UIColor.blueColor()
return circleRenderer
}
return MKOverlayRenderer(overlay: overlay)
}

How to create an MKCoordinate region similar to 50 miles radius in ios Swift?

I'm trying to create a region similar to a circle radius using CLLocation. I understand radius logic and how its measured in meters, but not so clear on a MKCoordinate region and how long delta and lat delta translate to area. I would like to get a 75 mile region. Here is my code....
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
If you could please provide an explanation more than just a short answer it would be appreciated.
If you're trying to create an actual circular region:
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let radius: CLLocationDistance = 60350.4 // meters for 37.5 miles
let regionIdentifier = "CircularRegion" // any desired String
let circularRegion = CLCircularRegion(center: center, radius: radius, identifier: regionIdentifier)
You could use MKCoordinateRegionMakeWithDistance function:
Creates a new MKCoordinateRegion from the specified coordinate and
distance values.
func MKCoordinateRegionMakeWithDistance(
_ centerCoordinate: CLLocationCoordinate2D,
_ latitudinalMeters: CLLocationDistance,
_ longitudinalMeters: CLLocationDistance) -> MKCoordinateRegion
centerCoordinate - The center point of the new coordinate region.
latitudinalMeters - The amount of north-to-south distance (measured in meters) to use for the span.
longitudinalMeters - The amount of east-to-west distance (measured in meters) to use for the span.
So you will have something like:
let rect = MKCoordinateRegionMakeWithDistance(center, 50 * 1609.34, 50 * 1609.34)