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

gov.nasa.worldwind.layers.Earth.UTMBaseGraticuleLayer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.layers.Earth;

import gov.nasa.worldwind.View;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.coords.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.*;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.view.orbit.OrbitView;

import java.awt.*;
import java.util.*;

/**
 * Displays the UTM graticule.
 *
 * @author Patrick Murris
 * @version $Id: UTMBaseGraticuleLayer.java 2153 2014-07-17 17:33:13Z tgaskins $
 */
public class UTMBaseGraticuleLayer extends AbstractGraticuleLayer
{
    public static final String GRATICULE_UTM = "Graticule.UTM";

    protected static final double ONEHT = 100e3;
    protected static final int UTM_MIN_LATITUDE = -80;
    protected static final int UTM_MAX_LATITUDE = 84;

    protected MetricScaleSupport metricScaleSupport = new MetricScaleSupport();
    protected long frameCount = 0;

    // Exceptions for some meridians. Values: longitude, min latitude, max latitude
    private static final int[][] specialMeridians = {{3, 56, 64}, {6, 64, 72}, {9, 72, 84}, {21, 72, 84}, {33, 72, 84}};
    // Latitude bands letters - from south to north
    private static final String latBands = "CDEFGHJKLMNPQRSTUVWX";

    public UTMBaseGraticuleLayer()
    {
        createUTMRenderables();
        initRenderingParams();
        this.setPickEnabled(false);
        this.setName(Logging.getMessage("layers.Earth.UTMGraticule.Name"));
    }

    /**
     * Returns whether or not graticule lines will be rendered.
     *
     * @return true if graticule lines will be rendered; false otherwise.
     */
    public boolean isDrawGraticule()
    {
        return getUTMRenderingParams().isDrawLines();
    }

    /**
     * Sets whether or not graticule lines will be rendered.
     *
     * @param drawGraticule true to render graticule lines; false to disable rendering.
     */
    public void setDrawGraticule(boolean drawGraticule)
    {
        getUTMRenderingParams().setDrawLines(drawGraticule);
    }

    /**
     * Returns the graticule line Color.
     *
     * @return Color used to render graticule lines.
     */
    public Color getGraticuleLineColor()
    {
        return getUTMRenderingParams().getLineColor();
    }

