Search code examples
javascriptgoogle-maps-api-3polygon

Google map Polygon: Set transparent for circles intersecting


I follow the post Change map opacity outside circle of Google Maps JavaScript API v3, but I have multi circles and sometimes they are intersecting. In the intersecting, they are not transparent. How I can set them transparent?

Map have multi circles

Code On jsfiddle.net

Thank you.

// This example uses the Google Maps JavaScript API's Data layer
// to create a rectangular polygon with 2 holes in it.
const places = [
  {
    center: { "lat": 1.3512492664554796, "lng": 103.85072488875811 },
    radius: 2000
  },
  {
    center: { "lat": 1.3225896205754812, "lng": 103.86600275130694 },
    radius: 2000
  },
  {
    center:{ "lat": 1.3136655910729036, "lng": 103.88866205306475 },
    radius: 2000
  },
  {
    center: { "lat": 1.305427997082587, "lng": 103.83613367171709 },
    radius: 2000
  }
];

let map;

const mapDefault = {
    
};

function initMap() {
    map = new google.maps.Map(document.getElementById('map'), mapDefault);
  const bounds = new google.maps.LatLngBounds();
  
  let pathsCircle = [];
  
  places.forEach(place => {
    bounds.extend(place.center);
    pathsCircle.push(drawCircle(place.center, place.radius, 1));
  });

  map.fitBounds(bounds);
  
  const worldCoords = [
    new google.maps.LatLng(-85.1054596961173, -180),
    new google.maps.LatLng(85.1054596961173, -180),
    new google.maps.LatLng(85.1054596961173, 180),
    new google.maps.LatLng(-85.1054596961173, 180),
    new google.maps.LatLng(-85.1054596961173, 0)
  ];
  
  new google.maps.Polygon({
    paths: [worldCoords, ...pathsCircle],
    strokeColor: '#fff',
    strokeOpacity: 0,
    strokeWeight: 0,
    fillColor: '#000',
    fillOpacity: 0.3,
    map,
  });
}

function drawCircle(point, radius, dir) {
}

That is my code.


