There are thousands of data that I want to add on google map using clustering.
Problem
It is possible that multiple records may have same latitude and longitude so I want to add those marker in such a way that it doesn't overlap one another. They can be close to each other but not overlapped.
So I have went through the google place api documentation and even googled or the various articles but I didn't found a solution that solves this issue.
Setting pin on map:-
- (void)setPinsToMap {
[self.mapGoogle clear];
for (int i=0; i<arrUsers.count; i++) {
ClassUser *cls =[arrUsers objectAtIndex:i];
double lat;
double long1;
//Here, what to do with the location coordinates?
lat = [cls.strLat doubleValue];
long1 = [cls.strLong doubleValue];
NSLog(#"\n------------------\nname = %# lat = %#, lng = %#\n---------------\n", cls.strUserName, cls.strLat, cls.strLong);
NSString *name = [NSString stringWithFormat:#"%d", i];
id<GMUClusterItem> item = [[POIItem1 alloc] initWithPosition:CLLocationCoordinate2DMake(lat, long1) name:name];
[_clusterManager addItem:item];
[_clusterManager cluster];
}
}
Could any one suggest a way to get this fixed. Any help will be welcomed.
Related
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 :)
I need some help with a filter. How do I implement an algorithm that filters out related map annotations within the radius of user's location, thanks a lot guys! Any feedback is appreciated! Thank you and have a nice day.
My code I have so far:
CLLocationCoordinate2D coordinates;
CLLocationDistance test = 1000; // set distance
coordinates.latitude = [[row objectForKey:#"latitude"] doubleValue];// get user's latitude
coordinates.longitude = [[row objectForKey:#"longitude"] doubleValue];
MapAnnotation *annotation = [[MapAnnotation alloc] initWithCoordinate:coordinates title:titles subtitle:contents image:[row objectForKey:#"image"]]; // specify annotation's detail
[self.mapView addAnnotation:annotation];// adding annotation into the map
[annotation release];
I'm working on an application for the iPhone and I'm keeping track of the users current location. When the didupdateLocations delegate method actually executes i would like to test if the location in the NSArray is in a predefined array the contains other locations, this array mind you could grow over time.
I'm running a for loop within this method to test against my own array of locations but i would like to move that to a separate thread. So in case my own array with multiple locations grows to a large number the for loop does not freeze my UI.
I have tried it like this but I'm getting undesirable results. I understand that the location tracking definitely happens in a separate thread. However those didupdateLocations execute on a separate thread. The Apple doc's are not very clear on the matter. My end goal again is to compare against my array and not lock the UI.
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)thisLocation {
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// get the last object in the array of locations
CLLocation* location = [thisLocation lastObject];
dispatch_async(queue, ^{
[self checkmyArray:location];
});
}
-(void)checkmyArray:(CLLocation *)workingLocation{
NSLog(#"SoundTheAlarm");
int alarm_on_c = 0;
NSUInteger tmp_count = [theData.LocationsObjectArray count];
BOOL alarm;
NSMutableDictionary * tempObject;
CLLocationDistance distance = 0.0;
for (int i = 0; i < tmp_count; i++) {
tempObject= [theData.LocationsObjectArray objectAtIndex:i];
thisLoc = [[tempObject objectForKey:#"onoff"] isEqual:#YES];
if (thisLoc) {
//check if we are near that location
double lat = [[tempObject objectForKey:#"latitude"] doubleValue];
double lon = [[tempObject objectForKey:#"longitude"] doubleValue];
// goal = [[CLLocation alloc] initWithLatitude:40.097771 longitude:-74.941399];
goal = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
// check the destination between current location and goal location - in meters
distance = [goal distanceFromLocation:workingLocation];
NSLog(#"distance %f\n", distance);
}
// if distance from goal is less than 350 meters
if (distance <= 350){
[self scheduleNotification:[tempObject objectForKey:#"name"]];
// turn off tracking for this location
[tempObject setObject:#NO forKey:#"onoff"];
[theData.LocationsObjectArray replaceObjectAtIndex:i withObject:tempObject];
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
ExtendedSavedCellTableViewCell *cell = (ExtendedSavedCellTableViewCell *)[self.tableView cellForRowAtIndexPath:path];
cell.switchView.on = NO;
// save the update to the switch to the database as well
NSString *lat = [tempObject objectForKey:#"latitude"];
/*check to determine if the uiswitch is turned off or on.*/
[self fetchedResultsController:#NO lat:lat index:path];
[self displayAlertViewForAlarm:[tempObject objectForKey:#"name"]];
}
-(void)displayAlertViewForAlarm:(NSString *)nameOfLocation{
dispatch_async(dispatch_get_main_queue(), ^(void) {
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Destination reached"
message:nameOfLocation
delegate:self
cancelButtonTitle:#"Go Away"
otherButtonTitles:#"Notify", nil];
[myAlert show];
});
}
It's generally a bad idea to use threads in iOS if you can at all avoid it. In your case, I'd implement the function which does the looping to automatically pop out of the loop after too many iterations and then schedule the next chunk of iterating to happen in another pass through the event loop. In other words, something like this:
- (void) checkLocationsStartingAt:(NSNumber)start
{
NSInteger s = (start) ? [start intValue] : 0;
for (i = s; i < list.length; i++) {
if (i > TOO_MANY) {
[self performSelector:#selector(checkLocationsStartingAt:)
withObject:#(i)
afterDelay:0.001];
return;
} else {
// check element at i
}
}
}
see: NSObject Reference
You are checking one location against an array of other locations.
You display an alert for time you are closer than 350m from a location.
You could be within 350m of many items in the list.
You have no code prevent multiple alerts.
So you will sometimes have many alerts.
You have a couple options. One will probably suit your needs better than the others. Possibly one I didn't list.
You could re-use a single UIAlertView instance and check the
visible property on it. If it's already visible, do nothing.
You could break out of the loop once you get a single "hit" on being
<350m.
You could stick in a BOOL which records if you've shown the alert and
check the BOOL every time before you show the alert again.
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
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.