javascriptleafletpolygon

Can I use leaflet to cut out an area from my map and blacken out the rest?


I use leaflet to show maps on my website. I want to blacken out everything EXCEPT for the area in the polygon I am showing. For instance, showing the rest of the map with a black background (maybe 90% black so you can faintly see the rest of the map).

This is what I am trying to get:

map showing polygon cut-out and the rest is blacked out

I found a similar question here but it's 10 years old and the first answer's jsfiddles come up blank, and the second answer refers a github repo not updated in 6 years and compatible only with version 1beta. Any suggestions that take the latest leaflet into account?

Here is my code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Leaflet Polygon in City of London</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <style>
        #map { height: 600px; }
    </style>
</head>
<body>

    <div id="map"></div>

    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>

        var map = L.map('map').setView([51.5074, -0.1278], 13); // City of London (latitude, longitude)


        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        }).addTo(map);


        var polygonCoords = [
            [51.5174, -0.1349], // top-left
            [51.5115, -0.1225], // top-right
            [51.5053, -0.1281], // bottom-right
            [51.5093, -0.1406], // bottom-left
            [51.5174, -0.1349]  // close the polygon (same as top-left)
        ];

        // This is an example of a polygon that should be cut out
        var polygon = L.polygon(polygonCoords, {
            color: 'blue',       // Outline color
            fillColor: 'cyan',   // Fill color
            fillOpacity: 0.5     // Fill opacity
        }).addTo(map);

    </script>
</body>
</html>

Solution

  • You can add a layer on top of the map and then create a hole in the shape of the polygon. Here's your code, updated with the additional changes:

    const map = L.map('map').setView([51.5074, -0.1278], 13); // City of London (latitude, longitude)
    
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);
    
    // Create the bounds of the map:
    const mapBounds = [
      [-90, -180], // Bottom-left
      [-90, 180],  // Bottom-right
      [90, 180],   // Top-right
      [90, -180],  // Top-left
      [-90, -180]  // Closing the loop back to Bottom-left
    ];
    
    const polygonCoords = [
        [51.5174, -0.1349], // top-left
        [51.5115, -0.1225], // top-right
        [51.5053, -0.1281], // bottom-right
        [51.5093, -0.1406], // bottom-left
        [51.5174, -0.1349]  // close the polygon (same as top-left)
    ];
    
    // Create a mask layer that will fill the map with a transparent black layer
    // and create a hole based on the polygon shape:  
    const mask = L.polygon([ // The outer rectangle
      ...mapBounds,
      ...polygonCoords.reverse() // The polygon as a hole
    ], {
      color: 'black',
      fillColor: 'black',
      fillOpacity: 0.25, // Adjust transparency
      stroke: false
    }).addTo(map);
    
    const polygon = L.polygon(polygonCoords, {
        stroke: false,
        fillOpacity: 0     // Fill opacity
    }).addTo(map);
    

    Note: the code above will produce the same result, even of the last polygon initialization is removed, since the masking works exactly as you want. You can keep the polygon if you want to supply extra styling to the polygon area, e.g. add borders, apply a color, etc.

    Here's a live demo that looks pretty similar to the screenshot that you've attached:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Leaflet Polygon in City of London</title>
        <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
        <style>
            #map { height: 600px; }
        </style>
    </head>
    <body>
    
        <div id="map"></div>
    
        <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
        <script>
    
            const map = L.map('map').setView([51.5074, -0.1278], 13); // City of London (latitude, longitude)
    
    
            L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
            }).addTo(map);
    
            // Create the bounds of the map:
            const mapBounds = [
              [-90, -180], // Bottom-left
              [-90, 180],  // Bottom-right
              [90, 180],   // Top-right
              [90, -180],  // Top-left
              [-90, -180]  // Closing the loop back to Bottom-left
            ];
    
            const polygonCoords = [
                [51.5174, -0.1349], // top-left
                [51.5115, -0.1225], // top-right
                [51.5053, -0.1281], // bottom-right
                [51.5093, -0.1406], // bottom-left
                [51.5174, -0.1349]  // close the polygon (same as top-left)
            ];
    
            // Create a mask layer that will fill the map with a transparent black layer
            // and create a hole based on the polygon shape:  
            const mask = L.polygon([ // The outer rectangle
              ...mapBounds,
              ...polygonCoords.reverse() // The polygon as a hole
            ], {
              color: 'black',
              fillColor: 'black',
              fillOpacity: 0.25, // Adjust transparency
              stroke: false
            }).addTo(map);
          
            const polygon = L.polygon(polygonCoords, {
                stroke: false,
                fillOpacity: 0     // Fill opacity
            }).addTo(map);
    
        </script>
    </body>
    </html>