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
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
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 }