Skip to content

Commit

Permalink
AddressStreetNameCheck (osmlab#145)
Browse files Browse the repository at this point in the history
* AddressStreetNameCheck

* config default; docs

* consistency

* pr feedback

* code clean up

* move package
  • Loading branch information
Bentleysb authored and chrushr committed Apr 10, 2019
1 parent 26cf666 commit b31060e
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 0 deletions.
9 changes: 9 additions & 0 deletions config/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
"defaultPriority": "LOW"
}
},
"AddressStreetNameCheck": {
"bounds.size": 100.0,
"challenge": {
"description": "Tasks contain nodes with addr:street names that don't match the surrounding roads",
"blurb": "Nodes with mismatched addr:street names",
"instruction": "Open your favorite editor and edit the node street names",
"difficulty": "HARD"
}
},
"AreasWithHighwayTagCheck":{
"tag.filters":"highway->*&area->yes",
"challenge":{
Expand Down
26 changes: 26 additions & 0 deletions docs/checks/addressStreetNameCheck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Address Street Name Check

This check flags Points that have an `addr:street` value that does not match any of the names of surrounding streets.
The search distance for surrounding streets is based on a configurable value that has a default of 100 meters.

#### Live Examples

1. Node [id:847673678](https://www.openstreetmap.org/node/847673678) has an `addr:street` value of Stykkishólmsvegur that does not match any name of surrounding streets.
2. Node [id:2416844306](https://www.openstreetmap.org/node/2416844306) has an `addr:street` value of Vitatorg that is a typo of the nearby street V**í**tatorg.

#### Code Review

In [Atlas](https://github.com/osmlab/atlas), OSM elements are represented as Edges, Points, Lines, Nodes, Areas & Relations;
in our case, we’re are looking at
[Points](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Point.java) and
[Edges](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Edge.java).

This check first validates objects by checking that they are Points with an `addr:street` tag.

If an object is valid, the check gathers all name tag values from surrounding roads (Edges). The surrounding roads are defined by a
configurable search distance that is 100m by default. All name tag values are collected (`name` and localized name tags).

The `addr:street` is then compared against the collected list of street names. If no match is found the Point is flagged.

To learn more about the code, please look at the comments in the source code for the check.
[AddressStreetNameCheck](../../src/main/java/org/openstreetmap/atlas/checks/validation/tag/AddressStreetNameCheck.java)
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.openstreetmap.atlas.checks.validation.points;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.openstreetmap.atlas.checks.base.BaseCheck;
import org.openstreetmap.atlas.checks.flag.CheckFlag;
import org.openstreetmap.atlas.geography.atlas.items.AtlasObject;
import org.openstreetmap.atlas.geography.atlas.items.Edge;
import org.openstreetmap.atlas.geography.atlas.items.Point;
import org.openstreetmap.atlas.tags.AddressStreetTag;
import org.openstreetmap.atlas.tags.annotations.validation.Validators;
import org.openstreetmap.atlas.tags.names.NameTag;
import org.openstreetmap.atlas.utilities.collections.Iterables;
import org.openstreetmap.atlas.utilities.configuration.Configuration;
import org.openstreetmap.atlas.utilities.scalars.Distance;

/**
* This flags {@link Point}s where their addr:street tag value does not match any of the name tag
* values of {@link Edge}s within a configurable search distance.
*
* @author bbreithaupt
*/
public class AddressStreetNameCheck extends BaseCheck<Long>
{

private static final long serialVersionUID = 5401402333350044455L;

private static final List<String> FALLBACK_INSTRUCTIONS = Collections.singletonList(
"Address node {0,number,#} has an addr:street value that does not match the name of any roads within {1,number,#} meters.");
private static final Double SEARCH_DISTANCE_DEFAULT = 100.0;

// Distance to search for Edges around a Point
private final Distance searchDistance;

/**
* The default constructor that must be supplied. The Atlas Checks framework will generate the
* checks with this constructor, supplying a configuration that can be used to adjust any
* parameters that the check uses during operation.
*
* @param configuration
* the JSON configuration for this check
*/
public AddressStreetNameCheck(final Configuration configuration)
{
super(configuration);
this.searchDistance = configurationValue(configuration, "bounds.size",
SEARCH_DISTANCE_DEFAULT, Distance::meters);
}

/**
* This function will validate if the supplied atlas object is valid for the check.
*
* @param object
* the atlas object supplied by the Atlas-Checks framework for evaluation
* @return {@code true} if this object should be checked
*/
@Override
public boolean validCheckForObject(final AtlasObject object)
{
return object instanceof Point && Validators.hasValuesFor(object, AddressStreetTag.class);
}

/**
* This is the actual function that will check to see whether the object needs to be flagged.
*
* @param object
* the atlas object supplied by the Atlas-Checks framework for evaluation
* @return an optional {@link CheckFlag} object that
*/
@Override
protected Optional<CheckFlag> flag(final AtlasObject object)
{
// Gather the values of all name tags of all edges that are within the search distance
final Set<String> streetNameValues = Iterables
.stream(object.getAtlas().edgesIntersecting(
((Point) object).getLocation().boxAround(this.searchDistance),
Edge::isMasterEdge))
.flatMap(edge -> edge.getTags(tag -> tag.startsWith(NameTag.KEY)).values())
.collectToSet();

// Flag the object if there are edges within the search distance and the addr:street values
// is not present in the set of Edge name tag values
return !streetNameValues.isEmpty()
&& !streetNameValues.contains(object.tag(AddressStreetTag.KEY))
? Optional
.of(this.createFlag(object,
this.getLocalizedInstruction(0, object.getOsmIdentifier(),
this.searchDistance.asMeters())))
: Optional.empty();
}

@Override
protected List<String> getFallbackInstructions()
{
return FALLBACK_INSTRUCTIONS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.openstreetmap.atlas.checks.validation.points;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.openstreetmap.atlas.checks.configuration.ConfigurationResolver;
import org.openstreetmap.atlas.checks.validation.verifier.ConsumerBasedExpectedCheckVerifier;

/**
* Unit test for {@link AddressStreetNameCheck}
*
* @author bbreithaupt
*/
public class AddressStreetNameCheckTest
{
@Rule
public AddressStreetNameCheckTestRule setup = new AddressStreetNameCheckTestRule();

@Rule
public ConsumerBasedExpectedCheckVerifier verifier = new ConsumerBasedExpectedCheckVerifier();

@Test
public void validAddressStreetTagTest()
{
this.verifier.actual(this.setup.validAddressStreetTagAtlas(),
new AddressStreetNameCheck(ConfigurationResolver.emptyConfiguration()));
this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size()));
}

@Test
public void validAddressStreetLocalizedTagTest()
{
this.verifier.actual(this.setup.validAddressStreetLocalizedTagAtlas(),
new AddressStreetNameCheck(ConfigurationResolver.emptyConfiguration()));
this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size()));
}

@Test
public void invalidAddressStreetTagTest()
{
this.verifier.actual(this.setup.invalidAddressStreetTagAtlas(),
new AddressStreetNameCheck(ConfigurationResolver.emptyConfiguration()));
this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size()));
}

@Test
public void validAddressStreetTagConfigNoEdgeInRangeTest()
{
this.verifier.actual(this.setup.validAddressStreetTagAtlas(),
new AddressStreetNameCheck(ConfigurationResolver
.inlineConfiguration("{\"AddressStreetNameCheck.bounds.size\":1.0}")));
this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.openstreetmap.atlas.checks.validation.points;

import org.openstreetmap.atlas.geography.atlas.Atlas;
import org.openstreetmap.atlas.utilities.testing.CoreTestRule;
import org.openstreetmap.atlas.utilities.testing.TestAtlas;
import org.openstreetmap.atlas.utilities.testing.TestAtlas.Edge;
import org.openstreetmap.atlas.utilities.testing.TestAtlas.Loc;
import org.openstreetmap.atlas.utilities.testing.TestAtlas.Node;
import org.openstreetmap.atlas.utilities.testing.TestAtlas.Point;

/**
* Test rule for {@link AddressStreetNameCheckTest}
*
* @author bbreithaupt
*/
public class AddressStreetNameCheckTestRule extends CoreTestRule
{
private static final String TEST_1 = "48.1780944662566,-122.645324334797";
private static final String TEST_2 = "48.1784193930508,-122.644707774486";
private static final String TEST_3 = "48.1789233570657,-122.645035943684";
private static final String TEST_4 = "48.1785221755876,-122.645165222459";

@TestAtlas(
// points
points = {
@Point(coordinates = @Loc(value = TEST_4), tags = { "addr:street=1st st" }) },
// nodes
nodes = { @Node(coordinates = @Loc(value = TEST_1)),
@Node(coordinates = @Loc(value = TEST_2)),
@Node(coordinates = @Loc(value = TEST_3)) },
// edges
edges = {
@Edge(coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2) }, tags = {
"highway=residential", "name=1st st" }),
@Edge(coordinates = { @Loc(value = TEST_2), @Loc(value = TEST_3) }, tags = {
"highway=residential", "name=2nd st" }) })
private Atlas validAddressStreetTagAtlas;

@TestAtlas(
// points
points = { @Point(coordinates = @Loc(value = TEST_4), tags = {
"addr:street=Rue de adresse" }) },
// nodes
nodes = { @Node(coordinates = @Loc(value = TEST_1)),
@Node(coordinates = @Loc(value = TEST_2)) },
// edges
edges = { @Edge(coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2) }, tags = {
"highway=residential", "name=1st st", "name:fr=Rue de adresse" }) })
private Atlas validAddressStreetLocalizedTagAtlas;

@TestAtlas(
// points
points = {
@Point(coordinates = @Loc(value = TEST_4), tags = { "addr:street=3rd st" }) },
// nodes
nodes = { @Node(coordinates = @Loc(value = TEST_1)),
@Node(coordinates = @Loc(value = TEST_2)),
@Node(coordinates = @Loc(value = TEST_3)) },
// edges
edges = {
@Edge(coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2) }, tags = {
"highway=residential", "name=1st st", "name:fr=Rue de adresse" }),
@Edge(coordinates = { @Loc(value = TEST_2), @Loc(value = TEST_3) }, tags = {
"highway=residential", "name=2nd st" }) })
private Atlas invalidAddressStreetTagAtlas;

public Atlas validAddressStreetTagAtlas()
{
return this.validAddressStreetTagAtlas;
}

public Atlas validAddressStreetLocalizedTagAtlas()
{
return this.validAddressStreetLocalizedTagAtlas;
}

public Atlas invalidAddressStreetTagAtlas()
{
return this.invalidAddressStreetTagAtlas;
}
}

0 comments on commit b31060e

Please sign in to comment.