/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.geotools.filter; import java.awt.Color; import java.util.ArrayList; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * Unit tests for the Interpolate function. * * @author Michael Bedward */ public class InterpolateFunctionTest extends SEFunctionTestBase { private static final double TOL = 1.0e-6d; private final Double[] data = { 10.0, 20.0, 40.0, 80.0 }; private final Double[] values = { 1.0, 2.0, 3.0, 4.0 }; private final Color[] colors = { Color.RED, Color.ORANGE, Color.GREEN, Color.BLUE }; @Before public void setup() { parameters = new ArrayList<Expression>(); } @Test public void testFindInterpolateFunction() throws Exception { System.out.println(" testFindInterpolateFunction"); Literal fallback = ff2.literal("NOT_FOUND"); setupParameters(data, values); Function fn = finder.findFunction("interpolate", parameters, fallback); Object result = fn.evaluate(feature(0)); assertFalse("Could not locate 'interpolate' function", result.equals(fallback.getValue())); } @Test public void testLinearNumericInterpolation() throws Exception { System.out.println(" testLinearNumericInterpolation"); setupParameters(data, values); parameters.add(ff2.literal(InterpolateFunction.METHOD_NUMERIC)); parameters.add(ff2.literal(InterpolateFunction.MODE_LINEAR)); Function fn = finder.findFunction("interpolate", parameters); // test mid-points Double result; double expected; for (int i = 1; i < data.length; i++) { double testValue = (data[i] + data[i-1]) / 2.0; result = fn.evaluate(feature(Double.valueOf(testValue)), Double.class); expected = (values[i] + values[i-1]) / 2.0; assertEquals(expected, result, TOL); } // test boundaries for (int i = 0; i < data.length; i++) { result = fn.evaluate(feature(Double.valueOf(data[i])), Double.class); expected = values[i]; assertEquals(expected, result, TOL); } // test outside range of interpolation points result = fn.evaluate(feature(Double.valueOf(data[0] - 10)), Double.class); assertEquals(values[0], result, TOL); result = fn.evaluate(feature(Double.valueOf(data[data.length - 1] + 10)), Double.class); assertEquals(values[values.length - 1], result, TOL); } @Test public void testLinearColorInterpolation() throws Exception { System.out.println(" testLinearColorInterpolation"); setupParameters(data, colors); parameters.add(ff2.literal(InterpolateFunction.METHOD_COLOR)); parameters.add(ff2.literal(InterpolateFunction.MODE_LINEAR)); Function fn = finder.findFunction("interpolate", parameters); Color result = null; // at mid-points for (int i = 1; i < data.length; i++) { double testValue = (data[i] + data[i-1]) / 2.0; result = fn.evaluate(feature(testValue), Color.class); Color expected = new Color( (int) Math.round((colors[i].getRed() + colors[i-1].getRed()) / 2.0), (int) Math.round((colors[i].getGreen() + colors[i-1].getGreen()) / 2.0), (int) Math.round((colors[i].getBlue() + colors[i-1].getBlue()) / 2.0) ); assertEquals(expected, result); } // at interpolation points for (int i = 0; i < data.length; i++) { result = fn.evaluate(feature(data[i]), Color.class); assertEquals(colors[i], result); } // outside range of interpolation points result = fn.evaluate(feature(Double.valueOf(data[0] - 10)), Color.class); assertEquals(colors[0], result); result = fn.evaluate(feature(Double.valueOf(data[data.length - 1] + 10)), Color.class); assertEquals(colors[colors.length - 1], result); } @Test public void testCosineNumericInterpolation() throws Exception { System.out.println(" testCosineNumericInterpolation"); setupParameters(data, values); parameters.add(ff2.literal(InterpolateFunction.METHOD_NUMERIC)); parameters.add(ff2.literal(InterpolateFunction.MODE_COSINE)); Function fn = finder.findFunction("interpolate", parameters); // test points within segments away from mid-points double t = 0.1; Double result; double expected; for (int i = 1; i < data.length; i++) { double testValue = data[i-1] + t * (data[i] - data[i-1]); result = fn.evaluate(feature(Double.valueOf(testValue)), Double.class); expected = values[i-1] + (values[i] - values[i-1]) * (1.0 - Math.cos(t * Math.PI)) * 0.5; assertEquals(expected, result, TOL); } // test boundaries for (int i = 0; i < data.length; i++) { result = fn.evaluate(feature(Double.valueOf(data[i])), Double.class); expected = values[i]; assertEquals(expected, result, TOL); } // test outside range of interpolation points result = fn.evaluate(feature(Double.valueOf(data[0] - 10)), Double.class); assertEquals(values[0], result, TOL); result = fn.evaluate(feature(Double.valueOf(data[data.length - 1] + 10)), Double.class); assertEquals(values[values.length - 1], result, TOL); } @Test public void testCubicNumericInterpolation() throws Exception { System.out.println(" testCubicNumericInterpolation"); setupParameters(data, values); parameters.add(ff2.literal(InterpolateFunction.METHOD_NUMERIC)); parameters.add(ff2.literal(InterpolateFunction.MODE_CUBIC)); Function fn = finder.findFunction("interpolate", parameters); // test points within segments away from mid-points double t = 0.1; Double result; double expected; for (int i = 2; i < data.length - 2; i++) { double testValue = data[i-1] + t * (data[i] - data[i-1]); result = fn.evaluate(feature(Double.valueOf(testValue)), Double.class); expected = cubic(testValue, new double[]{data[i-2], data[i-1], data[i], data[i+1]}, new double[]{values[i-2], values[i-1], values[i], values[i+1]}); assertEquals(expected, result, TOL); } // test outside range of interpolation points result = fn.evaluate(feature(Double.valueOf(data[0] - 10)), Double.class); assertEquals(values[0], result, TOL); result = fn.evaluate(feature(Double.valueOf(data[data.length - 1] + 10)), Double.class); assertEquals(values[values.length - 1], result, TOL); } @Test public void testAsRasterData() throws Exception { System.out.println(" testRasterData"); setupParameters(data, colors); parameters.set(0, ff2.literal("RasterData")); parameters.add(ff2.literal(InterpolateFunction.METHOD_COLOR)); parameters.add(ff2.literal(InterpolateFunction.MODE_LINEAR)); Function fn = finder.findFunction("interpolate", parameters); Color result = null; // at mid-points for (int i = 1; i < data.length; i++) { double rasterValue = (data[i] + data[i-1]) / 2.0; result = fn.evaluate(rasterValue, Color.class); Color expected = new Color( (int) Math.round((colors[i].getRed() + colors[i-1].getRed()) / 2.0), (int) Math.round((colors[i].getGreen() + colors[i-1].getGreen()) / 2.0), (int) Math.round((colors[i].getBlue() + colors[i-1].getBlue()) / 2.0) ); assertEquals(expected, result); } // at interpolation points for (int i = 0; i < data.length; i++) { result = fn.evaluate(data[i], Color.class); assertEquals(colors[i], result); } // outside range of interpolation points result = fn.evaluate(Double.valueOf(data[0] - 10), Color.class); assertEquals(colors[0], result); result = fn.evaluate(Double.valueOf(data[data.length - 1] + 10), Color.class); assertEquals(colors[colors.length - 1], result); } @Test public void testForOutOfRangeColorValues() { System.out.println(" out of range color values"); parameters = new ArrayList<Expression>(); parameters.add(ff2.literal("RasterData")); // Create interpolation points that will lead to a cubic // curve going out of range: the unclamped curve will dip // below 0 betwee points 1 and 2 and go above 255 between // points 3 and 4 double[] x = { 0, 1, 2, 3, 4, 5 }; int[] reds = { 128, 0, 0, 255, 255, 128 }; for (int i = 0; i < x.length; i++) { parameters.add(ff2.literal(x[i])); String color = String.format("#%02x0000", reds[i]); parameters.add(ff2.literal(color)); } parameters.add(ff2.literal(InterpolateFunction.METHOD_COLOR)); parameters.add(ff2.literal(InterpolateFunction.MODE_CUBIC)); Function fn = finder.findFunction("interpolate", parameters); // check between points 1 and 2 Color result = fn.evaluate(Double.valueOf(1.5), Color.class); assertEquals(0, result.getRed()); // check between points 3 and 4 result = fn.evaluate(Double.valueOf(3.5), Color.class); assertEquals(255, result.getRed()); } @Test public void testNoMethodParameter() throws Exception { System.out.println(" testNoMethodParameter"); setupParameters(data, values); parameters.add(ff2.literal(InterpolateFunction.MODE_LINEAR)); Function fn = finder.findFunction("interpolate", parameters); // test mid-points Double result; double expected; for (int i = 1; i < data.length; i++) { double testValue = (data[i] + data[i-1]) / 2.0; result = fn.evaluate(feature(Double.valueOf(testValue)), Double.class); expected = (values[i] + values[i-1]) / 2.0; assertEquals(expected, result, TOL); } } @Test public void testNoModeParameter() throws Exception { System.out.println(" testNoModeParameter"); setupParameters(data, values); parameters.add(ff2.literal(InterpolateFunction.METHOD_NUMERIC)); Function fn = finder.findFunction("interpolate", parameters); // test mid-points Double result; double expected; for (int i = 1; i < data.length; i++) { double testValue = (data[i] + data[i-1]) / 2.0; result = fn.evaluate(feature(Double.valueOf(testValue)), Double.class); expected = (values[i] + values[i-1]) / 2.0; assertEquals(expected, result, TOL); } } @Test public void testColorValuesNumericMethodMismatch() throws Exception { System.out.println(" testColorValuesNumericMethodMismatch"); /* * Set interpolation points but let the function default to * linear / numeric */ setupParameters(data, colors); Function fn = finder.findFunction("interpolate", parameters); boolean gotEx = false; try { fn.evaluate(feature(data[1]), Color.class); } catch (IllegalArgumentException ex) { gotEx = true; } assertTrue(gotEx); } @Test public void testNumericValuesColorMethodMismatch() throws Exception { System.out.println(" testNumericValuesColorMethodMismatch"); setupParameters(data, values); parameters.add(ff2.literal(InterpolateFunction.METHOD_COLOR)); Function fn = finder.findFunction("interpolate", parameters); boolean gotEx = false; try { fn.evaluate(feature(data[1]), Double.class); } catch (IllegalArgumentException ex) { gotEx = true; } assertTrue(gotEx); } /** * Set up parameters for the Interpolate function with a set of * input data and output values */ private void setupParameters(Object[] data, Object[] values) { if (data.length != values.length) { throw new IllegalArgumentException("data and values arrays should be the same length"); } parameters = new ArrayList<Expression>(); parameters.add(ff2.property("value")); for (int i = 0; i < data.length; i++) { parameters.add(ff2.literal(data[i])); parameters.add(ff2.literal(values[i])); } } private static double cubic(double x, double[] xi, double[] yi) { double span01 = xi[1] - xi[0]; double span12 = xi[2] - xi[1]; double span23 = xi[3] - xi[2]; double t = (x - xi[1]) / span12; double t2 = t*t; double t3 = t2 * t; double m1 = 0.5 * ((yi[2] - yi[1]) / span12 + (yi[1] - yi[0]) / span01); double m2 = 0.5 * ((yi[3] - yi[2]) / span23 + (yi[2] - yi[1]) / span12); double y = (2*t3 - 3*t2 + 1) * yi[1] + (t3 - 2*t2 + t) * span12 * m1 + (-2*t3 + 3*t2) * yi[2] + (t3 - t2) * span12 * m2; return y; } }