Package org.locationtech.udig.project.internal.render.impl

Source Code of org.locationtech.udig.project.internal.render.impl.ScaleUtils$ZoomCalculation

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2007-2011, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.project.internal.render.impl;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.measure.converter.UnitConverter;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;

import org.locationtech.udig.core.Pair;
import org.locationtech.udig.project.ILayer;
import org.locationtech.udig.project.IMap;
import org.locationtech.udig.project.ProjectBlackboardConstants;
import org.locationtech.udig.project.internal.Layer;
import org.locationtech.udig.project.internal.ProjectPlugin;
import org.locationtech.udig.project.internal.render.ViewportModel;
import org.locationtech.udig.project.preferences.PreferenceConstants;
import org.locationtech.udig.project.render.IRenderManager;
import org.locationtech.udig.project.render.displayAdapter.IMapDisplay;

import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;

/**
* Methods for calculating the ScaleDenominator
*
* @author jesse
*/
public final class ScaleUtils {

  static final double ACCURACY = 0.00000001;
  private static final int MAX_ITERATIONS = 10;
  private static Envelope WORLD = new Envelope(-180, 180, -90, 90);

    /**
     * Default pixel size in meters, producing a default of 90.7 DPI
     */
    public static final double DEFAULT_PIXEL_SIZE_METER = 0.00028;
    public static final double METERS_PER_DEGREE = 6378137.0 * 2.0 * Math.PI / 360.0;
    public static final double DEGREES_PER_METER = 360.0 / 6378137.0 * 2.0 * Math.PI;
    public static final double FEET_TO_METERS = 0.3048;
   
  private ScaleUtils() {/*no instance for you*/}

  /**
   * Calculate the resolution of a for a tile given a tile scale in pixels.
   * <p>
   * This is used to calculate the resolution for a WMS tile. We need to consider the total size of the
   * WMS Layer; and the tile size.
   *
   * @param bounds The full bounds of a WMS layer
   * @param scale The tile scale (viewport_scale * tile.width)
   * @param tileWidth The tile width in pixels
   * @return the resolution
   */
  public static double calculateResolutionFromScale(ReferencedEnvelope bounds, double scale, int tileWidth) {
      if (isLatLong(bounds.getCoordinateReferenceSystem())){
          return  (DEFAULT_PIXEL_SIZE_METER * DEGREES_PER_METER) * ( (scale*tileWidth) * DEGREES_PER_METER);
      } else {
          return DEFAULT_PIXEL_SIZE_METER * scale;
      }
  }

  public static Unit<?> getUnit(CoordinateReferenceSystem crs) {
    return crs.getCoordinateSystem().getAxis(0).getUnit();
  }

  public static double fromMeterToCrs(double value,
      CoordinateReferenceSystem crs) {
    Unit<?> unit = getUnit(crs);
    UnitConverter converter = SI.METER.getConverterTo(unit);
    return converter.convert(value);
  }

  public static double fromCrsToMeter(double value,
      CoordinateReferenceSystem crs) {
    Unit<?> unit = getUnit(crs);
    UnitConverter converter = unit.getConverterTo(javax.measure.unit.SI.METER);
    return converter.convert(value);
  }

  /**
   * Determines if the crs is a lat/long crs (has angular units)
   *
   * @return true if the crs is a latlong crs (has angular units)
   */
  public static boolean isLatLong(CoordinateReferenceSystem crs) {
    Unit<?> unit = getUnit(crs);
    Unit<?> degrees = getUnit(DefaultGeographicCRS.WGS84);
    boolean isLatLong = CRS.equalsIgnoreMetadata(unit, degrees);
    return isLatLong;
  }

  /**
   * Calculates the width of the bounding box that will fit in the display
   * size at the specified scale.
   *
   * @param newScaleDenominator
   *            The scale denominator to use to calculate the bounds
   * @param displaySize
   *            the size of the display
   * @param dpi
   *            the dots per inch of the display
   * @param currentBounds
   *            the current bounds, this is required if the current CRS is
   *            latlong
   * @return the width of the extent that is at the specified scale
   */

