// License: GPL. For details, see LICENSE file. package poly; import static org.openstreetmap.josm.tools.I18n.tr; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.progress.NullProgressMonitor; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.io.CachedFile; import org.openstreetmap.josm.io.IllegalDataException; import org.openstreetmap.josm.io.OsmImporter; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.xml.sax.SAXException; /** * Imports poly files. * * @author zverik */ public class PolyImporter extends OsmImporter { public PolyImporter() { super(PolyType.FILE_FILTER); } protected DataSet parseDataSet(final String source) throws IOException, SAXException, IllegalDataException { return parseDataSet(new CachedFile(source).getInputStream(), NullProgressMonitor.INSTANCE); } @Override protected DataSet parseDataSet(InputStream in, ProgressMonitor progressMonitor) throws IllegalDataException { if (progressMonitor == null) progressMonitor = NullProgressMonitor.INSTANCE; CheckParameterUtil.ensureParameterNotNull(in, "in"); try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF8"))) { progressMonitor.beginTask(tr("Reading polygon filter file..."), 2); progressMonitor.indeterminateSubTask(tr("Reading polygon filter file...")); List<Area> areas = loadPolygon(reader); progressMonitor.worked(1); progressMonitor.indeterminateSubTask(tr("Preparing data set...")); DataSet ds = constructDataSet(areas); progressMonitor.worked(1); return ds; } catch (IOException e) { throw new IllegalDataException(tr("Error reading poly file: {0}", e.getMessage()), e); } finally { progressMonitor.finishTask(); } } private List<Area> loadPolygon(BufferedReader reader) throws IllegalDataException, IOException { String name = reader.readLine(); if (name == null || name.trim().length() == 0) throw new IllegalDataException(tr("The file must begin with a polygon name")); List<Area> areas = new ArrayList<>(); Area area = null; boolean parsingSection = false; int fixedCoords = 0; while (true) { String line; do { line = reader.readLine(); if (line == null) throw new IllegalDataException("File ended prematurely without END record"); line = line.trim(); } while (line.length() == 0); if (line.equals("END")) { if (!parsingSection) break; else { // an area has been read if (area.getNodeCount() < 2) throw new IllegalDataException(tr("There are less than 2 points in an area")); areas.add(area); if (areas.size() == 1) areas.get(0).setPolygonName(name); parsingSection = false; } } else { if (!parsingSection) { area = new Area(line); parsingSection = true; } else { // reading area, parse coordinates String[] tokens = line.split("\\s+"); double[] coords = new double[2]; int tokenCount = 0; for (String token : tokens) { if (token.length() > 0) { if (tokenCount > 2) throw new IllegalDataException(tr("A polygon coordinate line must contain exactly 2 numbers")); try { coords[tokenCount++] = Double.parseDouble(token); } catch (NumberFormatException e) { throw new IllegalDataException(tr("Unable to parse {0} as a number", token)); } } } if (tokenCount < 2) throw new IllegalDataException(tr("A polygon coordinate line must contain exactly 2 numbers")); LatLon coord = new LatLon(coords[1], coords[0]); if (!coord.isValid()) { // fix small deviations double lat = coord.lat(); double lon = coord.lon(); if (lon < -180.0 && lon > -185.0) lon = -180.0; if (lon > 180.0 && lon < 185.0) lon = 180.0; if (lat < -90.0 && lat > -95.0) lat = -90.0; if (lat > 90.0 && lat < 95.0) lat = 90.0; fixedCoords++; coord = new LatLon(lat, lon); if (!coord.isValid()) throw new IllegalDataException(tr("Invalid coordinates were found: {0}, {1}", coord.lat(), coord.lon())); } area.addNode(coord); } } } if (fixedCoords > 0) JOptionPane.showMessageDialog(Main.parent, tr("{0} points were outside world bounds and were moved", fixedCoords), "Import poly", JOptionPane.WARNING_MESSAGE); return areas; } private DataSet constructDataSet(List<Area> areas) { DataSet ds = new DataSet(); ds.setUploadDiscouraged(true); boolean foundInner = false; for (Area area : areas) { if (!area.isOuter()) foundInner = true; area.constructWay(ds); } if (foundInner) { Relation mp = new Relation(); mp.put("type", "multipolygon"); for (Area area : areas) { mp.addMember(new RelationMember(area.isOuter() ? "outer" : "inner", area.getWay())); } ds.addPrimitive(mp); } return ds; } private class Area { private String name; private String polygonName; private List<LatLon> nodes; private boolean outer; private Way way; Area(String name) { this.name = name; outer = name.charAt(0) != '!'; if (!outer) this.name = this.name.substring(1); nodes = new ArrayList<>(); way = null; polygonName = null; } public void setPolygonName(String polygonName) { this.polygonName = polygonName; } public void addNode(LatLon node) { if (nodes.isEmpty() || !(nodes.get(nodes.size()-1).equals(node) || nodes.get(0).equals(node))) nodes.add(node); } public boolean isOuter() { return outer; } public int getNodeCount() { return nodes.size(); } public Way getWay() { return way; } public void constructWay(DataSet ds) { way = new Way(); for (LatLon coord : nodes) { Node node = new Node(coord); ds.addPrimitive(node); way.addNode(node); } way.addNode(way.getNode(0)); if (polygonName != null) way.put("name", polygonName); ds.addPrimitive(way); } } }