Google Maps Api V3 - Hide overlapping custom overlays

311 Views Asked by At

I have placed many custom overlays for different cities on a map and am trying to the hide/collapse the overlapping ones. Wherever there is space, I would like to show/expand the ones with the highest population.

I would like to achieve the exact same behavior as google flights (explore section).

What I tried

I have tried the following. I cycle through all custom overlays, check if there is overlap and then hide if there is one with higher population. The problem is that too many get hidden, see the following example:

  • City A (3000 population)
  • City B (2000 population)
  • City C (1000 population)
  • A and B overlap
  • B and C overlap
  • A and C do not overlap

Both A and C should be visible, but C is hidden because B and C overlap and B has a higher population.

Please note that I am using Angular, but this is a general js question.

hideOverlays(){

  this.overlays.forEach(mainElement => {
    let hide = false;

    this.overlays.forEach(compareElement => {
      if(this.checkOverlap(mainElement.nativeElement, compareElement.nativeElement)){
        if(mainElement.nativeElement.dataset.population < compareElement.nativeElement.dataset.population){
          hide = true
        }
      }
    })

    if(hide){
      mainElement.nativeElement.style.display = "none";
    }else{
      mainElement.nativeElement.style.display = "flex";
    }
  });
}

checkOverlap(element1, element2) {
  const rect1 = element1.getBoundingClientRect();
  const rect2 = element2.getBoundingClientRect();

  const overlap = !(rect1.right < rect2.left || 
                    rect1.left > rect2.right || 
                    rect1.bottom < rect2.top || 
                    rect1.top > rect2.bottom)
  return overlap;
}

Does anyone know how to do this properly? I am also open to a completely different approach if the direction that I am heading is not efficient.

1

There are 1 best solutions below

0
On BEST ANSWER

I found a solution. Not sure if it is the best way to do this, but it works.

I first create an array of objects with the overlay element, population and hidden=false. Then I cycle through and compare each element with all other elements. If the elements are overlapping and main.hidden == true and main.population >= compare.population, I set the compare element to hidden == true. In this way the hidden elements do not cause other elements to hide.

Note that my original array is already sorted by population, this order is important for this to work in the right way.

Finally I cycle through the elements again and add classes and styles to the elements based on if they are hidden or not.

filterLocations(){
  let overlayArray = [];

  this.overlays.forEach(element => {
    const overlayObj = {element: element.nativeElement, population: Number(element.nativeElement.dataset.population), hidden: false};
    overlayArray.push(overlayObj);
  })

  overlayArray.forEach( (main, i) => {
    overlayArray.forEach( (compare, j) => {
      if(i != j && !main.hidden && main.population >= compare.population && this.checkOverlap(compare.element, main.element)){
        overlayArray[j].hidden = true;
      }
    })
  })

  overlayArray.forEach( (obj, i) => {
    if(!obj.hidden){
      obj.element.classList.add("expanded");
      obj.element.style.pointerEvents = 'auto';
      obj.element.style.zIndex = overlayArray.length + overlayArray.length - i;
    }else{
      obj.element.classList.remove("expanded");
      obj.element.style.pointerEvents = 'none'; 
      obj.element.style.zIndex = overlayArray.length - i;
    }
  })
}

checkOverlap(element1, element2) {
  const rect1 = element1.getBoundingClientRect();
  const rect2 = element2.getBoundingClientRect();

  return !(rect1.right < rect2.left || 
           rect1.left > rect2.right || 
           rect1.bottom < rect2.top || 
           rect1.top > rect2.bottom)
}