  public static ReferencedEnvelope calculateBoundsFromScale(
      double newScaleDenominator, Dimension displaySize, int dpi,
      ReferencedEnvelope currentBounds) {
    double MIN_SCALE = 1.0E-100;
    if (newScaleDenominator <= MIN_SCALE || Double.isInfinite(newScaleDenominator) || Double.isNaN(newScaleDenominator)) {
      return currentBounds;
    }

    return calculateBoundsFromScaleInternal(newScaleDenominator,
        displaySize, dpi, currentBounds, 0);
  }

  private static ReferencedEnvelope calculateBoundsFromScaleInternal(
      double newScaleDenominator, Dimension displaySize, int dpi,
      ReferencedEnvelope currentBounds, int iterations) {
    double oldScaleDenom = calculateScaleDenominator(currentBounds,
        displaySize, dpi);
   
    if(oldScaleDenom<=0 || Double.isInfinite(oldScaleDenom) || Double.isNaN(oldScaleDenom)){
      return currentBounds;
    }
    double ratio = newScaleDenominator / oldScaleDenom;
    double newWidth = currentBounds.getWidth() * ratio;
    ReferencedEnvelope newExtent = calculateBounds(newWidth, displaySize,
        currentBounds);
    double calculatedScale = calculateScaleDenominator(newExtent,
        displaySize, dpi);
    if (Math.abs(calculatedScale - newScaleDenominator) > ACCURACY
        && iterations < MAX_ITERATIONS && calculatedScale>0) {
      return calculateBoundsFromScaleInternal(newScaleDenominator,
          displaySize, dpi, newExtent, iterations + 1);
    } else {
      return newExtent;
    }
  }

  private static ReferencedEnvelope calculateBounds(double width,
      Dimension displaySize, ReferencedEnvelope originalExtent) {
    Coordinate center = originalExtent.centre();
    double height = width * displaySize.height / displaySize.width;
    CoordinateReferenceSystem crs = originalExtent
        .getCoordinateReferenceSystem();
    double minx = center.x - width / 2;
    double maxx = center.x + width / 2;
    double miny = center.y - height / 2;
    double maxy = center.y + height / 2;
    return new ReferencedEnvelope(minx, maxx, miny, maxy, crs);
  }

  private static double distancePerPixel(Dimension displaySize,
      ReferencedEnvelope currentBounds) {
    ReferencedEnvelope referencePixel = toValidPixelBoundsClosestToCenter(
        displaySize, currentBounds);
    try {
      ReferencedEnvelope referencePixelLatLong = referencePixel
          .transform(DefaultGeographicCRS.WGS84, true);
      double minX = referencePixelLatLong.getMinX();
      double maxX = referencePixelLatLong.getMaxX();
      double scale = 1;

      if (referencePixelLatLong.getWidth() > 360) {
        scale = referencePixelLatLong.getWidth() / 18;
        minX = 0;
        maxX = 180;
      }
      GeodeticCalculator calc = new GeodeticCalculator();
      double centerY = centeredYWithinWorld(referencePixelLatLong);
      calc.setStartingGeographicPoint(minX, centerY);
      calc.setDestinationGeographicPoint(maxX, centerY);
      return calc.getOrthodromicDistance() * scale;
    } catch (FactoryException e) {
      ProjectPlugin.log("error transforming: " + referencePixel
          + " to latlong", e);
      return -1;
    } catch (TransformException e) {
            ProjectPlugin.log("error transforming: " + referencePixel
                    + " to latlong", e);
            return -1;
        } catch (AssertionError e) {
            ProjectPlugin.log("Bad parameters", e);
            return -1;
        }
  }

