I am trying to print coordinates of all route steps, similar to Google Maps SDK's "legs".
But it tells me that I cannot use polyline property to obtain a coordinate?
Try this:
for step in self.route!.steps as [MKRouteStep] {
otherwise it treats step as AnyObject (which doesn't have a polyline property defined so you get that compiler error).
By the way, note that polyline.coordinate just gives the average center of the polyline or one endpoint. A polyline can have more than one line segment.
If you need to get all the line segments and coordinates along the polyline, see latitude and longitude points from MKPolyline (Objective-C).
Here's one possible translation to Swift (with help from this answer):
for step in route!.steps as [MKRouteStep] {
let pointCount = step.polyline.pointCount
var cArray = UnsafeMutablePointer<CLLocationCoordinate2D>.alloc(pointCount)
step.polyline.getCoordinates(cArray, range: NSMakeRange(0, pointCount))
for var c=0; c < pointCount; c++ {
let coord = cArray[c]
println("step coordinate[\(c)] = \(coord.latitude),\(coord.longitude)")
}
cArray.dealloc(pointCount)
}
As the first linked answer warns, you may get hundreds or thousands of coordinates per step depending on the route.
Swift 4.1, as of July 2018, based on the other answer.
let pointCount = step.polyline.pointCount
let cArray = UnsafeMutablePointer<CLLocationCoordinate2D>.allocate(capacity: pointCount)
step.polyline.getCoordinates(cArray, range: NSMakeRange(0, pointCount))
for c in 0..<pointCount {
let coord = cArray[c]
print("step coordinate[\(c)] = \(coord.latitude),\(coord.longitude)")
}
cArray.deallocate()
Related
I am using MKPolylineRenderer to render series of polylines. I am facing challenge with deciding clockwise/anticlockwise direction of its drawing on map. I tried my best put my issue. I am attaching screenshot for more clarity.
Approach: I am still exploring. To find direction between two Polylines [clockwise and anti-clockwise]
Let me know your thought :)
I am able to solve my problem using formula referred at forum
Solution : Actually We need two lines(polylines) to decide direction. I am considering first two polyline in my case to decide its direction.
I am sharing my swift version code here.
func polylinesAngle(line1: [CLLocationCoordinate2D], line2: [CLLocationCoordinate2D]) {
let VA1 = MKMapPoint(line1[0])
// Common Vertx
let VA2 = MKMapPoint(line1[1])
let VB1 = MKMapPoint(line2[0])
let VB2 = MKMapPoint(line2[1])
let dAx = VA2.x - VA1.x
let dAy = VA2.y - VA1.y
let dBx = VB2.x - VB1.x
let dBy = VB2.y - VB1.y
let angle = atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy).(180.0 / .pi) // to degree
}
// Clockwise : Positive value
// Anticlockwise : Negative Value
Am trying to plot Polyline on Googlemap to show the position of driver/delivery boy to user like in Uber.
I use google directions API get overview Polyline and draw it on map. Now I get the driver location from our own server and in order to update the user location on map I iterate through the co-ordinates in GMSPath which I obtained by decoding the over-view Polyline as
if let jsonArray = jsonResult["routes"].array, jsonArray.count > 0 {
for json in jsonArray {
if let polyline = json["overview_polyline"]["points"].string {
self.possibleOverViewPolylines.append(polyline)
}
}
}
self.currentPolyline = self.possibleOverViewPolylines[0]
self.path = GMSMutablePath.init(fromEncodedPath: self.currentPolyline)
self.polyline = GMSPolyline(path: self.path)
Google usually returns multiple routes when sent alternative=true so I cache all the overview_polyline and use the first one as current over line.
Now from reading and trial-error I have figured out that there can be error in lat-long of driver captured and it might range from 5-50meters. So once I get the driver location I iterate through entire coordinates in path to find the closest point in map and snap driver to that location
var overallDistance: CLLocationDistance = 50
for index in 0 ..< strongSelf.path.count() {
let coordinate = strongSelf.path.coordinate(at: UInt(index))
let distance = location.distance(to: coordinate)
if distance < overallDistance {
foundIndex = Int(index)
overallDistance = distance
}
}
if overallDistance >= 50 {
debugPrint("\(location)")
evaluateAlternativeRoutes()
}
else {
updatepolyline(location: strongSelf.path.coordinate(at: UInt(foundIndex)))
}
update Polyline as
self?.polyline.map = nil
while strongSelf.path.coordinate(at: UInt(0)).latitude != location.latitude && strongSelf.path.coordinate(at: UInt(0)).longitude != location.longitude {
self?.path.removeCoordinate(at: 0)
}
if strongSelf.path.coordinate(at: 0).latitude == location.latitude && strongSelf.path.coordinate(at: UInt(0)).longitude == location.longitude {
self?.path.removeCoordinate(at: 0)
}
self?.polyline = GMSPolyline(path: strongSelf.path)
Finally alternative routes are evaluated as
var overallDistance: CLLocationDistance = 50
var foundIndex = -1
for (polylineIndex,polyline) in strongSelf.possibleOverViewPolylines.enumerated() {
if let path = GMSMutablePath.init(fromEncodedPath: polyline) {
for index in 0 ..< path.count() {
let coordinate = path.coordinate(at: UInt(index))
let distance = location.distance(to: coordinate)
if distance < overallDistance {
foundIndex = polylineIndex
overallDistance = distance
}
}
}
}
if foundIndex != -1 {
self?.path = GMSMutablePath.init(fromEncodedPath: strongSelf.possibleOverViewPolylines[foundIndex])
}
else {
//make routes API call again
}
If none of the alternative routes available matches the driver location, driver might be taking a whole different route so I make routes API call again with driver location
Why So many optimisation?
Google's routes APIs are costly and calling google routes API unnecessarily will add financial burden as well as screw up the whole user experience, hence want to make most of the calculations locally
But above code works sub-optimally :( It works but not great :|
Issues with this approach
Issue 1:
Method assumes the possible error rate in drivers location is 50m max, and when I evaluate all the distance to points in path are checked against this 50, but unfortunately coordinates in google paths are not equally distributed, on a lengthy straight road, distance between 2 subsequent points in path's coordinates can be up to 200m. I tested it with
for i in 0 ..< self.path.count() {
if i == 0 {
debugPrint(self.path.coordinate(at: i))
}
else {
debugPrint("distance between \(self.path.coordinate(at: (i - 1))) and \(self.path.coordinate(at: (i))) is \(self.path.coordinate(at: (i - 1)).distance(to: self.path.coordinate(at: (i))))")
}
}
So logic of comparing drivers location to all points in path with 50m as cap logic fails there.
Solution I can think of
If I can interpolate the points between any two coordinates in google path with a regular interval of 50m, and raise the cap to 100m (50m distance between two points in path, 50m for error in lat-long) that should give fairly better chance for me to reduce number of API calls
What I have tried?
I tried solving it with linear interpolation using
No need to tell the result is disastrous, because equation assumes a Cartesian plane and earth is not flat :|
So finally What the heck are you asking?
Is it a correct approach to interpolate the points between two coordinates of google path to achieve what am trying to achieve?
If yes, what is the better interpolation algorithm I should be using? Clearly linear doesn't make much sense :(
Please help, thanks in advance
Google itself has provided various methods for interpolating within GMSGeometryUtils module. I think what you need for interpolating could be the:
https://developers.google.com/maps/documentation/ios-sdk/reference/group___geometry_utils.html#gad0c5870bd9d182d22310f84a77888124
GMSGeometryInterpolate uses shortest path between 'from' and 'to' coordinate that you provide at a given fraction, and GMSPath indeed connects shortest paths between each sub-sequential coordinates you provide so it should be sufficient.
I am creating a wayfinding app that will display directions based on the device's current location and desired destination. I am trying to add stop points. How would I find the x and y values of the AGSPoint given the latitude and longitude of the location? I found the following code after doing some research, but the compiler tells me that * is not a prefix unary operator
func findAGSPointXY(longitude:Double, latitude:Double) -> (){
let mercatorX = longitude * 0.017453292519943295 * 6378137.0;
let a = latitude * 0.017453292519943295;
let mercatorY = 3189068.5 * log((1.0 + sin(a))/(1.0 - sin(a)));
AGSPoint *graphicPoint = [AGSPoint pointWithX:mercatorX y:mercatorY spatialReference:[AGSSpatialReference wgs84SpatialReference]];
}
It looks like you're mixing objective-c and swift, there are objc pointers (*), objc methods call ([]) and swift const (let) in the same snippet. I assume you're using swift.
The ArcGIS SDK can reproject geometries, you don't have to compute theses values by hand.
//Create an AGSPoint from your parameters, specifying them as WGS 84.
let wgsPoint = AGSPoint(x: longitude, y: latitude, spatialReference: AGSSpatialReference.wgs84())
//Preparing a Geometry Engine
let geometryEngine = AGSGeometryEngine.defaultGeometryEngine()
//Get a projected (in web mercator) geometry (points, lines, polygons and whatever inherit AGSGeometry).
let projGeometry = geometryEngine.projectGeometry(wgsPoint, toSpatialReference: AGSSpatialReference.webMercatorSpatialReference())
//Cast it back to AGSPoint, might have some optional to manage
let projPoint = projGeometry as AGSPoint // Might be some optional to manage there
//Here you can use projPoint.x and projPoint.y
I´m developing an iPhone app, and I need some help with this case:
I need to check, if user leave google maps route (GMSPolyline) and if distance from user location to nearest point of route is more than 40 meters -- I need to rebuild route.
I can't find the right algorithm to detect if distance from user to route is more than 40 meters.
I've tried to use this method to find projection of user location (converted to CGPoint by CGPointMake) on route :
+ (CGPoint)projectionOfPoint:(CGPoint)origPoint toSegmentP1:(CGPoint)p1 p2:(CGPoint)p2 {
// for case line is parallel to x axis
if (p2.y == p1.y) {
return CGPointMake(origPoint.x, p1.y);
// for case line is parallel to y axis
} else if (p2.x == p1.x) {
return CGPointMake(p1.x, origPoint.y);
}
// line from segment
CGFloat kKoefLine1 = (p2.x - p1.x)/(p2.y - p1.y);
CGFloat bKoefLine1 = p1.y - kKoefLine1*p1.x;
// perpendicular line
CGFloat kKoefLine2 = -1/kKoefLine1;
CGFloat bKoefLine2 = origPoint.y - kKoefLine2*origPoint.x;
// cross point
CGFloat krossX = (bKoefLine2 - bKoefLine1)/(kKoefLine1 - kKoefLine2);
CGFloat krossY = kKoefLine2*krossX + bKoefLine2;
return CGPointMake(krossX, krossY);}
Then I calculate distance from returned projection (converted to CLLocation) and user location, but it doesn't works.
P.S.: I will be thankful if solution would be written on swift.
There is a GMSGeometryIsLocationOnPath function in the GMSGeometryUtils module in the Google Maps SDK.
You should be able to use that to calculate what you need.
Pseudocode (not tested):
let currentLocation: CLLocationCoordinate2D = ...
let routePath: GMSPath = routePolyline.path
let geodesic = true
let tolerance: CLLocationDistance = 40
let within40Meters = GMSGeometryIsLocationOnPath(currentLocation, routePath, geodesic, tolerance)
for swift 5.0 and based on #Arthur answer I wrote follwoing function
func isInRoute(posLL: CLLocationCoordinate2D, path: GMSPath) -> Bool
{
let geodesic = true
let tolerance: CLLocationDistance = 40
let within40Meters = GMSGeometryIsLocationOnPathTolerance(posLL, path, geodesic, tolerance)
return within40Meters
}
While I don't recall much about the GMS SDK off the top of my head, before I give you an answer, I will say that nobody on here will write your code for you. That's your job and should be done on your time. You haven't given any background as to how far you've gotten in terms of calculating routes, whether or not you've figured out how to calculate distance at all, etc.
With that being said, routes on Google Maps are comprised of "legs", which denote a path to take before a turn is made in efforts to reach the end destination. By querying your "route" dictionary, you can extract an array of dictionaries where each element (which is a dictionary) contains metadata about a "leg". You can then loop through that array, go through each dictionary and extract the "distance" value, and sum them to a single "distance" var.
You can recalculate this as often as needed and use a conditional to check whether or not the leg distance sum is < 40M, else rebuild.
link to an article that should help (I didn't have the time to go through the entire thing for you, so do your due diligence and research) here.
I have multiple arrays that I want to iterate over and plug in as values later in my code for markers on a map. I have the markers populated but can't figure out how to apply the text marker title and subtitle to the marker associated with the location.
iOS and Swift 2
edit: using Mapbox iOS SDK as well.
How do I write the for loop that will do this?
My code trimmed down so far.
Arrays taken from geojson file:
//place variable to array of strings
var place = [String]()
//result from this array ["park", "playground", "parking"]
//pass variable to array of strings
var pass = [String]()
//result from this array ["True", "False", ""]
I have also done this with latitude and longitude but won't add that here. But I do need to add the for loop for it.
//lat and long is served and then all the r,z points, then another lat, long...need to fix this and it may work...
for var (i,x) in zip(lat, long) {
print(i)
print(x)
//print(i + ", " + x)
// String to double conversion
let lati = NSString(string: i).doubleValue
let longi = NSString(string: x).doubleValue
var point = MGLPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: lati, longitude: longi)
let r = place
print(r)
point.title = r
print(r)
let z = pass
print(z)
point.subtitle = z
mapView.addAnnotation(point)
}
So the markers are all added correctly to the map based on lat and long. The marker text however is only added once, and uses the last record served. I am betting I am missing something simple here, as I am newish to coding with Swift 2.
I am receiving this error message "Cannot assign value of type '[String]' to type 'String?'" associated with this line "point.title = r" and this line "point.subtitle = z" which complicates things.
Any suggestions will be greatly appreciated.
If you need more information I can edit as needed but hopefully you have everything you need to help me set up this for loop.
What is the data type of title and subtitle in point.title and point.subtitle respectively?
You are assigning a String Array place to r. Whereas, point.title is not an array type. I'm still missing your question on where do you want to add the loop?