Package com.bbn.openmap.layer.terrain

Source Code of com.bbn.openmap.layer.terrain.LOSGenerator

// **********************************************************************
//
// <copyright>
//
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
//
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/terrain/LOSGenerator.java,v $
// $RCSfile: LOSGenerator.java,v $
// $Revision: 1.3.2.3 $
// $Date: 2005/08/09 21:17:47 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.layer.terrain;

import java.awt.*;
import java.awt.event.*;

import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.MoreMath;
import com.bbn.openmap.event.*;
import com.bbn.openmap.gui.ProgressListenerGauge;
import com.bbn.openmap.dataAccess.dted.DTEDFrameCache;
import com.bbn.openmap.layer.util.stateMachine.*;
import com.bbn.openmap.omGraphics.*;
import com.bbn.openmap.proj.*;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.SwingWorker;

/**
* The LOSGenerator uses gestures to create a mask over the map. The
* circular mask of green pixels shows what places are within the
* sight of the center of the circle. Additional height can be added
* to the center of the circle via the TerrainLayer palette, to
* represent a tower, building, or aircraft.
*/
public class LOSGenerator implements TerrainTool {

    // These are used to control the algorithm type. Right now, the
    // first two are eliminated, since the azimuth alogorithm is
    // faster
    // and more precise.
    final static int PRECISE = 0;
    final static int GOODENOUGH = 1;
    final static int AZIMUTH = 2;

    // RED
    Color toolColor = new Color(255, 0, 0);

    // The colors of pixels
    final static int INVISIBLE = 0;
    final static int VISIBLE = 1;
    final static int MAYBEVISIBLE = 2;
    int[] colortable;

    Projection proj;
    protected LOSStateMachine stateMachine;
    TerrainLayer layer;

    /** Lat lon of the center of hte circle. */
    LatLonPoint LOScenterLLP;
    /** The xy of the center of the circle. */
    Point LOScenterP = new Point();
    /** The height of the earth at the center point. */
    int LOScenterHeight;
    /** The height of the object at the center point. */
    int LOSobjectHeight = 0;
    /** The diameter of the circle of interest. */
    int LOSedge;
    protected OMGraphicList graphics = new OMGraphicList();
    OMRaster LOSimage; // The image for the mask
    OMCircle LOScirc; // The circle modified for the image definition
    int LOSprecision; // The flag for the algorithm type
    LatLonPoint LOSOffPagell = new LatLonPoint(-79f, -170f);
    Point LOSOffPagep1 = new Point(-10, -10);

    /** The thread worker used to create the Terrain images. */
    LOSWorker currentWorker;
    /**
     * Set when the projection has changed while a swing worker is
     * gathering graphics, and we want him to stop early.
     */
    protected boolean cancelled = false;

    protected ProgressSupport progressSupport;

    class LOSWorker extends SwingWorker {
        /** Constructor used to create a worker thread. */
        public LOSWorker() {}

        /**
         * Compute the value to be returned by the <code>get</code>
         * method.
         */
        public Object construct() {
            Debug.message("terrain", layer.getName() + "|LOSWorker.construct()");
            layer.fireStatusUpdate(LayerStatusEvent.START_WORKING);
            createLOSImage();
            return null;
        }

        /**
         * Called on the event dispatching thread (not on the worker
         * thread) after the <code>construct</code> method has
         * returned.
         */
        public void finished() {
            layer.fireStatusUpdate(LayerStatusEvent.FINISH_WORKING);
            workerComplete();
        }
    }

    /**
     * Not the preferred way to create one of these. It's full of
     * defaults.
     */
    private LOSGenerator() {
        init();
    }

    /**
     * The creation of the tool starts here. The DTED data cache is
     * passed in, along with a path to the dted directory to get more
     * data if needed.
     */
    public LOSGenerator(TerrainLayer tLayer) {
        layer = tLayer;
        init();
    }

    public synchronized OMGraphicList getGraphics() {
        return graphics;
    }

    public State getState() {
        return stateMachine.getState();
    }

    public void init() {
        progressSupport = new ProgressSupport(this);
        addProgressListener(new ProgressListenerGauge("LOS Mask Creation"));

        // colortable
        colortable = new int[3];
        colortable[INVISIBLE] = new Color(0, 0, 0, 0).getRGB();
        colortable[VISIBLE] = new Color(0, 255, 0, 255).getRGB();
        colortable[MAYBEVISIBLE] = new Color(255, 255, 0, 255).getRGB();

        stateMachine = new LOSStateMachine(this);

        // set the graphics
        reset(true, true);
        graphics.add(LOSimage);
        graphics.add(LOScirc);

    }

