How can I group MKAnnotations automatically regarding zoom level? - iphone

if the user zooms out on a MKMapView, i want MKAnnotations which are near to each other automatically grouped into one "group" annotation.
if the user zooms back in, the "group" annotation should be split again to the unique/original annotations.
apple does this already in the iOS 4 Photos.app
is there a common, "predefined" way to do this?

Its normal working with more than 1500 annotations on the map:
-(void)mapView:(MKMapView *)mapView_ regionDidChangeAnimated:(BOOL)animated
{
NSMutableSet * coordSet = [[NSMutableSet alloc] init];
for(id<MKAnnotation> an in mapView_.annotations)
{
if([an isKindOfClass:[MKUserLocation class]])
continue;
CGPoint point = [mapView_ convertCoordinate:an.coordinate toPointToView:nil];
CGPoint roundedPoint;
roundedPoint.x = roundf(point.x/10)*10;
roundedPoint.y = roundf(point.y/10)*10;
NSValue * value = [NSValue valueWithCGPoint:roundedPoint];
MKAnnotationView * av = [mapView_ viewForAnnotation:an];
if([coordSet containsObject:value])
{
av.hidden = YES;
}
else
{
[coordSet addObject:value];
av.hidden = NO;
}
}
[coordSet release];
}

That's a brilliant idea. I'm working on a similar app, I hope you don't mind if I als implement the concept :).
To answer your question to the best of my own ability, no, I don't think there is a predefined way to do this.
The best way I can think of to do it (after looking at the iOS4 photos app), is to make use of the mapView:regionDidChangeAnimated: delegate method. Any time the user scrolls/zooms, this method will be called.
Inside that method, you could have some quick geometry math to determine whether your points are "close enough" to consider merging. Once they're "merged", you'd remove one or both annotations, and put another annotation back in the same place that is a reference to both (you could make an AnnotationCluster class very easily that could conform to MKAnnotation but also hold an NSArray of annotations, and also contain methods for "breaking out" or "absorbing" other annotations and/or AnnotationCluster instances, etc).
When I say "quick geometry math", I mean the distance of the two points relative to the span of the map, and taking their relative distance as a percentage of the span of the whole map.
Where that would get tricky is if you had hundreds of annotations, as I can't off-hand think of a good way to implement that w/o a double loop.
What do you reckon?

This project does something interesting. Though, have a look at reported issues before changing too many things in your code. Because it could be not good enough for your needs yet.
I personnaly ended up implementing this

Related

Displaying google direction api response on Map

