Ticket #5642: validator-sw-dr-m4.patch

File validator-sw-dr-m4.patch, 15.6 KB (added by bilbo, 10 years ago)

Finished version

  • src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java

     
     1// License: GPL. See LICENSE file for details.
     2package org.openstreetmap.josm.data.validation.tests;
     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.LinkedList;
     10import java.util.LinkedHashSet;
     11import java.util.List;
     12import java.util.Map;
     13import java.util.Vector;
     14
     15import org.openstreetmap.josm.Main;
     16import org.openstreetmap.josm.command.ChangeCommand;
     17import org.openstreetmap.josm.command.Command;
     18import org.openstreetmap.josm.command.DeleteCommand;
     19import org.openstreetmap.josm.command.SequenceCommand;
     20import org.openstreetmap.josm.data.coor.LatLon;
     21import org.openstreetmap.josm.data.osm.Node;
     22import org.openstreetmap.josm.data.osm.OsmPrimitive;
     23import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     24import org.openstreetmap.josm.data.osm.Relation;
     25import org.openstreetmap.josm.data.osm.RelationMember;
     26import org.openstreetmap.josm.data.osm.Way;
     27import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     28import org.openstreetmap.josm.data.validation.Severity;
     29import org.openstreetmap.josm.data.validation.Test;
     30import org.openstreetmap.josm.data.validation.TestError;
     31import org.openstreetmap.josm.tools.MultiMap;
     32/**
     33 * Tests if there are duplicate relations
     34 */
     35public class DuplicateRelation extends Test
     36{
     37
     38    public class RelMember {
     39        private String role;
     40        private OsmPrimitiveType type;
     41        private Map<String, String> tags;
     42        private List<LatLon> coor;
     43        private long rel_id;
     44
     45        @Override
     46        public int hashCode() {
     47            return role.hashCode()+(int)rel_id+tags.hashCode()+type.hashCode()+coor.hashCode();
     48        }
     49
     50        @Override
     51        public boolean equals(Object obj) {
     52            if (!(obj instanceof RelMember)) return false;
     53            RelMember rm = (RelMember) obj;
     54            return rm.role.equals(role) && rm.type.equals(type) && rm.rel_id==rel_id && rm.tags.equals(tags) && rm.coor.equals(coor);
     55        }
     56
     57        public RelMember(RelationMember src) {
     58            role=src.getRole();
     59            type=src.getType();
     60            rel_id=0;
     61            coor=new ArrayList<LatLon>();
     62
     63            if (src.isNode()) {
     64                Node r=src.getNode();
     65                tags=r.getKeys();
     66                coor=new ArrayList<LatLon>(1);
     67                coor.add(r.getCoor());
     68            }
     69            if (src.isWay()) {
     70                Way r=src.getWay();
     71                tags=r.getKeys();
     72                List<Node> wNodes = r.getNodes();
     73                coor=new ArrayList<LatLon>(wNodes.size());
     74                for (int i=0;i<wNodes.size();i++) {
     75                    coor.add(wNodes.get(i).getCoor());
     76                }
     77            }
     78            if (src.isRelation()) {
     79                Relation r=src.getRelation();
     80                tags=r.getKeys();
     81                rel_id=r.getId();
     82                coor=new ArrayList<LatLon>();
     83            }
     84        }
     85    }
     86
     87    private class RelationMembers {
     88        public List<RelMember> members;
     89        public RelationMembers(List<RelationMember> _members) {
     90            members=new ArrayList<RelMember>(_members.size());
     91            for (int i=0;i<_members.size();i++) {
     92                members.add(new RelMember(_members.get(i)));
     93            }
     94        }
     95        @Override
     96        public int hashCode() {
     97            return members.hashCode();
     98        }
     99        @Override
     100        public boolean equals(Object obj) {
     101            if (!(obj instanceof RelationMembers)) return false;
     102            RelationMembers rm = (RelationMembers) obj;
     103            return rm.members.equals(members);
     104        }
     105    }
     106
     107    private class RelationPair {
     108        public RelationMembers members;
     109        public Map<String, String> keys;
     110        public RelationPair(List<RelationMember> _members,Map<String, String> _keys) {
     111            members=new RelationMembers(_members);
     112            keys=_keys;
     113        }
     114        @Override
     115        public int hashCode() {
     116            return members.hashCode()+keys.hashCode();
     117        }
     118        @Override
     119        public boolean equals(Object obj) {
     120            if (!(obj instanceof RelationPair)) return false;
     121            RelationPair rp = (RelationPair) obj;
     122            return rp.members.equals(members) && rp.keys.equals(keys);
     123        }
     124    }
     125
     126    protected static int DUPLICATE_RELATION = 1901;
     127    protected static int SAME_RELATION = 1902;
     128
     129    /** MultiMap of all relations */
     130    MultiMap<RelationPair, OsmPrimitive> relations;
     131
     132    /** MultiMap of all relations, regardless of keys */
     133    MultiMap<List<RelationMember>, OsmPrimitive> relations_nokeys;
     134
     135    /**
     136     * Constructor
     137     */
     138    public DuplicateRelation()
     139    {
     140        super(tr("Duplicated relations")+".",
     141              tr("This test checks that there are no relations with same tags and same members with same roles."));
     142    }
     143
     144
     145    @Override
     146    public void startTest(ProgressMonitor monitor)
     147    {
     148        super.startTest(monitor);
     149        relations = new MultiMap<RelationPair, OsmPrimitive>(1000);
     150        relations_nokeys = new MultiMap<List<RelationMember>, OsmPrimitive>(1000);
     151    }
     152
     153    @Override
     154    public void endTest()
     155    {
     156        super.endTest();
     157        for(LinkedHashSet<OsmPrimitive> duplicated : relations.values() )
     158        {
     159            if( duplicated.size() > 1)
     160            {
     161                TestError testError = new TestError(this, Severity.ERROR, tr("Duplicated relations"), DUPLICATE_RELATION, duplicated);
     162                errors.add( testError );
     163            }
     164        }
     165        relations = null;
     166        for(LinkedHashSet<OsmPrimitive> duplicated : relations_nokeys.values() )
     167        {
     168            if( duplicated.size() > 1)
     169            {
     170                TestError testError = new TestError(this, Severity.WARNING, tr("Relations with same members"), SAME_RELATION, duplicated);
     171                errors.add( testError );
     172            }
     173        }
     174        relations_nokeys = null;
     175    }
     176
     177    @Override
     178    public void visit(Relation r)
     179    {
     180        if( !r.isUsable() )
     181            return;
     182        List<RelationMember> rMembers=r.getMembers();
     183        Map<String, String> rkeys=r.getKeys();
     184        rkeys.remove("created_by");
     185        RelationPair rKey=new RelationPair(rMembers,rkeys);
     186        relations.put(rKey, r);
     187        relations_nokeys.put(rMembers, r);
     188    }
     189
     190    /**
     191     * Fix the error by removing all but one instance of duplicate relations
     192     */
     193    @Override
     194    public Command fixError(TestError testError)
     195    {
     196        if (testError.getCode() == SAME_RELATION) return null;
     197        Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
     198        HashSet<Relation> rel_fix = new HashSet<Relation>();
     199
     200        for (OsmPrimitive osm : sel)
     201            if (osm instanceof Relation)
     202                rel_fix.add((Relation)osm);
     203
     204        if( rel_fix.size() < 2 )
     205            return null;
     206
     207        long idToKeep = 0;
     208        Relation relationToKeep = rel_fix.iterator().next();
     209        // Only one relation will be kept - the one with lowest positive ID, if such exist
     210        // or one "at random" if no such exists. Rest of the relations will be deleted
     211        for (Relation w: rel_fix) {
     212            if (!w.isNew()) {
     213                if (idToKeep == 0 || w.getId() < idToKeep) {
     214                    idToKeep = w.getId();
     215                    relationToKeep = w;
     216                }
     217            }
     218        }
     219
     220        // Find the relation that is member of one or more relations. (If any)
     221        Relation relationWithRelations = null;
     222        List<Relation> rel_ref = null;
     223        for (Relation w : rel_fix) {
     224            List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
     225            if (!rel.isEmpty()) {
     226                if (relationWithRelations != null)
     227                    throw new AssertionError("Cannot fix duplicate relations: More than one relation is member of another relation.");
     228                relationWithRelations = w;
     229                rel_ref = rel;
     230            }
     231        }
     232
     233        Collection<Command> commands = new LinkedList<Command>();
     234
     235        // Fix relations.
     236        if (relationWithRelations != null && relationToKeep != relationWithRelations) {
     237            for (Relation rel : rel_ref) {
     238                Relation newRel = new Relation(rel);
     239                for (int i = 0; i < newRel.getMembers().size(); ++i) {
     240                    RelationMember m = newRel.getMember(i);
     241                    if (relationWithRelations.equals(m.getMember())) {
     242                        newRel.setMember(i, new RelationMember(m.getRole(), relationToKeep));
     243                    }
     244                }
     245                commands.add(new ChangeCommand(rel, newRel));
     246            }
     247        }
     248
     249        //Delete all relations in the list
     250        rel_fix.remove(relationToKeep);
     251        commands.add(new DeleteCommand(rel_fix));
     252        return new SequenceCommand(tr("Delete duplicate relations"), commands);
     253    }
     254
     255    @Override
     256    public boolean isFixable(TestError testError)
     257    {
     258        if (!(testError.getTester() instanceof DuplicateRelation))
     259            return false;
     260
     261        if (testError.getCode() == SAME_RELATION) return false;
     262
     263        // We fix it only if there is no more than one relation that is relation member.
     264        Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
     265        HashSet<Relation> relations = new HashSet<Relation>();
     266
     267        for (OsmPrimitive osm : sel)
     268            if (osm instanceof Relation)
     269                relations.add((Relation)osm);
     270
     271        if (relations.size() < 2)
     272            return false;
     273
     274        int relationsWithRelations = 0;
     275        for (Relation w : relations) {
     276            List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
     277            if (!rel.isEmpty()) {
     278                ++relationsWithRelations;
     279            }
     280        }
     281        return (relationsWithRelations <= 1);
     282    }
     283}
  • src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java

     
    3333public class DuplicateWay extends Test
    3434{
    3535
    36     private static class WayPair {
     36    private class WayPair {
    3737        public List<LatLon> coor;
    3838        public Map<String, String> keys;
    3939        public WayPair(List<LatLon> _coor, Map<String, String> _keys) {
     
    5555        }
    5656    }
    5757
     58    private class WayPairNoTags {
     59        public List<LatLon> coor;
     60        public WayPairNoTags(List<LatLon> _coor) {
     61            coor=_coor;
     62        }
     63        @Override
     64        public int hashCode() {
     65            return coor.hashCode();
     66        }
     67        @Override
     68        public boolean equals(Object obj) {
     69            if (!(obj instanceof WayPairNoTags)) return false;
     70            WayPairNoTags wp = (WayPairNoTags) obj;
     71            return wp.coor.equals(coor);
     72        }
     73    }
     74
    5875    protected static int DUPLICATE_WAY = 1401;
     76    protected static int SAME_WAY = 1402;
    5977
    6078    /** Bag of all ways */
    6179    MultiMap<WayPair, OsmPrimitive> ways;
    6280
     81    /** Bag of all ways, regardless of tags */
     82    MultiMap<WayPairNoTags, OsmPrimitive> waysNoTags;
     83
    6384    /**
    6485     * Constructor
    6586     */
    6687    public DuplicateWay() {
    6788        super(tr("Duplicated ways")+".",
    68               tr("This test checks that there are no ways with same tags and same node coordinates."));
     89              tr("This test checks that there are no ways with same node coordinates and optionally also same tags."));
    6990    }
    7091
    7192
     
    7394    public void startTest(ProgressMonitor monitor) {
    7495        super.startTest(monitor);
    7596        ways = new MultiMap<WayPair, OsmPrimitive>(1000);
     97        waysNoTags = new MultiMap<WayPairNoTags, OsmPrimitive>(1000);
    7698    }
    7799
    78100    @Override
     
    84106                errors.add(testError);
    85107            }
    86108        }
     109
     110        for(Set<OsmPrimitive> sameway : waysNoTags.values()) {
     111            if( sameway.size() > 1) {
     112                //Report error only if at least some tags are different, as otherwise the error was already reported as duplicated ways
     113                Map<String, String> tags0=null;
     114                boolean skip=true;
     115
     116                for(OsmPrimitive o : sameway) {
     117                    if (tags0==null) {
     118                        tags0=o.getKeys();
     119                        removeUninterestingKeys(tags0);
     120                    } else {
     121                        Map<String, String> tagsCmp=o.getKeys();
     122                        removeUninterestingKeys(tagsCmp);
     123                        if (!tagsCmp.equals(tags0)) {
     124                            skip=false;
     125                            break;
     126                        }
     127                    }
     128                }
     129                if (skip) continue;
     130                TestError testError = new TestError(this, Severity.WARNING, tr("Ways with same position"), SAME_WAY, sameway);
     131                errors.add(testError);
     132            }
     133        }
    87134        ways = null;
     135        waysNoTags = null;
    88136    }
    89137
     138    /**
     139     * Remove uninteresting keys, like created_by to normalize the tags
     140     */
     141    public void removeUninterestingKeys(Map<String, String> wkeys) {
     142        wkeys.remove("created_by");
     143    }
     144
    90145    @Override
    91146    public void visit(Way w) {
    92147        if (!w.isUsable())
     
    97152             wLat.add(wNodes.get(i).getCoor());
    98153        }
    99154        Map<String, String> wkeys = w.getKeys();
    100         wkeys.remove("created_by");
     155        removeUninterestingKeys(wkeys);
    101156        WayPair wKey = new WayPair(wLat, wkeys);
    102157        ways.put(wKey, w);
     158        WayPairNoTags wKeyN = new WayPairNoTags(wLat);
     159        waysNoTags.put(wKeyN, w);
    103160    }
    104161
    105162    /**
     
    173230        if (!(testError.getTester() instanceof DuplicateWay))
    174231            return false;
    175232
     233        //Do not automatically fix same ways with different tags
     234        if (testError.getCode()!=DUPLICATE_WAY) return false;
     235
    176236        // We fix it only if there is no more than one way that is relation member.
    177237        Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
    178238        HashSet<Way> ways = new HashSet<Way>();
  • src/org/openstreetmap/josm/data/validation/OsmValidator.java

     
    3030import org.openstreetmap.josm.data.validation.tests.CrossingWays;
    3131import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
    3232import org.openstreetmap.josm.data.validation.tests.DuplicateWay;
     33import org.openstreetmap.josm.data.validation.tests.DuplicateRelation;
    3334import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
    3435import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
    3536import org.openstreetmap.josm.data.validation.tests.NameMismatch;
     
    9495            MultipolygonTest.class, // ID  1601 ..  1699
    9596            RelationChecker.class, // ID  1701 ..  1799
    9697            TurnrestrictionTest.class, // ID  1801 ..  1899
     98            DuplicateRelation.class, // ID 1901 .. 1999
    9799    };
    98100
    99101    public OsmValidator() {