    public void doImage() {
        // If there isn't a worker thread working on this already,
        // create a thread that will do the real work. If there is
        // a thread working on this, then set the cancelled flag
        // in the layer.
        if (currentWorker == null) {
            currentWorker = new LOSWorker();
            currentWorker.execute();
        } else
            setCancelled(true);
    }

    /**
     * The TerrainWorker calls this method on the layer when it is
     * done working. If the calling worker is not the same as the
     * "current" worker, then a new worker is created.
     */
    protected synchronized void workerComplete() {
        if (!isCancelled()) {
            currentWorker = null;
            layer.repaint();
        } else {
            setCancelled(false);
            currentWorker = new LOSWorker();
            currentWorker.execute();
        }
    }

    /**
     * Used to set the cancelled flag in the layer. The swing worker
     * checks this once in a while to see if the projection has
     * changed since it started working. If this is set to true, the
     * swing worker quits when it is safe.
     */
    public synchronized void setCancelled(boolean set) {
        cancelled = set;
    }

    /** Check to see if the cancelled flag has been set. */
    public synchronized boolean isCancelled() {
        return cancelled;
    }

    /**
     * Without arguments, the reset() call makes both graphics go
     * offscreen in their smallest size.
     */
    public void reset() {
        reset(true, true);
    }

    /**
     * Circ is for the circle to be reset, and image is for the image
     * to be reset. Sometimes you only want one to be moved.
     */
    public void reset(boolean circ, boolean image) {
        graphics.clear();
        if (image) {
            LOSimage = new OMRaster(LOSOffPagell.getLatitude(), LOSOffPagell.getLongitude(), LOSOffPagep1.x, LOSOffPagep1.y, 1, 1, new int[1]);
        }
        if (circ) {
            LOScirc = new OMCircle(LOSOffPagell.getLatitude(), LOSOffPagell.getLongitude(), 1, 1);
            LOScirc.setLinePaint(toolColor);
        }
        layer.repaint();
        stateMachine.reset();
    }

    /**
     * Called on every getRectangle, in order to let the cache get
     * sized right, and to reset the graphics if the scale changed
     * (since they won't make sense.
     */
    public void setScreenParameters(Projection p) {
        reset(true, true);
        proj = p;
        LOSprecision = AZIMUTH;
        graphics.generate(proj);
    }

    /**
     * Takes the member settings and manages the creation of the
     * image. A large vector of slope values are created, depending on
     * the size of the circle, and how many pixels are around it. Each
     * entry in the vector is the value of the largest slope value in
     * that direction. The image is created from the inside out, pixel
     * by pixel. The slope from the pixel to the center is calculated,
     * and then compared with the value for that direction (in the
     * vector). If the pixel's slope is larger, the point is visible,
     * and is colored that way. The vector is updated, and the cycle
     * continues.
     */
    public synchronized void createLOSImage() {
        if (Debug.debugging("los")) {
            Debug.output("createLOSimage: Entered with diameter = " + LOSedge);
        }

        if (layer == null || layer.frameCache == null) {
            Debug.error("LOSGenerator:  can't access the DTED data through the terrain layer.");
            return;
        }

        int squareRadius = LOSedge / 2 + 1;
        int[] newPixels = new int[LOSedge * LOSedge];
        float[] azimuthVals = new float[8 * (squareRadius - 1)];
        // center point of raster
        newPixels[((LOSedge / 2) * LOSedge) + squareRadius] = MAYBEVISIBLE;

        if (Debug.debugging("los")) {
            Debug.output("createLOSimage: size of azimuth array = "
                    + azimuthVals.length);
        }

        fireProgressUpdate(ProgressEvent.START,
                "Building LOS Image Mask...",
                0,
                100);
        int x, y;
        boolean mark = false;
        int markColor = colortable[INVISIBLE];
        int range;
        float pix_arc_interval = (float) (2 * Math.PI / azimuthVals.length);
        //  Do this in a spiral, around the center point.
        for (int round = 1; round < squareRadius; round++) {
            if (Debug.debugging("los")) {
                Debug.output("createLOSimage: round " + round);
            }
            y = LOScenterP.y - round;
            x = LOScenterP.x - round;

            if (round == 1) {
                mark = true;
                markColor = colortable[MAYBEVISIBLE];
            }

            else
                mark = false;

            if (LOSprecision == AZIMUTH) { // As of now, this is the
                // only option
                range = ((LOSedge * 4) - 4) / (round * 16);
                for (; x < LOScenterP.x + round; x++)
                    // top
                    resolveImagePoint(x,
                            y,
                            newPixels,
                            azimuthVals,
                            range,
                            pix_arc_interval,
                            mark,
                            markColor);
                for (; y < LOScenterP.y + round; y++)
                    // right
                    resolveImagePoint(x,
                            y,
                            newPixels,
                            azimuthVals,
                            range,
                            pix_arc_interval,
                            mark,
                            markColor);
                for (; x > LOScenterP.x - round; x--)
                    // bottom
                    resolveImagePoint(x,
                            y,
                            newPixels,
                            azimuthVals,
                            range,
                            pix_arc_interval,
                            mark,
                            markColor);
                for (; y > LOScenterP.y - round; y--)
                    // left
                    resolveImagePoint(x,
                            y,
                            newPixels,
                            azimuthVals,
                            range,
                            pix_arc_interval,
                            mark,
                            markColor);
            }

            int whereWeAre = (int) (100f * ((float) round / (float) squareRadius));
            fireProgressUpdate(ProgressEvent.UPDATE,
                    "Analyzing data...",
                    whereWeAre,
                    100);

        }

        fireProgressUpdate(ProgressEvent.UPDATE, "Creating Mask", 100, 100);

        LOSimage = new OMRaster(LOScenterLLP.getLatitude(), LOScenterLLP.getLongitude(), (-1 - LOSedge / 2), (-1 - LOSedge / 2), LOSedge, LOSedge, newPixels);
        LOSimage.generate(proj);
        graphics.clear();
        graphics.add(LOSimage);

        fireProgressUpdate(ProgressEvent.DONE, "LOS mask complete", 100, 100);

        if (Debug.debugging("los")) {
            Debug.output("createLOSimage: Done...");
        }
    }

