planetiler/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/ShapefileReader.java

170 wiersze
7.1 KiB
Java
Czysty Zwykły widok Historia

package com.onthegomap.planetiler.reader;
2021-04-10 09:25:42 +00:00
import com.onthegomap.planetiler.Profile;
import com.onthegomap.planetiler.collection.FeatureGroup;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.stats.Stats;
import com.onthegomap.planetiler.util.FileUtils;
import com.onthegomap.planetiler.worker.WorkerPipeline;
2021-04-22 21:19:58 +00:00
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
2021-05-01 20:40:44 +00:00
import java.nio.file.FileSystems;
2021-05-01 20:08:20 +00:00
import java.nio.file.Path;
2021-09-10 00:46:20 +00:00
import java.util.HashMap;
2021-04-22 21:19:58 +00:00
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
2021-04-10 09:25:42 +00:00
2021-09-10 00:46:20 +00:00
/**
* Utility that reads {@link SourceFeature SourceFeatures} from the geometries contained in an ESRI shapefile.
* <p>
* Shapefile processing handled by geotools {@link ShapefileDataStore}.
*
* @see <a href=
* "https://www.esri.com/content/dam/esrisites/sitecore-archive/Files/Pdfs/library/whitepapers/pdfs/shapefile.pdf">ESRI
* Shapefile Specification</a>
2021-09-10 00:46:20 +00:00
*/
public class ShapefileReader extends SimpleReader implements Closeable {
2021-04-22 21:19:58 +00:00
2021-09-10 00:46:20 +00:00
private final FeatureCollection<SimpleFeatureType, org.opengis.feature.simple.SimpleFeature> inputSource;
2021-04-23 21:01:00 +00:00
private final String[] attributeNames;
2021-04-22 21:19:58 +00:00
private final ShapefileDataStore dataStore;
2021-05-13 10:25:06 +00:00
private MathTransform transformToLatLon;
2021-04-22 21:19:58 +00:00
2021-09-10 00:46:20 +00:00
ShapefileReader(String sourceProjection, String sourceName, Path input, Profile profile, Stats stats) {
2021-05-25 11:47:34 +00:00
super(profile, stats, sourceName);
2021-09-10 00:46:20 +00:00
dataStore = open(input);
2021-04-22 21:19:58 +00:00
try {
String typeName = dataStore.getTypeNames()[0];
2021-09-10 00:46:20 +00:00
FeatureSource<SimpleFeatureType, org.opengis.feature.simple.SimpleFeature> source = dataStore
.getFeatureSource(typeName);
2021-04-22 21:19:58 +00:00
inputSource = source.getFeatures(Filter.INCLUDE);
CoordinateReferenceSystem src =
sourceProjection == null ? source.getSchema().getCoordinateReferenceSystem() : CRS.decode(sourceProjection);
CoordinateReferenceSystem dest = CRS.decode("EPSG:4326", true);
2021-05-13 10:25:06 +00:00
transformToLatLon = CRS.findMathTransform(src, dest);
if (transformToLatLon.isIdentity()) {
transformToLatLon = null;
2021-04-22 21:19:58 +00:00
}
attributeNames = new String[inputSource.getSchema().getAttributeCount()];
for (int i = 0; i < attributeNames.length; i++) {
attributeNames[i] = inputSource.getSchema().getDescriptor(i).getLocalName();
}
} catch (IOException | FactoryException e) {
throw new RuntimeException(e);
}
}
2021-09-10 00:46:20 +00:00
ShapefileReader(String name, Path input, Profile profile, Stats stats) {
this(null, name, input, profile, stats);
}
2021-04-22 21:19:58 +00:00
2021-09-10 00:46:20 +00:00
/**
* Renders map features for all elements from an ESRI Shapefile based on the mapping logic defined in {@code profile}.
* Overrides the coordinate reference system defined in the shapefile.
*
* @param sourceProjection code for the coordinate reference system of the input data, to be parsed by
* {@link CRS#decode(String)}
2021-09-10 00:46:20 +00:00
* @param sourceName string ID for this reader to use in logs and stats
* @param input path to the {@code .shp} file on disk, or a {@code .zip} file containing the shapefile
* components
* @param writer consumer for rendered features
* @param config user-defined parameters controlling number of threads and log interval
* @param profile logic that defines what map features to emit for each source feature
* @param stats to keep track of counters and timings
* @throws IllegalArgumentException if a problem occurs reading the input file
*/
public static void processWithProjection(String sourceProjection, String sourceName, Path input, FeatureGroup writer,
PlanetilerConfig config, Profile profile, Stats stats) {
2021-09-10 00:46:20 +00:00
try (var reader = new ShapefileReader(sourceProjection, sourceName, input, profile, stats)) {
reader.process(writer, config);
}
}
/**
* Renders map features for all elements from an ESRI Shapefile based on the mapping logic defined in {@code profile}.
* Infers the coordinate reference system from the shapefile.
*
* @param sourceName string ID for this reader to use in logs and stats
* @param input path to the {@code .shp} file on disk, or a {@code .zip} file containing the shapefile components
2021-09-10 00:46:20 +00:00
* @param writer consumer for rendered features
* @param config user-defined parameters controlling number of threads and log interval
* @param profile logic that defines what map features to emit for each source feature
* @param stats to keep track of counters and timings
* @throws IllegalArgumentException if a problem occurs reading the input file
*/
public static void process(String sourceName, Path input, FeatureGroup writer, PlanetilerConfig config,
Profile profile,
2021-09-10 00:46:20 +00:00
Stats stats) {
processWithProjection(null, sourceName, input, writer, config, profile, stats);
}
2021-04-22 21:19:58 +00:00
2021-09-10 00:46:20 +00:00
private ShapefileDataStore open(Path path) {
try {
URI uri;
2021-05-01 20:40:44 +00:00
if (FileUtils.hasExtension(path, "zip")) {
try (var zipFs = FileSystems.newFileSystem(path)) {
Path shapeFileInZip = FileUtils.walkFileSystem(zipFs)
.filter(z -> FileUtils.hasExtension(z, "shp"))
2021-04-23 21:01:00 +00:00
.findFirst()
2021-05-01 20:08:20 +00:00
.orElseThrow(() -> new IllegalArgumentException("No .shp file found inside " + path));
2021-05-01 20:40:44 +00:00
uri = shapeFileInZip.toUri();
2021-04-22 21:19:58 +00:00
}
2021-05-01 20:40:44 +00:00
} else if (FileUtils.hasExtension(path, "shp")) {
2021-05-01 20:08:20 +00:00
uri = path.toUri();
2021-04-22 21:19:58 +00:00
} else {
2021-05-01 20:08:20 +00:00
throw new IllegalArgumentException("Invalid shapefile input: " + path + " must be zip or shp");
2021-04-22 21:19:58 +00:00
}
return new ShapefileDataStore(uri.toURL());
} catch (IOException e) {
2021-09-10 00:46:20 +00:00
throw new IllegalArgumentException(e);
2021-04-22 21:19:58 +00:00
}
2021-04-10 09:25:42 +00:00
}
2021-04-11 20:10:28 +00:00
@Override
public long getCount() {
2021-04-22 21:19:58 +00:00
return inputSource.size();
}
@Override
2021-09-10 00:46:20 +00:00
public WorkerPipeline.SourceStep<SimpleFeature> read() {
2021-04-22 21:19:58 +00:00
return next -> {
2021-04-23 09:44:29 +00:00
try (var iter = inputSource.features()) {
2021-06-07 11:46:03 +00:00
long id = 0;
2021-04-23 09:44:29 +00:00
while (iter.hasNext()) {
2021-06-07 11:46:03 +00:00
id++;
2021-09-10 00:46:20 +00:00
org.opengis.feature.simple.SimpleFeature feature = iter.next();
2021-04-23 09:44:29 +00:00
Geometry source = (Geometry) feature.getDefaultGeometry();
2021-05-13 10:25:06 +00:00
Geometry latLonGeometry = source;
if (transformToLatLon != null) {
latLonGeometry = JTS.transform(source, transformToLatLon);
2021-04-23 09:44:29 +00:00
}
2021-05-13 10:25:06 +00:00
if (latLonGeometry != null) {
2021-09-10 00:46:20 +00:00
SimpleFeature geom = SimpleFeature.create(latLonGeometry, new HashMap<>(attributeNames.length), sourceName,
null, id);
2021-04-23 11:26:02 +00:00
for (int i = 1; i < attributeNames.length; i++) {
geom.setTag(attributeNames[i], feature.getAttribute(i));
}
2021-04-23 09:44:29 +00:00
next.accept(geom);
}
2021-04-22 21:19:58 +00:00
}
}
};
2021-04-11 20:10:28 +00:00
}
@Override
2021-04-22 21:19:58 +00:00
public void close() {
dataStore.dispose();
2021-04-10 09:25:42 +00:00
}
}