  /**
   * Finds the y coord that is centered extent and or the center of the world
   */
  private static double centeredYWithinWorld(ReferencedEnvelope extent) {
    Coordinate centre = extent.centre();
    if (WORLD.contains(centre)) {
      return centre.y;
    }

    return 0;
  }

  /**
   * Calculates the world bounds of the center pixel of the screen. If the
   * pixel is not within the world lat/long bounds it finds the closest pixel
   * that is within the world and calculates the bounds for that.
   */
  static ReferencedEnvelope toValidPixelBoundsClosestToCenter(
      Dimension displaySize, ReferencedEnvelope currentBounds) {

    Coordinate centre = currentBounds.centre();
    Point referencePixel = nearestPixel(centre.x, centre.y, currentBounds,
        displaySize);

    ReferencedEnvelope pixelBounds = pixelBounds(referencePixel.x,
        referencePixel.y, currentBounds, displaySize);

    return shiftToWorld(pixelBounds);
  }

  static ReferencedEnvelope shiftToWorld(ReferencedEnvelope pixelBounds) {
    DefaultGeographicCRS wgs84 = DefaultGeographicCRS.WGS84;
    ReferencedEnvelope latLong;
    try {
      latLong = pixelBounds.transform(wgs84, true);

      if (WORLD.contains(latLong)) {
        return pixelBounds;
      }

      double deltax = 0, deltay = 0;
      if (latLong.getWidth() < WORLD.getWidth()) {
        if (latLong.getMinX() < WORLD.getMinX()) {
          deltax = WORLD.getMinX() - latLong.getMinX();
        }
        if (latLong.getMaxX() > WORLD.getMaxX()) {
          deltax = WORLD.getMaxX() - latLong.getMaxX();
        }
      }

      if (latLong.getHeight() < WORLD.getHeight()) {
        if (latLong.getMinY() < WORLD.getMinY()) {
          deltay = WORLD.getMinY() - latLong.getMinY();
        }
        if (latLong.getMaxY() > WORLD.getMaxY()) {
          deltay = WORLD.getMaxX() - latLong.getMaxY();
        }
      }

      latLong.translate(deltax, deltay);

      return latLong.transform(
          pixelBounds.getCoordinateReferenceSystem(), true);
    } catch (TransformException e) {
      ProjectPlugin.log("", e);
    } catch (FactoryException e) {
      ProjectPlugin.log("", e);
    }
    return pixelBounds;
  }

  /**
   * calculates the pixel closest to x and y that is contained within the
   * world
   *
   * @param displaySize
   */
  static Point nearestPixel(double x, double y, ReferencedEnvelope extent,
      Dimension displaySize) {
    if (WORLD.contains(x, y)) {
      return worldToPixel(new Coordinate(x, y), extent, displaySize);
    }

    double newX, newY;

    if (x < WORLD.getMinX()) {
      newX = WORLD.getMinX();
    } else if (x > WORLD.getMaxX()) {
      newX = WORLD.getMaxX();
    } else {
      newX = x;
    }

    if (y < WORLD.getMinY()) {
      newY = WORLD.getMinY();
    } else if (y > WORLD.getMaxY()) {
      newY = WORLD.getMaxY();
    } else {
      newY = y;
    }

    return worldToPixel(new Coordinate(newX, newY), extent, displaySize);
  }

  public static ReferencedEnvelope pixelBounds(int x, int y,
      ReferencedEnvelope currentBounds, Dimension displaySize) {
    double minX = ((double) x);
    double maxX = ((double) x) + 1;
    double minY = ((double) y);
    double maxY = ((double) y) + 1;

    Coordinate ul = pixelToWorld(minX, minY, currentBounds, displaySize);
    Coordinate lr = pixelToWorld(maxX, maxY, currentBounds, displaySize);

    if( ul==null || lr==null ){
      return new ReferencedEnvelope(new Envelope(), currentBounds.getCoordinateReferenceSystem());
    }
   
    return new ReferencedEnvelope(ul.x, lr.x, ul.y, lr.y, currentBounds
        .getCoordinateReferenceSystem());

  }

