package de.westnordost.streetcomplete.data.osm.download;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import de.westnordost.streetcomplete.data.meta.OsmAreas;
import de.westnordost.streetcomplete.data.osm.ElementGeometry;
import de.westnordost.osmapi.map.data.Element;
import de.westnordost.osmapi.map.data.LatLon;
import de.westnordost.osmapi.map.data.Node;
import de.westnordost.osmapi.map.data.Relation;
import de.westnordost.osmapi.map.data.RelationMember;
import de.westnordost.osmapi.map.data.Way;
public class ElementGeometryCreator
{
protected WayGeometrySource data;
public void setWayGeometryProvider(WayGeometrySource data)
{
this.data = data;
}
public ElementGeometry create(Node node)
{
return new ElementGeometry(node.getPosition());
}
public ElementGeometry create(Way way)
{
if(data == null) throw new NullPointerException();
List<LatLon> polyline = data.getNodePositions(way.getId());
// unable to create geometry
if(polyline.isEmpty()) return null;
eliminateDuplicates(polyline);
List<List<LatLon>> polylines = new ArrayList<>(1);
polylines.add(polyline);
ElementGeometry result;
if(OsmAreas.isArea(way))
{
// ElementGeometry considers polygons that are defined clockwise holes, so ensure that
// it is defined CCW here.
if(ElementGeometry.isRingDefinedClockwise(polyline)) {
Collections.reverse(polyline);
}
result = new ElementGeometry(null, polylines);
}
else
{
result = new ElementGeometry(polylines, null);
}
if(result.center == null) return null;
return result;
}
private void eliminateDuplicates(List<LatLon> polyline)
{
Iterator<LatLon> it = polyline.iterator();
LatLon previous = null;
while(it.hasNext())
{
LatLon line = it.next();
if(previous == null ||
line.getLatitude() != previous.getLatitude() ||
line.getLongitude() != previous.getLongitude())
{
previous = line;
}
else
{
it.remove();
}
}
}
public ElementGeometry create(Relation relation)
{
if(data == null) throw new NullPointerException();
if(OsmAreas.isArea(relation))
{
return createMultipolygonGeometry(relation);
}
else
{
return createPolylinesGeometry(relation);
}
}
private List<List<LatLon>> getWaysOfRelationWithRole(Relation relation, String role)
{
List<List<LatLon>> result = new ArrayList<>();
for(RelationMember member : relation.getMembers())
{
if(member.getType() != Element.Type.WAY) continue;
long wayId = member.getRef();
if(role == null || role.equals(member.getRole()))
{
List<LatLon> nodePositions = data.getNodePositions(wayId);
eliminateDuplicates(nodePositions);
if(nodePositions.size() > 1)
{
result.add(nodePositions);
}
}
}
return result;
}
private ElementGeometry createPolylinesGeometry(Relation relation)
{
List<List<LatLon>> waysNodePositions = getWaysOfRelationWithRole(relation, null);
ConnectedWays ways = joinWays(waysNodePositions);
List<List<LatLon>> polylines = ways.rest;
polylines.addAll(ways.rings);
// no valid geometry
if(polylines.isEmpty()) return null;
ElementGeometry result = new ElementGeometry(polylines, null);
if(result.center == null) return null;
return result;
}
private ElementGeometry createMultipolygonGeometry(Relation relation)
{
List<List<LatLon>> rings = new ArrayList<>();
rings.addAll(createNormalizedRingGeometry(relation, "outer", false));
rings.addAll(createNormalizedRingGeometry(relation, "inner", true));
// no valid geometry
if(rings.isEmpty()) return null;
ElementGeometry result = new ElementGeometry(null, rings);
if(result.center == null) return null;
return result;
}
private List<List<LatLon>> createNormalizedRingGeometry(Relation relation, String role,
boolean clockwise)
{
List<List<LatLon>> waysNodePositions = getWaysOfRelationWithRole(relation, role);
List<List<LatLon>> ringGeometry = joinWays(waysNodePositions).rings;
setOrientation(ringGeometry, clockwise);
return ringGeometry;
}
/** Ensures that all given rings are defined in clockwise/counter-clockwise direction */
private static void setOrientation(List<List<LatLon>> rings, boolean clockwise)
{
for(List<LatLon> ring : rings)
{
if(ElementGeometry.isRingDefinedClockwise(ring) != clockwise)
{
Collections.reverse(ring);
}
}
}
private static class ConnectedWays
{
List<List<LatLon>> rings = new ArrayList<>();
List<List<LatLon>> rest = new ArrayList<>();
}
private static ConnectedWays joinWays(List<List<LatLon>> waysNodePositions)
{
NodeWayMap<LatLon> nodeWayMap = new NodeWayMap<>(waysNodePositions);
ConnectedWays result = new ConnectedWays();
List<LatLon> currentWay = new ArrayList<>();
while(nodeWayMap.hasNextNode())
{
LatLon node;
if(currentWay.isEmpty())
{
node = nodeWayMap.getNextNode();
}
else
{
node = currentWay.get(currentWay.size()-1);
}
List<List<LatLon>> waysAtNode = nodeWayMap.getWaysAtNode(node);
if(waysAtNode == null)
{
result.rest.add(currentWay);
currentWay = new ArrayList<>();
}
else
{
List<LatLon> way = waysAtNode.get(0);
addTo(way, currentWay);
nodeWayMap.removeWay(way);
// finish ring and start new one
if(isRing(currentWay))
{
result.rings.add(currentWay);
currentWay = new ArrayList<>();
}
}
}
if(!currentWay.isEmpty())
{
result.rest.add(currentWay);
}
return result;
}
private static boolean isRing(List<?> way)
{
return way.get(0).equals(way.get(way.size() - 1));
}
/** add <tt>way</tt> to the end of <tt>polyWay</tt>, if necessary in reverse */
private static void addTo(List<LatLon> way, List<LatLon> polyWay)
{
if(polyWay.isEmpty())
{
polyWay.addAll(way);
}
else
{
LatLon addLast = way.get(way.size() - 1);
LatLon toLast = polyWay.get(polyWay.size() - 1);
if(addLast == toLast)
{
way = Lists.reverse(way);
}
// +1 to not add the first vertex because it has already been added
ListIterator<LatLon> it = way.listIterator(1);
while(it.hasNext())
{
polyWay.add(it.next());
}
}
}
}