    /**
     * Calculates the color for each pixel. After is gets the slope
     * value for that pixel, it manages the comparison to get the
     * pixel colored correctly.
     */
    protected void resolveImagePoint(int x, int y, int[] newPixels,
                                     float[] azimuthVals, int range,
                                     float pix_arc_interval, boolean mark,
                                     int colorForMark) {

        int ox = LOScenterP.x - LOSedge / 2;
        int oy = LOScenterP.y - LOSedge / 2;
        int dist = TerrainLayer.numPixelsBetween(LOScenterP.x,
                LOScenterP.y,
                x,
                y);
        if (dist > (LOSedge - 1) / 2) {
            mark = true;
            colorForMark = INVISIBLE;
        }
        if (dist == (LOSedge - 1) / 2) {
            mark = true;
            colorForMark = MAYBEVISIBLE;
        }

        // This needs to be before the next two lines after this
        LatLonPoint cord = proj.inverse(x, y);
        x -= ox;
        y -= oy;

        if (Debug.debugging("losdetail")) {
            Debug.output("resolveImagePoint x = " + x + ", y = " + y);
        }

        if (mark == true) {
            newPixels[x + y * LOSedge] = colorForMark;
            mark = false;
            return;
        }

        float centerRadLat = LOScenterLLP.radlat_;
        float centerRadLon = LOScenterLLP.radlon_;

        float arc_dist = GreatCircle.spherical_distance(centerRadLat,
                centerRadLon,
                cord.radlat_,
                cord.radlon_);

        float slope = (float) calculateLOSslope(cord, arc_dist);

        float arc_angle = GreatCircle.spherical_azimuth(centerRadLat,
                centerRadLon,
                cord.radlat_,
                cord.radlon_);
        int index = Math.round(arc_angle / pix_arc_interval);
        int maxIndex = (LOSedge * 4) - 4; // 4 corners out for
        // redundancy
        if (index < 0)
            index = maxIndex + index;
        else if (index >= maxIndex)
            index = index - maxIndex;

        if (Debug.debugging("losdetail")) {
            Debug.output(" angle = " + arc_angle + ", index/maxIndex = "
                    + index + "/" + maxIndex + ", slope = " + slope
                    + " compared to slope[index]=" + azimuthVals[index]);
        }
        int color = colortable[INVISIBLE];
        if (azimuthVals[index] < slope) {
            for (int i = (index - range); i < index + range - 1; i++) {
                if (i < 0)
                    azimuthVals[maxIndex + i] = slope;
                else if (i >= maxIndex)
                    azimuthVals[i - maxIndex] = slope;
                else
                    azimuthVals[i] = slope;
            }
            color = colortable[VISIBLE];
        }
        if (Debug.debugging("losdetail")) {
            Debug.output(" color = " + color);
        }
        newPixels[x + y * LOSedge] = color;
    }

