I need to place annotations according to their polar coordinate (with radius in pixels) originated at the user position. I use the following code snippet to do that.
CGPoint origin = [mapView convertCoordinate:userCoordinate toPointToView:mapView];
double dx = radius * cos(theta);
double dy = radius * sin(theta);
CGPoint point = CGPointMake(origin.x + dx, origin.y + dy);
NSLog(#"mapViewSize=(%f, %f), point=(%f,%f)", CGRectGetWidth(mapView.bounds), CGRectGetHeight(mapView.bounds), point.x, point.y);
return [mapView convertPoint:point toCoordinateFromView:mapView];
Here is an example of the NSLog output:
mapViewSize=(228.000000, 228.000000), point=(40.817908,123.346518)
In my opinion, this would mean that the point is indeed located on the map view. However, when adding them back on the map after convertPoint:point toCoordinateFromView:, it is placed very far (distance ~ 148847.221119 meters while the span of my region is just hundreds of meter) and therefore are out of the range of my map. What am I doing wrong?
Related
My map view contains one circle which covers many pinpoints with one center point.Whenever the user clicks a button the map should be zoomed in such a level the circle is visible.The map should not be zoomed more or less it should show exact circle.When user logs out then circle will have new radius in next login.How to calculate the appropriate zoom level.
My code:
-(void)showCircle{
//calculate new radius
long radius=[self calculateRadius];
MKCircle *circle= [[MKCircle alloc]init];
circle = [MKCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake([groupLat floatValue], [groupLon floatValue]) radius:radius];
[myMapView addOverlay:circle];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake([groupLat floatValue], [groupLon floatValue]), 800, 800);
MKCoordinateSpan span;
//calculate zoom level
double radius=[circle radius]);
double rad = radius + radius / 2;
double scale = rad / 500;
zoomLevel=(16 - log(scale) / log(2));
region.span.latitudeDelta =zoomLevel;
region.span.longitudeDelta =zoomLevel;
[myMapView setRegion:region animated:YES];
}
Thank you in advance!
You can try : mapRectThatFits(:) or mapRectThatFits(:edgePadding:)
Something like this, maybe, to zoom the map according to your circle :
myMapView.visibleMapRect = [myMapView mapRectThatFits:circle.boundingMapRect];
Hope this helps you :)
So I currently get the location of a touch by using
CGPoint location = [touch locationInView:self.view];
Now what I want to do is check the location on the next touch to see if the locations are close, say 25 points on x or y axis.
There are a few posts that show how to compare if two touches are equivalent but is there to calculate the distance between multiple points? Any info would be awesome.
To estimate the distance between two CGPoints, you can make use of simple Pythagorean formula:
CGFloat dX = (p2.x - p1.x);
CGFloat dY = (p2.y - p1.y);
CGFloat distance = sqrt((dX * dX) + (dY * dY));
I'm trying to zoom to fit annotations on a map while locking the center and providing some insets.
- (void)fitAnnotations:(NSArray *)annotations edgeInsets:(UIEdgeInsets)insets
{
CLLocationCoordinate2D originalCenter = self.centerCoordinate;
MKMapRect mapRect = MKMapRectNull;
for (id<MKAnnotation> annotation in annotations) {
MKMapPoint p = MKMapPointForCoordinate([annotation coordinate]);
mapRect = MKMapRectUnion(mapRect, MKMapRectMake(p.x, p.y, 0, 0));
}
mapRect = [self mapRectThatFits:mapRect edgePadding:insets];
MKCoordinateRegion mapRegion = MKCoordinateRegionForMapRect(mapRect);
// we now try to go back to the original center, while increasing the span by neccessary amount
MKCoordinateSpan centeringDelta = MKCoordinateSpanMake(fabs(mapRegion.center.latitude - originalCenter.latitude), fabs(mapRegion.center.longitude - originalCenter.longitude));
mapRegion.center = originalCenter;
mapRegion.span.latitudeDelta += centeringDelta.latitudeDelta * 2.0;
mapRegion.span.longitudeDelta += centeringDelta.longitudeDelta * 2.0;
mapRegion = [self regionThatFits:mapRegion];
[self setRegion:mapRegion animated:YES];
}
The first part of the code here works as expected: it zooms to fit while respecting the insets. However, it shifts the center.
I try to re-adjust the center after that, but it fails. I'm not sure if my math on the re-centering is correct.
The first part of your code that calculates the bounding map rect that fits the annotations is OK.
Only the adjustment of that "minimal" map rect so that the "locked" center is actually in the center needs to be corrected.
The main problem, I believe, is that the code in the question is adjusting the span to both re-center the region and account for the insets after calling mapRectThatFits: (which will itself already give a slightly modified version of the rect you actually requested).
Instead, your code should only calculate the actual, minimal rect it wants and then finally call setVisibleMapRect:edgePadding:animated: and let the map view figure out both the "rect that fits" and the insets.
Please try the following:
- (void)fitAnnotations:(NSArray *)annotations edgeInsets:(UIEdgeInsets)insets
{
MKMapPoint centerMapPoint = MKMapPointForCoordinate(originalCenter);
//--- First create minimal bounding map rect to tightly fit annotations...
MKMapRect minimalMapRect = MKMapRectNull;
for (id<MKAnnotation> annotation in annotations) {
MKMapPoint annMapPoint = MKMapPointForCoordinate(annotation.coordinate);
minimalMapRect = MKMapRectUnion(minimalMapRect, MKMapRectMake(annMapPoint.x, annMapPoint.y, 0, 0));
}
//--- Now create adjusted map rect so center coordinate is in the center...
//distance of desired center from minimal left edge...
double centerXOffset = centerMapPoint.x - minimalMapRect.origin.x;
//raw amount width needs to be adjusted to get center in center...
//negative/positive indicates whether center is in left/right half
double widthOffset = 2.0 * centerXOffset - minimalMapRect.size.width;
//add absolute value of raw width offset to minimal width...
double adjustedWidth = minimalMapRect.size.width + fabs(widthOffset);
//distance of desired center from minimal top edge...
double centerYOffset = centerMapPoint.y - minimalMapRect.origin.y;
//raw amount height needs to be adjusted to get center in center...
//negative/positive indicates whether center is in top/bottom half
double heightOffset = 2.0 * centerYOffset - minimalMapRect.size.height;
//add absolute value of raw height offset to minimal height...
double adjustedHeight = minimalMapRect.size.height + fabs(heightOffset);
//adjust origin if necessary (if center is in top/left half)...
MKMapPoint adjustedOrigin = minimalMapRect.origin;
if ((centerXOffset / minimalMapRect.size.width) < 0.5)
{
adjustedOrigin.x = adjustedOrigin.x + widthOffset;
}
if ((centerYOffset / minimalMapRect.size.height) < 0.5)
{
adjustedOrigin.y = adjustedOrigin.y + heightOffset;
}
//create adjusted MKMapRect...
MKMapRect adjustedMapRect = MKMapRectMake(adjustedOrigin.x, adjustedOrigin.y, adjustedWidth, adjustedHeight);
//--- Apply the adjusted map rect with insets to map view...
[mapView setVisibleMapRect:adjustedMapRect edgePadding:insets animated:YES];
}
Try something like this, where you use your calculated mapRect to create the new region with your originalCenter via the MKCoordinateRegionMake method
MKCoordinateRegion mapRegion = MKCoordinateRegionForMapRect(mapRect);
mapRegion = MKCoordinateRegionMake(originalCenter, mapRegion.span);
mapView.region = mapRegion;
Try this.
MKMapPoint center = MKMapPointForCoordinate(self.mapView.centerCoordinate);
double maxX = 0;
double maxY = 0;
for (MKPointAnnotation *a in self.mapView.annotations)
{
MKMapPoint p = MKMapPointForCoordinate(a.coordinate);
double deltaX = fabs(center.x - p.x);
double deltaY = fabs(center.y - p.y);
maxX = MAX(maxX, deltaX);
maxY = MAX(maxY, deltaY);
}
MKMapRect rect = MKMapRectMake(center.x - maxX, center.y - maxY, maxX * 2, maxY * 2);
rect = [self.mapView mapRectThatFits:rect edgePadding:UIEdgeInsetsMake(20, 20, 20, 20)];
[self.mapView setVisibleMapRect:rect animated:1];
#moby I am thinking of a different approach. How about taking the maps centre location as you already did. Now calculate distance to each annotation from this centre coordinate till you find the longest annotation (say 'requiredDistance' ).
Get a new map rect with all your annotations plotted with same centre using below code:
MKCircle *circleLine = [MKCircle circleWithCenterCoordinate:self.centerCoordinate radius:requiredDistance];
[self.mapView setVisibleMapRect:circleLine.boundingMapRect];
Coming to your insets what ever insets you wanted to apply should be applied to your 'requiredDistace' variable in such a way that your 'requiredDistance' variable has a value always greater than or equal to the distance between your centre coordinate and your longest annotation to make sure all the annotations are always visible.
I'm trying to see if some extents (max x, max y, min x, min y coordinates) I have are in the current visible map view.
I take my extents and create a MKMapRect:
MKMapPoint upperLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake([boundary.extents.maxY floatValue], [boundary.extents.minY floatValue]));
MKMapPoint lowerLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake([boundary.extents.minY floatValue], [boundary.extents.minY floatValue]));
MKMapPoint upperRight = MKMapPointForCoordinate(CLLocationCoordinate2DMake([boundary.extents.maxY floatValue], [boundary.extents.maxY floatValue]));
MKMapRect mapRect = MKMapRectMake(upperLeft.x, upperLeft.y, fabs(upperLeft.x - upperRight.x), fabs(upperLeft.y - lowerLeft.y));
Now I want to check if my 'mapRect' is in the mapView.visibleMapRect:
if (MKMapRectContainsRect(mapView.visibleMapRect, mapRect)) {
// do some stuff
}
But my extents are never contained by the mapView.visibleMapRect when I know they should be.
If I replace the mapView.visibleMapRect with MKMapRectWorld, then it will contain my extents 'mapRect'.
Am I doing something wrong? is mapView.visibleMapRect not what I think it is (the viewable area on the screen)?
D'oh!
The issue was that I used minY instead of minX.
MKMapPoint upperLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake([boundary.extents.maxY floatValue], [boundary.extents.**minX** floatValue]));
MKMapPoint lowerLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake([boundary.extents.minY floatValue], [boundary.extents.**minX** floatValue]));
MKMapPoint upperRight = MKMapPointForCoordinate(CLLocationCoordinate2DMake([boundary.extents.maxY floatValue], [boundary.extents.**maxX** floatValue]));
mapView.visibleMapRect is exactly what you think it is, the map rect displayed by your map view. The problem is probably that the MKMapRectContainsRect function only tells you if one map rect is entirely contained (completely enclosed) in another. It's likely that you just want to use MKMapRectIntersectsRect which just tells you that part of your map rect is inside your mapView.visibleMapRect
I got MKMapView and a number of annotations on it. I use next code for displaying all annotations:
NSArray *coordinates = [self.mapView valueForKeyPath:#"annotations.coordinate"];
CLLocationCoordinate2D maxCoord = {-90.0f, -180.0f};
CLLocationCoordinate2D minCoord = {90.0f, 180.0f};
for(NSValue *value in coordinates) {
CLLocationCoordinate2D coord = {0.0f, 0.0f};
[value getValue:&coord];
if(coord.longitude > maxCoord.longitude) {
maxCoord.longitude = coord.longitude;
}
if(coord.latitude > maxCoord.latitude) {
maxCoord.latitude = coord.latitude;
}
if(coord.longitude < minCoord.longitude) {
minCoord.longitude = coord.longitude;
}
if(coord.latitude < minCoord.latitude) {
minCoord.latitude = coord.latitude;
}
}
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center.longitude = (minCoord.longitude + maxCoord.longitude) / 2.0;
region.center.latitude = (minCoord.latitude + maxCoord.latitude) / 2.0;
region.span.longitudeDelta = (maxCoord.longitude - minCoord.longitude) * 1.5;
region.span.latitudeDelta = (maxCoord.latitude - minCoord.latitude) * 1.5;
[self.mapView setRegion:region animated:YES];
This code centers all annotations - perfect. But what if I want to fit annotations, lets say, on right half part of map. Is there any way to do that?
Here how it's now:
Here what I want to achieve:
If your target area is always to the left or right (i.e. still allowing full height) then it is very easy, you just need to work out the maths for your region's boundaries.
Let's assume you want the pins to be in the right-hand quarter of the page. This would mean you need to tell the map to view a region 4x (targetspan/currentspan) bigger than the pin region and with the middle of that region being shifted left by 1.5x ((targetspan-currentspan)/2)the width of the region. (I'm going to sat latitude is from 0 to 1 but really you just use what ever you have calculated already)
Imagine the view being divided into four vertical strips, there would be 5 lines. The left edge of the screen is 0, the next line is 1, the middle is 2, the next is 3 and the last is 4.
You want your pins to be between lines 3 and 4, meaning the longitude span you calculated above is 1 unit wide and the center.longitude is at 3.5.
You have a region that is ((3.5,0.5), (1,1)) and you need to tell your map to zoom to the region ((2,0.5),(4,1)). So, get the longitudeDelta, multiply it by 1.5 and subtract that from the center.longitude, that is your new target center.longitude. Then multiply the longitudeDelta by 4 and that is your new longitudeDelta.
You can do something similar with latitude on a small scale, but on a large scale the calculations ned to take into account the map projection which means the pixels near the equator represent more land than those near the poles.