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.io;
10  
11  import java.io.IOException;
12  import java.io.Reader;
13  import java.text.ParseException;
14  import java.util.StringTokenizer;
15  
16  import org.locationtech.spatial4j.context.SpatialContext;
17  import org.locationtech.spatial4j.context.SpatialContextFactory;
18  import org.locationtech.spatial4j.exception.InvalidShapeException;
19  import org.locationtech.spatial4j.shape.Point;
20  import org.locationtech.spatial4j.shape.Shape;
21  
22  /**
23   * Reads a shape from the old format.
24   * <ul>
25   *   <li>Point: X Y
26   *   <br> 1.23 4.56
27   *   </li>
28   *   <li>Rect: XMin YMin XMax YMax
29   *   <br> 1.23 4.56 7.87 4.56
30   *   </li>
31   *   <li>{CIRCLE} '(' {POINT} {DISTANCE} ')' <br>
32   *   CIRCLE is "CIRCLE" or "Circle" (no other case), and POINT is "X Y" order pair of doubles, or
33   *   "Y,X" (lat,lon) pair of doubles, and DISTANCE is "d=RADIUS" or "distance=RADIUS" where RADIUS
34   *   is a double that is the distance radius in degrees.
35   *   </li>
36   * </ul>
37   */
38  @Deprecated
39  public class LegacyShapeReader implements ShapeReader {
40  
41    final SpatialContext ctx;
42  
43    public LegacyShapeReader(SpatialContext ctx, SpatialContextFactory factory) {
44      this.ctx = ctx;
45    }
46  
47  
48    /** Reads the shape specification as defined in the class javadocs. If the first character is
49     * a letter but it doesn't complete out "Circle" or "CIRCLE" then this method returns null,
50     * offering the caller the opportunity to potentially try additional parsing.
51     * If the first character is not a letter then it's assumed to be a point or rectangle. If that
52     * doesn't work out then an {@link org.locationtech.spatial4j.exception.InvalidShapeException} is thrown.
53     */
54    public static Shape readShapeOrNull(String str, SpatialContext ctx) throws InvalidShapeException {
55      if (str == null || str.length() == 0) {
56        throw new InvalidShapeException(str);
57      }
58  
59      if (Character.isLetter(str.charAt(0))) {
60        if (str.startsWith("Circle(") || str.startsWith("CIRCLE(")) {
61          int idx = str.lastIndexOf(')');
62          if (idx > 0) {
63            String body = str.substring("Circle(".length(), idx);
64            StringTokenizer st = new StringTokenizer(body, " ");
65            String token = st.nextToken();
66            Point pt;
67            if (token.indexOf(',') != -1) {
68              pt = readLatCommaLonPoint(token, ctx);
69            } else {
70              double x = Double.parseDouble(token);
71              double y = Double.parseDouble(st.nextToken());
72              pt = ctx.makePoint(x, y);
73            }
74            Double d = null;
75  
76            String arg = st.nextToken();
77            idx = arg.indexOf('=');
78            if (idx > 0) {
79              String k = arg.substring(0, idx);
80              if (k.equals("d") || k.equals("distance")) {
81                d = Double.parseDouble(arg.substring(idx + 1));
82              } else {
83                throw new InvalidShapeException("unknown arg: " + k + " :: " + str);
84              }
85            } else {
86              d = Double.parseDouble(arg);
87            }
88            if (st.hasMoreTokens()) {
89              throw new InvalidShapeException("Extra arguments: " + st.nextToken() + " :: " + str);
90            }
91            if (d == null) {
92              throw new InvalidShapeException("Missing Distance: " + str);
93            }
94            //NOTE: we are assuming the units of 'd' is the same as that of the spatial context.
95            return ctx.makeCircle(pt, d);
96          }
97        }
98        return null;//caller has opportunity to try other parsing
99      }
100 
101     if (str.indexOf(',') != -1)
102       return readLatCommaLonPoint(str, ctx);
103     StringTokenizer st = new StringTokenizer(str, " ");
104     double p0 = Double.parseDouble(st.nextToken());
105     double p1 = Double.parseDouble(st.nextToken());
106     if (st.hasMoreTokens()) {
107       double p2 = Double.parseDouble(st.nextToken());
108       double p3 = Double.parseDouble(st.nextToken());
109       if (st.hasMoreTokens())
110         throw new InvalidShapeException("Only 4 numbers supported (rect) but found more: " + str);
111       return ctx.makeRectangle(p0, p2, p1, p3);
112     }
113     return ctx.makePoint(p0, p1);
114   }
115 
116   /** Reads geospatial latitude then a comma then longitude. */
117   private static Point readLatCommaLonPoint(String value, SpatialContext ctx) throws InvalidShapeException {
118     double[] latLon = ParseUtils.parseLatitudeLongitude(value);
119     return ctx.makePoint(latLon[1], latLon[0]);
120   }
121   
122   //-------
123 
124   @Override
125   public String getFormatName() {
126     return ShapeIO.LEGACY;
127   }
128 
129   @Override
130   public Shape read(Object value) throws IOException, ParseException, InvalidShapeException {
131     Shape shape = readShapeOrNull(value.toString(), ctx);
132     if(shape==null) {
133       throw new ParseException("unable to read shape: "+value, 0);
134     }
135     return readShapeOrNull(value.toString(), ctx);
136   }
137 
138   @Override
139   public Shape readIfSupported(Object value) throws InvalidShapeException {
140     return readShapeOrNull(value.toString(), ctx);
141   }
142 
143   @Override
144   public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException {
145     return read(WKTReader.readString(reader));
146   }
147 }