ios: predict if two annotation views would overlap each other - ios

I'm developing some MKMapView logic. There are many annotations on my map view. I need to compose few locations in one if those annotation views would overlap each other, and display Annotation view with changes. So I should predict that case, and I need to determinate that only from annotations' location properties and current MKMapView's zoom.
- (BOOL)shouldAlert:(CoordinatesAlert *)alert1 beInGroupWithAlert:(CoordinatesAlert *)alert2 {
// somehow calculate current map view zoom
CGFloat zoom = ...
if ([alert1.location distanceFromLocation:alert2.location] / zoom < kSomeCoeficient) {
return YES;
}
return NO;
}
Also I worried about this solution because I need to reload annotations and those views on every zoom change.
How can I do this in better way? Is there any default solution for annotation views clustering?

All of exist libraries do not suit for my needs, they all works with areas but I need to group annotations only if they would be overlapped. Also I do not need so many annotations, so i do not need performance optimization.
So, I've found some solution, it is better then nothing.
- (BOOL)isViewForLocation:(CLLocation *)location1 overlappedByViewForLocation:(CLLocation *)location2 {
const CLLocationDegrees deltaLatitude = ABS(location1.coordinate.latitude - location1.coordinate.latitude);
const CLLocationDegrees deltaLongitude = ABS(location2.coordinate.longitude - location2.coordinate.longitude);
const CLLocationDegrees mapAreaLatitude = self.mapView.region.span.latitudeDelta;
const CLLocationDegrees mapAreaLongitude = self.mapView.region.span.longitudeDelta;
return (deltaLatitude / mapAreaLatitude) < (kAnnotationTapArea.size.height / self.mapView.frame.size.height)
&& (deltaLongitude / mapAreaLongitude) < (kAnnotationTapArea.size.width / self.mapView.frame.size.width);
}

Related

In Google Maps iOS how to turn Marker as per turns on road like in Uber's Trip screen

I am working on an iOS application that works like Uber. When the device moves I am moving the car marker as well. But am not able to turn it as per turns on road like Uber does. How can I make it possible?
Also how can I keep this car marker only on road?
You can calculate bearing then rotate marker based on the calculated bearing.
Use this function to calculate bearing:
+(float)getBearing:(CLLocationCoordinate2D)locations1 andSecond:(CLLocationCoordinate2D)locattion2
{
float fLat = degreesToRadians(locations1.latitude);
float fLng = degreesToRadians(locations1.longitude);
float tLat = degreesToRadians(locattion2.latitude);
float tLng = degreesToRadians(locattion2.longitude);
float degree = radiansToDegrees(atan2(sin(tLng-fLng)*cos(tLat), cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(tLng-fLng)));
if (degree >= 0) {
return degree;
} else {
return 360+degree;
}
}
Then rotate the marker itself using rotation property
marker.rotation = CALCULATED_BEARING - 180;
Take a look at rotating the marker alongside the update or the moving of locations with regards to the bearing. Not really sure how you can achieve with keeping the marker on the road since you are relying on the gps to provide exact location where you will place your marker.
Take a look at this SO as a sample.

Displaying MKAnnotationViews that are within the bounds of the visible mapview

