Class ShapeReader

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12  
 13 package org.locationtech.jts.awt;
 14  
 15 import java.awt.Shape;
 16 import java.awt.geom.AffineTransform;
 17 import java.awt.geom.PathIterator;
 18 import java.util.ArrayList;
 19 import java.util.List;
 20  
 21 import org.locationtech.jts.algorithm.Orientation;
 22 import org.locationtech.jts.geom.Coordinate;
 23 import org.locationtech.jts.geom.CoordinateList;
 24 import org.locationtech.jts.geom.Geometry;
 25 import org.locationtech.jts.geom.GeometryFactory;
 26 import org.locationtech.jts.geom.LinearRing;
 27  
 28  
 29 /**
 30  * Converts a Java2D {@link Shape} 
 31  * or the more general {@link PathIterator} into a {@link Geometry}.
 32  * <p>
 33  * The coordinate system for Java2D is typically screen coordinates, 
 34  * which has the Y axis inverted
 35  * relative to the usual JTS coordinate system.
 36  * This is rectified during conversion. 
 37  * <p>
 38  * PathIterators to be converted are expected to be linear or flat.
 39  * That is, they should contain only <tt>SEG_MOVETO</tt>, <tt>SEG_LINETO</tt>, and <tt>SEG_CLOSE</tt> segment types.
 40  * Any other segment types will cause an exception.
 41  * 
 42  * @author Martin Davis
 43  *
 44  */
 45 public class ShapeReader 
 46 {
 47   private static final AffineTransform INVERT_Y = AffineTransform.getScaleInstance(1, -1);
 48  
 49   /**
 50    * Converts a flat path to a {@link Geometry}.
 51    * 
 52    * @param pathIt the path to convert
 53    * @param geomFact the GeometryFactory to use
 54    * @return a Geometry representing the path
 55    */
 56   public static Geometry read(PathIterator pathIt, GeometryFactory geomFact)
 57   {
 58     ShapeReader pc = new ShapeReader(geomFact);
 59     return pc.read(pathIt);
 60   }
 61   
 62   /**
 63    * Converts a Shape to a Geometry, flattening it first.
 64    * 
 65    * @param shp the Java2D shape
 66    * @param flatness the flatness parameter to use
 67    * @param geomFact the GeometryFactory to use
 68    * @return a Geometry representing the shape
 69    */
 70   public static Geometry read(Shape shp, double flatness, GeometryFactory geomFact)
 71   {
 72     PathIterator pathIt = shp.getPathIterator(INVERT_Y, flatness);
 73     return ShapeReader.read(pathIt, geomFact);
 74   }
 75  
 76   private GeometryFactory geometryFactory;
 77   
 78   public ShapeReader(GeometryFactory geometryFactory) {
 79     this.geometryFactory = geometryFactory;
 80   }
 81  
 82   /**
 83    * Converts a flat path to a {@link Geometry}.
 84    * 
 85    * @param pathIt the path to convert
 86    * @return a Geometry representing the path
 87    */
 88   public Geometry read(PathIterator pathIt)
 89   {
 90     List pathPtSeq = toCoordinates(pathIt);
 91     
 92     List polys = new ArrayList();
 93     int seqIndex = 0;
 94     while (seqIndex < pathPtSeq.size()) {
 95       // assume next seq is shell 
 96       // TODO: test this
 97       Coordinate[] pts = (Coordinate[]) pathPtSeq.get(seqIndex);
 98       LinearRing shell = geometryFactory.createLinearRing(pts);
 99       seqIndex++;
100       
101       List holes = new ArrayList();
102       // add holes as long as rings are CCW
103       while (seqIndex < pathPtSeq.size() && isHole((Coordinate[]) pathPtSeq.get(seqIndex))) {
104         Coordinate[] holePts = (Coordinate[]) pathPtSeq.get(seqIndex);
105         LinearRing hole = geometryFactory.createLinearRing(holePts);
106         holes.add(hole);
107         seqIndex++;
108       }
109       LinearRing[] holeArray = GeometryFactory.toLinearRingArray(holes);
110       polys.add(geometryFactory.createPolygon(shell, holeArray));
111     }
112     return geometryFactory.buildGeometry(polys);
113   }
114   
115   private boolean isHole(Coordinate[] pts)
116   {
117     return Orientation.isCCW(pts);
118   }
119   
120   /**
121    * Extracts the points of the paths in a flat {@link PathIterator} into
122    * a list of Coordinate arrays.
123    * 
124    * @param pathIt a path iterator
125    * @return a List of Coordinate arrays
126    * @throws IllegalArgumentException if a non-linear segment type is encountered
127    */
128   public static List toCoordinates(PathIterator pathIt)
129   {
130     List coordArrays = new ArrayList();
131     while (! pathIt.isDone()) {
132       Coordinate[] pts = nextCoordinateArray(pathIt);
133       if (pts == null)
134         break;
135       coordArrays.add(pts);
136     }
137     return coordArrays;
138   }
139   
140   private static Coordinate[] nextCoordinateArray(PathIterator pathIt)
141   {
142     double[] pathPt = new double[6];
143     CoordinateList coordList = null;
144     boolean isDone = false;
145     while (! pathIt.isDone()) {
146       int segType = pathIt.currentSegment(pathPt);
147       switch (segType) {
148       case PathIterator.SEG_MOVETO:
149         if (coordList != null) {
150           // don't advance pathIt, to retain start of next path if any
151           isDone = true;
152         }
153         else {
154           coordList = new CoordinateList();
155           coordList.add(new Coordinate(pathPt[0], pathPt[1]));
156           pathIt.next();
157         }
158         break;
159       case PathIterator.SEG_LINETO:
160         coordList.add(new Coordinate(pathPt[0], pathPt[1]));
161         pathIt.next();
162         break;
163       case PathIterator.SEG_CLOSE:  
164         coordList.closeRing();
165         pathIt.next();
166         isDone = true;   
167         break;
168       default:
169           throw new IllegalArgumentException("unhandled (non-linear) segment type encountered");
170       }
171       if (isDone) 
172         break;
173     }
174     return coordList.toCoordinateArray();
175   }
176  
177 }
178