MKMapView image overlay drawing inverted - mkmapview

I am currently creating an app which requires numerous overlays of different sizes to be drawn on a map. The app currently grabs 2 map points which represent the corners of a bounding box in which the overlay should sit. I am then converting these in to a MKMapRect for use within an MKOverlay class. The overlay is drawing in the correct place but the image seems to be flipped vertically, I can tell this as I tried applying a CGAffineTransformMakeRotation(180) and the image appeared the right way around but the writing was backwards.
If anyone can help me figure out why it's doing this it would be much appreciated, please see my code below.
Setup code:
// First grab the overlay co-ordinates
double left = [[self.storedWildMapObject.arrayBBox objectAtIndex:0] doubleValue], bottom = [[self.storedWildMapObject.arrayBBox objectAtIndex:1] doubleValue], right = [[self.storedWildMapObject.arrayBBox objectAtIndex:2] doubleValue], top = [[self.storedWildMapObject.arrayBBox objectAtIndex:3] doubleValue];
// Store these in 2 coordinates representing top left / bot right
CLLocationCoordinate2D upperLeftCoord =
CLLocationCoordinate2DMake(top,left);
CLLocationCoordinate2D lowerRightCoord =
CLLocationCoordinate2DMake(bottom,right);
//Convert to map points
MKMapPoint upperLeft = MKMapPointForCoordinate(upperLeftCoord);
MKMapPoint lowerRight = MKMapPointForCoordinate(lowerRightCoord);
// Work out sizes
double rightToLeftDiff = lowerRight.y - upperLeft.y;
double topToBottDiff = lowerRight.x - upperLeft.x;
MKMapRect bounds = MKMapRectMake(upperLeft.x, upperLeft.y, topToBottDiff, rightToLeftDiff);
// Add to overlay
MKMapOverlay *mapOverlay = [[MKMapOverlay alloc] initWithMapRect:bounds andCoord:upperLeftCoord];
[self.mapV addOverlay:mapOverlay];
// Map overlay
- (id)initWithMapRect:(MKMapRect)rect andCoord:(CLLocationCoordinate2D)coord
{
self = [super init];
if (self)
{
self.centerPos = coord;
self.mkMapRect = rect;
}
return self;
}
-(CLLocationCoordinate2D)coordinate
{
return self.centerPos;
}
- (MKMapRect)boundingMapRect
{
return self.mkMapRect;
}
// MapOverlayView
- (id)initWithOverlay:(id <MKOverlay>)overlay andImage:(UIImage *)img
{
self = [super initWithOverlay:overlay];
if (self)
{
self.image = img;
}
return self;
}
/** Degrees to Radian **/
#define degreesToRadians( degrees ) ( ( degrees ) / 180.0 * M_PI )
/** Radians to Degrees **/
#define radiansToDegrees( radians ) ( ( radians ) * ( 180.0 / M_PI ) )
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)ctx
{
CGImageRef imageReference = self.image.CGImage;
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
CGRect clipRect = [self rectForMapRect:mapRect];
CGContextAddRect(ctx, clipRect);
CGContextClip(ctx);
//CGContextRotateCTM(ctx, degreesToRadians(180.0f));
CGContextDrawImage(ctx, theRect, imageReference);
}
Just incase it helps I also have a debugger print out of theMapRect as used in MapOverlayView:
(lldb) p theMapRect
(MKMapRect) $17 = {
(MKMapPoint) origin = {
(double) x = 1.27662e+08
(double) y = 7.86099e+07
}
(MKMapSize) size = {
(double) width = 8.12378e+06
(double) height = 1.27474e+07
}
}
Below is what it looks like:

Have you verified that the storedwildmapobject has the coordinates in the order you are expecting them?

The answer found here seems to draw my image the correct way around with the same mapRect:
https://stackoverflow.com/a/3599448/1090024
So I'm using this for now.

Related

THREE JS - how to impement the automatic 3d model scale

