Ticket #13307: improve_MultipolygonTest_v6.patch

File improve_MultipolygonTest_v6.patch, 29.1 KB (added by anonymous, 8 years ago)
  • src/org/openstreetmap/josm/command/RemoveRepeatedRelationMembersCommand.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.command;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.ArrayList;
     7import java.util.Collection;
     8import java.util.HashSet;
     9import java.util.List;
     10import java.util.Objects;
     11
     12import javax.swing.Icon;
     13
     14import org.openstreetmap.josm.Main;
     15import org.openstreetmap.josm.data.osm.OsmPrimitive;
     16import org.openstreetmap.josm.data.osm.Relation;
     17import org.openstreetmap.josm.data.osm.RelationMember;
     18import org.openstreetmap.josm.gui.DefaultNameFormatter;
     19import org.openstreetmap.josm.tools.ImageProvider;
     20
     21/**
     22 * Command that removes repeated relation members (first occurrence is kept)
     23 *
     24 * @author Gerd Petermann
     25 */
     26public class RemoveRepeatedRelationMembersCommand extends Command {
     27
     28    // The relation to be changed
     29    private final Relation relation;
     30    // Old value of modified
     31    private Boolean oldModified;
     32    private final List<OsmPrimitive> repeatedPrims;
     33    private List<RelationMember> oldMembers;
     34
     35
     36    /**
     37     * @param r the relation
     38     * @param membersToRemove list of members to remove
     39     */
     40    public RemoveRepeatedRelationMembersCommand(Relation r, List<OsmPrimitive> membersToRemove) {
     41        this.relation = r;
     42        this.repeatedPrims = membersToRemove;
     43    }
     44
     45    @Override
     46    public boolean executeCommand() {
     47        boolean executed = false;
     48        long t1 = System.currentTimeMillis();
     49        oldMembers = relation.getMembers();
     50
     51        List<RelationMember> newMembers = new ArrayList<>();
     52        HashSet<OsmPrimitive> toRemove = new HashSet<>(repeatedPrims);
     53        HashSet<OsmPrimitive> found = new HashSet<>(repeatedPrims.size());
     54        for (RelationMember rm : oldMembers) {
     55            if (toRemove.contains(rm.getMember())) {
     56                if (found.contains(rm.getMember()) == false) {
     57                    found.add(rm.getMember());
     58                    newMembers.add(rm);
     59                }
     60            } else
     61                newMembers.add(rm);
     62        }
     63        oldModified = relation.isModified();
     64        relation.setMembers(newMembers);
     65        long t2 = System.currentTimeMillis();
     66        Main.debug("remove repeated relation members action took " + (t2 - t1) + " ms, removed"
     67                + (oldMembers.size() - newMembers.size()) + " members");
     68        return executed;
     69    }
     70
     71    @Override
     72    public void undoCommand() {
     73        if (oldMembers != null) {
     74            relation.setMembers(oldMembers);
     75            if (oldModified != null) {
     76                relation.setModified(oldModified);
     77            }
     78        }
     79    }
     80
     81    @Override
     82    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
     83        modified.add(relation);
     84    }
     85
     86    @Override
     87    public String getDescriptionText() {
     88        return tr("Remove relation member(s) from {0}",
     89                relation.getDisplayName(DefaultNameFormatter.getInstance()));
     90    }
     91
     92    @Override
     93    public Icon getDescriptionIcon() {
     94        return ImageProvider.get(relation.getDisplayType());
     95    }
     96
     97    @Override
     98    public int hashCode() {
     99        return Objects.hash(super.hashCode(), relation, repeatedPrims.hashCode());
     100    }
     101
     102    @Override
     103    public boolean equals(Object obj) {
     104        if (this == obj) return true;
     105        if (obj == null || getClass() != obj.getClass()) return false;
     106        if (!super.equals(obj)) return false;
     107        RemoveRepeatedRelationMembersCommand that = (RemoveRepeatedRelationMembersCommand) obj;
     108        return
     109                Objects.equals(relation, that.relation) &&
     110                repeatedPrims.size() == that.repeatedPrims.size() &&
     111                repeatedPrims.containsAll(that.repeatedPrims);
     112    }
     113}
  • src/org/openstreetmap/josm/data/osm/WaySegment.java

     
    114114                s2.getSecondNode().getEastNorth().east(), s2.getSecondNode().getEastNorth().north());
    115115    }
    116116
     117    /**
     118     * Checks whether this segment and another way segment share the same points
     119     * @param s2 The other segment
     120     * @return true if other way segment is the same or reverse
     121     * @since r10819
     122     */
     123    public boolean isSimilar(WaySegment s2) {
     124        if (getFirstNode().equals(s2.getFirstNode()) && getSecondNode().equals(s2.getSecondNode()))
     125            return true;
     126        if (getFirstNode().equals(s2.getSecondNode()) && getSecondNode().equals(s2.getFirstNode()))
     127            return true;
     128        return false;
     129    }
     130
    117131    @Override
    118132    public String toString() {
    119133        return "WaySegment [way=" + way.getUniqueId() + ", lowerIndex=" + lowerIndex + ']';
  • src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java

     
    99import java.util.Collections;
    1010import java.util.HashSet;
    1111import java.util.Iterator;
     12import java.util.LinkedHashSet;
    1213import java.util.List;
    1314import java.util.Set;
    1415
     
    175176         */
    176177        public JoinedWay(List<Node> nodes, Collection<Long> wayIds, boolean selected) {
    177178            this.nodes = new ArrayList<>(nodes);
    178             this.wayIds = new ArrayList<>(wayIds);
     179            this.wayIds = new LinkedHashSet<>(wayIds);
    179180            this.selected = selected;
    180181        }
    181182
  • src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55import static org.openstreetmap.josm.tools.I18n.trn;
    66
    7 import java.awt.geom.GeneralPath;
     7import java.awt.geom.Path2D;
     8import java.awt.geom.Point2D;
    89import java.text.MessageFormat;
    910import java.util.ArrayList;
    1011import java.util.Arrays;
    1112import java.util.Collection;
    1213import java.util.Collections;
     14import java.util.HashMap;
    1315import java.util.HashSet;
    1416import java.util.LinkedList;
    1517import java.util.List;
     18import java.util.Map;
     19import java.util.Map.Entry;
    1620import java.util.Set;
    1721
    1822import org.openstreetmap.josm.Main;
    1923import org.openstreetmap.josm.actions.CreateMultipolygonAction;
     24import org.openstreetmap.josm.command.Command;
     25import org.openstreetmap.josm.command.RemoveRepeatedRelationMembersCommand;
     26import org.openstreetmap.josm.data.coor.EastNorth;
     27import org.openstreetmap.josm.data.coor.LatLon;
    2028import org.openstreetmap.josm.data.osm.Node;
    2129import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2230import org.openstreetmap.josm.data.osm.Relation;
    2331import org.openstreetmap.josm.data.osm.RelationMember;
    2432import org.openstreetmap.josm.data.osm.Way;
     33import org.openstreetmap.josm.data.osm.WaySegment;
    2534import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
    2635import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
    27 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
    2836import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
    2937import org.openstreetmap.josm.data.validation.OsmValidator;
    3038import org.openstreetmap.josm.data.validation.Severity;
    3139import org.openstreetmap.josm.data.validation.Test;
    3240import org.openstreetmap.josm.data.validation.TestError;
     41import org.openstreetmap.josm.data.validation.util.ValUtil;
    3342import org.openstreetmap.josm.gui.DefaultNameFormatter;
    3443import org.openstreetmap.josm.gui.mappaint.ElemStyles;
    3544import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
     
    6776    public static final int NO_STYLE_POLYGON = 1611;
    6877    /** Area style on outer way */
    6978    public static final int OUTER_STYLE = 1613;
     79    /** Multipolygon member repeated (same primitive, same role */
     80    public static final int REPEATED_MEMBER_SAME_ROLE = 1614;
     81    /** Multipolygon member repeated (same primitive, different role) */
     82    public static final int REPEATED_MEMBER_DIFF_ROLE = 1615;
    7083
    7184    private static volatile ElemStyles styles;
    7285
    7386    private final Set<String> keysCheckedByAnotherTest = new HashSet<>();
     87    /** All way segments, grouped by cells */
     88    private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000);
     89    /** The already detected ways in error */
     90    private final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50);
    7491
    7592    /**
    7693     * Constructs a new {@code MultipolygonTest}.
     
    103120        super.endTest();
    104121    }
    105122
    106     private static GeneralPath createPath(List<Node> nodes) {
    107         GeneralPath result = new GeneralPath();
    108         result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());
     123    private static Path2D.Double createPath(List<Node> nodes) {
     124        Path2D.Double result = new Path2D.Double();
     125        result.moveTo(nodes.get(0).getCoor().lon(), nodes.get(0).getCoor().lat());
    109126        for (int i = 1; i < nodes.size(); i++) {
    110127            Node n = nodes.get(i);
    111             result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon());
     128            result.lineTo(n.getCoor().lon(), n.getCoor().lat());
    112129        }
    113130        return result;
    114131    }
    115132
    116     private static List<GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {
    117         List<GeneralPath> result = new ArrayList<>();
     133    private static List<Path2D.Double> createPolygons(List<Multipolygon.PolyData> joinedWays) {
     134        List<Path2D.Double> result = new ArrayList<>();
    118135        for (Multipolygon.PolyData way : joinedWays) {
    119136            result.add(createPath(way.getNodes()));
    120137        }
     
    121138        return result;
    122139    }
    123140
    124     private static Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) {
    125         boolean inside = false;
    126         boolean outside = false;
    127 
    128         for (Node n : inner) {
    129             boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon());
    130             inside = inside | contains;
    131             outside = outside | !contains;
    132             if (inside & outside) {
    133                 return Intersection.CROSSING;
    134             }
    135         }
    136 
    137         return inside ? Intersection.INSIDE : Intersection.OUTSIDE;
    138     }
    139 
    140141    @Override
    141142    public void visit(Way w) {
    142143        if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) {
     
    155156    @Override
    156157    public void visit(Relation r) {
    157158        if (r.isMultipolygon()) {
     159            crossingWays.clear();
     160            cellSegments.clear();
     161
    158162            checkMembersAndRoles(r);
     163            checkRepeatedMembers(r);
    159164            checkOuterWay(r);
    160165
    161166            // Rest of checks is only for complete multipolygons
     
    163168                Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r);
    164169
    165170                // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match.
    166                 checkMemberRoleCorrectness(r);
     171                boolean rolesWereChecked = checkMemberRoleCorrectness(r);
    167172                checkStyleConsistency(r, polygon);
    168                 checkGeometry(r, polygon);
     173                checkGeometry(r, polygon, rolesWereChecked);
    169174            }
    170175        }
    171176    }
     
    194199     * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li>
    195200     * </ul>
    196201     * @param r relation
     202     * @return true if member roles were checked
    197203     */
    198     private void checkMemberRoleCorrectness(Relation r) {
     204    private boolean checkMemberRoleCorrectness(Relation r) {
    199205        final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false);
    200206        if (newMP != null) {
    201207            for (RelationMember member : r.getMembers()) {
     
    216222                }
    217223            }
    218224        }
     225        return newMP != null;
    219226    }
    220227
    221228    /**
     
    285292        }
    286293    }
    287294
     295    private static class LatLonPolyData {
     296        final PolyData pd;
     297        final Path2D.Double latLonPath;
     298
     299        LatLonPolyData(PolyData polyData, Path2D.Double path) {
     300            this.pd = polyData;
     301            this.latLonPath = path;
     302        }
     303    }
     304
    288305    /**
    289306     * Various geometry-related checks:<ul>
    290307     * <li>{@link #NON_CLOSED_WAY}: Multipolygon is not closed</li>
     
    293310     * </ul>
    294311     * @param r relation
    295312     * @param polygon multipolygon
     313     * @param rolesWereChecked might be used to skip most of the tests below
    296314     */
    297     private void checkGeometry(Relation r, Multipolygon polygon) {
     315    private void checkGeometry(Relation r, Multipolygon polygon, boolean rolesWereChecked) {
    298316        List<Node> openNodes = polygon.getOpenEnds();
    299317        if (!openNodes.isEmpty()) {
    300318            List<OsmPrimitive> primitives = new LinkedList<>();
    301319            primitives.add(r);
    302320            primitives.addAll(openNodes);
    303             addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes));
     321            addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY,
     322                    primitives, openNodes));
    304323        }
     324        List<PolyData> innerPolygons = polygon.getInnerPolygons();
     325        List<PolyData> outerPolygons = polygon.getOuterPolygons();
    305326
     327        HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons);
     328
     329        //Polygons may intersect without crossing ways when one polygon lies completely inside the other
     330        List<LatLonPolyData> inner = calcLatLonPolygons(innerPolygons);
     331        List<LatLonPolyData> outer = calcLatLonPolygons(outerPolygons);
    306332        // For painting is used Polygon class which works with ints only. For validation we need more precision
    307         List<PolyData> innerPolygons = polygon.getInnerPolygons();
    308         List<PolyData> outerPolygons = polygon.getOuterPolygons();
    309         List<GeneralPath> innerPolygonsPaths = innerPolygons.isEmpty() ? Collections.<GeneralPath>emptyList() : createPolygons(innerPolygons);
    310         List<GeneralPath> outerPolygonsPaths = createPolygons(outerPolygons);
    311         for (int i = 0; i < outerPolygons.size(); i++) {
    312             PolyData pdOuter = outerPolygons.get(i);
    313             // Check for intersection between outer members
    314             for (int j = i+1; j < outerPolygons.size(); j++) {
    315                 checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdOuter, j);
     333        for (int i = 0; i + 1 < outer.size(); i++) {
     334            // report outer polygons which lie inside another outer
     335            LatLonPolyData outer1 = outer.get(i);
     336            for (int j = 0; j < outer.size(); j++) {
     337                if (i != j && !crossing(crossingPolyMap, outer1.pd, outer.get(j).pd)) {
     338                    LatLon c = outer.get(j).pd.getNodes().get(0).getCoor();
     339                    if (outer1.latLonPath.contains(c.lon(), c.lat())) {
     340                        addError(r,
     341                                new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
     342                                        CROSSING_WAYS, Collections.singletonList(r),
     343                                        Arrays.asList(outer1.pd.getNodes(), outer.get(j).pd.getNodes())));
     344                    }
     345                }
    316346            }
    317347        }
    318         for (int i = 0; i < innerPolygons.size(); i++) {
    319             PolyData pdInner = innerPolygons.get(i);
    320             // Check for intersection between inner members
    321             for (int j = i+1; j < innerPolygons.size(); j++) {
    322                 checkCrossingWays(r, innerPolygons, innerPolygonsPaths, pdInner, j);
     348        for (int i = 0; i < inner.size(); i++) {
     349            LatLonPolyData inner1 = inner.get(i);
     350            LatLon innerPoint = inner1.pd.getNodes().get(0).getCoor();
     351            for (int j = 0; j < inner.size(); j++) {
     352                LatLonPolyData inner2 = inner.get(j);
     353                if (i != j && !crossing(crossingPolyMap, inner1.pd, inner2.pd)) {
     354                    if (inner.get(j).latLonPath.contains(innerPoint.lon(), innerPoint.lat())) {
     355                        addError(r,
     356                                new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
     357                                        CROSSING_WAYS, Collections.singletonList(r),
     358                                        Arrays.asList(inner1.pd.getNodes(), inner2.pd.getNodes())));
     359                    }
     360                }
    323361            }
    324             // Check for intersection between inner and outer members
     362
     363            // Find inner polygons which are not inside any outer
    325364            boolean outside = true;
    326             for (int o = 0; o < outerPolygons.size(); o++) {
    327                 outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE;
     365            boolean crossingWithOuter = false;
     366
     367            for (int o = 0; o < outer.size(); o++) {
     368                if (crossing(crossingPolyMap, inner1.pd, outer.get(o).pd)) {
     369                    crossingWithOuter = true;
     370                    break;
     371                }
     372                outside &= outer.get(o).latLonPath.contains(innerPoint.lon(), innerPoint.lat()) == false;
     373                if (!outside)
     374                    break;
    328375            }
    329             if (outside) {
     376            if (outside && !crossingWithOuter) {
    330377                addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"),
    331                         INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(pdInner.getNodes())));
     378                        INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes())));
    332379            }
    333380        }
    334381    }
    335382
    336     private Intersection checkCrossingWays(Relation r, List<PolyData> polygons, List<GeneralPath> polygonsPaths, PolyData pd, int idx) {
    337         Intersection intersection = getPolygonIntersection(polygonsPaths.get(idx), pd.getNodes());
    338         if (intersection == Intersection.CROSSING) {
    339             PolyData pdOther = polygons.get(idx);
    340             if (pdOther != null) {
    341                 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
    342                         CROSSING_WAYS, Collections.singletonList(r), Arrays.asList(pd.getNodes(), pdOther.getNodes())));
    343             }
     383    private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) {
     384        List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1);
     385        if (crossingWithFirst != null) {
     386            if (crossingWithFirst.contains(pd2))
     387                return true;
    344388        }
    345         return intersection;
     389        List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2);
     390        if (crossingWith2nd != null) {
     391            if (crossingWith2nd.contains(pd1))
     392                return true;
     393        }
     394        return false;
    346395    }
    347396
     397    private List<LatLonPolyData> calcLatLonPolygons(List<PolyData> polygons) {
     398        if (polygons == null || polygons.isEmpty())
     399            return Collections.emptyList();
     400        List<LatLonPolyData> latLonPolygons = new ArrayList<>();
     401        List<Path2D.Double> polygonsPaths = createPolygons(polygons);
     402        for (int i = 0; i < polygons.size(); i++) {
     403            latLonPolygons.add(new LatLonPolyData(polygons.get(i), polygonsPaths.get(i)));
     404        }
     405        return latLonPolygons;
     406    }
     407
    348408    /**
    349409     * Check for:<ul>
    350410     * <li>{@link #WRONG_MEMBER_ROLE}: No useful role for multipolygon member</li>
     
    389449        addRelationIfNeeded(error, r);
    390450        errors.add(error);
    391451    }
     452
     453    /**
     454     * Determine multipolygon ways which are intersecting. This is now allowed.
     455     * See {@link CrossingWays}
     456     * @param r the relation (for error reporting)
     457     * @param innerPolygons list of inner polygons
     458     * @param outerPolygons list of outer polygons
     459     * @return map of crossing polygons (including polygons touching outer)
     460     */
     461    private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons,
     462            List<PolyData> outerPolygons) {
     463        List<Way> innerWays = new ArrayList<>();
     464        List<Way> outerWays = new ArrayList<>();
     465        for (Way w : r.getMemberPrimitives(Way.class)) {
     466            for (PolyData pd : innerPolygons) {
     467                if (pd.getWayIds().contains(w.getUniqueId())) {
     468                    innerWays.add(w);
     469                    break;
     470                }
     471            }
     472            for (PolyData pd : outerPolygons) {
     473                if (pd.getWayIds().contains(w.getUniqueId())) {
     474                    outerWays.add(w);
     475                    break;
     476                }
     477            }
     478        }
     479        for (Way w : innerWays) {
     480            checkCrossingWay(w, r, true /* allow shared ways */);
     481        }
     482        for (Way w : outerWays) {
     483            checkCrossingWay(w, r, false/* don't allow shared ways */);
     484        }
     485        HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>();
     486        for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) {
     487            List<Way> ways = entry.getKey();
     488            PolyData[] crossingPolys = new PolyData[2];
     489            for (Way w : ways) {
     490                for (int j = 0; j < crossingPolys.length; j++) {
     491                    for (PolyData pd : innerPolygons) {
     492                        if (pd.getWayIds().contains(w.getUniqueId())) {
     493                            crossingPolys[j] = pd;
     494                            break;
     495                        }
     496                    }
     497                    if (crossingPolys[j] != null)
     498                        break;
     499                    for (PolyData pd : outerPolygons) {
     500                        if (pd.getWayIds().contains(w.getUniqueId())) {
     501                            crossingPolys[j] = pd;
     502                            break;
     503                        }
     504                    }
     505                }
     506            }
     507            if (crossingPolys[0] != null && crossingPolys[1] != null) {
     508                List<PolyData> x = crossingPolygonsMap.get(crossingPolys[0]);
     509                if (x == null) {
     510                    x = new ArrayList<>();
     511                    crossingPolygonsMap.put(crossingPolys[0], x);
     512                }
     513                x.add(crossingPolys[1]);
     514            }
     515        }
     516        return crossingPolygonsMap;
     517    }
     518
     519    /**
     520     *
     521     * @param w way that is member of the relation
     522     * @param r the relation (used for error messages)
     523     * @param allowSharedWaySegment false: treat similar way segment as crossing
     524     */
     525    private void checkCrossingWay(Way w, Relation r, boolean allowSharedWaySegment) {
     526
     527        int nodesSize = w.getNodesCount();
     528        for (int i = 0; i < nodesSize - 1; i++) {
     529            final WaySegment es1 = new WaySegment(w, i);
     530            final EastNorth en1 = es1.getFirstNode().getEastNorth();
     531            final EastNorth en2 = es1.getSecondNode().getEastNorth();
     532            if (en1 == null || en2 == null) {
     533                Main.warn("Crossing ways test skipped " + es1);
     534                continue;
     535            }
     536            for (List<WaySegment> segments : getSegments(en1, en2)) {
     537                for (WaySegment es2 : segments) {
     538
     539                    List<WaySegment> highlight;
     540
     541                    if (!es1.intersects(es2)) {
     542                        if (allowSharedWaySegment || !es1.isSimilar(es2))
     543                            continue;
     544                    }
     545
     546                    List<Way> prims = Arrays.asList(es1.way, es2.way);
     547                    if ((highlight = crossingWays.get(prims)) == null) {
     548                        highlight = new ArrayList<>();
     549                        highlight.add(es1);
     550                        highlight.add(es2);
     551
     552                        addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
     553                                CROSSING_WAYS, Arrays.asList(r, es1.way, es2.way), highlight));
     554                        crossingWays.put(prims, highlight);
     555                    } else {
     556                        highlight.add(es1);
     557                        highlight.add(es2);
     558                    }
     559                }
     560                segments.add(es1);
     561            }
     562        }
     563    }
     564
     565    /**
     566     * Returns all the cells this segment crosses.  Each cell contains the list
     567     * of segments already processed
     568     *
     569     * @param n1 The first EastNorth
     570     * @param n2 The second EastNorth
     571     * @return A list with all the cells the segment crosses
     572     */
     573    private List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) {
     574
     575        List<List<WaySegment>> cells = new ArrayList<>();
     576        for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) {
     577            List<WaySegment> segments = cellSegments.get(cell);
     578            if (segments == null) {
     579                segments = new ArrayList<>();
     580                cellSegments.put(cell, segments);
     581            }
     582            cells.add(segments);
     583        }
     584        return cells;
     585    }
     586
     587    /**
     588     * Check for:<ul>
     589     * <li>{@link #REPEATED_MEMBER_DIFF_ROLE}: Multipolygon member(s) repeated with different role</li>
     590     * <li>{@link #REPEATED_MEMBER_SAME_ROLE}: Multipolygon member(s) repeated with same role</li>
     591     * </ul>
     592     * @param r relation
     593     */
     594    private void checkRepeatedMembers(Relation r) {
     595        boolean hasDups = false;
     596        Map<OsmPrimitive, List<RelationMember>> seenMemberPrimitives = new HashMap<>();
     597        for (RelationMember rm : r.getMembers()) {
     598            List<RelationMember> list = seenMemberPrimitives.get(rm.getMember());
     599            if (list == null) {
     600                list = new ArrayList<>(2);
     601                seenMemberPrimitives.put(rm.getMember(), list);
     602            } else
     603                hasDups = true;
     604            list.add(rm);
     605        }
     606        if (!hasDups)
     607            return;
     608
     609        List<OsmPrimitive> repeatedSameRole = new ArrayList<>();
     610        List<OsmPrimitive> repeatedDiffRole = new ArrayList<>();
     611        for (Entry<OsmPrimitive, List<RelationMember>> e : seenMemberPrimitives.entrySet()) {
     612            List<RelationMember> visited = e.getValue();
     613            if (e.getValue().size() == 1)
     614                continue;
     615            // we found a duplicate member, check if the roles differ
     616            boolean rolesDiffer = false;
     617            RelationMember rm = visited.get(0);
     618            List<OsmPrimitive> primitives = new ArrayList<>();
     619            for (int i = 1; i < visited.size(); i++) {
     620                RelationMember v = visited.get(i);
     621                primitives.add(rm.getMember());
     622                if (v.getRole().equals(rm.getRole()) == false) {
     623                    rolesDiffer = true;
     624                }
     625            }
     626            if (rolesDiffer)
     627                repeatedDiffRole.addAll(primitives);
     628            else
     629                repeatedSameRole.addAll(primitives);
     630        }
     631        addRepeatedMemberError(r, repeatedDiffRole, REPEATED_MEMBER_DIFF_ROLE, tr("Multipolygon member(s) repeated with different role"));
     632        addRepeatedMemberError(r, repeatedSameRole, REPEATED_MEMBER_SAME_ROLE, tr("Multipolygon member(s) repeated with same role"));
     633    }
     634
     635    private void addRepeatedMemberError(Relation r, List<OsmPrimitive> repeatedMembers, int errorCode, String msg) {
     636        if (!repeatedMembers.isEmpty()) {
     637            List<OsmPrimitive> prims = new ArrayList<>(1 + repeatedMembers.size());
     638            prims.add(r);
     639            prims.addAll(repeatedMembers);
     640            addError(r, new TestError(this, Severity.WARNING, msg, errorCode, prims, repeatedMembers));
     641        }
     642
     643    }
     644
     645    @Override
     646    public Command fixError(TestError testError) {
     647        if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) {
     648            ArrayList<OsmPrimitive> primitives = new ArrayList<>(testError.getPrimitives());
     649            if (primitives.size() >= 2) {
     650                if (primitives.get(0) instanceof Relation) {
     651                    Relation r = (Relation) primitives.get(0);
     652                    return new RemoveRepeatedRelationMembersCommand(r, primitives.subList(1, primitives.size()));
     653                }
     654            }
     655        }
     656        return null;
     657    }
     658
     659    @Override
     660    public boolean isFixable(TestError testError) {
     661        if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE)
     662            return true;
     663        return false;
     664    }
    392665}