code: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.locationtech.spatial4j.shape.jts;
  
  import org.junit.Assert;
  import org.junit.Test;
  import org.locationtech.jts.geom.Coordinate;
  import org.locationtech.jts.geom.Geometry;
  import org.locationtech.jts.geom.GeometryFactory;
  import org.locationtech.jts.geom.Polygon;
  import org.locationtech.spatial4j.context.SpatialContext;
  import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
  import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
  import org.locationtech.spatial4j.distance.DistanceUtils;
  import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc;
  import org.locationtech.spatial4j.shape.Circle;
  import org.locationtech.spatial4j.shape.Point;
  import org.locationtech.spatial4j.shape.Shape;
  import org.locationtech.spatial4j.shape.impl.GeoCircle;
  import org.locationtech.spatial4j.shape.impl.PointImpl;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertTrue;
  
  public class JtsShapeFactoryTest {
  
    @Test
    public void testIndex() {
  
      JtsSpatialContextFactory ctxFactory = new JtsSpatialContextFactory();
      ctxFactory.autoIndex = true;
  
      Geometry g = ctxFactory.getGeometryFactory().createPoint(new Coordinate(0,0)).buffer(0.1);
  
      JtsSpatialContext ctx = ctxFactory.newSpatialContext();
  
      JtsGeometry jtsGeom1 = (JtsGeometry) ctx.getShapeFactory().makeShapeFromGeometry(g);
      assertTrue(jtsGeom1.isIndexed());
  
      JtsGeometry jtsGeom2 = ctx.getShapeFactory().makeShape(g);
      assertTrue(jtsGeom2.isIndexed());
    }
  
    @Test
    public void testEmptyPoint() {
      JtsSpatialContextFactory jtsCtxFactory = new JtsSpatialContextFactory();
      JtsSpatialContext jtsCtx = jtsCtxFactory.newSpatialContext();
      GeometryFactory geometryFactory = jtsCtxFactory.getGeometryFactory();
      final org.locationtech.jts.geom.Point  point = geometryFactory.createPoint();//empty
      final Shape shape = jtsCtx.getShapeFactory().makeShapeFromGeometry(point); // don't throw
      assertTrue(shape.isEmpty());
    }
  
    @Test
    public void testCircleGeometryConversions() {
      // Hawaii (Far West)
      circleGeometryConversionTest(-155.84, 19.74, 50);
      // Nunavat (Far North)
      circleGeometryConversionTest(-83.10, 70.30, 100);
      // Sydney (South East)
      circleGeometryConversionTest(151.21, 33.87, 1);
    }
  
    private void circleGeometryConversionTest(double x, double y, double radiusKm) {
      Point circleCenter = new PointImpl(x, y, SpatialContext.GEO);
      double radiusDeg = DistanceUtils.dist2Degrees(radiusKm, DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM);
      GeoCircle geoCircle = new GeoCircle(circleCenter, radiusDeg, SpatialContext.GEO);
  
      JtsShapeFactory shapeFactory = JtsSpatialContext.GEO.getShapeFactory();
      // Let's ensure the circle-to-polygon conversion is accurate accounting for geodesics.
      Geometry geometry = shapeFactory.getGeometryFrom(geoCircle);
      Assert.assertTrue(geometry instanceof Polygon);
      Polygon polygon = (Polygon) geometry;
      Coordinate[] coordinates = polygon.getExteriorRing().getCoordinates();
      int size = coordinates.length;
      Assert.assertTrue(size >= 100);
      GeodesicSphereDistCalc distCalc = new GeodesicSphereDistCalc.Haversine();
      double maxDeltaKm = radiusKm / 100; // allow 1% inaccuracy
      for (Coordinate coordinate : coordinates) {
        // Check distance from center of each point
        Point point = new PointImpl(coordinate.x, coordinate.y, SpatialContext.GEO);
        double distance = distCalc.distance(point, circleCenter);
        double distanceKm = DistanceUtils.degrees2Dist(distance, DistanceUtils.EARTH_MEAN_RADIUS_KM);
        assertEquals(String.format("Distance from point to center: %.2f km. Expected: %.2f km", distanceKm,
                radiusKm), radiusKm, distanceKm, maxDeltaKm);
      }
    }
  
    @Test
    public void testCircleDateLineWrapping() {
      // left wrapping (-180)
      circleGeometryConversionDateLineTest(-179.99, 51.22, 5);
      // right wrapping (+180)
      circleGeometryConversionDateLineTest(179.99, -35.6, 10);
    }
  
    private void circleGeometryConversionDateLineTest(double lon, double lat, double radiusKm) {
      JtsShapeFactory shapeFactory = JtsSpatialContext.GEO.getShapeFactory();
      Circle circle = shapeFactory.circle(lon, lat, DistanceUtils.dist2Degrees(radiusKm, DistanceUtils.EARTH_MEAN_RADIUS_KM));
      Geometry geom = shapeFactory.getGeometryFrom(circle);
  
      assertTrue(circle.getBoundingBox().getCrossesDateLine());
      assertEquals("MultiPolygon", geom.getGeometryType());
  
      GeodesicSphereDistCalc distCalc = new GeodesicSphereDistCalc.Haversine();
      double maxDeltaKm = radiusKm / 100; // allow 1% inaccuracy
      for (Coordinate c : geom.getCoordinates()) {
        // Check distance from center of each point
        Point point = new PointImpl(c.x, c.y, SpatialContext.GEO);
        double distance = distCalc.distance(point, lon, lat);
        double distanceKm = DistanceUtils.degrees2Dist(distance, DistanceUtils.EARTH_MEAN_RADIUS_KM);
        assertEquals(String.format("Distance from point to center: %.2f km. Expected: %.2f km", distanceKm,
                radiusKm), radiusKm, distanceKm, maxDeltaKm);
      }
    }
  }
  