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.StringWriter;
23  import java.io.Writer;
24  import java.util.Iterator;
25  
26  import org.locationtech.spatial4j.context.SpatialContext;
27  import org.locationtech.spatial4j.context.SpatialContextFactory;
28  import org.locationtech.spatial4j.shape.Circle;
29  import org.locationtech.spatial4j.shape.Point;
30  import org.locationtech.spatial4j.shape.Rectangle;
31  import org.locationtech.spatial4j.shape.Shape;
32  import org.locationtech.spatial4j.shape.ShapeCollection;
33  import org.locationtech.spatial4j.shape.impl.BufferedLine;
34  import org.locationtech.spatial4j.shape.impl.BufferedLineString;
35  
36  
37  /**
38   * Wrap the 'Encoded Polyline Algorithm Format' defined in: 
39   * <a href="https://developers.google.com/maps/documentation/utilities/polylinealgorithm">Google Maps API</a>
40   * with flags to encode various Shapes:  Point, Line, Polygon, etc
41   * 
42   * For more details, see <a href="https://github.com/locationtech/spatial4j/blob/master/FORMATS.md#polyshape">FORMATS.md</a>
43   * 
44   * This format works well for geographic shapes (-180...+180 / -90...+90), but is not appropriate for other coordinate systems
45   * 
46   * @see <a href="https://developers.google.com/maps/documentation/utilities/polylinealgorithm">Google Maps API</a>
47   * @see PolyshapeReader
48   */
49  public class PolyshapeWriter implements ShapeWriter {
50  
51    public PolyshapeWriter(SpatialContext ctx, SpatialContextFactory factory) {
52  
53    }
54  
55    @Override
56    public String getFormatName() {
57      return ShapeIO.POLY;
58    }
59  
60  
61    @Override
62    public void write(Writer output, Shape shape) throws IOException {
63      if (shape == null) {
64        throw new NullPointerException("Shape can not be null");
65      }
66      write(new Encoder(output), shape);
67    }
68  
69    public void write(Encoder enc, Shape shape) throws IOException {
70      if (shape instanceof Point) {
71        Point v = (Point) shape;
72        enc.write(KEY_POINT);
73        enc.write(v.getX(), v.getY());
74        return;
75      }
76      if (shape instanceof Rectangle) {
77        Rectangle v = (Rectangle) shape;
78        enc.write(KEY_BOX);
79        enc.write(v.getMinX(), v.getMinY());
80        enc.write(v.getMaxX(), v.getMaxY());
81        return;
82      }
83      if (shape instanceof BufferedLine) {
84        BufferedLine v = (BufferedLine) shape;
85        enc.write(KEY_LINE);
86        if(v.getBuf()>0) {
87          enc.writeArg(v.getBuf());
88        }
89        enc.write(v.getA().getX(), v.getA().getY());
90        enc.write(v.getB().getX(), v.getB().getY());
91        return;
92      }
93      if (shape instanceof BufferedLineString) {
94        BufferedLineString v = (BufferedLineString) shape;
95        enc.write(KEY_LINE);
96        if(v.getBuf()>0) {
97          enc.writeArg(v.getBuf());
98        }
99        BufferedLine last = null;
100       Iterator<BufferedLine> iter = v.getSegments().iterator();
101       while (iter.hasNext()) {
102         BufferedLine seg = iter.next();
103         enc.write(seg.getA().getX(), seg.getA().getY());
104         last = seg;
105       }
106       if (last != null) {
107         enc.write(last.getB().getX(), last.getB().getY());
108       }
109       return;
110     }
111     if (shape instanceof Circle) {
112       // See: https://github.com/geojson/geojson-spec/wiki/Proposal---Circles-and-Ellipses-Geoms
113       Circle v = (Circle) shape;
114       Point center = v.getCenter();
115       double radius = v.getRadius();
116       enc.write(KEY_CIRCLE);
117       enc.writeArg(radius);
118       enc.write(center.getX(), center.getY());
119       return;
120     }
121     if (shape instanceof ShapeCollection) {
122       @SuppressWarnings("unchecked") ShapeCollection<Shape> v = (ShapeCollection<Shape>) shape;
123       Iterator<Shape> iter = v.iterator();
124       while(iter.hasNext()) {
125         write(enc, iter.next());
126         if(iter.hasNext()) {
127           enc.seperator();
128         }
129       }
130       return;
131     }
132     enc.writer.write("{unkwnwon " + LegacyShapeWriter.writeShape(shape) +"}");
133   }
134 
135   @Override
136   public String toString(Shape shape) {
137     try {
138       StringWriter buffer = new StringWriter();
139       write(buffer, shape);
140       return buffer.toString();
141     } catch (IOException ex) {
142       throw new RuntimeException(ex);
143     }
144   }
145   
146   public static final char KEY_POINT      = '0';
147   public static final char KEY_LINE       = '1';
148   public static final char KEY_POLYGON    = '2';
149   public static final char KEY_MULTIPOINT = '3';
150   public static final char KEY_CIRCLE     = '4';
151   public static final char KEY_BOX        = '5';
152   
153   public static final char KEY_ARG_START  = '(';
154   public static final char KEY_ARG_END    =  ')';
155   public static final char KEY_SEPERATOR  = ' ';
156     
157 
158   /**
159    * Encodes a sequence of LatLngs into an encoded path string.
160    * 
161    * from Apache 2.0 licensed:
162    * https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/PolyUtil.java
163    */
164   public static class Encoder {
165     long lastLat = 0;
166     long lastLng = 0;
167     
168     final Writer writer;
169     
170     public Encoder(Writer writer) {
171       this.writer = writer;
172     }
173     
174     public void seperator() throws IOException {
175       writer.write(KEY_SEPERATOR);
176       lastLat = lastLng = 0;
177     }
178 
179     public void startRing() throws IOException {
180       writer.write(KEY_ARG_START);
181       lastLat = lastLng = 0;
182     }
183     
184     public void write(char event) throws IOException {
185       writer.write(event);
186       lastLat = lastLng = 0;
187     }
188 
189     public void writeArg(double value) throws IOException {
190       writer.write(KEY_ARG_START);
191       encode(Math.round(value * 1e5));
192       writer.write(KEY_ARG_END);
193     }
194     
195     public void write(double latitude, double longitude) throws IOException {
196 
197       long lat = Math.round(latitude * 1e5);
198       long lng = Math.round(longitude * 1e5);
199 
200       long dLat = lat - lastLat;
201       long dLng = lng - lastLng;
202 
203       encode(dLat);
204       encode(dLng);
205 
206       lastLat = lat;
207       lastLng = lng;
208     }
209     
210     private void encode(long v) throws IOException {
211       v = v < 0 ? ~(v << 1) : v << 1;
212       while (v >= 0x20) {
213         writer.write(Character.toChars((int) ((0x20 | (v & 0x1f)) + 63)));
214         v >>= 5;
215       }
216       writer.write(Character.toChars((int) (v + 63)));
217     }
218   }
219 }