package de.lmu.ifi.dbs.elki.application.geo; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import de.lmu.ifi.dbs.elki.application.AbstractApplication; import de.lmu.ifi.dbs.elki.data.DoubleVector; import de.lmu.ifi.dbs.elki.data.ModifiableHyperBoundingBox; import de.lmu.ifi.dbs.elki.logging.Logging; import de.lmu.ifi.dbs.elki.math.GeoUtil; import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException; import de.lmu.ifi.dbs.elki.utilities.exceptions.UnableToComplyException; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.GreaterEqualConstraint; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.EnumParameter; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.IntParameter; /** * Visualization function for Cross-track distance function * * TODO: make resolution configurable. * * TODO: make origin point / rectangle configurable. * * @author Niels Dörre * @author Erich Schubert */ public class VisualizeGeodesicDistances extends AbstractApplication { /** * Get a logger for this class. */ private final static Logging LOG = Logging.getLogger(VisualizeGeodesicDistances.class); /** * Visualization mode. * * @author Erich Schubert * * @apiviz.exclude */ public static enum Mode { /** Cross track distance */ CTD, /** Along track distance */ ATD, /** Mindist */ MINDIST } /** * Holds the file to print results to. */ private File out; /** * Image size. */ final int width = 2000, height = 1000; /** * Number of steps for shades. */ protected int steps = 10; /** * Visualization mode */ private Mode mode = Mode.CTD; /** * Constructor. * * @param out Output filename * @param steps Number of steps in the color map * @param mode Visualization mode */ public VisualizeGeodesicDistances(File out, int steps, Mode mode) { super(); this.out = out; this.steps = steps; this.mode = mode; } @Override public void run() throws UnableToComplyException { // Format: Latitude, Longitude // München: DoubleVector stap = new DoubleVector(new double[] { 48.133333, 11.566667 }); // New York: DoubleVector endp = new DoubleVector(new double[] { 40.712778, -74.005833 }); // Bavaria: ModifiableHyperBoundingBox bb = new ModifiableHyperBoundingBox(new double[] { 47.27011150, 8.97634970 }, new double[] { 50.56471420, 13.83963710 }); // Bavaria slice on lat // bb = new ModifiableHyperBoundingBox(new double[] { 47.27011150, -80 }, // // new double[] { 50.56471420, 80 }); // Bavaria slice on lon // bb = new ModifiableHyperBoundingBox(new double[] { -10, 8.97634970 }, // // new double[] { 50, 13.83963710 }); BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); final double max; switch(mode) { case ATD: // Currently half the circumference - we're missing the sign max = GeoUtil.EARTH_RADIUS * Math.PI; break; case CTD: // Quarter (!) the circumference is the maximum CTD! max = .5 * GeoUtil.EARTH_RADIUS * Math.PI; break; case MINDIST: // Half the circumference max = GeoUtil.EARTH_RADIUS * Math.PI; break; default: throw new AbortException("Invalid mode: " + mode); } // Red: left off-course, green: right off-course int red = 0xffff0000; int green = 0xff00ff00; for (int x = 0; x < width; x++) { final double lon = x * 360. / width - 180.; for (int y = 0; y < height; y++) { final double lat = y * -180. / height + 90.; switch(mode) { case ATD: { final double atd = GeoUtil.alongTrackDistance(stap.doubleValue(0), stap.doubleValue(1), endp.doubleValue(0), endp.doubleValue(1), lat, lon); if (atd < 0) { img.setRGB(x, y, colorMultiply(red, -atd / max, false)); } else { img.setRGB(x, y, colorMultiply(green, atd / max, false)); } break; } case CTD: { final double ctd = GeoUtil.crossTrackDistance(stap.doubleValue(0), stap.doubleValue(1), endp.doubleValue(0), endp.doubleValue(1), lat, lon); if (ctd < 0) { img.setRGB(x, y, colorMultiply(red, -ctd / max, false)); } else { img.setRGB(x, y, colorMultiply(green, ctd / max, false)); } break; } case MINDIST: { final double dist = GeoUtil.latlngMinDistDeg(lat, lon, bb.getMin(0), bb.getMin(1), bb.getMax(0), bb.getMax(1)); if (dist < 0) { img.setRGB(x, y, colorMultiply(red, -dist / max, true)); } else { img.setRGB(x, y, colorMultiply(green, dist / max, true)); } break; } } } } try { ImageIO.write(img, "png", out); } catch (IOException e) { LOG.exception(e); } } private int colorMultiply(int col, double reldist, boolean ceil) { if (steps > 0) { if (!ceil) { reldist = Math.round(reldist * steps) * 1. / steps; } else { reldist = Math.ceil(reldist * steps) / steps; } } int a = (col >> 24) & 0xFF, r = (col >> 16) & 0xFF, g = (col >> 8) & 0xFF, b = (col) & 0xFF; a = (int) (a * Math.sqrt(reldist)) & 0xFF; return a << 24 | r << 16 | g << 8 | b; } /** * Main method for application. * * @param args Parameters */ public static void main(String[] args) { VisualizeGeodesicDistances.runCLIApplication(VisualizeGeodesicDistances.class, args); } /** * Parameterization class. * * @author Erich Schubert * * @apiviz.exclude */ public static class Parameterizer extends AbstractApplication.Parameterizer { /** * Number of steps in the distance map. */ public static final OptionID STEPS_ID = new OptionID("ctdvis.steps", "Number of steps for the distance map."); /** * Visualization mode. */ public static final OptionID MODE_ID = new OptionID("ctdvis.mode", "Visualization mode."); /** * Holds the file to print results to. */ protected File out = null; /** * Number of steps in the color map */ protected int steps = 0; /** * Visualization mode */ protected Mode mode = Mode.CTD; @Override protected void makeOptions(Parameterization config) { super.makeOptions(config); out = super.getParameterOutputFile(config, "Output image file"); IntParameter stepsP = new IntParameter(STEPS_ID); stepsP.setOptional(true); stepsP.addConstraint(new GreaterEqualConstraint(0)); if (config.grab(stepsP)) { steps = stepsP.intValue(); } EnumParameter modeP = new EnumParameter(MODE_ID, Mode.class, Mode.CTD); if (config.grab(modeP)) { mode = modeP.getValue(); } } @Override protected VisualizeGeodesicDistances makeInstance() { return new VisualizeGeodesicDistances(out, steps, mode); } } }