Ey guys, so a seemingly simple problem but apparently too complicated for me. I am trying to create one instance of MKPolygon and it ain't going too well. Here is the code:
MKMapPoint point1 = {38.53607,-121.765793};
MKMapPoint point2 = {38.537606,-121.768379};
MKMapPoint point3 = {38.53487,-121.770578};
NSArray *mapPointArr = [[NSArray alloc] initWithObjects:point1,point2,point3,nil count:3]; //errors here
MKPolygon *polygon = [MKPolygon polygonWithPoints:mapPointArr count:3];
I am getting a bunch of errors on the line at which I initialize the array(incompatible type for argument 1...). Any idea what's wrong? Thanks in advance!
MKMapPoint is a plain c-structure and you can't add it to objective-c container directly.
In your case you do not need to do that as +polygonWithPoints: requires not a NSArray, but a c-array as 1st parameter. Proper way to create polygon will be:
MKMapPoint points[3] = {{38.53607,-121.765793}, {38.537606,-121.768379}, {38.53487,-121.770578}};
MKPolygon *polygon = [MKPolygon polygonWithPoints:points count:3];
Related
I want to draw corridor using MKPolygon over MKMap using Mapkit. I have one route from station A to B.
I have MKMapRects around route for drawing corridor. Now i want to merge all rectangles in single Polygon and that is my Corridor along with route. How to join all the rectangles in single Polygon .
No.of rectangles : 160
Here i am attaching sample image indicating what i needed.
Here is code snippet.
for(int i=0;i<[self.boundingRectsArr count];i++) {
lat1 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"xLT"] doubleValue];
long1 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"yLT"] doubleValue];
lat2 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"xRT"] doubleValue];
long2 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"yRT"] doubleValue];
lat3 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"xRB"] doubleValue];
long3 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"yRB"] doubleValue];
lat4 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"xLB"] doubleValue];
long4 = [[[self.boundingRectsArr objectAtIndex:i] valueForKey:#"yLB"] doubleValue];
CLLocationCoordinate2D rect[5];
rect[0] = CLLocationCoordinate2DMake(lat1, long1);
rect[1] = CLLocationCoordinate2DMake(lat2, long2);
rect[2] = CLLocationCoordinate2DMake(lat3, long3);
rect[3] = CLLocationCoordinate2DMake(lat4, long4);
rect[4] = CLLocationCoordinate2DMake(lat1, long1);
MKPolygon* polyCorridor = [MKPolygon polygonWithCoordinates:rect count:5];
polyCorridor.title = #"Colorado";
[self.map addOverlay:polyCorridor];
}
Thanks in Advance. Welcome to your answers.
Regards, Sagar P.
So, to be fast:
Draw corrigor: draw second thick line.
Highlight objects in corridor: calculate them without a polygon union, but with distance-to-route approach.
You could draw two lines in your overlay implementation, one, thin, for route, and one for a corridor - thiiick and semi-transparent, you could try to calculate point to km ratio using data MKMapView provides and calculate the thick line width. And for the objects on map you want to highlight - you could use different approaches, there are number of algos for finding points near curve or a straight line. You even could be rather straightforward: split the route into straight lines and check the distance of all the objects - that would be very slow, but it will work (sure you'll need to google for those complex algos for that).
Simple, first convert the MKMapRects to MKPolygons, then create the union of all the MKPolygons using this library:
https://github.com/SunGard-Labs/MKPolygon-GPC unfortunately its not free.
I'm trying to create then retrieve an array of CLLocationCoordinate2D objects, but for some reason the array is always empty.
I have:
NSMutableArray *currentlyDisplayedTowers;
CLLocationCoordinate2D new_coordinate = { currentTowerLocation.latitude, currentTowerLocation.longitude };
[currentlyDisplayedTowers addObject:[NSData dataWithBytes:&new_coordinate length:sizeof(new_coordinate)] ];
I've also tried this for adding the data:
[currentlyDisplayedTowers addObject:[NSValue value:&new_coordinate withObjCType:#encode(struct CLLocationCoordinate2D)] ];
And either way, the [currentlyDisplayedTowers count] always returns zero. Any ideas what might be going wrong?
Thanks!
To stay in object land, you could create instances of CLLocation and add those to the mutable array.
CLLocation *towerLocation = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
[currentDisplayedTowers addObject:towerLocation];
To get the CLLocationCoordinate struct back from CLLocation, call coordinate on the object.
CLLocationCoordinate2D coord = [[currentDisplayedTowers lastObject] coordinate];
As SB said, make sure your array is allocated and initialized.
You’ll also probably want to use NSValue wrapping as in your second code snippet. Then decoding is as simple as:
NSValue *wrappedCoordinates = [currentlyDisplayedTowers lastObject]; // or whatever object you wish to grab
CLLocationCoordinate2D coordinates;
[wrappedCoordinates getValue:&coordinates];
You need to allocate your array.
NSMutableArray* currentlyDisplayedTowers = [[NSMutableArray alloc] init];
Then you can use it. Be sure to call release when you are done with it or use another factory method.
I had currentlyDisplayedTowers = nil which was causing all the problems. Also, the previous advice to init and alloc were necessary. Thanks everyone for the help!
For anyone else with this issue, there's another solution if you are planning on using MapKit.
(The reason I say IF, of course, is because importing a module such as MapKit purely for a convenient wrapper method is probably not the best move.. but nonetheless here you go.)
#import MapKit;
Then just use MapKit's coordinate value wrapper whenever you need to:
[coordinateArray addObject:[NSValue valueWithMKCoordinate:coordinateToAdd]];
In your example..
[currentlyDisplayedTowers addObject:[NSValue valueWithMKCoordinate:new_coordinate]];
I want to store my CGPoint to the NSMutable Array, so , I have method like this:
[self.points addObject:CGPointMake(x, y)];
But I got the error, it said that :
Incompatible type for argument 1 of
"addObject".
So, I check out the API,
- (void)addObject:(id)anObject
anObject The object to add to the end
of the receiver's content. This value
must not be nil.
So, I think the "CGPointMake" can make a Object, but it can't be assigned. What happens?
The problem is that CGPoint is actually just a C structure it is not an object:
struct CGPoint {
CGFloat x;
CGFloat y;
};
typedef struct CGPoint CGPoint;
If you are on the iPhone you can use the NSValue UIKit additions to convert the CGPoint to an NSValue object.
See this previous answer for examples: How can I add CGPoint objects to an NSArray the easy way?
You can also do the following:
[myArray addObject:[NSValue valueWithCGPoint:MyCGPoint]];
Unfortunately for you a CGPoint isn't an Objective-c object. It is a c struct. if you Apple double click on CGPoint you should jump to the definition
struct CGPoint {
CGFloat x;
CGFloat y;
};
typedef struct CGPoint CGPoint;
If you want to store CGPoint in an NSArray you will need to wrap them first. You can use NSValue for this or write your own wrapper.
see Converting a CGPoint to NSValue
EDIT> There is a small overhead for each objective-c method call, and creating and destroying objects involves many method calls before they are even used for anything. You shouldn't worry about this normally but for very small objects which encapsulate little behaviour and that have short lifetimes it can affect performance. If Apple used objects for all points, rect, sizes and even ints, floats, etc performance would be worse.
To build on the answer given by atbreuer11, you can convert your CGPoint to NSValue, store it in NSMutableArray and convert it back using the following:
//Convert CGPoint and Store it
CGPoint pointToConvert = CGPointMake(100.0f, 100.0f);
NSValue *valueToStore = [NSValue valueWithCGPoint:pointToConvert];
NSMutableArray *arrayToKeep =[NSMutableArray arrayWithObject:valueToStore];
Then restore it again:
CGPoint takeMeBack;
for (NSValue *valuetoGetBack in arrayToKeep) {
takeMeBack = [valuetoGetBack CGPointValue];
//do something with the CGPoint
}
That's probably the easiest way to do it. You can write a complete class and do all types of data manipulation, but I think it would be an overkill, unless you really have to.
EDIT
For Swift 5 (I'm not sure why one would want to do this, given that we can use literal arrays nowadays, but here goes):
Save Values:
let somePoint = CGPoint(x: 200, y: 400)
let array = NSMutableArray(array: [somePoint])
To retrieve it:
let points = array.compactMap({ ($0 as? NSValue)?.cgPointValue })
Swift 3.x
// Convert CGPoint to NSValue
let cgPoint = CGPoint(x: 101.4, y: 101.0)
let nsValue = NSValue(cgPoint: cgPoint)
var array = NSArray(object: nsValue)
// Restore it again
var cgPoint : CGPoint!
for i in array {
cgPoint = i as? CGPoint
}
A simple way to handle CGPoint (or any other non NSObject inherited structure) is to create a new class inherited from NSObject.
The code is longer, but clean. An example is below:
In .h file:
#interface MyPoint:NSObject
{
CGPoint myPoint;
}
- (id) init;
- (id) Init:(CGPoint) point;
- (BOOL)isEqual:(id)anObject;
#end
In .m file:
#implementation MyPoint
- (id) init
{
self = [super init];
myPoint = CGPointZero;
return self;
}
- (id) Init:(CGPoint) point{
myPoint.x = point.x;
myPoint.y = point.y;
return self;
}
- (BOOL)isEqual:(id)anObject
{
MyPoint * point = (MyPoint*) anObject;
return CGPointEqualToPoint(myPoint, point->myPoint);
}
#end
Here is some code sample showing the usage, do not forget to release!!!
//init the array
NSMutableArray *pPoints;
pPoints = [[NSMutableArray alloc] init];
// init a point
MyPoint *Point1 = [[MyPoint alloc]Init:CGPointMake(1, 1)];
// add the point to the array
[pPoints addObject:[[MyPoint alloc] Point1]];
//add another point
[Point1 Init:CGPointMake(10, 10)];
[pPoints addObject:[[MyPoint alloc] Point1]];
[Point1 Init:CGPointMake(3, 3)];
if ([pPoints Point1] == NO))
NSLog(#"Point (3,3) is not in the array");
[Point1 Init:CGPointMake(1, 1)];
if ([pPoints Point1] == YES))
NSLog(#"Point (1,1) is in the array");
How is a 1000ft or 1/2 mile distance determined with mapkit? Either a radius from some pin or the distance between two pins.
For example, I center the map on pin A. Pins B, C, and D are also on the map at various distances from pin A. B and C are within 1/2 mile from A but D is 1 mile away. I'd like to know that B and C are within 1/2 mile from A. How can I calculate that?
Since you've stated that the two different points are "pins", I'm going to assume you're using MKPinAnnotationView (or some other annotation view). If not, you're going to have to get the location some other way.
If you have pointers to the annotation objects for these locations, then you can easily call -coordinate on them, create CLLocations from these coordinates (using -initWithLatitude:longitude:), and then use the method -getDistanceFrom to retrieve the distance in meters. From there, it's an easy conversion to miles. All told, the code would look something like this:
CLLocationCoordinate2D pointACoordinate = [pointAAnnotation coordinate];
CLLocation *pointALocation = [[CLLocation alloc] initWithLatitude:pointACoordinate.latitude longitude:pointACoordinate.longitude];
CLLocationCoordinate2D pointBCoordinate = [pointBAnnotation coordinate];
CLLocation *pointBLocation = [[CLLocation alloc] initWithLatitude:pointBCoordinate.latitude longitude:pointBCoordinate.longitude];
double distanceMeters = [pointALocation getDistanceFrom:pointBLocation];
double distanceMiles = (distanceMeters / 1609.344);
You'll end up with the distance in miles, and can compare it from there. Hope this helps.
getDistanceFrom is now deprecated so here's an alternative answer for anyone looking to do this.
CLLocationCoordinate2D pointACoordinate = [pointAAnnotation coordinate];
CLLocation *pointALocation = [[CLLocation alloc] initWithLatitude:pointACoordinate.latitude longitude:pointACoordinate.longitude];
CLLocationCoordinate2D pointBCoordinate = [pointBAnnotation coordinate];
CLLocation *pointBLocation = [[CLLocation alloc] initWithLatitude:pointBCoordinate.latitude longitude:pointBCoordinate.longitude];
float distanceMeters = [pointALocation distanceFromLocation:pointBLocation];
float distanceMiles = (distanceMeters / 1609.344);
[pointALocation release];
[pointBLocation release];
As above, you could use float instead of double and you could cast the results to an int if you don't require the precision of the float like so:
int distanceCastToInt = (int) [pointALocation distanceFromLocation:pointBLocation];
The int is handy if you wanted to give a rough idea of distance in the annotation like so:
pointAAnnotation.title = #"Point A";
pointAAnnotation.subtitle = [NSString stringWithFormat: #"Distance: %im",distanceCastToInt];
The subtitle of the annotation would read "Distance: 50m" for example.
This is Swift 3 equivalent of the given answer:
let pointACoordinate = pointAAnnotation.coordinate
let pointALocation = CLLocation(latitude: pointACoordinate.latitude, longitude: pointACoordinate.longitude)
let pointBCoordinate = pointBAnnotation.coordinate
let pointBLocation = CLLocation(latitude: pointBCoordinate.latitude, longitude: pointBCoordinate.longitude)
let distanceMeters = pointALocation.distance(from: pointBLocation)
let distanceMiles = (distanceMeters / 1609.344)
I found some mapkit code on the internet that looks like this:
- (void)recenterMap {
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;
}
--omitted the rest
I want to know why valueForKeypath is used to get the coordinate data, instead of just
[self.mapView.annotations]
Has it something to do with speed?
He's using the valueForKeyPath: construct to return an array of all the coordinates for all of the annotations.
Assuming that self.mapView is an MKMapView then it has an annotations property which returns an array of objects conforming to the MKAnnotation protocol. That means that every object in that array should implement the coordinate property. By issuing a call to [self.mapView valueForKeyPath:#"annotations.coordinate"] he is immediately getting an array of all of the coordinates without having to iterate over each individual annotation item in the array.
Without using the KVC construct here he'd have to do something similar to this:
NSMutableArray *coordinates = [NSMutableArray array];
for (id<MKAnnotation> annotation in self.mapView.annotations)
[coordinates addObject:annotation.coordinate];
In all, it just makes for simpler, easier to read code.