private Stream<AtlasItem> leafMembers() { final Stream<AtlasItem> nonRelationMembers = members().stream() .map(RelationMember::getEntity).filter(entity -> !(entity instanceof Relation)) .map(entity -> (AtlasItem) entity); final Stream<AtlasItem> relationMembers = members().stream().map(RelationMember::getEntity) .filter(entity -> entity instanceof Relation).map(entity -> (Relation) entity) .flatMap(Relation::leafMembers); return Stream.concat(nonRelationMembers, relationMembers); } }
private boolean isSameRoadViaAndTo(final Relation relation) { final Set<Long> fromIdentifiers = new TreeSet<>(); final Set<Long> toIdentifiers = new TreeSet<>(); relation.members().stream().filter(member -> "to".equals(member.getRole())) .forEach(member -> toIdentifiers.add(member.getEntity().getIdentifier())); relation.members().stream().filter(member -> "from".equals(member.getRole())) .forEach(member -> fromIdentifiers.add(member.getEntity().getIdentifier())); return fromIdentifiers.equals(toIdentifiers); }
/** * Gets all of the polygons contained in this object, if this object has any. * * @param object * any atlas object * @return A singleton stream if object is an Area, a stream if object is a Multipolygon, or an * empty stream if object is neither */ private Stream<Polygon> getPolygons(final AtlasObject object) { if (object instanceof Area) { return Stream.of(((Area) object).asPolygon()); } else if (((Relation) object).isMultiPolygon()) { return ((Relation) object).members().stream().map(this::toPolygon) .flatMap(optPoly -> optPoly.map(Stream::of).orElse(Stream.empty())); } return Stream.empty(); }
@Override protected Optional<CheckFlag> flag(final AtlasObject object) { final Optional<CheckFlag> result; final Relation relation = (Relation) object; if (!TurnRestriction.from(relation).isPresent()) { final Set<AtlasObject> members = relation.members().stream() .map(RelationMember::getEntity).collect(Collectors.toSet()); result = Optional.of(createFlag(members, this.getLocalizedInstruction(0, relation.getOsmIdentifier()))); } else { result = Optional.empty(); } return result; }
/** * When this is called, all lines (closed and non-closed) crossing country boundaries will have * been sliced. In the pre-process step, we want to handle a couple of scenarios. The first one * is when a multipolygon relation is made up of distinct, non-closed lines tied together with a * Relation. It's possible that after line slicing, we may need to close some of the gaps that * formed along the border. Another scenario we need to handle is to look at all closed members * and identify any that need to be merged. * * @param relation * The {@link Relation} to pre-process */ private void preProcessMultiPolygonRelation(final Relation relation) { final Map<String, List<RelationMember>> roleMap = relation.members().stream() .filter(member -> member.getEntity().getType() == ItemType.LINE) .collect(Collectors.groupingBy(RelationMember::getRole)); final List<RelationMember> outers = roleMap.get(RelationTypeTag.MULTIPOLYGON_ROLE_OUTER); final List<RelationMember> inners = roleMap.get(RelationTypeTag.MULTIPOLYGON_ROLE_INNER); patchNonClosedMembers(relation, outers, inners); mergeOverlappingClosedMembers(relation, outers, inners); }
/** * Checks if an {@link AtlasObject} is a outline or part member of a building relation. This is * an equivalent tagging to building=* or building:part=yes. * * @param object * {@link AtlasObject} to check * @return true if the object is part of any relation where it has role outline or part */ private boolean isBuildingRelationMember(final AtlasObject object) { return object instanceof AtlasEntity && ((AtlasEntity) object).relations().stream() .anyMatch(relation -> Validators.isOfType(relation, RelationTypeTag.class, RelationTypeTag.BUILDING) && relation.members().stream() .anyMatch(member -> member.getEntity().equals(object) && (member.getRole().equals("outline")) || member.getRole().equals("part"))); }
/** * This check determines whether an entity is part of an associated street relation. * * @param object * An Atlas entity * @return True if the point is part of an associated street relation, false otherwise. */ private boolean hasAssociatedStreetRelation(final AtlasObject object) { final Point point = (Point) object; return point.relations().stream() .filter(relation -> Validators.isOfType(relation, RelationTypeTag.class, RelationTypeTag.ASSOCIATEDSTREET)) .anyMatch(relation -> relation.members().stream() .anyMatch(member -> member.getRole().equals(STREET_RELATION_ROLE) && member.getEntity().getType().equals(ItemType.EDGE))); } }
final Set<AtlasItem> viaMembers = relation.members().stream() .filter(member -> member.getRole().equals(RelationTypeTag.RESTRICTION_ROLE_VIA)) .filter(member -> member.getEntity() instanceof Node relation.getIdentifier()); relation.members().stream().filter( member -> member.getRole().equals(RelationTypeTag.RESTRICTION_ROLE_TO)) .forEach(member -> temporaryToMembers.add((AtlasItem) member.getEntity())); relation.members().stream().filter(member -> member.getRole() .equals(RelationTypeTag.RESTRICTION_ROLE_FROM) && member.getEntity() instanceof Edge relation.members().stream().filter(member -> member.getRole() .equals(RelationTypeTag.RESTRICTION_ROLE_TO) && member.getEntity() instanceof Edge
@Override public boolean multiPolygonEntityIntersecting(final MultiPolygon multiPolygon, final AtlasEntity entity) { if (entity instanceof LineItem) { return multiPolygon.overlaps(((LineItem) entity).asPolyLine()); } if (entity instanceof LocationItem) { return multiPolygon .fullyGeometricallyEncloses(((LocationItem) entity).getLocation()); } if (entity instanceof Area) { return multiPolygon.overlaps(((Area) entity).asPolygon()); } if (entity instanceof Relation) { return ((Relation) entity).members().stream().map(RelationMember::getEntity) .anyMatch(relationEntity -> this .multiPolygonEntityIntersecting(multiPolygon, relationEntity)); } else { return false; } }
final Set<RelationMember> relationMembers = relation.members().stream() .filter(this::isValidMultiPolygonRelationMember).collect(Collectors.toSet());
@Test public void testRelationMemberLocationItemInclusion() { // Based on https://www.openstreetmap.org/relation/578254 - the Node in the Relation gets // created as both an Atlas point and node. Let's verify that the Relation consists of both. final Atlas slicedRawAtlas = this.setup.getNodeAndPointAsRelationMemberAtlas(); final CountryBoundaryMap boundaryMap = CountryBoundaryMap .fromPlainText(new InputStreamResource(() -> WaySectionProcessorTest.class .getResourceAsStream("nodeAndPointRelationMemberBoundaryMap.txt"))); final Atlas finalAtlas = new WaySectionProcessor(slicedRawAtlas, AtlasLoadingOption.createOptionWithAllEnabled(boundaryMap)).run(); final RelationMemberList members = finalAtlas.relation(578254000000L).members(); Assert.assertEquals("Six members - 2 pairs of reverse edges, 1 node and 1 point", 6, members.size()); Assert.assertEquals("Single point", 1, members.stream() .filter(member -> member.getEntity().getType() == ItemType.POINT).count()); Assert.assertEquals("Single node", 1, members.stream() .filter(member -> member.getEntity().getType() == ItemType.NODE).count()); }
@Override public boolean multiPolygonEntityIntersecting(final MultiPolygon multiPolygon, final AtlasEntity entity) { if (entity instanceof LineItem) { return multiPolygon.fullyGeometricallyEncloses(((LineItem) entity).asPolyLine()); } if (entity instanceof LocationItem) { return multiPolygon .fullyGeometricallyEncloses(((LocationItem) entity).getLocation()); } if (entity instanceof Area) { return multiPolygon.fullyGeometricallyEncloses(((Area) entity).asPolygon()); } if (entity instanceof Relation) { return ((Relation) entity).members().stream().map(RelationMember::getEntity) .anyMatch(relationEntity -> this .multiPolygonEntityIntersecting(multiPolygon, relationEntity)); } else { return false; } }
@Override public boolean polygonEntityIntersecting(final Polygon polygon, final AtlasEntity entity) { if (entity instanceof LineItem) { return polygon.fullyGeometricallyEncloses(((LineItem) entity).asPolyLine()); } if (entity instanceof LocationItem) { return polygon.fullyGeometricallyEncloses(((LocationItem) entity).getLocation()); } if (entity instanceof Area) { return polygon.fullyGeometricallyEncloses(((Area) entity).asPolygon()); } if (entity instanceof Relation) { return ((Relation) entity).members().stream().map(RelationMember::getEntity) .anyMatch(relationEntity -> this.polygonEntityIntersecting(polygon, relationEntity)); } else { return false; } }
@Override public boolean geometricSurfaceEntityIntersecting(final GeometricSurface geometricSurface, final AtlasEntity entity) { if (entity instanceof LineItem) { return geometricSurface .fullyGeometricallyEncloses(((LineItem) entity).asPolyLine()); } if (entity instanceof LocationItem) { return geometricSurface .fullyGeometricallyEncloses(((LocationItem) entity).getLocation()); } if (entity instanceof Area) { return geometricSurface.fullyGeometricallyEncloses(((Area) entity).asPolygon()); } if (entity instanceof Relation) { return ((Relation) entity).members().stream().map(RelationMember::getEntity) .anyMatch(relationEntity -> this.geometricSurfaceEntityIntersecting( geometricSurface, relationEntity)); } else { return false; } } };
final List<RelationMember> matchingMembers = relation.members().stream() .filter(member -> matcher.test(member.getEntity())) .collect(Collectors.toList());
final List<RelationMember> validMembers = relation.members().stream() .filter(member -> builder.peek().entity(member.getEntity().getIdentifier(), member.getEntity().getType()) != null)
@Test public void testRemoveRelationMemberIsReflectedInMemberListAutomatically() { final Atlas atlas = this.rule.getAtlas(); final ChangeBuilder changeBuilder = new ChangeBuilder(); final Relation disconnectedFeatures = atlas.relation(41834000000L); // Remove the point from the list final RelationMemberList newMembers = new RelationMemberList(disconnectedFeatures.members() .stream().filter(member -> !(member.getEntity() instanceof Point)) .collect(Collectors.toList())); // Here only remove the point. Do not remove the member in the relation. It should // automatically be removed. changeBuilder.add(new FeatureChange(ChangeType.REMOVE, CompletePoint.shallowFrom(atlas.point(41822000000L)))); final Change change = changeBuilder.get(); final Atlas changeAtlas = new ChangeAtlas(atlas, change); Assert.assertEquals(newMembers.asBean(), changeAtlas.relation(41834000000L).members().asBean()); final Relation parentRelation = changeAtlas.relation(41860000000L); final Relation fromRelation = (Relation) Iterables.stream(parentRelation.members()) .firstMatching(member -> "child1".equals(member.getRole())).get().getEntity(); Assert.assertEquals(newMembers.asBean(), fromRelation.members().asBean()); }
.stream().filter(member -> !member.getEntity().equals(edge)) .collect(Collectors.toList())); return CompleteRelation.shallowFrom(relation)
@Test public void testRemoveRelationMember() { final Atlas atlas = this.rule.getAtlas(); final ChangeBuilder changeBuilder = new ChangeBuilder(); final Relation disconnectedFeatures = atlas.relation(41834000000L); // Remove the point from the list final RelationMemberList newMembers = new RelationMemberList(disconnectedFeatures.members() .stream().filter(member -> !(member.getEntity() instanceof Point)) .collect(Collectors.toList())); changeBuilder.add( new FeatureChange(ChangeType.ADD, CompleteRelation.shallowFrom(disconnectedFeatures) .withMembersAndSource(newMembers, disconnectedFeatures))); changeBuilder.add(new FeatureChange(ChangeType.REMOVE, CompletePoint.shallowFrom(atlas.point(41822000000L)))); final Change change = changeBuilder.get(); final Atlas changeAtlas = new ChangeAtlas(atlas, change); Assert.assertEquals(newMembers.asBean(), changeAtlas.relation(41834000000L).members().asBean()); final Relation parentRelation = changeAtlas.relation(41860000000L); final Relation fromRelation = (Relation) Iterables.stream(parentRelation.members()) .firstMatching(member -> "child1".equals(member.getRole())).get().getEntity(); Assert.assertEquals(newMembers.asBean(), fromRelation.members().asBean()); }
@Test public void testRemovedRelationMemberFromIndirectRemoval() { final Atlas atlas = this.rule.getAtlas(); // Remove a node. This removal will trigger an indirect removal of the connected edges. This // should also trigger an indirect removal of these members from any relation member lists. final ChangeBuilder changeBuilder = new ChangeBuilder(); changeBuilder.add(new FeatureChange(ChangeType.REMOVE, CompleteNode.shallowFrom(atlas.node(38984000000L)))); final Atlas changeAtlas = new ChangeAtlas(atlas, changeBuilder.get()); final RelationMemberList memberList = changeAtlas.relation(39008000000L).members(); final RelationMemberList newMembers = new RelationMemberList( changeAtlas.relation(39008000000L).members().stream() .filter(member -> member.getEntity().getIdentifier() == -39002000001L || member.getEntity().getIdentifier() == 39002000001L) .collect(Collectors.toList())); Assert.assertEquals(newMembers, memberList); // Now, let's do the same thing but remove an additional node. This will trigger the // indirect removal of all the relation's members - so we should see the relation disappear. final ChangeBuilder changeBuilder2 = new ChangeBuilder(); changeBuilder2.add(new FeatureChange(ChangeType.REMOVE, CompleteNode.shallowFrom(atlas.node(38984000000L)))); changeBuilder2.add(new FeatureChange(ChangeType.REMOVE, CompleteNode.shallowFrom(atlas.node(38982000000L)))); final Atlas changeAtlas2 = new ChangeAtlas(atlas, changeBuilder2.get()); Assert.assertNull(changeAtlas2.relation(39008000000L)); }