BBoxCalculatorTest.java

/*******************************************************************************
 * Copyright (c) 2015 David Smiley
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License, Version 2.0 which
 * accompanies this distribution and is available at
 *    http://www.apache.org/licenses/LICENSE-2.0.txt
 ******************************************************************************/

package org.locationtech.spatial4j.shape.impl;

import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.RandomizedShapeTest;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.SpatialRelation;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class BBoxCalculatorTest extends RandomizedShapeTest {

  public BBoxCalculatorTest() {
    super(SpatialContext.GEO);
  }

  // note: testing latitude would be so simple that's effectively the same code as the code to be tested. So I don't.


  @Test @Repeat(iterations = 100)
  public void testGeoLongitude() {
    BBoxCalculator calc = new BBoxCalculator(ctx);
    final int numShapes = randomIntBetween(1, 4);//inclusive
    List<Rectangle> rects = new ArrayList<>(numShapes);
    for (int i = 0; i < numShapes; i++) {
      Rectangle rect = randomRectangle(30);// divisible by
      rects.add(rect);
      calc.expandRange(rect);
    }
    Rectangle boundary = calc.getBoundary();
    if (numShapes == 1) {
      assertEquals(rects.get(0), boundary);
      return;
    }

    // If the boundary is the world-bounds, check that it's right.
    if (boundary.getMinX() == -180 && boundary.getMaxX() == 180) {
      // each longitude should be present in at least one shape:
      for (int lon = -180; lon <= +180; lon++) {
        assertTrue(atLeastOneRectHasLon(rects, lon));
      }
      return;
    }

    // Test that it contains all shapes:
    for (Rectangle rect : rects) {
      assertRelation(SpatialRelation.CONTAINS, boundary, rect);
    }

    // Test that the left & right are boundaries:
    assertTrue(atLeastOneRectHasLon(rects, boundary.getMinX()));
    assertFalse(atLeastOneRectHasLon(rects, normX(boundary.getMinX() - 0.5)));

    assertTrue(atLeastOneRectHasLon(rects, boundary.getMaxX()));
    assertFalse(atLeastOneRectHasLon(rects, normX(boundary.getMaxX() + 0.5)));

    // Test that this is the smallest enclosing boundary by ensuring the gap (opposite the bbox) is
    //  the largest:
    if (boundary.getWidth() > 180) { // conversely if wider than 180 then no wider gap is possible
      double biggerGap = 360.0 - boundary.getWidth() + 0.5;
      for (Rectangle rect : rects) {
        // try to see if a bigger gap could lie to the right of this rect
        double gapRectLeft = rect.getMaxX() + 0.25;
        double gapRectRight = gapRectLeft + biggerGap;
        Rectangle testGap = makeNormRect(gapRectLeft, gapRectRight, -90, 90);
        boolean fits = true;
        for (Rectangle rect2 : rects) {
          if (rect2.relate(testGap).intersects()) {
            fits = false;
            break;
          }
        }
        assertFalse(fits);//should never fit because it's larger than the biggest gap
      }
    }
  }

  private boolean atLeastOneRectHasLon(List<Rectangle> rects, double lon) {
    for (Rectangle rect : rects) {
      if (rect.relateXRange(lon, lon).intersects()) {
        return true;
      }
    }
    return false;
  }

}