Solution

  • To avoid the non-transparent intersections, the circles need to be combined into a single polygon.

    One option would be to use the JSTS library to compute the union of the circles.

    var geometryFactory = new jsts.geom.GeometryFactory();
    
    var poly1,poly2,polyunion;
    var polys = [];
    for (var i=0; i<pathsCircle.length; i++) {
       if (i==0) {
         poly1 = geometryFactory.createPolygon(geometryFactory.createLinearRing(array2JSTS(pathsCircle[i])));
         poly1.normalize();
         polys[i] = poly1;
    
       } else {
         poly2 = geometryFactory.createPolygon(geometryFactory.createLinearRing(array2JSTS(pathsCircle[i])));
         poly2.normalize();
         polys[i] = polys[i-1].union(poly2);
         polys[i].normalize();
       }
    }
    
      polys[i-1].normalize();
      var boundary = polys[i-1].getBoundary();
      var outputPath = jsts2googleMaps(boundary);
    
    var jsts2googleMaps = function (geometry) {
      var coordArray = geometry.getCoordinates();
      GMcoords = [];
      for (var i = 0; i < coordArray.length; i++) {
        GMcoords.push(new google.maps.LatLng(coordArray[i].x, coordArray[i].y));
      }
      return GMcoords;
    }
    var array2JSTS = function(boundaries) {
      var coordinates = [];
      for (var i = 0; i < boundaries.length; i++) {
        coordinates.push(new jsts.geom.Coordinate(
            boundaries[i].lat, boundaries[i].lng));
      }
      return coordinates;
    };
    

    proof of concept fiddle

    screenshot of resulting map

    code snippet:

    const places = [
      {
        center: { "lat": 1.3512492664554796, "lng": 103.85072488875811 },
        radius: 2000
      },
      {
        center: { "lat": 1.3225896205754812, "lng": 103.86600275130694 },
        radius: 2000
      },
      {
        center:{ "lat": 1.3136655910729036, "lng": 103.88866205306475 },
        radius: 2000
      },
      {
        center: { "lat": 1.305427997082587, "lng": 103.83613367171709 },
        radius: 2000
      }
    ];
    
    let map;
    
    const mapDefault = {
        center: places[0].center,
        zoom: 10,
        minZoom: 1,
        maxZoom: 19,
        clickableIcons: false
    };
    
    function initMap() {
        map = new google.maps.Map(document.getElementById('map'), mapDefault);
      const bounds = new google.maps.LatLngBounds();
      
      let pathsCircle = [];
      
      places.forEach(place => {
        bounds.extend(place.center);
        pathsCircle.push(drawCircle(place.center, place.radius, 1));
      });
    
      map.fitBounds(bounds);
      
      const worldCoords = [
        new google.maps.LatLng(-85.1054596961173, -180),
        new google.maps.LatLng(85.1054596961173, -180),
        new google.maps.LatLng(85.1054596961173, 180),
        new google.maps.LatLng(-85.1054596961173, 180),
        new google.maps.LatLng(-85.1054596961173, 0)
      ];
    var geometryFactory = new jsts.geom.GeometryFactory();
    
    var poly1,poly2,polyunion;
    var polys = [];
    for (var i=0; i<pathsCircle.length; i++) {
       if (i==0) {
         poly1 = geometryFactory.createPolygon(geometryFactory.createLinearRing(array2JSTS(pathsCircle[i])));
         poly1.normalize();
         polys[i] = poly1;
    
       } else {
         poly2 = geometryFactory.createPolygon(geometryFactory.createLinearRing(array2JSTS(pathsCircle[i])));
         poly2.normalize();
         polys[i] = polys[i-1].union(poly2);
         polys[i].normalize();
       }
    }
    
      polys[i-1].normalize();
      var boundary = polys[i-1].getBoundary();
      var outputPath = jsts2googleMaps(boundary);
    
    new google.maps.Polygon({
        paths: [worldCoords, outputPath],
        strokeColor: '#fff',
        strokeOpacity: 0,
        strokeWeight: 0,
        fillColor: '#000',
        fillOpacity: 0.3,
        map,
      });
    }
    
    function drawCircle(point, radius, dir) {
      let d2r = Math.PI / 180;   // degrees to radians 
      let r2d = 180 / Math.PI;   // radians to degrees 
      let earthsradius = 6378137; // 6378137 is the radius of the earth in metters
      let points = 128; // number of point to draw Circle
    
      // find the raidus in lat/lon 
      let rlat = (radius / earthsradius) * r2d;
      let rlng = rlat / Math.cos(point.lat * d2r);
    
      let extp = [];
    
      let start, end;
      if (dir == 1) {  // one extra here makes sure we connect the
        start = 0; end = points + 1
      } else {
        start = points + 1; end = 0
      }
      for (let i = start; (dir == 1 ? i < end : i > end); i = i + dir) {
        let theta = Math.PI * (i / (points / 2));
        const ey = point.lng + (rlng * Math.cos(theta)); // center a + radius x * cos(theta) 
        const ex = point.lat + (rlat * Math.sin(theta)); // center b + radius y * sin(theta) 
        extp.push({
          lat: ex,
          lng: ey,
        });
      }
      return extp;
    }
    var jsts2googleMaps = function (geometry) {
      var coordArray = geometry.getCoordinates();
      GMcoords = [];
      for (var i = 0; i < coordArray.length; i++) {
        GMcoords.push(new google.maps.LatLng(coordArray[i].x, coordArray[i].y));
      }
      return GMcoords;
    }
    var array2JSTS = function(boundaries) {
      var coordinates = [];
      for (var i = 0; i < boundaries.length; i++) {
        coordinates.push(new jsts.geom.Coordinate(
            boundaries[i].lat, boundaries[i].lng));
      }
      return coordinates;
    };
    /* Always set the map height explicitly to define the size of the div
           * element that contains the map. */
    #map {
      height: 100%;
    }
    
    /* Optional: Makes the sample page fill the window. */
    html,
    body {
      height: 100%;
      margin: 0;
      padding: 0;
    }
    <!DOCTYPE html>
    <html>
      <head>
        <title>Data Layer: Polygon</title>
        <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jsts.js"></script>
        <!-- jsFiddle will insert css and js -->
      </head>
      <body>
        <div id="map"></div>
    
        <!-- Async script executes immediately and must be after any DOM elements used in callback. -->
        <script
          src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&v=weekly&channel=2"
          async
        ></script>
      </body>
    </html>