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

gov.nasa.worldwind.layers.placename.PlaceNameLayer 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.placename;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.event.BulkRetrievalListener;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe2D;
import gov.nasa.worldwind.layers.AbstractLayer;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.retrieve.*;
import gov.nasa.worldwind.util.*;

import java.io.*;
import java.nio.*;
import java.util.*;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.logging.Level;

/**
 * @author Paul Collins
 * @version $Id: PlaceNameLayer.java 2392 2014-10-20 20:02:44Z tgaskins $
 */
public class PlaceNameLayer extends AbstractLayer implements BulkRetrievable
{
    protected final PlaceNameServiceSet placeNameServiceSet;
    protected PriorityBlockingQueue requestQ = new PriorityBlockingQueue(64);
    protected Vec4 referencePoint;
    protected final Object fileLock = new Object();
    protected boolean cullNames = true; // this flag is no longer used. placenames participate in global decluttering

    public static final double LEVEL_A = 0x1 << 26; // 67,108 km
    public static final double LEVEL_B = 0x1 << 24; // 16,777 km
    public static final double LEVEL_C = 0x1 << 23; // 8,388 km
    public static final double LEVEL_D = 0x1 << 22; // 4,194 km
    public static final double LEVEL_E = 0x1 << 21; // 2,097 km
    public static final double LEVEL_F = 0x1 << 20; // 1,048 km
    public static final double LEVEL_G = 0x1 << 19; // 524 km
    public static final double LEVEL_H = 0x1 << 18; // 262 km
    public static final double LEVEL_I = 0x1 << 17; // 131 km
    public static final double LEVEL_J = 0x1 << 16; // 65 km
    public static final double LEVEL_K = 0x1 << 15; // 32 km
    public static final double LEVEL_L = 0x1 << 14; // 16 km
    public static final double LEVEL_M = 0x1 << 13; // 8 km
    public static final double LEVEL_N = 0x1 << 12; // 4 km
    public static final double LEVEL_O = 0x1 << 11; // 2 km
    public static final double LEVEL_P = 0x1 << 10; // 1 km
    public static final LatLon GRID_1x1 = new LatLon(Angle.fromDegrees(180d), Angle.fromDegrees(360d));
    public static final LatLon GRID_4x8 = new LatLon(Angle.fromDegrees(45d), Angle.fromDegrees(45d));
    public static final LatLon GRID_8x16 = new LatLon(Angle.fromDegrees(22.5d), Angle.fromDegrees(22.5d));
    public static final LatLon GRID_16x32 = new LatLon(Angle.fromDegrees(11.25d), Angle.fromDegrees(11.25d));
    public static final LatLon GRID_36x72 = new LatLon(Angle.fromDegrees(5d), Angle.fromDegrees(5d));
    public static final LatLon GRID_72x144 = new LatLon(Angle.fromDegrees(2.5d), Angle.fromDegrees(2.5d));
    public static final LatLon GRID_144x288 = new LatLon(Angle.fromDegrees(1.25d), Angle.fromDegrees(1.25d));
    public static final LatLon GRID_288x576 = new LatLon(Angle.fromDegrees(0.625d), Angle.fromDegrees(0.625d));
    public static final LatLon GRID_576x1152 = new LatLon(Angle.fromDegrees(0.3125d), Angle.fromDegrees(0.3125d));
    public static final LatLon GRID_1152x2304 = new LatLon(Angle.fromDegrees(0.1563d), Angle.fromDegrees(0.1563d));

    protected List navTiles = new ArrayList();
    //top navigation tiles for each service

    /**
     * @param placeNameServiceSet the set of PlaceNameService objects that PlaceNameLayer will render.
     *
     * @throws IllegalArgumentException if  {@link gov.nasa.worldwind.layers.placename.PlaceNameServiceSet} is null
     */
    public PlaceNameLayer(PlaceNameServiceSet placeNameServiceSet)
    {
        if (placeNameServiceSet == null)
        {
            String message = Logging.getMessage("nullValue.PlaceNameServiceSetIsNull");
            Logging.logger().fine(message);
            throw new IllegalArgumentException(message);
        }

        //  
        this.placeNameServiceSet = placeNameServiceSet.deepCopy();
        for (int i = 0; i < this.placeNameServiceSet.getServiceCount(); i++)
        {
            //todo do this for long as well and pick min
            int calc1 = (int) (PlaceNameService.TILING_SECTOR.getDeltaLatDegrees()
                / this.placeNameServiceSet.getService(i).getTileDelta().getLatitude().getDegrees());
            int numLevels = (int) Math.log(calc1);
            navTiles.add(
                new NavigationTile(this.placeNameServiceSet.getService(i), PlaceNameService.TILING_SECTOR, numLevels,
                    "top"));
        }

        if (!WorldWind.getMemoryCacheSet().containsCache(Tile.class.getName()))
        {
            long size = Configuration.getLongValue(AVKey.PLACENAME_LAYER_CACHE_SIZE, 2000000L);
            MemoryCache cache = new BasicMemoryCache((long) (0.85 * size), size);
            cache.setName("Placename Tiles");
            WorldWind.getMemoryCacheSet().addCache(Tile.class.getName(), cache);
        }
    }

    /**
     * @return not in use
     *
     * @deprecated This flag no longer has any effect. Placenames participate in global decluttering.
     */
    public boolean isCullNames()
    {
        return cullNames;
    }

    /**
     * @param cullNames not used
     *
     * @deprecated This flag no longer has any effect. Placenames participate in global decluttering.
     */
    public void setCullNames(boolean cullNames)
    {
        this.cullNames = cullNames;
    }

    public final PlaceNameServiceSet getPlaceNameServiceSet()
    {
        return this.placeNameServiceSet;
    }

    protected PriorityBlockingQueue getRequestQ()
    {
        return this.requestQ;
    }

    // ============== Tile Assembly ======================= //
    // ============== Tile Assembly ======================= //
    // ============== Tile Assembly ======================= //

    protected class NavigationTile
    {
        String id;
        protected PlaceNameService placeNameService;
        public Sector navSector;
        protected List subNavTiles = new ArrayList();
        protected List tileKeys = new ArrayList();
        protected int level;

        NavigationTile(PlaceNameService placeNameService, Sector sector, int levels, String id)
        {
            this.placeNameService = placeNameService;
            this.id = id;
            this.navSector = sector;
            level = levels;
        }

        protected void buildSubNavTiles()
        {
            if (level > 0)
            {
                //split sector, create a navTile for each quad
                Sector[] subSectors = this.navSector.subdivide();
                for (int j = 0; j < subSectors.length; j++)
                {
                    subNavTiles.add(new NavigationTile(placeNameService, subSectors[j], level - 1, this.id + "." + j));
                }
            }
        }

        public List navTilesVisible(DrawContext dc, double minDistSquared, double maxDistSquared)
        {
            ArrayList navList = new ArrayList();
            if (this.isNavSectorVisible(dc, minDistSquared, maxDistSquared))
            {
                if (this.level > 0 && !this.hasSubTiles())
                    this.buildSubNavTiles();

                if (this.hasSubTiles())
                {
                    for (NavigationTile nav : subNavTiles)
                    {
                        navList.addAll(nav.navTilesVisible(dc, minDistSquared, maxDistSquared));
                    }
                }
                else  //at bottom level navigation tile
                {
                    navList.add(this);
                }
            }

            return navList;
        }

        public boolean hasSubTiles()
        {
            return !subNavTiles.isEmpty();
        }

        protected boolean isNavSectorVisible(DrawContext dc, double minDistanceSquared, double maxDistanceSquared)
        {
            if (!navSector.intersects(dc.getVisibleSector()))
                return false;

            if (dc.is2DGlobe())
            {
                Sector limits = ((Globe2D)dc.getGlobe()).getProjection().getProjectionLimits();
                if (limits != null && !limits.intersectsInterior(navSector))
                    return false;
            }

            View view = dc.getView();
            Position eyePos = view.getEyePosition();
            if (eyePos == null)
                return false;

            //check for eyePos over globe
            if (Double.isNaN(eyePos.getLatitude().getDegrees()) || Double.isNaN(eyePos.getLongitude().getDegrees()))
                return false;

            double distSquared;
            if (dc.isContinuous2DGlobe())
            {
                // Just use the eye altitude since the majority of non-visible sectors are culled elsewhere.
                distSquared = eyePos.getAltitude() * eyePos.getAltitude();
            }
            else
            {
                Angle lat = clampAngle(eyePos.getLatitude(), navSector.getMinLatitude(),
                    navSector.getMaxLatitude());
                Angle lon = clampAngle(eyePos.getLongitude(), navSector.getMinLongitude(),
                    navSector.getMaxLongitude());
                Vec4 p = dc.getGlobe().computePointFromPosition(lat, lon, 0d);
                distSquared = dc.getView().getEyePoint().distanceToSquared3(p);
            }

            //noinspection RedundantIfStatement
            if (minDistanceSquared > distSquared || maxDistanceSquared < distSquared)
                return false;

            return true;
        }

        public List getTiles()
        {
            if (tileKeys.isEmpty())
            {
                Tile[] tiles = buildTiles(this.placeNameService, this);
                //load tileKeys
                for (Tile t : tiles)
                {
                    tileKeys.add(t.getFileCachePath());
                    WorldWind.getMemoryCache(Tile.class.getName()).add(t.getFileCachePath(), t);
                }
                return Arrays.asList(tiles);
            }
            else
            {
                List dataTiles = new ArrayList();
                for (String s : tileKeys)
                {
                    Tile t = (Tile) WorldWind.getMemoryCache(Tile.class.getName()).getObject(s);
                    if (t != null)
                    {
                        dataTiles.add(t);
                    }
                }
                return dataTiles;
            }
        }
    }

    protected static class Tile implements Cacheable
    {
        protected final PlaceNameService placeNameService;
        protected final Sector sector;
        protected final int row;
        protected final int column;
        private Integer hashInt = null;
        // Computed data.
        protected String fileCachePath = null;
        protected double extentVerticalExaggeration = Double.MIN_VALUE;
        protected double priority = Double.MAX_VALUE; // Default is minimum priority
        protected PlaceNameChunk dataChunk = null;

        Tile(PlaceNameService placeNameService, Sector sector, int row, int column)
        {
            this.placeNameService = placeNameService;
            this.sector = sector;
            this.row = row;
            this.column = column;
            this.fileCachePath = this.placeNameService.createFileCachePathFromTile(this.row, this.column);
            this.hashInt = this.computeHash();
        }

        public void setDataChunk(PlaceNameChunk chunk)
        {
            dataChunk = chunk;
        }

        public PlaceNameChunk getDataChunk()
        {
            return dataChunk;
        }

        public long getSizeInBytes()
        {

            long result = 32; //references

            result += this.getSector().getSizeInBytes();
            if (this.getFileCachePath() != null)
                result += this.getFileCachePath().length();

            if (dataChunk != null)
            {
                result += dataChunk.estimatedMemorySize;
            }

            return result;
        }

        static int computeRow(Angle delta, Angle latitude)
        {
            if (delta == null || latitude == null)
            {
                String msg = Logging.getMessage("nullValue.AngleIsNull");
                Logging.logger().severe(msg);
                throw new IllegalArgumentException(msg);
            }
            return (int) ((latitude.getDegrees() + 90d) / delta.getDegrees());
        }

        static int computeColumn(Angle delta, Angle longitude)
        {
            if (delta == null || longitude == null)
            {
                String msg = Logging.getMessage("nullValue.AngleIsNull");
                Logging.logger().severe(msg);
                throw new IllegalArgumentException(msg);
            }
            return (int) ((longitude.getDegrees() + 180d) / delta.getDegrees());
        }

        static Angle computeRowLatitude(int row, Angle delta)
        {
            if (delta == null)
            {
                String msg = Logging.getMessage("nullValue.AngleIsNull");
                Logging.logger().severe(msg);
                throw new IllegalArgumentException(msg);
            }
            return Angle.fromDegrees(-90d + delta.getDegrees() * row);
        }

        static Angle computeColumnLongitude(int column, Angle delta)
        {
            if (delta == null)
            {
                String msg = Logging.getMessage("nullValue.AngleIsNull");
                Logging.logger().severe(msg);
                throw new IllegalArgumentException(msg);
            }
            return Angle.fromDegrees(-180 + delta.getDegrees() * column);
        }

        public Integer getHashInt()
        {
            return hashInt;
        }

        int computeHash()
        {
            return this.getFileCachePath() != null ? this.getFileCachePath().hashCode() : 0;
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            final Tile tile = (Tile) o;

            return !(this.getFileCachePath() != null ? !this.getFileCachePath().equals(tile.getFileCachePath())
                : tile.getFileCachePath() != null);
        }

        public String getFileCachePath()
        {
            if (this.fileCachePath == null)
                this.fileCachePath = this.placeNameService.createFileCachePathFromTile(this.row, this.column);

            return this.fileCachePath;
        }

        public PlaceNameService getPlaceNameService()
        {
            return placeNameService;
        }

        public java.net.URL getRequestURL() throws java.net.MalformedURLException
        {
            return this.placeNameService.createServiceURLFromSector(this.sector);
        }

        public Sector getSector()
        {
            return sector;
        }

        public int hashCode()
        {
            return this.hashInt;
        }

        protected boolean isTileInMemoryWithData()
        {
            Tile t = (Tile) WorldWind.getMemoryCache(Tile.class.getName()).getObject(this.getFileCachePath());
            return !(t == null || t.getDataChunk() == null);
        }

        public double getPriority()
        {
            return priority;
        }

        public void setPriority(double priority)
        {
            this.priority = priority;
        }
    }

    protected Tile[] buildTiles(PlaceNameService placeNameService, NavigationTile navTile)
    {
        final Angle dLat = placeNameService.getTileDelta().getLatitude();
        final Angle dLon = placeNameService.getTileDelta().getLongitude();

        // Determine the row and column offset from the global tiling origin for the southwest tile corner
        int firstRow = Tile.computeRow(dLat, navTile.navSector.getMinLatitude());
        int firstCol = Tile.computeColumn(dLon, navTile.navSector.getMinLongitude());
        int lastRow = Tile.computeRow(dLat, navTile.navSector.getMaxLatitude().subtract(dLat));
        int lastCol = Tile.computeColumn(dLon, navTile.navSector.getMaxLongitude().subtract(dLon));

        int nLatTiles = lastRow - firstRow + 1;
        int nLonTiles = lastCol - firstCol + 1;

        Tile[] tiles = new Tile[nLatTiles * nLonTiles];

        Angle p1 = Tile.computeRowLatitude(firstRow, dLat);
        for (int row = 0; row <= lastRow - firstRow; row++)
        {
            Angle p2;
            p2 = p1.add(dLat);

            Angle t1 = Tile.computeColumnLongitude(firstCol, dLon);
            for (int col = 0; col <= lastCol - firstCol; col++)
            {
                Angle t2;
                t2 = t1.add(dLon);
                //Need offset row and column to correspond to total ro/col numbering
                tiles[col + row * nLonTiles] = new Tile(placeNameService, new Sector(p1, p2, t1, t2),
                    row + firstRow, col + firstCol);
                t1 = t2;
            }
            p1 = p2;
        }

        return tiles;
    }

    // ============== Place Name Data Structures ======================= //
    // ============== Place Name Data Structures ======================= //
    // ============== Place Name Data Structures ======================= //

    protected static class PlaceNameChunk implements Cacheable
    {
        protected final PlaceNameService placeNameService;
        protected final CharBuffer textArray;
        protected final int[] textIndexArray;
        protected final double[] latlonArray;
        protected final int numEntries;
        protected final long estimatedMemorySize;

        protected PlaceNameChunk(PlaceNameService service, CharBuffer text, int[] textIndices,
            double[] positions, int numEntries)
        {
            this.placeNameService = service;
            this.textArray = text;
            this.textIndexArray = textIndices;
            this.latlonArray = positions;
            this.numEntries = numEntries;
            this.estimatedMemorySize = this.computeEstimatedMemorySize();
        }