    /**
     * Sets the graticule line Color.
     *
     * @param color Color that will be used to render graticule lines.
     *
     * @throws IllegalArgumentException if color is null.
     */
    public void setGraticuleLineColor(Color color)
    {
        if (color == null)
        {
            String message = Logging.getMessage("nullValue.ColorIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        getUTMRenderingParams().setLineColor(color);
    }

    /**
     * Returns the graticule line width.
     *
     * @return width of the graticule lines.
     */
    public double getGraticuleLineWidth()
    {
        return getUTMRenderingParams().getLineWidth();
    }

    /**
     * Sets the graticule line width.
     *
     * @param lineWidth width of the graticule lines.
     */
    public void setGraticuleLineWidth(double lineWidth)
    {
        getUTMRenderingParams().setLineWidth(lineWidth);
    }

    /**
     * Returns the graticule line rendering style.
     *
     * @return rendering style of the graticule lines.
     */
    public String getGraticuleLineStyle()
    {
        return getUTMRenderingParams().getLineStyle();
    }

    /**
     * Sets the graticule line rendering style.
     *
     * @param lineStyle rendering style of the graticule lines. One of LINE_STYLE_PLAIN, LINE_STYLE_DASHED, or
     *                  LINE_STYLE_DOTTED.
     *
     * @throws IllegalArgumentException if lineStyle is null.
     */
    public void setGraticuleLineStyle(String lineStyle)
    {
        if (lineStyle == null)
        {
            String message = Logging.getMessage("nullValue.StringIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        getUTMRenderingParams().setLineStyle(lineStyle);
    }

    /**
     * Returns whether or not graticule labels will be rendered.
     *
     * @return true if graticule labels will be rendered; false otherwise.
     */
    public boolean isDrawLabels()
    {
        return getUTMRenderingParams().isDrawLabels();
    }

    /**
     * Sets whether or not graticule labels will be rendered.
     *
     * @param drawLabels true to render graticule labels; false to disable rendering.
     */
    public void setDrawLabels(boolean drawLabels)
    {
        getUTMRenderingParams().setDrawLabels(drawLabels);
    }

    /**
     * Returns the graticule label Color.
     *
     * @return Color used to render graticule labels.
     */
    public Color getLabelColor()
    {
        return getUTMRenderingParams().getLabelColor();
    }

    /**
     * Sets the graticule label Color.
     *
     * @param color Color that will be used to render graticule labels.
     *
     * @throws IllegalArgumentException if color is null.
     */
    public void setLabelColor(Color color)
    {
        if (color == null)
        {
            String message = Logging.getMessage("nullValue.ColorIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        getUTMRenderingParams().setLabelColor(color);
    }

    /**
     * Returns the Font used for graticule labels.
     *
     * @return Font used to render graticule labels.
     */
    public Font getLabelFont()
    {
        return getUTMRenderingParams().getLabelFont();
    }

    /**
     * Sets the Font used for graticule labels.
     *
     * @param font Font that will be used to render graticule labels.
     *
     * @throws IllegalArgumentException if font is null.
     */
    public void setLabelFont(Font font)
    {
        if (font == null)
        {
            String message = Logging.getMessage("nullValue.FontIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        getUTMRenderingParams().setLabelFont(font);
    }

    // --- Graticule Rendering --------------------------------------------------------------

    protected String getTypeFor(int resolution)
    {
        return GRATICULE_UTM;
    }

    protected void initRenderingParams()
    {
        GraticuleRenderingParams params = new GraticuleRenderingParams();
        params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(.8f, .8f, .8f, .5f));
        params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(1f, 1f, 1f, .8f));
        params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-14"));
        params.setValue(GraticuleRenderingParams.KEY_DRAW_LABELS, Boolean.TRUE);
        setRenderingParams(GRATICULE_UTM, params);
    }

    private GraticuleRenderingParams getUTMRenderingParams()
    {
        return this.graticuleSupport.getRenderingParams(GRATICULE_UTM);
    }

    /**
     * Select the visible grid elements
     *
     * @param dc the current DrawContext.
     */
    protected void selectRenderables(DrawContext dc)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        Sector vs = dc.getVisibleSector();
        OrbitView view = (OrbitView) dc.getView();
        // Compute labels offset from view center
        Position centerPos = view.getCenterPosition();
        Double pixelSizeDegrees = Angle.fromRadians(view.computePixelSizeAtDistance(view.getZoom())
            / dc.getGlobe().getEquatorialRadius()).degrees;
        Double labelOffsetDegrees = pixelSizeDegrees * view.getViewport().getWidth() / 4;
        Position labelPos = Position.fromDegrees(centerPos.getLatitude().degrees - labelOffsetDegrees,
            centerPos.getLongitude().degrees - labelOffsetDegrees, 0);
        Double labelLatDegrees = labelPos.getLatitude().normalizedLatitude().degrees;
        labelLatDegrees = Math.min(Math.max(labelLatDegrees, -76), 78);
        labelPos = new Position(Angle.fromDegrees(labelLatDegrees), labelPos.getLongitude().normalizedLongitude(), 0);

        if (vs != null)
        {
            for (GridElement ge : this.gridElements)
            {
                if (ge.isInView(dc))
                {
                    if (ge.renderable instanceof GeographicText)
                    {
                        GeographicText gt = (GeographicText) ge.renderable;
                        if (labelPos.getLatitude().degrees < 72 || "*32*34*36*".indexOf("*" + gt.getText() + "*") == -1)
                        {
                            // Adjust label position according to eye position
                            Position pos = gt.getPosition();
                            if (ge.type.equals(GridElement.TYPE_LATITUDE_LABEL))
                                pos = Position.fromDegrees(pos.getLatitude().degrees,
                                    labelPos.getLongitude().degrees, pos.getElevation());
                            else if (ge.type.equals(GridElement.TYPE_LONGITUDE_LABEL))
                                pos = Position.fromDegrees(labelPos.getLatitude().degrees,
                                    pos.getLongitude().degrees, pos.getElevation());

                            gt.setPosition(pos);
                        }
                    }

                    this.graticuleSupport.addRenderable(ge.renderable, GRATICULE_UTM);
                }
            }
            //System.out.println("Total elements: " + count + " visible sector: " + vs);
        }
    }

    /** Create the graticule grid elements */
    private void createUTMRenderables()
    {
        this.gridElements = new ArrayList();

        ArrayList positions = new ArrayList();

        // Generate meridians and zone labels
        int lon = -180;
        int zoneNumber = 1;
        int maxLat;
        for (int i = 0; i < 60; i++)
        {
            Angle longitude = Angle.fromDegrees(lon);
            // Meridian
            positions.clear();
            positions.add(new Position(Angle.fromDegrees(-80), longitude, 10e3));
            positions.add(new Position(Angle.fromDegrees(-60), longitude, 10e3));
            positions.add(new Position(Angle.fromDegrees(-30), longitude, 10e3));
            positions.add(new Position(Angle.ZERO, longitude, 10e3));
            positions.add(new Position(Angle.fromDegrees(30), longitude, 10e3));
            if (lon < 6 || lon > 36)
            {
                // 'regular' UTM meridians
                maxLat = 84;
                positions.add(new Position(Angle.fromDegrees(60), longitude, 10e3));
                positions.add(new Position(Angle.fromDegrees(maxLat), longitude, 10e3));
            }
            else
            {
                // Exceptions: shorter meridians around and north-east of Norway
                if (lon == 6)
                {
                    maxLat = 56;
                    positions.add(new Position(Angle.fromDegrees(maxLat), longitude, 10e3));
                }
                else
                {
                    maxLat = 72;
                    positions.add(new Position(Angle.fromDegrees(60), longitude, 10e3));
                    positions.add(new Position(Angle.fromDegrees(maxLat), longitude, 10e3));
                }
            }
            Object polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
            Sector sector = Sector.fromDegrees(-80, maxLat, lon, lon);
            this.gridElements.add(new GridElement(sector, polyline, GridElement.TYPE_LINE));

            // Zone label
            GeographicText text = new UserFacingText(zoneNumber + "",
                Position.fromDegrees(0, lon + 3, 0));
            sector = Sector.fromDegrees(-90, 90, lon + 3, lon + 3);
            this.gridElements.add(new GridElement(sector, text, GridElement.TYPE_LONGITUDE_LABEL));

            // Increase longitude and zone number
            lon += 6;
            zoneNumber++;
        }

        // Generate special meridian segments for exceptions around and north-east of Norway
        for (int i = 0; i < 5; i++)
        {
            positions.clear();
            lon = specialMeridians[i][0];
            positions.add(new Position(Angle.fromDegrees(specialMeridians[i][1]), Angle.fromDegrees(lon), 10e3));
            positions.add(new Position(Angle.fromDegrees(specialMeridians[i][2]), Angle.fromDegrees(lon), 10e3));
            Object polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
            Sector sector = Sector.fromDegrees(specialMeridians[i][1], specialMeridians[i][2], lon, lon);
            this.gridElements.add(new GridElement(sector, polyline, GridElement.TYPE_LINE));
        }

        // Generate parallels - no exceptions
        int lat = -80;
        for (int i = 0; i < 21; i++)
        {
            Angle latitude = Angle.fromDegrees(lat);
            for (int j = 0; j < 4; j++)
            {
                // Each prallel is divided into four 90 degrees segments
                positions.clear();
                lon = -180 + j * 90;
                positions.add(new Position(latitude, Angle.fromDegrees(lon), 10e3));
                positions.add(new Position(latitude, Angle.fromDegrees(lon + 30), 10e3));
                positions.add(new Position(latitude, Angle.fromDegrees(lon + 60), 10e3));
                positions.add(new Position(latitude, Angle.fromDegrees(lon + 90), 10e3));
                Object polyline = createLineRenderable(new ArrayList(positions), AVKey.LINEAR);
                Sector sector = Sector.fromDegrees(lat, lat, lon, lon + 90);
                this.gridElements.add(new GridElement(sector, polyline, GridElement.TYPE_LINE));
            }
            // Latitude band label
            if (i < 20)
            {
                GeographicText text = new UserFacingText(latBands.charAt(i) + "",
                    Position.fromDegrees(lat + 4, 0, 0));
                Sector sector = Sector.fromDegrees(lat + 4, lat + 4, -180, 180);
                this.gridElements.add(new GridElement(sector, text, GridElement.TYPE_LATITUDE_LABEL));
            }

            // Increase latitude
            lat += lat < 72 ? 8 : 12;
        }
    }

    //=== Support classes and methods ====================================================

    protected Position computePosition(int zone, String hemisphere, double easting, double northing)
    {
        return zone > 0 ?
            computePositionFromUTM(zone, hemisphere, easting, northing) :
            computePositionFromUPS(hemisphere, easting, northing);
    }

    protected Position computePositionFromUTM(int zone, String hemisphere, double easting, double northing)
    {
        try
        {
            UTMCoord UTM = UTMCoord.fromUTM(zone, hemisphere, easting, northing, globe);
            return new Position(Angle.fromRadiansLatitude(UTM.getLatitude().radians),
                Angle.fromRadiansLongitude(UTM.getLongitude().radians), 10e3);
        }
        catch (IllegalArgumentException e)
        {
            return null;
        }
    }

    protected Position computePositionFromUPS(String hemisphere, double easting, double northing)
    {
        try
        {
            UPSCoord UPS = UPSCoord.fromUPS(hemisphere, easting, northing, globe);
            return new Position(Angle.fromRadiansLatitude(UPS.getLatitude().radians),
                Angle.fromRadiansLongitude(UPS.getLongitude().radians), 10e3);
        }
        catch (IllegalArgumentException e)
        {
            return null;
        }
    }

    //--- Metric scale support -----------------------------------------------------------

    protected class MetricScaleSupport
    {
        private int zone;

        private double offsetFactorX = -.5;
        private double offsetFactorY = -.5;
        private double visibleDistanceFactor = 10;
        private int scaleModulo = (int) 10e6;
        private double maxResolution = 1e5;

        // 5 levels 100km to 10m
        UTMExtremes[] extremes;

        private class UTMExtremes
        {
            protected double minX, maxX, minY, maxY;
            protected String minYHemisphere, maxYHemisphere;

            public UTMExtremes()
            {
                this.clear();
            }

            public void clear()
            {
                minX = 1e6;
                maxX = 0;
                minY = 10e6;
                minYHemisphere = AVKey.NORTH;
                maxY = 0;
                maxYHemisphere = AVKey.SOUTH;
            }
        }

        public int getZone()
        {
            return this.zone;
        }

        public void setScaleModulo(int modulo)
        {
            this.scaleModulo = modulo;
        }

        public void setMaxResolution(double resolutionInMeter)
        {
            this.maxResolution = resolutionInMeter;
            this.clear();
        }

        public void computeZone(DrawContext dc)
        {
            try
            {
                Position centerPos = ((OrbitView) dc.getView()).getCenterPosition();
                if (centerPos != null)
                {
                    if (centerPos.latitude.degrees <= UTM_MAX_LATITUDE
                        && centerPos.latitude.degrees >= UTM_MIN_LATITUDE)
                    {
                        UTMCoord UTM = UTMCoord.fromLatLon(centerPos.getLatitude(), centerPos.getLongitude(),
                            dc.getGlobe());
                        this.zone = UTM.getZone();
                    }
                    else
                        this.zone = 0;
                }
            }
            catch (Exception ex)
            {
                this.zone = 0;
            }
        }

        public void clear()
        {
            int numLevels = (int) Math.log10(this.maxResolution);
            this.extremes = new UTMExtremes[numLevels];
            for (int i = 0; i < numLevels; i++)
            {
                this.extremes[i] = new UTMExtremes();
                this.extremes[i].clear();
            }
        }

        public void computeMetricScaleExtremes(int UTMZone, String hemisphere, GridElement ge, double size)
        {
            if (UTMZone != this.zone)
                return;
            if (size < 1 || size > this.maxResolution)
                return;

            UTMExtremes levelExtremes = this.extremes[(int) Math.log10(size) - 1];

            if (ge.type.equals(GridElement.TYPE_LINE_EASTING)
                || ge.type.equals(GridElement.TYPE_LINE_EAST)
                || ge.type.equals(GridElement.TYPE_LINE_WEST))
            {
                levelExtremes.minX = ge.value < levelExtremes.minX ? ge.value : levelExtremes.minX;
                levelExtremes.maxX = ge.value > levelExtremes.maxX ? ge.value : levelExtremes.maxX;
            }
            else if (ge.type.equals(GridElement.TYPE_LINE_NORTHING)
                || ge.type.equals(GridElement.TYPE_LINE_SOUTH)
                || ge.type.equals(GridElement.TYPE_LINE_NORTH))
            {
                if (hemisphere.equals(levelExtremes.minYHemisphere))
                    levelExtremes.minY = ge.value < levelExtremes.minY ? ge.value : levelExtremes.minY;
                else if (hemisphere.equals(AVKey.SOUTH))
                {
                    levelExtremes.minY = ge.value;
                    levelExtremes.minYHemisphere = hemisphere;
                }
                if (hemisphere.equals(levelExtremes.maxYHemisphere))
                    levelExtremes.maxY = ge.value > levelExtremes.maxY ? ge.value : levelExtremes.maxY;
                else if (hemisphere.equals(AVKey.NORTH))
                {
                    levelExtremes.maxY = ge.value;
                    levelExtremes.maxYHemisphere = hemisphere;
                }
            }
        }

        public void selectRenderables(DrawContext dc)
        {
            try
            {
                OrbitView view = (OrbitView) dc.getView();
                // Compute easting and northing label offsets
                Double pixelSize = view.computePixelSizeAtDistance(view.getZoom());
                Double eastingOffset = view.getViewport().width * pixelSize * offsetFactorX / 2;
                Double northingOffset = view.getViewport().height * pixelSize * offsetFactorY / 2;
                // Derive labels center pos from the view center
                Position centerPos = view.getCenterPosition();
                double labelEasting;
                double labelNorthing;
                String labelHemisphere;
                if (this.zone > 0)
                {
                    UTMCoord UTM = UTMCoord.fromLatLon(centerPos.getLatitude(), centerPos.getLongitude(),
                        dc.getGlobe());
                    labelEasting = UTM.getEasting() + eastingOffset;
                    labelNorthing = UTM.getNorthing() + northingOffset;
                    labelHemisphere = UTM.getHemisphere();
                    if (labelNorthing < 0)
                    {
                        labelNorthing = 10e6 + labelNorthing;
                        labelHemisphere = AVKey.SOUTH;
                    }
                }
                else
                {
                    UPSCoord UPS = UPSCoord.fromLatLon(centerPos.getLatitude(), centerPos.getLongitude(),
                        dc.getGlobe());
                    labelEasting = UPS.getEasting() + eastingOffset;
                    labelNorthing = UPS.getNorthing() + northingOffset;
                    labelHemisphere = UPS.getHemisphere();
                }

                Frustum viewFrustum = dc.getView().getFrustumInModelCoordinates();

                Position labelPos;
                for (int i = 0; i < this.extremes.length; i++)
                {
                    UTMExtremes levelExtremes = this.extremes[i];
                    double gridStep = Math.pow(10, i);
                    double gridStepTimesTen = gridStep * 10;
                    String graticuleType = getTypeFor((int) gridStep);
                    if (levelExtremes.minX <= levelExtremes.maxX)
                    {
                        // Process easting scale labels for this level
                        for (double easting = levelExtremes.minX; easting <= levelExtremes.maxX; easting += gridStep)
                        {
                            // Skip multiples of ten grid steps except for last (higher) level
                            if (i == this.extremes.length - 1 || easting % gridStepTimesTen != 0)
                            {
                                try
                                {
                                    labelPos = computePosition(this.zone, labelHemisphere, easting, labelNorthing);
                                    if (labelPos == null)
                                        continue;
                                    Angle lat = labelPos.getLatitude();
                                    Angle lon = labelPos.getLongitude();
                                    Vec4 surfacePoint = getSurfacePoint(dc, lat, lon);
                                    if (viewFrustum.contains(surfacePoint) && isPointInRange(dc, surfacePoint))
                                    {
                                        String text = String.valueOf((int) (easting % this.scaleModulo));
                                        GeographicText gt = new UserFacingText(text, new Position(lat, lon, 0));
                                        gt.setPriority(gridStepTimesTen);
                                        addRenderable(gt, graticuleType);
                                    }
                                }
                                catch (IllegalArgumentException ignore)
                                {
                                }
                            }
                        }
                    }
                    if (!(levelExtremes.maxYHemisphere.equals(AVKey.SOUTH) && levelExtremes.maxY == 0))
                    {
                        // Process northing scale labels for this level
                        String currentHemisphere = levelExtremes.minYHemisphere;
                        for (double northing = levelExtremes.minY; (northing <= levelExtremes.maxY)
                            || !currentHemisphere.equals(levelExtremes.maxYHemisphere); northing += gridStep)
                        {
                            // Skip multiples of ten grid steps except for last (higher) level
                            if (i == this.extremes.length - 1 || northing % gridStepTimesTen != 0)
                            {
                                try
                                {
                                    labelPos = computePosition(this.zone, currentHemisphere, labelEasting, northing);
                                    if (labelPos == null)
                                        continue;
                                    Angle lat = labelPos.getLatitude();
                                    Angle lon = labelPos.getLongitude();
                                    Vec4 surfacePoint = getSurfacePoint(dc, lat, lon);
                                    if (viewFrustum.contains(surfacePoint) && isPointInRange(dc, surfacePoint))
                                    {
                                        String text = String.valueOf((int) (northing % this.scaleModulo));
                                        GeographicText gt = new UserFacingText(text, new Position(lat, lon, 0));
                                        gt.setPriority(gridStepTimesTen);
                                        addRenderable(gt, graticuleType);
                                    }
                                }
                                catch (IllegalArgumentException ignore)
                                {
                                }

                                if (!currentHemisphere.equals(levelExtremes.maxYHemisphere)
                                    && northing >= 10e6 - gridStep)
                                {
                                    // Switch hemisphere
                                    currentHemisphere = levelExtremes.maxYHemisphere;
                                    northing = -gridStep;
                                }
                            }
                        }
                    } // end northing
                } // for levels
            }
            catch (IllegalArgumentException ignore)
            {
            }
        }

        private boolean isPointInRange(DrawContext dc, Vec4 point)
        {
            double altitudeAboveGround = computeAltitudeAboveGround(dc);
            return dc.getView().getEyePoint().distanceTo3(point)
                < altitudeAboveGround * this.visibleDistanceFactor;
        }

        public String toString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 5; i++)
            {
                sb.append("level ");
                sb.append(String.valueOf(i));
                sb.append(" : ");
                UTMExtremes levelExtremes = this.extremes[i];
                if (levelExtremes.minX < levelExtremes.maxX ||
                    !(levelExtremes.maxYHemisphere.equals(AVKey.SOUTH) && levelExtremes.maxY == 0))
                {
                    sb.append(levelExtremes.minX);
                    sb.append(", ");
                    sb.append(levelExtremes.maxX);
                    sb.append(" - ");
                    sb.append(levelExtremes.minY);
                    sb.append(AVKey.NORTH.equals(levelExtremes.minYHemisphere) ? "N" : "S");
                    sb.append(", ");
                    sb.append(levelExtremes.maxY);
                    sb.append(AVKey.NORTH.equals(levelExtremes.maxYHemisphere) ? "N" : "S");
                }
                else
                {
                    sb.append("empty");
                }
                sb.append("\n");
            }
            return sb.toString();
        }
    }

    // --- UTM/UPS square zone ------------------------------------------------------------------

    protected ArrayList createSquaresGrid(int UTMZone, String hemisphere, Sector UTMZoneSector,
        double minEasting, double maxEasting, double minNorthing, double maxNorthing)
    {
        ArrayList squares = new ArrayList();
        double startEasting = Math.floor(minEasting / ONEHT) * ONEHT;
        double startNorthing = Math.floor(minNorthing / ONEHT) * ONEHT;
        int cols = (int) Math.ceil((maxEasting - startEasting) / ONEHT);
        int rows = (int) Math.ceil((maxNorthing - startNorthing) / ONEHT);
        SquareZone[][] squaresArray = new SquareZone[rows][cols];
        int col = 0;
        for (double easting = startEasting; easting < maxEasting; easting += ONEHT)
        {
            int row = 0;
            for (double northing = startNorthing; northing < maxNorthing; northing += ONEHT)
            {
                SquareZone sz = new SquareZone(UTMZone, hemisphere, UTMZoneSector, easting, northing, ONEHT);
                if (sz.boundingSector != null && !sz.isOutsideGridZone())
                {
                    squares.add(sz);
                    squaresArray[row][col] = sz;
                }
                row++;
            }
            col++;
        }

        // Keep track of neighbors
        for (col = 0; col < cols; col++)
        {
            for (int row = 0; row < rows; row++)
            {
                SquareZone sz = squaresArray[row][col];
                if (sz != null)
                {
                    sz.setNorthNeighbor(row + 1 < rows ? squaresArray[row + 1][col] : null);
                    sz.setEastNeighbor(col + 1 < cols ? squaresArray[row][col + 1] : null);
                }
            }
        }

        return squares;
    }

    /** Represent a generic UTM/UPS square area */
    private class SquareSector
    {
        public static final int MIN_CELL_SIZE_PIXELS = 50;

        protected int UTMZone;
        protected String hemisphere;
        protected Sector UTMZoneSector;
        protected double SWEasting;
        protected double SWNorthing;
        protected double size;

        protected Position sw, se, nw, ne;  // Four corners position
        protected Sector boundingSector;
        protected LatLon centroid;
        protected LatLon squareCenter;
        protected boolean isTruncated = false;

        public SquareSector(int UTMZone, String hemisphere, Sector UTMZoneSector, double SWEasting,
            double SWNorthing, double size)
        {
            this.UTMZone = UTMZone;
            this.hemisphere = hemisphere;
            this.UTMZoneSector = UTMZoneSector;
            this.SWEasting = SWEasting;
            this.SWNorthing = SWNorthing;
            this.size = size;

            // Compute corners positions
            this.sw = computePosition(this.UTMZone, this.hemisphere, SWEasting, SWNorthing);
            this.se = computePosition(this.UTMZone, this.hemisphere, SWEasting + size, SWNorthing);
            this.nw = computePosition(this.UTMZone, this.hemisphere, SWEasting, SWNorthing + size);
            this.ne = computePosition(this.UTMZone, this.hemisphere, SWEasting + size, SWNorthing + size);
            this.squareCenter = computePosition(this.UTMZone, this.hemisphere, SWEasting + size / 2,
                SWNorthing + size / 2);

            // Compute approximate bounding sector and center point
            if (this.sw != null && this.se != null && this.nw != null && this.ne != null)
            {
                adjustDateLineCrossingPoints();
                this.boundingSector = Sector.boundingSector(Arrays.asList(sw, se, nw, ne));
                if (!isInsideGridZone())
                    this.boundingSector = this.UTMZoneSector.intersection(this.boundingSector);

                this.centroid = this.boundingSector != null ? this.boundingSector.getCentroid() : this.squareCenter;
                //this.squareCenter = this.boundingSector.getCentroid();
            }

            // Check whether this square is truncated by the grid zone boundary
            this.isTruncated = !isInsideGridZone();
        }

        private void adjustDateLineCrossingPoints()
        {
            ArrayList corners = new ArrayList(Arrays.asList(sw, se, nw, ne));
            if (!LatLon.locationsCrossDateLine(corners))
                return;

            double lonSign = 0;
            for (LatLon corner : corners)
            {
                if (Math.abs(corner.getLongitude().degrees) != 180)
                    lonSign = Math.signum(corner.getLongitude().degrees);
            }

            if (lonSign == 0)
                return;

            if (Math.abs(sw.getLongitude().degrees) == 180 && Math.signum(sw.getLongitude().degrees) != lonSign)
                sw = new Position(sw.getLatitude(), sw.getLongitude().multiply(-1), sw.getElevation());
            if (Math.abs(se.getLongitude().degrees) == 180 && Math.signum(se.getLongitude().degrees) != lonSign)
                se = new Position(se.getLatitude(), se.getLongitude().multiply(-1), se.getElevation());
            if (Math.abs(nw.getLongitude().degrees) == 180 && Math.signum(nw.getLongitude().degrees) != lonSign)
                nw = new Position(nw.getLatitude(), nw.getLongitude().multiply(-1), nw.getElevation());
            if (Math.abs(ne.getLongitude().degrees) == 180 && Math.signum(ne.getLongitude().degrees) != lonSign)
                ne = new Position(ne.getLatitude(), ne.getLongitude().multiply(-1), ne.getElevation());
        }

        public Extent getExtent(Globe globe, double ve)
        {
            return Sector.computeBoundingCylinder(globe, ve, this.boundingSector);
        }

        @SuppressWarnings({"RedundantIfStatement"})
        public boolean isInView(DrawContext dc)
        {
            if (!dc.getView().getFrustumInModelCoordinates().intersects(
                this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration())))
                return false;

            // Check apparent size
            if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS)
                return false;

            return true;
        }

        /**
         * Determines whether this square is fully inside its parent grid zone.
         *
         * @return true if this square is totaly inside its parent grid zone.
         */
        @SuppressWarnings({"RedundantIfStatement"})
        public boolean isInsideGridZone()
        {
            if (!this.isPositionInside(this.nw))
                return false;
            if (!this.isPositionInside(this.ne))
                return false;
            if (!this.isPositionInside(this.sw))
                return false;
            if (!this.isPositionInside(this.se))
                return false;
            return true;
        }

        /**
         * Determines whether this square is fully outside its parent grid zone.
         *
         * @return true if this square is totaly outside its parent grid zone.
         */
        @SuppressWarnings({"RedundantIfStatement"})
        public boolean isOutsideGridZone()
        {
            if (this.isPositionInside(this.nw))
                return false;
            if (this.isPositionInside(this.ne))
                return false;
            if (this.isPositionInside(this.sw))
                return false;
            if (this.isPositionInside(this.se))
                return false;
            return true;
        }

        public boolean isPositionInside(Position position)
        {
            return position != null && this.UTMZoneSector.contains(position);
        }

        public double getSizeInPixels(DrawContext dc)
        {
            View view = dc.getView();
            Vec4 centerPoint = getSurfacePoint(dc, this.centroid.getLatitude(), this.centroid.getLongitude());
            Double distance = view.getEyePoint().distanceTo3(centerPoint);
            return this.size / view.computePixelSizeAtDistance(distance);
        }
    }

    /** Represent a 100km square zone inside an UTM zone. */
    protected class SquareZone extends SquareSector
    {
        protected String name;
        protected SquareGrid squareGrid;
        protected ArrayList gridElements;

        private SquareZone northNeighbor, eastNeighbor;

        public SquareZone(int UTMZone, String hemisphere, Sector UTMZoneSector, double SWEasting,
            double SWNorthing, double size)
        {
            super(UTMZone, hemisphere, UTMZoneSector, SWEasting, SWNorthing, size);
        }

        public void setName(String name)
        {
            this.name = name;
        }

        public void setNorthNeighbor(SquareZone sz)
        {
            this.northNeighbor = sz;
        }

        public void setEastNeighbor(SquareZone sz)
        {
            this.eastNeighbor = sz;
        }

        public void selectRenderables(DrawContext dc, Sector vs)
        {
            // Select our renderables
            if (this.gridElements == null)
                createRenderables();

            boolean drawMetricLabels = getSizeInPixels(dc) > MIN_CELL_SIZE_PIXELS * 2;
            String graticuleType = getTypeFor((int) this.size);
            for (GridElement ge : this.gridElements)
            {
                if (ge.isInView(dc, vs))
                {
                    if (ge.type.equals(GridElement.TYPE_LINE_NORTH) && this.isNorthNeighborInView(dc))
                        continue;
                    if (ge.type.equals(GridElement.TYPE_LINE_EAST) && this.isEastNeighborInView(dc))
                        continue;

                    if (drawMetricLabels)
                        metricScaleSupport.computeMetricScaleExtremes(this.UTMZone, this.hemisphere, ge,
                            this.size * 10);
                    addRenderable(ge.renderable, graticuleType);
                }
            }

            if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS * 2)
                return;

            // Select grid renderables
            if (this.squareGrid == null)
                this.squareGrid = new SquareGrid(this.UTMZone, this.hemisphere, this.UTMZoneSector, this.SWEasting,
                    this.SWNorthing, this.size);
            if (this.squareGrid.isInView(dc))
            {
                this.squareGrid.selectRenderables(dc, vs);
            }
            else
                this.squareGrid.clearRenderables();
        }

        private boolean isNorthNeighborInView(DrawContext dc)
        {
            return this.northNeighbor != null && this.northNeighbor.isInView(dc);
        }

        private boolean isEastNeighborInView(DrawContext dc)
        {
            return this.eastNeighbor != null && this.eastNeighbor.isInView(dc);
        }

        public void clearRenderables()
        {
            if (this.gridElements != null)
            {
                this.gridElements.clear();
                this.gridElements = null;
            }
            if (this.squareGrid != null)
            {
                this.squareGrid.clearRenderables();
                this.squareGrid = null;
            }
        }

        public void createRenderables()
        {
            this.gridElements = new ArrayList();

            ArrayList positions = new ArrayList();
            Position p1, p2;
            Object polyline;
            Sector lineSector;

            // left segment
            positions.clear();
            if (this.isTruncated)
            {
                computeTruncatedSegment(sw, nw, this.UTMZoneSector, positions);
            }
            else
            {
                positions.add(sw);
                positions.add(nw);
            }
            if (positions.size() > 0)
            {
                p1 = positions.get(0);
                p2 = positions.get(1);
                polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
                lineSector = Sector.boundingSector(p1, p2);
                GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_WEST);
                ge.setValue(this.SWEasting);
                this.gridElements.add(ge);
            }

            // right segment
            positions.clear();
            if (this.isTruncated)
            {
                computeTruncatedSegment(se, ne, this.UTMZoneSector, positions);
            }
            else
            {
                positions.add(se);
                positions.add(ne);
            }
            if (positions.size() > 0)
            {
                p1 = positions.get(0);
                p2 = positions.get(1);
                polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
                lineSector = Sector.boundingSector(p1, p2);
                GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_EAST);
                ge.setValue(this.SWEasting + this.size);
                this.gridElements.add(ge);
            }