I'm querying my server for a list of locations that are closest to the user based on his observable portion of the mapview.
I'm not entirely sure on how to do this, but should I send the center lat and long of the mapview's visible region and then search for results based on nearest radius?
I know that MKMapView has two properties called region and visibleMapRect. Should I use one of those, and if so, which one is more appropriate based on my question?
EDIT
I'm looking to implement functionality that's identical to the apple maps and Yelp app when you search for nearby locations and it shows you what's relevant based off the visible portion of the map view.
EDIT 2
I have seen a lot of people break up the visible portion of their mapview into quadrants, most commonly NW,NE,SW and SE. However, I'm still not entirely sure why theyr'e doing this. I would like to know the best way to query the back end, which contains a lat and long for each location, to find the locations which exist on the mapview.
I have looked everywhere on stackoverflow and I found a similar question here. However, it doesn't answer my question and there's no mention of visibleMapRect because it's over 5 years old.
Any help is tremendously appreciated.
So you would need to provide your server call with these 2 coord bottomleft and topright 4 points all together.
if lat of location > bottomleft.lat and if lat of location < topright.lat
if long of location > bottomleft.long and if long of location < topright.long
iOS Code would look like this the make server call with these four variables as parameters
fun viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
}
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// Using region
var span: MKCoordinateSpan = mapView.region.span
var center: CLLocationCoordinate2D = mapView.region.center
// This is the farthest Lat point to the left
var farthestLeft = center.latitude - span.latitudeDelta * 0.5
// This is the farthest Lat point to the Right
var farthestRight = center.latitude + span.latitudeDelta * 0.5
// This is the farthest Long point in the Upward direction
var farthestTop = center.longitude - span.longitudeDelta * 0.5
// This is the farthest Long point in the Downward direction
var farthestBottom = center.longitude + span.longitudeDelta * 0.5
var SWCoord = MKCoordinateForMapPoint(farthestBottom, farthestLeft)
var NECoord = MKCoordinateForMapPoint(farthestTop, farthestRight)
// Using visibleMapRect
var mapRect = mapView.visibleMapRect
// This is the top right Coordinate
var NECoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMaxX(mapRect), y: mapRect.origin.y)
// This is the bottom left Coordinate
var SWCoord = getCoordinateFromMapRectanglePoint(mapRect.origin.x, y: MKMapRectGetMaxY(mapRect))
// Not needed but could be useful
// var NWCoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMinX(mapRect), y: mapRect.origin.y)
// var SECoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMaxX(mapRect), y: MKMapRectGetMaxY(mapRect))
}
func getCoordinateFromMapRectanglePoint(x: Double, y: Double) -> CLLocationCoordinate2D {
var mapPoint = MKMapPointMake(x, y)
return MKCoordinateForMapPoint(mapPoint)
}
If you have anymore questions just comment
EDIT 2 I have seen a lot of people break up the visible portion of their mapview into quadrants, most commonly NW,NE,SW and SE.
However, I'm still not entirely sure why theyr'e doing this. I would
like to know the best way to query the back end, which contains a lat
and long for each location, to find the locations which exist on the
mapview.
I'm not sure if this is what you're referring to but they could be using quadtrees as a way to efficiently search the locations of a huge amount of coordinates.
This post from Thoughtbot demonstrates how to use quadtrees to display thousands of place coordinates as clusters on the map and it includes gifs that very succinctly explain how it works:
A quad tree is a data structure comprising nodes which store a bucket of points and a bounding box. Any point which is contained within the node’s bounding box is added to its bucket. Once the bucket gets filled up, the node splits itself into four nodes, each with a bounding box corresponding to a quadrant of its parents bounding box. All points which would have gone into the parent’s bucket now go into one of its children’s buckets.
Source: How To Efficiently Display Large Amounts of Data on iOS Maps

zoom in zoom out at levels in shinobicharts ios

I am using ShinobiCharts in my iOS app to plot a line chart. This requires a feature where in the default view will be in Days. When i pinch zoom, i will be getting Weeks data, and more pinch zooming will give me Months data. Same applies for zoom out in reverse order.
I am not able to find a way to show this data at different zoom levels.
Please help me with this.
Im using following delegate method to check zoom level
- (void)sChartIsZooming:(ShinobiChart *)chart withChartMovementInformation:
(const SChartMovementInformation *)information;
but i dont find any way to check zoom levels.
One way of checking this is to determine the number of days currently displayed within the axis' visible range.
First off you'll need a way to record the current granularity of data on display in the chart:
typedef NS_ENUM(NSUInteger, DataView)
{
DataViewDaily,
DataViewWeekly,
DataViewMonthly,
};
The initial view will be DataViewDaily and is assigned within viewDidLoad to the property currentDataView.
Then within sChartIsZooming:withChartMovementInformation: you could do:
- (void)sChartIsZooming:(ShinobiChart *)chart withChartMovementInformation:(const SChartMovementInformation *)information
{
// Assuming x is our independent axis
CGFloat span = [_chart.xAxis.axisRange.span doubleValue];
static NSUInteger dayInterval = 60 * 60 * 24;
NSUInteger numberOfDaysDisplayed = span / dayInterval;
DataView previousDataView = _currentDataView;
if (numberOfDaysDisplayed <= 7)
{
// Show daily data
_currentDataView = DataViewDaily;
}
else if (numberOfDaysDisplayed <= 30)
{
// Show weekly data
_currentDataView = DataViewWeekly;
}
else
{
// Show monthly data
_currentDataView = DataViewMonthly;
}
// Only reload if the granularity has changed
if (previousDataView != _currentDataView)
{
// Reload and redraw chart to show new data
[_chart reloadData];
[_chart redrawChart];
}
}
Now within your datasource method sChart:dataPointAtIndex:forSeriesAtIndex: you can return the appropriate data points by switching on the value of _currentDataView.
Note that you may need to also update sChart:numberOfDataPointsForSeriesAtIndex to return the number of points to display at the current view level.

