Building a full world map by collating MKMapview screenshots - ios

I am trying to build a picture of the whole world with a set of overlaid pins on MkMapView.
In order to do so I try to my algorithm is as follow:
take a screenshot
set MkMapRegion to next part of the map not currently on screen
collates all screenshots taken into one big picture.
I have an issue as MkMapView doesn't accurately centre the map on the desired point when I change the latitude, the latitude I set vs the one I end up are different (and varies according to the associated longitude . It's fine for longitude I seem to be able to centre the map on the point I want. Bottom line I have can't seem to be able to re-create the whole world map accurately as I end up with too much or too little of the screenshots vertically.
Anyone with a suggestion?
Code snippet here:
dispatch_queue_t _queue2 = dispatch_queue_create("com.screenShots", DISPATCH_QUEUE_SERIAL);
dispatch_async(_queue2, ^{
int maxLatitudeScreens,maxLongitudeScreens;
if (UIUserInterfaceIdiomPad==UI_USER_INTERFACE_IDIOM()){
maxLatitudeScreens=SCREENS_LATITUDE_IPAD;
maxLongitudeScreens=SCREENS_LONGITUDE_IPAD;
}else{
maxLatitudeScreens=SCREENS_LATITUDE_IPHONE;
maxLongitudeScreens=SCREENS_LONGITUDE_IPHONE;
}
#synchronized(self){
for (int j=0; j<maxLongitudeScreens;j++){
for (int i=0 ; i<maxLatitudeScreens; i++) {
double latitudeDelta=45;
double longitudeDelta=180;
double latit=55.0-j*111.0;
double longit=-180.0+i*longitudeDelta*2/maxLatitudeScreens;
CLLocationCoordinate2D newCenter= CLLocationCoordinate2DMake(latit, longit);
MKCoordinateSpan theSpan= MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
MKCoordinateRegion theRegion=MKCoordinateRegionMake(newCenter, theSpan);
[_contactMapView setRegion:theRegion animated:YES];
NSLog(#"latit=%f and longit=%f",_contactMapView.centerCoordinate.latitude, _contactMapView.centerCoordinate.longitude );
NSLog(#"mapviewCenterlatitude=%f and longitude=%f",_contactMapView.centerCoordinate.latitude, _contactMapView.centerCoordinate.longitude );
NSLog(#"latitudeSpan=%f and longitudeSpan=%f",_contactMapView.region.span.latitudeDelta, _contactMapView.region.span.longitudeDelta );
sleep(3);
[self screenshot:mapViewPrintFormatter.view];
sleep(1);
}
}
//UIImageWriteToSavedPhotosAlbum ([self collateImages],nil,nil,nil);
[self collateImages];
[self shareCollatedMapViewImage];
}
});

Have you tried leaving the region out of the equation after the original setup? Just move the center using setCenterCoordinate:animated: and the zoom should stay consistent.

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.

iOS-Charts how to allow clicks only on plotted points?

I'm using iOS charts framework to plot this chart, I want to detect tap or touch only on the line's path or on the small circle's on the lines.
My question is,
Is there any default code block to do this?
I tried comparing the entry.value with the array plotted(as in the following code), but it doesn't workout.
-(void)chartValueSelected:(ChartViewBase *)chartView entry:(ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight *)highlight{
if ([arrayOfPlottedValues containsObject:[NSNumber numberWithInt:(int)entry.value]]) {
//Tapped on line path
}
else{
//Tapped on empty area
}
}
Any insights will be appreciated.
eg : Line chart
I found a way by considering #Wingzero's suggestion, but the major difference was that, I just used the touch point to find out if its on the "marker" or if its outside it. I'm not sure if its the right way, but the solution is,
-(void)chartValueSelected:(ChartViewBase *)chartView entry:(ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight *)highlight{
//-----------------------------------------------------getting recognizer value
UIGestureRecognizer *recognisedGesture = [chartView.gestureRecognizers objectAtIndex:0];
CGPoint poinOfTouch =[recognisedGesture locationInView:chartView];
CGPoint poinOfMarker =[chartView getMarkerPositionWithEntry:entry highlight:highlight];
if (check if the chartview is BarChartView and if true) {
//-----------------------------------------------------If you want to detect touch/tap only on barchartview's bars
if (poinOfTouch.y > poinOfMarker.y) {
NSLog(#"within the bar area!");
}
else{
NSLog(#"Outside the bar area!");
}
}
else
{
//-----------------------------------------------------If you want to detect touch/tap only on linechartView's markers
//-----------------------------------------------------creating two arrays of x and y points(possible nearby points of touch location)
NSMutableArray *containingXValue = [[NSMutableArray alloc]init];
NSMutableArray *containingYValue = [[NSMutableArray alloc]init];
for (int i =0 ; i<5; i++) {
int roundedX = (poinOfMarker.x + 0.5);
int sumXValuesPositive = roundedX+i;
[containingXValue addObject:[NSNumber numberWithInt:sumXValuesPositive]];
int sumXValuesNegative = roundedX-i;
[containingXValue addObject:[NSNumber numberWithInt:sumXValuesNegative]];
int roundedY = (poinOfMarker.y + 0.5);
int sumYValuesPositive = roundedY+i;
[containingYValue addObject:[NSNumber numberWithInt:sumYValuesPositive]];
int sumYValuesNegative = roundedY-i;
[containingYValue addObject:[NSNumber numberWithInt:sumYValuesNegative]];
}
//-----------------------------------------------------------------------------------------------------------------------------------------
int roundXPointTOuched = (poinOf.x + 0.5);
int roundYPointTOuched = (poinOf.y + 0.5);
//-----------------------------------------------------check if touchpoint exists in the arrays of possible points
if ([containingXValue containsObject:[NSNumber numberWithInt:roundXPointTOuched]] && [containingYValue containsObject:[NSNumber numberWithInt:roundYPointTOuched]])
{
// continue, the click is on marker!!!!
}
else
{
// stop, the click is not on marker!!!!
}
//-----------------------------------------------------------------------------------------------------------------------------------------
}
}
}
Edit : The initial solution was applicable only for the line chart, Now if this same situation arises for bar chart, you could handle it with the above code itself.
Man, I'd been running around it for a while now, feeling really great for getting a positive lead. There is no direction for this issue yet, hope this will be useful for someone like me cheers!
P.S. I'm marking this as answer just to make sure, it reaches the needful :). Thanks
It has a default highlight logic, that is, calculate the closest dataSet and xIndex, so we know which data to highlight.
You can customize this logic to restrain the allowed smallest distance. e.g. define the max allowed distance is 10, if the touch point is away from the closest dot > 10, you return false and not highlgiht.
Highlighter is a class, like BarChartHighlighter, ChartHighlighter, etc.
Update towards your comment:
when you tapped, the delegate method get called, so you know which data is highlighted. Your codes seems fine, however the condition code is blackbox to me. But the delegate will be called for sure, so you only have to worry about your logic.

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.

Issue while Creating Latitudes-Lines on Globe using WhirlyGlobeComponet

I am working on WhirlyGlobe component tester application (great framework for globe app in ios by mousebird team)and trying to create Latitudes and longitudes. I have created longitudes on globe using the method : - (void)addGreatCircles:(LocationInfo *)locations len:(int)len stride:(int)stride offset:(int)offset
and assigning the values as in the array :LocationInfo locations[NumLocations] but when i try to create latitudes on the globe by giving the coordinates in the as:
LocationInfo locations[NumLocations] = {
{"twenty five",0, 180},
{"twenty six",0, -10}
// {"three",30,180.0},
// {"four",30,0},
// {"five",60, 180},
//{"six",60, 0},
}
and son on... i am just able to get half the latitude Line on the globe. I do not know why this issue is coming up.Is this due to OpenGL or what.Somebody please help me do it correctly.
the screenshot when i give starting points(0,-180) end point(0,0) comes up like shown in IMAGES- 1,2:
IMAGE-1
IMAGE-2
and what i need is that the complete latitude Line to be drawn on the Globe .Using start point(0,0) to end point(0,360) gives me a blank output(no line drawn on the globe).
I have tried with Start point(0,10) end point (0,-10) so that line covers complete globe but no success yet.Please help guys!!
First, you'll want to move to the develop branch on github. That's version 2.2 which is just about to be released.
I've added an example in the test app that does exactly this.
Here's how we pull that off.
- (void)addLinesLon:(float)lonDelta lat:(float)latDelta color:(UIColor *)color
{
NSMutableArray *vectors = [[NSMutableArray alloc] init];
NSDictionary *desc = #{kMaplyColor: color, kMaplySubdivType: kMaplySubdivSimple, kMaplySubdivEpsilon: #(0.001), kMaplyVecWidth: #(4.0), kMaplyDrawPriority: #(1000)};
// Longitude lines
for (float lon = -180;lon < 180;lon += lonDelta)
{
MaplyCoordinate coords[3];
coords[0] = MaplyCoordinateMakeWithDegrees(lon, -90);
coords[1] = MaplyCoordinateMakeWithDegrees(lon, 0);
coords[2] = MaplyCoordinateMakeWithDegrees(lon, +90);
MaplyVectorObject *vec = [[MaplyVectorObject alloc] initWithLineString:coords numCoords:3 attributes:nil];
[vectors addObject:vec];
}
// Latitude lines
for (float lat = -90;lat < 90;lat += latDelta)
{
MaplyCoordinate coords[5];
coords[0] = MaplyCoordinateMakeWithDegrees(-180, lat);
coords[1] = MaplyCoordinateMakeWithDegrees(-90, lat);
coords[2] = MaplyCoordinateMakeWithDegrees(0, lat);
coords[3] = MaplyCoordinateMakeWithDegrees(90, lat);
coords[4] = MaplyCoordinateMakeWithDegrees(+180, lat);
MaplyVectorObject *vec = [[MaplyVectorObject alloc] initWithLineString:coords numCoords:5 attributes:nil];
[vectors addObject:vec];
}
latLonObj = [baseViewC addVectors:vectors desc:desc];
}
WhirlyGlobe-Maply 2.2 adds a few tricks to vector rendering and subdivision. You can now tell the toolkit to subdivide lines up to an epsilon to make them acceptable. We can also render one thing on top of another without worrying about z buffering. So there you go, it's pretty easy now.
The only real trickery here is that we have to break the lines into pieces. We need at least three points or the subdivision logic will just detect a degenerate case. The 5 point lines for latitude works some error test logic.

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