measuretool.java

来自「world wind java sdk 源码」· Java 代码 · 共 1,437 行 · 第 1/4 页

JAVA
1,437
字号
/*
Copyright (C) 2001, 2008 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.util.measure;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.Logging;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.util.List;

/**
 * A utility class to interactively draw shapes and measure distance and area across the terrain. When armed,
 * the class monitors mouse events to allow the definition of a measure shape that can be one of {@link #SHAPE_LINE},
 * {@link #SHAPE_PATH}, {@link #SHAPE_POLYGON}, {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, {@link #SHAPE_SQUARE}
 * or {@link #SHAPE_QUAD}.
 *
 * <p>In order to allow user interaction with the measuring shape, a controller must be set by calling
 * {@link #setController(MeasureToolController)} with a new instance of a <code>MeasureToolController</code>.</p>
 *
 * <p>The interaction sequence for drawing a shape and measuring is as follows:
 * <ul>
 * <li>Set the measure shape.</li>
 * <li>Arm the <code>MeasureTool</code> object by calling its {@link #setArmed(boolean)} method with an argument
 * of true.</li>
 * <li>Click on the terrain to add points.</li>
 * <li>Disarm the <code>MeasureTool</code> object by calling its {@link #setArmed(boolean)} method with an argument
 * of false. </li>
 * <li>Read the measured length or area by calling the <code>MeasureTool</code> {@link #getLength()} or
 * {@link #getArea()} method. Note that the length and area can be queried at any time during or after the process.</li>
 * </ul>
 * </p>
 * <p>While entering points or after the measure tool has been disarmed, dragging the control points allow to
 * change the initial points positions and alter the measure shape.</p>
 *
 * <p> While the <code>MeasureTool</code> is armed, pressing and immediately releasing mouse button one while also
 * pressing the control key (Ctl) removes the last point entered. </p>
 *
 * <p>Arming and disarming the <code>MeasureTool</code> does not change the contents or attributes of the measure
 * tool's layer. Note that the measure tool will NOT disarm itself after the second point of a line or a regular shape
 * has been entered - the MeasureToolController has that responsability.</p>
 *
 * <p><b>Setting the measure shape from the application</b></p>
 *
 * <p>The application can set the measure shape to an arbitrary list of positions using
 * {@link #setPositions(java.util.ArrayList)}. If the provided list contains two positions, the measure shape will
 * be set to {@link #SHAPE_LINE}. If more then two positions are provided, the measure shape will be set to
 * {@link #SHAPE_PATH} if the last position differs from the first (open path), or {@link #SHAPE_POLYGON} if the
 * path is closed.</p>
 *
 * <p>The application can also set the measure shape to a predefined regular shape by calling
 * {@link #setMeasureShape(String, Position, double, double, Angle)}, providing a shape type (one of
 * {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, {@link #SHAPE_SQUARE} or {@link #SHAPE_QUAD}), a center position,
 * a wdth, a height (in meters) and a heading angle.</p>
 *
 * <p>Finally, the application can use an existing <code>Polyline</code> or <code>SurfaceShape</code> by using
 * {@link #setMeasureShape(Polyline)} or {@link #setMeasureShape(SurfaceShape)}. The surface shape can be one of
 * <code>SurfacePolygon</code>, <code>SurfaceQuad</code>, <code>SurfaceSquare</code>, <code>SurfaceEllipse</code> or
 * <code>SurfaceCircle</code>.
 *
 * <p><b>Measuring</b></p>
 *
 * <p>The application can read the measured length or area by calling the <code>MeasureTool</code> {@link #getLength()}
 * or {@link #getArea()} method. These methods will return -1 when no value is available.</p>
 *
 * <p>Regular shapes are defined by a center position, a width a height and a heading angle. Those attributes can be
 * accessed by calling the {@link #getCenterPosition()}, {@link #getWidth()}, {@link #getHeight()} and
 * {@link #getOrientation()} methods.</p>
 *
 * <p><b>Events</b></p>
 *
 * <p>The <code>MeasureTool</code> will send events on several occasions: when the position list has changed -
 * {@link #EVENT_POSITION_ADD}, {@link #EVENT_POSITION_REMOVE} or {@link #EVENT_POSITION_REPLACE}, when metrics
 * has changed {@link #EVENT_METRIC_CHANGED} or when the tool is armed or disarmed {@link #EVENT_ARMED}.</p>
 *
 * <p>Events will also be fired at the start and end of a rubber band operation during shape creation:
 * {@link #EVENT_RUBBERBAND_START} and {@link #EVENT_RUBBERBAND_STOP}.</p>
 *
 * <p>See {@link gov.nasa.worldwind.examples.MeasureToolPanel} for some events usage.</p>
 *
 * <p>Several instances of this class can be used simultaneously. However, each instance should be disposed of after
 * usage by calling the {@link #dispose()} method.</p>
 * 
 *
 * @author Patrick Murris
 * @version $Id: MeasureTool.java 10590 2009-04-28 17:12:31Z patrickmurris $
 * @see MeasureToolController
 */
