I am already using the method GMSGeometryContainsLocation in order to determine if the coordinate (from a tap user) is within GMSPolygon, but I can't make it work for GMSPolyline.
-(void) didTapOnPolyline:(CLLocationCoordinate2D)coordinate andMap:(GMSMapView *)mapView {
if (_pathVS != nil) {
for (id key in _pathVS) {
if (GMSGeometryIsLocationOnPath(coordinate, [_pathVS objectForKey:key], YES)) {
_myMarker = [GMSMarker markerWithPosition:coordinate];
_myMarker.opacity = 1.f;
// _myMarker.icon = [UIImage imageNamed:#"marker-maps"];
_myMarker.map = mapView;
_myMarker.userData = key;
[_mapView setSelectedMarker:_myMarker];
}
}
}
}
I am calling this method from the triggered delegate method :
-(void) mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate
{
if (_myMarker)
_myMarker.map = nil;
[self didTapOnPolygon:coordinate andMap:mapView];
[self didTapOnPolyline:coordinate andMap:mapView];
}
If the tapped point is not exactly on the poly line then GMSGeometryIsLocationOnPath condition will never return true. So you have to use GMSGeometryIsLocationOnPathTolerance
Google Map SDK document says
A point that is not equal to a vertex is on one side or the other of any path segment -- it can never be "exactly on the border"
BOOL GMSGeometryIsLocationOnPathTolerance (CLLocationCoordinate2D point, GMSPath *path, BOOL geodesic, CLLocationDistance tolerance)
Returns whether point lies on or near path, within the specified tolerance in meters.
Related
I am working on a project where I need to draw a line on map with each update of user location to show a full route where user has travelled.
I am currently using Mapbox iOS SDK (v4.0.2) for this. To achieve above goal I am using MGLPolyline to draw a line over map.
With the following code provided, I am facing an issue as some default shape automatically get drawn before the route gets started.
Following is the code which I have implemented in my project :
#interface ViewController () <MGLMapViewDelegate>
{
MGLPolyline *polyline;
}
#end
- (void)mapView:(MGLMapView *)mapView didUpdateUserLocation:(MGLUserLocation *)userLocation
{
CLLocationCoordinate2D coord[1];
coord[0] = userLocation.coordinate;
if (polyline)
{
[polyline appendCoordinates:coord count:sizeof(coord)];
}
else
{
polyline = [MGLPolyline polylineWithCoordinates:coord count:sizeof(coord)];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^(void) {
[weakSelf.mapView addAnnotation:polyline];
});
}
}
- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation
{
return 1.0f;
}
- (CGFloat)mapView:(MGLMapView *)mapView lineWidthForPolylineAnnotation:(MGLPolyline *)annotation
{
return 5.0f;
}
- (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation
{
return [UIColor redColor];
}
i've done similar project before with swift.
user selects two point on map for beginning and destination then we use a google service which takes two point and gives list of point for best route and i was using this method to draw a polyLine
i think you have two way to do this
1_using didupdate func of mapkit for saving all point that user moves then use the func i put below for drawing poly line
2_take the beginning and destination points then draw a polyline
func addPolyLineToMap(googlemaplist: [CLLocation?]){
var coordinates = googlemaplist.map({ (location: CLLocation!) -> CLLocationCoordinate2D in
return location.coordinate
})
print("locatios count")
print(googlemaplist.count)
var polyline = MKPolyline(coordinates: &coordinates, count: googlemaplist.count)
DispatchQueue.main.async {
self.MapKit.add(polyline)
}
}
i'll try to keep this very simple :
I'm very new to Objective-C (or programming actually) so i'll probably need an explanation more than just an answer.
What i'm trying to do and can't do (yet):
Getting a pin's coordinate after tapping it's disclosure, and eventually pass it through a segue.
Practically, i'm clicking my "info" icon on a pin I just created, and i'd like to see it's info in another page (a webview). This page would show coordinates (and other stuff that we don't need here).
Why :
Practically, i'm clicking my "info" icon on a pin's annotation created earlier by the user, and i'd like to see it's info in another page (a webview). This page would show coordinates (and other stuff that we don't need here).
My pins coordinates are stored into a
CLLocationCoordinate2D location;
I can't re-use the variables i used before because they might be outdated (it would only work if the user asks for the last one created...)
And I do not know how i can get a pin's coordinates ; there must be a method or something but i jsut can't find it. I've found MANY answers on the internet, and none seemed to work, probably because i didn't understand them properly. Anyway, i couldn't use them.
I'll show you bits of my code, it's pretty straight forward i guess :
There is the viewDidLoad, you probably want to see it, i guess :
- (void)viewDidLoad
{
[super viewDidLoad];
name = [[NSString alloc]init];
detail = [[NSString alloc]init];
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longpressToGetLocation:)];
lpgr.minimumPressDuration = 2.0; //user must press for 2 seconds
[_map addGestureRecognizer:lpgr];
CLLocationCoordinate2D loc = CLLocationCoordinate2DMake(50.837863, 4.353616);
MKCoordinateSpan span = MKCoordinateSpanMake(.035, .035);
MKCoordinateRegion reg = MKCoordinateRegionMake(loc, span);
self.map.region = reg;
[self buildBaseAnno];
self.map.showsUserLocation = true;
}
I know this is important but to be honest i don't fully understand how this bit works ; still, it does work. :D
-(MKAnnotationView*)mapView:(MKMapView*)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView* v = nil;
{
static NSString* ident = #"Pin";
v = [_map dequeueReusableAnnotationViewWithIdentifier:ident];
if (v == nil)
{
v = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ident];
((MKPinAnnotationView*)v).pinColor = MKPinAnnotationColorRed;
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
v.rightCalloutAccessoryView = infoButton;
v.centerOffset= CGPointMake(0,-20);
v.canShowCallout= YES;
}
v.annotation = annotation;
}
return v;
}
Touch pin creation :
- (void)longpressToGetLocation:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan)
return;
CGPoint touchPoint = [gestureRecognizer locationInView:self.map];
location = [self.map convertPoint:touchPoint toCoordinateFromView:self.map];
[self showAlertName];
}
The actual annotation/pin creation method
-(void)buildAnno
{
CLLocationCoordinate2D coordinate;
coordinate.latitude = location.latitude;
coordinate.longitude = location.longitude;
MKPointAnnotation* newann = [MKPointAnnotation new];
newann.coordinate = coordinate;
newann.title = name;
newann.subtitle = detail;
[self.map addAnnotation:newann];
}
Please, do tell if you need more of the code ; i'm not really sure what i can give you, as my code is probably 'correct' (or decent), i just need to actually know how to get the information i need (that is, coordinates and other stuff from the pin i just tapped.)
And i actually don't tap the pin, i tap the disclosure in the pin's annotation. Anyway, That's enough for now !
Once i can catch these coordinates, i believe i'll be able to pass them through the segue as passing Data is already well explained here, but if there is anything 'special', i'd be really glad if you could add it to your explanation because i'm still really uncomfortable with all this and most of the tutorials/guides/links i've found didn't really help me.
Thank you very much for your time and help :)
(Edit : I had found a similar question here but i believe i need extra help/explanation.)
You don't need the gesture recogniser - the map view delegate protocol already has a method that tells you when the callout accessory was tapped calloutAccessoryControlTapped and this method receives the relevant annotationView. The annotationView.annotation property gets you back to the relevant annotation object and then you can access its coordinate property to get the coordinates of the pin.
First, create a new property in your class:
#property CLLocationCoordinate2D tappedCoord;
then implement the delegate method
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
MKPointAnnotation *annotation=(MKPointAnnotation*)view.annotation;
self.tappedcoord=annotation.coordinate;
[self performSegueWithIdentifier:#"detailViewSegue"]; // Use your appropriate segue identifier
}
Then you can access the property in prepareForSegue (again, change to the appropriate segue name and destination view controller class)
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"detailViewSegue" ]){
DetailViewController *dvc=(DetailViewController *)segue.destinationViewController;
dvc.coord=self.tappedCoord;
}
Also, since your MapView is displaying the user's location, there will be an annotation for that. You need to address this in your viewForAnnotation method, returning nil if the annotation isn't one of yours. You can check the class of the annotation to determine this -
-(MKAnnotationView*)mapView:(MKMapView*)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView* v = nil;
if ([annotation isMemberOfClass:[MKPointAnnotation class]]) {
static NSString* ident = #"Pin";
v = [_map dequeueReusableAnnotationViewWithIdentifier:ident];
if (v == nil)
{
v = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ident];
((MKPinAnnotationView*)v).pinColor = MKPinAnnotationColorRed;
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
v.rightCalloutAccessoryView = infoButton;
v.centerOffset= CGPointMake(0,-20);
v.canShowCallout= YES;
}
v.annotation = annotation;
}
return v;
}
I'm creating an array representing a path made by the user by adding the user's current location to an array. Each time a new point is being added, I remove the previous annotation, and add a new one. then the new path is being drawn on a mapView with RMShape and layerForAnnotation. The problem is that every time the RMSape layer is being drawn, it has like a slide transition.
I have 2 questions:
How do I fix this "slide" effect, and draw a continuous line representing the user's path smoothly?
Is there a better way for tracing user's path?
.
-(void)mapView:(RMMapView *)mapView didUpdateUserLocation:(RMUserLocation *)userLocation
{
if (path) {
[self.mapView removeAnnotation:path];
}
path = [[RMShapeAnnotation alloc] initWithMapView:mapView points:points];
[mapView addAnnotation:path];
}
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation
{
if (annotation.isUserLocationAnnotation) {
return nil;
}
if ([annotation isKindOfClass:[RMShapeAnnotation class]]) {
for (int i = 0; i < points.count; i++) {
CLLocation *location = points[i];
[self.path addLineToCoordinate:location.coordinate];
}
return self.path;
}
return nil;
}
I would update your annotation layer directly as opposed to constantly replacing your annotation and thus its layer. You can do this by obtaining annotation.layer, casting it to RMShape, and then using methods like -addLineToCoordinate: to update it.
I have an NSArray of custom objects which are called Proximity. I add these to the map by creating a new ProximityAnnotation like the following:
// Add the annotations to the map
if (self.proximityItems) {
for (Proximity *proximity in self.proximityItems) {
// Create a pin
ProximityAnnotation *proximityAnnotation = [[ProximityAnnotation alloc] init];
proximityAnnotation.coordinate = CLLocationCoordinate2DMake([proximity.latitude doubleValue], [proximity.longitude doubleValue]);
proximityAnnotation.title = proximity.title;
proximityAnnotation.subtitle = NSLocalizedString(#"Drag to change location", nil);
[self.map addAnnotation:proximityAnnotation];
}//end
// Create the map rect
MapUtility *util = [[MapUtility alloc] init];
[util zoomMapViewToFitAnnotations:self.map animated:YES];
}//end
This works great.
Now, when I drag an annotation, I would like to update the corresponding ProximityAnnotation object that is contained in my proximityItems array. I am trying to do this by doing the following:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState {
// Get the coordiante
if ([annotationView.annotation isKindOfClass:[ProximityAnnotation class]] && newState == MKAnnotationViewDragStateEnding) {
ProximityAnnotation *annotation = (ProximityAnnotation *)annotationView.annotation;
CLLocationCoordinate2D coordinate = annotation.coordinate;
// Find the annotation that matches
for (Proximity *proximity in self.proximityItems) {
NSLog(#"%f == %f && %f == %f && %# == %#", [proximity.latitude doubleValue], coordinate.latitude, [proximity.longitude doubleValue], coordinate.longitude, annotation.title, proximity.title);
if ([proximity.latitude doubleValue] == coordinate.latitude && [proximity.longitude doubleValue] == coordinate.longitude && [annotation.title isEqualToString:proximity.title]) {
// Update the proximity item
proximity.longitude = [NSNumber numberWithDouble:coordinate.longitude];
proximity.latitude = [NSNumber numberWithDouble:coordinate.latitude];
break;
}
}//end
}//end
}//end
Unfortunately, this doesn't seem to get a match even though there is only 1 annotation on the map. Here is what was logged from my NSLog:
37.627946 == 37.622267 && -122.431599 == -122.435596 && Testlocation == Testlocation
Strangely, the double values seem to get a bit off, but I am not sure why.
Is there a better way to match the annotation to the object that I have in my array, so that I can updated that original object?
The coordinate values are most likely "off" because the annotation has been dragged to a new location.
Even if the values were equal, I don't recommend comparing floating-point numbers as a test for object equality.
Instead, I suggest these options:
Add a reference to the source Proximity object in the ProximityAnnotation class and set it when creating the annotation (eg. proximityAnnotation.sourceProximity = proximity;). Then to update the original Proximity object, you can get a reference to it directly from the annotation itself.
Eliminate the ProximityAnnotation class and make the Proximity class itself implement the MKAnnotation protocol in which case an update might not even be necessary.
I have a lot of annotations on the mapView and user location dot. Then, if user tap for 2 sec. on the map, I add an extra annotation with options. I need to remove that last added annotation from map by pressing the button. How can I remove it without to remove any other annotation?
- (void)addPin:(UILongPressGestureRecognizer*)recognizer {
if(UIGestureRecognizerStateBegan == recognizer.state) {
CGPoint tappedPoint = [recognizer locationInView:mapView];
CLLocationCoordinate2D locCoord= [mapView convertPoint:tappedPoint toCoordinateFromView:mapView];
MKPointAnnotation *annot = [[MKPointAnnotation alloc] init];
annot.coordinate = locCoord;
[self.mapView addAnnotation:annot];
}
if(UIGestureRecognizerStateChanged == recognizer.state) {
// Do repeated work here (repeats continuously) while finger is down
}
if(UIGestureRecognizerStateEnded == recognizer.state) {
// Do end work here when finger is lifted
}
}
To remove all the annotations from map view:
[vwMap removeAnnotations:vwMap.annotations];
PS: vwMap is the MKMap view object
Do the following,
If you have the annotation object
[self.mapView removeAnnotation:annot];
If you have the index of the object
[self.mapView removeAnnotation:self.mapView.annotations.lastObject];
Do this to remove your last added annotation in your delete Action:
[self.mapView removeAnnotation:[self.mapView.annotations lastObject]];
Hope helpful
I managed to remove the annotation object that is touched by doing the following, I know this wasn't the question but it may help someone out
set the mapView as delegate
- (void)mapView:(MKMapView *)thisMapView didSelectAnnotationView:(MKAnnotationView *)view {
MKPointAnnotation *thisTouchedAnnotation = view.annotation;
uint8_t annotationCount = thisMapView.annotations.count;
for(int i =0; i<annotationCount; i++)
{
if ([thisMapView.annotations objectAtIndex:i]==thisTouchedAnnotation){
[thisMapView removeAnnotation:[mapView.annotations objectAtIndex:i]];
break;
}
}
}
not flawless code but it may guide you :-)
Use this code!
NSArray *array=self.mapview.annotations;
for (MKPointAnnotation *anno in array)
{
if(anno==[array lastObject])
{
[self.mapview removeAnnotation:anno];
}
}