package org.osm2world.core.map_elevation.creation; import static java.lang.Math.abs; import static java.util.Arrays.asList; import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.*; import java.util.ArrayList; import java.util.List; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.world.data.WorldObject; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; /** * a wrapper for an {@link EleConstraintEnforcer} that passes all calls though, * but looks for obvious contradictions in the constraints to help with debugging. */ public class EleConstraintValidator implements EleConstraintEnforcer { private final MapData mapData; private final EleConstraintEnforcer enforcer; private final Multimap<EleConnector, EleConnector> sameEleMap = HashMultimap.create(); private final Multimap<EleConnector, EleConnector> verticalDistMap = HashMultimap.create(); private final List<List<EleConnector>> smoothnessTriples = new ArrayList<List<EleConnector>>(); public EleConstraintValidator(MapData mapData, EleConstraintEnforcer enforcer) { this.mapData = mapData; this.enforcer = enforcer; } @Override public void addConnectors(Iterable<EleConnector> connectors) { enforcer.addConnectors(connectors); for (EleConnector c : connectors) { sameEleMap.put(c, c); } //TODO this does not join connectors added in different addConnectors calls for (EleConnector c1 : connectors) { for (EleConnector c2 : connectors) { if (c1 != c2 && c1.connectsTo(c2)) { sameEleMap.putAll(c1, sameEleMap.get(c2)); sameEleMap.putAll(c2, sameEleMap.get(c1)); } } } } @Override public void requireSameEle(EleConnector c1, EleConnector c2) { enforcer.requireSameEle(c1, c2); if (verticalDistMap.containsEntry(c1, c2)) { failValidation("vertical distance despite same ele", c1, c2); } sameEleMap.putAll(c1, sameEleMap.get(c2)); sameEleMap.putAll(c2, sameEleMap.get(c1)); } @Override public void requireSameEle(Iterable<EleConnector> cs) { enforcer.requireSameEle(cs); for (EleConnector c1 : cs) { for (EleConnector c2 : cs) { if (verticalDistMap.containsEntry(c1, c2)) { failValidation("vertical distance despite same ele", c1, c2); } sameEleMap.putAll(c1, sameEleMap.get(c2)); sameEleMap.putAll(c2, sameEleMap.get(c1)); } } } @Override public void requireVerticalDistance(ConstraintType type, double distance, EleConnector upper, EleConnector lower) { enforcer.requireVerticalDistance(type, distance, upper, lower); if ((type != MAX && distance > 0) || (type != MIN && distance < 0)) { if (sameEleMap.containsEntry(upper, lower)) { failValidation("vertical distance despite same ele", upper, lower); } verticalDistMap.putAll(upper, sameEleMap.get(lower)); verticalDistMap.putAll(lower, sameEleMap.get(upper)); } } @Override public void requireVerticalDistance(ConstraintType type, double distance, EleConnector upper, EleConnector base1, EleConnector base2) { enforcer.requireVerticalDistance(type, distance, upper, base1, base2); } @Override public void requireIncline(ConstraintType type, double incline, List<EleConnector> cs) { enforcer.requireIncline(type, incline, cs); } @Override public void requireSmoothness( EleConnector v1, EleConnector v2, EleConnector v3) { enforcer.requireSmoothness(v1, v2, v3); smoothnessTriples.add(asList(v1, v2, v3)); } @Override public void enforceConstraints() { enforcer.enforceConstraints(); printSmoothnessLog(); } private void printSmoothnessLog() { StringBuilder log = new StringBuilder("smoothness log:\n"); for (List<EleConnector> triple : smoothnessTriples) { double inc1 = getIncline( triple.get(1).getPosXYZ(), triple.get(0).getPosXYZ()); double inc2 = getIncline( triple.get(2).getPosXYZ(), triple.get(1).getPosXYZ()); double inclineDiff = abs(inc2 - inc1); double dist = triple.get(0).pos.distanceTo(triple.get(2).pos); double inclineDiffPerMeter = inclineDiff / dist; if (inclineDiffPerMeter > 200) { log.append(String.format("%.1f%% over %.1fm at ", inclineDiff * 100, dist)); appendEleConnectorString(log, triple.get(1)); log.append('\n'); } } System.out.println(log); } private double getIncline(VectorXYZ v1, VectorXYZ v2) { return (v2.y - v1.y) / v1.distanceToXZ(v2); } private void failValidation(String constraintText, EleConnector... cs ) { StringBuilder text = new StringBuilder("invalid constraint:\n"); text.append(constraintText); text.append("participating connectors:\n"); for (EleConnector c : cs) { appendEleConnectorString(text, c); text.append('\n'); } throw new Error(text.toString()); } private void appendEleConnectorString(StringBuilder out, EleConnector c) { out.append(c); for (WorldObject wo : mapData.getWorldObjects()) { for (EleConnector woConnector : wo.getEleConnectors()) { if (woConnector == c) { out.append(" from " + wo); } } } } }