Detect touch nearby to CLLocationCoordinate2D in pixels - ios

I have few GMSMarker on GMSMapView, all of them are draggable, so when I longpress them, I can move them around the map. However I have also an action on longpress on GMSMapView, which adds a marker.
- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker {
self.moving = YES;
}
- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
self.moving = NO;
}
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
if (self.moving) {
return;
}
[self addMarkerAtCoordinate:coordinate];
}
Now the problem is, that sometimes user mistap and instead of moving marker he adds a new one. Because of this, I'd like to add small area around marker, where user can't add new markers. I've thought about something like this:
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
CGFloat zoomFactor = 35.f - self.mapView.camera.zoom;
CLLocation *location = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
for (GMSMarker *marker in self.markers) {
CLLocation *sectorLocation = [[CLLocation alloc] initWithLatitude:marker.position.latitude longitude:marker.position.longitude];
if ([location distanceFromLocation:sectorLocation] < zoomFactor) {
return;
}
}
}
But of course I don't like this solution, because the area is changing with changed zoom. I'd like something like a finger width around marker to be longpress banned. How to calculate this distance?

It's easy to convert coordinate to location on view using method pointForCoordinate: on GMSProjection GMSMapView object.
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
CGPoint longpressPoint = [mapView.projection pointForCoordinate:coordinate];
for (GMSMarker *marker in self.markers) {
CLLocationCoordinate2D markerCoordinate = marker.position;
CGPoint sectorPoint = [mapView.projection pointForCoordinate:markerCoordinate];
if (fabsf(longpressPoint.x - markerCoordinate.x) < 30.f && fabsf(longpressPoint.y - markerCoordinate.y) < 30.f) { // 30.f ofc should be defined as constant
// handle situation when touchpoint is too close to marker
}
}
}

Related

How to move marker on the Google Map like Ola app

I am working on app the like Ola cabs. When the user drags map the a view transparent view appears with marker moving and when the user stops dragging the we have to centre the gmscamera position same like ola cab app. This the image when the map is not dragged.
after dragging I am getting the location using the below code.
- (void) mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
latitude = mapView.camera.target.latitude;
longitude = mapView.camera.target.longitude;
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(latitude, longitude);
[self hourelywebsevice:updatedlogintokenstring withlat:latitude withlong:longitude withcoustamerid:updatednamestring];
// [self hourelywebsevice:#"abc" withlat:latitude withlong:longitude];
marker.position = center;
}
Now my problem is how create second screen.
I have achieved this kind of things in two apps which are related with live tracking of vahicle.
There are some requirements that I have used:
App having Always location usage permission with proper battery usage message.
Used Socket connection on both end. Driver & Customer. For that Socket.io is used. Same is configured in Web.
Trigged location service at every 2 sec, so that I can handle battery usage.
Every 2 sec, send data to socket and socket listener can use that data to show car on map.
Car icon in 90 degree rotate(car indicate to move on right side)
Most important thing is that Driver latitude and longitude should with correct for showing car movement on the map. gmsMarker location consider as old value and newLocation value is current location.
let preLoc = CLLocation.init(latitude: self.gmsMarker.position.latitude, longitude: self.gmsMarker.position.longitude)
let curLoc = CLLocation.init(latitude: newLocation.latitude, longitude: newLocation.longitude)
let changeInLocation = preLoc.distance(from: curLoc)
if changeInLocation > 5{
let degree = self.DegreeBearing(preLoc, curLoc)
if degree > 5{
self.gmsMarker.rotation = degree
}
}
Below four function will calculate your travel direction and based on that car will show correct on road with moving.
func DegreeBearing(_ A:CLLocation,_ B:CLLocation)-> Double{
var dlon = self.ToRad(degrees: B.coordinate.longitude - A.coordinate.longitude)
let dPhi = log(tan(self.ToRad(degrees: B.coordinate.latitude) / 2 + M_PI / 4) / tan(self.ToRad(degrees: A.coordinate.latitude) / 2 + M_PI / 4))
if abs(dlon) > M_PI{
dlon = (dlon > 0) ? (dlon - 2*M_PI) : (2*M_PI + dlon)
}
return self.ToBearing(radians: atan2(dlon, dPhi))
}
func ToRad(degrees:Double) -> Double{
return degrees*(M_PI/180)
}
func ToBearing(radians:Double)-> Double{
return (ToDegrees(radians: radians) + 360).truncatingRemainder(dividingBy: 360)
}
func ToDegrees(radians:Double)->Double{
return radians * 180 / M_PI
}
For Customer side, just show marker with whatever data you received from Driver side as self.gmsMarker.rotation value.
Use delegates of GMSMapView
- (void) mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker;
- (void) mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker;
- (void) mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker;
#interface ViewController () <CLLocationManagerDelegate, GMSMapViewDelegate> {
GMSMarker *marker2;
}
In viewDidLoad
marker2 = [[GMSMarker alloc] init];
// Create a GMSCameraPosition that tells the map to display the
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:latitude
longitude:longitude
zoom:18];
// Create GMSMapView
GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
// Available map types: kGMSTypeNormal, kGMSTypeSatellite, kGMSTypeHybrid,
// kGMSTypeTerrain, kGMSTypeNone
// Set the mapType to Satellite
mapView.mapType = kGMSTypeSatellite;
mapView.myLocationEnabled = YES;
self.view = mapView;
// Creates a marker in the center of the map.
GMSMarker *marker = [[GMSMarker alloc] init];
marker.position = CLLocationCoordinate2DMake(latitude, longitude);
marker.map = mapView;
mapView.delegate = self;
After that call delegate functions
- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture {
// NSLog(#"willMove");
}
- (void) mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
// NSLog(#"didChangeCameraPosition");
// Creates a marker in the center of the map.
// NSLog(#"%#", position);
marker2.position = CLLocationCoordinate2DMake(mapView.camera.target.latitude, mapView.camera.target.longitude);
marker2.map = mapView;
// marker2.draggable = YES;
marker2.icon = [GMSMarker markerImageWithColor:[UIColor blueColor]];
}

