// 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); } }