package org.osm2world.core.map_elevation.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.osm2world.core.map_data.data.MapElement;
import org.osm2world.core.map_data.data.MapWaySegment;
import org.osm2world.core.map_elevation.creation.ElevationCalculator;
import org.osm2world.core.math.VectorXYZ;
import org.osm2world.core.math.VectorXZ;
/**
* elevation profile for a {@link MapWaySegment}
*/
public class WaySegmentElevationProfile extends ElevationProfile {
private final MapWaySegment line;
private List<VectorXYZ> pointsWithEle = null;
public WaySegmentElevationProfile(MapWaySegment line) {
this.line = line;
}
@Override
protected MapElement getElement() {
return line;
}
//TODO: is this needed?
/** sort pointsWithEle by ascending (squared) distance from startPos */
private void sortPointsWithEle() {
Collections.sort(pointsWithEle, new Comparator<VectorXYZ>() {
final VectorXZ startPos = line.getPrimaryRepresentation().getStartPosition();
@Override
public int compare(VectorXYZ v1, VectorXYZ v2) {
return Double.compare(v1.xz().subtract(startPos)
.lengthSquared(), v2.xz().subtract(startPos)
.lengthSquared());
}
});
}
/*
* methods providing access to the results
*/
/**
* returns all points along the line where elevation values exist. This will
* at least include the starting and end point of the line. Other elements
* in between are ordered along the line. Must not be used before calculation
* results have been set using {@link #addPointWithEle(VectorXYZ)}
*/
@Override
public List<VectorXYZ> getPointsWithEle() {
if (pointsWithEle == null) {
throw new IllegalStateException("elevations have not been calculated yet");
} else if (pointsWithEle.size() < 2) {
throw new IllegalStateException("a line must have at least two points with elevation");
}
return pointsWithEle;
}
@Override
public double getEleAt(VectorXZ pos) {
if (pointsWithEle == null) {
throw new IllegalStateException("elevations have not been calculated yet");
} else if (pointsWithEle.size() < 2) {
throw new IllegalStateException("a line must have at least two points with elevation");
}
//TODO: start pos isn't identical with first pointWithEle! can this cause problems?
VectorXZ startPos = line.getPrimaryRepresentation().getStartPosition();
final double posDistance = pos.subtract(startPos).length();
// find points with known elevation directly before and after pos
VectorXYZ before = null;
VectorXYZ after = null;
double beforeDistance = 0;
double afterDistance = 0;
for (VectorXYZ v : pointsWithEle) {
double vFraction = v.xz().subtract(startPos).length();
if (vFraction < posDistance) {
before = v;
beforeDistance = vFraction;
} else if (vFraction == posDistance) {
return v.y;
} else {
after = v;
afterDistance = vFraction;
break; // pointsWithEle are ordered
}
}
// handle pos outside [startPos; endPos]
if (before == null) {
return after.y;
} else if (after == null) {
return before.y;
}
// interpolate between points before and after
double influenceOfAfter = (posDistance - beforeDistance)
/ (afterDistance - beforeDistance);
double ele = before.y * (1-influenceOfAfter) + after.y * influenceOfAfter;
return ele;
}
/**
* adds a result of {@link ElevationCalculator}.
* Must be called at least twice (start and end node)
*/
public void addPointWithEle(VectorXYZ pointWithEle) {
if (pointsWithEle == null) {
pointsWithEle = new ArrayList<VectorXYZ>();
}
this.pointsWithEle.add(pointWithEle);
sortPointsWithEle(); //TODO: (performance) don't do this every time; insert in the right place instead
}
@Override
public double getMaxEle() {
if (pointsWithEle == null) {
throw new IllegalStateException("elevations have not been calculated yet");
}
double maxEle = Double.MIN_VALUE;
for (VectorXYZ pointWithEle : pointsWithEle) {
maxEle = Math.max(maxEle, pointWithEle.y);
}
return maxEle;
}
@Override
public double getMinEle() {
if (pointsWithEle == null) {
throw new IllegalStateException("elevations have not been calculated yet");
}
double minEle = Double.MAX_VALUE;
for (VectorXYZ pointWithEle : pointsWithEle) {
minEle = Math.min(minEle, pointWithEle.y);
}
return minEle;
}
}