I currently have a UIView that draws radar data on top of a MKMapView using OpenGL. Because of the level of detail in the radar image, OpenGL is required (CoreGraphics is not fast enough).
All of the images that I am drawing are saved in MKMapPoints. I choose them over the standard CLLocationCoordinate2D because their lengths do not depend on the latitude. The basic method for drawing is this:
Add the GLView as a subview of the MKMapView and set GLView.frame = MKMapView.frame.
Using GLOrthof, set the projection of the GLView to equal the current visible MKMapRect of the map. Here is the code that does this.
CLLocationCoordinate2D coordinateTopLeft =
[mapView convertPoint:CGPointMake(0, 0)
toCoordinateFromView:mapView];
MKMapPoint pointTopLeft = MKMapPointForCoordinate(coordinateTopLeft);
CLLocationCoordinate2D coordinateBottomRight =
[mapView convertPoint:CGPointMake(mapView.frame.size.width,
mapView.frame.size.height)
toCoordinateFromView:mapView];
MKMapPoint pointBottomRight = MKMapPointForCoordinate(coordinateBottomRight);
glLoadIdentity();
glOrthof(pointTopLeft.x, pointBottomRight.x,
pointBottomRight.y, pointTopLeft.y, -1, 1);
Set the viewport to be the correct size using glViewport(0, 0, backingWidth, backingHeight) where backingWidth and backingHeight is the size of the mapView in points.
Draw using glDrawArrays. Not sure if this matters, but GL_VERTEX_ARRAY and GL_TEXTURE_COORD_ARRAY are both enabled during the draw.
Using this method, everything works fine. The drawing is performed like it is supposed to. The only problem is since it is a subview of the mapView (and not an overlay), the radar image is drawn on top of any other MKAnnotations and MKOverlays. I need this layer to be drawn under the other annotations and overlays.
What I tried to do to get this working was to make the glView a subview of a custom MKOverlayView instead of the mapView. What I did was give the MKOverlay a boundingMapRect of MKMapRectWorld and set the frame of the glView the same way that I set the projection (since frame of a MKOverlayView is determined by MKMapPoints and not CGPoints). Again, here is the code.
CLLocationCoordinate2D coordinateTopLeft =
[mapView convertPoint:CGPointMake(0, 0)
toCoordinateFromView:mapView];
MKMapPoint pointTopLeft = MKMapPointForCoordinate(coordinateTopLeft);
CLLocationCoordinate2D coordinateBottomRight =
[mapView convertPoint:CGPointMake(mapView.frame.size.width,
mapView.frame.size.height)
toCoordinateFromView:mapView];
MKMapPoint pointBottomRight = MKMapPointForCoordinate(coordinateBottomRight);
glRadarView.frame = CGRectMake(pointTopLeft.x, pointTopLeft.y,
pointBottomRight.x - pointTopLeft.x,
pointBottomRight.y - pointTopLeft.y);
When I do this, the glView is positioned correctly on the screen (in the same place that is was while it was a subview of the mapView), but the drawing no longer works correctly. When the image does come up, it is not the right size and not in the correct location. I did a check and backingWidth and backingHeight are still the size of the view in points (as they should be).
Any idea why this is not working?
I've been away from iphone for too long to really fully grok your code, but I seem to recall from when I messed with Open GL on the iPhone some time ago, I found that it was necessary to maintain my own z-Index and simply draw items in that order... Each draw operation happened rotated in 3d properly, but something drawn later was always on top of something drawn earlier. One of my early test programs drew a grid on a surface, and then caused the whole thing to flip. My expectation was that the grid would disappear when the back of the object was facing me, but it remained visible because it was drawn later in a separate operation (IIRC)
It's possible that I was doing something wrong that caused that problem, but my solution was to order my draws by a z index.
Can you draw the image first using your first method?
I think that you should just set the viewport before setting the projection mode.
Related
I've got few pixels wide circle (circle is UIBezierPath based). I have to put an arc (which is basically UIView subclass with custom drawing) on the circle so the arc covers circle. I know how to calculate rotation of arc and position but something is not right. I mean I know the reason - it's beacause center property which is assigned to center of UIView, if it was center of the arc, everything would be great. But it's not.
I also know how to solve that. I have to calculate smaller radius where I will put arcs on. But how to do that. Basically it seems easy but because of the arc is in rectangular UIView it gets a bit harder. I'll show you some images so you can see the problem.
The easiest way to do this is to change the anchor point of each arc view's layer. You can read about the anchor point here if you don't already know about it.
You will need to add the QuartzCore framework to your build target and add #import <QuartzCore/QuartzCore.h>.
CGRect circleBounds = circleView.bounds;
topArcView.layer.anchorPoint = CGPointMake(.5, 0);
topArcView.layer.position = CGPointMake(CGRectGetMidX(circleBounds), 0);
bottomArcView.layer.anchorPoint = CGPointMake(.5, 1);
bottomArcView.layer.position = CGPointMake(CGRectGetMidX(circleBounds), CGRectGetMaxY(circleBounds));
leftArcView.layer.anchorPoint = CGPointMake(0, .5);
leftArcView.layer.position = CGPointMake(circleBounds.origin.x, CGRectGetMidY(circleBounds));
rightArcView.layer.anchorPoint = CGPointMake(1, .5);
rightArcView.layer.position = CGPointMake(CGRectGetMaxX(circleBounds), CGRectGetMidY(circleBounds));
Perhaps you could make the UIView subclass the same size and center point as the circle's rect. Then draw the arc in the correct location.
OK this is actually three distinct questions in one thread:
1) I'm using:
- (void)viewDidLoad
{
[super viewDidLoad];
[mapView setFrame :CGRectMake(-100,-100,520,520)];
[mapView setAutoresizesSubviews:true];
mapView.showsUserLocation = YES;
locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
[locManager startUpdatingLocation];
[locManager startUpdatingHeading];
[bt_toolbar setEnabled:YES];
}
- (IBAction) CreatePicture
{
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(mapView.userLocation.coordinate.latitude, mapView.userLocation.coordinate.longitude);
CGPoint annPoint = [self.mapView convertCoordinate:coord toPointToView:self.mapView];
mapPic = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"pic.png"]];
mapPic.frame = CGRectMake(annPoint.x, annPoint.y, 32, 32);
[self.view addSubview:macPic];
}
to place an image using GCRectMake over the user's coordinates, but sadly the image is not being placed in the right spot:
And if i move the map, the picture will always be placed with the exact same offset regarding the user's location. What am i missing? Shouldn't it have been placed directly above the user? I'm guessing the offset (-100, -100) I gave the mapView in the first place is what's causing this (i've got a toolbar below, hence the offset).
2) My iOS 5 simulator is acting crazy, placing my location in Glendale, Phoenix (shouldn't it have been in Cupertino??) for some reason and acting as i were on the move due east; everything works fine in my iPhone 4 though (apart from the incorrect placement of the picture). Any ideas?
3) I've been thinking about using Annotations instead, but i've heard they're static (and i really need them dynamic). Is this true?
Thanks!
When adding a subview, its frame origin is relative to that of its superview. In this case you derived an origin from the map view and then added the image to the view controller's view. You would need to convert your image's origin to the self.view's coordinate system like this:
CGPoint imageOrigin = [self.view convertPoint:annPoint fromView:self.mapView];
mapPic.frame.origin = imageOrigin;
[self.view addSubview:macPic];
NB: this is untested code, but the principal is that you need to convert between the mapView's coordinate space (which is what you got from [self.mapView convertCoordinate:coord toPointToView:self.mapView]) to self.view's coordinate space. This example is a little contrived to be illustrative since an even simpler approach would be to use self.view's coordinate space in the first place:
CGPoint annPoint = [self.mapView convertCoordinate:coord toPointToView:self.view];
I think you would be better off using a custom MKPinAnnotationView using your own image.
as per this answer.
Add image behind MKPinAnnotationView
MKMapView provides the following methods for translating between view space and geographic coordinates:
– convertCoordinate:toPointToView:
– convertPoint:toCoordinateFromView:
– convertRegion:toRectToView:
– convertRect:toRegionFromView:
These are good for tasks like translating touches to map coordinates. For the task you describe, I think a map overlay would be more appropriate than trying to draw on the map using a subview. An overlay is a kind of annotation that allows you to draw whatever content you like, but lets the map take care of determining when the overlay is visible and needs to be drawn at all.
3) I've been thinking about using Annotations instead, but i've heard
they're static (and i really need them dynamic). Is this true?
What do you mean by "static" vs. "dynamic" here? Do you mean that you want to change the content in the overlay view? Or do you want to change the location of the overlay itself? I think it should be possible to update the overlay view as often as you need to. I'm not sure if you can move the overlay by simply adjusting it's boundingMapRect property, but I'd expect that to work. If it doesn't, you can always just remove the overlay and add it again.
Trying to create your own map overlay system instead of using the tools provided by MapKit should be the last thing on your list of options. Life is always easier when you can work with a framework instead of against it, and your code is much less likely to break in the future.
I'm trying to add a box to the centre of the screen in a zoom view. E.g. if I move into an area of the content view and try using the offset coordinates, it becomes erratic if I zoom in or out. I can't seem to figure out the right mathematical formula for this.
If you are working with a UIView or one of it's subclasses. You'll always have a center property available for you. That property is a CGPoint and you can do something like this to test if it is the required result you seek:
CGPoint center = [YourUIViewOrSubclass center];
NSLog(#"Center x is '%f' and y is '%f', center.c, center.y);
I hope this helps you. Otherwise try and rephrase your question and include a little context.
hi all upto now i know making rectangle with the CGrectmake and this rect(frame) i am using as imageview frame like UIImageView *someImage=[[uiimageview alloc]initwithframe:someRect]; now i can add an image with the frame of someRect. my problem here is when the coordinates like
(rectangleFirstx-coordinate,tectangleFirstY-cordinate)=(10,10)
(rectangleLastx-cordinate,rectangleLasty-cordinate)=(17,7) this, how can i give frame to the uiimageview....This is like a inclined rectangle..can any one suggest me how to apply frame through the ios library for these type of coordinates..Thanks in advance..
Your example isn't very clear because a rectangle with opposite corners at (10,10) and (10,7) can be in any one of a myriad of different orientations, including one perfectly aligned along the x and y axis.
What you can certainly do is create a UIImageView of the desired size and location and then rotate it by using one of many techniques, including animation methods.
[UIImageView animateWithDuration:0.1 animations:^
{
your_UIImageView_here.transform = CGAffineTransformMakeRotation((M_PI/180.0) * degrees);
}];
You can hide the UIImageView until the rotation is done and then show it.
If your question is about how to use the coordinates you provided to arrive at an angle I'd suggest that more data is needed because it is impossible to pick one of the billions of possible rectangles with corners at those two points without more information. Once you have more data then it is pretty basic trigonometry to figure out the angle to feed into the rotation.
There are methods to transfer a CGPoint from one UIView to another and from one CALayer to another. I cannot find a way to change a CGPoint from a UIView's coordinate system to a CALayer's coordinate system.
Does a layer and it's host view have the same coordinate system? Do I need to transform points between them?
Thanks,
Corey
[EDIT]
Thanks for the answer! It is hard to find information on the differences/similarities between CA on the iPhone and Mac. I am surprised I couldn't find this issue addressed directly in any Apple Documentation.
I was pursuing this answer to help with a bug I am troubleshooting, and this was so far my best guess, but I suppose I am barking up the wrong tree. If the coordinate systems are the same, then I have another issue...
The actual issue I am having can be found here on Stack Overflow:
layer hit test only returning layer when bottom half of layer is touched
The documentation for hitTest says:
/* Returns the farthest descendant of the layer containing point 'p'.
* Siblings are searched in top-to-bottom order. 'p' is in the
* coordinate system of the receiver's superlayer. */
So you need to do (something like this):
CGPoint thePoint = [touch locationInView:self];
thePoint = [self.layer convertPoint:thePoint toLayer:self.layer.superlayer];
CALayer *theLayer = [self.layer hitTest:thePoint];
In terms of Apple documentation, I found an "iPhone OS Note" in the Layer Coordinate System section of the Core Animation Programming Guide (2008-11-13).
The default root layer of a UIView instance uses a flipped coordinate system that matches the default coordinate system of a UIView instance–the origin is in the top-left and values increase down and to the right. Layers created by instantiating CALayer directly use the standard Core Animation coordinate system.
You can set the transform property of CALayers to the appropriate transform if you want to flip their coordinate system, but note that this will probably flip their drawing of the contents as well (I have not tested that, but it makes sense that this would be true). My assertion that the CALayer associated with a UIView shares the same coordinate system could in fact be entirely erroneous. It could also be that CALayers use the same coordinate system as UIViews (i.e. they're never flipped vertically), but I thought they were since CoreGraphics uses a flipped coordinate system relative to UIKit.
A simple way to test would be to add a screen-sized CALayer as the sublayer of a view's layer, then add another small CALayer as a sublayer of that. You could set it to show up at (0, 0, 320, 100) and see if it shows up on the top or the bottom of the iPhone's screen. This will tell you in which direction the Y axis goes for CoreAnimation.
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *rootLayer = [CALayer layer];
rootLayer.frame = self.view.layer.bounds;
CALayer *smallLayer = [CALayer layer];
smallLayer.frame = CGRectMake(0, 0, rootLayer.bounds.size.width, 50);
smallLayer.backgroundColor = [UIColor blueColor].CGColor;
[rootLayer addSublayer:smallLayer];
[self.view.layer addSublayer:rootLayer];
}
I just performed this test myself, and it appears CALayers actually use the same coordinate system as UIViews, so my assertion that CALayer's flip the Y axis is definitely wrong. However, if you do drawing with CoreGraphics directly, be aware that CoreGraphics does use a flipped Y axis (though when drawing in a UIView subclass or, I assume, a CALayer delegate, the CoreGraphics context has already been flipped to match the UIView's (or CALayer's) coordinate system).
So the short answer, if you made it this far, is the coordinate system for CALayer should match the coordinate system for its corresponding UIView.
From my tests I've found out that sublayers share the same coordinate system as it's parent layer and therefore if you are adding sublayer to a UIView layer then they will share the same coordinate system.
In case you still want to flip the coordinate system so that it's origin is in the upper left corner you should use this transformation that flips the y axis.
(x,y,1) = (x', y', 1) * [1 0 0],[0 -1 0],[0 heigth 1]
which translated to code is:
[your_layer setAffineTransform:CGAffineTransformMake(1,0,0,-1,0,heigth)];
I believe the coordinate system of a UIView and its associated layer should be the same. However, if you create layers yourself, the coordinate system is flipped vertically.
Methods that fixing frames and points
- (CGRect) fixRect:(CGRect)rect inRect:(CGRect)container
{
CGRect frame = rect;
frame.origin.y = container.size.height - frame.origin.y - frame.size.height;
return frame;
}
- (CGPoint) fixPoint:(CGPoint)point fromPoint:(CGSize)containerSize
{
CGPoint frame = point;
frame.y = size.height - frame.y;
return frame;
}