package me.osm.gazetter.striper.builders;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import me.osm.gazetter.striper.readers.RelationsReader.Relation;
import me.osm.gazetter.striper.readers.WaysReader.Way;
import me.osm.gazetter.utils.MultiMap;
import org.slf4j.Logger;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTWriter;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;
import com.vividsolutions.jts.operation.valid.IsValidOp;
import com.vividsolutions.jts.operation.valid.TopologyValidationError;
/**
* Geometry creation utilities
*
* Provides:
* Polygonization
* Invalid geometry mending
*
* */
public class BuildUtils {
//private static final Logger log = LoggerFactory.getLogger(BuildUtils.class);
private static final GeometryFactory geometryFactory = new GeometryFactory();
private static final SelfIntersectionsMender siMender = new BufferSelfIntersectionsMender();
/**
* Build MultiPolygon geometry
*
* Create MultiPolygon for provided collections of
* inner and outer line segments.
*
* Trying to mend errors such as Self-Intersections
*
* */
public static MultiPolygon buildMultyPolygon(final Logger log, final Relation rel,
List<LineString> outers, List<LineString> inners) {
MultiPolygon outer = buildOuterGeometry(log, rel, outers);
if(outer == null) {
return null;
}
if(inners == null || inners.isEmpty()) {
return outer;
}
MultiPolygon inner = polygonizeLinestrings(log, rel, inners);
MultiPolygon mix = substract(outer, inner);
if(mix != null && !mix.isEmpty()) {
IsValidOp validOptions = new IsValidOp(mix);
if(!validOptions.isValid() &&
validOptions.getValidationError().getErrorType()
== TopologyValidationError.DISCONNECTED_INTERIOR) {
mix = (MultiPolygon) mix.buffer(0.0);
}
return mix;
}
else {
log.warn("Multipolygon is invalid after substract inners. Use only outers. Rel. id: {}", rel.id);
return outer;
}
}
private static MultiPolygon buildOuterGeometry(final Logger log,
final Relation rel, List<LineString> outers) {
MultiPolygon outer = polygonizeLinestrings(log, rel, outers);
if(outer == null) {
return null;
}
IsValidOp validOptions = new IsValidOp(outer);
if(!validOptions.isValid()) {
TopologyValidationError validationError = validOptions.getValidationError();
MultiPolygon mended = null;
try {
int errorType = validationError.getErrorType();
if(errorType == TopologyValidationError.SELF_INTERSECTION
|| errorType == TopologyValidationError.RING_SELF_INTERSECTION ) {
Geometry mendedG = siMender.mend(outer);
if(mendedG instanceof MultiPolygon) {
mended = (MultiPolygon)mendedG;
}
else if(mendedG instanceof Polygon) {
mended = geometryFactory.createMultiPolygon(
new Polygon[]{(Polygon) mendedG });
}
}
else if (errorType == TopologyValidationError.NESTED_SHELLS) {
mended = dropOverlaps(outer, validOptions);
}
else {
log.warn("Polygon for relation {} is invalid. Error is {}",
rel.id, validationError.toString());
if(log.isDebugEnabled()) {
log.debug(outer.toString());
}
return null;
}
if(mended == null || !mended.isValid() || mended.isEmpty()) {
log.warn("Can't mend polygon for {}.", rel.id);
if(log.isDebugEnabled()) {
log.debug(outer.toString());
}
return null;
}
log.info("Mend polygon for {}. Error is {}", rel.id, validationError.toString());
outer = mended;
}
catch (Exception e) {
log.warn("Failed to mend polygon for {}. Cause: {}", rel.id, e.getMessage());
}
}
return outer;
}
private static MultiPolygon dropOverlaps(MultiPolygon outer, IsValidOp validOptions) {
Geometry result = null;
for(int i=0; i < outer.getNumGeometries(); i++) {
Geometry geometryN = outer.getGeometryN(i);
if(i == 0) {
result = geometryN;
}
else {
result = result.union(geometryN);
}
}
if(result instanceof MultiPolygon) {
return (MultiPolygon) result;
}
if(result instanceof Polygon) {
return geometryFactory.createMultiPolygon(new Polygon[]{(Polygon)result});
}
return null;
}
private static MultiPolygon substract(MultiPolygon outer, MultiPolygon inner) {
List<Polygon> polygons = new ArrayList<Polygon>();
if(inner != null && !inner.isEmpty()) {
for(int j = 0; j < outer.getNumGeometries(); j++) {
Polygon outerN = (Polygon) outer.getGeometryN(j);
for(int i = 0; i < inner.getNumGeometries(); i++) {
Polygon innerN = (Polygon) inner.getGeometryN(i);
if(outerN.intersects(innerN)) {
outerN = (Polygon) outerN.difference(innerN);
}
}
if(!outerN.isEmpty()) {
polygons.add(outerN);
}
}
}
Polygon[] ps = polygons.toArray(new Polygon[polygons.size()]);
MultiPolygon mp = geometryFactory.createMultiPolygon(ps);
if(mp.isValid()) {
return mp;
}
return null;
}
private static MultiPolygon polygonizeLinestrings(final Logger log, final Relation rel, List<LineString> linestrings) {
if(!linestrings.isEmpty()) {
MultiMap<Point, LineString> point2line = new MultiMap<Point, LineString>();
for(LineString ls : linestrings) {
point2line.put(ls.getStartPoint(), ls);
point2line.put(ls.getEndPoint(), ls);
}
boolean closed = true;
for(Entry<Point, Set<LineString>> entry : point2line.entrySet()) {
if(entry.getValue().size() < 2 ) {
if(!(entry.getValue().size() == 1 && entry.getValue().iterator().next().isClosed())) {
log.warn("Not closed ring in multipolygon. Relation {} near {}",
rel.id, entry.getKey().toString());
closed = false;
}
}
}
if(!closed) {
if(log.isDebugEnabled()) {
for(LineString ls : linestrings) {
log.debug("Not closed ring in {}. Way: {}", rel.id, ls.toString());
}
}
return null;
}
Polygonizer polygonizer = new Polygonizer();
polygonizer.add(linestrings);
try {
@SuppressWarnings("unchecked")
Collection<Polygon> polygons = polygonizer.getPolygons();
if(!polygons.isEmpty()) {
Polygon[] ps = polygons.toArray(new Polygon[polygons.size()]);
MultiPolygon mp = geometryFactory.createMultiPolygon(ps);
return mp;
}
}
catch (Exception e) {
if(log.isWarnEnabled()) {
StringBuilder sb = new StringBuilder();
WKTWriter wktWriter = new WKTWriter();
for(LineString ls : linestrings) {
sb.append("\n").append(wktWriter.write(ls));
}
log.warn("Cant polygonize relation: {} \nLines ({}):{}\nCause: {}", new Object[]{
rel.id,
linestrings.size(),
sb.toString(),
e.getMessage()
});
}
}
}
return null;
}
public static Coordinate[] buildWayGeometry(Way line, List<ByteBuffer> nodes,
final int idOffset, int lonOffset, int latOffset) {
int c = 0;
if(!nodes.isEmpty()) {
Coordinate[] geometry = new Coordinate[line.nodes.size()];
Collections.sort(nodes, Builder.FIRST_LONG_FIELD_COMPARATOR);
for(final long n : line.nodes) {
int ni = Collections.binarySearch(nodes, null, new Comparator<ByteBuffer>() {
@Override
public int compare(ByteBuffer bb, ByteBuffer key) {
return Long.compare(bb.getLong(idOffset), n);
}
});
if(ni >= 0) {
ByteBuffer node = nodes.get(ni);
double lon = node.getDouble(lonOffset);
double lat = node.getDouble(latOffset);
geometry[c++] = new Coordinate(lon, lat);
}
}
return geometry;
}
return null;
}
}