Richard
Richard

Reputation: 65560

Mapbox GL JS: zoom to filtered polygon?

I am using Mapbox GL JS to display a polygon layer. I would to allow the user to choose a name from a dropdown, and then highlight and zoom to the matching polygon.

I already know how to highlight the matching polygon using map.setFilter, but I don't know how to zoom to the bounds of the matching polygon. This is my current code:

map.addLayer({
    'id': 'polygon_hover',
    'source': 'mysource',
    'source-layer': 'mylayer',
    'type': 'fill',
    'paint': {
        'fill-color': 'red',
        "fill-opacity": 0.6
    },
    "filter": ["==", 'CUSTNAME', ""]
});
// Get details from dropdown
custname.on("change", function(e) {
    // get details of name from select event
    map.setFilter('polygon_hover', ["==", 'CUSTNAME', name]);
    // Get bounds of filtered polygon somehow?
    // var bounds = ??;
    // map.fitBounds(bounds);
});

I have examined the Mapbox example of zooming to bounds, but it assumes that you already know what the bounds are.

Is there any way it's possible to get the bounds of the polygon matching a map filter in Mapbox?

Upvotes: 13

Views: 9215

Answers (5)

Jan
Jan

Reputation: 41

Finally solved it.

I have extended different answers from https://stackoverflow.com/a/35685551/10331580 with a recursive approach to be able to process all types of polygons and multipolygons. With the answer of tobias47n9e I got partly no results, because the different structures of the polygons were not addressed. I didn't want to import Leaflet or Turf!

function getPolygonBoundingBox(arr, bounds = [[], []]) {
  for (var i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i][0][0])){
      bounds = getPolygonBoundingBox(arr[i], bounds);
    } else {
      polygon = arr[i];
      for (var j = 0; j < polygon.length; j++) {
        longitude = polygon[j][0];
        latitude = polygon[j][1];
        bounds[0][0] = bounds[0][0] < longitude ? bounds[0][0] : longitude;
        bounds[1][0] = bounds[1][0] > longitude ? bounds[1][0] : longitude;
        bounds[0][1] = bounds[0][1] < latitude ? bounds[0][1] : latitude;
        bounds[1][1] = bounds[1][1] > latitude ? bounds[1][1] : latitude;
      }
    }
  }
  return bounds;
}


getPolygonBoundingBox(feature.geometry.coordinates);

Upvotes: 1

user
user

Reputation: 192

Here's a similar question with an answer https://stackoverflow.com/a/35685551/10331580

I ported the answer there over to typescript for myself.

getBoundingBox(feature: mapboxgl.MapboxGeoJSONFeature): mapboxgl.LngLatBoundsLike | undefined {
  let coordinates = (feature.geometry as any).coordinates[0];
  let bounds: {
    xMin?: number,
    xMax?: number,
    yMin?: number,
    yMax?: number
  } = {}

  for (let i = 0; i < coordinates.length; i++) {
    let longitude = coordinates[i][0];
    let latitude = coordinates[i][1];
    if (bounds.xMin && bounds.xMax && bounds.yMin && bounds.yMax) {
      bounds.xMin = bounds.xMin < longitude ? bounds.xMin : longitude
      bounds.xMax = bounds.xMax > longitude ? bounds.xMax : longitude;
      bounds.yMin = bounds.yMin < latitude ? bounds.yMin : latitude;
      bounds.yMax = bounds.yMax > latitude ? bounds.yMax : latitude;
    } else {
      bounds.xMin = longitude
      bounds.xMax = longitude
      bounds.yMin = latitude
      bounds.yMax = latitude
    }
  }
  if (bounds.xMin && bounds.xMax && bounds.yMin && bounds.yMax) {
    return new mapboxgl.LngLatBounds(
      new mapboxgl.LngLat(bounds.xMin, bounds.yMin),
      new mapboxgl.LngLat(bounds.xMax, bounds.yMax)
    )
  }
  return undefined;
}

fitToPolygon(feature: mapboxgl.MapboxGeoJSONFeature) {
  let bounds = this.getBoundingBox(feature)
  if (bounds) {
    this.map.fitBounds(bounds, {
      padding: 20
    })
  }
}

Upvotes: 0

Anna
Anna

Reputation: 21

I see that the question is still relevant - I solved it making a separate request to the database containing all points of a given polygon and building bounds [[minLng, minLat], [maxLng, maxLat]]. All attempts to address geometry of already rendered or source features didn't work for me - most probably because Mapbox doesn't keep initial geoJSON in the tiles.

Upvotes: 2

Nuno Ribeiro
Nuno Ribeiro

Reputation: 2565

I've the following code to fitBounds to Polygon center coords:

var coordinates = f.geometry.coordinates[0];
var bounds = coordinates.reduce(function (bounds, coord) {
    return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(coordinates[0], coordinates[0]));

map.fitBounds(bounds, {
    padding: 20
});

Where f is one Feature.

Upvotes: 7

Luis Estevez
Luis Estevez

Reputation: 1417

I found a solution to your problem. Leaflet has a polygon Class which takes an Array of polygon coordinates and has a function called getBounds() that returns south west and north east bounds. However, Leaflet doesn't follow the convention of LngLat, its format is LatLng. Therefore, you have to switch it. I took an example out from Mapbox Show drawn polygon area, and added exactly what you're looking for.

var polygon = data.features[0].geometry.coordinates;
var fit = new L.Polygon(polygon).getBounds();
var southWest = new mapboxgl.LngLat(fit['_southWest']['lat'], fit['_southWest']['lng']);
var northEast = new mapboxgl.LngLat(fit['_northEast']['lat'], fit['_northEast']['lng']);
var center = new mapboxgl.LngLatBounds(southWest, northEast).getCenter();
// map.flyTo({center: center, zoom: 10});
map.fitBounds(new mapboxgl.LngLatBounds(southWest, northEast));

Upvotes: 3

Related Questions