0

I have a static image with only center point lat/long (for example https://api.mapbox.com/styles/v1/mapbox/light-v9/static/-78.4649,42.5128,5,0,0/300x200) and I want to put on this map some markers(lat.long) with the help of canvas. But I need to calculate somehow the xy coordinates for those markers. So I know the center of map(lat/long) and the lat/long marker coordinates. Is there any way to convert lat/long to xy knowing only zoom level and center?

Or if I know the xy of the center lat/long(it always be the same 150px * 100px) and zoom level, could I calculate the xy for other markers?

I have a lot of markers (>200, and they all are custom svg generated and so on) to place it on this map. I can't use mapbox mapbox static map because of the markers limitation and so on.

UPD: Based on the comments I updated the question.

How to calculate it for 256px square tiles?

SERG
  • 3,907
  • 8
  • 44
  • 89
  • 2
    Is there a reason you want to overlay it with canvas instead of just using the API’s built in marker overlay capabilities? https://docs.mapbox.com/api/maps/#marker – riastrad Nov 08 '19 at 13:05
  • 1
    Are you aware that even if this was possible, >200 markers on a 150px *100px image, I doubt you could see much of the underlying map! Anyway, check out how it's done with standard 256 tiles a la google maps, particularly the final example and the corresponding Mercator transform, https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/coordinates – LuisTavares Nov 17 '19 at 23:13
  • @LuisTavares 150*100 is for example. Thank you for the link. – SERG Nov 19 '19 at 06:40

2 Answers2

2

Based on the OP comment I'm assuming that the requested image is square, for the sake of simplicity (TILE_SIZE could be decomposed in a TILE_SIZE_X and TILE_SIZE_Y component). I'm also assuming that the image is 256-pixels wide TILE_SIZE=256

I'm giving both the pixel coordinates relative to the center of the image (distanceInPixels function), and to the Lower Left Corner (imageCoordinates function). Changing to the Upper Left Corner in case that's necessary should be trivial (X will be equal and Y = TILE_SIZE -Y).

<!DOCTYPE html>
<html>
<body>      
<p id="demo"></p>    
<script>
    var latLngMarker = {};
    var latLngCenter = {};  
// Image dimensions in pixels
    var TILE_SIZE = 256; 
    var zoom = 5;   
// Coordinates of the marker to be projected on the image
    latLngMarker.lat = 41.850;
    latLngMarker.lng = -87.650; 
// Coordinates of the image center  
    latLngCenter.lat = 41.850; 
    latLngCenter.lng = -87.650;
// Coordinates projected on the cartographic plane (Mercator)     
    var centerProjected = project(latLngCenter);
    var markerProjected = project(latLngMarker);

// The result should be X=Y=0, because I made Marker Lat Lng  = Center Lat Lng
    var distanceFromCenter = distanceInPixels(centerProjected, markerProjected);
    alert("X: " + distanceFromCenter.x + " Y: " + distanceFromCenter.y);

// The result should be X=Y=256/2=128 for the same reason      
    var coords = imageCoordinates(centerProjected, markerProjected);
    alert("X: " + coords.x + " Y: " + coords.y);

    // The horizontal distance represented by one pixel for a given latitude and zoom level
    function pixelResolution (latLng, zoom) {
        var radius = 6378137.0 // semi-axis of WGS84 ellipsoid
        var circumference =  2 * Math.PI * radius;
        var distancePerImage = circumference * Math.cos(latLng.lat * Math.PI / 180.0) / Math.pow(2,zoom);
        var distancePerPixel = distancePerImage / TILE_SIZE;
        return distancePerPixel
    }   

    // Web mercator projection.
    function project(latLng) {
        var siny = Math.sin(latLng.lat * Math.PI / 180);
        siny = Math.min(Math.max(siny, -0.9999), 0.9999);
        var xy = {};
        xy.x = TILE_SIZE * (0.5 + latLng.lng / 360);
        xy.y = TILE_SIZE * (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI));       
        return xy
    }

    // Marker pixel coordinates relative to the image Center 
    function distanceInPixels(centerProjected, markerProjected) {
        var delta = {};
        var spacing = pixelResolution(latLngCenter, zoom);
        delta.x = Math.round((centerProjected.x - markerProjected.x)/spacing);
        delta.y = Math.round((centerProjected.y - markerProjected.y)/spacing);
        return delta
    }   

    // Marker pixel coordinates relative to the Lower Left Corner
    function imageCoordinates(centerProjected, markerProjected) {
        var pixelCoordinates = {};
        var spacing = pixelResolution(latLngCenter, zoom);
        var deltaPixels = distanceInPixels(centerProjected, markerProjected);
        pixelCoordinates.x = TILE_SIZE / 2 - deltaPixels.x;
        pixelCoordinates.y = TILE_SIZE / 2 - deltaPixels.y;
        return pixelCoordinates
    }       
</script>    
</body>
</html>

Note: I can confirm that the pixelResolution function only works with square image tiles with dimensions of powers of 2. The Math.pow(2,zoom); snippet gives the game away!

Web Mercator function based on:

https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/map-coordinates

Horizontal distance represented by one pixel from :

https://wiki.openstreetmap.org/wiki/Zoom_levels

See also:

https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Resolution_and_Scale

LuisTavares
  • 2,146
  • 2
  • 12
  • 18
1

If you're going to linearly interpolate you'd need to know the lat/long & x/y for 2 points. It wouldn't be possible with only the center point unless you also have a conversion metric for pixels - ie. 50 pixels is .1 delta lat/long.

If you have the lat/long & x/y for two points you can create the ratio as y1 - y2 / lat1-lat2 or x1-x2/long1-long2 each of which should result in the same ratio

Then it'd be relatively easy, assume the ratio is 5 meaning 5px/l so you had a point that was (3,-4) away from that center point you'd simply multiple to find the pixel offset (15,-20) and add that to the center = (165, 80).

Since all of your images are zoomed the same amount you could manually calculate the ratio once and store it as a constant.

sudo/untested python:

def getRatio(latlongs=[(1,1),(0,0)], xys=[(5,5),(0,0)]:
  return (xys[0][1]-xys[1][1]) / (latlongs[0][0] - latlongs[1][0])

centerLatLong = (5,5)
centerXY = (150, 100)

def getCoord(lat,long,ratio):
  y = (lat-centerLatLong[0])*ratio + centerXY[1]
  x = (long-centerLatLong[1])*ratio + centerXY[0]
  return x, y
Schalton
  • 2,867
  • 2
  • 32
  • 44
  • 1
    The flaw with this answer is that the variations cannot be assumed to be "linear". That's not how Mercator projection works. Besides what is missing from the question is the image origin coordinates (for example, Upper Left Corner). See, https://stackoverflow.com/questions/14329691/convert-latitude-longitude-point-to-a-pixels-x-y-on-mercator-projection – LuisTavares Nov 17 '19 at 23:22
  • 1
    it's all relative to the center of the image -- if his projection requires offsets you could have a radial ratio that is variable from the center. Instead of the ratio being a constant it'd be a (again likely linear) function. You could compute the ratio function using linear regression from the center – Schalton Nov 17 '19 at 23:38
  • 1
    If, and only if, the distances are very, very small can we assume a Equirectangular projection. Even more, we know that the image requested from Mapbox is in Mercator. – LuisTavares Nov 18 '19 at 00:22
  • 1
    I didn't know that about Mapbox - this seem like a question for one of the other stack exchange sites more than stack overflow based on the domain specifics. Agreed -- the zoom level would be critical to assess viability. Given the question / limited requirements - I'm not sure that there is a better answer, perhaps the question needs to be updated/improved – Schalton Nov 18 '19 at 04:16
  • 1
    The only possibility I can think of is requesting standardized 256*256 tiles structured in quadtree (they need to be square) https://docs.mapbox.com/help/glossary/quadtree – LuisTavares Nov 18 '19 at 06:07