All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinfour.svm.SvmContourGraph Maven / Gradle / Ivy

Go to download

The Simple Volumetric Model (SVM) uses bathymetry to estimate the capacity of lakes and reservoirs

There is a newer version: 2.1.7
Show newest version
/* --------------------------------------------------------------------
 * Copyright (C) 2019  Gary W. Lucas.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ---------------------------------------------------------------------
 */

 /*
 * -----------------------------------------------------------------------
 *
 * Revision History:
 * Date     Name         Description
 * ------   ---------    -------------------------------------------------
 * 08/2019  G. Lucas     Created  
 *
 * Notes:
 *
 * -----------------------------------------------------------------------
 */
package org.tinfour.svm;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.tinfour.common.IConstraint;
import org.tinfour.common.IIncrementalTin;
import org.tinfour.common.IIncrementalTinNavigator;
import org.tinfour.common.IQuadEdge;
import org.tinfour.common.PolygonConstraint;
import org.tinfour.common.Vertex;
import org.tinfour.contour.Contour;
import org.tinfour.contour.ContourBuilderForTin;
import org.tinfour.contour.ContourRegion;
import org.tinfour.svm.properties.SvmProperties;
import org.tinfour.utils.AxisIntervals;
import org.tinfour.utils.SmoothingFilter;
import org.tinfour.utils.rendering.RenderingSurfaceAid;

/**
 * A utility for writing contour graphs.
 */
class SvmContourGraph {

  /**
   * A palette giving values from blue to yellow based on the CIE LCH color
   * model.
   */
  private static final int[][] paletteB2Y = {
    {0, 0, 255},
    {0, 34, 255},
    {0, 50, 255},
    {0, 63, 255},
    {0, 73, 255},
    {0, 82, 255},
    {0, 90, 255},
    {0, 97, 255},
    {0, 104, 255},
    {0, 110, 255},
    {0, 116, 255},
    {0, 121, 255},
    {0, 127, 255},
    {0, 132, 255},
    {0, 136, 255},
    {0, 141, 255},
    {0, 145, 255},
    {0, 149, 255},
    {0, 153, 255},
    {0, 157, 255},
    {0, 160, 255},
    {0, 164, 255},
    {0, 167, 255},
    {0, 170, 255},
    {0, 174, 255},
    {0, 177, 255},
    {0, 180, 255},
    {0, 183, 255},
    {0, 186, 255},
    {0, 189, 255},
    {0, 192, 255},
    {0, 195, 249},
    {0, 198, 241},
    {0, 201, 233},
    {0, 203, 224},
    {0, 206, 216},
    {0, 209, 207},
    {0, 212, 199},
    {0, 214, 190},
    {0, 217, 181},
    {0, 220, 173},
    {0, 222, 164},
    {0, 225, 156},
    {0, 227, 147},
    {0, 230, 139},
    {0, 232, 131},
    {0, 234, 122},
    {0, 236, 114},
    {0, 238, 106},
    {0, 240, 98},
    {0, 242, 90},
    {0, 244, 82},
    {0, 245, 74},
    {65, 247, 67},
    {94, 248, 59},
    {117, 250, 51},
    {136, 251, 42},
    {154, 252, 34},
    {170, 253, 25},
    {186, 253, 15},
    {200, 254, 4},
    {215, 254, 0},
    {228, 255, 0},
    {242, 255, 0},
    {255, 255, 0}
  };

  private static Color getColor(double index, double iMin, double iMax) {
    int i = (int) ((paletteB2Y.length - 1) * index / (iMax - iMin));
    if (i == 72) {
      System.out.println("merde");
    }
    return new Color(
            paletteB2Y[i][0],
            paletteB2Y[i][1],
            paletteB2Y[i][2]
    );

  }

  private SvmContourGraph() {
    // a private constructor to deter application code from
    // constructing instances of this class.
  }

