/* * This file is part of ELKI: * Environment for Developing KDD-Applications Supported by Index-Structures * * Copyright (C) 2017 * 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 <http://www.gnu.org/licenses/>. */ package de.lmu.ifi.dbs.elki.application.experiments; 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.logging.progress.FiniteProgress; import de.lmu.ifi.dbs.elki.math.geodesy.EarthModel; import de.lmu.ifi.dbs.elki.math.geodesy.SphereUtil; import de.lmu.ifi.dbs.elki.math.geodesy.SphericalVincentyEarthModel; import de.lmu.ifi.dbs.elki.utilities.Alias; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; 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; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ObjectParameter; import net.jafama.FastMath; /** * Visualization function for Cross-track distance function * * TODO: make origin point / rectangle configurable. * * @author Niels Dörre * @author Erich Schubert * @since 0.5.5 */ @Alias({ "de.lmu.ifi.dbs.elki.application.geo.VisualizeGeodesicDistances" }) 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 */ XTD, /** Along track distance */ ATD, /** Mindist */ MINDIST } /** * Holds the file to print results to. */ private File out; /** * Image size. */ protected int width = 2000, height = 1000; /** * Number of steps for shades. */ protected int steps = 10; /** * Visualization mode. */ protected Mode mode = Mode.XTD; /** * Earth model. */ protected EarthModel model; /** * Constructor. * * @param out Output filename * @param steps Number of steps in the color map * @param mode Visualization mode * @param model Earth model */ public VisualizeGeodesicDistances(File out, int resolution, int steps, Mode mode, EarthModel model) { super(); this.width = resolution; this.height = resolution >> 1; this.out = out; this.steps = steps; this.mode = mode; this.model = model; } @Override public void run() { // Format: Latitude, Longitude // München: DoubleVector stap = DoubleVector.wrap(new double[] { 48.133333, 11.566667 }); // New York: DoubleVector endp = DoubleVector.wrap(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 = model.getEquatorialRadius() * Math.PI; // Red: left off-course, green: right off-course int red = 0xffff0000; int green = 0xff00ff00; FiniteProgress prog = LOG.isVerbose() ? new FiniteProgress("columns", width, LOG) : null; 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 = model.getEquatorialRadius() * SphereUtil.alongTrackDistanceDeg(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 XTD: { final double ctd = model.getEquatorialRadius() * SphereUtil.crossTrackDistanceDeg(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 = model.minDistDeg(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; } } } LOG.incrementProcessed(prog); } LOG.ensureCompleted(prog); 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 = FastMath.round(reldist * steps) / steps; } else { reldist = FastMath.ceil(reldist * steps) / steps; } } else if(steps < 0 && reldist > 0.) { double s = reldist * -steps; double off = Math.abs(s - FastMath.round(s)); double factor = -steps * 1. / 1000; // height; if(off < factor) { // Blend with black: factor = (off / factor); int a = (col >> 24) & 0xFF; a = (int) (a * FastMath.sqrt(reldist)) & 0xFF; a = (int) ((1 - factor) * 0xFF + factor * a); int r = (int) (factor * ((col >> 16) & 0xFF)); int g = (int) (factor * ((col >> 8) & 0xFF)); int b = (int) (factor * (col & 0xFF)); return a << 24 | r << 16 | g << 8 | b; } } int a = (col >> 24) & 0xFF, r = (col >> 16) & 0xFF, g = (col >> 8) & 0xFF, b = (col) & 0xFF; a = (int) (a * FastMath.sqrt(reldist)) & 0xFF; return a << 24 | r << 16 | g << 8 | b; } /** * Main method for application. * * @param args Parameters */ public static void main(String[] args) { 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("geodistvis.steps", "Number of steps for the distance map. Use negative numbers to get contour lines."); /** * Image resolution. */ public static final OptionID RESOLUTION_ID = new OptionID("geodistvis.resolution", "Horizontal resolution for the image map (vertical resolution is horizonal / 2)."); /** * Visualization mode. */ public static final OptionID MODE_ID = new OptionID("geodistvis.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; /** * Horizontal resolution. */ protected int resolution = 2000; /** * Visualization mode. */ protected Mode mode = Mode.XTD; /** * Earth model to use. */ protected EarthModel model; @Override protected void makeOptions(Parameterization config) { super.makeOptions(config); out = super.getParameterOutputFile(config, "Output image file name."); IntParameter stepsP = new IntParameter(STEPS_ID); stepsP.setOptional(true); if(config.grab(stepsP)) { steps = stepsP.intValue(); } IntParameter resolutionP = new IntParameter(RESOLUTION_ID, 2000); if(config.grab(resolutionP)) { resolution = resolutionP.intValue(); } EnumParameter<Mode> modeP = new EnumParameter<>(MODE_ID, Mode.class, Mode.XTD); if(config.grab(modeP)) { mode = modeP.getValue(); } ObjectParameter<EarthModel> modelP = new ObjectParameter<>(EarthModel.MODEL_ID, EarthModel.class, SphericalVincentyEarthModel.class); if(config.grab(modelP)) { model = modelP.instantiateClass(config); } } @Override protected VisualizeGeodesicDistances makeInstance() { return new VisualizeGeodesicDistances(out, resolution, steps, mode, model); } } }