/******************************************************************************* * 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 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 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); } } }