How to optimize code for drawing map overlay - iphone

I draw path on MKMapView based on coordinates stored in SQLite on iPhone.
But now I stored 14000 coordinates (just lat/lng) in database and now when I want to display overlay path I get application crash.
My question is is there any way to optimize this code to be faster?
This is in view did load:
// ar is NSMutableArray and it is populate from database for a few seconds but code bellow cost me app crash
for(Path* p in ar)
{
self.routeLine = nil;
self.routeLineView = nil;
// while we create the route points, we will also be calculating the bounding box of our route
// so we can easily zoom in on it.
MKMapPoint northEastPoint;
MKMapPoint southWestPoint;
// create a c array of points.
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * ar.count);
for(int idx = 0; idx < ar.count; idx++)
{
Path *m_p = [ar objectAtIndex:idx];
CLLocationDegrees latitude = m_p.Latitude;
CLLocationDegrees longitude = m_p.Longitude;
// create our coordinate and add it to the correct spot in the array
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);
MKMapPoint point = MKMapPointForCoordinate(coordinate);
// adjust the bounding box
// if it is the first point, just use them, since we have nothing to compare to yet.
if (idx == 0) {
northEastPoint = point;
southWestPoint = point;
}
else
{
if (point.x > northEastPoint.x)
northEastPoint.x = point.x;
if(point.y > northEastPoint.y)
northEastPoint.y = point.y;
if (point.x < southWestPoint.x)
southWestPoint.x = point.x;
if (point.y < southWestPoint.y)
southWestPoint.y = point.y;
}
pointArr[idx] = point;
}
// create the polyline based on the array of points.
self.routeLine = [MKPolyline polylineWithPoints:pointArr count:ar.count];
_routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);
// clear the memory allocated earlier for the points
free(pointArr);
[self.mapView removeOverlays: self.mapView.overlays];
// add the overlay to the map
if (nil != self.routeLine) {
[self.mapView addOverlay:self.routeLine];
}
UPDATE
ViewDidLoad:
...
[self performSelectorInBackground:#selector(drawPathInBackground) withObject:nil];
...
-(void)drawPathInBackground{
for(int idx = 0; idx < ar.count; idx++)
{ ... }
[self.mapView performSelector:#selector(addOverlay:) onThread:[NSThread mainThread] withObject:self.routeLine waitUntilDone:YES];
}
I did like this and UI not freezes.
The only thing that left is how to draw MKPolyLine on every X points?

three approaches:
don't display every point but rather combine nearby points to just one. the saving depends on your data and the necessity to display all.
if possible load the data in a background thread and display in multiple batches on the main thread. the user will practically see how the data is loaded after time.
load and display data lazily. means: only display those points which are visible on screen

Do fetching from the database and processing in a background thread.
Then reduce the number of coordinates in the path using the Douglas–Peucker algorithm:
And cache the results.

If you have array of coordinates the use this code
here routes is array of coordinates.
NSLog(#"count %d",[routes count]);
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * [routes count]);
for(int idx = 0; idx < [routes count]; idx++)
{
CLLocation* location = [routes objectAtIndex:idx];
CLLocationCoordinate2D workingCoordinate;
workingCoordinate.latitude=location.coordinate.latitude;
workingCoordinate.longitude=location.coordinate.longitude;
NSLog(#"loop = %f,%f",workingCoordinate.latitude, workingCoordinate.longitude);
MKMapPoint point = MKMapPointForCoordinate(workingCoordinate);
pointArr[idx] = point;
}
// create the polyline based on the array of points.
self.routeLine = [MKPolyline polylineWithPoints:pointArr count:[routes count]];
[mapView addOverlay:self.routeLine];
free(pointArr);
Hope this helps.

google has a algorithm that can encode the locations to string.
for your situation , 14000 coordinates will be encoded to a String nearly 14000 length.
then put the String into sqlite.
it will accelerate the speed to get data from DB

Related

Find whether a point is inside a given polygon?

I have downloaded this code. I want to check whether the users current location belongs to any of the region. For that i have called following method but its always returning FALSE. Below is my code:
-(void)checkPoint
{
CLLocationCoordinate2D mapCoordinate ;
mapCoordinate.longitude=34.54664654;
mapCoordinate.latitude=-117.05646;
NSMutableArray *overlays = (NSMutableArray *)[HHLViewController usStatesAndTerritoryOverlays];
MKMapPoint mapPoint = MKMapPointForCoordinate(mapCoordinate);
for (int i=0; i<[overlays count]; i++)
{
MKOverlayView *overlayview = (MKOverlayView *)[self.stateMapView viewForOverlay:[overlays objectAtIndex:i]];
MKPolygonView *polygonView =[[MKPolygonView alloc] initWithOverlay:overlayview.overlay];
CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon =
CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO);
NSLog(#"polygon is inside %d",mapCoordinateIsInPolygon);
}
}
My problem is i want always getting NO value in mapCoordinateIsInPolygon eventhough the point belongs to that region.
I dont want to display map so any other options are also welcomed.

Issue with drawing path over MapKit

I have created an application that constantly reads the current user coordinates and store them in a SQLite database.
I have a map that is displayed over the whole screen.
And now I want to draw a line over the map while the user moves.
I already created all this.
The problem is that I can't make it to be a 'live'. The Overlay is not updating.
This is the logic:
In ViewDidLoad I have
...
if (nil != self.routeLine) {
[self.mapView addOverlay:self.routeLine];
}
In a function that handle each new coordinates I have:
...
NSString* coordinate = [NSString stringWithFormat:#"%f,%f", thisLocation.longitude, thisLocation.latitude];
[self.paths addObject:coordinate];
MKMapPoint northEastPoint;
MKMapPoint southWestPoint;
// create a c array of points.
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * self.paths.count);
for(int idx = 0; idx < self.paths.count; idx++)
{
// break the string down even further to latitude and longitude fields.
NSString* currentPointString = [self.paths objectAtIndex:idx];
NSArray* latLonArr = [currentPointString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#","]];
CLLocationDegrees latitude = [[latLonArr objectAtIndex:1] doubleValue];
CLLocationDegrees longitude = [[latLonArr objectAtIndex:0] doubleValue];
// create our coordinate and add it to the correct spot in the array
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);
MKMapPoint point = MKMapPointForCoordinate(coordinate);
// adjust the bounding box
// if it is the first point, just use them, since we have nothing to compare to yet.
if (idx == 0) {
northEastPoint = point;
southWestPoint = point;
}
else
{
if (point.x > northEastPoint.x)
northEastPoint.x = point.x;
if(point.y > northEastPoint.y)
northEastPoint.y = point.y;
if (point.x < southWestPoint.x)
southWestPoint.x = point.x;
if (point.y < southWestPoint.y)
southWestPoint.y = point.y;
}
pointArr[idx] = point;
}
// create the polyline based on the array of points.
self.routeLine = [MKPolyline polylineWithPoints:pointArr count:self.paths.count];
_routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);
// clear the memory allocated earlier for the points
free(pointArr);
This is viewForOverlay delegate function:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayView* overlayView = nil;
if(overlay == self.routeLine)
{
//if we have not yet created an overlay view for this overlay, create it now.
if(nil == self.routeLineView)
{
self.routeLineView = [[MKPolylineView alloc] initWithPolyline:self.routeLine];
self.routeLineView.fillColor = [UIColor blueColor];
self.routeLineView.strokeColor = [UIColor blueColor];
self.routeLineView.lineWidth = 5;
}
overlayView = self.routeLineView;
}
return overlayView;
}
In viewDidLoad, the code calls addOverlay with self.routeLine which I assume is initially set to the previously-saved coordinates.
The map view adds the MKPoyline that routeLine points to into its internal list of overlays and draws the overlay.
Then, in the "function that handles each new coordinate", self.routeLine is changed to point to a new MKPolyline.
The reason the overlay view is not updated by the map is because the map view is still using the original MKPolyline that was passed to it when addOverlay was called.
Since MKPolyline itself is not mutable (it does not allow one to change the list of points/coordinates after creation), there are two main options:
Remove the existing overlay and add a new one with the updated coordinates. This is the simplest option. After setting self.routeLine to a new MKPolyline, do the following (for example):
[self.mapView removeOverlays:mapView.overlays];
self.routeLineView = nil;
[self.mapView addOverlay:self.routeLine];
The main drawback to this simple approach is that the overlay will seem to flicker if the updates are done frequently or if the overlay is large or complex.
The alternative is to create a custom overlay and overlay view that are mutable and enabling you to dynamically refresh the overlay more quickly and smoothly.
Fortunately, Apple has provided a sample app called Breadcrumb which does exactly that.
Maybe you just forgot to call [self.view setNeedsDisplay]?
UIView docs

CLLocationManager tracks wrong Location (Track Me)

I am implementing Track me option in my code.CLLocationManager is not working as expected.when I start app remain at the same position ,CLLocationManager changes around 20-30 meters with-in 1 min minutes..then I remain Constant.
And If i change my position to track same thing happen in starting 1 min CLLocationManager moves 20-30 min extra then moves with my speed..
Why this happening..
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.distanceFilter = 0.0001;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
-(void)start {
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager*)manager
didUpdateToLocation:(CLLocation*)newLocation
fromLocation:(CLLocation*)oldLocation {
[self processLocationChange:newLocation fromLocation:oldLocation];
}
-(void)processLocationChange:(CLLocation*)newLocation fromLocation:oldLocation {
if (newLocation != oldLocation) {
NSLog(#"Moved from %# to %#", oldLocation, newLocation);
CLLocation* lastKnownLocation = NULL;
if ([self.locationPoints count] > 0) {
lastKnownLocation = [self.locationPoints objectAtIndex:[self.locationPoints count] - 1];
}
else {
lastKnownLocation = newLocation;
self.bottomLeft = newLocation.coordinate;
self.topRight = newLocation.coordinate;
}
// Check for new boundaries
CLLocationCoordinate2D coords = newLocation.coordinate;
if (coords.latitude < bottomLeft.latitude || coords.longitude < bottomLeft.longitude) {
self.bottomLeft = coords;
NSLog(#"Changed bottom left corner");
}
if (coords.latitude > topRight.latitude || coords.longitude > topRight.longitude) {
self.topRight = coords;
NSLog(#"Changed top right corner");
}
double speed = fabs(newLocation.speed);
double deltaDist = fabs([newLocation distanceFromLocation:lastKnownLocation]);
double newAvgSpeed = (self.totalDistance + deltaDist) / ((double)[self getElapsedTimeInMilliseconds] / 1000.0);
double accuracy = newLocation.horizontalAccuracy;
double alt = newLocation.altitude;
NSLog(#"Change in position: %f", deltaDist);
NSLog(#"Accuracy: %f", accuracy);
NSLog(#"Speed: %f", speed);
NSLog(#"Avg speed: %f", newAvgSpeed);
self.totalDistance += deltaDist;
self.currentSpeed = speed;
self.avgSpeed = newAvgSpeed;
self.altitude = alt;
NSLog(#"Delta distance = %f", deltaDist);
NSLog(#"New distance = %f", self.totalDistance);
// Add new location to path
[self.locationPoints addObject:newLocation];
// Update stats display
[self.first.start1 updateRunDisplay];
// Update map view
[self updateMap:lastKnownLocation newLocation:newLocation];
}
}
I faced the same problem in my current Pedometer app. I stretched, banged my head for couple of days. Then I found out that CLLocationManager is not able to track <5 meter distance and location generate updates. I kept self.locationManager.distanceFilter =2.0; and it gave me location updates even device was stationary. So I just changed distancefilter to 5.0 meter and it started working great. Try taking 5 meters it should work, I tested and all my wrong notifications issues vanished:
self.locationManager.distanceFilter =5.0;
You are taking self.locationManager.distancefilter=0.0001 which is I suppose out of capacity for CLLocationManager to track such a minor movement. Also you need to filter out old locations i.e. cached location updates as mentioned in Location Awareness Guide by Apple. I have used this condition in my code to filter all events which are older than 5 seconds.
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
{
CLLocation *currentLocation=[locations lastObject];
NSDate* eventDate = currentLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if(abs(howRecent)<5.0 && self.currentLocation.horizontalAccuracy<=10 && self.currentLocation.horizontalAccuracy>0)
{
//you have got fresh location event here.
}
}
I think give the distance filter effective with this
self.locationManager.distanceFilter = kCLDistanceFilterNone;
and you can start updating location method but also try this method these both methods are required to get exact location
[locationManager startMonitoringSignificantLocationChanges];

Why does MKMetersBetweenMapPoints give me different results when I swap the parameters?

I'm actually trying to calculate the distance between the max and min point in the x and y coordinates for the MKMapPoints.
For that, I'm doing this (max distance in y axis):
MKMapPoint test1, test2;
double dist;
test1.x = 0.0;
test1.y = 0.0;
test2.x = 0.0;
test2.y = MKMapSizeWorld.height;
dist = MKMetersBetweenMapPoints(test2, test1);
NSLog(#"Distance %f",dist);
I get 18997878.291251 in the console. But when I change the distance calculation to:
dist = MKMetersBetweenMapPoints(test1, test2);
I get 18873651.664238, so I don't understand what's the difference. I don't even know if I'm doing the right thing to get the max values of distance in the x and y axes.
Any help will be appreciated.
I guess it's an algorithm problem. Some kind of approximation that stops when a certain precision is achieved. That's why no commutation there. You can try sample code to get distance between two points on map without using MKMapPoints:
- (float) distanceToLPU {
useDelegate
CLLocationCoordinate2D pointACoordinate = appDelegate.usrLoc;
CLLocation *pointALocation = [[CLLocation alloc] initWithLatitude:pointACoordinate.latitude longitude:pointACoordinate.longitude];
CLLocationCoordinate2D pointBCoordinate;
pointBCoordinate.latitude = [self.latitude doubleValue];
pointBCoordinate.longitude = [self.longitude doubleValue];
CLLocation *pointBLocation = [[CLLocation alloc] initWithLatitude:pointBCoordinate.latitude longitude:pointBCoordinate.longitude];
float distanceMeters = [pointALocation distanceFromLocation:pointBLocation];
[pointALocation release];
[pointBLocation release];
NSString *dist = [NSString stringWithFormat:#" (%.0f м)", distanceMeters];
NSLog(#"Distance to this LPU:%#", dist);
return distanceMeters;
}

iPhone - remove pin annotations of map, but not mine

I use a MapView and I set annotations on it (purple pins) and my user location (which is a blue circle).
Because the purple pin annotations will move, I have to remove and set them new to the map.
I set it with:
CLLocationCoordinate2D coordinate;
coordinate.latitude = 49.2802;
coordinate.longitude = -123.1182;
NSUInteger count = 1;
for(int i = 0; i < 10; i++) {
CGFloat latDelta = rand()*.035/RAND_MAX - .02;
CGFloat longDelta = rand()*.03/RAND_MAX - .015;
CLLocationCoordinate2D newCoord = {coordinate.latitude+latDelta, coordinate.longitude+longDelta};
MyMapAnnotation* annotation = [[MyMapAnnotation alloc] initWithCoordinate:newCoord andID:count++];
[mapView addAnnotation:annotation];
[annotation release];
}
Before that, I do a
[mapView removeAnnotations:mapView.annotations];
but this line also remove my location with the blue point!
How could I do this without removing my location.
Thanks a lot in advance & Best Regards.
A very easy way to do this is to include this line before the loop where you remove the annotations:
self.mapView.showsUserLocation = NO;
and then after the loop, put the user location back in
self.mapView.showsUserLocation = YES;
EDIT: I just saw that you didn't loop through the locations to remove them. I'm not sure if this will work with the way you do it.