public class MeasureTool extends AVListImpl implements Disposable
{

    public static final String SHAPE_LINE = "MeasureTool.ShapeLine";
    public static final String SHAPE_PATH = "MeasureTool.ShapePath";
    public static final String SHAPE_POLYGON = "MeasureTool.ShapePolygon";
    public static final String SHAPE_CIRCLE = "MeasureTool.ShapeCircle";
    public static final String SHAPE_ELLIPSE = "MeasureTool.ShapeEllipse";
    public static final String SHAPE_QUAD = "MeasureTool.ShapeQuad";
    public static final String SHAPE_SQUARE = "MeasureTool.ShapeSquare";

    public static final String EVENT_POSITION_ADD = "MeasureTool.AddPosition";
    public static final String EVENT_POSITION_REMOVE = "MeasureTool.RemovePosition";
    public static final String EVENT_POSITION_REPLACE = "MeasureTool.ReplacePosition";
    public static final String EVENT_METRIC_CHANGED = "MeasureTool.MetricChanged";
    public static final String EVENT_ARMED = "MeasureTool.Armed";
    public static final String EVENT_RUBBERBAND_START = "MeasureTool.RubberBandStart";
    public static final String EVENT_RUBBERBAND_STOP = "MeasureTool.RubberBandStop";

    protected final WorldWindow wwd;
    protected MeasureToolController controller;

    protected ArrayList<Position> positions = new ArrayList<Position>();
    protected ArrayList<Renderable> controlPoints = new ArrayList<Renderable>();
    protected RenderableLayer applicationLayer;
    protected CustomRenderableLayer layer;
    protected CustomRenderableLayer controlPointsLayer;
    protected CustomRenderableLayer shapeLayer;
    protected Polyline line;
    protected SurfaceShape surfaceShape;
    protected ScreenAnnotation annotation;

    protected Color lineColor = Color.YELLOW;
    protected Color fillColor = new Color(.6f, .6f, .4f, .5f);
    protected double lineWidth = 2;
    protected String pathType = AVKey.GREAT_CIRCLE;
    protected AnnotationAttributes controlPointsAttributes;
    protected AnnotationAttributes annotationAttributes;
    protected String measureShape = SHAPE_LINE;
    protected boolean followTerrain = false;
    protected boolean showControlPoints = true;
    protected boolean showAnnotation = true;

    // Rectangle enclosed regular shapes attributes
    protected Rectangle2D.Double shapeRectangle = null;
    protected Position shapeCenterPosition = null;
    protected Angle shapeOrientation = null;
    protected int shapeIntervals = 64;

    protected class CustomRenderableLayer extends RenderableLayer implements PreRenderable, Renderable
    {
        public void render(DrawContext dc)
        {
            if (dc.isPickingMode() && !this.isPickEnabled())
                return;
            if (!this.isEnabled())
                return;

            super.render(dc);
        }
    }


    /**
     * Construct a new measure tool drawing events from the specified <code>WorldWindow</code>.
     *
     * @param wwd the <code>WorldWindow</code> to draw events from.
     */
    public MeasureTool(final WorldWindow wwd)
    {
        this(wwd, null);
    }

