Check if CLLocation is in Shape? - ios

Im looking for a way to detect if the user is in The Netherlands or in Belgium. I don't want to call an API or use the users telephone services, so everything needs to be done locally and should be something easy and quick, small check. I have the users location each second and want to check each minute.
Goal:
The user is travelling with our app and I want to show a different view when he/she arrived Belgium/Netherlands.
I've found two shape files from http://www.diva-gis.org/datadown with Netherlands and Belgium. These files are quite small and now Im looking for a way to create this method:
- (BOOL)isUserInNetherlands:(CLLocation)location;
which should check the location is it is in the content of the shapefile.
The shapefile for the Netherlands looks something like this:
I can convert the shapefile to a sqlLite database but not idea what to do next.
Any idea's?

One way to do this is:
1) convert the shapefile to geoJson
2) load the geoJson file data in your code.
3) create polygons from these gps points (note: CountryDetectorController is my controller class)
+ (MKPolygon *)overlaysFromPolygons:(NSArray *)polygons title:(NSString *)title;
{
NSMutableArray *interiorPolygons = [NSMutableArray arrayWithCapacity:[polygons count] - 1];
for (int i = 1; i < [polygons count]; i++) {
[interiorPolygons addObject:[CountryDetectorController polygonFromPoints:polygons[i] interiorPolygons:nil]];
}
MKPolygon *overlayPolygon = [CountryDetectorController polygonFromPoints:polygons[0] interiorPolygons:interiorPolygons];
overlayPolygon.title = title;
return overlayPolygon;
}
+ (MKPolygon *)polygonFromPoints:(NSArray *)points interiorPolygons:(NSArray *)polygons;
{
NSInteger numberOfCoordinates = [points count];
CLLocationCoordinate2D *polygonPoints = malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
NSInteger index = 0;
for (NSArray *pointArray in points) {
polygonPoints[index] = CLLocationCoordinate2DMake([pointArray[1] floatValue], [pointArray[0] floatValue]);
index++;
}
MKPolygon *polygon;
if (polygons) {
polygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates interiorPolygons:polygons];
} else {
polygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates];
}
free(polygonPoints);
return polygon;
}
4) create a CGPath from the polygon
+ (CGPathRef)pathForPolygon:(MKPolygon*)aPolygon;
{
CGMutablePathRef mpr = CGPathCreateMutable();
MKMapPoint *polygonPoints = aPolygon.points;
size_t nCount = aPolygon.pointCount;
for (int p = 0; p < nCount; p++)
{
MKMapPoint mp = polygonPoints[p];
if (p == 0)
CGPathMoveToPoint(mpr, NULL, mp.x, mp.y);
else
CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y);
}
return mpr; //Keep in memory;
}
5) check if coordinate is in CGPath (pathsBelgium is an array with NSValue of CGPaths)
- (void)checkCoordinate:(CLLocationCoordinate2D)coordinate;
{
MKMapPoint mapPoint = MKMapPointForCoordinate(coordinate);
CGPoint mapPointAsCGP = CGPointMake(mapPoint.x, mapPoint.y);
BOOL userIsInBelgium = FALSE;
for(NSValue *valuePathBE in pathsBelgium)
{
CGPathRef pathBe;
[valuePathBE getValue:&pathBe];
BOOL pointInBelgium = CGPathContainsPoint(pathBe, NULL, mapPointAsCGP, FALSE);
if(pointInBelgium)
{
userIsInBelgium = TRUE;
break;
}
}
NSLog(#"User is in Belgium: %i", userIsInBelgium);
}

A simpler method would be to get the user's location and then do a reverse geocode. Then you can extract the country from Apple's API instead of doing all the work yourself.
More here: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/UsingGeocoders/UsingGeocoders.html

Related

How to add new Annotation with existed annotations?

In my application, I was published recorded video from particular location. I can get the list of published location details from another service response. so Initially I got that published location details and displayed it on MapView. In MapView the pins are displayed with Cluster effect, so I used kingpin.
Here below I have loaded the Annotations on Map.
- (void)loadLocations:(NSArray *)arrayValues
{
_annotationArray = arrayValues;
[self.clusteringController setAnnotations:[self reSetannotations]];
[self.mapView setZoomEnabled:YES];
[self.mapView setCenterCoordinate:self.mapView.userLocation.coordinate];
[self.mapView setUserTrackingMode:MKUserTrackingModeFollow];
KPGridClusteringAlgorithm *algorithm = [KPGridClusteringAlgorithm new];
algorithm.annotationSize = CGSizeMake(25, 50);
algorithm.clusteringStrategy = KPGridClusteringAlgorithmStrategyTwoPhase;
self.clusteringController = [[KPClusteringController alloc] initWithMapView:self.mapView
clusteringAlgorithm:algorithm];
self.clusteringController.delegate = self;
self.clusteringController.animationOptions = UIViewAnimationOptionCurveEaseOut;
[self.clusteringController setAnnotations:[self annotations]];
NSString * lastobjlat;
double miles;
CLLocation * location = [COMMON currentLocation];
lastobjlat = [NSString stringWithFormat:#"%f",location.coordinate.latitude];
miles = 1.;
double scalingFactor = ABS( (cos(2 * M_PI * [lastobjlat floatValue] / 360.0) ));
MKCoordinateSpan span;
span.latitudeDelta = miles/69.0;
span.longitudeDelta = miles/(scalingFactor * 69.0);
MKCoordinateRegion region;
region.span = span;
region.center = location.coordinate;
[self.mapView setRegion:region animated:YES];
self.mapView.showsUserLocation = YES;
//Call in the below selectAnnotationAction method when I came to this, after I published new one on existed or new locations
if (COMMON.isRecentPublication == YES) {
COMMON.isRecentPublication = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self selectAnnotationAction];
});
}
}
//To reset the Annotations
- (NSArray *)reSetannotations
{
NSMutableArray *annotations = [NSMutableArray array];
return annotations;
}
//Here I managed location details on my custom marker class MapAnnotation.
- (NSArray *)annotations {
CLLocationCoordinate2D locationPort;
NSMutableArray *annotations = [NSMutableArray array];
NSString *latitude, *longitude;
if ([_annotationArray count] > 0) {
for (int i = 0; i <[_annotationArray count]; i++){
latitude = [NSString stringWithFormat:#"%#",[[_annotationArray objectAtIndex:i] valueForKey:#"latitude"]];
latitude = [latitude stringByReplacingOccurrencesOfString:#"\n" withString:#""];
latitude = [latitude stringByReplacingOccurrencesOfString:#"\t" withString:#""];
longitude = [NSString stringWithFormat:#"%#",[[_annotationArray objectAtIndex:i] valueForKey:#"longitude"]];
longitude = [longitude stringByReplacingOccurrencesOfString:#"\n" withString:#""];
longitude = [longitude stringByReplacingOccurrencesOfString:#"\t" withString:#""];
latitude = [NSString replaceEmptyStringInsteadOfNull:latitude];
longitude = [NSString replaceEmptyStringInsteadOfNull:longitude];
int publicationRatio = [[[_annotationArray objectAtIndex:i] valueForKey:#"publicationRatio"] intValue];
int publicationCount = [[[_annotationArray objectAtIndex:i] valueForKey:#"publicationsCount"] intValue];
int teazLocationId = [[[_annotationArray objectAtIndex:i] valueForKey:#"objectId"] intValue];
BOOL isUpgrade = [[[_annotationArray objectAtIndex:i] valueForKey:#"isUpgraded"] boolValue];
locationPort = CLLocationCoordinate2DMake([latitude doubleValue] ,
[longitude doubleValue]);
//TODO : This is my custom annotation method
MapAnnotation *a1 = [[MapAnnotation alloc] initWithCoordinate:locationPort
tag:i
publicationRatio:publicationRatio
publicationCount:publicationCount
teazLocationId:teazLocationId isUpgraded:isUpgrade];
a1.itemindex = i + 1;
a1.publicationRatio = publicationRatio;
a1.publicationCount = publicationCount;
a1.teazLocationId = teazLocationId;
a1.isUpgraded = isUpgrade;
a1.coordinate = CLLocationCoordinate2DMake([latitude doubleValue] ,
[longitude doubleValue] );
[annotations addObject:a1];
if (COMMON.isRecentPublication == YES) {
if ([COMMON.recentPublicationLocationID isEqual: #(publishedLocationId)]) {
_recentAnnotationView = [[MKPinAnnotationView alloc] initWithAnnotation:a1 reuseIdentifier:#"cluster"];
}
}
}
}
return annotations;
}
It's not a problem to load Annotations with clustering effect on initial time. But when I published some one from same or different(new also) locations, I need to redirect Map screen(publishing section is other screen) and I need to display that publication detail with Active pin image.
Following steps I made to show the published location detail on Map
I created recentPublicationLocationID as a global variable and store the recentPublishedLocationID from response after published service.
Then this return type method - (NSArray *)annotations(after redirect to mapView I got the location details from another webservice after that it will called),
I have compared with recentPublishedId If existed or not. Then If existed, I have assigned my custom annotation (contains the recentPublished location details) to global MKPinAnnotationView instance - _recentAnnotationView
Then I directly called the didSelectPinAnnotation delegate method from this method - (void)loadLocations:(NSArray *)arrayValues like below,
//Pass recent published location details
if (COMMON.isRecentPublication == YES)
{
COMMON.isRecentPublication = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self selectAnnotationAction];
});
}
//selectAnnotationAction
- (void)selectAnnotationAction {
COMMON.isRecentPublication = NO;
COMMON.recentPublicationLocationID = nil;
[self mapView:self.mapView didSelectAnnotationView:_recentAnnotationView];
}
If I directly passed recentPublishedLocation details to didSelectAnnotationView delegate, I can only show the In Active pin instead of Active pin.
Then I debug with breakpoint why I can see the In active pin only ?
Because In this situation the didselect delegate was called and I can see the Active pin image. But it's only within sec.
Because viewForAnnotation delegate was called quickly for other pin annotations so the selected one goes to unselected state
This is the real problem. How can I overcome this work with clusters ?
Because when I displayed that published location detail correctly on map even it should be work with clustering effect. Yes I will zoom back to see the pins with cluster effect .
Finally I got the solution and achieved my requirement using ClusterKit library instead of Kingpin.
This tool really helpful me against possible to achieve append annotations and customize every thing adopt with my requirements.
So it's more helpful to me. Of course all of you.
And this tool supports Apple & Goole maps with Objective c as well as Swift.
I hope this one more helpful to other developers also.
Thanks :)

iOS : App crashes when zooming out a map

I have this situation where my app crashes when I zoom out the map.
The problem arises because of the large number of annotations that I'm adding. Please have a look at my code below :
- (void) plotUsersInMap
{
for (id<MKAnnotation> annotation in self.mapView.annotations) {
[self.mapView removeAnnotation:annotation];
}
NSUInteger count = //get total count
NSLog(#"count * %d", count);
for (int i = 0; i < count; i++)
{
NSNumber *latitude = //get latitude from json
NSNumber *longitude = //get longitude from json
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
#autoreleasepool {
MyLocation *annotation = [[MyLocation alloc] initWithName:#"test" coordinate:coordinate QuestionId:nil];
//annotations are added
[self.mapView addAnnotation:annotation];
}
}
}
Here I'm trying to add more than 400 pins which I think is the cause of crash [probably a memory leak!]. I would like to know if there is any way to add the pins one by one as I zoom out?
Map in initial stage, without any problem :
And when I zoom out :
Try clustering. Basically you group together annotations.
The code repo from the article I linked to: https://github.com/applidium/ADClusterMapView

iOS path following: convert list of coordinates to a bezier curve

I have an array of coordinates which represents a users route. I want to be able to redraw this route and have another user follow it exactly. Although I only want to save significant coordinates (waypoints) when the route will change direction.
Is there a way I can convert the array of coordinates to a bezier curve and use this so I only get significant coordinates instead of saving every one in an array(most of which are in the same direction and aren't needed)
I want to be able to reconstruct the route to give waypoints. These waypoints will be used to direct the user along the route as eventually I will be using MKDirections to direct them to each waypoint.
You should not use MKDirections since it is not made for following a precalculated path but to give you the fastest route to the destination! Therefore I will use MKMapOverlayRenderer instead!
There are several things you can to achieve this. I want to talk about easy algorithm for removing the unnecessary waypoints:
NSArray* path = YOURPATH
NSMutableArray* waypoints = [path mutableCopy];
CGFloat smoothness = 0.01;
for(NSInteger scale=1;scale<log2(waypoints.count);scale++)
{
for(NSInteger index = 0; index<waypoints.count-2;index++)
{
CLLocation *start = [waypoints objectAtIndex:index];
CLLocation *middle = [waypoints objectAtIndex:index+1];
CLLocation *end = [waypoints objectAtIndex:index+2];
CGFloat dist_start_end = [end distanceFromLocation:start];
CGFloat dist_start_middle_end = [middle distanceFromLocation:start]+[end distanceFromLocation:middle];
CGFloat diff = dist_start_end-dist_start_middle_end;
CGFloat ratio = diff/(dist_start_end);
if (ratio<1+smoothness) {
[waypoints removeObjectAtIndex:index+1];
index-=1;
}
}
}
This should remove the unwanted points. You should playaround with the 'smoothness' variable. The bigger the value, the more details will be removed! (therefore better compression)
Now with this array, you can construct your bezier path!
I will not go into the details on how to actually put the bezier path onto the map, there are so many other questions about this on Stackoverflow and also tutorials on the internet. But I will shortly touch on how you should create/use the two control points of the bezier path:
Given the waypoints array we created above, you will probably add some additional control points, in order to avoid driving directions taking a shortcut through a building. Therefore you should calculate the control points between two actual points on the path like this:
CGFloat cornerRadius = 5;
NSMutableArray* pathPoints = [NSMutableArray new];
if (waypoints.count>1) {
[pathPoints addObject:[waypoints objectAtIndex:0]];
[pathPoints addObject:[waypoints objectAtIndex:0]];
[pathPoints addObject:[waypoints objectAtIndex:1]];
[pathPoints addObject:[waypoints objectAtIndex:1]];
}
for (NSInteger index = 1;index<waypoints.count-1;index++) {
CLLocation* lastLocation = [waypoints objectAtIndex:index-1];
CLLocation* currentLocation = [waypoints objectAtIndex:index];
CLLocation* nextLocation = [waypoints objectAtIndex:index+1];
[pathPoints addObject:currentLocation];
CGFloat latDiff = currentLocation.coordinate.latitude - lastLocation.coordinate.latitude;
CGFloat longDiff = currentLocation.coordinate.longitude - lastLocation.coordinate.longitude;
CGFloat dist = [currentLocation distanceFromLocation:lastLocation];
CGFloat controlPointALat = cornerRadius*latDiff/dist;
CGFloat controlPointALong = cornerRadius*longDiff/dist;
CLLocation* controlPointA =
[[CLLocation alloc] initWithLatitude:controlPointALat longitude:controlPointALong];
[pathPoints addObject:controlPointA];
if (index<waypoints.count-2) {
CLLocation* nextNextLocation = [waypoints objectAtIndex:index+2];
latDiff = nextNextLocation.coordinate.latitude - nextLocation.coordinate.latitude;
longDiff = nextNextLocation.coordinate.longitude - nextLocation.coordinate.longitude;
dist = [nextNextLocation distanceFromLocation:nextLocation];
CGFloat controlPointBLat = cornerRadius*latDiff/dist;
CGFloat controlPointBLong = cornerRadius*longDiff/dist;
CLLocation* controlPointB =
[[CLLocation alloc] initWithLatitude:controlPointBLat longitude:controlPointBLong];
[pathPoints addObject:controlPointB];
}else{
[pathPoints addObject:controlPointA];
}
[pathPoints addObject:nextLocation];
}
Now you can use this pathArray to build your bezierPaths with a simple for loop:
for(NSInteger index=0;index<pathPoints.count-3;index+=4)
{
CLLocation *start = [pathPoints objectAtIndex:index];
CLLocation *controlPointA = [pathPoints objectAtIndex:index+1];
CLLocation *controlPointB = [pathPoints objectAtIndex:index+2];
CLLocation *end = [pathPoints objectAtIndex:index+3];
//BEZIER CURVE HERE
}
You can either use a UIBezierPath and use the CGPath property to create your MKMapOverlayRenderer, or you can directly use a CGPath. Later adding the path to your map is described here:
MKMapOverlayRenderer
Createing a UIBezierPath is described here:
UIBezierPath
You can take an array of NSValues because you can't add a CGPoint directly into an Array
This is how we do it
NSMutableArray *arrayOfPoints = [[NSMutableArray alloc] init];
[arrayOfPoints addObject:[NSValue valueWithCGPoint:CGPointMake(10, 20)]];
// here point is the cgpoint you want to add
Now you have your points added to an array what you can do is traverse every object and add them to your UIBezierPath
NSInteger i = 0;
UIBezierPath _path1 = [UIBezierPath bezierPath];
for(NSValue *val in arrayOfPoints){
if (i==0){
[_path1 moveToPoint:[val CGPointValue]];
}
else{
[_path1 addLineToPoint:[val CGPointValue]];
}
i++;
}
I hope this helps and solve your problem

How to load grouped annotation point from SQLite

I have a database that contains 16000 annotations. When the user is zooming out, there are too many points loaded and points overlap. I found a filter with code but i want to load some point only from SQL, not to load all and filter it after. I don't know if there is a stored procedure in iOS or other solution.
This is my code
iphoneScaleFactorLatitude = 3.3;
float iphoneScaleFactorLongitude = 6.38;
float latDelta = self.mapView.region.span.latitudeDelta/iphoneScaleFactorLatitude;
float longDelta = self.mapView.region.span.longitudeDelta/iphoneScaleFactorLongitude;
NSMutableArray *shopsToShow = [[NSMutableArray alloc] initWithCapacity:0];
for (int i=0; i<[placesToFilter count]; i++) {
MKPointAnnotation *checkingLocation = [placesToFilter objectAtIndex:i];
CLLocationDegrees latitude = [checkingLocation coordinate].latitude;
CLLocationDegrees longitude = [checkingLocation coordinate].longitude;
bool found=FALSE;
for (MKPointAnnotation *tempPlacemark in shopsToShow) {
if(fabs([tempPlacemark coordinate].latitude-latitude) < latDelta &&
fabs([tempPlacemark coordinate].longitude-longitude) <longDelta ){
[self.mapView removeAnnotation:checkingLocation];
found = TRUE;
break;
}
}
if (!found){
[shopsToShow addObject:checkingLocation];
[self.mapView addAnnotation:checkingLocation];
}
}
But they load too many point and my problem is that I don't want to load all the points because each chagement I have to use the algorithm to the sorting.

calling [mapView addOverlay:] has no effect

For some reason, when I use [mapView addOverlay:], nothing happens.
Code:
NSData* dataLat = [[NSUserDefaults standardUserDefaults] objectForKey:#"latKey"];
NSArray* overlayLat = [NSKeyedUnarchiver unarchiveObjectWithData:dataLat];
double lats[overlayLat.count];
NSData* dataLong = [[NSUserDefaults standardUserDefaults] objectForKey:#"longKey"];
NSArray* overlayLong = [NSKeyedUnarchiver unarchiveObjectWithData:dataLong];
double longs[overlayLong.count];
for(int iii = 0; iii < overlayLat.count; iii++)
{
NSNumber* a = (NSNumber*)[overlayLat objectAtIndex:iii];
lats[iii] = [a doubleValue];
}
for(int iii = 0; iii < overlayLong.count; iii++)
{
NSNumber* a = (NSNumber*)[overlayLong objectAtIndex:iii];
longs[iii] = [a doubleValue];
}
int size = (sizeof(lats) / sizeof(lats[0]));
NSLog(#"%d", size);
MKMapPoint points[size];
for(int iii = 0; iii < overlayLong.count; iii++)
{
MKMapPoint point = MKMapPointMake(lats[iii], longs[iii]);
if(lats[iii] != 0)
points[iii] = point;
}
for(int iii = 0; iii < 15; iii++)
{
NSLog(#"Lat (x):%f", points[iii].x);
NSLog(#"Long (y):%f", points[iii].y);
}
MKPolyline* line = [MKPolyline polylineWithPoints:points count:size];
[mapView addOverlay:line];
NSLog(#"finished");
The vast majority of this code just accesses the data and turns it into usable coordinates. My question is (because I know that the coordinates are valid because of the NSLogs), why doesn't anything get added to the mapView when addOverlay: is called? I can post more code if need be. Thanks!
The polylineWithPoints:count: method takes a C array of MKMapPoint structs.
An MKMapPoint is not the same as a CLLocationCoordinate2D (latitude, longitude). You are creating MKMapPoint structs using MKMapPointMake but giving it latitude and longitude values. Such an MKMapPoint is not at the expected location.
Either use MKMapPointForCoordinate to create an MKMapPoint from lat/long coordinates or, probably easier, create a C array of CLLocationCoordinate2D structs and call polylineWithCoordinates:count: instead.
Could it be your 'size' is wrong? Your points array will also have missing items anywhere the source data had 0. I'd go with an NSMutableArray and add items to that if they are deemed valid, then count that at the end and pass it to polylineWithPoints.

Resources