How to implement MKAnnotationViews that rotate with the map

I have an app with some annotations on it, and up until now they are just symbols that look good with the default behavior (e.g. like numbers and letters). Their directions are fixed in orientation with the device which is appropriate.
Now however I need some annotations that need to be fixed in orientation to the actual map, so if the map rotates, then the annotation symbols need to rotate with it (like an arrow indicating the flow of a river for example).
I don't want them to scale with the map like an overlay but I do want them to rotate with the map.
I need a solution that primarily works when the user manually rotates the map with their fingers, and also when it rotates due to be in tracking with heading mode.
On Google Maps (at least on android) this is very easy with a simple MarkerOptions.flat(true)
I am sure it won't be too much more difficult on ios, I hope.
Thanks in advance!
Here's what I used for something similar.
- (void)rotateAnnotationView:(MKAnnotationView *)annotationView toHeading:(double)heading
{
// Convert mapHeading to 360 degree scale.
CGFloat mapHeading = self.mapView.camera.heading;
if (mapHeading < 0) {
mapHeading = fabs(mapHeading);
} else if (mapHeading > 0) {
mapHeading = 360 - mapHeading;
}
CGFloat offsetHeading = (heading + mapHeading);
while (offsetHeading > 360.0) {
offsetHeading -= 360.0;
}
CGFloat headingInRadians = offsetHeading * M_PI / 180;
annotationView.layer.affineTransform = CGAffineTransformMakeRotation(headingInRadians);
}
And then, this is called in regionDidChange etc.
Unfortunately, this solution doesn't rotate while the user is rotating the map, but it corrects itself afterwards to make sure it has the proper heading. I wrap some of the affineTransform into an animation block to make it look nice.
Hopefully this can help, or maybe help get you pointed in the right direction.

Fancy Effects on MKMapOverlay CGPath

