0

For a visualization I need an optical satellite image for a specific rectangular AOI, that is defined by two lat/long coordinates. I tried Mapbox Static Images API, which takes a lat/long bounding box and a resolution in width/height pixel for the output. The problem is that it looks like to me that if ratio of the lat/long box is not the same as the w/h pixels, it will add padding to the lat/long bounding box to fill the w/h of the pixel image.

And this would prevent me from combining the optical image with the other data, because I would not know which image pixel would (roughly) correspond to which lat/long coordinate.

I see three "solutions", but I don't know how to achive any of them.

  1. "Make" Mapbox return the images with out padding.
  2. Compute the ratio for the correct w/h pixel ratio using the lat/long coordinate, so there would be no padding. Maybe with https://en.wikipedia.org/wiki/Equirectangular_projection like discussed here: https://stackoverflow.com/a/16271669/380038?
  3. Find a way to determine the lat/long coordinates of the optical satellite image so I can cut off the possible padding.

I checked How can I extract a satellite image from google maps given a Lat Long Rectangle?, but I would prefer to use my existing paid Mapbox account and I got the impression that I still wouldn't get the exact optical image or the exact corner coordinates of the optical image.

Framester
  • 33,341
  • 51
  • 130
  • 192
  • 1 degree of latitude could be to a different length of a degree of longitude. So your "ratio" doesn't work. The purpose of the static image, it is to have static images (e.g. for background). If needed, it is your task to crop the image (it will be very expensive on CPU and memory, to do it on a server). – Giacomo Catenazzi Sep 15 '21 at 13:53
  • Your 2nd proposed solution seems doable to me, using the w/h ratio of the final image size you are generating to determine the correct lng/lat coordinate. Could you share your existing code in some sort of environment such as CodePen.io or CodeSandbox.io that we can experiment with? – Brandon McConnell Sep 17 '21 at 18:36
  • Thank you Brandon, unfortunately, I don't have code that I am at the liberty to share. – Framester Sep 20 '21 at 15:00

3 Answers3

2
  • Mapbox Static Images API serves maps
  • You have optical image from other source
  • You want to overlay these data

Right?

Note the Red and Green pins: the waypoints are at opposite corners on Mapbox.

After Equirectangular correction Mapbox matches Openstreetmaps (little wonder), but Google coordinates are quite close too.

curl -g "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/[17.55490,47.10434,17.55718,47.10543]/600x419?access_token=YOUR_TOKEN_HERE" --output example-walk-600x419-nopad.png

Openstreetmaps route Mapbox after Equirectangular correction Position according to Google

What is your scale? 1 km - 100 km?

What is your source of optical image?

What is the required accuracy?

Just to mention, optical images have their own sources of distortions.

In practice:

You must have the extent of your non optical satellite data (let's preserve the mist around...) I'll call it ((x1, y1), (x2, y2)) We are coders, not cartographers - right!? If you feed your extent to https://docs.mapbox.com/playground/static/ as

min longitude = x1, min lattitude = y1, max longitude = x2, max lattitude = y2

Select "Bounding box" entry! Do you see mapbox around your data!? Don't mind the exact dimensions, just check if mapbox is related to your data! May be you have to swap some values to get to the right corner of the globe.

If you have the right ((x1, y1), (x2, y2)) coordinates, do the equirectangular transformation to get the right pixel size.

You've called it Solution #2.

Let's say the with of your non optical satellite data is Wd, the height is Hd. The mapbox image will fit your data, if you ask for Wm widht, and Hm height of mapbox data where

Wm = Wd

Hm = Wd * (y2 - y1) * cos(x1) / (x2 - x1) 

Now you can pull the mapbox by

curl -g "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/[<x1>,<y1>,<x2>,<y2>]/<Wm>x<Hm>?access_token=<YOUR_TOKEN>" --output overlay.png

If (Hd == Hm) 

  then {you are lucky :) the two images just fit each other}

  else { the two images are for the same area, but you have to scale the height of one of the images to make match }

Well... almost. You have not revealed what size of area you want to cover. The equation above is just an approximation which works up to the size of a smaller country (~100 km or so). For continent scale you probably have to apply more accurate formulas.

  • Dear Balazs, I actually have non optical satellite data, that I want to overlay with static satellite data from Mapbox. – Framester Sep 20 '21 at 15:02
  • 1
    Dear Framester, please check **In practice** section above. – Balazs Papp Sep 20 '21 at 16:23
  • Thank you Balazs for your update. In the end I decided to use the Haversine formula, because it is (as I understand it) more precise than the equirectangular transformation and there is a nice sklearn implementation: https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.haversine_distances.html – Framester Sep 22 '21 at 06:37
1

In my opinion, your #2 idea is the way to go. You do have the LLng bbox, so all that remains is calculate its "real" size in pixels.

Let us say that you want (or can allow, or can afford) a resolution of 50m per pixel, and the area is small enough not to have distortions (i.e., a rectangle of say 1 arcsecond of latitude and 1 arcsecond of longitude has top and bottom sides of the same length, with an error less than your chosen resolution). These are, I believe, very loose requisites and easy to fulfill.

Then, you just need to calculate the distance between the (Lat1, Lon1) and (Lat1, Lon2) points, and betwen (Lat1, Lon1) and (Lat2, Lon1). Divide that distance in meters by 50, and you'll get the exact number of pixels:

      Lon1            Lon2
Lat1   +---------------+
       |               |
       |               |
Lat2   +---------------+

And you have a formula for that - the haversine formula.

If you need a higher precision, you could recourse to the Vincenty oblate spheroid (here a Javascript library). On the MT site (first link) there is a live calculator that you can use to plug data from your calls, and verify whether the approach is indeed working. I.e. you plug in your bounding box, get the distance in meters, divide and get the pixel size of the image (if the image is good, chances are that you can go with the simpler haversine. If it isn't, then there has to be some further quirk in the maps API - its projection, perhaps - that doesn't return the expected bounding box. But it seems unlikely).

LSerni
  • 55,617
  • 10
  • 65
  • 107
0

I've had this exact problem when using a satellite image on an apple watch. I overlay some markers and a path. I convert everything from coordinates to pixels. Below is my code to determine the exact bbox result

var maxHoleLat = 52.5738902
var maxHoleLon = 4.9577606
var minHoleLat = 52.563994
var minHoleLon = 4.922364

var mapMaxLat = 0.0
var mapMaxLon = 0.0
var mapMinLat = 0.0
var mapMinLon = 0.0

let token = "your token"



var resX = 1000.0
var resY = 1000.0

let screenX = 184.0
let screenY = 224.0 // 448/2 = 224 - navbarHeight
let navbarHeight = 0.0


var latDist = 111000.0
var lonDist = 111000.0

var dx = 0.0
var dy = 0.0

func latLonDist(){
    //calgary.rasc.ca/latlong.htm
    let latRad = maxHoleLat * .pi / 180
    
    //distance between 1 degree of longitude at given latitude
    self.lonDist = 111412.88 * cos(latRad) - 0.09350*cos(3 * latRad) + 0.00012 * cos(5 * latRad)
    print("lonDist = \(self.lonDist)")
    
    //distance between 1 degree of latitude at a given longitude
    self.latDist = 111132.95 - 0.55982 * cos(2 * latRad) + 0.00117 * cos(4 * latRad)
    print("latDist = \(self.latDist)")
    
   
    
}

func getMapUrl(){
    
    
     self.dx = (maxHoleLon - minHoleLon) * lonDist
     self.dy = (maxHoleLat - minHoleLat) * latDist

    //the map is square, but the hole not
    //check if the hole has less x than y
    
    if dx < dy {
        mapMaxLat = maxHoleLat
        mapMinLat = minHoleLat
        
        let midLon = (maxHoleLon + minHoleLon ) / 2
        mapMaxLon = midLon + dy / 2 / lonDist
        mapMinLon = midLon - dy / 2 / lonDist
        
        
    } else {
        mapMaxLon = maxHoleLon
        mapMinLon = minHoleLon
        
        let midLat = (maxHoleLat + minHoleLat ) / 2
        mapMaxLat = midLat + dx / 2 / latDist
        mapMinLat = midLat - dx / 2 / latDist
        
    }
    
    

    self.imageUrl = URL(string:"https://api.mapbox.com/styles/v1/mapbox/satellite-v9/static/[\(mapMinLon),\(mapMinLat),\(mapMaxLon),\(mapMaxLat)]/1000x1000?logo=false&access_token=\(token)")
    
        print("\(imageUrl)")
    }