// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.graphview.core.visualisation;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.openstreetmap.josm.plugins.graphview.core.graph.GraphEdge;
import org.openstreetmap.josm.plugins.graphview.core.graph.GraphNode;
import org.openstreetmap.josm.plugins.graphview.core.property.GraphEdgeSegments;
import org.openstreetmap.josm.plugins.graphview.core.property.RoadPropertyType;
import org.openstreetmap.josm.plugins.graphview.core.transition.Segment;
/**
* scheme using values of a property to determine a color.
* Nodes are given an average color of all incoming and outgoing segments.
*/
public class FloatPropertyColorScheme implements ColorScheme {
private final Class<? extends RoadPropertyType<Float>> propertyClass;
private final Map<Float, Color> colorMap;
private final Color defaultColor;
/**
* @param propertyClass property type to get values for; != null
* @param colorMap map from some values to colors.
* Colors for all other values are interpolated.
* This map will be copied and not used directly later. != null
* @param defaultColor color that is used when the property is not available; != null
*/
public FloatPropertyColorScheme(Class<? extends RoadPropertyType<Float>> propertyClass,
Map<Float, Color> colorMap, Color defaultColor) {
assert propertyClass != null && colorMap != null && defaultColor != null;
this.propertyClass = propertyClass;
this.colorMap = new HashMap<>(colorMap);
this.defaultColor = defaultColor;
}
@Override
public Color getSegmentColor(Segment segment) {
assert segment != null;
Float propertyValue = null;
Collection<RoadPropertyType<?>> availableProperties = segment.getAvailableProperties();
for (RoadPropertyType<?> property : availableProperties) {
if (propertyClass.isInstance(property)) {
@SuppressWarnings("unchecked") //has been checked using isInstance
RoadPropertyType<Float> floatProperty = (RoadPropertyType<Float>) property;
propertyValue = segment.getPropertyValue(floatProperty);
break;
}
}
if (propertyValue != null) {
return getColorForValue(propertyValue);
} else {
return defaultColor;
}
}
@Override
public Color getNodeColor(GraphNode node) {
List<Color> segmentColors = new ArrayList<>();
for (GraphEdge edge : node.getInboundEdges()) {
List<Segment> edgeSegments = edge.getPropertyValue(GraphEdgeSegments.PROPERTY);
if (edgeSegments.size() > 0) {
Segment firstSegment = edgeSegments.get(0);
segmentColors.add(getSegmentColor(firstSegment));
}
}
for (GraphEdge edge : node.getOutboundEdges()) {
List<Segment> edgeSegments = edge.getPropertyValue(GraphEdgeSegments.PROPERTY);
if (edgeSegments.size() > 0) {
Segment lastSegment = edgeSegments.get(edgeSegments.size()-1);
segmentColors.add(getSegmentColor(lastSegment));
}
}
if (segmentColors.size() > 0) {
return averageColor(segmentColors);
} else {
return Color.WHITE;
}
}
/**
* returns the color for a value
* @param value value to get color for; != null
* @return color; != null
*/
protected Color getColorForValue(Float value) {
assert value != null;
if (colorMap.containsKey(value)) {
return colorMap.get(value);
} else {
LinkedList<Float> valuesWithDefinedColor = new LinkedList<>(colorMap.keySet());
Collections.sort(valuesWithDefinedColor);
if (value <= valuesWithDefinedColor.getFirst()) {
return colorMap.get(valuesWithDefinedColor.getFirst());
} else if (value >= valuesWithDefinedColor.getLast()) {
return colorMap.get(valuesWithDefinedColor.getLast());
} else {
/* interpolate */
Float lowerValue = valuesWithDefinedColor.getFirst();
Float higherValue = null;
for (Float v : valuesWithDefinedColor) {
if (v >= value) {
higherValue = v;
break;
}
lowerValue = v;
}
assert lowerValue != null && higherValue != null;
Color lowerColor = colorMap.get(lowerValue);
Color higherColor = colorMap.get(higherValue);
float weightHigherColor = (value - lowerValue) / (higherValue - lowerValue);
return weightedAverageColor(lowerColor, higherColor, weightHigherColor);
}
}
}
/**
* returns an average of all colors that have been passed as parameter
*
* @param colors colors to calculate average from; not empty or null
* @return average color; != null
*/
private static Color averageColor(List<Color> colors) {
assert colors != null && colors.size() > 0;
float weightPerColor = 1.0f / colors.size();
Color average = new Color(0, 0, 0);
for (Color color : colors) {
average = new Color(
Math.min(Math.round(average.getRed() + weightPerColor*color.getRed()), 255),
Math.min(Math.round(average.getGreen() + weightPerColor*color.getGreen()), 255),
Math.min(Math.round(average.getBlue() + weightPerColor*color.getBlue()), 255));
}
return average;
}
/**
* returns a weighted average of two colors
*
* @param color1 first color for the average; != null
* @param color2 second color for the average; != null
* @param weightColor2 weight of color2; must be in [0..1]
* @return average color; != null
*/
private static Color weightedAverageColor(Color color1, Color color2, float weightColor2) {
assert color1 != null && color2 != null;
assert 0 <= weightColor2 && weightColor2 <= 1;
return new Color(
Math.round((1 - weightColor2) * color1.getRed() + weightColor2 * color2.getRed()),
Math.round((1 - weightColor2) * color1.getGreen() + weightColor2 * color2.getGreen()),
Math.round((1 - weightColor2) * color1.getBlue() + weightColor2 * color2.getBlue()));
}
}