            // bottom segment
            positions.clear();
            if (this.isTruncated)
            {
                computeTruncatedSegment(sw, se, this.UTMZoneSector, positions);
            }
            else
            {
                positions.add(sw);
                positions.add(se);
            }
            if (positions.size() > 0)
            {
                p1 = positions.get(0);
                p2 = positions.get(1);
                polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
                lineSector = Sector.boundingSector(p1, p2);
                GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_SOUTH);
                ge.setValue(this.SWNorthing);
                this.gridElements.add(ge);
            }

            // top segment
            positions.clear();
            if (this.isTruncated)
            {
                computeTruncatedSegment(nw, ne, this.UTMZoneSector, positions);
            }
            else
            {
                positions.add(nw);
                positions.add(ne);
            }
            if (positions.size() > 0)
            {
                p1 = positions.get(0);
                p2 = positions.get(1);
                polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
                lineSector = Sector.boundingSector(p1, p2);
                GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_NORTH);
                ge.setValue(this.SWNorthing + this.size);
                this.gridElements.add(ge);
            }

            // Label
            if (this.name != null)
            {
                // Only add a label to squares above some dimension
                if (this.boundingSector.getDeltaLon().degrees * Math.cos(this.centroid.getLatitude().radians) > .2
                    && this.boundingSector.getDeltaLat().degrees > .2)
                {
                    LatLon labelPos = null;
                    if (this.UTMZone != 0) // Not at poles
                    {
                        labelPos = this.centroid;
                    }
                    else if (this.isPositionInside(new Position(this.squareCenter, 0)))
                    {
                        labelPos = this.squareCenter;
                    }
                    else if (this.squareCenter.getLatitude().degrees <= this.UTMZoneSector.getMaxLatitude().degrees
                        && this.squareCenter.getLatitude().degrees >= this.UTMZoneSector.getMinLatitude().degrees)
                    {
                        labelPos = this.centroid;
                    }
                    if (labelPos != null)
                    {
                        GeographicText text = new UserFacingText(this.name, new Position(labelPos, 0));
                        text.setPriority(this.size * 10);
                        this.gridElements.add(
                            new GridElement(this.boundingSector, text, GridElement.TYPE_GRIDZONE_LABEL));
                    }
                }
            }
        }
    }

    /** Represent a square 10x10 grid and recursive tree in easting/northing coordinates */
    protected class SquareGrid extends SquareSector
    {
        private ArrayList gridElements;
        private ArrayList subGrids;

        public SquareGrid(int UTMZone, String hemisphere, Sector UTMZoneSector, double SWEasting,
            double SWNorthing, double size)
        {
            super(UTMZone, hemisphere, UTMZoneSector, SWEasting, SWNorthing, size);
        }

        @SuppressWarnings({"RedundantIfStatement"})
        public boolean isInView(DrawContext dc)
        {
            if (!dc.getView().getFrustumInModelCoordinates().intersects(
                this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration())))
                return false;

            // Check apparent size
            if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS * 4)
                return false;

            return true;
        }

        public void selectRenderables(DrawContext dc, Sector vs)
        {
            // Select our renderables
            if (this.gridElements == null)
                createRenderables();

            int gridStep = (int) this.size / 10;
            boolean drawMetricLabels = getSizeInPixels(dc) > MIN_CELL_SIZE_PIXELS * 4 * 1.7;
            String graticuleType = getTypeFor(gridStep);

            for (GridElement ge : this.gridElements)
            {
                if (ge.isInView(dc, vs))
                {
                    if (drawMetricLabels)
                        metricScaleSupport.computeMetricScaleExtremes(this.UTMZone, this.hemisphere, ge, this.size);

                    addRenderable(ge.renderable, graticuleType);
                }
            }

            if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS * 4 * 2)
                return;

            // Select sub grids renderables
            if (this.subGrids == null)
                createSubGrids();
            for (SquareGrid sg : this.subGrids)
            {
                if (sg.isInView(dc))
                {
                    sg.selectRenderables(dc, vs);
                }
                else
                    sg.clearRenderables();
            }
        }

        public void clearRenderables()
        {
            if (this.gridElements != null)
            {
                this.gridElements.clear();
                this.gridElements = null;
            }
            if (this.subGrids != null)
            {
                for (SquareGrid sg : this.subGrids)
                {
                    sg.clearRenderables();
                }
                this.subGrids.clear();
                this.subGrids = null;
            }
        }

        public void createSubGrids()
        {
            this.subGrids = new ArrayList();
            double gridStep = this.size / 10;
            for (int i = 0; i < 10; i++)
            {
                double easting = this.SWEasting + gridStep * i;
                for (int j = 0; j < 10; j++)
                {
                    double northing = this.SWNorthing + gridStep * j;
                    SquareGrid sg = new SquareGrid(this.UTMZone, this.hemisphere, this.UTMZoneSector,
                        easting, northing, gridStep);
                    if (!sg.isOutsideGridZone())
                        this.subGrids.add(sg);
                }
            }
        }

        public void createRenderables()
        {
            this.gridElements = new ArrayList();
            double gridStep = this.size / 10;
            Position p1, p2;
            ArrayList positions = new ArrayList();

            // South-North lines
            for (int i = 1; i <= 9; i++)
            {
                double easting = this.SWEasting + gridStep * i;
                positions.clear();
                p1 = computePosition(this.UTMZone, this.hemisphere, easting, SWNorthing);
                p2 = computePosition(this.UTMZone, this.hemisphere, easting, SWNorthing + this.size);
                if (this.isTruncated)
                {
                    computeTruncatedSegment(p1, p2, this.UTMZoneSector, positions);
                }
                else
                {
                    positions.add(p1);
                    positions.add(p2);
                }
                if (positions.size() > 0)
                {
                    p1 = positions.get(0);
                    p2 = positions.get(1);
                    Object polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
                    Sector lineSector = Sector.boundingSector(p1, p2);
                    GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_EASTING);
                    ge.setValue(easting);
                    this.gridElements.add(ge);
                }
            }
            // West-East lines
            for (int i = 1; i <= 9; i++)
            {
                double northing = this.SWNorthing + gridStep * i;
                positions.clear();
                p1 = computePosition(this.UTMZone, this.hemisphere, SWEasting, northing);
                p2 = computePosition(this.UTMZone, this.hemisphere, SWEasting + this.size, northing);
                if (this.isTruncated)
                {
                    computeTruncatedSegment(p1, p2, this.UTMZoneSector, positions);
                }
                else
                {
                    positions.add(p1);
                    positions.add(p2);
                }
                if (positions.size() > 0)
                {
                    p1 = positions.get(0);
                    p2 = positions.get(1);
                    Object polyline = createLineRenderable(new ArrayList(positions), AVKey.GREAT_CIRCLE);
                    Sector lineSector = Sector.boundingSector(p1, p2);
                    GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_NORTHING);
                    ge.setValue(northing);
                    this.gridElements.add(ge);
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy