I am drawing a route in MapKit between multiple points.
-(void) drawRoute
{
// Removing previous lines
if (routeLine) {
[self.mapView removeOverlay:routeLine];
}
// Count how many points are in the array now
NSInteger numberOfPoints = [coordinatesArray count];
if (numberOfPoints > 1)
{
// Draw a line between two coordinates in the main array of coordinates
for (int i=0;i<([coordinatesArray count]-1);) {
CLLocationCoordinate2D routeCoordinates[2];
// Establish first and last point
NSArray *coordinatePoint1 = [coordinatesArray objectAtIndex:i];
NSArray *coordinatePoint2 = [coordinatesArray objectAtIndex:i+1];
// Get latitude and longitude of the first and last points
double latitudePoint1 = [[coordinatePoint1 objectAtIndex:0]doubleValue];
double longitudePoint1 = [[coordinatePoint1 objectAtIndex:1]doubleValue];
double latitudePoint2 = [[coordinatePoint2 objectAtIndex:0]doubleValue];
double longitudePoint2 = [[coordinatePoint2 objectAtIndex:1]doubleValue];
// Put the coordinates on the route array
routeCoordinates[0] = CLLocationCoordinate2DMake(latitudePoint1, longitudePoint1);
routeCoordinates[1] = CLLocationCoordinate2DMake(latitudePoint2, longitudePoint2);
// Draw the line
routeLine = [MKPolyline polylineWithCoordinates:routeCoordinates count:2];
i++;
// Show the line on the map
[self.mapView addOverlay:routeLine];
}
}
}
The array "coordinatesArray" is created using textfields. So, whenever I enter different points in the fields, the new route is drawn as intended and the old route is removed except for one line. One old line never gets removed. Should I not use removeOverlay? Thanks!
Related
I have a list of 50+ coordinates. What is the most efficient way to draw lines between all these coordinates (should create a "circular" path because they all have a display order) that is also easy to customize (line thickness, color, etc...)?
Thanks!
I am not sure I understand your question for certain. If you are looking for a list of points to display from end to end, then you will want to create a MKPolyline object from those points, making sure the points are added to the myPoints array in the order you want to connect them:
CLLocationCoordinate2D coordinates[[myPoints count]];
int i = 0;
for (Checkpoint *point in myPoints)
{
coordinates[i] = CLLocationCoordinate2DMake([point.lat floatValue] , [point.lon floatValue]);
i++;
}
self.polyline = [MKPolyline polylineWithCoordinates:coordinates count: [myPoints count]];
[mapView addOverlay:self.polyline];
Then make sure you are implementing the delegate method - mapView:rendererForOverlay:. Here's an example, but tailor it to your needs:
-(MKOverlayRenderer*)mapView:(MKMapView*)mapView rendererForOverlay:(id <MKOverlay>)overlay
{
MKPolylineRenderer* lineView = [[MKPolylineRenderer alloc] initWithPolyline:self.polyline];
lineView.strokeColor = [UIColor blueColor];
lineView.lineWidth = 7;
return lineView;
}
However, if you really want a closed loop (circular) object, then you will want to create a MKPolygon object instead. The process is quite similar; in that case replace the self.polyline initializer above with this code:
self.polygon = [MKPolygon polygonWithCoordinates:coordinates count: [myPoints count]];
[mapView addOverlay:self.polygon];
The - mapView:rendererForOverlay: code should remain the same I think. I haven't tested this code, but hopefully it gets you moving in the right direction.
I am using google map sdk for ios to provide directions between current user location and an end location. I have so far achieved to draw a GMSPolyline between the current user location and the end location using the code below and it's working great.
GMSPath *encodedPath = [GMSPath pathFromEncodedPath:encodedPathSting];
self.polyline = [GMSPolyline polylineWithPath:encodedPath];
self.polyline.strokeWidth = 4;
self.polyline.strokeColor = [UIColor colorWithRed:55.0/255.0 green:160.0/255.0 blue:250.0/255.0 alpha:1.0];;
self.polyline.map = self.mapView;
Is it possible to remove a part of the GMSPolyline that has been covered by the user through driving/walking? The GMSPolyline must gradually decrease in length as we trace the path.
One way to achieve this is by redrawing the path repeatedly but this is not or may not be efficient.
Thanks.
So get the latlng point of the polyline in an array as described here:
//route is the MKRoute in this example
//but the polyline can be any MKPolyline
NSUInteger pointCount = route.polyline.pointCount;
//allocate a C array to hold this many points/coordinates...
CLLocationCoordinate2D * routeCoordinates = malloc(pointCount * sizeof(CLLocationCoordinate2D));
//get the coordinates (all of them)...
[route.polyline getCoordinates: routeCoordinates
range: NSMakeRange(0, pointCount)
];
//this part just shows how to use the results...
NSLog(#"route pointCount = %d", pointCount);
for (int c = 0; c < pointCount; c++) {
NSLog(#"routeCoordinates[%d] = %f, %f",
c, routeCoordinates[c].latitude, routeCoordinates[c].longitude);
}
//free the memory used by the C array when done with it...
free(routeCoordinates);
Then, implement a while loop for the first point as you move along the path like this:
int c = 0;
while (pointCount.size() > 0)
{
pointCount.get(0).remove();
}
Note: I'm not that experienced with iOS and haven't tested this solution. Treat it as a suggestion rather than a fix. Thanks!
Hope it helps!
In my app I'm drawing the polyline overlay on the map with the points starting from the user Locations obtained from the didUpdateUserLocation: delegate method.
But for some reason it always starts from Africa.
What could be the reason? Do I have to specify any starting coordinate for the polyline?
-(void)drawTheRoute{
[self.mapViewTrace removeOverlay:self.polyline];
CLLocationCoordinate2D coordinates[arrayOfPoint.count];
int i = 1;
for (TMPoint *point in arrayOfPoint) {
coordinates[i] = point.coordinate;
i++;
}
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coordinates count:arrayOfPoint.count];
[self.mapViewTrace addOverlay:polyline];
self.polyline = polyline;
self.lineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:self.polyline];
self.lineRenderer.strokeColor = [UIColor redColor];
self.lineRenderer.lineWidth = 5;
}
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
TMPoint *point = [[TMPoint alloc] initWithCoordinate:userLocation.location.coordinate withSpeed:userLocation.location.speed];
[arrayOfPoint addObject:point];
[self drawTheRoute];
}
In the drawTheRoute method, the C array coordinates is declared with arrayOfPoint.count elements and the for loop initializes each index of the C array using i as the index variable.
The problem is that the i index variable is initially set to 1 instead of 0.
The first index of a C array (and NSArrays) is index 0 -- not 1.
This leaves the first coordinate of coordinates as uninitialized and the memory happened to contain values that were interpreted as the coordinate 0, 0 (and sometimes "random" coordinates).
(The other effect is that when arrayOfPoint has only one object, the C array will only have one element with an index of 0. But since i is initialized to 1, the for loop will end up setting index 1 (the second element) of the C array even though only one element of memory was allocated and you will be accessing memory not allocated to you which could result in EXC_BAD_ACCESS.)
Change this line:
int i = 1;
to this:
int i = 0;
I am using GMSCoordinateBounds to get a list of markers that are in visible area. But I am getting all the markers that are plotted instead of just visible ones.
This is how I am doing it:
GMSVisibleRegion visibleRegion = [mapView_.projection visibleRegion];
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc]initWithRegion:visibleRegion];
GMSMarker *resultMarker = [[GMSMarker alloc]init];
for (int i=0; i<[markerArray count]; i++) //this has all the markers
{
resultMarker = [markerArray objectAtIndex:i];
if ([bounds containsCoordinate:resultMarker.position])
{
NSLog(#"User is present on screen");
[listTableArray addObject:resultMarker.title];
}
}
[listTableView reloadData];
Your code looks fine. I'm pretty sure whatever your issue is, it's coming from somewhere other than the code you posted.
Another potential issue is that if your map allows rotation, all manner of disorienting stuff will happen to your GMSVisibleRegion object. (ie. the farLeft property won't correspond to the north-western point). I'd think GMSCoordinateBounds would take that into account and not get tripped by it.
Having said that, you can write your own method to check if a marker's coordinate is contained in a region. This is one that I've written (including my own "wrappers" for region and marker):
-(BOOL)isMarker:(SGMarker*)m inVisibleRegion:(SGRegion*)region
{
CLLocationCoordinate2D upperLeftPosition = region.topLeft;
CLLocationCoordinate2D lowerRightPosition = region.bottomRight;
if (m.position.latitude > lowerRightPosition.latitude && m.position.latitude < upperLeftPosition.latitude &&
m.position.longitude < lowerRightPosition.longitude && m.position.longitude > upperLeftPosition.longitude) {
return YES;
}
return NO;
}
// In my region wrapper, this is how I make sure I have the north-east/south-west points
+(SGRegion*)regionWithGMSVisibleRegion:(GMSVisibleRegion)region
{
SGRegion* mapRegion = [[self alloc] init];
// Since the camera can rotate, first we need to find the upper left and lower right positions of the
// visible region, which may not correspond to the farLeft and nearRight points in GMSVisibleRegion.
double latitudes[] = {region.farLeft.latitude, region.farRight.latitude, region.nearLeft.latitude};
double longitudes[] = {region.nearRight.longitude, region.nearLeft.longitude, region.farLeft.longitude};
double highestLatitude = latitudes[0], lowestLatitude = latitudes[0];
double highestLongitude = longitudes[0], lowestLongitude = longitudes[0];
for (int i = 1; i < 3; i++) {
if (latitudes[i] >= highestLatitude)
highestLatitude = latitudes[i];
if (latitudes[i] < lowestLatitude)
lowestLatitude = latitudes[i];
if (longitudes[i] >= highestLongitude)
highestLongitude = longitudes[i];
if (longitudes[i] < lowestLongitude)
lowestLongitude = longitudes[i];
}
mapRegion.topLeft = CLLocationCoordinate2DMake(highestLatitude, lowestLongitude);
mapRegion.bottomRight = CLLocationCoordinate2DMake(lowestLatitude, highestLongitude);
return mapRegion;
}
So if you use these methods instead, you should be able to absolutely tell where your issue is coming from (ie. not from here ;) ).
I see that the GMSPolyline protocol already defines a color property for its stroke color, but is there a way to shade the inside of its polygon (ideally with transparency)? I’m looking for a Google Maps equivalent to MKPolygon and friends.
A Polyline is different to a Polygon's. Polylines' have no concept of a fill color. File a feature request for Polygons to be added to the SDK.
There is a way, you can get something like this:
The approach is rather simple:
Add transparent noninteracting UIView with overriden drawing code and pass it CGPoints for drawing polygons
Get your CLLocationCoordinate2D coordinates for polygons and convert them to CGPoints for drawing
Update those CGPoints every time map moves so you can redraw them in the right position and make that UIView redraw itself.
So, what you want to do is add an UIView on top of your mapview, which is transparent and non-userinteractive, which has overridden drawRect method. It is provided with a double array of CGPoints, like CGpoint **points, acccessed with points[i][j] where i is each of closed polygons and j are individual points of each polygon. The class would be, let's call it OverView:
#import "OverView.h"
#interface OverView ()
{
CGPoint **points;
int *pointsForPolygon;
int count;
}
#end
#implementation OverView
- (id)initWithFrame:(CGRect)frame andNumberOfPoints:(int)numpoints andPoints:(CGPoint **)passedPoints andPointsForPolygon:(int *)passedPointsForPolygon;{
self = [super initWithFrame:frame];
if (self) {
// You want this to be transparent and non-user-interactive
self.userInteractionEnabled = NO;
self.backgroundColor = [UIColor clearColor];
// Passed data
points = passedPoints; // all CGPoints
pointsForPolygon = passedPointsForPolygon; // number of cgpoints for each polygon
count = numpoints; // Number of polygons
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
for(int i=0; i<count; i++) // For each of polygons, like blue ones in picture above
{
if (pointsForPolygon[i] < 2) // Require at least 3 points
continue;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
CGContextSetLineWidth(context, 2.0);
for(int j = 0; j < pointsForPolygon[i]; j++)
{
CGPoint point = points[i][j];
if(j == 0)
{
// Move to the first point
CGContextMoveToPoint(context, point.x, point.y);
}
else
{
// Line to others
CGContextAddLineToPoint(context, point.x, point.y);
}
}
CGContextClosePath(context); // And close the path
CGContextFillPath(context);
CGContextStrokePath(context);
}
}
#end
Now, in original UIViewController with mapview, you need to have access to all coordinates that make all the polygons (same array as points, but consisting of CLLocationCoordinate2D, and several others:
#interface ViewController () <GMSMapViewDelegate>
{
CGPoint **points;
int howmanypoints;
int *pointsForPolygon;
CLLocationCoordinate2D **acoordinates;
}
acoordinates is populated wherever you get your coordinates for polygons, I parse the response string from Fusion Tables, part of my parser method
- (void)parseResponse2
{
NSMutableArray *fullArray = [[self.fusionStringBeaches componentsSeparatedByString:#"\n"] mutableCopy];
howmanypoints = fullArray.count; // This is number of polygons
pointsForPolygon = (int *)calloc(howmanypoints, sizeof(int)); // Number of points for each of the polygons
points = (CGPoint **)calloc(howmanypoints, sizeof(CGPoint *));
acoordinates = (CLLocationCoordinate2D **)calloc(howmanypoints, sizeof(CLLocationCoordinate2D *));
for(int i=0; i<fullArray.count; i++)
{
// Some parsing skipped here
points[i] = (CGPoint *)calloc(koji, sizeof(CGPoint));
acoordinates[i] = (CLLocationCoordinate2D *)calloc(koji, sizeof(CLLocationCoordinate2D));
pointsForPolygon[i] = koji;
if (koji > 2)
{
// Parsing skipped
for (int j=0; j<koji; j++)
{
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(coordinates[j].latitude, coordinates[j].longitude);
// Here, you convert coordinate and add it to points array to be passed to overview
points[i][j] = [self.mapView.projection pointForCoordinate:coordinate];
// and added that coordinate to array for future access
acoordinates[i][j] = coordinate;
}
}
}
// Finally, allocate OverView passing points array and polygon and coordinate counts
self.overView = [[OverView alloc] initWithFrame:self.view.bounds
andNumberOfPoints:howmanypoints
andPoints:points
andPointsForPolygon:pointsForPolygon];
// And add it to view
[self.view addSubview:self.overView];
}
Now, you have Polygons where you want them, but must observe - (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position delegate method as drawn polygons won't move with map. The trick is that you have your 2D array of coordinates acoordinates and you can user helper function (CGPoint *)[self.mapview.projection pointForCoordinate:(CLLocationCoordinate2D)coordinate] to recalculate the positions, like:
- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position
{
if (points != nil)
{
// Determine new points to pass
for (int i=0; i<howmanypoints; i++)
{
for(int j=0; j<pointsForPolygon[i]; j++)
{
// Call method to determine new CGPoint for each coordinate
points[i][j] = [self.mapView.projection pointForCoordinate:acoordinates[i][j]];
}
}
// No need to pass points again as they were passed as pointers, just refresh te view
[self.overView setNeedsDisplay];
}
}
And that's it. Hope you got the gist of it. Please, comment if I need to clarify something. I can also make a small complete project and upload it to github so you can research it better.