  public static double calculateScaleDenominator(ReferencedEnvelope bounds,
      Dimension displaySize, int dpi) {
   
    if( bounds.getWidth()==0 || bounds.getHeight()==0){
      return -1;
    }

    CoordinateReferenceSystem crs = bounds.getCoordinateReferenceSystem();

    int width = displaySize.width;
    int height = displaySize.height;
    boolean isLatLong = isLatLong(crs);

    if (isLatLong) {
      double distancePerPixel = distancePerPixel(displaySize, bounds);
      if (distancePerPixel < 0) {
        return -1;
      }
      double pixelSize = 1.0 / dpi * 25.4 / 1000;
      double scaleDenominator = distancePerPixel / pixelSize;
      return scaleDenominator;

    } else {
      double diaWidthUnits = Math.sqrt(bounds.getWidth() * bounds.getWidth() + bounds.getHeight() * bounds.getHeight());
      double diaWidthPx = Math.sqrt(width * width + height * height);
      double d1 = fromCrsToMeter(diaWidthUnits , crs);
      double meter = (d1 * dpi / 2.54 * 100.0) / diaWidthPx;
      return meter;
    }
  }

  public static Envelope centerPixelBounds(IMapDisplay display,
      ReferencedEnvelope bounds) {
    Coordinate ul = pixelToWorld((int) (display.getWidth() / 2 - 0.5),
        Math.floor( ((double)display.getHeight()) / 2.0 - 0.5), bounds, display
            .getDisplaySize());
    Coordinate lr = pixelToWorld((int) (display.getWidth() / 2 + 0.5),
        Math.floor( ((double)display.getHeight()) / 2.0 + 0.5), bounds, display.getDisplaySize());
    return new Envelope(ul.x, lr.x, ul.y, lr.y);
  }

  public static boolean withinValidWorld(ReferencedEnvelope bounds) {
    Envelope world = new Envelope(-181, 181, -91, 91);
    return world.contains(bounds.centre());
  }

  public static Coordinate pixelToWorld(double x, double y,
      ReferencedEnvelope extent, Dimension displaySize) {
    // set up the affine transform and calculate scale values
    AffineTransform at = worldToScreenTransform(extent, displaySize);

    try {
      Point2D result = at.inverseTransform(
          new java.awt.geom.Point2D.Double(x, y),
          new java.awt.geom.Point2D.Double());
      Coordinate c = new Coordinate(result.getX(), result.getY());

      return c;
    } catch (Exception e) {
      ProjectPlugin.log("Error transforming point:" + x + "," + y
          + " to a coordinate", e);
    }

    return null;
  }

  /**
   * @see ViewportModel#worldToScreenTransform(Envelope, Dimension)
   */
  public static AffineTransform worldToScreenTransform(Envelope mapExtent,
      Dimension screenSize) {
    double scaleX = screenSize.getWidth() / mapExtent.getWidth();
    double scaleY = screenSize.getHeight() / mapExtent.getHeight();

    double tx = -mapExtent.getMinX() * scaleX;
    double ty = (mapExtent.getMinY() * scaleY) + screenSize.getHeight();

    AffineTransform at = new AffineTransform(scaleX, 0.0d, 0.0d, -scaleY,
        tx, ty);

    return at;
  }
 
  public static Point worldToPixel(Coordinate coord,
      ReferencedEnvelope bounds, Dimension displaySize) {

    Point2D w = new Point2D.Double(coord.x, coord.y);
    AffineTransform at = worldToScreenTransform(bounds, displaySize);
    Point2D p = at.transform(w, new Point2D.Double());
    return new Point((int)Math.round(p.getX()), (int)Math.round(p.getY()));
  }


