Ignore:
Timestamp:
2019-01-25T06:45:38+01:00 (6 years ago)
Author:
gerdp
Message:

see #17227 Show error message for erroneous poly file or accept it

  • rewrite of importer and exporter to improve name handling and multipolygon creation
  • add support for "Several of these files may be combined to one multi-sectioned file. In this case each section must be separated from its preceding section by an empty line (combined files are used by the programs osmrelpoly and osmassignpoly)."
  • add unit tests
Location:
applications/editors/josm/plugins/poly
Files:
13 added
6 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/poly

    • Property svn:ignore
      •  

        old new  
         1bin
        12build
        2 bin
         3buildtest
        34checkstyle-josm-poly.xml
        45findbugs-josm-poly.xml
        56javadoc
         7spotbugs-josm-poly.xml
  • applications/editors/josm/plugins/poly/.classpath

    r32680 r34860  
    22<classpath>
    33        <classpathentry kind="src" path="src"/>
    4         <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
     4        <classpathentry kind="src" output="buildtest" path="test/unit">
     5                <attributes>
     6                        <attribute name="test" value="true"/>
     7                </attributes>
     8        </classpathentry>
     9        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
    510        <classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
    6         <classpathentry kind="output" path="bin"/>
     11        <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
     12        <classpathentry kind="output" path="build"/>
    713</classpath>
  • applications/editors/josm/plugins/poly/.project

    r33003 r34860  
    44        <comment></comment>
    55        <projects>
     6                <project>josm</project>
    67        </projects>
    78        <buildSpec>
  • applications/editors/josm/plugins/poly/src/poly/PolyExporter.java

    r34098 r34860  
    22package poly;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
    46import java.io.BufferedWriter;
    57import java.io.File;
    6 import java.io.FileOutputStream;
    78import java.io.IOException;
    8 import java.io.OutputStreamWriter;
    9 import java.util.ArrayList;
    10 import java.util.LinkedHashMap;
    11 import java.util.List;
     9import java.nio.charset.StandardCharsets;
     10import java.nio.file.Files;
     11import java.util.HashSet;
    1212import java.util.Locale;
    13 import java.util.Map;
    14 import java.util.TreeMap;
     13import java.util.Set;
    1514
    1615import org.openstreetmap.josm.data.osm.DataSet;
    1716import org.openstreetmap.josm.data.osm.Node;
    18 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1917import org.openstreetmap.josm.data.osm.Relation;
    2018import org.openstreetmap.josm.data.osm.RelationMember;
     
    2826 *
    2927 * @author zverik
     28 * @author Gerd Petermann
    3029 */
    3130public class PolyExporter extends OsmExporter {
    3231
     32    /**
     33     * Create exporter.
     34     */
    3335    public PolyExporter() {
    3436        super(PolyType.FILE_FILTER);
     
    3840    public void exportData(File file, Layer layer) throws IOException {
    3941        if (layer instanceof OsmDataLayer) {
    40             try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF8"))) {
     42            if (((OsmDataLayer) layer).getDataSet().getWays().stream().anyMatch(w -> !w.isClosed())) {
     43                throw new IOException(tr("Data contains unclosed ways."));
     44            }
     45            try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
    4146                DataSet ds = ((OsmDataLayer) layer).getDataSet();
    42                 Map<Way, Boolean> ways = new TreeMap<>();
    43                 String polygonName = file.getName();
    44                 if (polygonName.indexOf('.') > 0)
    45                     polygonName = polygonName.substring(0, polygonName.indexOf('.'));
    46                 for (Way w : ds.getWays()) {
    47                     if (w.isClosed()) {
    48                         boolean outer = isOuter(w);
    49                         ways.put(w, outer);
    50                         if (w.hasKey("name"))
    51                             polygonName = w.get("name").replace("\n", " ");
     47                HashSet<Way> written = new HashSet<>();
     48                boolean firstFile = true;
     49                String fileName = file.getName();
     50                if (fileName.lastIndexOf('.') > 0) {
     51                    // remove extension
     52                    fileName = fileName.substring(0, fileName.lastIndexOf('.'));
     53                }
     54
     55                for (Relation rel : ds.getRelations()) {
     56                    if (rel.isMultipolygon()) {
     57                        if (!firstFile) {
     58                            writer.newLine();
     59                        }
     60                        writeRelation(writer, fileName, rel, written);
     61                        firstFile = false;
    5262                    }
    5363                }
    54                 ways = sortOuterInner(ways);
    55 
     64               
     65                if (firstFile) {
     66                    writer.write(fileName);
     67                }
    5668                int counter = 1;
    57                 writer.write(polygonName);
    58                 writer.newLine();
    59                 for (Way w : ways.keySet()) {
    60                     if (!ways.get(w))
    61                         writer.write('!');
    62                     writer.write(String.valueOf(counter++));
    63                     writer.newLine();
    64                     for (Node n : w.getNodes()) {
    65                         writer.write(String.format(Locale.ENGLISH, "   %f   %f", n.getCoor().lon(), n.getCoor().lat()));
    66                         writer.newLine();
     69                for (Way w : ds.getWays()) {
     70                    if (!written.contains(w)) {
     71                        writeWay(writer, w, counter);
    6772                    }
    68                     writer.write("END");
    69                     writer.newLine();
    7073                }
    7174                writer.write("END");
     
    7578    }
    7679
    77     private boolean isOuter(Way w) {
    78         for (OsmPrimitive p : w.getReferrers()) {
    79             if (p instanceof Relation && ((Relation) p).isMultipolygon()) {
    80                 for (RelationMember m : ((Relation) p).getMembers()) {
    81                     if (m.refersTo(w) && "inner".equals(m.getRole())) {
    82                         return false;
    83                     }
    84                 }
     80    private static void writeRelation(BufferedWriter writer, String fileName, Relation rel, Set<Way> written) throws IOException {
     81        String polygonName = fileName;
     82        if (rel.getName() != null)
     83            polygonName = rel.getName();
     84        writer.write(polygonName);
     85        writer.newLine();
     86        int counter = 1;
     87        for (RelationMember rm: rel.getMembers()) {
     88            if (rm.isWay()) {
     89                if ("inner".equals(rm.getRole()))
     90                    writer.write('!');
     91                Way w = rm.getWay();
     92                counter = writeWay(writer, w, counter);
     93                written.add(w);
    8594            }
    8695        }
    87         return true;
    8896    }
    8997
    90     private Map<Way, Boolean> sortOuterInner(Map<Way, Boolean> ways) {
    91         LinkedHashMap<Way, Boolean> result = new LinkedHashMap<>(ways.size());
    92         List<Way> inner = new ArrayList<>();
    93         for (Way w : ways.keySet()) {
    94             Boolean outer = ways.get(w);
    95             if (outer)
    96                 result.put(w, outer);
    97             else
    98                 inner.add(w);
     98    private static int writeWay(BufferedWriter writer, Way w, int counter) throws IOException {
     99        String name = w.getName();
     100        if (name == null) {
     101            name = String.valueOf(counter++);
     102        }
     103        writer.write(name);
     104        writer.newLine();
     105
     106        for (Node n : w.getNodes()) {
     107            writer.write(String.format(Locale.ENGLISH, "   %f   %f", n.getCoor().lon(), n.getCoor().lat()));
     108            writer.newLine();
    99109        }
    100         for (Way w : inner) {
    101             result.put(w, Boolean.FALSE);
    102         }
    103         return result;
     110        writer.write("END");
     111        writer.newLine();
     112        return counter;
    104113    }
    105114}
  • applications/editors/josm/plugins/poly/src/poly/PolyImporter.java

    r34546 r34860  
    88import java.io.InputStream;
    99import java.io.InputStreamReader;
     10import java.nio.charset.StandardCharsets;
    1011import java.util.ArrayList;
    1112import java.util.List;
     
    2728import org.openstreetmap.josm.io.IllegalDataException;
    2829import org.openstreetmap.josm.tools.CheckParameterUtil;
    29 import org.xml.sax.SAXException;
    3030
    3131/**
     
    3333 *
    3434 * @author zverik
     35 * @author Gerd Petermann
    3536 */
    3637public class PolyImporter extends OsmImporter {
     38    /**
     39     * Create importer.
     40     */
    3741    public PolyImporter() {
    3842        super(PolyType.FILE_FILTER);
    3943    }
    4044
    41     protected DataSet parseDataSet(final String source) throws IOException, SAXException, IllegalDataException {
    42         return parseDataSet(new CachedFile(source).getInputStream(), NullProgressMonitor.INSTANCE);
     45    protected DataSet parseDataSet(final String source) throws IOException, IllegalDataException {
     46        try (CachedFile cf = new CachedFile(source)) {
     47            return parseDataSet(cf.getInputStream(), NullProgressMonitor.INSTANCE);
     48        }
    4349    }
    4450
     
    4955        CheckParameterUtil.ensureParameterNotNull(in, "in");
    5056
    51         try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF8"))) {
    52             progressMonitor.beginTask(tr("Reading polygon filter file..."), 2);
     57        try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
     58            progressMonitor.beginTask(tr("Reading polygon filter file..."), 3);
    5359            progressMonitor.indeterminateSubTask(tr("Reading polygon filter file..."));
    5460            List<Area> areas = loadPolygon(reader);
     
    6672    }
    6773
    68     private List<Area> loadPolygon(BufferedReader reader) throws IllegalDataException, IOException {
     74    private static List<Area> loadPolygon(BufferedReader reader) throws IllegalDataException, IOException {
    6975        String name = reader.readLine();
    7076        if (name == null || name.trim().length() == 0)
     
    7682        while (true) {
    7783            String line;
    78             do {
    79                 line = reader.readLine();
    80                 if (line == null)
    81                     throw new IllegalDataException("File ended prematurely without END record");
    82                 line = line.trim();
    83             } while (line.length() == 0);
    84 
    85             if (line.equals("END")) {
    86                 if (!parsingSection)
    87                     break;
    88                 else {
     84            line = reader.readLine();
     85            if (line == null)
     86                break;
     87            line = line.trim();
     88            if (line.isEmpty()) {
     89                // empty line is only allowed after a complete file (one or more rings belonging to one polygon)
     90                if (parsingSection)
     91                    throw new IllegalDataException(tr("Empty line in coordinate section"));
     92                area = null;
     93                parsingSection = false;
     94                name = null;
     95            } else if (line.equals("END")) {
     96                if (!parsingSection) {
     97                    area = null;
     98                } else {
    8999                    // an area has been read
    90100                    if (area.getNodeCount() < 2)
    91101                        throw new IllegalDataException(tr("There are less than 2 points in an area"));
    92102                    areas.add(area);
    93                     if (areas.size() == 1)
    94                         areas.get(0).setPolygonName(name);
     103                    area.setPolygonName(name);
    95104                    parsingSection = false;
    96105                }
    97             } else {
     106            } else if (name == null) {
     107                name = line;
     108            } else if (line.length() > 0) {
    98109                if (!parsingSection) {
     110                    if (line.indexOf(' ') >= 0) {
     111                        boolean coordInsteadOfName = false;
     112                        try {
     113                            LatLon ll = parseCoordinate(line);
     114                            if (ll.isValid()) {
     115                                coordInsteadOfName = true;
     116                            }
     117                        } catch (IllegalDataException e) {
     118                            coordInsteadOfName = false;
     119                        }
     120                        if (coordInsteadOfName) {
     121                            throw new IllegalDataException(tr("Found coordinates ''{0}'' instead of name", line));
     122                        }
     123                    }
    99124                    area = new Area(line);
    100125                    parsingSection = true;
    101126                } else {
    102                     // reading area, parse coordinates
    103                     String[] tokens = line.split("\\s+");
    104                     double[] coords = new double[2];
    105                     int tokenCount = 0;
    106                     for (String token : tokens) {
    107                         if (token.length() > 0) {
    108                             if (tokenCount > 2)
    109                                 throw new IllegalDataException(tr("A polygon coordinate line must contain exactly 2 numbers"));
    110                             try {
    111                                 coords[tokenCount++] = Double.parseDouble(token);
    112                             } catch (NumberFormatException e) {
    113                                 throw new IllegalDataException(tr("Unable to parse {0} as a number", token));
    114                             }
    115                         }
    116                     }
    117                     if (tokenCount < 2)
    118                         throw new IllegalDataException(tr("A polygon coordinate line must contain exactly 2 numbers"));
    119                     LatLon coord = new LatLon(coords[1], coords[0]);
     127                    LatLon coord = parseCoordinate(line);
    120128                    if (!coord.isValid()) {
    121129                        // fix small deviations
     
    126134                        if (lat < -90.0 && lat > -95.0) lat = -90.0;
    127135                        if (lat > 90.0 && lat < 95.0) lat = 90.0;
     136                        coord = new LatLon(lat, lon);
    128137                        fixedCoords++;
    129                         coord = new LatLon(lat, lon);
    130138                        if (!coord.isValid())
    131139                            throw new IllegalDataException(tr("Invalid coordinates were found: {0}, {1}", coord.lat(), coord.lon()));
    132140                    }
    133                     area.addNode(coord);
     141                    area.addNode(parseCoordinate(line));
    134142                }
    135143            }
     
    141149    }
    142150
    143     private DataSet constructDataSet(List<Area> areas) {
     151    /**
     152     * Parse a line that should contain two double values which describe a latitude/longitude pair.
     153     * @param line the line to parse
     154     * @return a new LatLon
     155     * @throws IllegalDataException in case of error
     156     */
     157    private static LatLon parseCoordinate(String line) throws IllegalDataException {
     158        String[] tokens = line.split("\\s+");
     159        double[] coords = new double[2];
     160        int tokenCount = 0;
     161        for (String token : tokens) {
     162            if (token.length() > 0) {
     163                if (tokenCount > 2)
     164                    break;
     165                try {
     166                    coords[tokenCount++] = Double.parseDouble(token);
     167                } catch (NumberFormatException e) {
     168                    throw new IllegalDataException(tr("Unable to parse {0} as a number", token));
     169                }
     170            }
     171        }
     172        if (tokenCount != 2)
     173            throw new IllegalDataException(tr("A polygon coordinate line must contain exactly 2 numbers"));
     174        return new LatLon(coords[1], coords[0]);
     175    }
     176
     177    private static DataSet constructDataSet(List<Area> areas) {
    144178        DataSet ds = new DataSet();
    145179        ds.setUploadPolicy(UploadPolicy.DISCOURAGED);
    146180
    147         boolean foundInner = false;
     181       
     182        List<Area> curretSet = new ArrayList<>();
     183       
    148184        for (Area area : areas) {
    149             if (!area.isOuter())
    150                 foundInner = true;
    151             area.constructWay(ds);
    152         }
    153 
    154         if (foundInner) {
     185            if (!curretSet.isEmpty() && !area.polygonName.equals(curretSet.get(0).polygonName)) {
     186                constructPrimitive(ds, curretSet);
     187                curretSet.clear();
     188            }
     189            curretSet.add(area);
     190        }
     191        if (!curretSet.isEmpty())
     192            constructPrimitive(ds, curretSet);
     193
     194        return ds;
     195    }
     196
     197    private static void constructPrimitive(DataSet ds, List<Area> areas) {
     198        boolean isMultipolygon = areas.size() > 1;
     199        for (Area area : areas) {
     200            area.constructWay(ds, isMultipolygon);
     201        }
     202
     203        if (isMultipolygon) {
    155204            Relation mp = new Relation();
    156205            mp.put("type", "multipolygon");
     206            Area outer = areas.get(0);
     207            if (outer.polygonName != null) {
     208                mp.put("name", outer.polygonName);
     209            }
    157210            for (Area area : areas) {
    158211                mp.addMember(new RelationMember(area.isOuter() ? "outer" : "inner", area.getWay()));
     
    160213            ds.addPrimitive(mp);
    161214        }
    162 
    163         return ds;
    164215    }
    165216
     
    178229            nodes = new ArrayList<>();
    179230            way = null;
    180             polygonName = null;
     231            polygonName = "";
    181232        }
    182233
    183234        public void setPolygonName(String polygonName) {
    184             this.polygonName = polygonName;
     235            if (polygonName != null) {
     236                this.polygonName = polygonName;
     237            }
    185238        }
    186239
     
    202255        }
    203256
    204         public void constructWay(DataSet ds) {
     257        public void constructWay(DataSet ds, boolean isMultipolygon) {
    205258            way = new Way();
    206259            for (LatLon coord : nodes) {
     
    210263            }
    211264            way.addNode(way.getNode(0));
    212             if (polygonName != null)
    213                 way.put("name", polygonName);
     265            if (isMultipolygon && name != null)
     266                way.put("name", name);
     267            else {
     268                if (polygonName != null)
     269                    way.put("name", polygonName);
     270            }
    214271            ds.addPrimitive(way);
    215272        }
  • applications/editors/josm/plugins/poly/src/poly/PolyType.java

    r34546 r34860  
    1010 *
    1111 * @author zverik
     12 * @author Gerd Petermann
    1213 */
    13 public interface PolyType {
    14     String EXTENSION = "poly";
    15     ExtensionFileFilter FILE_FILTER = new ExtensionFileFilter(
     14
     15final class PolyType {
     16    private static final String EXTENSION = "poly";
     17   
     18    /** filter for osmosis poly files */
     19    static final ExtensionFileFilter FILE_FILTER = new ExtensionFileFilter(
    1620            EXTENSION, EXTENSION, tr("Osmosis polygon filter files") + " (*." + EXTENSION + ")");
     21    private PolyType() {}
    1722}
Note: See TracChangeset for help on using the changeset viewer.