// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.areafilter.common; import java.awt.geom.Area; import java.awt.geom.Path2D; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; /** * Reads the contents of a polygon file into an Area instance. * <p> * The file format is defined at http://www.maproom.psu.edu/dcw/. An example is * provided here. The first line contains the name of the file, the second line * contains the name of an individual polygon and if it is prefixed with ! it * means it is a negative polygon to be subtracted from the resultant extraction * polygon. * <pre> * australia_v * 1 * 0.1446763E+03 -0.3825659E+02 * 0.1446693E+03 -0.3826255E+02 * 0.1446627E+03 -0.3825661E+02 * 0.1446763E+03 -0.3824465E+02 * 0.1446813E+03 -0.3824343E+02 * 0.1446824E+03 -0.3824484E+02 * 0.1446826E+03 -0.3825356E+02 * 0.1446876E+03 -0.3825210E+02 * 0.1446919E+03 -0.3824719E+02 * 0.1447006E+03 -0.3824723E+02 * 0.1447042E+03 -0.3825078E+02 * 0.1446758E+03 -0.3826229E+02 * 0.1446693E+03 -0.3826255E+02 * END * !2 * 0.1422483E+03 -0.3839481E+02 * 0.1422436E+03 -0.3839315E+02 * 0.1422496E+03 -0.3839070E+02 * 0.1422543E+03 -0.3839025E+02 * 0.1422574E+03 -0.3839155E+02 * 0.1422467E+03 -0.3840065E+02 * 0.1422433E+03 -0.3840048E+02 * 0.1422420E+03 -0.3839857E+02 * 0.1422436E+03 -0.3839315E+02 * END * END * </pre> * * @author Brett Henderson */ public class PolygonFileReader { /** * The file containing polygon data. */ private File polygonFile; /** * The name of the polygon as stated in the file-header. */ private String myPolygonName; /** * Creates a new instance. * * @param polygonFile * The file to read polygon units from. */ public PolygonFileReader(final File polygonFile) { this.polygonFile = polygonFile; } /** * Builds an Area configured with the polygon information defined in the * file. * * @return A fully configured area. */ public Area loadPolygon() { try (BufferedReader bufferedReader = new BufferedReader(new FileReader(polygonFile))) { Area resultArea; // Create a new area. resultArea = new Area(); // Read the file header. myPolygonName = bufferedReader.readLine(); if (myPolygonName == null || myPolygonName.trim().length() == 0) { throw new OsmosisRuntimeException("The file must begin with a header naming the polygon file."); } // We now loop until no more sections are available. while (true) { String sectionHeader; boolean positivePolygon; Area sectionArea; // Read until a non-empty line is obtained. do { // Read the section header. sectionHeader = bufferedReader.readLine(); // It is invalid for the file to end without a global "END" record. if (sectionHeader == null) { throw new OsmosisRuntimeException("File terminated prematurely without a section END record."); } // Remove any whitespace. sectionHeader = sectionHeader.trim(); } while (sectionHeader.length() == 0); // Stop reading when the global END record is reached. if ("END".equals(sectionHeader)) { break; } // If the section header begins with a ! then the polygon is to // be subtracted from the result area. positivePolygon = (sectionHeader.charAt(0) != '!'); // Create an area for this polygon. sectionArea = loadSectionPolygon(bufferedReader); // Add or subtract the section area from the overall area as // appropriate. if (positivePolygon) { resultArea.add(sectionArea); } else { resultArea.subtract(sectionArea); } } return resultArea; } catch (IOException e) { throw new OsmosisRuntimeException("Unable to read from polygon file " + polygonFile + ".", e); } } /** * Loads an individual polygon from the polygon file. * * @param bufferedReader * The reader connected to the polygon file placed at the first * record of a polygon section. * @return An area representing the section polygon. */ private Area loadSectionPolygon(BufferedReader bufferedReader) throws IOException { Path2D.Double polygonPath; double[] beginPoint = null; // Create a new path to represent this polygon. polygonPath = new Path2D.Double(); while (true) { String sectionLine; double[] coordinates; // Read until a non-empty line is obtained. do { sectionLine = bufferedReader.readLine(); // It is invalid for the file to end without a section "END" record. if (sectionLine == null) { throw new OsmosisRuntimeException("File terminated prematurely without a section END record."); } // Remove any whitespace. sectionLine = sectionLine.trim(); } while (sectionLine.length() == 0); // Stop reading when the section END record is reached. if ("END".equals(sectionLine)) { break; } // Parse the line into its coordinates. coordinates = parseCoordinates(sectionLine); // Add the current point to the path. if (beginPoint != null) { polygonPath.lineTo(coordinates[0], coordinates[1]); } else { polygonPath.moveTo(coordinates[0], coordinates[1]); beginPoint = coordinates; } } // If we received data, draw another line from the final point back to the beginning point. if (beginPoint != null) { polygonPath.moveTo(beginPoint[0], beginPoint[1]); } // Convert the path into an area and return. return new Area(polygonPath); } /** * Parses a coordinate line into its constituent double precision * coordinates. * * @param coordinateLine * The raw file line. * @return A pair of coordinate values, first is longitude, second is * latitude. */ private double[] parseCoordinates(String coordinateLine) { String[] rawTokens; double[] results; int tokenCount; // Split the line into its sub strings separated by whitespace. rawTokens = coordinateLine.split("\\s"); // Copy the non-zero tokens into a result array. tokenCount = 0; results = new double[2]; for (int i = 0; i < rawTokens.length; i++) { if (rawTokens[i].length() > 0) { // Ensure we have no more than 2 coordinate values. if (tokenCount >= 2) { throw new OsmosisRuntimeException( "A polygon coordinate line must contain 2 numbers, not (" + coordinateLine + ")." ); } // Parse the token into a double precision number. try { results[tokenCount++] = Double.parseDouble(rawTokens[i]); } catch (NumberFormatException e) { throw new OsmosisRuntimeException( "Unable to parse " + rawTokens[i] + " into a double precision number."); } } } // Ensure we found two tokens. if (tokenCount < 2) { throw new OsmosisRuntimeException("Could not find two coordinates on line (" + coordinateLine + ")."); } return results; } /** * This method must only be called after {@link #loadPolygon()}. * @return The name of the polygon as stated in the file-header. */ public String getPolygonName() { return myPolygonName; } }