viewForAnnotation didn't moves the userLocation

I've implemented - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { method to change the default location of userLocation Annotation. Inside this method I've used:
if (annotation==(mapView.userLocation)) {
MKAnnotationView *myAnotation = [[MKAnnotationView alloc] init];
myAnotation.image = self.userPointImage.image;
myAnotation.centerOffset = CGPointMake(0, 250);
return myAnotation;
}
else
{
return nil;
}
Which successfully moves the default userLocation annotation to center bottom. But as I'm also using heading so when move my phone it is still rotating the map from center.
- (void)locationManager:(CLLocationManager *) manager didUpdateHeading:(CLHeading *) newHeading {
[self.mapView setUserTrackingMode:MKUserTrackingModeFollowWithHeading animated:true];
}
But the userLocation annotation is below. So how to do that?
UPDATE I found this question and tried applying it. But as I'm using heading so it crashes the app.
In my didUpdateUserLocation method I added [self moveCenterByOffset:CGPointMake(0, 200) from:userLocation.coordinate]; and this method is as follows:
- (CLLocationCoordinate2D)moveCenterByOffset:(CGPoint)offset from:(CLLocationCoordinate2D)coordinate
{
CGPoint point = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView];
point.x += offset.x;
point.y += offset.y;
CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
return center;
}
and then in my didUpdateUserLocation method I updated it as: MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([self moveCenterByOffset:CGPointMake(0, -250) from:userLocation.coordinate], 800.0f, 200.0f); and [mapView setRegion:region animated:NO]; as I'm using heading so animation is set to NO. But now as I run the code it starts jerking between center and the new point.
MKCoordinateRegion region = mapClinicsView.region;
set 'region.center'
It will helps you to navigate your map center position
make sure 'region.center' and annotation center latitute and longitute is same

Google maps iOS sdk get tapped overlay coordinates

