View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  
19  package org.locationtech.spatial4j.io;
20  
21  import java.io.IOException;
22  import java.io.Reader;
23  import java.io.StringReader;
24  import java.text.ParseException;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.locationtech.spatial4j.context.SpatialContext;
29  import org.locationtech.spatial4j.context.SpatialContextFactory;
30  import org.locationtech.spatial4j.exception.InvalidShapeException;
31  import org.locationtech.spatial4j.shape.Shape;
32  import org.locationtech.spatial4j.shape.ShapeFactory;
33  import org.locationtech.jts.geom.LinearRing;
34  
35  
36  /**
37   * @see PolyshapeWriter
38   */
39  public class PolyshapeReader implements ShapeReader {
40    final SpatialContext ctx;
41    final ShapeFactory shpFactory;
42  
43    public PolyshapeReader(SpatialContext ctx, SpatialContextFactory factory) {
44      this.ctx = ctx;
45      this.shpFactory = ctx.getShapeFactory();
46    }
47  
48    @Override
49    public String getFormatName() {
50      return ShapeIO.POLY;
51    }
52  
53    @Override
54    public Shape read(Object value) throws IOException, ParseException, InvalidShapeException {
55      return read(new StringReader(value.toString().trim()));
56    }
57  
58    @Override
59    public Shape readIfSupported(Object value) throws InvalidShapeException {
60      String v = value.toString().trim();
61      char first = v.charAt(0);
62      if(first >= '0' && first <= '9') {
63        try {
64          return read(new StringReader(v));
65        } catch (ParseException e) {
66        } catch (IOException e) {
67        }
68      }
69      return null;
70    }
71  
72    // --------------------------------------------------------------
73    // Read GeoJSON
74    // --------------------------------------------------------------
75  
76    @Override
77    public final Shape read(Reader r) throws ParseException, IOException
78    {
79      XReader reader = new XReader(r, shpFactory);
80      Double arg = null;
81      
82      Shape lastShape = null;
83      List<Shape> shapes = null;
84      while(!reader.isDone()) {
85        char event = reader.readKey();
86        if(event<'0' || event > '9') {
87          if(event == PolyshapeWriter.KEY_SEPERATOR) {
88            continue; // read the next key
89          }
90          throw new ParseException("expecting a shape key.  not '"+event+"'", -1);
91        }
92  
93        if(lastShape!=null) {
94          if(shapes==null) {
95            shapes = new ArrayList<>();
96          }
97          shapes.add(lastShape);
98        }
99        arg = null;
100       
101       if(reader.peek()==PolyshapeWriter.KEY_ARG_START) {
102         reader.readKey(); // skip the key
103         arg = reader.readDouble();
104         if(reader.readKey()!=PolyshapeWriter.KEY_ARG_END) {
105           throw new ParseException("expecting an argument end", -1);
106         }
107       }
108       if(reader.isEvent()) {
109         throw new ParseException("Invalid input. Event should be followed by data", -1);
110       }
111       
112       switch(event) {
113         case PolyshapeWriter.KEY_POINT: {
114           lastShape = shpFactory.pointXY(shpFactory.normX(reader.readLat()), shpFactory.normY(reader.readLng()));
115           break;
116         }
117         case PolyshapeWriter.KEY_LINE: {
118           ShapeFactory.LineStringBuilder lineBuilder = shpFactory.lineString();
119           reader.readPoints(lineBuilder);
120           
121           if(arg!=null) {
122             lineBuilder.buffer(shpFactory.normDist(arg));
123           }
124           lastShape = lineBuilder.build();
125           break;
126         }
127         case PolyshapeWriter.KEY_BOX: {
128           double lat1 = shpFactory.normX(reader.readLat());
129           double lon1 = shpFactory.normY(reader.readLng());
130           lastShape = shpFactory.rect(lat1, shpFactory.normX(reader.readLat()), 
131                   lon1, shpFactory.normY(reader.readLng()));
132           break;
133         }
134         case PolyshapeWriter.KEY_MULTIPOINT : {
135           lastShape = reader.readPoints(shpFactory.multiPoint()).build();
136           break;
137         }
138         case PolyshapeWriter.KEY_CIRCLE : {
139           if(arg==null) {
140             throw new IllegalArgumentException("the input should have a radius argument");
141           }
142           lastShape = shpFactory.circle(shpFactory.normX(reader.readLat()), shpFactory.normY(reader.readLng()), 
143                 shpFactory.normDist(arg.doubleValue()));
144           break;
145         }
146         case PolyshapeWriter.KEY_POLYGON: {
147           lastShape = readPolygon(reader);
148           break;
149         }
150         default: {
151           throw new ParseException("unhandled key: "+event, -1);
152         }
153       }
154     }
155     
156     if(shapes!=null) {
157       if(lastShape!=null) {
158         shapes.add(lastShape);
159       }
160 
161       ShapeFactory.MultiShapeBuilder<Shape> multiBuilder = shpFactory.multiShape(Shape.class); 
162       for (Shape shp : shapes) {
163         multiBuilder.add(shp);
164       }
165 
166       return multiBuilder.build();
167     }
168     return lastShape;
169   }
170   
171   protected Shape readPolygon(XReader reader) throws IOException {
172     ShapeFactory.PolygonBuilder polyBuilder = shpFactory.polygon();
173     
174     reader.readPoints(polyBuilder);
175 
176     if(!reader.isDone() && reader.peek()==PolyshapeWriter.KEY_ARG_START) {
177       while(reader.isEvent() && reader.peek()==PolyshapeWriter.KEY_ARG_START) {
178         reader.readKey(); // eat the event;
179         reader.readPoints(polyBuilder.hole()).endHole();
180       }
181     }
182 
183     return polyBuilder.build();
184   }
185 
186   /**
187    * from Apache 2.0 licensed:
188    * https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/PolyUtil.java
189    */
190   public static class XReader {
191     int lat = 0;
192     int lng = 0;
193     
194     int head = -1;
195     final Reader input;
196     final ShapeFactory shpFactory;
197 
198     public XReader(final Reader input, ShapeFactory shpFactory) throws IOException {
199       this.input = input;
200       this.shpFactory = shpFactory;
201       head = input.read();
202     }
203     
204     public <T extends ShapeFactory.PointsBuilder> T readPoints(T builder) throws IOException {
205       while(isData()) {
206         builder.pointXY(shpFactory.normX(readLat()), shpFactory.normY(readLng()));
207       }
208       return builder;
209     }
210 
211     public double readLat() throws IOException {
212       lat += readInt();
213       return lat * 1e-5;
214     }
215 
216     public double readLng() throws IOException {
217       lng += readInt();
218       return lng * 1e-5;
219     }
220     
221     public double readDouble() throws IOException {
222       return readInt() * 1e-5;
223     }
224     
225     public int peek() {
226       return head;
227     }
228 
229     public char readKey() throws IOException {
230       lat = lng = 0; // reset the offset
231       char key = (char)head;
232       head = input.read();
233       return key;
234     }
235 
236     public boolean isData() {
237       return head >= '?';
238     }
239 
240     public boolean isDone() {
241       return head < 0;
242     }
243     
244     public boolean isEvent() {
245       return head > 0 && head < '?';
246     }
247     
248     int readInt() throws IOException
249     {
250       int b;
251       int result = 1;
252       int shift = 0;
253       do {
254         b = head - 63 - 1;
255         result += b << shift;
256         shift += 5;
257         
258         head = input.read();
259       } while (b >= 0x1f);
260       return (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
261     }
262   }
263 }