I have implemented a simple 3D model viewer with three.js. The project contains a database of 3D models, divided into different categories. In one category, the models are tall (with a relatively small width, the height is much higher than the height of the other models), in another category, the models are small (relative to all products, their height and width are smaller than in others, these are small models), in another category, the models are large (their height and wider than many models from other categories.
The viewer has a fixed canvas width and height. For this reason, when loading a model in canvas, many models are immediately loaded at a small scale, which requires subsequent multiple zooming. There are also models, the upper part of which does not fit into the canvas, and the lower part does, at boot time. This also requires subsequent scaling.
It is necessary that the viewer first estimates the dimensions of the model, after that it automatically selects the scale for the model individually, after that it centers the model vertically and horizontally.
It is also necessary that the plane in which the model lies (the model has tangible width and height, the thickness is very small, all these models are close to flat) coincide with the plane of the screen. At boot time, many models have an offset for these planes. How do I implement this so that the viewer automatically expands the model?
Below I am attaching screenshots for models from different categories - where they are clearly recorded:
Below is the code of the viewer that is responsible for initializing the scene:
var objectUrl = $('#modelViewerModal').data('object-url');//'/storage/3d/qqq.obj';
var mesh, renderer, scene, camera, controls;
init();
animate();
function init() {
const screenshotPageWidth = $(document).width();
const screenshotPageHeight = $(document).height();
let modalBody = document.querySelector('#scene-container');
// renderer
renderer = new THREE.WebGLRenderer({modalBody});
var height = screenshotPageHeight / 2;
var width = screenshotPageWidth / 2;
if (screenshotPageHeight < screenshotPageWidth) { // landscape orientation
if (width > 3 * height / 2) {
width = 3 * height / 2;
} else if (width < 3 * height / 2) {
height = 2 * width / 3;
}
} else if (screenshotPageHeight > screenshotPageWidth) { // portrait orientation
if (height > 2 * width / 3) {
height = 2 * width / 3;
} else if (height < 2 * width / 3) {
width = 3 * height / 2;
}
}
// let limitHeight = screen.height - 137;
renderer.setSize(width, height);
modalBody.appendChild( renderer.domElement );
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
// camera
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 20, 20, 20 );
// controls
controls = new OrbitControls( camera, renderer.domElement );
// ambient
scene.add( new THREE.AmbientLight( 0x222222 ) );
// light
var light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 2000, 2000, 2000 );
scene.add( light );
var spotLight_01 = getSpotlight('rgb(145, 200, 255)', 1);
spotLight_01.name = 'spotLight_01';
var spotLight_02 = getSpotlight('rgb(255, 220, 180)', 1);
spotLight_02.name = 'spotLight_02';
scene.add(spotLight_01);
scene.add(spotLight_02);
// geometry
var geometry = new THREE.SphereGeometry( 5, 12, 8 );
// material
const material = new THREE.MeshPhongMaterial({
color: 0xeae8dc,
side: THREE.DoubleSide,
transparent: false,
flatShading: false,
opacity: 0
});
// mesh
var objLoader = new THREE.OBJLoader();
objLoader.load(objectUrl,
function ( obj ) {
mesh = obj;
mesh.scale.setScalar( 0.01 );
obj.traverse( function( child ) {
if ( child.isMesh ) child.material = material;
} );
// center the object
var aabb = new THREE.Box3().setFromObject( mesh );
var center = aabb.getCenter( new THREE.Vector3() );
mesh.position.x += (mesh.position.x - center.x);
mesh.position.y += (mesh.position.y - center.y);
mesh.position.z += (mesh.position.z - center.z);
scene.add( mesh );
animate();
} );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
function getSpotlight(color, intensity) {
var light = new THREE.SpotLight(color, intensity);
light.castShadow = true;
light.shadow.mapSize.x = 4096;
light.shadow.mapSize.y = 4096;
return light;
}
This can be done with just two things - the camera object, and the mesh object. What you need to do use use the camera frustum (the representation of the viewable area of the camera https://en.wikipedia.org/wiki/Viewing_frustum) to calculate the distance at which it is the same height (or width) as your model. The height of the model can easily be acquired from the geometry.
// Get the size of the model
mesh.geometry.computeBoundingBox()
const modelSize = mesh.geometry.boundingBox.getSize()
const width = modelSize.x // the exact axis will depend on which angle you are viewing it from, using x for demonstration here
// Compute necessary camera parameters
const fov = camera.fov
const aspect = camera.aspect // camera aspect ratio (width / height)
// three.js stores camera fov as vertical fov. We need to calculate horizontal fov
const hfov = (2 * Math.atan(Math.tan(MathUtils.degToRad(fov) / 2) * aspect) * 180) / Math.PI;
// calculate the distance from the camera at which the frustum is the same width as your model
const dist = (width * 0.5) / Math.tan(MathUtils.degToRad(hfov * 0.5));
// Position camera exactly based on model. There are more elegant ways to do this, but this is a quick and dirty solution
// Camera looks down its own negative Z axis, adding Z effectively "zooms out"
camera.position.copy(mesh.position).translateZ(dist)
These fov and frustum calculations may appear daunting, but these are well-solved problems in 3D engines, and the exact algorithms can be looked up pretty quickly with a few searches.
This solution obviously only works with width, and uses a rigid method for moving the camera, but hopefully it provides you with the tools to apply the solution in a way that works for your application.

Shooting a projectile in spritekit

I am looking to shoot a SKSpritenode (a cannonball) from a another Sprite node (enemy ship).
The cannonball should travel directly down toward bottom of screen from the enemy ship node.
I can't seem to get positioning right, cannonball seems to shoot from the corner of the screen and not move far, the enemy-ship node is randomly chosen from an array of all halos on screen at once and the cannonball position assigned to the front of halo:
// the cannonball
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:6];
ball.physicsBody.velocity = CGVectorMake(SHOT_SPEED, SHOT_SPEED);
// we dont want ball to lose speed or momentum when it hots edges so..
ball.physicsBody.restitution=1.0; // max bounceness of node
ball.physicsBody.linearDamping =0.0; // dont reduce linear velocity ove time
ball.physicsBody.friction=0.0;
ball.physicsBody.categoryBitMask = kCCBallCategory;
// ball.physicsBody.contactTestBitMask = kCCEdgeCategory;
ball.physicsBody.collisionBitMask= kCCEdgeCategory;
ball.physicsBody.contactTestBitMask = kCCEdgeCategory | KCCShieldUPCategory | kCCMultiUpCategory ; // | KCCShieldUPCategory notify
// the array of enemy ship nodes
NSMutableArray *allHalos = [NSMutableArray array];
[_mainLayer enumerateChildNodesWithName:#"halos" usingBlock:^(SKNode *node, BOOL *stop) {
[allHalos addObject:node];
}];
if ([allHalos count]>0) {
NSUInteger allHalosInteger = arc4random_uniform([allHalos count]);
SKSpriteNode *shooterHalo=[allHalos objectAtIndex:allHalosInteger];
ball.position = CGPointMake(shooterHalo.position.x, shooterHalo.position.y - shooterHalo.frame.size.height/2 + ball.frame.size.height / 2);
CGPoint bulletDestination = CGPointMake(shooterHalo.position.x, - ball.frame.size.height / 2);
[self fireBullet:ball toDestination:bulletDestination withDuration:2.0 ];
}
-(void)fireBullet:(SKNode*)ball toDestination:(CGPoint)destination withDuration:(NSTimeInterval)duration {
//1
SKAction* bulletAction = [SKAction sequence:#[[SKAction moveTo:destination duration:duration],
[SKAction waitForDuration:3.0/60.0],
[SKAction removeFromParent]]];
[ball runAction:[SKAction group:#[bulletAction, _soundLaser]]];
[self addChild:ball];
}
Any input appreciated.

Change Overlay Alpha based on MapType selection

I would like to know if its possible to change the map overlay alpha based on selecting the maptype here is my code I thought might work but it doesn't seem to. Can anyone provide some incite?
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay{
TileOverlayView *view = [[TileOverlayView alloc] initWithOverlay:overlay];
if(mapview.mapType == MKMapTypeHybrid) {
view.tileAlpha = 0.55;
} else if(mapview.mapType == MKMapTypeSatellite) {
view.tileAlpha = 0.0;
} else {
view.tileAlpha = 0.75;
}
return [view autorelease];
}
This code works fine
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
MKCircleView *circleView = [[MKCircleView alloc] initWithCircle:overlay];
CGFloat alpha;
if (mapView.mapType == MKMapTypeStandard) {
alpha = 0.5f;
} else {
alpha = 1.0f;
}
circleView.fillColor = [[UIColor blackColor] colorWithAlphaComponent:alpha];
return circleView;
}
But in mapView:viewForOverlay you set alpha based on current map type. To change alpha when map change map type you need to observe mapType property using KVO. So when map type is changing you just set new alpha for all overlays. To get view for overlay use
[mapView viewForOverlay:(id<MKOverlay>)overlay];

How to resize MKAnnotationView on iOS6?

Resize MKAnnotationView Image When map zooms in and out? This methord are successful on iOS5, but failed on iOS6.
I change the MKAnnotationView's transform directly, and no luck. The MKAnnotationView only resize in a flash(When touch up inside the MKMapView, after the touch up finish, the MKAnnotationView will restore the original size).
My code are below:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
for (id <MKAnnotation>annotation in _mapView.annotations) {
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
continue;
// handle our custom annotations
//
if ([annotation isKindOfClass:[XPMKAnnotation class]])
{
// try to retrieve an existing pin view first
MKAnnotationView *pinView = [_mapView viewForAnnotation:annotation];
//resize the pin view
double zoomLevel = [_mapView getZoomLevel];
double scale = (1.0 * zoomLevel / 16) + 0.5;
pinView.transform = CGAffineTransformMakeScale(scale, scale);
}
}
}
Can we resize the MKAnnotationView on iOS6? Anybody know any way?
Apple suggests resizing the .image property of an annotation view. In my case shown below, I looked at the UIImage that was set in the viewforAnnotation and re-scaled it to the zoom level in a UIPinchGestureRecognizer
UIImage *orangeImage = [UIImage imageNamed:#"Orange210.PNG"];
CGRect resizeRect;
//rescale image based on zoom level
resizeRect.size.height = orangeImage.size.height * scale;
resizeRect.size.width = orangeImage.size.width * scale ;
NSLog(#"height = %f, width = %f, zoomLevel = %f", resizeRect.size.height, resizeRect.size.width,zoomLevel );
resizeRect.origin = (CGPoint){0,0};
UIGraphicsBeginImageContext(resizeRect.size);
[orangeImage drawInRect:resizeRect];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
pinView.image = resizedImage;

CALayer as SubLayer Not Visible

I am trying to built an animated circle which would be drawn clockwise until it becomes complete circle as illustrated in iPhone Core Animation - Drawing a Circle
Problem is that CALayer object is not added or build. I tested and saw that it is not accessing my drawInContext:CGContextRef and animatingArc methods.
What so far I have done is:
In AnimateArc.h
#interface AnimateArc : CALayer {
CAShapeLayer *circle;
}
-(void) animatingArc;
#end
In AnimateArc.m
-(void) drawInContext:(CGContextRef)ctx
{
CGFloat radius = 50.0;
circle = [CAShapeLayer layer];
//make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.0, 0.0, 2 * radius, 2 * radius) cornerRadius:radius].CGPath;
CGPoint centerPoint = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2);
//center the shape in self.view
circle.position = centerPoint;
//configure appearence of circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor blackColor].CGColor;
circle.lineWidth = 5;
/*CGPointMake((self.contentsCenter.size.width), (self.contentsCenter.size.height));*/
//path the circle
CGContextAddArc(ctx, centerPoint.x, centerPoint.y, radius, 0.0, 2 * M_PI, 0);
CGContextClosePath(ctx);
//fill it
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextFillPath(ctx); }
///////////////////////////////////////////////////////////////////////
-(void) animatingArc
{
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"arcEnd"];
anim.duration = 20.0; //animate over 20 seconds
anim.repeatCount = 1.0; //animate only once
anim.removedOnCompletion = NO; //Reamin there after completion
//animate from start to end
anim.fromValue = [NSNumber numberWithFloat:50.0f];
anim.toValue = [NSNumber numberWithFloat:150.0f];
//experiment with timing to get appearence to look the way you want
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
//add animation to circle
[circle addAnimation:anim forKey:#"animatingArc"];
}
/////////////////////
//needed since key not part of animatable properties
+(BOOL) needsDisplayForKey:(NSString *)key
{
if([key isEqualToString:#"arcEnd"])
return YES;
else
return [super needsDisplayForKey:key];
}
//ensure custom properties copied to presentation layer
-(id) initWithLayer:(id)layer
{
if((self = [super initWithLayer:layer]))
{
if ([layer isKindOfClass:[AnimateArc class]])
{
AnimateArc *other = (AnimateArc *) layer;
[other setNeedsDisplay];
}
}
return self; }
And finally in my viewController,
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view.layer addSublayer:AnimateArcObject];
[AnimateArcObject animatingArc];
}
Apology for bad formatting.... Please can someone tell me what am I doing wrong? I have also doubt that my code can crash at any place after accessing those two functions since I am novice about Core Animation and haven't got any idea that I am in right direction or not.
Any help will be appreciated. Thanks.
From my painful experience with CoreAnimation, you must always set the bounds property of any CALayer you instantiate.
So, you're layer is not showing because you are missing something like:
layer.bounds = CGRectMake(0, 0, width, height);
you should place this as soon as you instantiate the layer, and make it a habit to do so, so you don't fall into it again.
As for your code crashing, sorry. It's too distributed and I am not sure how it's linked together, so I can't help you there.
After little searching, I thought that I am going in wrong direction. So I deleted this AnimateArc file and added new one which is inheriting from UIViewController.
Then in viewDidLoad Method, I wrote the code from this link to create circle and animations using path.
In parent view controller, I added AnimatedArc ViewController's subview. Now its working perfectly :)

Resources