From e4c6bb96261f67170a25f8a776a2169447f65fdd Mon Sep 17 00:00:00 2001 From: Daniel B Date: Mon, 21 May 2018 08:09:51 -0700 Subject: [PATCH] Waterbody And Island Size Check (#44) * initial check logic & configuration * change check name; add support for outer and inner multipolygon islands and water body relations * add decimals to instructions filter out inner members with natural=rock tag refactor a couple functions * units tests, code cleanup, additional docs * remove pbf, ha! * instruction cleanup, refactoring & additional unit tests * limit number of surface area calculations, more refactoring and comment updates * limit # of .surface calculation, check tutorial * typos and spotlessApply * doc cleanup * consolidate min/max instructions, make use of .addInstruction & addObject functions, clean up tests & update docs --- config/configuration.json | 17 ++ docs/checks/waterbodyAndIslandSizeCheck.md | 89 ++++++ .../areas/WaterbodyAndIslandSizeCheck.java | 275 ++++++++++++++++++ .../areas/WaterbodyAndIslandSizeTest.java | 100 +++++++ .../areas/WaterbodyAndIslandSizeTestRule.java | 211 ++++++++++++++ 5 files changed, 692 insertions(+) create mode 100644 docs/checks/waterbodyAndIslandSizeCheck.md create mode 100644 src/main/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeCheck.java create mode 100644 src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTest.java create mode 100644 src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTestRule.java diff --git a/config/configuration.json b/config/configuration.json index de7aa35d2..d723c7c55 100644 --- a/config/configuration.json +++ b/config/configuration.json @@ -255,5 +255,22 @@ "defaultPriority": "MEDIUM" }, "tags":"highway" + }, + "WaterbodyAndIslandSizeCheck": { + "surface":{ + "waterbody.minimum.meters": 10.0, + "waterbody.maximum.kilometers": 370000.0, + "island.minimum.meters": 10.0, + "island.maximum.kilometers": 2170000.0, + "islet.minimum.meters": 10.0, + "islet.maximum.kilometers": 1.0 + }, + "challenge": { + "description": "This tasks contains waterboadies and islands which are either too small or too large in size.", + "blurb": "Waterbodies and Islands that are too large, or too small", + "instruction": "Open your favorite editor and examine if the waterbody or island is correctly tagged and mapped.", + "difficulty": "NORMAL", + "defaultPriority": "MEDIUM" + } } } diff --git a/docs/checks/waterbodyAndIslandSizeCheck.md b/docs/checks/waterbodyAndIslandSizeCheck.md new file mode 100644 index 000000000..326b13243 --- /dev/null +++ b/docs/checks/waterbodyAndIslandSizeCheck.md @@ -0,0 +1,89 @@ +# Waterbody and Island Size Check + +#### Description +This check identifies waterbodies that are either to small, or too large in size. Each waterbody and islands' surface area is calculated and compared to minimum and maximum values set in the configuration. Meters squared is the unit of measurement for minimum values, while kilometers squared used when calculating and comparing maximum values. + +#### Live Example +1) This [MultiPolygon Water Relation](https://www.openstreetmap.org/relation/2622285#map=14/59.2859/14.6538) is tagged `natural=WATER`. In this case, the inner members are islands, while the outer member is the waterbody. This particular inner members' ([osm id:372647498](https://www.openstreetmap.org/way/372647498)) surface area is less 10 squared meters. + +2) This islet ([osm id: 23240011](https://www.openstreetmap.org/way/23240011)) is larger than the configured maximum surface area. According to OSM, any islet greater than 1 kilometer squared should be tagged as `place=ISLAND`. + +#### Code Review +In [Atlas](https://github.com/osmlab/atlas), OSM elements are represented as Edges, Points, Lines, +Nodes & Relations; in our case, we’re are looking at [Areas](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Area.java) and [Relations](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Relation.java). + +Our first goal is to validate the incoming Atlas object. Valid features for this check will satisfy +the following conditions: +* Must be an Area with one of the following tags: + * `natural=WATER` + * `place=ISLET` + * `place=ISLAND` + * `place=ARCHIPELAGO` +* If Object is a Relation, the following must be ture: + * Has `type=MULTIPOLYGON` & `natural=WATER` tags + * Has at least 2 members + +```java + @Override + public boolean validCheckForObject(final AtlasObject object) + { + // Object must be either a MultiPolygon Relation with at least one member, or an Area with + // a place=islet, place=island, place=archipelago, or natural=water tag + return (object instanceof Relation && IS_MULTIPOLYGON_WATER_RELATION.test(object) + && ((Relation) object).members().size() >= 2) + || (object instanceof Area + && (TagPredicates.IS_WATER_BODY.test(object) || IS_ISLET.test(object) + || IS_ISLAND.test(object)) + && !this.isFlagged(object.getIdentifier())); +``` + +After our preliminary filtering, we categorize each object as either an Area or Relation, and go from there. If the object is an Area, we simply calculate the surface area in meters and kilometers, and check whether the island, waterbody, or islet is larger or smaller than the configured maximums and minimums. + +```java + if (object instanceof Area) + { + final Area area = (Area) object; + final Surface surfaceArea = area.asPolygon().surface(); + final double surfaceAreaMeters = surfaceArea.asMeterSquared(); + final double surfaceAreaKilometers = surfaceArea.asKilometerSquared(); +... + } +``` + +If the object is a Relation, we must calculate the surface area of each individual member. Similar to above, upon completion of surface area calculation, we can compare the members value to the configured maximum and minimum waterbody and island sizes. + +```java + private Optional getMultiPolygonRelationFlags(final Relation relation, + final Set relationMembers) + { + final CheckFlag flag = new CheckFlag(this.getTaskIdentifier(relation)); + + for (final RelationMember member : relationMembers) + { + final Surface surfaceArea = ((Area) member.getEntity()).asPolygon().surface(); + final double surfaceAreaMeters = surfaceArea.asMeterSquared(); + final double surfaceAreaKilometers = surfaceArea.asKilometerSquared(); + final long memberOsmId = member.getEntity().getOsmIdentifier(); + + // Multipolygon Island Relations + if (member.getRole().equals(RelationTypeTag.MULTIPOLYGON_ROLE_INNER)) + { + if (surfaceAreaMeters < this.islandMinimumArea + || surfaceAreaKilometers > this.islandMaximumArea) +... + } + + // Multipolygon water body Relations + if (member.getRole().equals(RelationTypeTag.MULTIPOLYGON_ROLE_OUTER)) + { + if (surfaceAreaMeters < this.waterbodyMinimumArea + || surfaceAreaKilometers > this.waterbodyMaximumArea) +... + } + } + + } +``` + +To learn more about the code, please look at the comments in the source code for the check. +[WaterbodyAndIslandSizeCheck.java](../../src/main/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeCheck.java) \ No newline at end of file diff --git a/src/main/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeCheck.java b/src/main/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeCheck.java new file mode 100644 index 000000000..35f985630 --- /dev/null +++ b/src/main/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeCheck.java @@ -0,0 +1,275 @@ +package org.openstreetmap.atlas.checks.validation.areas; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.openstreetmap.atlas.checks.atlas.predicates.TagPredicates; +import org.openstreetmap.atlas.checks.base.BaseCheck; +import org.openstreetmap.atlas.checks.flag.CheckFlag; +import org.openstreetmap.atlas.geography.atlas.items.Area; +import org.openstreetmap.atlas.geography.atlas.items.AtlasObject; +import org.openstreetmap.atlas.geography.atlas.items.Relation; +import org.openstreetmap.atlas.geography.atlas.items.RelationMember; +import org.openstreetmap.atlas.tags.NaturalTag; +import org.openstreetmap.atlas.tags.PlaceTag; +import org.openstreetmap.atlas.tags.RelationTypeTag; +import org.openstreetmap.atlas.tags.annotations.validation.Validators; +import org.openstreetmap.atlas.utilities.configuration.Configuration; +import org.openstreetmap.atlas.utilities.scalars.Surface; + +/** + * This check identifies waterbodies and islands that are either too small, or too large in size. + * Minimum surface area values are measured in meters squared; while maximum values are measured in + * kilometers squared. We use two units of conversion to avoid very small/large values. For example, + * 10 meters squared is .00001 kilometers, and 2,000,000 kilometers squared is 2,000,000,000 meters + * squared. An island can either be tagged as place=island, place=islet, place=archipelago, or a + * MultiPolygon Relation with the type=multipolygon and natural=water tags. + * + * @author danielbaah + */ +public class WaterbodyAndIslandSizeCheck extends BaseCheck +{ + + private static final long serialVersionUID = 4105386144665109331L; + private static String WATERBODY_INSTRUCTION = "Waterbody with OSM ID {0,number,#} has a surface" + + " area of {1,number,#.##} meters squared, which is outside of the expected surface area" + + " range of {2} square meters to {3} square kilometers."; + private static String ISLAND_INSTRUCTION = "Island with OSM ID {0,number,#} has a surface area" + + " of {1,number,#.##} meters squared, which is outside of the expected surface area " + + "range of {2} square meters to {3} square kilometers."; + private static String ISLET_INSTRUCTION = "Islet with OSM ID {0,number,#} has an surface area " + + "of {1,number,#.##} meters squared, which is outside of the expected surface area range" + + " of {2} square meters to {3} square kilometers. Islets greater than {3} square " + + "kilometers should likely be tagged as place=ISLAND."; + private static final double WATERBODY_MIN_AREA_DEFAULT = 10; + // Waterbody maximum is based on the Caspian Sea surface area, the largest island waterbody in + // OSM that is tagged with natural=water. + private static final double WATERBODY_MAX_AREA_DEFAULT = 337000; + private static final double ISLAND_MIN_AREA_DEFAULT = 10; + // Island maximum is based on Greenland surface area, the largest island in OSM tagged as + // place=island. The next largest landmass is Australia which is a continent and not an island. + private static final double ISLAND_MAX_AREA_DEFAULT = 2170000; + private static final double ISLET_MIN_AREA_DEFAULT = 10; + // Islet maximum is specified by OSM Wiki (https://wiki.openstreetmap.org/wiki/Tag:place=islet) + private static final double ISLET_MAX_AREA_DEFAULT = 1; + private static final Predicate IS_ISLET = object -> Validators.isOfType(object, + PlaceTag.class, PlaceTag.ISLET); + private static final Predicate IS_ISLAND = object -> Validators.isOfType(object, + PlaceTag.class, PlaceTag.ISLAND, PlaceTag.ARCHIPELAGO); + // Multipolygon Relation must have type=multipolygon and natural=water tags + private static final Predicate IS_MULTIPOLYGON_WATER_RELATION = object -> Validators + .isOfType(object, RelationTypeTag.class, RelationTypeTag.MULTIPOLYGON) + && (Validators.isOfType(object, NaturalTag.class, NaturalTag.WATER)); + private final double waterbodyMinimumArea; + private final double waterbodyMaximumArea; + private final double islandMinimumArea; + private final double islandMaximumArea; + private final double isletMinimumArea; + private final double isletMaximumArea; + private static final List FALLBACK_INSTRUCTIONS = Arrays.asList(WATERBODY_INSTRUCTION, + ISLAND_INSTRUCTION, ISLET_INSTRUCTION); + + @Override + protected List getFallbackInstructions() + { + return FALLBACK_INSTRUCTIONS; + } + + /** + * 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 WaterbodyAndIslandSizeCheck(final Configuration configuration) + { + super(configuration); + this.waterbodyMinimumArea = (double) configurationValue(configuration, + "surface.waterbody.minimum.meters", WATERBODY_MIN_AREA_DEFAULT); + this.waterbodyMaximumArea = (double) configurationValue(configuration, + "surface.waterbody.maximum.kilometers", WATERBODY_MAX_AREA_DEFAULT); + this.islandMinimumArea = (double) configurationValue(configuration, + "surface.island.minimum.meters", ISLAND_MIN_AREA_DEFAULT); + this.islandMaximumArea = (double) configurationValue(configuration, + "surface.island.maximum.kilometers", ISLAND_MAX_AREA_DEFAULT); + this.isletMinimumArea = (double) configurationValue(configuration, + "surface.islet.minimum.meters", ISLET_MIN_AREA_DEFAULT); + this.isletMaximumArea = (double) configurationValue(configuration, + "surface.islet.maximum.kilometers", ISLET_MAX_AREA_DEFAULT); + } + + /** + * 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) + { + // Object must be either a MultiPolygon Relation with at least one member, or an Area with + // a place=islet, place=island, place=archipelago, or natural=water tag + return (object instanceof Relation && IS_MULTIPOLYGON_WATER_RELATION.test(object) + && ((Relation) object).members().size() >= 2) + || (object instanceof Area + && (TagPredicates.IS_WATER_BODY.test(object) || IS_ISLET.test(object) + || IS_ISLAND.test(object)) + && !this.isFlagged(object.getIdentifier())); + } + + /** + * 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 flag(final AtlasObject object) + { + if (object instanceof Area) + { + final Area area = (Area) object; + final Surface surfaceArea = area.asPolygon().surface(); + final double surfaceAreaMeters = surfaceArea.asMeterSquared(); + final double surfaceAreaKilometers = surfaceArea.asKilometerSquared(); + // Mark each object because we could potentially run into Multipolygon entities that get + // passed in as Areas + this.markAsFlagged(area.getIdentifier()); + + // natural=water tag + if (TagPredicates.IS_WATER_BODY.test(object)) + { + if (surfaceAreaMeters < this.waterbodyMinimumArea + || surfaceAreaKilometers > this.waterbodyMaximumArea) + { + return Optional.of(this.createFlag(object, + this.getLocalizedInstruction(0, object.getOsmIdentifier(), + surfaceAreaMeters, this.waterbodyMinimumArea, + this.waterbodyMaximumArea))); + } + } + // place=island or place=archipelago tags + if (IS_ISLAND.test(object)) + { + if (surfaceAreaMeters < this.islandMinimumArea + || surfaceAreaKilometers > this.islandMaximumArea) + { + return Optional.of(this.createFlag(object, + this.getLocalizedInstruction(1, object.getOsmIdentifier(), + surfaceAreaMeters, this.islandMinimumArea, + this.islandMaximumArea))); + } + } + // place=islet tag + if (IS_ISLET.test(object)) + { + if (surfaceAreaMeters < this.isletMinimumArea + || surfaceAreaKilometers > this.isletMaximumArea) + { + return Optional.of(this.createFlag(object, + this.getLocalizedInstruction(2, object.getOsmIdentifier(), + surfaceAreaMeters, this.isletMinimumArea, + this.isletMaximumArea))); + } + } + } + // There are two Relations to examine: + // 1. Islands included as members of a MultiPolygon (not otherwise tagged as place=island or + // place=islet) + // 2. Water bodies included as members of a Multipolygon (not otherwise tagged as + // natural=water) + // Each Relation will have n numbers of instructions, dependent on the amount of members + // that fail the size check + else if (object instanceof Relation) + { + final Relation relation = (Relation) object; + // Get Relation members that are an Area, have either an inner or outer member, + // and have yet to be examined + final Set relationMembers = relation.members().stream() + .filter(this::isValidMultiPolygonRelationMember).collect(Collectors.toSet()); + + if (!relationMembers.isEmpty()) + { + return this.getMultiPolygonRelationFlags(relation, relationMembers); + } + } + + return Optional.empty(); + } + + /** + * Helper function for filtering invalid Relation members. Each Relation member must be an Area, + * have either an inner or outer role, and the inner role must not contain the natural=rock tag. + * + * @param member + * - RelationMember + * @return true if member is valid + */ + private boolean isValidMultiPolygonRelationMember(final RelationMember member) + { + return member.getEntity() instanceof Area + && !this.isFlagged(member.getEntity().getIdentifier()) + && (member.getRole().equals(RelationTypeTag.MULTIPOLYGON_ROLE_OUTER) + || (member.getRole().equals(RelationTypeTag.MULTIPOLYGON_ROLE_INNER) + && !Validators.isOfType(member.getEntity(), NaturalTag.class, + NaturalTag.ROCK))); + } + + /** + * This function calculates the surface area of an individual Relation Member & creates an + * CheckFlag Optional with instruction and member entities it fails the surface area test. + * + * @param relation + * - Relation + * @param relationMembers + * - Valid Relation members + * @return CheckFlag Optional + */ + private Optional getMultiPolygonRelationFlags(final Relation relation, + final Set relationMembers) + { + final CheckFlag flag = new CheckFlag(this.getTaskIdentifier(relation)); + + for (final RelationMember member : relationMembers) + { + final Surface surfaceArea = ((Area) member.getEntity()).asPolygon().surface(); + final double surfaceAreaMeters = surfaceArea.asMeterSquared(); + final double surfaceAreaKilometers = surfaceArea.asKilometerSquared(); + final long memberOsmId = member.getEntity().getOsmIdentifier(); + + // Multipolygon Island Relations + if (member.getRole().equals(RelationTypeTag.MULTIPOLYGON_ROLE_INNER)) + { + if (surfaceAreaMeters < this.islandMinimumArea + || surfaceAreaKilometers > this.islandMaximumArea) + { + flag.addInstruction(this.getLocalizedInstruction(1, memberOsmId, + surfaceAreaMeters, this.islandMinimumArea, this.islandMaximumArea)); + flag.addObject(member.getEntity()); + } + } + // Multipolygon water body Relations + if (member.getRole().equals(RelationTypeTag.MULTIPOLYGON_ROLE_OUTER)) + { + if (surfaceAreaMeters < this.waterbodyMinimumArea + || surfaceAreaKilometers > this.waterbodyMaximumArea) + { + flag.addInstruction( + this.getLocalizedInstruction(0, memberOsmId, surfaceAreaMeters, + this.waterbodyMinimumArea, this.waterbodyMaximumArea)); + flag.addObject(member.getEntity()); + } + } + } + + return !flag.getFlaggedObjects().isEmpty() ? Optional.of(flag) : Optional.empty(); + } +} diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTest.java b/src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTest.java new file mode 100644 index 000000000..f2e40ee12 --- /dev/null +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTest.java @@ -0,0 +1,100 @@ +package org.openstreetmap.atlas.checks.validation.areas; + +import org.junit.Rule; +import org.junit.Test; +import org.openstreetmap.atlas.checks.configuration.ConfigurationResolver; +import org.openstreetmap.atlas.checks.validation.verifier.ConsumerBasedExpectedCheckVerifier; + +/** + * WaterbodyAndIslandSizeTest unit tests + * + * @author danielbaah + */ +public class WaterbodyAndIslandSizeTest +{ + @Rule + public WaterbodyAndIslandSizeTestRule setup = new WaterbodyAndIslandSizeTestRule(); + + @Rule + public ConsumerBasedExpectedCheckVerifier verifier = new ConsumerBasedExpectedCheckVerifier(); + + @Test + public void largeIsletTest() + { + this.verifier.actual(this.setup.getLargeIsletAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyNotEmpty(); + } + + @Test + public void validIslandSizeTest() + { + this.verifier.actual(this.setup.getValidSizeIslandAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyEmpty(); + } + + @Test + public void smallArchipelagoTest() + { + this.verifier.actual(this.setup.getSmallArchipelagoAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyNotEmpty(); + } + + @Test + public void smallWaterbodyTest() + { + this.verifier.actual(this.setup.getSmallWaterbodyAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyNotEmpty(); + } + + @Test + public void smallIsletAtlas() + { + this.verifier.actual(this.setup.getSmallIsletAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyNotEmpty(); + } + + @Test + public void smallMultiPolygonIslandTest() + { + this.verifier.actual(this.setup.getSmallMultiPolygonIslandAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyNotEmpty(); + } + + @Test + public void smallRockMultiPolygonIslandTest() + { + this.verifier.actual(this.setup.getSmallRockMultiPolygonIslandAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyEmpty(); + } + + @Test + public void smallIslandMultiPolygonDuplicateTest() + { + this.verifier.actual(this.setup.getSmallIslandMultiPolygonDuplicateAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyExpectedSize(1); + } + + @Test + public void invalidMultiPolygonNoNaturalWaterTagRelationTest() + { + this.verifier.actual(this.setup.getInvalidMultiPolygonNoNaturalWaterTagRelationAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyEmpty(); + } + + @Test + public void smallMultiPolygonWaterbodyMemberTest() + { + this.verifier.actual(this.setup.getSmallMultiPolygonWaterbodyMemberAtlas(), + new WaterbodyAndIslandSizeCheck(ConfigurationResolver.emptyConfiguration())); + this.verifier.verifyNotEmpty(); + } +} diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTestRule.java b/src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTestRule.java new file mode 100644 index 000000000..d57cc0276 --- /dev/null +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/areas/WaterbodyAndIslandSizeTestRule.java @@ -0,0 +1,211 @@ +package org.openstreetmap.atlas.checks.validation.areas; + +import org.openstreetmap.atlas.geography.atlas.Atlas; +import org.openstreetmap.atlas.tags.RelationTypeTag; +import org.openstreetmap.atlas.utilities.testing.CoreTestRule; +import org.openstreetmap.atlas.utilities.testing.TestAtlas; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Area; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Loc; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Relation; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Relation.Member; + +/** + * WaterbodyAndIslandSizeTestRule test Atlas + * + * @author danielbaah + */ +public class WaterbodyAndIslandSizeTestRule extends CoreTestRule +{ + + private static final String TEST_1 = "17.65021, -63.24537"; + private static final String TEST_2 = "17.61776, -63.26013"; + private static final String TEST_3 = "17.61499, -63.22494"; + private static final String TEST_4 = "17.64467, -63.21018"; + private static final String TEST_5 = "17.65024, -63.23069"; + + private static final String SMALL_AREA_TEST_1 = "17.64874807036599, -63.22976231575012"; + private static final String SMALL_AREA_TEST_2 = "17.64874807036599, -63.229751586914055"; + private static final String SMALL_AREA_TEST_3 = "17.648758294228287, -63.229751586914055"; + private static final String SMALL_AREA_TEST_4 = "17.648758294228287, -63.22976231575012"; + + // Islet with surface area larger than default 10 m^2 maximum + @TestAtlas( + // Area + areas = { @Area(id = "127001", coordinates = { @Loc(value = TEST_1), + @Loc(value = TEST_2), @Loc(value = TEST_3), @Loc(value = TEST_4), + @Loc(value = TEST_5), @Loc(value = TEST_1) }, tags = { "place=islet" }) }) + private Atlas largeIsletAtlas; + + // Island with valid surface area size + @TestAtlas( + // Area + areas = { @Area(id = "127001", coordinates = { @Loc(value = TEST_1), + @Loc(value = TEST_2), @Loc(value = TEST_3), @Loc(value = TEST_4), + @Loc(value = TEST_5), @Loc(value = TEST_1) }, tags = { "place=island" }) }) + private Atlas validSizeIslandAtlas; + + // Archipelago island with surface area smaller than default 10 m^2 minimum + @TestAtlas( + // Area + areas = { @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), + @Loc(value = SMALL_AREA_TEST_1) }, tags = { "place=archipelago" }) }) + private Atlas smallArchipelagoAtlas; + + // Waterbody with surface area smaller than default 10 m^2 + @TestAtlas( + // Area + areas = { @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), + @Loc(value = SMALL_AREA_TEST_1) }, tags = { "natural=water" }) }) + private Atlas smallWaterbodyAtlas; + + // Islet with surface area smaller than default 10 m^2 + @TestAtlas( + // Area + areas = { @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), + @Loc(value = SMALL_AREA_TEST_1) }, tags = { "place=islet" }) }) + private Atlas smallIsletAtlas; + + // MultiPolygon Relation island with surface area smaller than 10 m^2 minimum + @TestAtlas( + // Areas + areas = { + @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), @Loc(value = SMALL_AREA_TEST_1) }), + @Area(id = "127002", coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3), @Loc(value = TEST_4), @Loc(value = TEST_5), + @Loc(value = TEST_1) }) }, + // Relation + relations = { @Relation(id = "1001", members = { + @Member(id = "127001", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_INNER), + @Member(id = "127002", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_OUTER) }, tags = { + "type=multipolygon", "natural=water" }) }) + private Atlas smallMultiPolygonIslandAtlas; + + // MultiPolygon water Relation missing natural=water tag + @TestAtlas( + // Areas + areas = { + @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), @Loc(value = SMALL_AREA_TEST_1) }), + @Area(id = "127002", coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3), @Loc(value = TEST_4), @Loc(value = TEST_5), + @Loc(value = TEST_1) }) }, + // Relation + relations = { @Relation(id = "1001", members = { + @Member(id = "127001", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_INNER), + @Member(id = "127002", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_OUTER) }, tags = { + "type=multipolygon" }) }) + private Atlas invalidMultiPolygonNoNaturalWaterTagRelationAtlas; + + // Multipolygon inner member tagged natural=rock. This should be ignored by check + @TestAtlas( + // Areas + areas = { + @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), + @Loc(value = SMALL_AREA_TEST_1) }, tags = { "natural=rock" }), + @Area(id = "127002", coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3), @Loc(value = TEST_4), @Loc(value = TEST_5), + @Loc(value = TEST_1) }) }, + // Relation + relations = { @Relation(id = "1001", members = { + @Member(id = "127001", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_INNER), + @Member(id = "127002", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_OUTER) }, tags = { + "type=multipolygon", "natural=water" }) }) + private Atlas smallRockMultiPolygonIslandAtlas; + + // Feature 127001 could be flagged as an Area OR a MultiPolygon island + @TestAtlas( + // Areas + areas = { + @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), + @Loc(value = SMALL_AREA_TEST_1) }, tags = { "place=island" }), + @Area(id = "127002", coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3), @Loc(value = TEST_4), @Loc(value = TEST_5), + @Loc(value = TEST_1) }) }, + // Relations + relations = { @Relation(id = "1001", members = { + @Member(id = "127001", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_INNER), + @Member(id = "127002", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_OUTER) }, tags = { + "type=multipolygon", "natural=water" }) }) + private Atlas smallIslandMultiPolygonDuplicateAtlas; + + // MultiPolygon Relation with outer waterbody member smaller than 10 m^2 miminum + @TestAtlas( + // Areas + areas = { + @Area(id = "127001", coordinates = { @Loc(value = SMALL_AREA_TEST_1), + @Loc(value = SMALL_AREA_TEST_2), @Loc(value = SMALL_AREA_TEST_3), + @Loc(value = SMALL_AREA_TEST_4), @Loc(value = SMALL_AREA_TEST_1) }), + @Area(id = "127002", coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3), @Loc(value = TEST_4), @Loc(value = TEST_5), + @Loc(value = TEST_1) }) }, + // Relation + relations = { @Relation(id = "1001", members = { + @Member(id = "127002", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_INNER), + @Member(id = "127001", type = "area", role = RelationTypeTag.MULTIPOLYGON_ROLE_OUTER) }, tags = { + "type=multipolygon", "natural=water" }) }) + private Atlas smallMultiPolygonWaterbodyMemberAtlas; + + public Atlas getLargeIsletAtlas() + { + return largeIsletAtlas; + } + + public Atlas getValidSizeIslandAtlas() + { + return validSizeIslandAtlas; + + } + + public Atlas getSmallArchipelagoAtlas() + { + return smallArchipelagoAtlas; + } + + public Atlas getSmallWaterbodyAtlas() + { + return smallWaterbodyAtlas; + } + + public Atlas getSmallIsletAtlas() + { + return smallIsletAtlas; + } + + public Atlas getSmallMultiPolygonIslandAtlas() + { + return smallMultiPolygonIslandAtlas; + } + + public Atlas getSmallRockMultiPolygonIslandAtlas() + { + return smallRockMultiPolygonIslandAtlas; + } + + public Atlas getSmallIslandMultiPolygonDuplicateAtlas() + { + return smallIslandMultiPolygonDuplicateAtlas; + } + + public Atlas getInvalidMultiPolygonNoNaturalWaterTagRelationAtlas() + { + return invalidMultiPolygonNoNaturalWaterTagRelationAtlas; + } + + public Atlas getSmallMultiPolygonWaterbodyMemberAtlas() + { + return smallMultiPolygonWaterbodyMemberAtlas; + } +}