/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.util;
import gov.nasa.worldwind.WWObjectImpl;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.geom.*;
import java.net.*;
import java.util.*;
/**
* @author tag
* @version $Id: LevelSet.java 5055 2008-04-14 05:19:11Z tgaskins $
*/
public class LevelSet extends WWObjectImpl
{
public static final class SectorResolution
{
private final int levelNumber;
private final Sector sector;
public SectorResolution(Sector sector, int levelNumber)
{
this.levelNumber = levelNumber;
this.sector = sector;
}
}
private final Sector sector;
private final LatLon levelZeroTileDelta;
private final int numLevelZeroColumns;
private final java.util.ArrayList<Level> levels = new java.util.ArrayList<Level>();
private final SectorResolution[] sectorLevelLimits;
public LevelSet(Collection<Level> levels, AVList params)
{
this(params, levels);
}
public LevelSet(AVList params)
{
this(params, null);
}
public LevelSet(AVList params, Collection<Level> levels)
{
StringBuffer sb = new StringBuffer();
Object o = params.getValue(AVKey.LEVEL_ZERO_TILE_DELTA);
if (o == null || !(o instanceof LatLon))
sb.append(Logging.getMessage("term.tileDelta"));
o = params.getValue(AVKey.SECTOR);
if (o == null || !(o instanceof Sector))
sb.append(Logging.getMessage("term.sector"));
int numLevels = 0;
o = params.getValue(AVKey.NUM_LEVELS);
if (o == null || !(o instanceof Integer) || (numLevels = (Integer) o) < 1)
sb.append(Logging.getMessage("term.numLevels"));
int numEmptyLevels = 0;
o = params.getValue(AVKey.NUM_EMPTY_LEVELS);
if (o == null || !(o instanceof Integer) || (numEmptyLevels = (Integer) o) < 0)
sb.append(Logging.getMessage("term.numEMptyLevels"));
String[] inactiveLevels = null;
o = params.getValue(AVKey.INACTIVE_LEVELS);
if (o != null && !(o instanceof String))
sb.append(Logging.getMessage("term.sector")); // TODO field name
else if (o != null)
inactiveLevels = ((String) o).split(",");
SectorResolution[] sectorLimits = null;
o = params.getValue(AVKey.SECTOR_RESOLUTION_LIMITS);
if (o != null && !(o instanceof SectorResolution[]))
{
sb.append(Logging.getMessage("term.sectorResolutionLimits"));
}
else if (o != null)
{
sectorLimits = (SectorResolution[]) o;
for (SectorResolution sr : sectorLimits)
{
if (sr.levelNumber > numLevels - 1)
{
String message =
Logging.getMessage("LevelSet.sectorResolutionLimitsTooHigh", sr.levelNumber, numLevels - 1);
Logging.logger().warning(message);
break;
}
}
}
this.sectorLevelLimits = sectorLimits;
if (sb.length() > 0)
{
String message = Logging.getMessage("layers.LevelSet.InvalidLevelDescriptorFields", sb.toString());
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.levelZeroTileDelta = (LatLon) params.getValue(AVKey.LEVEL_ZERO_TILE_DELTA);
this.sector = (Sector) params.getValue(AVKey.SECTOR);
params = params.copy(); // copy so as not to modify the user's params
TileUrlBuilder tub = (TileUrlBuilder) params.getValue(AVKey.TILE_URL_BUILDER);
if (tub == null)
{
params.setValue(AVKey.TILE_URL_BUILDER, new TileUrlBuilder()
{
public URL getURL(Tile tile, String altImageFormat) throws MalformedURLException
{
String service = tile.getLevel().getService();
if (service == null || service.length() < 1)
return null;
StringBuffer sb = new StringBuffer(tile.getLevel().getService());
if (sb.lastIndexOf("?") != sb.length() - 1)
sb.append("?");
sb.append("T=");
sb.append(tile.getLevel().getDataset());
sb.append("&L=");
sb.append(tile.getLevel().getLevelName());
sb.append("&X=");
sb.append(tile.getColumn());
sb.append("&Y=");
sb.append(tile.getRow());
// Convention for NASA WWN tiles is to request them with common dataset name but without dds.
return new URL(altImageFormat == null ? sb.toString() : sb.toString().replace("dds", ""));
}
});
}
if (this.sectorLevelLimits != null)
{
Arrays.sort(this.sectorLevelLimits, new Comparator<SectorResolution>()
{
public int compare(SectorResolution sra, SectorResolution srb)
{
// sort order is deliberately backwards in order to list higher-resolution sectors first
return sra.levelNumber < srb.levelNumber ? 1 : sra.levelNumber == srb.levelNumber ? 0 : -1;
}
});
}
// Compute the number of level zero columns. This value is guaranteed to be a nonzero number, since there is
// generally at least one level zero tile.
this.numLevelZeroColumns =
(int) Math.max(1, Math.round(this.sector.getDeltaLon().divide(this.levelZeroTileDelta.getLongitude())));
if (levels != null)
{
this.levels.addAll(levels);
}
else
{
for (int i = 0; i < numLevels; i++)
{
params.setValue(AVKey.LEVEL_NAME, i < numEmptyLevels ? "" : Integer.toString(i - numEmptyLevels));
params.setValue(AVKey.LEVEL_NUMBER, i);
Angle latDelta = this.levelZeroTileDelta.getLatitude().divide(Math.pow(2, i));
Angle lonDelta = this.levelZeroTileDelta.getLongitude().divide(Math.pow(2, i));
params.setValue(AVKey.TILE_DELTA, new LatLon(latDelta, lonDelta));
this.levels.add(new Level(params));
}
}
if (inactiveLevels != null)
{
for (String s : inactiveLevels)
{
int i = Integer.parseInt(s);
this.getLevel(i).setActive(false);
}
}
}
public LevelSet(LevelSet source)
{
if (source == null)
{
String msg = Logging.getMessage("nullValue.LevelSetIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.levelZeroTileDelta = source.levelZeroTileDelta;
this.sector = source.sector;
this.numLevelZeroColumns = source.numLevelZeroColumns;
this.sectorLevelLimits = source.sectorLevelLimits;
for (Level level : source.levels)
{
this.levels.add(level); // Levels are final, so it's safe to copy references.
}
}
public final Sector getSector()
{
return this.sector;
}
public final LatLon getLevelZeroTileDelta()
{
return this.levelZeroTileDelta;
}
public final ArrayList<Level> getLevels()
{
return this.levels;
}
public final Level getLevel(int levelNumber)
{
return (levelNumber >= 0 && levelNumber < this.levels.size()) ? this.levels.get(levelNumber) : null;
}
public final int getNumLevels()
{
return this.levels.size();
}
public final Level getFirstLevel()
{
return this.getLevel(0);
}
public final Level getLastLevel()
{
return this.getLevel(this.getNumLevels() - 1);
}
public final Level getNextToLastLevel()
{
return this.getLevel(this.getNumLevels() > 1 ? this.getNumLevels() - 2 : 0);
}
public final Level getLastLevel(Sector sector)
{
Level level = this.getLevel(this.getNumLevels() - 1);
if (this.sectorLevelLimits != null)
for (SectorResolution sr : this.sectorLevelLimits)
{
if (sr.sector.intersects(sector) && sr.levelNumber <= level.getLevelNumber())
{
level = this.getLevel(sr.levelNumber);
break;
}
}
return level;
}
public final Level getLastLevel(Angle latitude, Angle longitude)
{
Level level = this.getLevel(this.getNumLevels() - 1);
if (this.sectorLevelLimits != null)
for (SectorResolution sr : this.sectorLevelLimits)
{
if (sr.sector.contains(latitude, longitude) && sr.levelNumber <= level.getLevelNumber())
{
level = this.getLevel(sr.levelNumber);
break;
}
}
return level;
}
public final boolean isFinalLevel(int levelNum)
{
return levelNum == this.getNumLevels() - 1;
}
public final boolean isLevelEmpty(int levelNumber)
{
return this.levels.get(levelNumber).isEmpty();
}
private int numColumnsInLevel(Level level)
{
int levelDelta = level.getLevelNumber() - this.getFirstLevel().getLevelNumber();
double twoToTheN = Math.pow(2, levelDelta);
return (int) (twoToTheN * this.numLevelZeroColumns);
}
private long getTileNumber(Tile tile)
{
return tile.getRow() * this.numColumnsInLevel(tile.getLevel()) + tile.getColumn();
}
/**
* Instructs the level set that a tile is likely to be absent.
*
* @param tile The tile to mark as having an absent resource.
* @throws IllegalArgumentException if <code>tile</code> is null
*/
public final void markResourceAbsent(Tile tile)
{
if (tile == null)
{
String msg = Logging.getMessage("nullValue.TileIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
tile.getLevel().markResourceAbsent(this.getTileNumber(tile));
}
/**
* Indicates whether a tile has been marked as absent.
*
* @param tile The tile in question.
* @return <code>true</code> if the tile is marked absent, otherwise <code>false</code>.
* @throws IllegalArgumentException if <code>tile</code> is null
*/
public final boolean isResourceAbsent(Tile tile)
{
if (tile == null)
{
String msg = Logging.getMessage("nullValue.TileIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (tile.getLevel().isEmpty())
return true;
int tileNumber = tile.getRow() * this.numColumnsInLevel(tile.getLevel()) + tile.getColumn();
return tile.getLevel().isResourceAbsent(tileNumber);
}
/**
* Removes the absent-tile mark associated with a tile, if one is associatied.
*
* @param tile The tile to unmark.
* @throws IllegalArgumentException if <code>tile</code> is null
*/
public final void unmarkResourceAbsent(Tile tile)
{
if (tile == null)
{
String msg = Logging.getMessage("nullValue.TileIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
tile.getLevel().unmarkResourceAbsent(this.getTileNumber(tile));
}
// Create the tile corresponding to a specified key.
public Sector computeSectorForKey(TileKey key)
{
if (key == null)
{
String msg = Logging.getMessage("nullValue.KeyIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Level level = this.getLevel(key.getLevelNumber());
// Compute the tile's SW lat/lon based on its row/col in the level's data set.
Angle dLat = level.getTileDelta().getLatitude();
Angle dLon = level.getTileDelta().getLongitude();
Angle minLatitude = Tile.computeRowLatitude(key.getRow(), dLat);
Angle minLongitude = Tile.computeColumnLongitude(key.getColumn(), dLon);
return new Sector(minLatitude, minLatitude.add(dLat), minLongitude, minLongitude.add(dLon));
}
// Create the tile corresponding to a specified key.
public Tile createTile(TileKey key)
{
if (key == null)
{
String msg = Logging.getMessage("nullValue.KeyIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Level level = this.getLevel(key.getLevelNumber());
// Compute the tile's SW lat/lon based on its row/col in the level's data set.
Angle dLat = level.getTileDelta().getLatitude();
Angle dLon = level.getTileDelta().getLongitude();
Angle minLatitude = Tile.computeRowLatitude(key.getRow(), dLat);
Angle minLongitude = Tile.computeColumnLongitude(key.getColumn(), dLon);
Sector tileSector = new Sector(minLatitude, minLatitude.add(dLat), minLongitude, minLongitude.add(dLon));
return new Tile(tileSector, level, key.getRow(), key.getColumn());
}
}