Making map pins spread out from a cluster - iphone

I've got two custom annotation classes for my map: one for a single post tied to a location, and one for a cluster of those posts. The cluster stores pointers to all of the posts it contains, as well as a central lat/long location (calculated using the locations of the posts it contains). I have the behaviour that when I click on a cluster annotation it removes the cluster and adds its posts to the map. What I want is to change the pin-drop annotation when expanding the clusters to an animation whereby the new pins move outwards from the centre of the cluster to their new locations. However, I also have some posts that are never clustered due to their distance from other points. Obviously they can't have this animation as there is no associated location for them to move outward from. Does anyone know how I might implement this?

Making the pins expand from the cluster center is actually pretty easy. When you make the new single-pin annotations, set their coordinates to the cluster center:
id <MKAnnotation> pin;
CLLocationCoordinate2D clusterCenter;
// ...
pin.coordinate = clusterCenter;
In viewForAnnotation:, don't animate the new pins:
MKPinAnnotationView *pinView;
// ...
pinView.animatesDrop = NO;
Then, after you've added the pins to the map view, you'll animate moving them to their real positions:
MKMapView *mapView;
id <MKAnnotation> pin;
// ...
// probably loop over annotations
[mapView addAnnotation:pin];
NSTimeInterval interval = 1.0; // or whatever
[UIView animateWithDuration:interval animations:^{
// probably loop over annotations here again
CLLocationCoordinate2D realCoord;
// ...
pin.coordinate = realCoord;
}];
As for the problem of non-clustered pins, that's harder to answer without knowing the implementation in detail, but I think there are lots of possibilities. You could just have a simple flag that skips the animation. Or you could just treat them exactly the same, and still "cluster" them even when they're solo, and still animate them ... not maximally efficient, but it would work and your code would be cleaner.

Related

Strange behaviour of MapKit pin Annotations

when i am placing 5 pin on mapView with same address but callout bubble is shown only for two pin when the we taps a selected annotation view. When i tap pin then callout display only for two pins.
How to resolve this, i want to show callout of all pins even they have same address.
This happens because the zoomlevel of your map is not proper as per your coordinates requirements. Although you have annoted five pins at same address there should be minor difference into coordinates, to get separated.
You should work on longitudeDelta & latitudeDelta to get over this.
For ex. You can set
<coordinate_object>.latitudeDelta = 0.04;
<coordinate_object>.longitudeDelta = 0.04;
The lesser the delta value there is higher a zoom level and vice-a-versa.
Enjoy Programming!
Before adding each annotation to the map you should check if there is already another annotation at the same place, or within a few meters. If so then combine the data for these annotations into a structure that can keep growing (NSMutableArray is my first guess) and then add that combined data as a new annotation*. Then when the pin is tapped it will ask for the call out details and tell you which annotation was tapped, you can check if the annotation has one datum or multiple data at set up your callout correctly.
You'll need to have a custom annotation class but you would probably have needed that anyway if you're storing useful data about each one.
*you'll also need to ensure the first annotation is not left on the map, so maybe you could do a scan through your data and combine into arrays before doing any annotation stuff. Each annotation would store an array of values, most of them would only have one, but where they are too close together the array would have many values and your callout function would have to display that.

Multiple CCTMXTiledMaps for iPhone Game