    /**
     * This method restricts the bounds so that the resulting bounding box is at a legal scale within the bounds.  The centers of
     * the new and old BBox should be the same.
     *
     * @param bbox the bbox to restrict to a value between the min and max scales.
     *
     * @return a new bounds that is withing the min and max bounds of the layer
     */
    public static ReferencedEnvelope fitToMinAndMax(ReferencedEnvelope bbox, ILayer layer) {
        ReferencedEnvelope bounds = restrictMinimum(bbox, layer);
        bounds = restrictMaximum(bounds, layer.getMap(), layer);

        return bounds;
    }

    /**
     * Checks the max scale denominator on the layer
     */
    private static ReferencedEnvelope restrictMaximum(ReferencedEnvelope bounds,
            IMap map, ILayer layer) {
        double maxFromLayer = ((Layer) layer).getMaxScaleDenominator();

        ReferencedEnvelope result = bounds;
        ReferencedEnvelope maxBounds = calculateBoundsFromScale(bounds, maxFromLayer, layer);
        if (bounds.contains((Envelope)maxBounds))
            result = maxBounds;
        return result;
    }

    /**
     * Checks the minScale denominator on the layer and the preference that is
     * set globally.
     */
    private static ReferencedEnvelope restrictMinimum(ReferencedEnvelope bounds, ILayer layer) {
        double minFromLayer = ((Layer) layer).getMinScaleDenominator();

        /*
         * Additional analysis of bounds. What if the only one point exists in
         * the layer? The application should not zoom to the scale 1:1
         */
        Integer minimumScale = layer.getMap().getBlackboard().getInteger(
                ProjectBlackboardConstants.LAYER__MINIMUM_ZOOM_SCALE);
        if (minimumScale == null) {
            minimumScale = ProjectPlugin.getPlugin().getPluginPreferences()
                    .getInt(PreferenceConstants.P_MINIMUM_ZOOM_SCALE);
        }

        ReferencedEnvelope result = bounds;
        if (minimumScale != null && minFromLayer < minimumScale) {
            ReferencedEnvelope minimumBounds = calculateBoundsFromScale(bounds,
                    minimumScale, layer);
            if (minimumBounds.contains((Envelope)bounds))
                result = minimumBounds;
        } else {
            ReferencedEnvelope minimumBounds = calculateBoundsFromScale(bounds,
                    minFromLayer, layer);
            if (minimumBounds.contains((Envelope)bounds))
                result = minimumBounds;
        }
        return result;
    }

    private static ReferencedEnvelope calculateBoundsFromScale(
            ReferencedEnvelope requestedBounds, double scaleDenominator, ILayer layer) {

        IRenderManager renderManager = layer.getMap().getRenderManager();
        if( renderManager==null ){
            // no render manager and therefore no scale to calculate.
            return requestedBounds;
        }
        IMapDisplay mapDisplay = renderManager.getMapDisplay();

        return ScaleUtils.calculateBoundsFromScale(scaleDenominator, mapDisplay
                .getDisplaySize(), mapDisplay.getDPI(), requestedBounds);
    }

    /**
     * Creates affine transform for zooming that keeps <i>fixedPoint</i> stationary.
     *
     * @param zoom zoom ration
     * @param fixedPoint point to keep stationary
     * @return an <i>AffineTransform</i> object containing scale transform that keeps
     *         <i>fixedPoint</i> stationary
     */
    public static AffineTransform createScaleTransformWithFixedPoint( double zoom,
            Coordinate fixedPoint ) {
        AffineTransform t = new AffineTransform();
        t.translate(fixedPoint.x, fixedPoint.y);
        t.scale(1 / zoom, 1 / zoom);
        t.translate(-fixedPoint.x, -fixedPoint.y);
        return t;
    }

