/******************************************************************************* * 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.context; import org.locationtech.spatial4j.distance.CartesianDistCalc; import org.locationtech.spatial4j.distance.DistanceCalculator; import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.io.BinaryCodec; import org.locationtech.spatial4j.io.LegacyShapeWriter; import org.locationtech.spatial4j.io.SupportedFormats; import org.locationtech.spatial4j.io.WKTReader; import org.locationtech.spatial4j.shape.*; import org.locationtech.spatial4j.shape.impl.RectangleImpl; import java.text.ParseException; import java.util.List; /** * This is a facade to most of Spatial4j, holding things like {@link DistanceCalculator}, * {@link ShapeFactory}, * {@link org.locationtech.spatial4j.io.ShapeIO}. * <p> * If you want a typical geodetic context, just reference {@link #GEO}. Otherwise, * You should either create and configure a {@link SpatialContextFactory} and then call * {@link SpatialContextFactory#newSpatialContext()}, OR, call * {@link org.locationtech.spatial4j.context.SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)} * to do this via configuration data. * <p> * Thread-safe & immutable. */ public class SpatialContext { /** A popular default SpatialContext implementation for geospatial. */ public static final SpatialContext GEO = new SpatialContext(new SpatialContextFactory()); //These are non-null private final boolean geo; private final ShapeFactory shapeFactory; private final DistanceCalculator calculator; private final Rectangle worldBounds; private final BinaryCodec binaryCodec; private final SupportedFormats formats; /** * Consider using {@link org.locationtech.spatial4j.context.SpatialContextFactory} instead. * * @param geo Establishes geo vs cartesian / Euclidean. * @param calculator Optional; defaults to haversine or cartesian depending on {@code geo}. * @param worldBounds Optional; defaults to GEO_WORLDBOUNDS or MAX_WORLDBOUNDS depending on units. */ @Deprecated public SpatialContext(boolean geo, DistanceCalculator calculator, Rectangle worldBounds) { this(initFromLegacyConstructor(geo, calculator, worldBounds)); } private static SpatialContextFactory initFromLegacyConstructor(boolean geo, DistanceCalculator calculator, Rectangle worldBounds) { SpatialContextFactory factory = new SpatialContextFactory(); factory.geo = geo; factory.distCalc = calculator; factory.worldBounds = worldBounds; return factory; } @Deprecated public SpatialContext(boolean geo) { this(initFromLegacyConstructor(geo, null, null)); } /** * Called by {@link org.locationtech.spatial4j.context.SpatialContextFactory#newSpatialContext()}. */ public SpatialContext(SpatialContextFactory factory) { this.geo = factory.geo; this.shapeFactory = factory.makeShapeFactory(this); if (factory.distCalc == null) { this.calculator = isGeo() ? new GeodesicSphereDistCalc.Haversine() : new CartesianDistCalc(); } else { this.calculator = factory.distCalc; } //TODO remove worldBounds from Spatial4j: see Issue #55 Rectangle bounds = factory.worldBounds; if (bounds == null) { this.worldBounds = isGeo() ? new RectangleImpl(-180, 180, -90, 90, this) : new RectangleImpl(-Double.MAX_VALUE, Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, this); } else { if (isGeo() && !bounds.equals(new RectangleImpl(-180, 180, -90, 90, this))) throw new IllegalArgumentException("for geo (lat/lon), bounds must be " + GEO.getWorldBounds()); if (bounds.getMinX() > bounds.getMaxX()) throw new IllegalArgumentException("worldBounds minX should be <= maxX: "+ bounds); if (bounds.getMinY() > bounds.getMaxY()) throw new IllegalArgumentException("worldBounds minY should be <= maxY: "+ bounds); //hopefully worldBounds' rect implementation is compatible this.worldBounds = new RectangleImpl(bounds, this); } this.binaryCodec = factory.makeBinaryCodec(this); factory.checkDefaultFormats(); this.formats = factory.makeFormats(this); } /** A factory for {@link Shape}s. */ public ShapeFactory getShapeFactory() { return shapeFactory; } public SupportedFormats getFormats() { return formats; } public DistanceCalculator getDistCalc() { return calculator; } /** Convenience that uses {@link #getDistCalc()} */ public double calcDistance(Point p, double x2, double y2) { return getDistCalc().distance(p, x2, y2); } /** Convenience that uses {@link #getDistCalc()} */ public double calcDistance(Point p, Point p2) { return getDistCalc().distance(p, p2); } /** * The extent of x & y coordinates should fit within the return'ed rectangle. * Do *NOT* invoke reset() on this return type. */ public Rectangle getWorldBounds() { return worldBounds; } /** If true then {@link #normX(double)} will wrap longitudes outside of the standard * geodetic boundary into it. Example: 181 will become -179. */ @Deprecated public boolean isNormWrapLongitude() { return shapeFactory.isNormWrapLongitude(); } /** Is the mathematical world model based on a sphere, or is it a flat plane? The word * "geodetic" or "geodesic" is sometimes used to refer to the former, and the latter is sometimes * referred to as "Euclidean" or "cartesian". */ public boolean isGeo() { return geo; } /** Normalize the 'x' dimension. Might reduce precision or wrap it to be within the bounds. This * is called by {@link org.locationtech.spatial4j.io.WKTReader} before creating a shape. */ @Deprecated public double normX(double x) { return shapeFactory.normX(x); } /** Normalize the 'y' dimension. Might reduce precision or wrap it to be within the bounds. This * is called by {@link org.locationtech.spatial4j.io.WKTReader} before creating a shape. */ @Deprecated public double normY(double y) { return shapeFactory.normY(y); } /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that * gets an 'x' dimension. */ @Deprecated public void verifyX(double x) { shapeFactory.verifyX(x); } /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that * gets a 'y' dimension. */ @Deprecated public void verifyY(double y) { shapeFactory.verifyY(y); } /** Construct a point. */ @Deprecated public Point makePoint(double x, double y) { return shapeFactory.pointXY(x, y); } /** Construct a rectangle. */ @Deprecated public Rectangle makeRectangle(Point lowerLeft, Point upperRight) { return shapeFactory.rect(lowerLeft, upperRight); } /** * Construct a rectangle. If just one longitude is on the dateline (+/- 180) * then potentially adjust its sign to ensure the rectangle does not cross the * dateline. */ @Deprecated public Rectangle makeRectangle(double minX, double maxX, double minY, double maxY) { return shapeFactory.rect(minX, maxX, minY, maxY); } /** Construct a circle. The units of "distance" should be the same as x & y. */ @Deprecated public Circle makeCircle(double x, double y, double distance) { return shapeFactory.circle(x, y, distance); } /** Construct a circle. The units of "distance" should be the same as x & y. */ @Deprecated public Circle makeCircle(Point point, double distance) { return shapeFactory.circle(point, distance); } /** Constructs a line string. It's an ordered sequence of connected vertexes. There * is no official shape/interface for it yet so we just return Shape. */ @Deprecated public Shape makeLineString(List<Point> points) { return shapeFactory.lineString(points, 0); } /** Constructs a buffered line string. It's an ordered sequence of connected vertexes, * with a buffer distance along the line in all directions. There * is no official shape/interface for it so we just return Shape. */ @Deprecated public Shape makeBufferedLineString(List<Point> points, double buf) { return shapeFactory.lineString(points, buf); } /** Construct a ShapeCollection, analogous to an OGC GeometryCollection. */ @Deprecated public <S extends Shape> ShapeCollection<S> makeCollection(List<S> coll) { return shapeFactory.multiShape(coll); } /** The {@link org.locationtech.spatial4j.io.WKTReader} used by {@link #readShapeFromWkt(String)}. */ @Deprecated public WKTReader getWktShapeParser() { return (WKTReader)formats.getWktReader(); } /** Reads a shape from the string formatted in WKT. * @see org.locationtech.spatial4j.io.WKTReader * @param wkt non-null WKT. * @return non-null * @throws ParseException if it failed to parse. */ @Deprecated public Shape readShapeFromWkt(String wkt) throws ParseException, InvalidShapeException { return getWktShapeParser().parse(wkt); } public BinaryCodec getBinaryCodec() { return binaryCodec; } /** * Try to read a shape from any supported formats * * @param value * @return shape or null if unable to parse any shape * @throws InvalidShapeException */ @Deprecated public Shape readShape(String value) throws InvalidShapeException { return formats.read(value); } /** Writes the shape to a String using the old/deprecated * {@link org.locationtech.spatial4j.io.LegacyShapeWriter}. The JTS based subclass will write it * to WKT if the legacy format doesn't support that shape. * <b>Spatial4j in the near future won't support writing shapes to strings.</b> * @param shape non-null * @return non-null */ @Deprecated public String toString(Shape shape) { return LegacyShapeWriter.writeShape(shape); } @Override public String toString() { if (this.equals(GEO)) { return GEO.getClass().getSimpleName()+".GEO"; } else { return getClass().getSimpleName()+"{" + "geo=" + geo + ", calculator=" + calculator + ", worldBounds=" + worldBounds + '}'; } } }