package jeql.workbench.ui.geomview; import java.awt.*; import java.awt.geom.*; import java.text.NumberFormat; import com.vividsolutions.jts.awt.PointTransformation; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.math.MathUtil; import com.vividsolutions.jts.util.Assert; /** * Maintains the information associated with mapping * the model view to the screen * * @author Martin Davis * */ public class Viewport implements PointTransformation { private static int INITIAL_VIEW_ORIGIN_X = -10; private static int INITIAL_VIEW_ORIGIN_Y = -10; private GeometryViewPanel panel; /** * Origin of view in model space */ private Point2D viewOriginInModel = new Point2D.Double(INITIAL_VIEW_ORIGIN_X, INITIAL_VIEW_ORIGIN_Y); /** * The scale is the factor which model distance * is multiplied by to get view distance */ private double scale = 1; private PrecisionModel scalePM = new PrecisionModel(scale); private NumberFormat scaleFormat; private Envelope viewEnvInModel; private AffineTransform modelToViewTransform; private java.awt.geom.Point2D.Double srcPt = new java.awt.geom.Point2D.Double(0, 0); private java.awt.geom.Point2D.Double destPt = new java.awt.geom.Point2D.Double(0, 0); public Viewport(GeometryViewPanel panel) { this.panel = panel; setScaleNoUpdate(1.0); } public Envelope getModelEnv() { return viewEnvInModel; } public Envelope getViewEnv() { return new Envelope( 0, getWidthInView(), 0, getHeightInView()); } public double getScale() { return scale; } public void setScaleNoUpdate(double scale) { this.scale = snapScale(scale); scalePM = new PrecisionModel(this.scale); scaleFormat = NumberFormat.getInstance(); int fracDigits = (int) (MathUtil.log10(this.scale)); if (fracDigits < 0) fracDigits = 0; //System.out.println("scale = " + this.scale); //System.out.println("fracdigits = " + fracDigits); scaleFormat.setMaximumFractionDigits(fracDigits); // don't show commas scaleFormat.setGroupingUsed(false); } public void setScale(double scale) { setScaleNoUpdate(scale); update(); } public NumberFormat getScaleFormat() { return scaleFormat; } private static final double ROUND_ERROR_REMOVAL = 0.00000001; /** * Snaps scale to nearest multiple of 2, 5 or 10. * This ensures that model coordinates entered * via the geometry view * don't carry more precision than the zoom level warrants. * * @param scaleRaw * @return */ private static double snapScale(double scaleRaw) { double scale = snapScaleToSingleDigitPrecision(scaleRaw); //System.out.println("requested scale = " + scaleRaw + " scale = " + scale + " Pow10 = " + pow10); return scale; } private static double snapScaleToSingleDigitPrecision(double scaleRaw) { // if the rounding error is not nudged, snapping can "stick" at some values double pow10 = Math.floor(MathUtil.log10(scaleRaw) + ROUND_ERROR_REMOVAL); double nearestLowerPow10 = Math.pow(10, pow10); int scaleDigit = (int) (scaleRaw / nearestLowerPow10); double scale = scaleDigit * nearestLowerPow10; //System.out.println("requested scale = " + scaleRaw + " scale = " + scale + " Pow10 = " + pow10); return scale; } /** * Not used - scaling to multiples of 10,5,2 is too coarse. * * * @param scaleRaw * @return */ private static double snapScaleTo_10_2_5(double scaleRaw) { // if the rounding error is not nudged, snapping can "stick" at some values double pow10 = Math.floor(MathUtil.log10(scaleRaw) + ROUND_ERROR_REMOVAL); double scaleRoundedToPow10 = Math.pow(10, pow10); double scale = scaleRoundedToPow10; // rounding to a power of 10 is too coarse, so allow some finer gradations //* if (3.5 * scaleRoundedToPow10 <= scaleRaw) scale = 5 * scaleRoundedToPow10; else if (2 * scaleRoundedToPow10 <= scaleRaw) scale = 2 * scaleRoundedToPow10; //*/ //System.out.println("requested scale = " + scaleRaw + " scale = " + scale + " Pow10 = " + pow10); return scale; } public double getViewOriginX() { return viewOriginInModel.getX(); } public double getViewOriginY() { return viewOriginInModel.getY(); } public void setViewOrigin(double viewOriginX, double viewOriginY) { this.viewOriginInModel = new Point2D.Double(viewOriginX, viewOriginY); update(); } public boolean intersectsInModel(Envelope env) { return viewEnvInModel.intersects(env); } public Point2D toModel(Point2D viewPt) { srcPt.x = viewPt.getX(); srcPt.y = viewPt.getY(); try { getModelToViewTransform().inverseTransform(srcPt, destPt); } catch (NoninvertibleTransformException ex) { return new Point2D.Double(0, 0); //Assert.shouldNeverReachHere(); } // snap to scale grid double x = scalePM.makePrecise(destPt.x); double y = scalePM.makePrecise(destPt.y); return new Point2D.Double(x, y); } public Coordinate toModelCoordinate(Point2D viewPt) { Point2D p = toModel(viewPt); return new Coordinate(p.getX(), p.getY()); } public void transform(Coordinate modelCoordinate, Point2D point) { point.setLocation(modelCoordinate.x, modelCoordinate.y); getModelToViewTransform().transform(point, point); } public Point2D toView(Coordinate modelCoordinate) { Point2D.Double pt = new Point2D.Double(); transform(modelCoordinate, pt); return pt; } public Point2D toView(Point2D modelPt) { return toView(modelPt, new Point2D.Double()); } public Point2D toView(Point2D modelPt, Point2D viewPt) { return getModelToViewTransform().transform(modelPt, viewPt); } public void update() { updateModelToViewTransform(); viewEnvInModel = computeEnvelopeInModel(); panel.forceRepaint(); } private void updateModelToViewTransform() { modelToViewTransform = new AffineTransform(); modelToViewTransform.translate(0, panel.getSize().height); modelToViewTransform.scale(1, -1); modelToViewTransform.scale(scale, scale); modelToViewTransform.translate(-viewOriginInModel.getX(), -viewOriginInModel.getY()); } public AffineTransform getModelToViewTransform() { if (modelToViewTransform == null) { updateModelToViewTransform(); } return modelToViewTransform; } public void zoomToInitialExtent() { setScale(1); setViewOrigin(INITIAL_VIEW_ORIGIN_X, INITIAL_VIEW_ORIGIN_Y); } public void zoom(Envelope zoomEnv) { zoomToInitialExtent(); double xScale = getWidthInModel() / zoomEnv.getWidth(); double yScale = getHeightInModel() / zoomEnv.getHeight(); setScale(Math.min(xScale, yScale)); double xCentering = (getWidthInModel() - zoomEnv.getWidth()) / 2d; double yCentering = (getHeightInModel() - zoomEnv .getHeight()) / 2d; setViewOrigin(zoomEnv.getMinX() - xCentering, zoomEnv.getMinY() - yCentering); } public double getWidthInModel() { return getUpperRightCornerInModel().getX() - getLowerLeftCornerInModel().getX(); } public double getHeightInModel() { return getUpperRightCornerInModel().getY() - getLowerLeftCornerInModel().getY(); } public Point2D getLowerLeftCornerInModel() { Dimension size = panel.getSize(); return toModel(new Point(0, size.height)); } public Point2D getUpperRightCornerInModel() { Dimension size = panel.getSize(); return toModel(new Point(size.width, 0)); } public double getHeightInView() { return panel.getSize().height; } public double getWidthInView() { return panel.getSize().getWidth(); } /** * Converts a distance in the view to a distance in the model. * * @param viewDist * @return */ public double toModel(double viewDist) { return viewDist / scale; } /** * Converts a distance in the model to a distance in the view. * * @param modelDist * @return */ public double toView(double modelDist) { return modelDist * scale; } private Envelope computeEnvelopeInModel() { double widthInModel = panel.getWidth() / scale; double heighInModel = panel.getHeight() / scale; return new Envelope( viewOriginInModel.getX(), viewOriginInModel.getX() + widthInModel, viewOriginInModel.getY(), viewOriginInModel.getY() + heighInModel); } public boolean containsInModel(Coordinate p) { return viewEnvInModel.contains(p); } private static final int MIN_GRID_RESOLUTION_PIXELS = 2; /** * Gets the magnitude (power of 10) * for the basic grid size. * * @return */ public int gridMagnitudeModel() { double pixelSizeModel = toModel(1); double pixelSizeModelLog = MathUtil.log10(pixelSizeModel); int gridMag = (int) Math.ceil(pixelSizeModelLog); /** * Check if grid size is too small and if so increase it one magnitude */ double gridSizeModel = Math.pow(10, gridMag); double gridSizeView = toView(gridSizeModel); // System.out.println("\ncand gridSizeView= " + gridSizeView); if (gridSizeView <= MIN_GRID_RESOLUTION_PIXELS ) gridMag += 1; // System.out.println("pixelSize= " + pixelSize + " pixelLog10= " + pixelSizeLog); return gridMag; } /** * Gets a PrecisionModel corresponding to the grid size. * * @return */ public PrecisionModel getGridPrecisionModel() { double gridSizeModel = getGridSizeModel(); return new PrecisionModel(1.0/gridSizeModel); } public double getGridSizeModel() { return Math.pow(10, gridMagnitudeModel()); } }