  public static Envelope transformEnvelope( ReferencedEnvelope srcEnvelope, AffineTransform transformer ) {
      Point2D lowLeft = new Point2D.Double(srcEnvelope.getMinX(), srcEnvelope.getMinY());
      Point2D transformedLowLeft = new Point2D.Double();
      transformedLowLeft = transformer.transform(lowLeft, transformedLowLeft);
      Point2D upRight = new Point2D.Double(srcEnvelope.getMaxX(), srcEnvelope.getMaxY());
      Point2D transformedUpRight = new Point2D.Double();
      transformedUpRight = transformer.transform(upRight, transformedUpRight);
      return new Envelope(transformedLowLeft.getX(), transformedUpRight.getX(),
              transformedLowLeft.getY(), transformedUpRight.getY());
  }
 
  public static class CalculateZoomLevelParameter {
    public final ViewportModel model;
    public final IMapDisplay display;
    public final double previousZoom;
    public final double zoomChange;
    public final double requiredCloseness;
    public final Coordinate fixedPoint;
    public final boolean alwayUsePreferredZoomLevels;
    public final boolean alwaysChangeZoom;

    /**
     *
     * @param requiredCloseness see calculateClosestScale for a description of this parameter
     */
    public CalculateZoomLevelParameter(ViewportModel model,
        IMapDisplay display, double previousZoom, double zoomChange, Coordinate fixedPoint,
        boolean alwayUsePreferredZoomLevels, boolean alwaysChangeZoom, double requiredCloseness) {
      this.model = model;
      this.display = display;
      this.previousZoom = previousZoom;
      this.zoomChange = zoomChange;
      this.fixedPoint = fixedPoint;
      this.alwayUsePreferredZoomLevels = alwayUsePreferredZoomLevels;
      this.alwaysChangeZoom = alwaysChangeZoom;
      this.requiredCloseness = requiredCloseness;
    }
  }
  public static double calculateZoomLevel(CalculateZoomLevelParameter params) {
   
    SortedSet<Double> preferredScaleDenominators = params.model.getPreferredScaleDenominators();
    if(!params.alwayUsePreferredZoomLevels && !preferredScaleDenominators.isEmpty() &&
        preferredScaleDenominators != params.model.getDefaultPreferredScaleDenominators()) {
          Pair<Double, ReferencedEnvelope> previousScale = calculateScaleFromZoom(params.previousZoom, params.model.getBounds(), params);
          ZoomCalculation targetZoomInfo = new ZoomCalculation(previousScale, params);
         
          double chosen;
         
      double varianceFromPrevious = Math.abs(targetZoomInfo.closestMatch - previousScale.getLeft())/previousScale.getLeft();
      if(params.alwaysChangeZoom && varianceFromPrevious < 0.1) {
            if(params.zoomChange < 1) {
          chosen = targetZoomInfo.nextGreater == null? targetZoomInfo.closestMatch : targetZoomInfo.nextGreater;
            } else {
              chosen = targetZoomInfo.nextSmaller == null ? targetZoomInfo.closestMatch : targetZoomInfo.nextSmaller;
            }
          } else {
            chosen = targetZoomInfo.closestMatch;
          }
          return params.model.getScaleDenominator()/chosen;
    } else {
        return Math.abs(params.previousZoom*params.zoomChange);
    }
  }

  private static Pair<Double, ReferencedEnvelope> calculateScaleFromZoom(double zoom,
      ReferencedEnvelope baseEnv, CalculateZoomLevelParameter params) {
    AffineTransform transformer = ScaleUtils.createScaleTransformWithFixedPoint(zoom,params.fixedPoint);
    ReferencedEnvelope transformedEnvelope = new ReferencedEnvelope(transformEnvelope(baseEnv, transformer),baseEnv.getCoordinateReferenceSystem());
    Double scale = ScaleUtils.calculateScaleDenominator(transformedEnvelope, params.display.getDisplaySize(), params.display.getDPI());
    // if there is a close match in preferred scale round to that scale
    Double closest = calculateClosestScale(params.model.getPreferredScaleDenominators(), scale, params.requiredCloseness);
    if(Math.abs(closest - scale)/scale < 0.01) {
      scale = closest;
    }
    return Pair.create(scale, transformedEnvelope);
  }

