// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.opendata.core.io.geographic;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.JOptionPane;
import org.geotools.data.DataStore;
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.opengis.feature.Feature;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.Property;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.plugins.opendata.core.datasets.AbstractDataSetHandler;
import org.openstreetmap.josm.plugins.opendata.core.datasets.NationalHandlers;
import org.openstreetmap.josm.tools.UserCancelException;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
public class ShpReader extends GeographicReader {
private final ShpHandler handler;
private final Set<OsmPrimitive> featurePrimitives = new HashSet<>();
public ShpReader(ShpHandler handler) {
super(handler, NationalHandlers.DEFAULT_SHP_HANDLERS);
this.handler = handler;
}
public static DataSet parseDataSet(InputStream in, File file,
AbstractDataSetHandler handler, ProgressMonitor instance) throws IOException {
if (in != null) {
in.close();
}
try {
return new ShpReader(handler != null ? handler.getShpHandler() : null).parse(file, instance);
} catch (IOException e) {
throw e;
} catch (Throwable t) {
throw new IOException(t);
}
}
private void parseFeature(Feature feature, final Component parent) throws UserCancelException, GeoMathTransformException,
FactoryException, GeoCrsException, MismatchedDimensionException, TransformException {
featurePrimitives.clear();
GeometryAttribute geometry = feature.getDefaultGeometryProperty();
if (geometry != null) {
GeometryDescriptor desc = geometry.getDescriptor();
if (crs == null) {
if (desc != null && desc.getCoordinateReferenceSystem() != null) {
crs = desc.getCoordinateReferenceSystem();
} else if (!GraphicsEnvironment.isHeadless()) {
GuiHelper.runInEDTAndWait(new Runnable() {
@Override
public void run() {
if (0 == JOptionPane.showConfirmDialog(
parent,
tr("Unable to detect Coordinate Reference System.\nWould you like to fallback to ESPG:4326 (WGS 84) ?"),
tr("Warning: CRS not found"),
JOptionPane.YES_NO_CANCEL_OPTION
)) {
crs = wgs84;
}
}
});
} else {
// Always use WGS84 in headless mode (used for unit tests only)
crs = wgs84;
}
if (crs != null) {
findMathTransform(parent, true);
} else {
throw new GeoCrsException(tr("Unable to detect CRS !"));
}
}
OsmPrimitive primitive = null;
if (geometry.getValue() instanceof Point) {
primitive = createOrGetEmptyNode((Point) geometry.getValue());
} else if (geometry.getValue() instanceof GeometryCollection) { // Deals with both MultiLineString and MultiPolygon
GeometryCollection mp = (GeometryCollection) geometry.getValue();
int nGeometries = mp.getNumGeometries();
if (nGeometries < 1) {
Main.error("empty geometry collection found");
} else {
Relation r = null;
Way w = null;
for (int i = 0; i < nGeometries; i++) {
Geometry g = mp.getGeometryN(i);
if (g instanceof Polygon) {
Polygon p = (Polygon) g;
// Do not create relation if there's only one polygon without interior ring
// except if handler prefers it
if (r == null && (nGeometries > 1 || p.getNumInteriorRing() > 0 ||
(handler != null && handler.preferMultipolygonToSimpleWay()))) {
r = createMultipolygon();
}
w = createOrGetWay(p.getExteriorRing());
if (r != null) {
addWayToMp(r, "outer", w);
for (int j = 0; j < p.getNumInteriorRing(); j++) {
addWayToMp(r, "inner", createOrGetWay(p.getInteriorRingN(j)));
}
}
} else if (g instanceof LineString) {
w = createOrGetWay((LineString) g);
} else if (g instanceof Point) {
// Some belgian data sets hold points into collections ?!
readNonGeometricAttributes(feature, createOrGetNode((Point) g));
} else {
Main.error("unsupported geometry : "+g);
}
}
primitive = r != null ? r : w;
}
} else {
// Debug unknown geometry
Main.debug("\ttype: "+geometry.getType());
Main.debug("\tbounds: "+geometry.getBounds());
Main.debug("\tdescriptor: "+desc);
Main.debug("\tname: "+geometry.getName());
Main.debug("\tvalue: "+geometry.getValue());
Main.debug("\tid: "+geometry.getIdentifier());
Main.debug("-------------------------------------------------------------");
}
if (primitive != null) {
// Read primitive non geometric attributes
readNonGeometricAttributes(feature, primitive);
}
}
}
public DataSet parse(File file, ProgressMonitor instance) throws IOException {
crs = null;
transform = null;
try {
if (file != null) {
Map<String, Serializable> params = new HashMap<>();
Charset charset = null;
params.put(ShapefileDataStoreFactory.URLP.key, file.toURI().toURL());
if (handler != null && handler.getDbfCharset() != null) {
charset = handler.getDbfCharset();
} else {
String path = file.getAbsolutePath();
// See http://gis.stackexchange.com/a/3663/17245
path = path.substring(0, path.lastIndexOf('.')) + ".cpg";
Path cpg = new File(path).toPath();
if (Files.exists(cpg)) {
try (BufferedReader reader = Files.newBufferedReader(cpg, StandardCharsets.UTF_8)) {
charset = Charset.forName(reader.readLine());
} catch (IOException | UnsupportedCharsetException | IllegalCharsetNameException e) {
Main.warn(e);
}
}
}
if (charset != null) {
Main.info("Using charset "+charset);
params.put(ShapefileDataStoreFactory.DBFCHARSET.key, charset.name());
}
DataStore dataStore = new ShapefileDataStoreFactory().createDataStore(params);
if (dataStore == null) {
throw new IOException(tr("Unable to find a data store for file {0}", file.getName()));
}
String[] typeNames = dataStore.getTypeNames();
String typeName = typeNames[0];
FeatureSource<?, ?> featureSource = dataStore.getFeatureSource(typeName);
FeatureCollection<?, ?> collection = featureSource.getFeatures();
FeatureIterator<?> iterator = collection.features();
if (instance != null) {
instance.beginTask(tr("Loading shapefile ({0} features)", collection.size()), collection.size());
}
int n = 0;
Component parent = instance != null ? instance.getWindowParent() : Main.parent;
try {
while (iterator.hasNext()) {
n++;
try {
Feature feature = iterator.next();
parseFeature(feature, parent);
if (handler != null) {
handler.notifyFeatureParsed(feature, ds, featurePrimitives);
}
} catch (UserCancelException e) {
e.printStackTrace();
return ds;
}
if (instance != null) {
instance.worked(1);
instance.setCustomText(n+"/"+collection.size());
}
}
} finally {
iterator.close();
nodes.clear();
if (instance != null) {
instance.setCustomText(null);
}
}
}
} catch (IOException e) {
e.printStackTrace();
throw e;
} catch (Exception e) {
e.printStackTrace();
throw new IOException(e);
}
return ds;
}
private static void readNonGeometricAttributes(Feature feature, OsmPrimitive primitive) {
try {
for (Property prop : feature.getProperties()) {
if (!(prop instanceof GeometryAttribute)) {
Name name = prop.getName();
Object value = prop.getValue();
if (name != null && value != null) {
String sName = name.toString();
String sValue = value.toString();
if (value instanceof Date) {
sValue = new SimpleDateFormat("yyyy-MM-dd").format(value);
}
if (!sName.isEmpty() && !sValue.isEmpty()) {
primitive.put(sName, sValue);
}
}
}
}
} catch (Exception e) {
Main.error(e);
}
}
@Override
protected Node createOrGetNode(Point p) throws MismatchedDimensionException, TransformException {
Node n = super.createOrGetNode(p);
featurePrimitives.add(n);
return n;
}
@Override
protected <T extends OsmPrimitive> T addOsmPrimitive(T p) {
featurePrimitives.add(p);
return super.addOsmPrimitive(p);
}
}