View Javadoc
1   /*******************************************************************************
2    * Copyright (c) 2015 Voyager Search and MITRE
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Apache License, Version 2.0 which
5    * accompanies this distribution and is available at
6    *    http://www.apache.org/licenses/LICENSE-2.0.txt
7    ******************************************************************************/
8   
9   package org.locationtech.spatial4j.context;
10  
11  import org.locationtech.spatial4j.distance.CartesianDistCalc;
12  import org.locationtech.spatial4j.distance.DistanceCalculator;
13  import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc;
14  import org.locationtech.spatial4j.exception.InvalidShapeException;
15  import org.locationtech.spatial4j.io.BinaryCodec;
16  import org.locationtech.spatial4j.io.LegacyShapeWriter;
17  import org.locationtech.spatial4j.io.SupportedFormats;
18  import org.locationtech.spatial4j.io.WKTReader;
19  import org.locationtech.spatial4j.shape.*;
20  import org.locationtech.spatial4j.shape.impl.RectangleImpl;
21  
22  import java.text.ParseException;
23  import java.util.List;
24  
25  /**
26   * This is a facade to most of Spatial4j, holding things like {@link DistanceCalculator},
27   * {@link ShapeFactory},
28   * {@link org.locationtech.spatial4j.io.ShapeIO}.
29   * <p>
30   * If you want a typical geodetic context, just reference {@link #GEO}.  Otherwise,
31   * You should either create and configure a {@link SpatialContextFactory} and then call
32   * {@link SpatialContextFactory#newSpatialContext()}, OR, call
33   * {@link org.locationtech.spatial4j.context.SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)}
34   * to do this via configuration data.
35   * <p>
36   * Thread-safe &amp; immutable.
37   */
38  public class SpatialContext {
39  
40    /** A popular default SpatialContext implementation for geospatial. */
41    public static final SpatialContext GEO = new SpatialContext(new SpatialContextFactory());
42  
43    //These are non-null
44    private final boolean geo;
45    private final ShapeFactory shapeFactory;
46    private final DistanceCalculator calculator;
47    private final Rectangle worldBounds;
48    private final BinaryCodec binaryCodec;
49    private final SupportedFormats formats;
50  
51    /**
52     * Consider using {@link org.locationtech.spatial4j.context.SpatialContextFactory} instead.
53     *
54     * @param geo Establishes geo vs cartesian / Euclidean.
55     * @param calculator Optional; defaults to haversine or cartesian depending on {@code geo}.
56     * @param worldBounds Optional; defaults to GEO_WORLDBOUNDS or MAX_WORLDBOUNDS depending on units.
57     */
58    @Deprecated
59    public SpatialContext(boolean geo, DistanceCalculator calculator, Rectangle worldBounds) {
60      this(initFromLegacyConstructor(geo, calculator, worldBounds));
61    }
62  
63    private static SpatialContextFactory initFromLegacyConstructor(boolean geo,
64                                                                   DistanceCalculator calculator,
65                                                                   Rectangle worldBounds) {
66      SpatialContextFactory factory = new SpatialContextFactory();
67      factory.geo = geo;
68      factory.distCalc = calculator;
69      factory.worldBounds = worldBounds;
70      return factory;
71    }
72  
73    @Deprecated
74    public SpatialContext(boolean geo) {
75      this(initFromLegacyConstructor(geo, null, null));
76    }
77  
78    /**
79     * Called by {@link org.locationtech.spatial4j.context.SpatialContextFactory#newSpatialContext()}.
80     */
81    public SpatialContext(SpatialContextFactory factory) {
82      this.geo = factory.geo;
83  
84      this.shapeFactory = factory.makeShapeFactory(this);
85  
86      if (factory.distCalc == null) {
87        this.calculator = isGeo()
88                ? new GeodesicSphereDistCalc.Haversine()
89                : new CartesianDistCalc();
90      } else {
91        this.calculator = factory.distCalc;
92      }
93  
94      //TODO remove worldBounds from Spatial4j: see Issue #55
95      Rectangle bounds = factory.worldBounds;
96      if (bounds == null) {
97        this.worldBounds = isGeo()
98                ? new RectangleImpl(-180, 180, -90, 90, this)
99                : new RectangleImpl(-Double.MAX_VALUE, Double.MAX_VALUE,
100               -Double.MAX_VALUE, Double.MAX_VALUE, this);
101     } else {
102       if (isGeo() && !bounds.equals(new RectangleImpl(-180, 180, -90, 90, this)))
103         throw new IllegalArgumentException("for geo (lat/lon), bounds must be " + GEO.getWorldBounds());
104       if (bounds.getMinX() > bounds.getMaxX())
105         throw new IllegalArgumentException("worldBounds minX should be <= maxX: "+ bounds);
106       if (bounds.getMinY() > bounds.getMaxY())
107         throw new IllegalArgumentException("worldBounds minY should be <= maxY: "+ bounds);
108       //hopefully worldBounds' rect implementation is compatible
109       this.worldBounds = new RectangleImpl(bounds, this);
110     }
111 
112     this.binaryCodec = factory.makeBinaryCodec(this);
113     
114     factory.checkDefaultFormats();
115     this.formats = factory.makeFormats(this);
116   }
117 
118   /** A factory for {@link Shape}s. */
119   public ShapeFactory getShapeFactory() {
120     return shapeFactory;
121   }
122 
123   public SupportedFormats getFormats() {
124     return formats;
125   }
126 
127   public DistanceCalculator getDistCalc() {
128     return calculator;
129   }
130 
131   /** Convenience that uses {@link #getDistCalc()} */
132   public double calcDistance(Point p, double x2, double y2) {
133     return getDistCalc().distance(p, x2, y2);
134   }
135 
136   /** Convenience that uses {@link #getDistCalc()} */
137   public double calcDistance(Point p, Point p2) {
138     return getDistCalc().distance(p, p2);
139   }
140 
141   /**
142    * The extent of x &amp; y coordinates should fit within the return'ed rectangle.
143    * Do *NOT* invoke reset() on this return type.
144    */
145   public Rectangle getWorldBounds() {
146     return worldBounds;
147   }
148 
149   /** If true then {@link #normX(double)} will wrap longitudes outside of the standard
150    * geodetic boundary into it. Example: 181 will become -179. */
151   @Deprecated
152   public boolean isNormWrapLongitude() {
153     return shapeFactory.isNormWrapLongitude();
154   }
155 
156   /** Is the mathematical world model based on a sphere, or is it a flat plane? The word
157    * "geodetic" or "geodesic" is sometimes used to refer to the former, and the latter is sometimes
158    * referred to as "Euclidean" or "cartesian". */
159   public boolean isGeo() {
160     return geo;
161   }
162 
163   /** Normalize the 'x' dimension. Might reduce precision or wrap it to be within the bounds. This
164    * is called by {@link org.locationtech.spatial4j.io.WKTReader} before creating a shape. */
165   @Deprecated
166   public double normX(double x) {
167     return shapeFactory.normX(x);
168   }
169 
170   /** Normalize the 'y' dimension. Might reduce precision or wrap it to be within the bounds. This
171    * is called by {@link org.locationtech.spatial4j.io.WKTReader} before creating a shape. */
172   @Deprecated
173   public double normY(double y) { return shapeFactory.normY(y); }
174 
175   /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that
176    * gets an 'x' dimension. */
177   @Deprecated
178   public void verifyX(double x) {
179     shapeFactory.verifyX(x);
180   }
181 
182   /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that
183    * gets a 'y' dimension. */
184   @Deprecated
185   public void verifyY(double y) {
186     shapeFactory.verifyY(y);
187   }
188 
189   /** Construct a point. */
190   @Deprecated
191   public Point makePoint(double x, double y) {
192     return shapeFactory.pointXY(x, y);
193   }
194 
195   /** Construct a rectangle. */
196   @Deprecated
197   public Rectangle makeRectangle(Point lowerLeft, Point upperRight) {
198     return shapeFactory.rect(lowerLeft, upperRight);
199   }
200 
201   /**
202    * Construct a rectangle. If just one longitude is on the dateline (+/- 180) (aka anti-meridian)
203    * then potentially adjust its sign to ensure the rectangle does not cross the
204    * dateline.
205    */
206   @Deprecated
207   public Rectangle makeRectangle(double minX, double maxX, double minY, double maxY) {
208     return shapeFactory.rect(minX, maxX, minY, maxY);
209   }
210 
211   /** Construct a circle. The units of "distance" should be the same as x &amp; y. */
212   @Deprecated
213   public Circle makeCircle(double x, double y, double distance) {
214     return shapeFactory.circle(x, y, distance);
215   }
216 
217   /** Construct a circle. The units of "distance" should be the same as x &amp; y. */
218   @Deprecated
219   public Circle makeCircle(Point point, double distance) {
220     return shapeFactory.circle(point, distance);
221   }
222 
223   /** Constructs a line string. It's an ordered sequence of connected vertexes. There
224    * is no official shape/interface for it yet so we just return Shape. */
225   @Deprecated
226   public Shape makeLineString(List<Point> points) {
227     return shapeFactory.lineString(points, 0);
228   }
229 
230   /** Constructs a buffered line string. It's an ordered sequence of connected vertexes,
231    * with a buffer distance along the line in all directions. There
232    * is no official shape/interface for it so we just return Shape. */
233   @Deprecated
234   public Shape makeBufferedLineString(List<Point> points, double buf) {
235     return shapeFactory.lineString(points, buf);
236   }
237 
238   /** Construct a ShapeCollection, analogous to an OGC GeometryCollection. */
239   @Deprecated
240   public <S extends Shape> ShapeCollection<S> makeCollection(List<S> coll) {
241     return shapeFactory.multiShape(coll);
242   }
243 
244   /** The {@link org.locationtech.spatial4j.io.WKTReader} used by {@link #readShapeFromWkt(String)}. */
245   @Deprecated
246   public WKTReader getWktShapeParser() {
247     return (WKTReader)formats.getWktReader();
248   }
249 
250   /** Reads a shape from the string formatted in WKT.
251    * @see org.locationtech.spatial4j.io.WKTReader
252    * @param wkt non-null WKT.
253    * @return non-null
254    * @throws ParseException if it failed to parse.
255    */
256   @Deprecated
257   public Shape readShapeFromWkt(String wkt) throws ParseException, InvalidShapeException {
258     return getWktShapeParser().parse(wkt);
259   }
260 
261   public BinaryCodec getBinaryCodec() { return binaryCodec; }
262 
263   /**
264    * Try to read a shape from any supported formats
265    * 
266    * @return shape or null if unable to parse any shape
267    */
268   @Deprecated
269   public Shape readShape(String value) throws InvalidShapeException {
270     return formats.read(value);
271   }
272 
273   /** Writes the shape to a String using the old/deprecated
274    * {@link org.locationtech.spatial4j.io.LegacyShapeWriter}. The JTS based subclass will write it
275    * to WKT if the legacy format doesn't support that shape.
276    * <b>Spatial4j in the near future won't support writing shapes to strings.</b>
277    * @param shape non-null
278    * @return non-null
279    */
280   @Deprecated
281   public String toString(Shape shape) {
282     return LegacyShapeWriter.writeShape(shape);
283   }
284   
285   @Override
286   public String toString() {
287     if (this.equals(GEO)) {
288       return GEO.getClass().getSimpleName()+".GEO";
289     } else {
290       return getClass().getSimpleName()+"{" +
291           "geo=" + geo +
292           ", calculator=" + calculator +
293           ", worldBounds=" + worldBounds +
294           '}';
295     }
296   }
297 }