    /**
     * CalculateLOSslope figures out the slope from the pixel to the
     * center, in radians. The arc_dist is in radians, and is the
     * radian arc distance of the point from the center point of the
     * image, on the earth. This slope calculation does take the
     * earth's curvature into account, based on the spherical model.
     */
    protected double calculateLOSslope(LatLonPoint cord, float arc_dist) {
        DTEDFrameCache frameCache = layer.frameCache;

        if (frameCache == null) {
            return 0;
        }

        int xyheight = frameCache.getElevation(cord.getLatitude(),
                cord.getLongitude());
        double ret = 0;
        double P = Math.sin(arc_dist)
                * (xyheight + Planet.wgs84_earthEquatorialRadiusMeters);

        double xPrime = Math.cos(arc_dist)
                * (xyheight + Planet.wgs84_earthEquatorialRadiusMeters);

        double bottom;
        double cutoff = LOScenterHeight
                + Planet.wgs84_earthEquatorialRadiusMeters;

        // Suggested changes, submitted by Mark Wigmore. Introduces
        // use of doubles, and avoidance of PI/2 tan() calculations.

        bottom = cutoff - xPrime;
        ret = MoreMath.HALF_PI_D - Math.atan(bottom / P);
        return ret;

        // Old way...
        //      if (xPrime < cutoff) {
        //          bottom = cutoff - xPrime;
        //          ret = Math.atan(P/bottom);

        //      } else if (xPrime == cutoff) {
        //          ret = MoreMath.HALF_PI_D;

        //      } else if (xPrime > cutoff) {
        //          double C = xPrime - cutoff;
        //          double gamma = Math.atan(P/C);
        //          ret = Math.PI - gamma;
        //      }

        //      return ret;
    }

    /**
     * Called when the circle is started. It starts the circle to be
     * drawn, and sets the parameters that will be needed to figure
     * out the image.
     *
     * @param event mouse event where the circle should be started.
     */
    public void setCenter(MouseEvent event) {
        graphics.clear();
        LOScenterP.x = event.getX();
        LOScenterP.y = event.getY();
        LOScenterLLP = proj.inverse(LOScenterP.x, LOScenterP.y);
        LOScenterHeight = LOSobjectHeight;
        if (layer.frameCache != null) {
            LOScenterHeight += layer.frameCache.getElevation(LOScenterLLP.getLatitude(),
                    LOScenterLLP.getLongitude());
        }
        LOScirc.setLatLon(LOScenterLLP.getLatitude(),
                LOScenterLLP.getLongitude());
        LOScirc.generate(proj);

        graphics.add(LOScirc);
    }

    /**
     * Used to modify the circle parameters with another mouse event.
     * Takes care of resetting hte circle parameters and regenerating
     * the circle.
     */
    public void addLOSEvent(MouseEvent event) {
        graphics.clear();
        LOSedge = TerrainLayer.numPixelsBetween(LOScenterP.x,
                LOScenterP.y,
                event.getX(),
                event.getY()) * 2 + 1;

        LOScirc.setWidth(LOSedge);
        LOScirc.setHeight(LOSedge);
        LOScirc.generate(proj);
        graphics.add(LOScirc);
    }

    /**
     * Sets the new object height to use at the center of the circle.
     * The old object is subtracted out first to get the center height
     * of the ground before the new value is added.
     *
     * @param value height of the object in meters.
     */
    public void setLOSobjectHeight(int value) {
        LOScenterHeight = LOScenterHeight - LOSobjectHeight;
        LOSobjectHeight = value;
        LOScenterHeight = LOScenterHeight + LOSobjectHeight;
    }

    /**
     * Add a ProgressListener that will display build progress.
     */
    public void addProgressListener(ProgressListener list) {
        progressSupport.addProgressListener(list);
    }

    /**
     * Remove a ProgressListener that displayed build progress.
     */
    public void removeProgressListener(ProgressListener list) {
        progressSupport.removeProgressListener(list);
    }

    /**
     * Clear all progress listeners.
     */
    public void clearProgressListeners() {
        progressSupport.removeAll();
    }

    /**
     * Fire an build update to progress listeners.
     *
     * @param frameNumber the current frame count
     * @param totalFrames the total number of frames.
     */
    protected void fireProgressUpdate(int type, String task, int frameNumber,
                                      int totalFrames) {
        progressSupport.fireUpdate(type, task, totalFrames, frameNumber);
    }

}
TOP

Related Classes of com.bbn.openmap.layer.terrain.LOSGenerator

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.