2

after reading through Projection's documentation, and some fail-and-try testing, i have found myself unable to find a solution to this issue.

Al the solutions i find play with the fact that the x and y coordinates are in reference to the mercator map projection, but in this case i have them from a local image placed on a layer over the map.

Let's assume i have a 400px * 200px grey image, placed on a leaflet map using ImageOverlay.Rotated, at the position:

var topleft = L.latLng(41.604766, 0.606402);
var topRight = L.latLng(41.605107, 0.606858);
var bottomleft = L.latLng(41.604610, 0.606613);

This image is placed on the map using top left, top right, and bottom left coordinates as reference.

the result is a rectangle slightly rotated to the north.

If i knew i left my phone at the pixel 100,50 ( the left corner of the original image ), how could i transform this cartesian coordinate into a latitude and longitude for my map to display it?

if it was a rectangle placed perfectly aligned to the north, i guess i could get the difference in º between the topLeft and bottomLeft corners, divide it by number of pixels, and then multiply the result with my phone's Y to get the latitude. using the prior value, i'd multiply it to my phone's X to get my phone's longitude.

but given the shape is rotated, the world is not flat, and i was really bad at geography and trigonometry, i'm not sure about it being precise enough.

in the end we're speaking of a phone, not a building. a few meters off ( 2-4) is okay, a building block away is not.

CptEric
  • 897
  • 9
  • 23
  • This becomes much more difficult as the 'resolution' of the image gets larger. Because of the curvature of the earth, latitude and longitude is not linear (as your image would be). This is dealt with using the haversine formula. Not sure if this helps or not but look that formula up. It may help you find an answer. – Jon Glazer Mar 07 '18 at 12:39
  • the source image is meant to be very small ( to represent only tens of metres at max), so the resolution of the image should always remain the same. to make it easier, i would only need the exact position at zoom level 25-30 as per generic google maps/leaflet zoom mesaures. – CptEric Mar 07 '18 at 12:48

1 Answers1

6

Oh, you're talking about my very own Leaflet.ImageOverlay.Rotated. I'll be happy to oblige.

You should have a look at its source code. All the maths are there. Let's go over some bits of that code:

    var pxTopLeft    = this._map.latLngToLayerPoint(this._topLeft);
    var pxTopRight   = this._map.latLngToLayerPoint(this._topRight);
    var pxBottomLeft = this._map.latLngToLayerPoint(this._bottomLeft);

This is converting the three lat-long coordinates into screen-relative pixel coordinates. The Leaflet tutorial about creating subclasses of L.Layer has a good explanation of what "layer point" means.

You can replace these calculations to any other reprojections you want, assuming that you're working in a plane and not on a geoid. In other words: you might want to transform your lat-long coordinates into EPSG:3857 spherical mercator coordinates (which is the display projection Leaflet uses). If you do so, you can later convert an interpolated EPSG:3857 coordinate into a EPSG:4326 "plain latitude-longitude" coordinate.

    // Calculate the skew angles, both in X and Y
    var vectorX = pxTopRight.subtract(pxTopLeft);
    var vectorY = pxBottomLeft.subtract(pxTopLeft);

The CSS transforms need the skew angles to distort the image properly. It is perhaps counter-intuitive, but ImageOverlay.Rotated uses CSS's skew() rather than rotate. But you don't need the skew angles, you just need those differential vectors.

If you want to visualize that, vectorX is a 2D vector that goes along the top side of your image (from left to right), and vectorY is a 2D vector that goes along the left side (from top to bottom).

Those vectors allow you to get the screen coordinate of any point in the image, assuming an input in the range of ([0..1], [0..1]) (with 0 being the top or left of the image, and 1 being the bottom or the right). But you probably don't want to work in numbers relative to the height/width of the image, because in your question you mention pixels. So let's grab pixels.

    var imgW = this._rawImage.width;
    var imgH = this._rawImage.height;

Good. We have extracted from ImageOverlay.Rotated all the bits of math required.


Now, if you divide the previous vectors by the image dimensions in pixels...

    var vectorXperPx = vectorX.divideBy(imgW);
    var vectorYperPx = vectorY.divideBy(imgW);

These vectors go from one pixel of your original image to a pixel either on its left (x) or underneath (y). So given a pixel (x,y) of the original image, the projected vector relative to the image corner will be:

    var deltaVector = 
               xPixelCoordinate.scaleBy(vectorXPerPx)
          .add(yPixelCoordinate.scaleBy(vectorYPerPx))

That's the vector, in screen coordinates, from the top-left corner of your image to the (xPixelCoordinate, yPixelCoordinate) pixel of your image.

Now add the screen coordinate of the top-left corner of the image, and you're set:

    var finalCoordinateForImagePixel = pxTopLeft.add(deltaVector);

As we used latLngToLayerPoint, the result will be relative to that frame of reference. Want to get it back? Easy:

    var finalCoordinateForImagePixelInLatLong = 
           map.layerPointToLatLng(finalCoordinateForImagePixel);

but given the shape is rotated, the world is not flat, and i was really bad at geography and trigonometry, i'm not sure about it being precise enough.

If you use the coordinate reference system that Leaflet uses for display (EPSG:3857), or any homomorphic coordinate systems (pixel coordinates relative to the screen, pixel coordinates relative to the layer origin point), you'll have no problem.

IvanSanchez
  • 18,272
  • 3
  • 30
  • 45
  • what is xPixelCoordinate meant to represent? is it meant to be a leaftlet Point? i'm trying to figure where the scaleBy comes from. pretty awesome answer btw. – CptEric Mar 07 '18 at 13:56
  • i've managed to output the result, but it doesn't seem to fit the image. even position 1,1 seems out of the line. maybe i should also skew the resulting coordinates? – CptEric Mar 07 '18 at 14:48
  • right. as my image is rotated, the coordinates are being calculated from the correct point, the top left corner of the image, but they are transformed in pure vertical or horizontal axis, not related to the image's axis. – CptEric Mar 07 '18 at 14:59
  • so if i want to put a marker on the pixel 601x, 359y of the image, it should scale from the original resolution to the displayed, and then do the calculations on the basis that the X and Y angles are "skewed" in an angle, and are not pure vertical and horiziontal, right? – CptEric Mar 07 '18 at 15:09
  • right now what i get is this: https://i.imgur.com/Y963K5V.png the little red dot is where pixel 601, 359 should be, the marker is the result of the calculations. – CptEric Mar 07 '18 at 15:15
  • after some testing i finally managed to do it by manually skewing the finalCoordinateForImagePixelInLatLong by the original skew of the image minus 8º ( there is some weird constant -8º/8º (depends on orientation) difference between sweking the image and skewing a lat_long). thank you very much for your insights. – CptEric Mar 08 '18 at 11:50