#13082 closed enhancement (fixed)
[RFC][PATCH] Warning for relations with only one member
Reported by: | naoliv | Owned by: | team |
---|---|---|---|
Priority: | normal | Milestone: | 20.06 |
Component: | Core validator | Version: | |
Keywords: | Cc: |
Description
Could we have a message (at informational level) for relation with exactly 1 member?
Such test would help to find some really unnecessary (or even wrong) relations with only one member.
JOSM:
URL:http://josm.openstreetmap.de/svn/trunk Repository:UUID: 0c6e7542-c601-0410-84e7-c038aed88b3b Last:Changed Date: 2016-06-28 00:39:58 +0200 (Tue, 28 Jun 2016) Build-Date:2016-06-28 01:32:14 Revision:10501 Relative:URL: ^/trunk Identification: JOSM/1.5 (10501 pt_BR) Linux Debian GNU/Linux testing (stretch) Memory Usage: 2159 MB / 10206 MB (1491 MB allocated, but free) Java version: 1.8.0_91-8u91-b14-2-b14, Oracle Corporation, OpenJDK 64-Bit Server VM VM arguments: [-Dawt.useSystemAAFontSettings=on] Dataset consistency test: No problems found
Attachments (0)
Change History (26)
comment:1 by , 8 years ago
comment:2 by , 8 years ago
What exactly is a type=node
relation?
There are only 36 relations like this http://taginfo.openstreetmap.org/tags/type=node
comment:3 by , 8 years ago
You need it for nodes above of each other like transponders on the same pole but different height. I can only tag one on the node and need a relation for the other.
See OSMwiki page
comment:4 by , 6 years ago
Proof of concept:
-
src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java
diff --git a/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java b/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java index ba7191f54..d6c6dbfb6 100644
a b public class MultipolygonTest extends Test { 77 77 public static final int EQUAL_RINGS = 1616; 78 78 /** Multipolygon rings share nodes */ 79 79 public static final int RINGS_SHARE_NODES = 1617; 80 /** Multipolygon with a single member */ 81 public static final int SINGLE_MEMBER = 1618; 80 82 81 83 private static final int FOUND_INSIDE = 1; 82 84 private static final int FOUND_OUTSIDE = 2; … … public class MultipolygonTest extends Test { 130 132 @Override 131 133 public void visit(Relation r) { 132 134 if (r.isMultipolygon() && r.getMembersCount() > 0) { 135 checkSingleMember(r); 133 136 List<TestError> tmpErrors = new ArrayList<>(30); 134 137 boolean hasUnexpectedWayRoles = checkMembersAndRoles(r, tmpErrors); 135 138 boolean hasRepeatedMembers = checkRepeatedWayMembers(r); … … public class MultipolygonTest extends Test { 145 148 } 146 149 } 147 150 151 private void checkSingleMember(Relation r) { 152 if (r.getMembersCount() == 1) { 153 errors.add(TestError.builder(this, Severity.OTHER, SINGLE_MEMBER) 154 .message(tr("Multipolygon with a single member")) 155 .primitives(r) 156 .build()); 157 } 158 } 159 148 160 /** 149 161 * Various style-related checks:<ul> 150 162 * <li>{@link #NO_STYLE_POLYGON}: Multipolygon relation should be tagged with area tags and not the outer way</li> -
src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
diff --git a/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java b/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java index 00ec95584..d6b3b7207 100644
a b public class RelationChecker extends Test { 55 55 public static final int RELATION_UNKNOWN = 1707; 56 56 /** Relation is empty */ 57 57 public static final int RELATION_EMPTY = 1708; 58 /** Relation has only one member */ 59 public static final int ONE_MEMBER = 1709; 58 60 // CHECKSTYLE.ON: SingleSpaceSeparator 59 61 60 62 /** … … public class RelationChecker extends Test { 126 128 // see #17010: don't report same problem twice 127 129 return; 128 130 } 131 if (n.getMembersCount() == 1) { 132 errors.add(TestError.builder(this, Severity.OTHER, ONE_MEMBER) 133 .message(tr("Relation has only one member")) 134 .primitives(n) 135 .build()); 136 } 129 137 Map<Role, String> allroles = buildAllRoles(n); 130 138 if (allroles.isEmpty() && n.hasTag("type", "route") 131 139 && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) {
comment:5 by , 6 years ago
Summary: | Warning for relations with only one member → [RFC][PATCH] Warning for relations with only one member |
---|
comment:6 by , 6 years ago
I think Multipolygons are the only case where a single member is OK, I would not even want to see an information for them. On the other hand, other relations with a single member are probably of Severity.Warning.
comment:7 by , 5 years ago
Instead implementing a validation rule (to annoy everybody with it :-)), is it easy to have a new search expression for this? (members
, for example)
This would allow us to search for relations with a specific number of member and also use it in custom MapCSS validation rules.
comment:8 by , 5 years ago
I think the osmose external rule has this validation. It can be downloaded from JOSM settings.
comment:10 by , 5 years ago
With this I am able to properly search for relations with any number of members inside JOSM:
-
src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
124 124 private final Collection<String> keywords = Arrays.asList("id", "version", "type", "user", "role", 125 125 "changeset", "nodes", "ways", "tags", "areasize", "waylength", "modified", "deleted", "selected", 126 126 "incomplete", "untagged", "closed", "new", "indownloadedarea", 127 "allindownloadedarea", "timestamp", "nth", "nth%", "hasRole", "preset" );127 "allindownloadedarea", "timestamp", "nth", "nth%", "hasRole", "preset", "members"); 128 128 129 129 @Override 130 130 public Match get(String keyword, boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) throws SearchParseError { … … 180 180 return new Nth(tokenizer, true); 181 181 case "hasRole": 182 182 return new HasRole(tokenizer); 183 case "members": 184 return new Members(tokenizer); 183 185 case "timestamp": 184 186 // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""}) 185 187 String rangeS = ' ' + tokenizer.readTextOrNumber() + ' '; … … 1249 1251 } 1250 1252 1251 1253 /** 1254 * Returns the number of members of a relation 1255 */ 1256 private static class Members extends Match { 1257 private final int members; 1258 1259 Members(PushbackTokenizer tokenizer) throws SearchParseError { 1260 this((int) tokenizer.readNumber(tr("Positive integer expected"))); 1261 } 1262 1263 private Members(int members) { 1264 this.members = members; 1265 } 1266 1267 @Override 1268 public boolean match(OsmPrimitive osm) { 1269 for (OsmPrimitive p : osm.getReferrers()) { 1270 final int num; 1271 if (p instanceof Relation) { 1272 Relation r = (Relation) p; 1273 num = r.getMembersCount(); 1274 } else { 1275 continue; 1276 } 1277 if (num == members) { 1278 return true; 1279 } 1280 } 1281 return false; 1282 } 1283 } 1284 1285 /** 1252 1286 * Matches objects with the given relation role (i.e. "outer"). 1253 1287 */ 1254 1288 private static class RoleMatch extends Match { -
src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java
347 347 .addKeyword("nodes:<i>20-</i>", "nodes:", tr("ways with at least 20 nodes, or relations containing at least 20 nodes")) 348 348 .addKeyword("ways:<i>3-</i>", "ways:", tr("nodes with at least 3 referring ways, or relations containing at least 3 ways")) 349 349 .addKeyword("tags:<i>5-10</i>", "tags:", tr("objects having 5 to 10 tags")) 350 .addKeyword("role:", "role:", tr("objects with given role in a relation")) 350 .addKeyword("members:<i>2</i>", "members:", tr("relations with 2 members")) 351 .addKeyword("role:", "role:", tr("objects with given role in a relation")), 352 GBC.eol()); 353 hintPanel.add(new SearchKeywordRow(hcbSearchString) 351 354 .addKeyword("areasize:<i>-100</i>", "areasize:", tr("closed ways with an area of 100 m\u00b2")) 352 355 .addKeyword("waylength:<i>200-</i>", "waylength:", tr("ways with a length of 200 m or more")), 353 GBC.eol() );356 GBC.eol().anchor(GBC.CENTER)); 354 357 hintPanel.add(new SearchKeywordRow(hcbSearchString) 355 358 .addTitle(tr("state")) 356 359 .addKeyword("modified", "modified ", tr("all modified objects"))
But this doesn't work:
relation[eval(JOSM_search("members:1"))] { throwWarning: tr("ha"); }
Using this it works, however:
relation[eval(JOSM_search("tags:1"))] { throwWarning: tr("ha"); }
JOSM_search
is working as expected, but somehow I am missing to include this members
somewhere else. Any ideas and feedback about this, please?
comment:11 by , 5 years ago
This one works as expected (search in JOSM, search with MapCSS)
My only doubt is about the code quality :-)
-
src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
124 124 private final Collection<String> keywords = Arrays.asList("id", "version", "type", "user", "role", 125 125 "changeset", "nodes", "ways", "tags", "areasize", "waylength", "modified", "deleted", "selected", 126 126 "incomplete", "untagged", "closed", "new", "indownloadedarea", 127 "allindownloadedarea", "timestamp", "nth", "nth%", "hasRole", "preset" );127 "allindownloadedarea", "timestamp", "nth", "nth%", "hasRole", "preset", "members"); 128 128 129 129 @Override 130 130 public Match get(String keyword, boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) throws SearchParseError { … … 180 180 return new Nth(tokenizer, true); 181 181 case "hasRole": 182 182 return new HasRole(tokenizer); 183 case "members": 184 return new Members(tokenizer); 183 185 case "timestamp": 184 186 // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""}) 185 187 String rangeS = ' ' + tokenizer.readTextOrNumber() + ' '; … … 1249 1251 } 1250 1252 1251 1253 /** 1254 * Matches relations with a certain number of members 1255 */ 1256 private static class Members extends RangeMatch { 1257 Members(Range range) { 1258 super(range); 1259 } 1260 1261 Members(PushbackTokenizer tokenizer) throws SearchParseError { 1262 this(tokenizer.readRange(tr("Range of numbers expected"))); 1263 } 1264 1265 @Override 1266 protected Long getNumber(OsmPrimitive osm) { 1267 if(!(osm instanceof Relation)) 1268 return null; 1269 Relation r = (Relation) osm; 1270 return (long) r.getMembersCount(); 1271 } 1272 1273 @Override 1274 protected String getString() { 1275 return "members"; 1276 } 1277 } 1278 1279 /** 1252 1280 * Matches objects with the given relation role (i.e. "outer"). 1253 1281 */ 1254 1282 private static class RoleMatch extends Match { -
src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java
347 347 .addKeyword("nodes:<i>20-</i>", "nodes:", tr("ways with at least 20 nodes, or relations containing at least 20 nodes")) 348 348 .addKeyword("ways:<i>3-</i>", "ways:", tr("nodes with at least 3 referring ways, or relations containing at least 3 ways")) 349 349 .addKeyword("tags:<i>5-10</i>", "tags:", tr("objects having 5 to 10 tags")) 350 .addKeyword("role:", "role:", tr("objects with given role in a relation")) 350 .addKeyword("members:<i>2</i>", "members:", tr("relations with 2 members")) 351 .addKeyword("role:", "role:", tr("objects with given role in a relation")), 352 GBC.eol()); 353 hintPanel.add(new SearchKeywordRow(hcbSearchString) 351 354 .addKeyword("areasize:<i>-100</i>", "areasize:", tr("closed ways with an area of 100 m\u00b2")) 352 355 .addKeyword("waylength:<i>200-</i>", "waylength:", tr("ways with a length of 200 m or more")), 353 GBC.eol() );356 GBC.eol().anchor(GBC.CENTER)); 354 357 hintPanel.add(new SearchKeywordRow(hcbSearchString) 355 358 .addTitle(tr("state")) 356 359 .addKeyword("modified", "modified ", tr("all modified objects"))
comment:15 by , 4 years ago
Any concers (e.g. regarding false positives or performance) to add this to our internal relation.mapcss validator file?
relation[type!=node][eval(JOSM_search("members:1"))] { throwWarning: tr("Relation with only one member."); }
follow-up: 20 comment:17 by , 4 years ago
I think 1 member MPs are needed only in rare cases. I would like to see a notification about 1 member MPs to get rid of the not useful ones. I think severity other is OK. What about:
relation[type!=node][type!=multipolygon][eval(JOSM_search("members:1"))] { throwWarning: tr("Relation with only one member."); } relation[type=multipolygon][eval(JOSM_search("members:1"))] { throwOther: tr("Relation with only one member."); }
follow-up: 19 comment:18 by , 4 years ago
Maybe add the type to the 2nd message text. Else "Ignore error" will probably ignore both.
Or just tr("Multipolygon with only one member.") ?
follow-up: 24 comment:19 by , 4 years ago
Replying to GerdP:
Maybe add the type to the 2nd message text.
I wanted to save one i18n string.
Else "Ignore error" will probably ignore both.
Indeed. So we have this problem already with other validator tests. The ignore list would need to include the severity. I'm not sure if you want to fiddle around with the ignore list handling again :)
follow-ups: 21 25 comment:20 by , 4 years ago
Replying to Klumbumbus:
I think 1 member MPs are needed only in rare cases.
What are rare cases? The shouldn't be a single case where this should be necessary.
BTW: I also find the single object relation of type=node a strange construct. As said in the comment section creating a second node is much easier.
comment:21 by , 4 years ago
Replying to stoecker:
What are rare cases? The shouldn't be a single case where this should be necessary.
Like mapping several shops as area in one building where each takes a whole storey.
You could either draw several ways on top of each other or use several MPs.
comment:23 by , 4 years ago
Cases where a one way multipolygon is used quite often:
1) a closed way with natural=coastline describes the outline of an island which is covered with wood. Same with other natural values. The MP is used because natural=wood cannot be added to the way. Other mappers duplicate the way.
2) a closed way with barrier=fence encloses a landuse=*. Some mappers don't like to combine both tags because one thing is a line object and the other an area.
follow-up: 26 comment:24 by , 4 years ago
Replying to Klumbumbus:
Else "Ignore error" will probably ignore both.
Indeed. So we have this problem already with other validator tests. The ignore list would need to include the severity. I'm not sure if you want to fiddle around with the ignore list handling again :)
I did not find other strings in mpacss rules with this problem so far.
comment:25 by , 4 years ago
Somehow related #19148, where one member MPs are a solution to get validator calm.
Replying to stoecker:
Replying to Klumbumbus:
I think 1 member MPs are needed only in rare cases.
What are rare cases? The shouldn't be a single case where this should be necessary.
They are not that rare. Two amenity
, shop
, leisure
or a mix out of the three on top of each other exists in reality and will get more common.
BTW: I also find the single object relation of type=node a strange construct. As said in the comment section creating a second node is much easier.
I get a warning Nodes at same position
, if I do not use a node relation.
Replying to GerdP:
Cases where a one way multipolygon is used quite often:
1) a closed way with natural=coastline describes the outline of an island which is covered with wood. Same with other natural values. The MP is used because natural=wood cannot be added to the way. Other mappers duplicate the way.
2) a closed way with barrier=fence encloses a landuse=*. Some mappers don't like to combine both tags because one thing is a line object and the other an area.
The problem is almost all the time either tags which conflict or the "one feature one object" rule. Which by the way is the reason in the first case, too.
comment:26 by , 4 years ago
Replying to GerdP:
I did not find other strings in mpacss rules with this problem so far.
You're right, the ones I was thinking of use placeholders so they are different.
Please, only inform about type=node if the member has no tags except source*=* fixme*=* note*=* and description*=*. Thanks