I am using google api V3 in my iOS App,
I requested to google api direction service, by http GET method, I got a very large json response, I got many alternate routes from origin to destination, now I want to show the route of each alternative on map, how should I done this ? is there any need to integrate google sdk in iOS, or can i use webView only, please help me, and suggest the simplest way.
Thanks in advance
you do not need to include any google SDK to draw the route over map. Look at the following classes.
MKPolyline
MKPolyLineview
The google map api Direction service will give you the legs(coordinates) to draw the route between two specific points.
As you already have the json response from the Direction api now you have to parse the json and get all the legs point from the json to create Coordinate array.
Jsonkit for parsing
These points may be or mostly encripted. How to decode the Google Directions API polylines field into lat long points in objective-C for iPhone?
If you have the coordinates array then first you have to create a CLLocationCoordinate2D array like following:
CLLocationCoordinate2D *pointArr = malloc(sizeof(CLLocationCoordinate2D) * [your_CoordinateArray count]);
for(int i = 0; i < [your_CoordinateArray count]; i++){
pointArr[i] = [your_CoordinateArray objectAtIndex:i];
}
Then you have to add the polyline to your map
MKPolyline *line = [MKPolyline polylineWithCoordinates:pointArr count:[your_CoordinateArray count]];
if (nil != line)
{
[your_Map addOverlay:line];
}
Then you have to implement the following map delegate:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayView* overlayView = nil;
lineView = [[MKPolylineView alloc] initWithPolyline:overlay];// lineView is an ivar, object of MKPolylineView
lineView.fillColor = [UIColor greenColor];
lineView.strokeColor = [UIColor greenColor];
lineView.lineWidth = 10.0;
lineView.alpha = 0.9;
overlayView = lineView;
return overlayView;
}
Now, if you want to show two different routes between two points then you have to create two different array of coordinates and apply the above method for both Arrays
Hope this will help you.
There is no need per say to integrate google sdk and you can simply use webView since I believe the webView gives you alternative views, but the user interface is much cleaner in the SDK than online. But at this moment, I don't believe the SDK allows other apps to show the alternative routes from what I've used of the new SDK. The code in maps.google.com is now considerably different from that in
the API and has access to different services. In particular, it has a large external module, mod_suggest.js, that doesn't exist in the API. But you may be able to implement it by
1) Throw the request at maps.google.com, and let it do the suggestions,
i.e. open a new browser window and pass it something like
http://www.google.com/maps?f=d&source=s_d&saddr=Jurong+West,+Singapore&da
ddr=Ang+Mo+Kio,+Singapore&hl=en
2) Just display the normal route and the avoid highways route.
3) Try to guess sensible waypoints to add in the middle of he route that
might lead to reasonable alternatives. That's not easy unless you have a
database of highways or highway intersections, and can look for such an
intersection that's somewhere between your start and end points (for
some approximate value of "between").
There may be problems using lat/lng coordinates for the waypoints,
particularly if there's a dual carriageway involved. If the coordinates
are for a point on the wrong carriageway, GDirections will drive a
considerable distance out of your way so that you visit the wrong side
of the road. But even using street names you may well get strange kinks
near the waypoints, like this:
from: Jurong West, Singapore to: Ayer Rajah Expy singapore to: Ang Mo
Kio, Singapore
Perhaps the only way to really deal with that is to include points on
both sides of a dual carriageway, and well clear of roads that cross
underneath or overhead, and then try to filter out the ones that are
silly.
Consider these examples
from: Jurong West, Singapore to: 1.32558,103.740764 to: Ang Mo Kio,
Singapore
from: Jurong West, Singapore to: 1.32582,103.740764 to: Ang Mo Kio,
Singapore
One of those adds 7 minutes to the trip by a complicated excursion to
visit the wrong side of the road.
Writing code to filter out silly routes isn't easy. As a first
approximation, you could discard routes that have estimated times that
are more than, say, 10% longer than the initial route. And you can also
compare the durations for pairs of points on opposite sides of a dual
carriageway and always discard the slower one.
Writing code to discard duplicate routes isn't easy either. For example,
a point on Bukit Timah Expy or Kranji Expy might create a route that's a
duplicate of Google's Seletar Expy suggestion.
You might want to look at: http://www.geocodezip.com/example_geo2.asp?waypts=yes&addr1=Jurong+West,+Singapore&addr2=Ang+Mo+Kio,+Singapore&waypt=Ayer+Rajah+Expy+singapore&geocode=1&geocode=2
But that may not be a legal implementation since the API does not allow it. Hope this helps.

NSMutableArray Difficulty