I'm using a MKOverlayView for drawing a path onto the apple maps. I'd like to draw many short paths onto it, because I need to colorize the track depending on some other values. But I'm getting some fancy effects doing it that way ... also my start- and ending points are connected, but I don't know why. After zooming in/out the fancy-effect-pattern changes and gets bigger/smaller. It seems that you can see the apple map tiles on my path ...
This is my code, its called inside the drawMapRect method of my overlay view.
for(int i = 0; i < tdpoints.pointCount-1; i++ ){
CGPoint firstCGPoint = [self pointForMapPoint:tdpoints.points[i]];
CGPoint secCGPoint = [self pointForMapPoint:tdpoints.points[i+1]];
if (lineIntersectsRect(tdpoints.points[i], tdpoints.points[i+1], clipRect)){
double val1 = (arc4random() % 10) / 10.0f;
double val2 = (arc4random() % 10) / 10.0f;
double val3 = (arc4random() % 10) / 10.0f;
CGContextSetRGBStrokeColor(context, val1 ,val2, val3, 1.0f);
CGContextSetLineWidth(context, lineWidth);
CGContextBeginPath(context);
CGContextMoveToPoint(context,firstCGPoint.x,firstCGPoint.y);
CGContextAddLineToPoint(context, secCGPoint.x, secCGPoint.y);
CGContextStrokePath(context);
CGContextClosePath(context);
}
}
http://imageshack.us/photo/my-images/560/iossimulatorbildschirmf.jpg/
http://imageshack.us/photo/my-images/819/iossimulatorbildschirmf.jpg/
I'm adding my GPS Points like that. (From Breadcrumbs Apple Example)
CLLocationCoordinate2D coord = {.latitude = 49.1,.longitude =12.1f};
[self drawPathWithLocations:coord];
CLLocationCoordinate2D coord1 = {.latitude = 49.2,.longitude =12.2f};
[self drawPathWithLocations:coord1];
CLLocationCoordinate2D coord2 = {.latitude = 50.1,.longitude =12.9f};
[self drawPathWithLocations:coord2];
This is the adding Method:
-(void) drawPathWithLocations:(CLLocationCoordinate2D)coord{
if (!self.crumbs)
{
// This is the first time we're getting a location update, so create
// the CrumbPath and add it to the map.
//
_crumbs = [[CrumbPath alloc] initWithCenterCoordinate:coord];
[self.trackDriveMapView addOverlay:self.crumbs];
// On the first location update only, zoom map to user location
[_trackDriveMapView setCenterCoordinate:coord zoomLevel:_zoomLevel animated:NO];
} else
{
// This is a subsequent location update.
// If the crumbs MKOverlay model object determines that the current location has moved
// far enough from the previous location, use the returned updateRect to redraw just
// the changed area.
//
// note: iPhone 3G will locate you using the triangulation of the cell towers.
// so you may experience spikes in location data (in small time intervals)
// due to 3G tower triangulation.
//
MKMapRect updateRect = [self.crumbs addCoordinate:coord];
if (!MKMapRectIsNull(updateRect))
{
// There is a non null update rect.
// Compute the currently visible map zoom scale
MKZoomScale currentZoomScale = (CGFloat)(self.trackDriveMapView.bounds.size.width / self.trackDriveMapView.visibleMapRect.size.width);
// Find out the line width at this zoom scale and outset the updateRect by that amount
CGFloat lineWidth = MKRoadWidthAtZoomScale(currentZoomScale);
updateRect = MKMapRectInset(updateRect, -lineWidth, -lineWidth);
// Ask the overlay view to update just the changed area.
[self.crumbView setNeedsDisplayInMapRect:updateRect];
}
}
This is the addCoordinate method:
- (MKMapRect)addCoordinate:(CLLocationCoordinate2D)coord
{
pthread_rwlock_wrlock(&rwLock);
// Convert a CLLocationCoordinate2D to an MKMapPoint
MKMapPoint newPoint = MKMapPointForCoordinate(coord);
MKMapPoint prevPoint = points[pointCount - 1];
// Get the distance between this new point and the previous point.
CLLocationDistance metersApart = MKMetersBetweenMapPoints(newPoint, prevPoint);
NSLog(#"PUNKTE SIND %f METER AUSEINANDER ... ", metersApart);
MKMapRect updateRect = MKMapRectNull;
if (metersApart > MINIMUM_DELTA_METERS)
{
// Grow the points array if necessary
if (pointSpace == pointCount)
{
pointSpace *= 2;
points = realloc(points, sizeof(MKMapPoint) * pointSpace);
}
// Add the new point to the points array
points[pointCount] = newPoint;
pointCount++;
// Compute MKMapRect bounding prevPoint and newPoint
double minX = MIN(newPoint.x, prevPoint.x);
double minY = MIN(newPoint.y, prevPoint.y);
double maxX = MAX(newPoint.x, prevPoint.x);
double maxY = MAX(newPoint.y, prevPoint.y);
updateRect = MKMapRectMake(minX, minY, maxX - minX, maxY - minY);
}
pthread_rwlock_unlock(&rwLock);
return updateRect;
}
Hint
I think my refresh algorithm only refreshes one tile of the whole map on the screen and because every time the drawMapRect method is called for this specific area a new random color is generated. (The rest of the path is clipped and the oder color remains ...).
The "fancy effects" you see are a combination of the way MKMapView calls drawMapRect and your decision to use random colours every time it is draw. To speed up display when the user pans the map around MKMapView caches tiles from your overlay. If one tile goes off screen it can be thrown away or stored in a different cache or something, but the ones still on screen are just moved about and don't need to be redrawn which is good because drawing might mean a trip to your data supply or some other long calculation. That's why you call setNeedsDisplayInMapRect, it only needs to fetch those tiles and not redraw everything.
This works in all the apps I've seen and is a good system on the whole. Except for when you draw something that isn't going to be the same each time, like your random colours. If you really want to colour the path like that then you should use a hash or something that seems random but is really based on something repeatable. Maybe the index the point is at, multiplied by the point coordinate, MD5ed and then take the 5th character and etc etc. What ever it is it must generate the same colour for the same line no matter how many times it is called. Personally I'd rather the line was one colour, maybe dashed. But that's between you and your users.
because whenever you draw any path you need to close it. and as you close the path it automatically draws line between lastPoint and firstPoint.
just remove last line in your path drawing
CGContextClosePath(context);
The purpose of CGContextClosePath is to literally close the path - connect start and end points. You don't need that, StrokePath drew the path already. Remove the line. Also move CGContextStrokePath outside your loop, the approach is to move/add line/move/add line... and then stroke (you can change colors as you do this, which you are).
For the "fancy effects" (tilted line joining), investigate the effects of possible CGContextSetLineJoin and CGContextSetLineCap call parameters.

Resources