i'm working with Google maps iOS sdk.
i want to get the coordinates of the touched point when user taps an overlay.
there is this delegate method:
- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate
but it's not called when you tap an overlay or a marker.
can i call it programmatically (but coordinate parameter is missing-that's what i want..)?
or get location from this:
- (void) mapView: (GMSMapView *) mapView didTapOverlay: (GMSOverlay *) overlay
any suggestion's precious!
thanks!
UPDATE 6/2/15
Just staple a UITapGestureRecognizer onto the map and then extract the coordinate from the touch point. Your didTapAtCoordinate and didTapAtOverlay will continue to fire as before.
UITapGestureRecognizer *touchTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTouchTap:)];
[self.view addGestureRecognizer:touchTap];
-(void)tapTouchTap:(UITapGestureRecognizer*)touchGesture
{
CGPoint point = [touchGesture locationInView:self.view];
CLLocationCoordinate2D coord = [self.mapView.projection coordinateForPoint:point];
NSLog(#"%f %f", coord.latitude, coord.longitude);
}
ORIGINAL POST
You are likely missing two snippets of code.
Adopt the GMSMapViewDelegate in your header file:
#interface Map : UIViewController <GMSMapViewDelegate>
You also need to set the delegate in your viewDidLoad:
self.mapView.delegate = self;
Now this should fire for you:
- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate
{
NSLog(#"You tapped at %f,%f", coordinate.latitude, coordinate.longitude);
}
If you just want to get the position of the marker, there is a position property
CLLocationCoordinate2D coord = marker.position;
When this delegate method gets called
- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate
its for screen taps that have no markers or overlays from my expierience.
GMSOverlay is a bit different because its a super class to a GMSMarker. You just need to subclass GMSOverlay for your custom overlays and add a position property. When you create the overlay, say in didTapAtCoordinate, you can assign the position (GPS coord) in there.
You could set the circle not tappable (default behaviour) and catch all the clicks on the map with the didTapAtCoordinate delegate.
Then when this event is triggered you could loop over all your circles to check if the user tapped inside one of the circles or outside.
self.mapview.settings.consumesGesturesInView = NO;
Add this line in viewdidload or after allocating.
And then implement this delegate method.
- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate
{
NSLog(#"%g",coordinate);
}
- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay
{
GMSCircle *circle=(GMSCircle *)overlay;
CLLocationCoordinate2D touchCoOrdinate= circle.position;
NSLog(#"%g",touchCoOrdinate);
}
So you can't get the didTapAtCoordinateMethod to fire for an overlay tap. However I did find a slightly dirty workaround.
Using an overlay to draw polylines, we need a way to recognize where a polyline was tapped. So when drawing polylines we can build them like this.
//draw line
GMSPolyline *polyline = [GMSPolyline polylineWithPath:path];
polyline.strokeColor = [UIColor purpleColor];
polyline.tappable = TRUE;
polyline.map = self.googleMapView;
polyline.title = routestring;
Where routestring is a built string from
routestring = [NSString stringWithFormat:#"%#/%#/%#",lat,lng,[annnotationobject objectForKey:#"linkId"]];
And lat and lng are string values of our coordinates. The last part is an ID for the polyline.
The routestring is storing the coordinates and an ID separated by '/' so that we can use component path of string to look them up later. This is assigned to the polylines title.
Now when the overlay is tapped:
-(void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay{
NSString *path = overlay.title;
//Finding componentpaths of string
NSArray *pathparts = [path pathComponents];
NSString *lat = [pathparts objectAtIndex:0];
NSString *lng = [pathparts objectAtIndex:1];
NSString *linkID = [pathparts objectAtIndex:2];
//Here we are building a marker to place near the users tap location on the polyline.
GMSMarker *marker = [GMSMarker markerWithPosition:CLLocationCoordinate2DMake([lat doubleValue],[lng doubleValue])];
marker.title = overlay.title;
marker.snippet = #"ROUTE DATA";
marker.map = self.googleMapView;
//This will popup a marker window
[self.googleMapView setSelectedMarker:marker];
}
We can use component paths of the string we built(separated by "/")to get the latitude and longitude coordinates from the polyline. Then assign them a marker to popup information on the overlay item.
You can subclass UITapGestureRecognizer and override its touchesEnded to retrieve the touch point, than retrieve the coordinate with GMSMapView's coordinateForPoint.
Subclass UITapGestureRecognizer and add it to your mapView:
self.touchTap = [[TouchGestureRecognizer alloc] initWithTarget:self action:#selector(tapTouchTap)];
self.touchTap.mapView = self.mapView;
[self.view addGestureRecognizer:self.touchTap];
TouchDownGestureRecognizer.h:
#import <UIKit/UIKit.h>
#interface TouchGestureRecognizer : UITapGestureRecognizer
#property GMSMapView *mapView;
#end
TouchDownGestureRecognizer.m:
#import "TouchGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#implementation TouchGestureRecognizer
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
UITouch *touch = touches.allObjects[0];
CGPoint point = [touch locationInView:self.mapView];
CLLocationCoordinate2D coord = [self.mapView.projection coordinateForPoint:point];
NSLog(#"%f %f", coord.latitude, coord.longitude);
}
#end

Overlay on MapView animated

I add an overlay (array of multiple coordinates) and draw a path.
It works perfectly, but I would like (if it's possible), to draw the path with an animation (coordinate by coordinate, or fade in, etc.)
My app is only on iOS 7 or later.
Here my methods:
- (void)drawPathWithAnnotations:(NSArray*)annotations
{
CLLocationCoordinate2D array[[annotations count]];
for (CLLocation *loc in annotations)
{
array[[annotations indexOfObject:loc]] = CLLocationCoordinate2DMake(loc.coordinate.latitude, loc.coordinate.longitude);
}
self.routeLine = [MKPolyline polylineWithCoordinates:array count:[annotations count]];
[self.mapview addOverlay:self.routeLine];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if(overlay == self.routeLine){
MKPolylineRenderer* lineView = [[MKPolylineRenderer alloc] initWithPolyline:self.routeLine];
lineView.strokeColor = UIColorFromRGB(kAppTintColor);
lineView.lineWidth = 3;
return lineView;
}
return nil;
}
- (void)mapView:(MKMapView *)mapView didAddOverlayRenderers:(NSArray *)renderers
{
// Animation here ?
}
Thank you, any suggestions or ideas are appreciated! :)
I was looking for an answer to your question and didn't find an accepted one so here is my solution. You were right to assume didAddOverlayRenderers is where you put your animations. Here's an example of how to animate a 'fade' for an overlay:
- (void)mapView:(MKMapView *)mapView didAddOverlayRenderers:(NSArray *)renderers
{
// Iterate through all overlayRenderers
for (MKOverlayRenderer *overlayRenderer in renderers)
{
// MKPolylineRenderer
// Animate a 'fade'
if ([overlayRenderer isKindOfClass:[MKPolylineRenderer class]])
{
// Get MKPolylineRenderer
MKPolylineRenderer *polylineRenderer = (MKPolylineRenderer *)overlayRenderer;
// Let's manually set alpha to 0
polylineRenderer.alpha = 0.0f;
// Animate it back to 1
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.4 animations:^{
polylineRenderer.alpha = 1.0f
}];
});
}
}
}

MKMapView - Limit map scroll to a square overlay

I have a MKMapView with a square overlay described as:
CLLocationCoordinate2D coordsBg[5]={
CLLocationCoordinate2DMake(31.750865,35.180882),
CLLocationCoordinate2DMake(31.740331,35.180882),
CLLocationCoordinate2DMake(31.740331,35.165452),
CLLocationCoordinate2DMake(31.750865,35.165452),
CLLocationCoordinate2DMake(31.750865,35.180882)
};
MKPolygon *bg=[MKPolygon polygonWithCoordinates:coordsBg count:5];
[map addOverlay:bg];
I wish to limit the user from scrolling outside of the overlay.
Can I limit the MKMapView scroll view for that? Or there is an other method?
Thanks
Shani
After Few hours of banging my head, I got to this solution that work great for me.
It mixes up :
This post: set the zoom level of an mkmapview
And This link: restrict mkmapview scrolling from #Anna Karenina comment to my question.
And This is my code:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
lastGoodMapRect = mapView.visibleMapRect;
lastGoodRegion = mapView.region;
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
if (manuallyChangingMapRect) {
manuallyChangingMapRect=NO;
return;
}
if( [mapView zoomLevel] > 16 )
{
[mapView setCenterCoordinate:lastGoodRegion.center zoomLevel:16 animated:YES];
}
MKMapRect visibleRect = mapView.visibleMapRect;
MKMapRect OverlayRect = bg.boundingMapRect;
MKMapRect intersectionRect = MKMapRectIntersection(visibleRect,OverlayRect);
//you can change the min and max zoom off course
if(!MKMapRectEqualToRect(visibleRect,intersectionRect)){
if( [mapView zoomLevel] < 15){
[mapView setCenterCoordinate:lastGoodRegion.center zoomLevel:15 animated:YES];
}else if( [mapView zoomLevel] > 16 )
{
[mapView setCenterCoordinate:lastGoodRegion.center zoomLevel:16 animated:YES];
}else{
manuallyChangingMapRect=YES;
[mapView setVisibleMapRect:lastGoodMapRect animated:YES];
}
}
}

Resources