    /**
     * Construct a new measure tool drawing events from the specified <code>WorldWindow</code> and using the given
     * <code>RenderableLayer</code>.
     *
     * @param wwd the <code>WorldWindow</code> to draw events from.
     * @param applicationLayer the <code>RenderableLayer</code> to use - can be null.
     */
    public MeasureTool(final WorldWindow wwd, RenderableLayer applicationLayer)
    {
        if (wwd == null)
        {
            String msg = Logging.getMessage("nullValue.WorldWindow");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
        this.wwd = wwd;
        this.applicationLayer = applicationLayer; // can be null

        // Set up layers

        this.layer = createCustomRenderableLayer();
        this.shapeLayer = createCustomRenderableLayer();
        this.controlPointsLayer = createCustomRenderableLayer();

        this.shapeLayer.setPickEnabled(false);
        this.layer.setName("Measure Tool");
        this.layer.addRenderable(this.shapeLayer);          // add shape layer to render layer
        this.layer.addRenderable(this.controlPointsLayer);  // add control points layer to render layer
        this.controlPointsLayer.setEnabled(this.showControlPoints);
        if (this.applicationLayer != null)
            this.applicationLayer.addRenderable(this.layer);    // add render layer to the application provided layer
        else
            this.wwd.getModel().getLayers().add(this.layer);    // add render layer to the globe model

        // Init control points rendering attributes
        this.controlPointsAttributes = new AnnotationAttributes();
        // Define an 8x8 square centered on the screen point
        this.controlPointsAttributes.setFrameShape(FrameFactory.SHAPE_RECTANGLE);
        this.controlPointsAttributes.setLeader(FrameFactory.LEADER_NONE);
        this.controlPointsAttributes.setAdjustWidthToText(Annotation.SIZE_FIXED);
        this.controlPointsAttributes.setSize(new Dimension(8, 8));
        this.controlPointsAttributes.setDrawOffset(new Point(0, -4));
        this.controlPointsAttributes.setInsets(new Insets(0, 0, 0, 0));
        this.controlPointsAttributes.setBorderWidth(0);
        this.controlPointsAttributes.setCornerRadius(0);
        this.controlPointsAttributes.setBackgroundColor(Color.BLUE);    // Normal color
        this.controlPointsAttributes.setTextColor(Color.GREEN);         // Highlighted color
        this.controlPointsAttributes.setHighlightScale(1.2);
        this.controlPointsAttributes.setDistanceMaxScale(1);            // No distance scaling
        this.controlPointsAttributes.setDistanceMinScale(1);
        this.controlPointsAttributes.setDistanceMinOpacity(1);

        // Annotation attributes
        this.annotationAttributes = new AnnotationAttributes();
        this.annotationAttributes.setFrameShape(FrameFactory.SHAPE_NONE);
        this.annotationAttributes.setInsets(new Insets(0, 0, 0, 0));
        this.annotationAttributes.setDrawOffset(new Point(0, 10));
        this.annotationAttributes.setTextAlign(MultiLineTextRenderer.ALIGN_CENTER);
        this.annotationAttributes.setEffect(MultiLineTextRenderer.EFFECT_OUTLINE);
        this.annotationAttributes.setFont(Font.decode("Arial-Bold-14"));
        this.annotationAttributes.setTextColor(Color.WHITE);
        this.annotationAttributes.setBackgroundColor(Color.BLACK);
        this.annotationAttributes.setSize(new Dimension(220, 0));
        this.annotation = new ScreenAnnotation("", new Point(0, 0), this.annotationAttributes);
        this.annotation.getAttributes().setVisible(false);
        this.annotation.getAttributes().setDrawOffset(null); // use defaults
        this.shapeLayer.addRenderable(this.annotation);
    }

    public WorldWindow getWwd()
    {
        return this.wwd;
    }

    /**
     * @return Instance of the custom renderable layer to use of our internal layers
     */
    protected CustomRenderableLayer createCustomRenderableLayer()
    {
        return new CustomRenderableLayer();
    }

    /**
     * Set the controller object for this measure tool - can be null.
     *
     * @param controller the controller object for this measure tool.
     */
    public void setController(MeasureToolController controller)
    {
        if (this.controller != null)
        {
            this.wwd.getInputHandler().removeMouseListener(this.controller);
            this.wwd.getInputHandler().removeMouseMotionListener(this.controller);
            this.wwd.removePositionListener(this.controller);
            this.wwd.removeSelectListener(this.controller);
            this.wwd.removeRenderingListener(this.controller);
            this.controller = null;
        }
        if (controller != null)
        {
            this.controller = controller;
            this.controller.setMeasureTool(this);
            this.wwd.getInputHandler().addMouseListener(this.controller);
            this.wwd.getInputHandler().addMouseMotionListener(this.controller);
            this.wwd.addPositionListener(this.controller);
            this.wwd.addSelectListener(this.controller);
            this.wwd.addRenderingListener(this.controller);
        }
    }

    /**
     * Get the <code>MeasureToolController</code> for this measure tool.
     * 
     * @return the <code>MeasureToolController</code> for this measure tool.
     */
    public MeasureToolController getController()
    {
        return this.controller;
    }

    /**
     * Arms and disarms the measure tool controller. When armed, the controller monitors user input and builds the
     * shape in response to user actions. When disarmed, the controller ignores all user input.
     *
     * @param state true to arm the controller, false to disarm it.
     */
    public void setArmed(boolean state)
    {
        if (this.controller != null)
            this.controller.setArmed(state);
    }

    /**
     * Identifies whether the measure tool controller is armed.
     *
     * @return true if armed, false if not armed.
     */
    public boolean isArmed()
    {
        return this.controller != null && this.controller.isArmed();
    }

    /**
     * Returns the measure tool layer.
     *
     * @return the layer containing the measure shape and control points.
     */
    public RenderableLayer getLayer()
    {
        return this.layer;
    }

    /**
     * Returns the polyline currently used to display lines and path.
     *
     * @return the polyline currently used to display lines and path.
     */
    public Polyline getLine()
    {
        return this.line;
    }

    /**
     * Returns the surface shape currently used to display polygons.
     *
     * @return the surface shape currently used to display polygons.
     */
    public SurfaceShape getSurfaceShape()
    {
        return this.surfaceShape;
    }

    /**
     * Get the list of positions that define the current measure shape.
     *
     * @return the list of positions that define the current measure shape.
     */
    public ArrayList<? extends Position> getPositions()
    {
        return this.positions;
    }

    /**
     * Set the measure shape to an arbitrary list of positions using {@link #setPositions(java.util.ArrayList)}.
     * If the provided list contains two positions, the measure shape will be set to {@link #SHAPE_LINE}. If more
     * then two positions are provided, the measure shape will be set to {@link #SHAPE_PATH} if the last position
     * differs from the first (open path), or {@link #SHAPE_POLYGON} if the path is closed.
     *
     * @param newPositions the shape position list.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?