  /**
   * Find the scale in the set of scaleDenominators that is closest to scale.
   *
   * @param scaleDenominators the options that can be chosen
   * @param scale the desired scale
   * @param requiredCloseness the nearness required to the next zoom level before the algorithm will allow zooming in to the next leve.
   *                 for example when zooming you might want the scale to be 70% of the way to the next closest level before zooming
   *               in to that level.  This is useful when zooming to a set of features.  If the zoom is 50% like normal then the zoom will not
   *               show all features.
   *               values are 0-1 where 1 means the chosen scale will always be less than scale and 0 means the chosen scale will always be greater
   *               Usually this is set in the preferences.  see zoomClosenessPreference.
   * @return
   */
  public static Double calculateClosestScale(SortedSet<Double> scaleDenominators, double scale, double requiredCloseness) {
    SortedSet<Double> tail = scaleDenominators.tailSet(scale);
    SortedSet<Double> head = scaleDenominators.headSet(scale);
    Double distantZoom = first(tail,Double.MAX_VALUE);
    Double closeZoom = last(head,Double.MIN_VALUE);
    if(Math.abs(distantZoom - Double.MAX_VALUE) < 0.0001) {
      return closeZoom;
    }
    if(Math.abs(closeZoom - Double.MIN_VALUE) < 0.0001) {
      return distantZoom;
    }
    if((distantZoom - scale) < (distantZoom - closeZoom)*requiredCloseness) {
      return distantZoom;
    } else {
      return closeZoom;
    }
  }

  private static class ZoomCalculation {
        Double nextSmaller;
        double closestMatch;
        Double nextGreater;
        double scale;
       
        public ZoomCalculation(Pair<Double,ReferencedEnvelope> previous,CalculateZoomLevelParameter params) {
        scale = calculateScaleFromZoom(params.zoomChange, previous.getRight(), params).getLeft();
        SortedSet<Double> preferredScaleDenominators = new TreeSet<Double>(params.model.getPreferredScaleDenominators());
        boolean zoomingIn = params.zoomChange < 1;
        SortedSet<Double> tailSet = preferredScaleDenominators.tailSet(scale);
        SortedSet<Double> headSet = preferredScaleDenominators.headSet(scale);
       
        if(preferredScaleDenominators.contains(scale)) {
          closestMatch = scale;
          tailSet.remove(scale);
          headSet.remove(scale);
          nextGreater = first(tailSet,null);
          nextSmaller = last(headSet, null);
        } else if(zoomingIn) {
          closestMatch = tailSet.isEmpty() ? scale : tailSet.first();
          tailSet.remove(closestMatch);
          nextGreater = first(tailSet,null);
          nextSmaller = last(headSet,null);
        } else {
          closestMatch = headSet.isEmpty() ? scale : headSet.last();
          headSet.remove(closestMatch);
          nextSmaller = last(headSet,null);
          nextGreater = first(tailSet,null);       
        }
    }
     
      @Override
      public String toString() {
        return scale+" ["+nextSmaller+", *"+closestMatch+"*, "+nextGreater+"]";
      }
  }

  private static Double first(SortedSet<Double> set, Double defaultVal) {
    if(set.isEmpty()) return defaultVal;
    else return set.first();
  }
  private static Double last(SortedSet<Double> set, Double defaultVal) {
    if(set.isEmpty()) return defaultVal;
    else return set.last();
  }

  public static double zoomClosenessPreference() {
    return ProjectPlugin.getPlugin().getPreferenceStore().getDouble(PreferenceConstants.P_ZOOM_REQUIRED_CLOSENESS);
  }
}
TOP

Related Classes of org.locationtech.udig.project.internal.render.impl.ScaleUtils$ZoomCalculation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
m/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');