2

I have an app where users can add an image to a map. This is pretty straightforward. It becomes much more difficult when I want to add rotation (taken from the current map heading). The code I use to create an image is pretty straightforward:

let imageAspectRatio = Double(image.size.height / image.size.width)
let mapAspectRatio = Double(visibleMapRect.size.height / visibleMapRect.size.width)

var mapRect = visibleMapRect

if mapAspectRatio > imageAspectRatio {
    // Aspect ratio of map is bigger than aspect ratio of image (map is higher than the image), take away height from the rectangle
    let heightChange = mapRect.size.height - mapRect.size.width * imageAspectRatio
    mapRect.size.height = mapRect.size.width * imageAspectRatio
    mapRect.origin.y += heightChange / 2
} else {
    // Aspect ratio of map is smaller than aspect ratio of image (map is higher than the image), take away width from the rectangle
    let widthChange = mapRect.size.width - mapRect.size.height / imageAspectRatio
    mapRect.size.width = mapRect.size.height / imageAspectRatio
    mapRect.origin.x += widthChange / 2
}

photos.append(ImageOverlay(image: image, boundingMapRect: mapRect, rotation: cameraHeading))

The ImageOverlay class inherits from MKOverlay, which I can easily draw on the map. Here's the code for that class:

class ImageOverlay: NSObject, MKOverlay {
    let image: UIImage
    let boundingMapRect: MKMapRect
    let coordinate: CLLocationCoordinate2D
    let rotation: CLLocationDirection
    
    init(image: UIImage, boundingMapRect: MKMapRect, rotation: CLLocationDirection) {
        self.image = UIImage.fixedOrientation(for: image) ?? image
        self.boundingMapRect = boundingMapRect
        self.coordinate = CLLocationCoordinate2D(latitude: boundingMapRect.midX, longitude: boundingMapRect.midY)
        self.rotation = rotation
    }
}

I have figured out by how much I need to scale and translate the context to fit on the correct location on the map (from which it was added). I can't figure out how to rotate the context to make the image render in the correct location.

I figured out that the map rotation was in degrees, and the rotate method takes radians (took longer than I dare to admit), but the image moves around when I apply the rotation.

I use the following code to render the overlay:

override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
    guard let overlay = overlay as? ImageOverlay else {
        return
    }
    
    let rect = self.rect(for: overlay.boundingMapRect)
    
    context.scaleBy(x: 1.0, y: -1.0)
    context.translateBy(x: 0.0, y: -rect.size.height)
    context.rotate(by: CGFloat(overlay.rotation * Double.pi / 180))
    context.draw(overlay.image.cgImage!, in: rect)
}

How do I need to rotate this context to get the image to be aligned properly?

This is an open source project with the code here

Edit: I have tried (and failed) to use some kind of trig function. If I scale by a factor of 3 * sin(rotation) / 4 (no clue where the 3/4 comes from), I get a correct scale for some rotations, but not for others.

vrwim
  • 13,020
  • 13
  • 63
  • 118
  • Seems you are trying to rotate the overlay around some point which is not the center of it. So this might be helpful for you https://stackoverflow.com/questions/19304470/how-can-i-reliably-rotate-an-image-around-a-point – SamB Aug 29 '21 at 06:59

1 Answers1

0

It sounds like you trying to rotate the object in it's local coordinates, but are actually rotating in the world coordinates. I admit I'm not familiar with this library, but the moral of the story is that order of operation on transformations matter. But it looks like you have "TranslateBy" and you are sending in zero, which might not be moving it at all? If you are trying to translate back to local you'd need to translate to local by subtracting it's current coordinates in the CLLocationCoordinate2D struct.

  1. Translate to local coordinates if not already there (which might be X:0, y:0, you might need to subtract the current coordinate values instead of trying to set them to a specific number like 0)
  2. Apply rotation
  3. Translate back to world coordinates (where you had it before, originally defined as CLLocationCoordinate2D)

This should allow the image to be in the correct position but now rotated to align with the heading.

Here is a paper which explains what you are probably encountering, although more in depth and specific to the matrix/opengl, but the first slide illustrates your issue.

Transforms PDF

Neil.Work
  • 985
  • 7
  • 9
  • I have tried (and failed) to place the transformations in a different order, sometimes the rotation doesn't even match up, even though translations and scaling shouldn't have an influence on that. I am starting to think that the rotation and translation is related to the 3D map projection (and not just the 2D map), and that I will have to do some fancy things that I don't understand :s – vrwim Sep 20 '21 at 11:43
  • In your example code you have context.translateBy(x: 0.0, y: -rect.size.height) That won't translate it to 0 on the x axis, is that still the code you are trying? you need to subtract it's current position. – Neil.Work Sep 21 '21 at 15:15