If you wanna see the code Im having problem with, here is the link:
Code
My question is connected with my past question.
I'm really having problem with my NSMutableArray, I'm currently using iCarousel for my slotMachine object(slot1 and slot2). My app works this way:
From PhotoViewController I made a view that has thumbnail images, then assign its frame with button. So if 1 image was pressed, it will save that integer via NSUserDefaults.
Then I will retrieve it in my carouselViewController
Im thinking of adjusting the array but I can't.
I also have tried my question here:
Comparing with NSMutableArray
If only I can do it the same as Array 2 it would be much easy, but still not working.
(ADDITIONAL INFO:)
I have done it this way, have a Viewcontroller that contains the UIImageView with a button in it, so when the user taps it, my CustomPicker pops up. My CustomPicker contains the image on what the user have picked on the camera roll. So each button has a specific value sent to my iCarouselView using NSUserDefaults. carousel1 for First slot and carousel2 for Second slot.
Here is what I wanna do: I want to forcefully make it stop to the index the user picks. (Which Im doing in my carouselDidEndScrollingAnimtaion)
In my carouselDidEndScrollingAnimation method i tested all of my condition(individually) it works perfectly in terms of comparing.
Then when I combine the conditions, the first Two comparison or STOP is RIGHT, but the next two are always wrong. Or sometimes Got mixed up.
I need to scroll the two specific indexes/integer which was User Picked( I already done that) was able to scroll 2 pairs of them but then the next two were always wrong because I think there indexes were adjusting.
PICTURES:
Image Below is my PhotoViewController which contained the Comparing Stage SETTING of my game.UIImageVIew with UIButton.Image that will be put in the number according to it will be Forcefully and should be forcefully shown.
When my iCarousel start then it stops for example in the image below(Which is not the same as the above):
Will be forcefully scroll to the inputted image in the PhotoViewController
Into:
Summary:
Its like this. I have a settingsView from there, I will import my images(Multiple) for Slot1 & Slot2.
Then in another View the PhotoViewController that is where the image above is shown. THe first column corresponds to 1st slot followed by the 2nd slot. if a view is pressed (for example No. 1 of Slot 1 it will load a thumbnail of images loading the images picked from Picker for the Slot 1.
You will have to do it 4 times(pair) ----> The displayed here I get their indexes via NSUserDefaults via button.tag then send to iCarouselView.
Then when you are done (pressed Done button) it will go to iCarouselView then, as shown above thats the view of it.
When pressed it will spin for couple of seconds, then when finished but not stop at the user picked in the PhotoView it will forcefully scroll to that index.
QUESTION:
Is there a way to make my array or my iCarousel.view not adjust their indexes when Im deleting. To still retain my indexes the right way. Or are there other solution like adjusting my array, the same as adjusting my PhotoViewController picked indexes too. Because I think that when my array retain their indexes even deleting I would be able to solve this problem. But still can't.
Hope you understand my question.
Is there a way to make my array or my iCarousel.view not adjust their indexes when Im deleting. To still retain my indexes the right way. Or are there other solution like adjusting my array, the same as adjusting my PhotoViewController picked indexes too. Because I think that when my array retain their indexes even deleting I would be able to solve this problem. But still can't.
The only way you have to modify the way iCarousel manages its indexes is by modifying the code. Indeed, if you look at the removeViewAtIndex method in iCarousel.m, you will see that indexes are managed through an NSDictionary, and at the moment of deleting, the dictionary is rearranged (items are reordered). You could take that method:
- (void)removeViewAtIndex:(NSInteger)index
{
NSMutableDictionary *newItemViews = [NSMutableDictionary dictionaryWithCapacity:[itemViews count] - 1];
for (NSNumber *number in [self indexesForVisibleItems])
{
NSInteger i = [number integerValue];
if (i < index)
{
[newItemViews setObject:[itemViews objectForKey:number] forKey:number];
}
else if (i > index)
{
[newItemViews setObject:[itemViews objectForKey:number] forKey:[NSNumber numberWithInteger:i - 1]];
}
}
self.itemViews = newItemViews;
}
You could apply the same logic to your array, so that the carousel and your array keep in sync. Of course, if you store the indexes somewhere (slot1/slot2/slot2/slot4?), you should also update their values after removing an element.
On the other hand, I think that what you are asking here is how to do something that you believe would solve the problem you have, but you are not really explaining what the problem is. Indeed, if I understand you correctly, what you do is:
spinning the carousel;
when the carousel stops, if it is not by chance on the desired item, you "force" it to scroll to that item.
There is no reason why this should not work after deleting some elements (unless iCarousel has some bugs, then the solution would be catching the bug). The only part is knowing which index is the one you would like to move to.
As a suggestion, I would start off by simplifying your delegate carouselDidEndScrollingAnimation method. Indeed, your carouselDidEndScrollingAnimation has a parameter called carousel, well, I think this is the only carousel you should ever be referring to in that method. If you don't see it, this is the reasoning: each of your carousel will stop scrolling and the carouselDidEndScrollingAnimation will be called; so that method will be called twice. Each time that method is executed you will modify the state of both carousel1 and carousel2 (by calling scrollToItemAtIndex); therefore, on each carousel you will call scrollToItemAtIndex twice.
This does no sound very correct to me. So you should find a way to scroll only carousel1 when carouselDidEndScrollingAnimation is called for carousel1 and to scroll only carousel2 when carouselDidEndScrollingAnimation is called for carousel2.
More generally, another point I would like to raise is that the idea of:
letting a carousel stop;
scrolling it again so that it reaches the desired position;
does not seem the best implementation possible since the user would see the carousel stopping and then starting over again.
The way I would approach this is by modifying directly iCarousel implementation so that it supports this specific behavior you need.
Concretely, give a look at the step method in iCarousel.m. This is called at each frame to produce the carousel animation. Now, in this method there is decelerating branch:
else if (decelerating)
{
CGFloat time = fminf(scrollDuration, currentTime - startTime);
CGFloat acceleration = -startVelocity/scrollDuration;
CGFloat distance = startVelocity * time + 0.5f * acceleration * powf(time, 2.0f);
scrollOffset = startOffset + distance;
[self didScroll];
if (time == (CGFloat)scrollDuration)
{
decelerating = NO;
if ([delegate respondsToSelector:#selector(carouselDidEndDecelerating:)])
{
[delegate carouselDidEndDecelerating:self];
}
if (scrollToItemBoundary || (scrollOffset - [self clampedOffset:scrollOffset]) != 0.0f)
{
if (fabsf(scrollOffset/itemWidth - self.currentItemIndex) < 0.01f)
{
//call scroll to trigger events for legacy support reasons
//even though technically we don't need to scroll at all
[self scrollToItemAtIndex:self.currentItemIndex duration:0.01];
}
else
{
[self scrollToItemAtIndex:self.currentItemIndex animated:YES];
}
}
else
{
CGFloat difference = (CGFloat)self.currentItemIndex - scrollOffset/itemWidth;
if (difference > 0.5)
{
difference = difference - 1.0f;
}
else if (difference < -0.5)
{
difference = 1.0 + difference;
}
toggleTime = currentTime - MAX_TOGGLE_DURATION * fabsf(difference);
toggle = fmaxf(-1.0f, fminf(1.0f, -difference));
}
}
}
and you see that when the carousel stops decelerating, it is scrolled again. This is exactly the same as you are doing, so you might find a way to modify this code and have the carousel scrolls exactly to the index you need. In this way you would get a far smoother spinning of the carousel.
Hope this helps and apologies for the lengthy reply.
Its a little difficult to know what the issue is here. Are you using a single NSMutableArray for the images and using the NSUserDefaults value to get the object at the index in the array?
Im not 100% sure on what is happening. What does the user do(and in what view) and what is triggered after that(which view is presented).
Are you trying to stop the "spinning" images on the image that is the same as the one picked from the previous view?
According to your images above, the images are off by a single index. Is this the case every time? Maybe there is an issue with your fetching from the array.
If you give me some more info I can help.
I looked through the code you pasted again and I think this might be your issue
if (twoSlot1 > [(UIImageView*)[self.carousel2 currentItemView] tag]){
[self.carousel1 scrollToItemAtIndex:(-twoSlot1)-2 duration: 3.5f];
} else {
[self.carousel1 scrollToItemAtIndex:-twoSlot1 duration: 3.5f];
}
On all other code blocks like that you have this where you call each carousel. In the above code you call carousel 1 twice.
if (slot2 > [(UIImageView*)[self.carousel1 currentItemView] tag]){
[self.carousel1 scrollToItemAtIndex:(-slot2)-2 duration: 3.0f];
} else {
[self.carousel1 scrollToItemAtIndex:-slot2 duration: 3.0f];
}
if (twoSlot2 > [(UIImageView*)[self.carousel2 currentItemView] tag]){
[self.carousel2 scrollToItemAtIndex:(-twoSlot2)-2 duration: 3.5f];
} else {
[self.carousel2 scrollToItemAtIndex:-twoSlot2 duration: 3.5f];
}
You call self.carousel1 when you should be calling number 2.
Is this correct?
Referring to your question. You want an array that does not change its members' indexes when a member is deleted from the array.
I guess you could use an NSMutableDictionary. It is an associative array so to say, where the indexes are of your choice and they remain unchanged when you delete a member from in between.
You may still use 0..n as your Index. You can still use some methods that you are familiar with from NSArray, such as count. You can use an enumerator to go through all members of the dictionary. On the other hand you can still use your for-loops as you are used to use them with arrays. Just be prepared that a) objectForKey:i may return nil if the key/index does not exist (e.g. was deleted) and that count retuns the number of the objects but not the highest index+1 as it does with arrays.
Not sure if I understand completely, but when one of the elements in your mutable array is deleted, rather than just deleting it, maybe insert it with another "dummy" place holder object? That way your indexes won't change at all when a delete occurs
I'm having a hard time understanding your overall problem, but from what I can gather the crux of your question is this:
Is there a way to make my array or my iCarousel.view not adjust their indexes when Im deleting.
I don't know whether it will solve your bigger issue, but using an NSMutableDictionary to simulate an array should allow you to do this. You can simply use the indices as the keys to the dictionary, and then when you remove the item associated with an index, no other indices will be adjusted as a result. For example:
NSMutableDictionary *arrayDict = [[NSMutableDictionary alloc] init];
[arrayDict setValue:foo forKey:[NSNumber numberWithInt:[arrayDict count]]];
[arrayDict setValue:bar forKey:[NSNumber numberWithInt:[arrayDict count]]];
[arrayDict setValue:fooBar forKey:[NSNumber numberWithInt:[arrayDict count]]];
And then you can access the object an at index with [arrayDict objectForKey:[NSNumber numberWithInt:index]].
Note that using an NSNumber for the key parameter of setValue:forKey: will generate a warning, but you can safely ignore this (or use the string representation if it bothers you).

json vs xml annotations for mapkit

I have been working on a maps application (iphone) originally I had my annotations set up to pull XML from google using their Places API. I'm having 3 issues.
For my annotation info, I was going
off of an example from Zen
(http://www.zen-sign.com/finding-business-listings-and-displaying-with-mapkit-part-1/
) and he has it set up to do it by
keyword, which wasn't really
necessary for me ( but I used it
anyway just to get a feel for
getting the annotations) in the
parser header he has:
-(void) getBusinessListingsByKeyword:(NSString*)keyword atLat:(float)lat atLng:(float)lng;
and in the the viewdidload of his
view controller
[locationsMap findLocationsByKeyword:#"Apple" ];
I'm not sure how to move from the
keyword parse version used in zen to
something that just does it
automatically (in the parser object- without the viewdidload in a different view controller if possible).
Any advice on what to read/watch or
sample code much appreciated
For places information
Google isn't the only kid on the
block and XML I hear comes in second
to JSON. So I wanted to know what
the best practice was for map
annotations made from business
information: JSON or XML?
The other issue I was having was
only getting 10 annotations (I want
to get 50 or more). So on top of
your advice on using XML or JSON.
How to I increase the amount of
annotations I'm getting.
Sorry for making this 3 parts but again any tutorials (text of video) would be very helpful. (So far I've watched hegarty from Stanford, Larson from MATC)
First
Don't know what you mean by automaticaly. But if you want to launch the map on users current location here you have two methods you can use:
-(IBAction)goToCurrentLocation{
CLLocation *location = [[CLLocation alloc]
initWithLatitude:myMap.userLocation.coordinate.latitude
longitude:myMap.userLocation.coordinate.longitude];
[self setCurrentLocation:location];
}
- (void)setCurrentLocation:(CLLocation *)location {
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center = location.coordinate;
region.span.longitudeDelta = 0.15f;
region.span.latitudeDelta = 0.15f;
[self.myMap setRegion:region animated:YES];
}
Second. I don't know what are the best practicies but I used json with this jeson parser for my app ijustmadelove
Three. There is no problem in getting more then 10 annotations on the map. You have to have an error or a limitation in your code.

How to simulate a user driving a route in a MKMapView?

I need to simulate how my application will look when a user is driving around for a demo. I have a MKMapView, how can I simulate the look of a user driving around which will use the map.userLocation functionality, which obviously will not be available in the demo.
Thanks!
No way to simulate in iPhone simulator. You'll need to load it onto your device and move around.
Well I got something going, I just did essentially this
- (void)moveIcon:(MKAnnotationView*)locationView toLocation:(CLLocation*)newLoc
{
LocationAnnotation* annotation = [[[LocationAnnotation alloc] initWithCoordinate:newLoc.coordinate] autorelease];
[locationView setAnnotation:annotation];
[map setCenterCoordinate:newLoc.coordinate animated:YES];
}
Then I call this guy in a loop between all of my vertices with a slight delay. Works quite qell.
I'm not an iPhone dev expert, but how does the map view receive the coordinates? If it's through a function that calls the CoreLocation API, could you possibly just write a function that randomly generates longitude and latitude values at a certain time interval and have your map view pull the coordinates from there instead? Just a thought.
You could also check out iSimulate which claims to be able to simulate several features only available on the iPhone in the iPhone simulator include CoreLocation. I have not tried this myself so your mileage may vary.
In order to simulate driving you'll need to establish 2 basic functionalities:
Reading CLLocations from an archive (which you'd log during the drive test with a device). Ideally you'll do this based on the timestamps on the locations, i.e. reproducing the exact same location updates which were received during the drive test.
Updating your MKAnnotationView's position on the map based on the locations read from log.
For part 1, take a look at CLLocationDispatch, a handy class which provides archiving/unarchiving of CLLocations and dispatches them to one or more listeners (using CLLocationManagerDelegate protocol).
For part 2, take a look at Moving-MKAnnotationView.
I found a better way would be to subclass MKUserLocation:
class SimulatedUserLocation: MKUserLocation {
private var simulatedCoordinate = CLLocationCoordinate2D(latitude: 39, longitude: -76)
override dynamic var coordinate: CLLocationCoordinate2D {
get {
return simulatedCoordinate
}
set {
simulatedCoordinate = newValue
}
}
}
Then add it as an annotation mapView.addAnnotation(SimulatedUserLocation()). (You might also want to hide the real location first mapView.showsUserLocation = false)
iOS would render the annotation exactly like the real user location.
dynamic is used on the property so that changing coordinate triggers KVO and moves it on the map.
The answer is NO. Then, how about adding an abstraction layer between your code and MKMapKit? You can do xUnit tests for your objective.

Heeelp! Debugger says "out of scope"!

I just cannot imagine what the hell the problem could be.
I made a pretty app, and decided to use only CALayers to "render".
When I saw that the changes in the position property gets animated, decided to implement a custom getter-setter "abstract" property called tanCenter to set the position without animating.
-(void) setTanCenter: (CGPoint) sentCenter
{
//Remove any transactions.
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
//Set position.
self.position = sentCenter;
[CATransaction commit];
//Set value.
tanCenter = sentCenter;
}
-(CGPoint) tanCenter { return tanCenter; }
Since I merged this into the project, it crashes without any "understandable" (for me) error message. I can see only those "out of scope"-s. I cant even get this tanCenter property NSLog-ged without crash.
Help me, Obi-Wan Kenobi; you're my only hope.
If you run in the debugger (Command-Y) and make sure you have global breakpoints enabled, the debugger should stop at the place where the crash occurred giving you an idea of what is nil or over-released.
hey I had the same problem till now. Finally I have found my bug after investigating 2 weeks of bug tracking (it really sucks)
maybe my problem helps you:
I started with a TableView that opens on click another view. So I created in:
-(void)tableView:didSelectRowAtIndexPath:
first the controller for the other view and set the value for a global variable:
SomeView *dtview = [[SomeView alloc] initWithNibName:#"SomeView" bundle:nil];
dtview.lblTitle = cl.textLabel.text; // cl is the cell
[self presentModalViewController:dtview animated:NO];
[dtview release];
So opened the other view and done much functions with much memory usage :)
When I after that close the other view and go back to the table and scroll some times the App terminates with the message "out of scope"
I searched really, really long to find out what was the effect. It seems that when the other view is released also the text of the first table is released.
After putting a copy to the call it worked for me:
dtview.lblTitle = [cl.textLabel.text copy];
For int and bool the first solutions works fine, because these aren't objects but for NSObject's you should copy the values to another view.