  static void write(
          PrintStream ps,
          SvmProperties properties,
          SvmBathymetryData data,
          double shoreReferenceElevation,
          IIncrementalTin tin) {
    File output = properties.getContourGraphFile();
    if (output == null) {
      // the properties did not request a contour graph,
      // so don't write one.
      return;
    }

    ps.println("Constructing smoothing filter");
    SmoothingFilter filter = new SmoothingFilter(tin);
    ps.println("Time to construct smoothing filter "
            + filter.getTimeToConstructFilter() + " ms");

    double zMin = filter.getMinZ();
    double zMax = filter.getMaxZ();

    List boundaryConstraints = new ArrayList<>();
    List allConstraints = tin.getConstraints();
    int maxIndex = 0;
    for (IConstraint icon : allConstraints) {
      if (icon instanceof PolygonConstraint) {
        if (icon.getConstraintIndex() > maxIndex) {
          maxIndex = icon.getConstraintIndex();
        }
        boundaryConstraints.add((PolygonConstraint) icon);
      }
    }

    boolean[] water = new boolean[maxIndex + 1];
    for (PolygonConstraint p : boundaryConstraints) {
      Object o = p.getApplicationData();
      if (o instanceof Boolean && (Boolean) o) {
        water[p.getConstraintIndex()] = true;
      }
    }

    // even if all vertices in the source were inside the boundary constraints
    // the flat-fixer logic would have create boundaries outside the
    // constraints (that's a bug, not a feature).  In either case, 
    // it is necessary to force outside values to be at least the
    // shoreline reference elevation.
    ps.println("\nChecking for vertices lying outside of constraints");
    IIncrementalTinNavigator navigator = tin.getNavigator();

    long time0 = System.currentTimeMillis();
    int nOutsiders = 0;
    List vList = tin.getVertices();
    double[] zArray = filter.getVertexAdjustments();
    for (Vertex v : vList) {
      int index = v.getIndex();
      double x = v.getX();
      double y = v.getY();
      IQuadEdge test = navigator.getNeighborEdge(x, y);
      IConstraint con = tin.getRegionConstraint(test);
      if (con == null || !water[con.getConstraintIndex()]) {
        nOutsiders++;
        if (zArray[index] < shoreReferenceElevation) {
          zArray[index] = shoreReferenceElevation;
        }
      }
    }
    filter.setVertexAdjustments(zArray);
    long time1 = System.currentTimeMillis();
    ps.println("Found " + nOutsiders
            + " vertices outside constraints,"
            + "check required " + (time1 - time0) + " ms");

    // For different data sets, we will need different contour intervals.
    // Attempt to create a specification with about 10 contour intervals
    // using the axis too.  Then, take the results from the axis tool
    // and create our zContour array.  Not that the zContours must be
    // greater than the zMin value and less than the shoreReferenceElevation
    double[] aArray = null;
    double contourInterval = properties.getContourGraphInterval();
    if (contourInterval > 0) {
      long i0 = (long) Math.ceil(zMin / contourInterval);
      long i1 = (long) Math.floor(zMax / contourInterval);
      int nC = (int) (i1 - i0 + 1);
      if (nC >= 1 && nC <= 100) {
        aArray = new double[nC];
        for (int i = 0; i < nC; i++) {
          aArray[i] = (i + i0) * contourInterval;
        }
      }
    }
    
    AxisIntervals aIntervals = AxisIntervals.computeIntervals(
            zMin,
            zMax,
            2,
            1,
            20,
              false);
    if (aArray == null) {
      aArray = aIntervals.getLabelCoordinates();
    }
    int i0 = -1;
    int i1 = 0;
    for (int i = 0; i < aArray.length; i++) {
      if (i0 == -1 && aArray[i] > zMin) {
        i0 = i;
      }
      if (aArray[i] < shoreReferenceElevation) {
        i1 = i;
      }
    }

    if (i0 == -1) {
      ps.format("Failed to construct intervals for contour plot");
      return;
    }
    double[] zContour = new double[i1 - i0 + 1];
    for (int i = i0; i <= i1; i++) {
      zContour[i - i0] = aArray[i];
    }

    ps.println("\nBuilding contours for graph");
    ContourBuilderForTin builder
            = new ContourBuilderForTin(tin, filter, zContour, true);
    double areaFactor = properties.getUnitOfArea().getScaleFactor();
    builder.summarize(ps, areaFactor);

    Dimension dimension = properties.getContourGraphDimensions();
    int width = dimension.width;
    int height = dimension.height;

    Rectangle2D bounds = tin.getBounds();
    double x0 = bounds.getMinX();
    double y0 = bounds.getMinY();
    double x1 = bounds.getMaxX();
    double y1 = bounds.getMaxY();
    RenderingSurfaceAid rsa;

    rsa = new RenderingSurfaceAid(width, height, 10, x0, y0, x1, y1);
    rsa.fillBackground(Color.white);

    BufferedImage bImage = rsa.getBufferdImage();
    Graphics2D g2d = rsa.getGraphics2D();
    AffineTransform af = rsa.getCartesianToPixelTransform();
    g2d.setStroke(new BasicStroke(1.0f));

    // The first step is to draw a color-fill representation of the
    // bounding (shoreline) constraint for the body of water.
    int iN = zContour.length;
    Color color = getColor(iN, 0, iN);
    g2d.setColor(color);
    for (PolygonConstraint p : boundaryConstraints) {
      if (water[p.getConstraintIndex()]) {
        g2d.setColor(color);
      } else {
        g2d.setColor(Color.white);
      }
      Path2D path = p.getPath2D(af);
      g2d.fill(path);
      g2d.draw(path);
    }

    // Next, draw the area filled regions 
    List regions = builder.getRegions();
    for (ContourRegion region : regions) {
      int rIndex = region.getRegionIndex();
      if (rIndex >= iN) {
        continue;
      }
      color = getColor(rIndex, 0, iN);
      g2d.setColor(color);
      Path2D path = region.getPathWithNesting(af);
      g2d.fill(path);
      g2d.draw(path);
    }

    // Next, draw the contours in semi-transparent gray
    g2d.setColor(new Color(128, 128, 128, 128));
    List contours = builder.getContours();
    for (Contour c : contours) {
      if (c.getContourType() == Contour.ContourType.Interior) {
        Path2D path = c.getPath2D(af);
        g2d.draw(path);
      }
    }

    // Draw the land-areas in gray
    g2d.setStroke(new BasicStroke(1.0f));
    g2d.setColor(Color.white);
    for (PolygonConstraint p : boundaryConstraints) {
      if (p.getArea() < 0) {
        Path2D path = p.getPath2D(af);
        g2d.fill(path);
      }
    }

    //Draw the shoreline.  Do this last so that it 
    // is on top of all other water features and gives a strong finish
    // to the rendering.
    g2d.setColor(Color.black);
    for (IConstraint con : boundaryConstraints) {
      if (con instanceof PolygonConstraint) {
        PolygonConstraint p = (PolygonConstraint) con;
        Path2D path = p.getPath2D(af);
        g2d.draw(path);
      }
    }

    g2d.setColor(Color.gray);
    g2d.drawRect(0, 0, width - 1, height - 1);

    BufferedImage compositeImage
            = new BufferedImage(width, height + 200, BufferedImage.TYPE_INT_ARGB);
    g2d = compositeImage.createGraphics();
    g2d.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(
            RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    g2d.setColor(Color.white);
    g2d.fillRect(0, 0, width + 1, height + 200 + 1);
    g2d.drawImage(bImage, 0, 0, null);

    Font font = new Font("Arial", Font.PLAIN, 12);
    Font legendFont = new Font("Arial", Font.BOLD, 14);
    String labFmt = aIntervals.getLabelFormat();
    String[] label = new String[zContour.length + 1];
    label[0] = String.format("Below " + labFmt, zContour[0]);
    String testFmt = labFmt + " to " + labFmt;
    for (int i = 1; i < zContour.length; i++) {
      label[i] = String.format(testFmt, zContour[i - 1], zContour[i]);
    }
    label[zContour.length]
            = String.format("Above " + labFmt, zContour[zContour.length - 1]);

    FontRenderContext frc = new FontRenderContext(null, true, true);
    TextLayout[] layout = new TextLayout[label.length];
    double xLabMax = 0;
    double yLabMax = 0;
    for (int i = 0; i < label.length; i++) {
      layout[i] = new TextLayout(label[i], font, frc);
      Rectangle2D r2d = layout[i].getBounds();
      if (r2d.getMaxX() > xLabMax) {
        xLabMax = r2d.getMaxX();
      }
      if (r2d.getMaxY() > yLabMax) {
        yLabMax = r2d.getMaxY();
      }
    }
    if (yLabMax < 20) {
      yLabMax = 20;
    }

    double yLegend = height + 10 + yLabMax;
    g2d.setFont(legendFont);
    g2d.setColor(Color.black);
    g2d.drawString(properties.getContourGraphLegendText(), 20, (int) yLegend);

    int nCol = (label.length + 4) / 5;
    double colWidth = 30 + 5 + xLabMax + 30;
    int k = 0;
    legendPlotLoop:
    for (int iCol = 0; iCol < nCol; iCol++) {
      double xCol = colWidth * iCol + 30;
      for (int iRow = 0; iRow < 5; iRow++) {
        double yRow = iRow * (yLabMax + 5) + yLegend + yLabMax;
        color = getColor(k, 0, iN);
        g2d.setColor(color);
        Rectangle2D r2d = new Rectangle2D.Double(xCol, yRow, 30, yLabMax);
        g2d.fill(r2d);
        g2d.setColor(Color.gray);
        g2d.draw(r2d);
        g2d.setColor(Color.black);
        r2d = layout[k].getBounds();
        float xLab = (float) (xCol + 35);
        float yLab = (float) (yRow + yLabMax / 2 - r2d.getY() / 2);
        layout[k].draw(g2d, xLab, yLab);
        k++;
        if (k == label.length) {
          break legendPlotLoop;
        }
      }
    }

    try {
      ImageIO.write(compositeImage, "PNG", output);
    } catch (IOException ioex) {
      ps.println("IOException writing " + output.getAbsolutePath()
              + ", " + ioex.getMessage());
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy