/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.layers.curtain; import static au.gov.ga.earthsci.worldwind.common.util.message.CommonMessageConstants.*; import static au.gov.ga.earthsci.worldwind.common.util.message.MessageSourceAccessor.getMessage; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.avlist.AVList; import gov.nasa.worldwind.avlist.AVListImpl; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.util.AbsentResourceList; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.TileKey; import au.gov.ga.earthsci.worldwind.common.util.AVKeyMore; import au.gov.ga.earthsci.worldwind.common.util.Util; /** * Represents a single LOD level for the {@link TiledCurtainLayer}. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class CurtainLevel extends AVListImpl implements Comparable<CurtainLevel> { private final AVList params; private final int levelNumber; private final String levelName; // null or empty level name signifies no data resources associated with this level private final int levelWidth; private final int levelHeight; private final int tileWidth; private final int tileHeight; private final String cacheName; private final String service; private final String dataset; private final String formatSuffix; private final double texelSize; private final String path; private final CurtainTileUrlBuilder urlBuilder; private long expiryTime; private boolean active = true; private final int columnCount; private final int rowCount; // Absent tiles: A tile is deemed absent if a specified maximum number of attempts have been made to retrieve it. // Retrieval attempts are governed by a minimum time interval between successive attempts. If an attempt is made // within this interval, the tile is still deemed to be absent until the interval expires. private final AbsentResourceList absentTiles; int DEFAULT_MAX_ABSENT_TILE_ATTEMPTS = 2; int DEFAULT_MIN_ABSENT_TILE_CHECK_INTERVAL = 10000; // milliseconds public CurtainLevel(AVList params) { if (params == null) { String message = Logging.getMessage("nullValue.LevelConfigParams"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.params = params.copy(); // Private copy to insulate from subsequent changes by the app String message = this.validate(params); if (message != null) { Logging.logger().severe(message); throw new IllegalArgumentException(message); } String ln = this.params.getStringValue(AVKey.LEVEL_NAME); this.levelName = ln != null ? ln : ""; this.levelNumber = (Integer) this.params.getValue(AVKey.LEVEL_NUMBER); this.levelWidth = (Integer) this.params.getValue(AVKeyMore.LEVEL_WIDTH); this.levelHeight = (Integer) this.params.getValue(AVKeyMore.LEVEL_HEIGHT); int tileWidth = (Integer) this.params.getValue(AVKey.TILE_WIDTH); int tileHeight = (Integer) this.params.getValue(AVKey.TILE_HEIGHT); this.cacheName = this.params.getStringValue(AVKey.DATA_CACHE_NAME); this.service = this.params.getStringValue(AVKey.SERVICE); this.dataset = this.params.getStringValue(AVKey.DATASET_NAME); this.formatSuffix = this.params.getStringValue(AVKey.FORMAT_SUFFIX); this.urlBuilder = (CurtainTileUrlBuilder) this.params.getValue(AVKey.TILE_URL_BUILDER); this.expiryTime = AVListImpl.getLongValue(params, AVKey.EXPIRY_TIME, 0L); Angle curtainLength = ((Path) this.params.getValue(AVKeyMore.PATH)).getLength(); //Angle curtainLength = (Angle) this.params.getValue(AVKeyMore.CURTAIN_LENGTH); this.texelSize = curtainLength.radians / this.levelWidth; //work out this level's tile width/height int widthsPerTile = Util.previousPowerOfTwo(tileWidth / this.levelWidth); int heightsPerTile = Util.previousPowerOfTwo(tileHeight / this.levelHeight); if (widthsPerTile > 1) { tileWidth /= widthsPerTile; tileHeight *= widthsPerTile; } if (heightsPerTile > 1) { tileWidth *= heightsPerTile; tileHeight /= heightsPerTile; } this.tileWidth = tileWidth; this.tileHeight = tileHeight; this.columnCount = (levelWidth - 1) / tileWidth + 1; this.rowCount = (levelHeight - 1) / tileHeight + 1; this.path = this.cacheName + "/" + this.levelName; Integer maxAbsentTileAttempts = (Integer) this.params.getValue(AVKey.MAX_ABSENT_TILE_ATTEMPTS); if (maxAbsentTileAttempts == null) maxAbsentTileAttempts = DEFAULT_MAX_ABSENT_TILE_ATTEMPTS; Integer minAbsentTileCheckInterval = (Integer) this.params.getValue(AVKey.MIN_ABSENT_TILE_CHECK_INTERVAL); if (minAbsentTileCheckInterval == null) minAbsentTileCheckInterval = DEFAULT_MIN_ABSENT_TILE_CHECK_INTERVAL; this.absentTiles = new AbsentResourceList(maxAbsentTileAttempts, minAbsentTileCheckInterval); } private String validate(AVList params) { StringBuffer sb = new StringBuffer(); Object o = params.getValue(AVKey.LEVEL_NUMBER); if (o == null || !(o instanceof Integer) || ((Integer) o) < 0) sb.append(Logging.getMessage("term.levelNumber")).append(" "); o = params.getValue(AVKey.LEVEL_NAME); if (o == null || !(o instanceof String)) sb.append(Logging.getMessage("term.levelName")).append(" "); o = params.getValue(AVKeyMore.LEVEL_WIDTH); if (o == null || !(o instanceof Integer) || ((Integer) o) < 0) sb.append(getMessage(getTermLevelWidthKey())).append(" "); o = params.getValue(AVKeyMore.LEVEL_HEIGHT); if (o == null || !(o instanceof Integer) || ((Integer) o) < 0) sb.append(getMessage(getTermLevelHeightKey())).append(" "); o = params.getValue(AVKey.TILE_WIDTH); if (o == null || !(o instanceof Integer) || ((Integer) o) < 0) sb.append(Logging.getMessage("term.tileWidth")).append(" "); o = params.getValue(AVKey.TILE_HEIGHT); if (o == null || !(o instanceof Integer) || ((Integer) o) < 0) sb.append(Logging.getMessage("term.tileHeight")).append(" "); o = params.getValue(AVKeyMore.PATH); if (o == null || !(o instanceof Path) || ((Path) o).getLength().radians <= 0) sb.append(getMessage(getTermPathKey())).append(" "); o = params.getValue(AVKey.DATA_CACHE_NAME); if (o == null || !(o instanceof String) || ((String) o).length() < 1) sb.append(Logging.getMessage("term.fileStoreFolder")).append(" "); o = params.getValue(AVKey.TILE_URL_BUILDER); if (o == null || !(o instanceof CurtainTileUrlBuilder)) sb.append(Logging.getMessage("term.tileURLBuilder")).append(" "); o = params.getValue(AVKey.EXPIRY_TIME); if (o != null && (!(o instanceof Long) || ((Long) o) < 1)) sb.append(Logging.getMessage("term.expiryTime")).append(" "); if (params.getStringValue(AVKey.LEVEL_NAME).length() > 0) { o = params.getValue(AVKey.DATASET_NAME); if (o == null || !(o instanceof String) || ((String) o).length() < 1) sb.append(Logging.getMessage("term.datasetName")).append(" "); o = params.getValue(AVKey.FORMAT_SUFFIX); if (o == null || !(o instanceof String) || ((String) o).length() < 1) sb.append(Logging.getMessage("term.formatSuffix")).append(" "); } if (sb.length() == 0) return null; return Logging.getMessage("layers.LevelSet.InvalidLevelDescriptorFields", sb.toString()); } public final AVList getParams() { return params; } public String getPath() { return this.path; } public final int getLevelNumber() { return this.levelNumber; } public String getLevelName() { return this.levelName; } public final int getTileWidth() { return this.tileWidth; } public final int getTileHeight() { return this.tileHeight; } public final String getFormatSuffix() { return this.formatSuffix; } public final String getService() { return this.service; } public final String getDataset() { return this.dataset; } public final String getCacheName() { return this.cacheName; } public final double getTexelSize() { return this.texelSize; } public final boolean isEmpty() { return this.levelName == null || this.levelName.equals("") || !this.active; } public final void markResourceAbsent(long tileNumber) { if (tileNumber >= 0) this.absentTiles.markResourceAbsent(tileNumber); } public final boolean isResourceAbsent(long tileNumber) { return this.absentTiles.isResourceAbsent(tileNumber); } public final void unmarkResourceAbsent(long tileNumber) { if (tileNumber >= 0) this.absentTiles.unmarkResourceAbsent(tileNumber); } public final long getExpiryTime() { return this.expiryTime; } public final void setExpiryTime(long expTime) { this.expiryTime = expTime; } public final int getLevelWidth() { return levelWidth; } public final int getLevelHeight() { return levelHeight; } public final int getColumnCount() { return columnCount; } public final int getRowCount() { return rowCount; } public boolean isActive() { return this.active; } public void setActive(boolean active) { this.active = active; } public AbsentResourceList getAbsentTiles() { return absentTiles; } @Override public Object setValue(String key, Object value) { if (key != null && key.equals(AVKey.MAX_ABSENT_TILE_ATTEMPTS) && value instanceof Integer) this.absentTiles.setMaxTries((Integer) value); else if (key != null && key.equals(AVKey.MIN_ABSENT_TILE_CHECK_INTERVAL) && value instanceof Integer) this.absentTiles.setMinCheckInterval((Integer) value); return super.setValue(key, value); } @Override public Object getValue(String key) { if (key != null && key.equals(AVKey.MAX_ABSENT_TILE_ATTEMPTS)) return this.absentTiles.getMaxTries(); else if (key != null && key.equals(AVKey.MIN_ABSENT_TILE_CHECK_INTERVAL)) return this.absentTiles.getMinCheckInterval(); return super.getValue(key); } /** * Returns the URL necessary to retrieve the specified tile. * * @param tile * the tile who's resources will be retrieved. * @param imageFormat * a string identifying the mime type of the desired image format * * @return the resource URL. * * @throws java.net.MalformedURLException * if the URL cannot be formed from the tile's parameters. * @throws IllegalArgumentException * if <code>tile</code> is null. */ public java.net.URL getTileResourceURL(CurtainTile tile, String imageFormat) throws java.net.MalformedURLException { if (tile == null) { String msg = Logging.getMessage("nullValue.TileIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } return this.urlBuilder.getURL(tile, imageFormat); } /*public Segment computeSegmentForPosition(Angle latitude, Angle longitude, LatLon tileOrigin) { if (latitude == null || longitude == null) { String message = Logging.getMessage("nullValue.LatLonIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (tileOrigin == null) { String message = Logging.getMessage("nullValue.TileOriginIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Compute the tile's SW lat/lon based on its row/col in the level's data set. Angle dLat = this.getTileDelta().getLatitude(); Angle dLon = this.getTileDelta().getLongitude(); Angle latOrigin = tileOrigin.getLatitude(); Angle lonOrigin = tileOrigin.getLongitude(); int row = Tile.computeRow(dLat, latitude, latOrigin); int col = Tile.computeColumn(dLon, longitude, lonOrigin); Angle minLatitude = Tile.computeRowLatitude(row, dLat, latOrigin); Angle minLongitude = Tile.computeColumnLongitude(col, dLon, lonOrigin); return new Sector(minLatitude, minLatitude.add(dLat), minLongitude, minLongitude.add(dLon)); }*/ public Segment computeSegmentForKey(TileKey key) { if (key == null) { String msg = Logging.getMessage("nullValue.KeyIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } return computeSegmentForRowColumn(key.getRow(), key.getColumn()); } public Segment computeSegmentForRowColumn(int row, int column) { double startX = computeStartPercentForColumn(column); double endX = computeStartPercentForColumn(column + 1); double startY = computeStartPercentForRow(row); double endY = computeStartPercentForRow(row + 1); return new Segment(startX, endX, startY, endY); } private double computeStartPercentForColumn(int column) { double x = column * tileWidth; return Util.clamp(x / levelWidth, 0, 1); } private double computeStartPercentForRow(int row) { double y = row * tileHeight; return Util.clamp(y / levelHeight, 0, 1); } @Override public int compareTo(CurtainLevel that) { if (that == null) { String msg = Logging.getMessage("nullValue.LevelIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } return this.levelNumber < that.levelNumber ? -1 : this.levelNumber == that.levelNumber ? 0 : 1; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final CurtainLevel level = (CurtainLevel) o; if (levelNumber != level.levelNumber) return false; if (tileHeight != level.tileHeight) return false; if (tileWidth != level.tileWidth) return false; if (levelHeight != level.levelHeight) return false; if (levelWidth != level.levelWidth) return false; if (cacheName != null ? !cacheName.equals(level.cacheName) : level.cacheName != null) return false; if (dataset != null ? !dataset.equals(level.dataset) : level.dataset != null) return false; if (formatSuffix != null ? !formatSuffix.equals(level.formatSuffix) : level.formatSuffix != null) return false; if (levelName != null ? !levelName.equals(level.levelName) : level.levelName != null) return false; if (service != null ? !service.equals(level.service) : level.service != null) return false; //noinspection RedundantIfStatement // if (texelSize != level.texelSize) // return false; return true; } @Override public int hashCode() { int result; result = levelNumber; result = 29 * result + (levelName != null ? levelName.hashCode() : 0); result = 29 * result + tileWidth; result = 29 * result + tileHeight; result = 29 * result + levelWidth; result = 29 * result + levelHeight; result = 29 * result + (formatSuffix != null ? formatSuffix.hashCode() : 0); result = 29 * result + (service != null ? service.hashCode() : 0); result = 29 * result + (dataset != null ? dataset.hashCode() : 0); result = 29 * result + (cacheName != null ? cacheName.hashCode() : 0); return result; } @Override public String toString() { return this.path; } }