mapbox/geo-viewport

geoViewport.bounds() returns bounds for wrong zoom level

timiyay opened this issue · 4 comments

version: 0.1.1

I am using geoViewport.bounds() to compute the bounding box of an image from the Mapbox Static Image API.

However, the bounding box returned by geoViewport.bounds() is much larger than the actual geographic extent of the Static Map API image.

var zoom = 17;
var imageWidth = 1080;
var imageHeight = 350;
var mapboxPublicKey = 'foo';

// Full JSON not shown - it's a FeatureCollection, containing a single polygon feature
var geojsonFeature = {...FeatureCollection..}.features[0];


var centroid = d3.geo.centroid(geojsonFeature);

// Compute the bounding box based on zoom, centroid, and image dimensions
var imageBbox = geoViewport.bounds(centroid, zoom, [imageWidth, imageHeight]);
var imageBboxFeature = turf.bboxPolygon(imageBbox);

// Construct the Mapbox Static Image API URL, using the same parameters
var staticImageUrl = `https://api.mapbox.com/styles/v1/my-account/cikn8jw2f00fxbgm1tjbpdwxl/static/${centroid[0]},${centroid[1]},${zoom},0.00,0.00/${imageWidth}x${imageHeight}?access_token=${mapboxPublicKey}`

// ...more code that renders the image and geojsonFeature

The image bounding box created by the above scenario should cover the following geographic extent (the static map API returns an image with the same extent):
expected_bbox_extent

Instead, the bounding box computed from geoViewport.bounds() covers a larger extent:
actual_bbox_extent

{
    "type": "Feature",
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [
                    144.9606192111969,
                    -37.83434424376551
                ],
                [
                    144.97220635414124,
                    -37.83434424376551
                ],
                [
                    144.97220635414124,
                    -37.83137845901042
                ],
                [
                    144.9606192111969,
                    -37.83137845901042
                ],
                [
                    144.9606192111969,
                    -37.83434424376551
                ]
            ]
        ]
    },
    "properties": {}
}

Use Case

I am building a D3 map, which will load a background image from the Mapbox Static Image API, and overlay it with GeoJSON. To do this, I need to compute the bounding box of the static image, based on the parameters I used in the API request (centre point, zoom, image width, image height). Once I have this bounding box, I use it to scale and translate the d3 mercator projection, so the GeoJSON features align with the background map image.

I'm able to get a correct results if a pass zoom + 1 into geoViewport.bounds().

Since I'm requesting a static image tile at zoom 17, then my function call becomes:

var imageWidth = 1080;
var imageHeight = 350;
var centroid = [144.96641657219595, -37.832862737349124];
var zoom = 17;

// Get the bounds for zoom 18, because it actually returns to bounds for zoom 17
var imageBbox = geoViewport.bounds(centroid, zoom + 1, [imageWidth, imageHeight]);

Looking at the source code for geo-viewport, that zoom parameter is being passed straight to the px() function from the sphericalmercator library (https://github.com/mapbox/node-sphericalmercator). Upstream bug or user error/misunderstanding?

I am also raising this issue in the node-sphericalmercator repo, as I try to flush it out.

OK, this blog post explains a lot: https://www.mapbox.com/blog/512px-map-tile

I now think the issue isn't a bug, as such, but a lack of support for tile sizes other than 256x256. Which means, a lack of support for using a vector map zoom level to calculate the bounds for Mapbox Static Image API images.

I'll revisit this and suggest a fix, which will likely involve passing a tile size to node-sphericalmercator.

Fixed by #4