View Javadoc
1   package au.gov.amsa.gt;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.Serializable;
7   import java.io.Writer;
8   import java.nio.file.Files;
9   import java.util.ArrayList;
10  import java.util.Arrays;
11  import java.util.HashMap;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.concurrent.atomic.AtomicInteger;
15  
16  import org.geotools.data.DataStore;
17  import org.geotools.data.DataStoreFinder;
18  import org.geotools.data.shapefile.ShapefileDataStore;
19  import org.geotools.data.simple.SimpleFeatureCollection;
20  import org.geotools.data.simple.SimpleFeatureIterator;
21  import org.geotools.data.simple.SimpleFeatureSource;
22  import org.geotools.feature.simple.SimpleFeatureBuilder;
23  import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
24  import org.geotools.geojson.feature.FeatureJSON;
25  import org.geotools.geometry.jts.JTS;
26  import org.geotools.referencing.CRS;
27  import org.opengis.feature.simple.SimpleFeature;
28  import org.opengis.referencing.FactoryException;
29  import org.opengis.referencing.crs.CoordinateReferenceSystem;
30  import org.opengis.referencing.operation.MathTransform;
31  
32  import com.vividsolutions.jts.awt.PointShapeFactory.X;
33  import com.vividsolutions.jts.geom.Coordinate;
34  import com.vividsolutions.jts.geom.Geometry;
35  import com.vividsolutions.jts.geom.GeometryFactory;
36  import com.vividsolutions.jts.geom.prep.PreparedGeometry;
37  import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
38  
39  public final class Shapefile {
40  
41      private final DataStore datastore;
42      private final static GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
43      // 0 = not loaded, not closed, 1 = loaded, 2 = closed
44      private final AtomicInteger state = new AtomicInteger(0);
45      private static final int NOT_LOADED = 0;
46      private static final int LOADED = 1;
47      private static final int CLOSED = 2;
48  
49      private volatile List<PreparedGeometry> geometries;
50      private final double bufferDistance;
51      private volatile List<SimpleFeature> features = new ArrayList<SimpleFeature>();
52      private volatile CoordinateReferenceSystem crs;
53  
54      private Shapefile(File file, double bufferDistance) {
55          this.bufferDistance = bufferDistance;
56          try {
57              datastore = new ShapefileDataStore(file.toURI().toURL());
58          } catch (IOException e) {
59              throw new RuntimeException(e);
60          }
61      }
62  
63      public static Shapefile from(File file) {
64          return from(file, 0);
65      }
66  
67      public static Shapefile from(File file, double bufferDistance) {
68          return new Shapefile(file, bufferDistance);
69      }
70  
71      public static void createPolygon(List<Coordinate> coords, File output) {
72          ShapefileCreator.createPolygon(coords, output);
73      }
74  
75      public static Shapefile fromZip(InputStream is) {
76          return fromZip(is, 0);
77      }
78  
79      public static DataStore fromZipAsDataStore(InputStream is) {
80          try {
81              File directory = Files.createTempDirectory("shape-").toFile();
82              ZipUtil.unzip(is, directory);
83              Map<String, Serializable> map = new HashMap<>();
84              map.put("url", directory.toURI().toURL());
85              return DataStoreFinder.getDataStore(map);
86          } catch (IOException e) {
87              throw new RuntimeException(e);
88          }
89      }
90  
91      public static Shapefile fromZip(InputStream is, double bufferDistance) {
92          try {
93              File directory = Files.createTempDirectory("shape-").toFile();
94              ZipUtil.unzip(is, directory);
95              File f = directory.listFiles(x -> x.getName().endsWith(".shp"))[0];
96              return new Shapefile(f, bufferDistance);
97          } catch (IOException e) {
98              throw new RuntimeException(e);
99          }
100     }
101 
102     private Shapefile load() {
103         if (state.compareAndSet(NOT_LOADED, LOADED)) {
104             try {
105                 final List<PreparedGeometry> geometries = new ArrayList<>();
106                 for (String typeName : datastore.getTypeNames()) {
107                     SimpleFeatureSource source = datastore.getFeatureSource(typeName);
108                     crs = source.getBounds().getCoordinateReferenceSystem();
109                     final SimpleFeatureCollection features = source.getFeatures();
110                     SimpleFeatureIterator it = features.features();
111                     while (it.hasNext()) {
112                         SimpleFeature feature = it.next();
113                         Geometry g = (Geometry) feature.getDefaultGeometry();
114                         if (bufferDistance > 0)
115                             g = g.buffer(bufferDistance);
116                         geometries.add(PreparedGeometryFactory.prepare(g));
117                         this.features.add(feature);
118                     }
119                     it.close();
120                 }
121                 this.geometries = geometries;
122             } catch (IOException e) {
123                 throw new RuntimeException(e);
124             }
125         } else if (state.get() == CLOSED)
126             throw new RuntimeException("Shapefile is closed and can't be accessed");
127         return this;
128     }
129 
130     public List<PreparedGeometry> geometries() {
131         load();
132         return geometries;
133     }
134 
135     public boolean contains(double lat, double lon) {
136         load();
137         return GeometryUtil.contains(GEOMETRY_FACTORY, geometries, lat, lon);
138     }
139 
140     public Rect mbr() {
141         // TODO assumes that shapefile is using WGS84?
142         load();
143         Rect r = null;
144         for (PreparedGeometry g : geometries) {
145             Coordinate[] v = g.getGeometry().getEnvelope().getCoordinates();
146             System.out.println(Arrays.toString(v));
147             Rect rect = new Rect(v[0].y, v[0].x, v[2].y, v[2].x);
148             if (r == null)
149                 r = rect;
150             else
151                 r = r.add(rect);
152         }
153         return r;
154     }
155 
156     public void close() {
157         if (state.compareAndSet(NOT_LOADED, CLOSED) || state.compareAndSet(LOADED, CLOSED))
158             datastore.dispose();
159     }
160 
161     public void writeGeoJson(Writer writer, String targetCrsName) {
162         load();
163 
164         try {
165             CoordinateReferenceSystem targetCrs = CRS.decode(targetCrsName);
166             MathTransform transform = CRS.findMathTransform(crs, targetCrs);
167             FeatureJSON f = new FeatureJSON();
168             f.writeCRS(targetCrs, writer);
169 
170             features.stream().forEach(feature -> {
171                 try {
172                     SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
173                     tb.setCRS(targetCrs);
174                     SimpleFeatureBuilder b = new SimpleFeatureBuilder(feature.getFeatureType());
175                     b.add(JTS.transform((Geometry) feature.getDefaultGeometry(), transform));
176                     f.writeFeature(b.buildFeature(feature.getID()), writer);
177                 } catch (Exception e) {
178                     throw new RuntimeException(e);
179                 }
180             });
181         } catch (IOException | FactoryException e) {
182             throw new RuntimeException(e);
183         }
184     }
185 }