So I want to divide my game into chunks by using several different CCTMXTiledMaps.
I am able to load the maps into my main 'HelloWorldLayer'. I am also able to detect whether the player sprite collides with a tile with the property of 'collectable'.
My problem occurs when I add several CCTMXTiledMap nodes to the game, as it doesn't do the collectible tile detection on all of them, just the first one.
Here is my working code that does the check, but only for the first added CCTMXTledMap:
CGPoint point = [self getTileCoordForPosition:position :map];
CCTMXLayer *metaLayer = [map layerNamed:#"Meta"];
CCTMXLayer *foregroundLayer = [map layerNamed:#"Foreground"];
CCSprite *metaTile = [metaLayer tileAt:point];
CCSprite *foregroundTile = [foregroundLayer tileAt:point];
if (foregroundTile)
{
NSLog(#"HIT!");
// Remove the meta tile and the foreground tile
[metaLayer removeTileAt:point];
[foregroundLayer removeTileAt:point];
}
How can I make this code do the check for every CCTMXTiledMap node that has been added?
The problem was that I was calculating the tile map positions wrong, in a tile map co-ordinates to map position function.
I was multiplying by the CC_SCALE_RATIO() function, or something like that (going off the top of my head), and it was mis-calculating the pixel positioning.
Just thought I'd write in an answer since I found the solution. Hope it helps somebody!

Trouble converting MapKit user coordinates to screen coordinates

OK this is actually three distinct questions in one thread:
1) I'm using:
- (void)viewDidLoad
{
[super viewDidLoad];
[mapView setFrame :CGRectMake(-100,-100,520,520)];
[mapView setAutoresizesSubviews:true];
mapView.showsUserLocation = YES;
locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
[locManager startUpdatingLocation];
[locManager startUpdatingHeading];
[bt_toolbar setEnabled:YES];
}
- (IBAction) CreatePicture
{
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(mapView.userLocation.coordinate.latitude, mapView.userLocation.coordinate.longitude);
CGPoint annPoint = [self.mapView convertCoordinate:coord toPointToView:self.mapView];
mapPic = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"pic.png"]];
mapPic.frame = CGRectMake(annPoint.x, annPoint.y, 32, 32);
[self.view addSubview:macPic];
}
to place an image using GCRectMake over the user's coordinates, but sadly the image is not being placed in the right spot:
And if i move the map, the picture will always be placed with the exact same offset regarding the user's location. What am i missing? Shouldn't it have been placed directly above the user? I'm guessing the offset (-100, -100) I gave the mapView in the first place is what's causing this (i've got a toolbar below, hence the offset).
2) My iOS 5 simulator is acting crazy, placing my location in Glendale, Phoenix (shouldn't it have been in Cupertino??) for some reason and acting as i were on the move due east; everything works fine in my iPhone 4 though (apart from the incorrect placement of the picture). Any ideas?
3) I've been thinking about using Annotations instead, but i've heard they're static (and i really need them dynamic). Is this true?
Thanks!
When adding a subview, its frame origin is relative to that of its superview. In this case you derived an origin from the map view and then added the image to the view controller's view. You would need to convert your image's origin to the self.view's coordinate system like this:
CGPoint imageOrigin = [self.view convertPoint:annPoint fromView:self.mapView];
mapPic.frame.origin = imageOrigin;
[self.view addSubview:macPic];
NB: this is untested code, but the principal is that you need to convert between the mapView's coordinate space (which is what you got from [self.mapView convertCoordinate:coord toPointToView:self.mapView]) to self.view's coordinate space. This example is a little contrived to be illustrative since an even simpler approach would be to use self.view's coordinate space in the first place:
CGPoint annPoint = [self.mapView convertCoordinate:coord toPointToView:self.view];
I think you would be better off using a custom MKPinAnnotationView using your own image.
as per this answer.
Add image behind MKPinAnnotationView
MKMapView provides the following methods for translating between view space and geographic coordinates:
– convertCoordinate:toPointToView:
– convertPoint:toCoordinateFromView:
– convertRegion:toRectToView:
– convertRect:toRegionFromView:
These are good for tasks like translating touches to map coordinates. For the task you describe, I think a map overlay would be more appropriate than trying to draw on the map using a subview. An overlay is a kind of annotation that allows you to draw whatever content you like, but lets the map take care of determining when the overlay is visible and needs to be drawn at all.
3) I've been thinking about using Annotations instead, but i've heard
they're static (and i really need them dynamic). Is this true?
What do you mean by "static" vs. "dynamic" here? Do you mean that you want to change the content in the overlay view? Or do you want to change the location of the overlay itself? I think it should be possible to update the overlay view as often as you need to. I'm not sure if you can move the overlay by simply adjusting it's boundingMapRect property, but I'd expect that to work. If it doesn't, you can always just remove the overlay and add it again.
Trying to create your own map overlay system instead of using the tools provided by MapKit should be the last thing on your list of options. Life is always easier when you can work with a framework instead of against it, and your code is much less likely to break in the future.

mapkit annotation error correction on iphone

