package au.gov.amsa.gt; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.Writer; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.geotools.data.DataStore; import org.geotools.data.DataStoreFinder; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.geojson.feature.FeatureJSON; import org.geotools.geometry.jts.JTS; import org.geotools.referencing.CRS; import org.opengis.feature.simple.SimpleFeature; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.prep.PreparedGeometry; import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory; public final class Shapefile { private final DataStore datastore; private final static GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); // 0 = not loaded, not closed, 1 = loaded, 2 = closed private final AtomicInteger state = new AtomicInteger(0); private static final int NOT_LOADED = 0; private static final int LOADED = 1; private static final int CLOSED = 2; private volatile List<PreparedGeometry> geometries; private final double bufferDistance; private volatile List<SimpleFeature> features = new ArrayList<SimpleFeature>(); private volatile CoordinateReferenceSystem crs; private Shapefile(File file, double bufferDistance) { this.bufferDistance = bufferDistance; try { Map<String, Serializable> map = new HashMap<>(); map.put("url", file.toURI().toURL()); datastore = DataStoreFinder.getDataStore(map); } catch (IOException e) { throw new RuntimeException(e); } } public static Shapefile from(File file) { return from(file, 0); } public static Shapefile from(File file, double bufferDistance) { return new Shapefile(file, bufferDistance); } public static void createPolygon(List<Coordinate> coords, File output) { ShapefileCreator.createPolygon(coords, output); } public static Shapefile fromZip(InputStream is) { return fromZip(is, 0); } public static DataStore fromZipAsDataStore(InputStream is) { try { File directory = Files.createTempDirectory("shape-").toFile(); ZipUtil.unzip(is, directory); Map<String, Serializable> map = new HashMap<>(); map.put("url", directory.toURI().toURL()); return DataStoreFinder.getDataStore(map); } catch (IOException e) { throw new RuntimeException(e); } } public static Shapefile fromZip(InputStream is, double bufferDistance) { try { File directory = Files.createTempDirectory("shape-").toFile(); ZipUtil.unzip(is, directory); return new Shapefile(directory, bufferDistance); } catch (IOException e) { throw new RuntimeException(e); } } private Shapefile load() { if (state.compareAndSet(NOT_LOADED, LOADED)) { try { final List<PreparedGeometry> geometries = new ArrayList<>(); for (String typeName : datastore.getTypeNames()) { SimpleFeatureSource source = datastore.getFeatureSource(typeName); crs = source.getBounds().getCoordinateReferenceSystem(); final SimpleFeatureCollection features = source.getFeatures(); SimpleFeatureIterator it = features.features(); while (it.hasNext()) { SimpleFeature feature = it.next(); Geometry g = (Geometry) feature.getDefaultGeometry(); if (bufferDistance > 0) g = g.buffer(bufferDistance); geometries.add(PreparedGeometryFactory.prepare(g)); this.features.add(feature); } it.close(); } this.geometries = geometries; } catch (IOException e) { throw new RuntimeException(e); } } else if (state.get() == CLOSED) throw new RuntimeException("Shapefile is closed and can't be accessed"); return this; } public List<PreparedGeometry> geometries() { load(); return geometries; } public boolean contains(double lat, double lon) { load(); return GeometryUtil.contains(GEOMETRY_FACTORY, geometries, lat, lon); } public Rect mbr() { // TODO assumes that shapefile is using WGS84? load(); Rect r = null; for (PreparedGeometry g : geometries) { Coordinate[] v = g.getGeometry().getEnvelope().getCoordinates(); System.out.println(Arrays.toString(v)); Rect rect = new Rect(v[0].y, v[0].x, v[2].y, v[2].x); if (r == null) r = rect; else r = r.add(rect); } return r; } public void close() { if (state.compareAndSet(NOT_LOADED, CLOSED) || state.compareAndSet(LOADED, CLOSED)) datastore.dispose(); } public void writeGeoJson(Writer writer, String targetCrsName) { load(); try { CoordinateReferenceSystem targetCrs = CRS.decode(targetCrsName); MathTransform transform = CRS.findMathTransform(crs, targetCrs); FeatureJSON f = new FeatureJSON(); f.writeCRS(targetCrs, writer); features.stream().forEach(feature -> { try { SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); tb.setCRS(targetCrs); SimpleFeatureBuilder b = new SimpleFeatureBuilder(feature.getFeatureType()); b.add(JTS.transform((Geometry) feature.getDefaultGeometry(), transform)); f.writeFeature(b.buildFeature(feature.getID()), writer); } catch (Exception e) { throw new RuntimeException(e); } }); } catch (IOException | FactoryException e) { throw new RuntimeException(e); } } }