Mask mapbox-gl map with arbitrary polygon
Asked Answered
W

1

8

I am using mapbox-gl and downloaded vector tiles from osm2vectortiles.org

I'd like to have the map visible only within an known polygon, and cannot find any way to pull this off.

I can imagine a couple ways to approach the problem, and every path has lead me to no answers. Here are some approaches I've tried to research:

  1. Some kind of map-masking layer. (Concept doesn't seem to exist)
  2. Draw the polygon and fill outside it instead of inside it. (Seems you can only fill inside polygons)
  3. Draw a filled box around the outer bounds of the map, and cutout the polygon. (MultiPolygon Features seem to stack instead of cutout)
  4. Modify the mbtiles to pretend everything outside the polygon is water. (https://github.com/mapbox/mbtiles-cutout seemed promising, but I can't seem to get it to work)

What's the right way to approach this problem?

Wendalyn answered 23/11, 2016 at 19:32 Comment(0)
S
10

All of the options you've mentioned could work. You can cut the original vector data to fit the mask region(as in option 4) or you can draw a mask layer on top of your data(option 3).

Here is how you can do it with a mask layer:

https://jsfiddle.net/kmandov/cr2rav7v/

enter image description here

Add your mask as a fill layer:

 map.addSource('mask', {
    "type": "geojson",
    "data": polyMask(mask, bounds)
  });

  map.addLayer({
    "id": "zmask",
    "source": "mask",
    "type": "fill",
    "paint": {
      "fill-color": "white",
      'fill-opacity': 0.999
    }
  });

You can create your mask in advance (say with QGis, or turf) or you can cut the mask directly in the browser using turf:

function polyMask(mask, bounds) {
  var bboxPoly = turf.bboxPolygon(bounds);
  return turf.difference(bboxPoly, mask);
}

var bounds = [-122.5336, 37.7049, -122.3122, 37.8398]; // wsen 

var mask = turf.polygon([
  [
    [-122.43764877319336,
      37.78645343442073
    ],
    [-122.40056991577148,
      37.78930232286027
    ],
    [-122.39172935485838,
      37.76630458915842
    ],
    [-122.43550300598145,
      37.75646561597495
    ],
    [-122.45378494262697,
      37.7781096293495
    ],
    [-122.43764877319336,
      37.78645343442073
    ]
  ]
]);

Probably, you would like to prevent the user from going outside of the mask boundaries by providing maxBounds:

var map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v9',
  center: [-122.42116928100586, 37.77532815168286],
  maxBounds: bounds,
  zoom: 12
});

Note that there seems to be a bug and you need to set fill-opacity to something like 0.999

Staphylo answered 24/11, 2016 at 12:28 Comment(3)
Thank you, thank you! I think the key I was missing was turf.difference() to invert the selected space. I really appreciate the completeness of your answer. You Rock!Wendalyn
@Staphylo I have quite a similar use case but very little experience with turf and mapbox, and was wondering whether you'b be able to help, at least pointing to proper vocabulary to google for what's needed. I want to cover the whole world in very small hexes (~50m side width), so probably need to pregenerate them somehow, but how?. Then, if zoom level is high enough, I'd highlight a hex if given coorrdinates are within its bounds. The most problematic thing I can't google out, is how to create an unchanging hex grid without having to draw all of them on the map (which will run out of memory)Vallejo
@maciej-gurban There are many ways you can approach this problem... You can pre-generate the tiles or you can calculate the Hex grid on the fly. You can use raster tiles or vector tiles. It really depends on the use case. These hex grids are often refered as "fishnets" and hexagon binning. Turf has a utility method to generate hex grids. You might probably want to look into deck.gl/kepler.gl as well. Cheers. KiroStaphylo

© 2022 - 2024 — McMap. All rights reserved.