code: /*******************************************************************************
   * Copyright (c) 2015 Voyager Search and MITRE
   * 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 org.locationtech.spatial4j.context.SpatialContext;
  import org.locationtech.spatial4j.context.SpatialContextFactory;
  import org.locationtech.spatial4j.distance.DistanceUtils;
  import org.locationtech.spatial4j.exception.InvalidShapeException;
  import org.locationtech.spatial4j.shape.*;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /** The default {@link org.locationtech.spatial4j.shape.ShapeFactory}.  It does not support polygon shapes. */
  public class ShapeFactoryImpl implements ShapeFactory {
  
    protected final SpatialContext ctx;
  
    private final boolean normWrapLongitude;
  
    public ShapeFactoryImpl(SpatialContext ctx, SpatialContextFactory factory) {
      this.ctx = ctx;
      this.normWrapLongitude = ctx.isGeo() && factory.normWrapLongitude;
    }
  
    @Override
    public SpatialContext getSpatialContext() {
      return ctx;
    }
  
    @Override
    public boolean isNormWrapLongitude() {
      return normWrapLongitude;
    }
  
    @Override
    public double normX(double x) {
      if (normWrapLongitude)
        x = DistanceUtils.normLonDEG(x);
      return x;
    }
  
    @Override
    public double normY(double y) { return y; }
  
    @Override
    public double normZ(double z) { return z; }
  
    @Override
    public double normDist(double d) {
      return d;
    }
  
    @Override
    public void verifyX(double x) {
      Rectangle bounds = ctx.getWorldBounds();
      if (x < bounds.getMinX() || x > bounds.getMaxX())//NaN will pass
        throw new InvalidShapeException("Bad X value "+x+" is not in boundary "+bounds);
    }
  
    @Override
    public void verifyY(double y) {
      Rectangle bounds = ctx.getWorldBounds();
      if (y < bounds.getMinY() || y > bounds.getMaxY())//NaN will pass
        throw new InvalidShapeException("Bad Y value "+y+" is not in boundary "+bounds);
    }
  
    @Override
    public void verifyZ(double z) { // bounds has no 'z' for this simple shapeFactory
    }
  
    @Override
    public Point pointXY(double x, double y) {
      verifyX(x);
      verifyY(y);
      return new PointImpl(x, y, ctx);
    }
  
    @Override
    public Point pointXYZ(double x, double y, double z) {
      return pointXY(x, y); // or throw?
    }
  
    @Override
    public Rectangle rect(Point lowerLeft, Point upperRight) {
      return rect(lowerLeft.getX(), upperRight.getX(),
              lowerLeft.getY(), upperRight.getY());
    }
  
    @Override
    public Rectangle rect(double minX, double maxX, double minY, double maxY) {
      Rectangle bounds = ctx.getWorldBounds();
      // Y
      if (minY < bounds.getMinY() || maxY > bounds.getMaxY())//NaN will pass
        throw new InvalidShapeException("Y values ["+minY+" to "+maxY+"] not in boundary "+bounds);
      if (minY > maxY)
        throw new InvalidShapeException("maxY must be >= minY: " + minY + " to " + maxY);
      // X
      if (ctx.isGeo()) {
        verifyX(minX);
        verifyX(maxX);
        //TODO consider removing this logic so that there is no normalization here
        //if (minX != maxX) {   USUALLY TRUE, inline check below
        //If an edge coincides with the dateline then don't make this rect cross it
        if (minX == 180 && minX != maxX) {
          minX = -180;
        } else if (maxX == -180 && minX != maxX) {
          maxX = 180;
        }
        //}
      } else {
        if (minX < bounds.getMinX() || maxX > bounds.getMaxX())//NaN will pass
          throw new InvalidShapeException("X values ["+minX+" to "+maxX+"] not in boundary "+bounds);
        if (minX > maxX)
          throw new InvalidShapeException("maxX must be >= minX: " + minX + " to " + maxX);
      }
      return new RectangleImpl(minX, maxX, minY, maxY, ctx);
    }
  
    @Override
    public Circle circle(double x, double y, double distance) {
      return circle(pointXY(x, y), distance);
    }
  
    @Override
    public Circle circle(Point point, double distance) {
      if (distance < 0)
        throw new InvalidShapeException("distance must be >= 0; got " + distance);
      if (ctx.isGeo()) {
        if (distance > 180) {
          // (it's debatable whether to error or not)
          //throw new InvalidShapeException("distance must be <= 180; got " + distance);
          distance = 180;
        }
        return new GeoCircle(point, distance, ctx);
      } else {
        return new CircleImpl(point, distance, ctx);
      }
    }
  
    @Override
    public Shape lineString(List<Point> points, double buf) {
      return new BufferedLineString(points, buf, ctx.isGeo(), ctx);
    }
  
    @Override
    public LineStringBuilder lineString() {
      return new LineStringBuilder() {
        final List<Point> points = new ArrayList<>();
        double bufferDistance = 0;
  
        @Override
        public LineStringBuilder buffer(double distance) {
          this.bufferDistance = distance;
          return this;
        }
  
        @Override
        public LineStringBuilder pointXY(double x, double y) {
          points.add(ShapeFactoryImpl.this.pointXY(x, y));
          return this;
        }
  
        @Override
        public LineStringBuilder pointXYZ(double x, double y, double z) {
          points.add(ShapeFactoryImpl.this.pointXYZ(x, y, z));
          return this;
        }
  
        @Override
        public LineStringBuilder pointLatLon(double latitude, double longitude) {
          points.add(ShapeFactoryImpl.this.pointLatLon(latitude, longitude));
          return this;
        }
  
        @Override
        public Shape build() {
          return new BufferedLineString(points, bufferDistance, false, ctx);
        }
      };
    }
  
    @Override
    public <S extends Shape> ShapeCollection<S> multiShape(List<S> coll) {
      return new ShapeCollection<>(coll, ctx);
    }
  
    @Override
    public <T extends Shape> MultiShapeBuilder<T> multiShape(Class<T> shapeClass) {
      return new GeneralShapeMultiShapeBuilder<>();
    }
  
    @Override
    public MultiPointBuilder multiPoint() {
      return new GeneralShapeMultiShapeBuilder<>();
    }
  
    @Override
    public MultiLineStringBuilder multiLineString() {
      return new GeneralShapeMultiShapeBuilder<>();
    }
  
    @Override
    public MultiPolygonBuilder multiPolygon() {
      return new GeneralShapeMultiShapeBuilder<>();
    }
  
    @Override
    public PolygonBuilder polygon() {
      throw new UnsupportedOperationException("Unsupported shape of this SpatialContext. Try JTS or Geo3D.");
    }
  
    protected class GeneralShapeMultiShapeBuilder<T extends Shape> implements MultiShapeBuilder<T>,
        MultiPointBuilder, MultiLineStringBuilder, MultiPolygonBuilder {
      protected List<Shape> shapes = new ArrayList<>();
  
      @Override
      public MultiShapeBuilder<T> add(T shape) {
        shapes.add(shape);
        return this;
      }
  
      @Override
      public MultiPointBuilder pointXY(double x, double y) {
        shapes.add(ShapeFactoryImpl.this.pointXY(x, y));
        return this;
      }
  
      @Override
      public MultiPointBuilder pointXYZ(double x, double y, double z) {
        shapes.add(ShapeFactoryImpl.this.pointXYZ(x, y, z));
        return this;
      }
  
      @Override
      public MultiPointBuilder pointLatLon(double latitude, double longitude) {
        shapes.add(ShapeFactoryImpl.this.pointLatLon(latitude, longitude));
        return this;
      }
  
      @Override
      public LineStringBuilder lineString() {
        return ShapeFactoryImpl.this.lineString();
      }
  
      @Override
      public MultiLineStringBuilder add(LineStringBuilder lineStringBuilder) {
        shapes.add(lineStringBuilder.build());
        return this;
      }
  
      @Override
      public PolygonBuilder polygon() {
        return ShapeFactoryImpl.this.polygon();
      }
  
      @Override
      public MultiPolygonBuilder add(PolygonBuilder polygonBuilder) {
        shapes.add(polygonBuilder.build());
        return this;
      }
  
      @Override
      public Shape build() {
        return new ShapeCollection<>(shapes, ctx);
      }
    }
  }
  