i have 4 annotation with same lat/long as they are pointing some location in 1 building , since they share common lat/long so i can show only one of them on map?? so is there any way to use some error correction so that i can show them Lil side by side??
here is my annotation code
MKCoordinateRegion SecondRegiona;
SecondRegiona.center.latitude = 111.31888;
SecondRegiona.center.longitude = 203.861;
MyAnnotation *aSecondAnnotationa = [[[MyAnnotation alloc] init]autorelease];
aSecondAnnotationa.title = [listItems objectAtIndex:15];//#"3rd Annotation";
aSecondAnnotationa.subtitle = [listItems objectAtIndex:16];
aSecondAnnotationa.coordinate = SecondRegiona.center;
Why would you expect the platform to position something someplace different than where you told it to place it? That certainly sounds undesirable and not something that I would call "Error Correction"
You need to detect that state and do something reasonable like coalesce them into a single custom annotation or adjust the lat lngs to position them nearby according to your needs.
I would detect whether points are really close together (maybe use the distance formula to see how close points are?) and use - (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view to get CLLocationCoordinate2D of a point close to the original pin (say, 3 pixels to the right, 3 pixels down). You would use this CLLocationCoordinate2D to display the new, "adjacent" point.
As for what "coalesce" means, Nick means to merge points together -- take points that are really close to each other and display only one to represent the close points. I guess this isn't what you're looking for though.
Hope this helps!

MKMapView setRegion "snaps" to predefined zoom levels?

Can anyone confirm that setRegion "snaps" to predefined zoom levels and whether or not this behavior is as designed (although undocumented) or a known bug? Specifically, it appears that setRegion snaps to the same zoom levels that correspond to the zoom levels used when the user double-taps the map.
I'm trying to restore a previously saved region but this behavior makes it impossible if the saved region was set via a pinch zoom and not a double-tap zoom.
A big clue to me that things are broken on the mapkit side is what occurs if I call regionThatFits on the map's current region. It should return the same region (since it obviously fits the map's frame) but it returns the region that corresponds to the next higher predefined zoom level instead.
setVisibleMapRect behaves similarly.
Any further insight or information would be appreciated.
I found these related posts but neither included a solution or definitive confirmation that this is in fact a mapkit bug:
MKMapView setRegion: odd behavior?
MKMapView show incorrectly saved region
EDIT:
Here is an example that demonstrates the problem. All values are valid for my map view's aspect ratio:
MKCoordinateRegion initialRegion;
initialRegion.center.latitude = 47.700200f;
initialRegion.center.longitude = -122.367109f;
initialRegion.span.latitudeDelta = 0.065189f;
initialRegion.span.longitudeDelta = 0.067318f;
[map setRegion:initialRegion animated:NO];
NSLog(#"DEBUG initialRegion: %f %f %f %f", initialRegion.center.latitude, initialRegion.center.longitude, initialRegion.span.latitudeDelta, initialRegion.span.longitudeDelta);
NSLog(#"DEBUG map.region: %f %f %f %f", map.region.center.latitude, map.region.center.longitude, map.region.span.latitudeDelta, map.region.span.longitudeDelta);
OUTPUT:
DEBUG initialRegion: 47.700199 -122.367111 0.065189 0.067318
DEBUG map.region: 47.700289 -122.367096 0.106287 0.109863
Note the discrepancy in the latitude/longitude delta values. The map's values are almost double what I requested. The larger values correspond to one of the zoom levels used when the user double-taps the map.
Yes, it snaps to discrete levels. I've done quite a bit of experimentation, and it seems to like multiples of 2.68220906e-6 degrees of longitude per pixel.
So if your map fills the whole width of the screen, the first level spans .0008583 degrees, then the next level up you can get is twice that, .001717, and then the next one is twice that, .003433, and so on. I'm not sure why they chose to normalize by longitude, it means that fixes zoom levels vary depending on what part of the world you are looking at.
I've also spent a lot of time trying to understand the significance of that number .68220906e-6 degrees. It comes out to about 30cm at the equator, which kind of makes sense since the high resolution photos used by Google Maps have a 30cm resolution, but I would have expected them to use latitude instead of longitude to establish the zoom levels. That way, at maximum zoom, you always the native resolution of the satellite images, but who knows, they probably have some smart-people reason for making it work like that.
In my application I need to display a certain range of latitude. I'm gonna work on some code to try to zoom the map as close as possible to that. If anyone is interested, contact me.
I found a solution.
If the received snapped zoom level, is, lets say a factor of 1.2 bigger than the desired one:
use this algorithm to correct:
Asumption: you want to set the map view to exactly show "longitudinalMeters" from left to right
1) Calculate the correction scale:
Calculate the relation between longitudinal span you received, to that one you have got.
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center, 0, longitudinalMeters);
MKCoordinateRegion regionFits = [mapView regionThatFits: region];
double correctionFactor = regionFits.span.longitudeDelta / region.span.longitudeDelta;
2) Create the transformation and apply it to the map
CGAffineTransform mapTransform = CGAffineTransformMakeScale(correctionScale, correctionScale);
CGAffineTransform pinTransform = CGAffineTransformInvert(mapTransform);
[mapView setTransform:mapTransform];
3) Apply the inverse transformation to the Map pins, to keep them at original size
[mapView setTransform:mapTransform];
for (id<MKAnnotation> annotation in self.mapView.annotations)
{
[[self.mapView viewForAnnotation:annotation] setTransform:pinTransform];
}
The weird behavior seems to be due to the fact that while one requests a particular region or view size, the actual API call to google is invoked with a center point and a zoom level. E.G.:
map.setCenter(new google.maps.LatLng(234.3453, 454.2345), 42);
Now it would be possible for Apple to request the appropriate zoom level and then adjust the sizing of the view to accommodate the actual region request, but it seems they fail to do so. I am drawing bus routes on a map, and one of my routes barely triggers a larger zoom level and thus scales too small (under-zooms) and looks ugly and smashed.
#pseudopeach, Please update me on the progress of your attempts to work around this issue. If one could detect the boundaries of a zoom level, the region request could then be deliberately underscaled to avoid the under-zoom. Since you are onto this I would be interested in seeing your code before I have to make an attempt at it myself.
There is an interesting category that the author of the blog Backspace Prolog has written to enable the direct manipulation of the Google Maps API by emulating their setCenter(centerPoint,ZoomLevel) call signature. You can find it here. I haven't spent the time yet, but the math can probably be reverse engineered to yield a means of calculating the zoom level for a given Region or MapRect. Depending on how far it is within the zoom level's range - i.e. how far it is over the threshold that triggers the lower zoom level - it could decide whether to go to the lower level or keep to higher one by under-requesting.
This is clearly a behavioral bug that needs to be fixed so that MKMapView can be used in a more refined manner.
This is an old question, but I recently investigated Google maps in detail, and can share some insight. I don't know whether this is also valid for the current Apple maps.
The reason that the resolution snaps to predefined zoomlevels is because the original maps fetched from Google's servers are drawn with those zoomlevels. The size of the features on those maps are drawn with a certain resolution in mind. For example, the width (in pixels) of a road on those maps is always the same. On higher resolution maps, more secundary roads are drawn, but their width is always the same. The resolution snaps to predefined levels to make sure those features are always depicted with the same size. That is, it is not a bug but a feature.
Those predefined resolutions vary with latitude because of the Mercator projection of the maps. Mercator projection is easy to work with because latitude lines are depicted straight and horizontal and longitude lines are straight and vertical. But with Mercator projection the top of the map has a slightly higher resolution than the bottom (on the Northern hemisphere). That has consequences for fitting maps together at the northern and sourthern edges.
That is, when you start on the equator and drive north, then the resolution of the Mercator maps you drive over will gradually increase. The longitude lines remain vertical, and therefore the longitude spans remains the same. But the resolution increases, and therefore the latitude span decreases. Still, on all those maps the roads have the same width in pixels, and texts are depicted in the same font size, etc.
Google uses a Mercator projection where the equator circumference is 256 pixels at zoomlevel 0. Each next zoomlevel doubles that amount. That is, at zoomlevel 1, the equator is 512 pixels long, at zoomlevel 2, the equator is 1024 pixels long, etc. The model for the earth they use is a FAI globe with a radius of exactly 6371 km, or circumference of 40030 km.
Therefore, resolution for zoomLevel 0 at the equator is 156.37 km/pixel, at zoomlevel 1 it is 78.19 km/pixel, etc. Those resolutions then vary with the cosinus of the latitude anywhere else on the earth.
MKCoordinateRegion region;
region.center.latitude = latitude;
region.center.longitude = longitude;
region.span.latitudeDelta = 5.0;
region.span.longitudeDelta = 5.0;
[mapView setRegion:region animated:YES];
I restore the region with no problem and with no variance as you describe. It is really impossible to tell what is specifically wrong in your case without some code to look at but here's what works for me:
Save both the center and span values somewhere. When you are restoring them specifically set both the center and span.
Restoring should look like this:
MKCoordinateRegion initialRegion;
initialRegion.center.latitude = Value you've stored
initialRegion.center.longitude = Value you've stored
initialRegion.span.latitudeDelta = Value you've stored
initialRegion.span.longitudeDelta = Value you've stored
[self.mapView setRegion:initialRegion animated:NO];
Also remember that this method is available in 4.0: `mapRectThatFits:edgePadding: MapRectThatFits helpfully adds a reasonable border to ensure that say a map annotation on the edge is not obscured and the the rect that you're attempting to display is fully visible. If you want to control the border use the call that gives you access to set edgePadding as well.
If you set up the MapView in InterfaceBuilder, make sure you don't do this:
_mapView = [[MKMapView alloc] init];
As soon as I removed this init line, my map view suddenly began responding properly to all the updates I sent it. I suspect that what happens is that if you do the alloc init, it's actually creating another view that's not being shown anywhere. The one you see on the screen is the one initialized by your nib. But if you alloc init a new one, then that's something somewhere else and it's not going to do anything.