        protected long computeEstimatedMemorySize()
        {
            long result = 0;
            if (!textArray.isDirect())
                result += (Character.SIZE / 8) * textArray.capacity();
            result += (Integer.SIZE / 8) * textIndexArray.length;
            result += (Double.SIZE / 8) * latlonArray.length;
            return result;
        }

        protected Position getPosition(int index)
        {
            int latlonIndex = 2 * index;
            return Position.fromDegrees(latlonArray[latlonIndex], latlonArray[latlonIndex + 1], 0);
        }

        protected PlaceNameService getPlaceNameService()
        {
            return this.placeNameService;
        }

        protected CharSequence getText(int index)
        {
            int beginIndex = textIndexArray[index];
            int endIndex = (index + 1 < numEntries) ? textIndexArray[index + 1] : textArray.length();

            // Cast the textArray CharBuffer to a CharSequence before calling subSequence. Java 7 broke interface
            // compatibility on this method by changing the return type to a CharBuffer. Compiling
            // CharBuffer.subSequence on Java 7 results in a NoSuchMethodError on Java 6. We cast to a CharSequence in
            // order to ensure that this code works on both Java 6 and Java 7 when compiled with Java 7.
            return ((CharSequence) this.textArray).subSequence(beginIndex, endIndex);
        }

        public long getSizeInBytes()
        {
            return this.estimatedMemorySize;
        }

        protected Iterable makeIterable(DrawContext dc)
        {
            //get dispay dist for this service for use in label annealing
            double maxDisplayDistance = this.getPlaceNameService().getMaxDisplayDistance();
            ArrayList list = new ArrayList();
            for (int i = 0; i < this.numEntries; i++)
            {
                CharSequence str = getText(i);
                Position pos = getPosition(i);
                GeographicText text = new UserFacingText(str, pos);
                text.setFont(this.placeNameService.getFont());
                text.setColor(this.placeNameService.getColor());
                text.setBackgroundColor(this.placeNameService.getBackgroundColor());
                text.setVisible(isNameVisible(dc, this.placeNameService, pos));
                text.setPriority(maxDisplayDistance);
                list.add(text);
            }
            return list;
        }
    }

    // ============== Rendering ======================= //
    // ============== Rendering ======================= //
    // ============== Rendering ======================= //

    @Override
    protected void doRender(DrawContext dc)
    {
        this.referencePoint = this.computeReferencePoint(dc);

        int serviceCount = this.placeNameServiceSet.getServiceCount();
        for (int i = 0; i < serviceCount; i++)
        {
            PlaceNameService placeNameService = this.placeNameServiceSet.getService(i);
            if (!isServiceVisible(dc, placeNameService))
                continue;

            double minDistSquared = placeNameService.getMinDisplayDistance() * placeNameService.getMinDisplayDistance();
            double maxDistSquared = placeNameService.getMaxDisplayDistance() * placeNameService.getMaxDisplayDistance();

            if (isSectorVisible(dc, placeNameService.getMaskingSector(), minDistSquared, maxDistSquared))
            {
                ArrayList baseTiles = new ArrayList();
                NavigationTile navTile = this.navTiles.get(i);
                //drill down into tiles to find bottom level navTiles visible
                List list = navTile.navTilesVisible(dc, minDistSquared, maxDistSquared);
                for (NavigationTile nt : list)
                {
                    baseTiles.addAll(nt.getTiles());
                }

                for (Tile tile : baseTiles)
                {
                    try
                    {
                        drawOrRequestTile(dc, tile, minDistSquared, maxDistSquared);
                    }
                    catch (Exception e)
                    {
                        Logging.logger().log(Level.FINE,
                            Logging.getMessage("layers.PlaceNameLayer.ExceptionRenderingTile"),
                            e);
                    }
                }
            }
        }

        this.sendRequests();
        this.requestQ.clear();
    }

    protected Vec4 computeReferencePoint(DrawContext dc)
    {
        if (dc.getViewportCenterPosition() != null)
            return dc.getGlobe().computePointFromPosition(dc.getViewportCenterPosition());

        java.awt.geom.Rectangle2D viewport = dc.getView().getViewport();
        int x = (int) viewport.getWidth() / 2;
        for (int y = (int) (0.5 * viewport.getHeight()); y >= 0; y--)
        {
            Position pos = dc.getView().computePositionFromScreenPoint(x, y);
            if (pos == null)
                continue;

            return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d);
        }

        return null;
    }

    protected Vec4 getReferencePoint()
    {
        return this.referencePoint;
    }

    protected void drawOrRequestTile(DrawContext dc, Tile tile, double minDisplayDistanceSquared,
        double maxDisplayDistanceSquared)
    {
        if (!isTileVisible(dc, tile, minDisplayDistanceSquared, maxDisplayDistanceSquared))
            return;

        if (tile.isTileInMemoryWithData())
        {
            PlaceNameChunk placeNameChunk = tile.getDataChunk();
            if (placeNameChunk.numEntries > 0)
            {
                Iterable renderIter = placeNameChunk.makeIterable(dc);
                dc.getDeclutteringTextRenderer().render(dc, renderIter);
            }
            return;
        }

        // Tile's data isn't available, so request it
        if (!tile.getPlaceNameService().isResourceAbsent(tile.getPlaceNameService().getTileNumber(
            tile.row, tile.column)))
        {
            this.requestTile(dc, tile);
        }
    }

    protected static boolean isServiceVisible(DrawContext dc, PlaceNameService placeNameService)
    {
        //noinspection SimplifiableIfStatement
        if (!placeNameService.isEnabled())
            return false;

        return (dc.getVisibleSector() != null) && placeNameService.getMaskingSector().intersects(dc.getVisibleSector());
//
//        return placeNameService.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates());
    }

    protected static boolean isSectorVisible(DrawContext dc, Sector sector, double minDistanceSquared,
        double maxDistanceSquared)
    {

        View view = dc.getView();
        Position eyePos = view.getEyePosition();
        if (eyePos == null)
            return false;

        double distSquared;
        if (dc.isContinuous2DGlobe())
        {
            // Just use the eye altitude since the majority of non-visible sectors are culled elsewhere.
            distSquared = eyePos.getAltitude() * eyePos.getAltitude();
        }
        else
        {
            Angle lat = clampAngle(eyePos.getLatitude(), sector.getMinLatitude(),
                sector.getMaxLatitude());
            Angle lon = clampAngle(eyePos.getLongitude(), sector.getMinLongitude(),
                sector.getMaxLongitude());
            Vec4 p = dc.getGlobe().computePointFromPosition(lat, lon, 0d);
            distSquared = dc.getView().getEyePoint().distanceToSquared3(p);
        }

        //noinspection RedundantIfStatement
        if (minDistanceSquared > distSquared || maxDistanceSquared < distSquared)
            return false;

        return true;
    }

    protected static boolean isTileVisible(DrawContext dc, Tile tile, double minDistanceSquared,
        double maxDistanceSquared)
    {
        if (!tile.getSector().intersects(dc.getVisibleSector()))
            return false;

        View view = dc.getView();
        Position eyePos = view.getEyePosition();
        if (eyePos == null)
            return false;

        double distSquared;
        if (dc.isContinuous2DGlobe())
        {
            // Just use the eye altitude since the majority of non-visible sectors are culled elsewhere.
            distSquared = eyePos.getAltitude() * eyePos.getAltitude();
        }
        else
        {
            Angle lat = clampAngle(eyePos.getLatitude(), tile.getSector().getMinLatitude(),
                tile.getSector().getMaxLatitude());
            Angle lon = clampAngle(eyePos.getLongitude(), tile.getSector().getMinLongitude(),
                tile.getSector().getMaxLongitude());
            Vec4 p = dc.getGlobe().computePointFromPosition(lat, lon, 0d);
            distSquared = dc.getView().getEyePoint().distanceToSquared3(p);
        }

        //noinspection RedundantIfStatement
        if (minDistanceSquared > distSquared || maxDistanceSquared < distSquared)
            return false;

        return true;
    }

    protected static boolean isNameVisible(DrawContext dc, PlaceNameService service, Position namePosition)
    {
        double elevation = dc.getVerticalExaggeration() * namePosition.getElevation();
        Vec4 namePoint = dc.getGlobe().computePointFromPosition(namePosition.getLatitude(),
            namePosition.getLongitude(), elevation);
        Vec4 eyeVec = dc.getView().getEyePoint();

        double dist = eyeVec.distanceTo3(namePoint);
        return dist >= service.getMinDisplayDistance() && dist <= service.getMaxDisplayDistance();
    }

    protected static Angle clampAngle(Angle a, Angle min, Angle max)
    {
        double degrees = a.degrees;
        double minDegrees = min.degrees;
        double maxDegrees = max.degrees;
        return Angle.fromDegrees(degrees < minDegrees ? minDegrees : (degrees > maxDegrees ? maxDegrees : degrees));
    }

    // ============== Image Reading and Downloading ======================= //
    // ============== Image Reading and Downloading ======================= //
    // ============== Image Reading and Downloading ======================= //

    protected void requestTile(DrawContext dc, Tile tile)
    {
        Vec4 centroid = dc.getGlobe().computePointFromPosition(tile.getSector().getCentroid(), 0);
        if (this.getReferencePoint() != null)
            tile.setPriority(centroid.distanceTo3(this.getReferencePoint()));

        RequestTask task = new RequestTask(tile, this);
        this.getRequestQ().add(task);
    }

    protected void sendRequests()
    {
        Runnable task = this.requestQ.poll();
        while (task != null)
        {
            if (!WorldWind.getTaskService().isFull())
            {
                WorldWind.getTaskService().addTask(task);
            }
            task = this.requestQ.poll();
        }
    }

    protected static class RequestTask implements Runnable, Comparable
    {
        protected final PlaceNameLayer layer;
        protected final Tile tile;

        RequestTask(Tile tile, PlaceNameLayer layer)
        {
            this.layer = layer;
            this.tile = tile;
        }

        public void run()
        {
            if (Thread.currentThread().isInterrupted())
                return;

            if (this.tile.isTileInMemoryWithData())
                return;

            final java.net.URL tileURL = this.layer.getDataFileStore().findFile(tile.getFileCachePath(), false);
            if (tileURL != null)
            {
                if (this.layer.loadTile(this.tile, tileURL))
                {
                    tile.getPlaceNameService().unmarkResourceAbsent(tile.getPlaceNameService().getTileNumber(
                        tile.row,
                        tile.column));
                    this.layer.firePropertyChange(AVKey.LAYER, null, this);
                    return;
                }
            }

            this.layer.downloadTile(this.tile);
        }

        /**
         * @param that the task to compare
         *
         * @return -1 if this less than that, 1 if greater than, 0 if equal
         *
         * @throws IllegalArgumentException if that is null
         */
        public int compareTo(RequestTask that)
        {
            if (that == null)
            {
                String msg = Logging.getMessage("nullValue.RequestTaskIsNull");
                Logging.logger().severe(msg);
                throw new IllegalArgumentException(msg);
            }
            return this.tile.getPriority() == that.tile.getPriority() ? 0 :
                this.tile.getPriority() < that.tile.getPriority() ? -1 : 1;
        }

        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            final RequestTask that = (RequestTask) o;

            // Don't include layer in comparison so that requests are shared among layers
            return !(tile != null ? !tile.equals(that.tile) : that.tile != null);
        }

        public int hashCode()
        {
            return (tile != null ? tile.hashCode() : 0);
        }

        public String toString()
        {
            return this.tile.toString();
        }
    }

    protected boolean loadTile(Tile tile, java.net.URL url)
    {
        if (WWIO.isFileOutOfDate(url, this.placeNameServiceSet.getExpiryTime()))
        {
            // The file has expired. Delete it then request download of newer.
            this.getDataFileStore().removeFile(url);
            String message = Logging.getMessage("generic.DataFileExpired", url);
            Logging.logger().fine(message);
            return false;
        }

        PlaceNameChunk tileData;
        synchronized (this.fileLock)
        {
            tileData = readTileData(tile, url);
        }

        if (tileData == null)
        {
            // Assume that something's wrong with the file and delete it.
            this.getDataFileStore().removeFile(url);
            tile.getPlaceNameService().markResourceAbsent(tile.getPlaceNameService().getTileNumber(tile.row,
                tile.column));
            String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
            Logging.logger().fine(message);
            return false;
        }

        tile.setDataChunk(tileData);
        WorldWind.getMemoryCache(Tile.class.getName()).add(tile.getFileCachePath(), tile);
        return true;
    }

    protected static PlaceNameChunk readTileData(Tile tile, java.net.URL url)
    {
        java.io.InputStream is = null;

        try
        {
            String path = url.getFile();
            path = path.replaceAll("%20", " "); // TODO: find a better way to get a path usable by FileInputStream

            java.io.FileInputStream fis = new java.io.FileInputStream(path);
            java.io.BufferedInputStream buf = new java.io.BufferedInputStream(fis);
            is = new java.util.zip.GZIPInputStream(buf);

            GMLPlaceNameSAXHandler handler = new GMLPlaceNameSAXHandler();
            javax.xml.parsers.SAXParserFactory.newInstance().newSAXParser().parse(is, handler);
            return handler.createPlaceNameChunk(tile.getPlaceNameService());
        }
        catch (Exception e)
        {
            //todo log actual error 
            Logging.logger().log(Level.FINE,
                Logging.getMessage("layers.PlaceNameLayer.ExceptionAttemptingToReadFile", url.toString()), e);
        }
        finally
        {
            try
            {
                if (is != null)
                    is.close();
            }
            catch (java.io.IOException e)
            {
                Logging.logger().log(Level.FINE,
                    Logging.getMessage("layers.PlaceNameLayer.ExceptionAttemptingToReadFile", url.toString()), e);
            }
        }

        return null;
    }

    protected static CharBuffer newCharBuffer(int numElements)
    {
        ByteBuffer bb = ByteBuffer.allocateDirect((Character.SIZE / 8) * numElements);
        bb.order(ByteOrder.nativeOrder());
        return bb.asCharBuffer();
    }

    protected static class GMLPlaceNameSAXHandler extends org.xml.sax.helpers.DefaultHandler
    {
        protected static final String GML_FEATURE_MEMBER = "gml:featureMember";
        protected static final String TOPP_FULL_NAME_ND = "topp:full_name_nd";
        protected static final String TOPP_LATITUDE = "topp:latitude";
        protected static final String TOPP_LONGITUDE = "topp:longitude";
        protected final LinkedList internedQNameStack = new LinkedList();
        protected boolean inBeginEndPair = false;
        protected StringBuilder latBuffer = new StringBuilder();
        protected StringBuilder lonBuffer = new StringBuilder();

        StringBuilder textArray = new StringBuilder();
        int[] textIndexArray = new int[16];
        double[] latlonArray = new double[16];
        int numEntries = 0;

        protected GMLPlaceNameSAXHandler()
        {
        }

        protected PlaceNameChunk createPlaceNameChunk(PlaceNameService service)
        {
            int numChars = this.textArray.length();
            CharBuffer textBuffer = newCharBuffer(numChars);
            textBuffer.put(this.textArray.toString());
            textBuffer.rewind();
            return new PlaceNameChunk(service, textBuffer, this.textIndexArray, this.latlonArray, this.numEntries);
        }

        protected void beginEntry()
        {
            int textIndex = this.textArray.length();
            this.textIndexArray = append(this.textIndexArray, this.numEntries, textIndex);
            this.inBeginEndPair = true;
        }

        protected void endEntry()
        {
            double lat = this.parseDouble(this.latBuffer);
            double lon = this.parseDouble(this.lonBuffer);
            int numLatLon = 2 * this.numEntries;
            this.latlonArray = this.append(this.latlonArray, numLatLon, lat);
            numLatLon++;
            this.latlonArray = this.append(this.latlonArray, numLatLon, lon);

            this.latBuffer.delete(0, this.latBuffer.length());
            this.lonBuffer.delete(0, this.lonBuffer.length());
            this.inBeginEndPair = false;
            this.numEntries++;
        }

        protected double parseDouble(StringBuilder sb)
        {
            double value = 0;
            try
            {
                value = Double.parseDouble(sb.toString());
            }
            catch (NumberFormatException e)
            {
                Logging.logger().log(Level.FINE,
                    Logging.getMessage("layers.PlaceNameLayer.ExceptionAttemptingToReadFile", ""), e);
            }
            return value;
        }

        protected int[] append(int[] array, int index, int value)
        {
            if (index >= array.length)
                array = this.resizeArray(array);
            array[index] = value;
            return array;
        }

        protected int[] resizeArray(int[] oldArray)
        {
            int newSize = 2 * oldArray.length;
            int[] newArray = new int[newSize];
            System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            return newArray;
        }

        protected double[] append(double[] array, int index, double value)
        {
            if (index >= array.length)
                array = this.resizeArray(array);
            array[index] = value;
            return array;
        }

        protected double[] resizeArray(double[] oldArray)
        {
            int newSize = 2 * oldArray.length;
            double[] newArray = new double[newSize];
            System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            return newArray;
        }

        @SuppressWarnings({"StringEquality"})
        public void characters(char ch[], int start, int length)
        {
            if (!this.inBeginEndPair)
                return;

            // Top of QName stack is an interned string,
            // so we can use pointer comparison.
            String internedTopQName = this.internedQNameStack.getFirst();

            StringBuilder sb = null;
            if (TOPP_LATITUDE == internedTopQName)
                sb = this.latBuffer;
            else if (TOPP_LONGITUDE == internedTopQName)
                sb = this.lonBuffer;
            else if (TOPP_FULL_NAME_ND == internedTopQName)
                sb = this.textArray;

            if (sb != null)
                sb.append(ch, start, length);
        }

        public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes)
        {
            // Don't validate uri, localName or attributes because they aren't used.
            // Intern the qName string so we can use pointer comparison.
            String internedQName = qName.intern();
            //noinspection StringEquality
            if (GML_FEATURE_MEMBER == internedQName)
                this.beginEntry();
            this.internedQNameStack.addFirst(internedQName);
        }

        public void endElement(String uri, String localName, String qName)
        {
            // Don't validate uri or localName because they aren't used.
            // Intern the qName string so we can use pointer comparison.
            String internedQName = qName.intern();
            //noinspection StringEquality
            if (GML_FEATURE_MEMBER == internedQName)
                this.endEntry();
            this.internedQNameStack.removeFirst();
        }
    }

    protected void downloadTile(final Tile tile)
    {
        downloadTile(tile, null);
    }

    protected void downloadTile(final Tile tile, DownloadPostProcessor postProcessor)
    {
        if (!this.isNetworkRetrievalEnabled())
            return;

        if (!WorldWind.getRetrievalService().isAvailable())
            return;

        java.net.URL url;
        try
        {
            url = tile.getRequestURL();
            if (WorldWind.getNetworkStatus().isHostUnavailable(url))
                return;
        }
        catch (java.net.MalformedURLException e)
        {
            Logging.logger().log(java.util.logging.Level.SEVERE,
                Logging.getMessage("layers.PlaceNameLayer.ExceptionCreatingUrl", tile), e);
            return;
        }

        Retriever retriever;

        if ("http".equalsIgnoreCase(url.getProtocol()) || "https".equalsIgnoreCase(url.getProtocol()))
        {
            if (postProcessor == null)
                postProcessor = new DownloadPostProcessor(this, tile);
            retriever = new HTTPRetriever(url, postProcessor);
            retriever.setValue(URLRetriever.EXTRACT_ZIP_ENTRY, "true"); // supports legacy layers
        }
        else
        {
            Logging.logger().severe(
                Logging.getMessage("layers.PlaceNameLayer.UnknownRetrievalProtocol", url.toString()));
            return;
        }

        // Apply any overridden timeouts.
        Integer cto = AVListImpl.getIntegerValue(this, AVKey.URL_CONNECT_TIMEOUT);
        if (cto != null && cto > 0)
            retriever.setConnectTimeout(cto);
        Integer cro = AVListImpl.getIntegerValue(this, AVKey.URL_READ_TIMEOUT);
        if (cro != null && cro > 0)
            retriever.setReadTimeout(cro);
        Integer srl = AVListImpl.getIntegerValue(this, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT);
        if (srl != null && srl > 0)
            retriever.setStaleRequestLimit(srl);

        WorldWind.getRetrievalService().runRetriever(retriever, tile.getPriority());
    }

    protected void saveBuffer(java.nio.ByteBuffer buffer, java.io.File outFile) throws java.io.IOException
    {
        synchronized (this.fileLock) // sychronized with read of file in RequestTask.run()
        {
            WWIO.saveBuffer(buffer, outFile);
        }
    }

    protected static class DownloadPostProcessor extends AbstractRetrievalPostProcessor
    {
        protected final PlaceNameLayer layer;
        protected final Tile tile;
        protected final FileStore fileStore;

        public DownloadPostProcessor(PlaceNameLayer layer, Tile tile)
        {
            // No arg check; the class has protected access.
            this(layer, tile, null);
        }

        public DownloadPostProcessor(PlaceNameLayer layer, Tile tile, FileStore fileStore)
        {
            // No arg check; the class has protected access.

            //noinspection RedundantCast
            super((AVList) layer);

            this.layer = layer;
            this.tile = tile;
            this.fileStore = fileStore;
        }

        protected FileStore getFileStore()
        {
            return this.fileStore != null ? this.fileStore : this.layer.getDataFileStore();
        }

        @Override
        protected void markResourceAbsent()
        {
            this.tile.getPlaceNameService().markResourceAbsent(
                this.tile.getPlaceNameService().getTileNumber(this.tile.row, this.tile.column));
        }

        @Override
        protected Object getFileLock()
        {
            return this.layer.fileLock;
        }

        protected File doGetOutputFile()
        {
            return this.getFileStore().newFile(this.tile.getFileCachePath());
        }

        @Override
        protected ByteBuffer handleSuccessfulRetrieval()
        {
            ByteBuffer buffer = super.handleSuccessfulRetrieval();

            if (buffer != null)
            {
                // Fire a property change to denote that the layer's backing data has changed.
                this.layer.firePropertyChange(AVKey.LAYER, null, this);
            }

            return buffer;
        }

        @Override
        protected ByteBuffer handleXMLContent() throws IOException
        {
            // Check for an exception report
            String s = WWIO.byteBufferToString(this.getRetriever().getBuffer(), 1024, null);
            if (s.contains(""))
            {
                // TODO: Parse the xml and include only the message text in the log message.

                StringBuilder sb = new StringBuilder(this.getRetriever().getName());

                sb.append("\n");
                sb.append(WWIO.byteBufferToString(this.getRetriever().getBuffer(), 2048, null));
                Logging.logger().warning(sb.toString());

                return null;
            }

            this.saveBuffer();

            return this.getRetriever().getBuffer();
        }
    }

    // *** Bulk download ***
    // *** Bulk download ***
    // *** Bulk download ***

    /**
     * Start a new {@link BulkRetrievalThread} that downloads all placenames for a given sector and resolution to the
     * current World Wind file cache.
     * 

* This method creates and starts a thread to perform the download. A reference to the thread is returned. To create * a downloader that has not been started, construct a {@link PlaceNameLayerBulkDownloader}. *

* Note that the target resolution must be provided in radians of latitude per texel, which is the resolution in * meters divided by the globe radius. * * @param sector the sector to download data for. * @param resolution the target resolution, provided in radians of latitude per texel. * @param listener an optional retrieval listener. May be null. * * @return the {@link PlaceNameLayerBulkDownloader} that executes the retrieval. * * @throws IllegalArgumentException if the sector is null or the resolution is less than zero. * @see PlaceNameLayerBulkDownloader */ public BulkRetrievalThread makeLocal(Sector sector, double resolution, BulkRetrievalListener listener) { PlaceNameLayerBulkDownloader thread = new PlaceNameLayerBulkDownloader(this, sector, resolution, this.getDataFileStore(), listener); thread.setDaemon(true); thread.start(); return thread; } /** * Start a new {@link BulkRetrievalThread} that downloads all placenames for a given sector and resolution to a * specified file store. *

* This method creates and starts a thread to perform the download. A reference to the thread is returned. To create * a downloader that has not been started, construct a {@link PlaceNameLayerBulkDownloader}. *

* Note that the target resolution must be provided in radians of latitude per texel, which is the resolution in * meters divided by the globe radius. * * @param sector the sector to download data for. * @param resolution the target resolution, provided in radians of latitude per texel. * @param fileStore the file store in which to place the downloaded elevations. If null the current World Wind file * cache is used. * @param listener an optional retrieval listener. May be null. * * @return the {@link PlaceNameLayerBulkDownloader} that executes the retrieval. * * @throws IllegalArgumentException if the sector is null or the resolution is less than zero. * @see PlaceNameLayerBulkDownloader */ public BulkRetrievalThread makeLocal(Sector sector, double resolution, FileStore fileStore, BulkRetrievalListener listener) { PlaceNameLayerBulkDownloader thread = new PlaceNameLayerBulkDownloader(this, sector, resolution, fileStore != null ? fileStore : this.getDataFileStore(), listener); thread.setDaemon(true); thread.start(); return thread; } /** * Get the estimated size in bytes of the placenames not in the World Wind file cache for the given sector and * resolution. *

* Note that the target resolution must be provided in radians of latitude per texel, which is the resolution in * meters divided by the globe radius. * * @param sector the sector to estimate. * @param resolution the target resolution, provided in radians of latitude per texel. * * @return the estimated size in bytes of the missing placenames. * * @throws IllegalArgumentException if the sector is null or the resolution is less than zero. */ public long getEstimatedMissingDataSize(Sector sector, double resolution) { return this.getEstimatedMissingDataSize(sector, resolution, this.getDataFileStore()); } /** * Get the estimated size in bytes of the placenames not in a specified file store for the given sector and * resolution. *

* Note that the target resolution must be provided in radians of latitude per texel, which is the resolution in * meters divided by the globe radius. * * @param sector the sector to estimate. * @param resolution the target resolution, provided in radians of latitude per texel. * @param fileStore the file store to examine. If null the current World Wind file cache is used. * * @return the estimated size in byte of the missing placenames. * * @throws IllegalArgumentException if the sector is null or the resolution is less than zero. */ public long getEstimatedMissingDataSize(Sector sector, double resolution, FileStore fileStore) { try { PlaceNameLayerBulkDownloader downloader = new PlaceNameLayerBulkDownloader(this, sector, resolution, fileStore != null ? fileStore : this.getDataFileStore(), null); return downloader.getEstimatedMissingDataSize(); } catch (Exception e) { String message = Logging.getMessage("generic.ExceptionDuringDataSizeEstimate", this.getName()); Logging.logger().severe(message); throw new RuntimeException(message); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy