com.rgi.g2t.RawImageTileReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swagd Show documentation
Show all versions of swagd Show documentation
SWAGD: Software to Aggregate Geospatial Data
The newest version!
/* The MIT License (MIT)
*
* Copyright (c) 2015 Reinventing Geospatial, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rgi.g2t;
import com.rgi.common.BoundingBox;
import com.rgi.common.Dimensions;
import com.rgi.common.Range;
import com.rgi.common.coordinate.Coordinate;
import com.rgi.common.coordinate.CoordinateReferenceSystem;
import com.rgi.common.coordinate.CrsCoordinate;
import com.rgi.common.coordinate.referencesystem.profile.CrsProfile;
import com.rgi.common.coordinate.referencesystem.profile.CrsProfileFactory;
import com.rgi.common.tile.TileOrigin;
import com.rgi.common.tile.scheme.TileMatrixDimensions;
import com.rgi.common.tile.scheme.TileScheme;
import com.rgi.common.tile.scheme.ZoomTimesTwo;
import com.rgi.common.util.FileUtility;
import com.rgi.store.tiles.TileHandle;
import com.rgi.store.tiles.TileStoreException;
import com.rgi.store.tiles.TileStoreReader;
import org.gdal.gdal.Dataset;
import utility.GdalUtility;
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.naming.OperationNotSupportedException;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.zip.DataFormatException;
/**
* TileStoreReader implementation for GDAL-readable image files
*
* @author Steven D. Lander
* @author Luke D. Lambert
*
*/
public class RawImageTileReader implements TileStoreReader
{
/**
* Constructor
*
* @param rawImage
* A raster image
* @param tileSize
* A {@link Dimensions} that describes what an individual tile
* looks like
* @param noDataColor
* The {@link Color} of the NODATA fields within the raster image
* @throws TileStoreException
* Thrown when GDAL could not get the correct
* {@link CoordinateReferenceSystem} of the input raster OR if
* the raw image could not be loaded as a {@link Dataset}
*/
public RawImageTileReader(final File rawImage,
final Dimensions tileSize,
final Color noDataColor) throws TileStoreException
{
this(rawImage,
tileSize,
noDataColor,
null);
}
/**
* Constructor
*
* @param rawImage
* A raster image {@link File}
* @param tileSize
* A {@link Dimensions} that describes what an individual tile
* looks like
* @param noDataColor
* The {@link Color} of the NODATA fields within the raster image
* @param coordinateReferenceSystem
* The {@link CoordinateReferenceSystem} the tiles should be
* output in
* @throws TileStoreException
* Thrown when GDAL could not get the correct
* {@link CoordinateReferenceSystem} of the input raster OR if
* the raw image could not be loaded as a {@link Dataset}
*/
public RawImageTileReader(final File rawImage,
final Dimensions tileSize,
final Color noDataColor,
final CoordinateReferenceSystem coordinateReferenceSystem) throws TileStoreException
{
this(rawImage,
GdalUtility.open(rawImage, coordinateReferenceSystem),
tileSize,
noDataColor,
coordinateReferenceSystem);
}
public RawImageTileReader(final File rawImage,
final Dataset dataset,
final Dimensions tileSize,
final Color noDataColor,
final CoordinateReferenceSystem coordinateReferenceSystem) throws TileStoreException
{
if(rawImage == null || !rawImage.canRead())
{
throw new IllegalArgumentException("Raw image may not be null, and must represent a valid file on disk.");
}
if(tileSize == null)
{
throw new IllegalArgumentException("Tile size may not be null.");
}
this.rawImage = rawImage;
this.tileSize = tileSize;
// TODO check noDataColor for null when the feature is implemented
// this.noDataColor = noDataColor;
this.dataset = dataset;
try
{
if(this.dataset.GetRasterCount() == 0)
{
throw new IllegalArgumentException("Input file has no raster bands");
}
if(this.dataset.GetRasterBand(1).GetColorTable() != null)
{
System.out.println("expand this raster to RGB/RGBA"); // TODO: make a temporary vrt with gdal_translate to expand this to RGB/RGBA
}
// We cannot tile an image with no geo referencing information
if(!GdalUtility.hasGeoReference(this.dataset))
{
throw new IllegalArgumentException("Input raster image has no georeference.");
}
this.coordinateReferenceSystem = GdalUtility.getCoordinateReferenceSystem(GdalUtility.getSpatialReference(this.dataset));
if(this.coordinateReferenceSystem == null)
{
throw new IllegalArgumentException("Image file is not in a recognized coordinate reference system");
}
this.profile = CrsProfileFactory.create(this.coordinateReferenceSystem);
this.tileScheme = new ZoomTimesTwo(0, MAX_ZOOM_LEVEL, 1, 1); // Use absolute tile numbering
final BoundingBox datasetBounds = GdalUtility.getBounds(this.dataset);
this.tileRanges = GdalUtility.calculateTileRanges(this.tileScheme,
datasetBounds,
this.profile.getBounds(),
this.profile,
RawImageTileReader.Origin);
//final int minimumZoom = GdalUtility.getMinimalZoom(this.dataset, this.tileRanges, Origin, this.tileScheme, tileSize);
final int minimumZoom = GdalUtility.getMinimalZoom(this.tileRanges);
final int maximumZoom = GdalUtility.getMaximalZoom(this.dataset, this.tileRanges, Origin, this.tileScheme, tileSize);
// The bounds of the dataset is **almost never** the bounds of the
// data. The bounds of the dataset fit inside the bounds of the
// data because the bounds of the data must align to the tile grid.
// The minimum zoom level is selected such that the entire dataset
// fits inside a single tile. that single tile is the minimum data
// bounds.
final Coordinate minimumTile = this.tileRanges.get(minimumZoom).getMinimum(); // The minimum and maximum for the range returned from tileRanges.get() should be identical (it's a single tile)
this.dataBounds = this.getTileBoundingBox(minimumTile.getX(),
minimumTile.getY(),
this.tileScheme.dimensions(minimumZoom));
this.zoomLevels = IntStream.rangeClosed(minimumZoom, maximumZoom)
.boxed()
.collect(Collectors.toSet());
this.tileCount = IntStream.rangeClosed(minimumZoom, maximumZoom)
.map(zoomLevel -> { final Range> range = this.tileRanges.get(zoomLevel);
return (range.getMaximum().getX() - range.getMinimum().getX() + 1) *
(range.getMinimum().getY() - range.getMaximum().getY() + 1);
})
.sum();
}
catch(final DataFormatException dfe)
{
this.close();
throw new TileStoreException(dfe);
}
this.cachedTiles = new HashMap<>();
}
@SuppressWarnings("unchecked")
@Override
public final void close() throws TileStoreException
{
// Remove temporary reprojected file
if(this.dataset != null)
{
final Iterable files = this.dataset.GetFileList();
this.dataset.delete();
for (final String file : files)
{
if(file.startsWith((System.getProperty("java.io.tmpdir"))))
{
try
{
Files.delete(Paths.get(file));
}
catch (final IOException e)
{
throw new TileStoreException(e);
}
}
}
}
// Remove final temporary cached tile(s)
try
{
this.cachedTiles.values().stream().forEach(tile -> {
try
{
Files.delete(tile);
}
catch (final IOException e)
{
// Break the stream if a tile cannot
// be deleted
//noinspection ThrowCaughtLocally
throw new RuntimeException(e);
}
});
}
catch(final RuntimeException e)
{
// Catch the exception when a tile cannot be deleted and throw a new TileStoreException
throw new TileStoreException(e);
}
}
@Override
public BoundingBox getBounds()
{
return this.dataBounds;
}
@Override
public long countTiles()
{
return this.tileCount;
}
@Override
public long getByteSize()
{
return this.rawImage.length();
}
@Override
public BufferedImage getTile(final int column, final int row, final int zoomLevel) throws TileStoreException
{
throw new TileStoreException(new OperationNotSupportedException(NOT_SUPPORTED_MESSAGE));
}
@Override
public BufferedImage getTile(final CrsCoordinate coordinate, final int zoomLevel) throws TileStoreException
{
throw new TileStoreException(new OperationNotSupportedException(NOT_SUPPORTED_MESSAGE));
}
@Override
public Set getZoomLevels()
{
// removes the possibility of the collection being modified during return
return Collections.unmodifiableSet(this.zoomLevels);
}
@Override
public Stream stream() throws TileStoreException
{
final List tileHandles = new ArrayList<>();
final Range zoomRange = new Range<>(this.zoomLevels, Integer::compare);
// Should always start with the lowest-integer-zoom-level that has only one tile
final Range> zoomInfo = this.tileRanges.get(zoomRange.getMinimum());
// Get the coordinate information
final Coordinate topLeftCoordinate = zoomInfo.getMinimum();
final Coordinate bottomRightCoordinate = zoomInfo.getMaximum();
// Parse each coordinate into min/max tiles for X/Y
final int zoomMinXTile = topLeftCoordinate.getX();
final int zoomMaxXTile = bottomRightCoordinate.getX();
final int zoomMinYTile = bottomRightCoordinate.getY();
final int zoomMaxYTile = topLeftCoordinate.getY();
//Make tiles for each tile at the min zoom level
for(int x = zoomMinXTile; x <= zoomMaxXTile; x++)
{
for (int y = zoomMinYTile; y <= zoomMaxYTile; y++)
{
this.makeTiles(tileHandles, new RawImageTileReader.RawImageTileHandle(zoomRange.getMinimum(), x, y), zoomRange.getMaximum());
}
}
// Sort the tile handles so that they decrement. This ensures all base level tiles
// are generated before the overview tiles
tileHandles.sort((o1, o2) -> Integer.compare(o2.getZoomLevel(), o1.getZoomLevel()));
return tileHandles.stream();
}
private boolean tileIntersectsData(final TileHandle tile)
{
final int zoom = tile.getZoomLevel();
final int column = tile.getColumn();
final int row = tile.getRow();
final Range> zoomRange = this.tileRanges.get(zoom);
return column >= zoomRange.getMinimum().getX() &&
column <= zoomRange.getMaximum().getX() &&
row >= zoomRange.getMaximum().getY() &&
row <= zoomRange.getMinimum().getY();
}
private void makeTiles(final List tileHandles, final TileHandle tile, final int maxZoom) throws TileStoreException
{
if(!this.tileIntersectsData(tile))
{
// Do nothing if the tile does not intersect with the data bounding box
return;
}
if(tile.getZoomLevel() == maxZoom)
{
// tell the RawImageTileHandle this is a special case: a gdalImage
tileHandles.add(new RawImageTileReader.RawImageTileHandle(tile.getZoomLevel(), tile.getColumn(), tile.getRow(), true));
}
else
{
// calculate all the tiles below this current one
final int zoomBelow = tile.getZoomLevel() + 1;
// Shift values instead of multiplying by 2 for possible performance improvement
final int zoomColumnBelow = tile.getColumn() << 1;
final int zoomRowBelow = tile.getRow() << 1;
// create handles for below tiles
final TileHandle belowOriginTile = new RawImageTileReader.RawImageTileHandle(zoomBelow,
zoomColumnBelow,
zoomRowBelow);
final TileHandle belowColumnShiftedTile = new RawImageTileReader.RawImageTileHandle(zoomBelow,
zoomColumnBelow + 1,
zoomRowBelow);
final TileHandle belowRowShiftedTile = new RawImageTileReader.RawImageTileHandle(zoomBelow,
zoomColumnBelow,
zoomRowBelow + 1);
final TileHandle belowBothShiftedTile = new RawImageTileReader.RawImageTileHandle(zoomBelow,
zoomColumnBelow + 1,
zoomRowBelow + 1);
// recurse
this.makeTiles(tileHandles, belowOriginTile, maxZoom);
this.makeTiles(tileHandles, belowColumnShiftedTile, maxZoom);
this.makeTiles(tileHandles, belowRowShiftedTile, maxZoom);
this.makeTiles(tileHandles, belowBothShiftedTile, maxZoom);
// finally, add this current tile
tileHandles.add(tile);
}
}
@Override
public Stream stream(final int zoomLevel) throws TileStoreException
{
final Range> zoomInfo = this.tileRanges.get(zoomLevel);
// Get the coordinate information
final Coordinate topLeftCoordinate = zoomInfo.getMinimum();
final Coordinate bottomRightCoordinate = zoomInfo.getMaximum();
// Parse each coordinate into min/max tiles for X/Y
final int zoomMinXTile = topLeftCoordinate.getX();
final int zoomMaxXTile = bottomRightCoordinate.getX();
final int zoomMinYTile = bottomRightCoordinate.getY();
final int zoomMaxYTile = topLeftCoordinate.getY();
// Create a tile handle list that we can append to
final Collection tileHandles = new ArrayList<>();
for(int tileY = zoomMaxYTile; tileY >= zoomMinYTile; --tileY)
{
for(int tileX = zoomMinXTile; tileX <= zoomMaxXTile; ++tileX)
{
tileHandles.add(new RawImageTileReader.RawImageTileHandle(zoomLevel, tileX, tileY));
}
}
// Return the entire tile handle list as a stream
return tileHandles.stream();
}
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem()
{
return this.coordinateReferenceSystem;
}
@Override
public String getName()
{
return FileUtility.nameWithoutExtension(this.rawImage);
}
@Override
public String getImageType() throws TileStoreException
{
try
{
final MimeType mimeType = new MimeType(Files.probeContentType(this.rawImage.toPath()));
if(mimeType.getPrimaryType().toLowerCase().equals("image"))
{
return mimeType.getSubType();
}
return null;
}
catch(final MimeTypeParseException | IOException ex)
{
throw new TileStoreException(ex);
}
}
@Override
public Dimensions getImageDimensions() throws TileStoreException
{
return this.tileSize;
}
@Override
public TileScheme getTileScheme() throws TileStoreException
{
return this.tileScheme;
}
@Override
public TileOrigin getTileOrigin()
{
return Origin;
}
@SuppressWarnings("NonStaticInnerClassInSecureContext")
private class RawImageTileHandle implements TileHandle
{
private final TileMatrixDimensions matrix;
private boolean gotImage;
private boolean gdalImage;
private Path cachedImageLocation;
private BufferedImage image;
private final int zoomLevel;
private final int column;
private final int row;
RawImageTileHandle(final int zoom,
final int column,
final int row) throws TileStoreException
{
this.zoomLevel = zoom;
this.column = column;
this.row = row;
this.matrix = RawImageTileReader.this.getTileScheme().dimensions(this.zoomLevel);
}
RawImageTileHandle(final int zoom,
final int column,
final int row,
final Path cachedImageLocation) throws TileStoreException
{
this.zoomLevel = zoom;
this.column = column;
this.row = row;
this.matrix = RawImageTileReader.this.getTileScheme().dimensions(this.zoomLevel);
this.cachedImageLocation = cachedImageLocation;
}
RawImageTileHandle(final int zoom,
final int column,
final int row,
final boolean gdalImage) throws TileStoreException
{
this.zoomLevel = zoom;
this.column = column;
this.row = row;
this.matrix = RawImageTileReader.this.getTileScheme().dimensions(this.zoomLevel);
this.gdalImage = gdalImage;
}
@Override
public int getZoomLevel()
{
return this.zoomLevel;
}
@Override
public int getColumn()
{
return this.column;
}
@Override
public int getRow()
{
return this.row;
}
public Path getCachedImagePath()
{
return this.cachedImageLocation;
}
@Override
public TileMatrixDimensions getMatrix() throws TileStoreException
{
return this.matrix;
}
@Override
public CrsCoordinate getCrsCoordinate() throws TileStoreException
{
return RawImageTileReader.this.tileToCrsCoordinate(this.column,
this.row,
this.matrix,
RawImageTileReader.this.getTileOrigin());
}
@Override
public CrsCoordinate getCrsCoordinate(final TileOrigin corner) throws TileStoreException
{
return RawImageTileReader.this.tileToCrsCoordinate(this.column,
this.row,
this.matrix,
corner);
}
@Override
public BoundingBox getBounds() throws TileStoreException
{
return RawImageTileReader.this.getTileBoundingBox(this.column,
this.row,
this.matrix);
}
@Override
public BufferedImage getImage() throws TileStoreException
{
if(this.gotImage)
{
return this.image;
}
if(this.gdalImage)
{
// Build the parameters for GDAL read raster call
final GdalUtility.GdalRasterParameters params = GdalUtility.getGdalRasterParameters(RawImageTileReader.this.dataset.GetGeoTransform(),
this.getBounds(),
RawImageTileReader.this.tileSize,
RawImageTileReader.this.dataset);
try
{
// Read image data directly from the raster
final byte[] imageData = GdalUtility.readRaster(params,
RawImageTileReader.this.dataset);
// TODO: logic goes here in the case that the querysize == tile size (gdalconstConstants.GRA_NearestNeighbour) (write directly)
final Dataset querySizeImageCanvas = GdalUtility.writeRaster(params,
imageData,
RawImageTileReader.this.dataset.GetRasterCount());
try
{
// Scale each band of tileDataInMemory down to the tile size (down from the query size)
final Dataset tileDataInMemory = GdalUtility.scaleQueryToTileSize(querySizeImageCanvas,
RawImageTileReader.this.tileSize);
this.image = GdalUtility.convert(tileDataInMemory);
// Write this image to disk for later overview generation
final Path baseTilePath = this.writeTempTile(this.image);
// Add the image path to the reader map
RawImageTileReader.this.cachedTiles.put(this.tileKey(this.zoomLevel,
this.column,
this.row),
baseTilePath);
// Clean up dataset
tileDataInMemory.delete();
this.gotImage = true;
return this.image;
}
catch(final TilingException | IOException ex)
{
throw new TileStoreException(ex);
}
finally
{
querySizeImageCanvas.delete();
}
}
catch(final TilingException ex)
{
throw new TileStoreException(ex);
}
catch(final IOException ignored)
{
// An IOException is thrown by GdalUtility.readRaster() when the tile boundary
// requested lies outside of the databounding box. This can sometimes occur
// when using the lowest-integer-zoom tile boundary as the tile-able area. This
// should not happen as the RawImageTileReader.makeTiles() method automatically
// checks if a generated tile does NOT intersect with the reported input raster
// bounding box.
// In this case, if readRaster does indeed throw IOException, just return a
// transparent tile.
this.gotImage = true;
return this.createTransparentImage();
}
}
// Make this tile by getting the tiles below it and scaling them to fit
return this.generateScaledTileFromChildren();
}
private Path writeTempTile(final RenderedImage tileImage) throws IOException
{
final Path baseTilePath = File.createTempFile("baseTile" + this.zoomLevel
+ this.column
+ this.row,
".png")
.toPath();
try(final ImageOutputStream fileOutputStream = ImageIO.createImageOutputStream(baseTilePath.toFile()))
{
ImageIO.write(tileImage, "png", baseTilePath.toFile());
}
return baseTilePath;
}
private BufferedImage createTransparentImage()
{
final int tileWidth = RawImageTileReader.this.tileSize.getWidth();
final int tileHeight = RawImageTileReader.this.tileSize.getHeight();
final BufferedImage transparentImage = new BufferedImage(tileWidth,
tileHeight,
BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphics = transparentImage.createGraphics();
graphics.setComposite(AlphaComposite.Clear);
graphics.fillRect(0, 0, tileWidth, tileHeight);
graphics.dispose();
// Return the transparent tile
return transparentImage;
}
private BufferedImage generateScaledTileFromChildren() throws TileStoreException
{
final int tileWidth = RawImageTileReader.this.tileSize.getWidth();
final int tileHeight = RawImageTileReader.this.tileSize.getHeight();
// Create the full-sized buffered image from the child tiles
final BufferedImage fullCanvas = new BufferedImage(tileWidth * 2,
tileHeight * 2,
BufferedImage.TYPE_INT_ARGB);
// Create the full-sized graphics object
final Graphics2D fullCanvasGraphics = fullCanvas.createGraphics();
// Get child handles
final List children = this.generateTileChildren();
// Get the cached children of this tile
final List transformedChildren = children.stream().map(tileHandle ->
{
final Coordinate resultCoordinate = RawImageTileReader.Origin.transform(TileOrigin.UpperLeft,
tileHandle.column,
tileHandle.row,
this.matrix);
try
{
return new RawImageTileHandle(tileHandle.zoomLevel,
resultCoordinate.getX(),
resultCoordinate.getY(),
tileHandle.cachedImageLocation);
} catch(TileStoreException e)
{
throw new RuntimeException(e);
}
})
.sorted((final TileHandle o1, final TileHandle o2) -> {
final int columnCompare = Integer.compare(o1.getColumn(), o2.getColumn());
final int rowCompare = Integer.compare(o1.getRow(), o2.getRow());
// column values are the same
if(columnCompare == 0)
{
return rowCompare;
}
if(rowCompare == 0)
{
return columnCompare;
}
// TODO: Duplicate tile case?
return 0;
})
.collect(Collectors.toList());
if(transformedChildren.size() == 4)
{
try
{
// Origin tile
if(transformedChildren.get(2).cachedImageLocation != null)
{
final BufferedImage originImage = ImageIO.read(transformedChildren.get(2).cachedImageLocation.toFile());
fullCanvasGraphics.drawImage(originImage, null, 0, 0);
}
// Tile that is Y+1 in relation to the origin
if(transformedChildren.get(0).cachedImageLocation != null)
{
final BufferedImage rowShiftedImage = ImageIO.read(transformedChildren.get(0).cachedImageLocation.toFile());
fullCanvasGraphics.drawImage(rowShiftedImage, null, 0, tileHeight);
}
// Tile that is X+1 in relation to the origin
if(transformedChildren.get(3).cachedImageLocation != null)
{
final BufferedImage columnShiftedImage = ImageIO.read(transformedChildren.get(3).cachedImageLocation.toFile());
fullCanvasGraphics.drawImage(columnShiftedImage, null, tileWidth, 0);
}
// Tile that is both X+1 and Y+1 in relation to the origin
if(transformedChildren.get(1).cachedImageLocation != null)
{
final BufferedImage bothShiftedImage = ImageIO.read(transformedChildren.get(1).cachedImageLocation.toFile());
fullCanvasGraphics.drawImage(bothShiftedImage, null, tileWidth, tileHeight);
}
}
catch(final IOException ex)
{
throw new TileStoreException(ex);
}
}
else
{
throw new RuntimeException("Cannot create tile from child tiles.");
}
// Write cached tile
try
{
final BufferedImage scaledTile = this.scaleToTileCanvas(fullCanvas);
RawImageTileReader.this.cachedTiles.put(this.tileKey(this.zoomLevel, this.column, this.row), this.writeTempTile(scaledTile));
return scaledTile;
}
catch(final IOException ex)
{
throw new TileStoreException(ex);
}
finally
{
// Clean-up step
fullCanvasGraphics.dispose();
children.stream().filter(Objects::nonNull).forEach(child ->
{
if(child.cachedImageLocation != null)
{
child.cachedImageLocation.toFile().delete();
}
RawImageTileReader.this.cachedTiles.remove(this.tileKey(child.zoomLevel, child.column, child.row));
});
}
}
private BufferedImage scaleToTileCanvas(final BufferedImage fullCanvas)
{
final BufferedImage tileCanvas = new BufferedImage(RawImageTileReader.this.tileSize.getWidth(),
RawImageTileReader.this.tileSize.getHeight(),
BufferedImage.TYPE_INT_ARGB);
final AffineTransform affineTransform = new AffineTransform();
affineTransform.scale(AFFINE_SCALE, AFFINE_SCALE);
final BufferedImageOp scaleOp = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR);
return scaleOp.filter(fullCanvas, tileCanvas);
}
private String tileKey(final int zoom, final int column, final int row)
{
return String.format("%d/%d/%d", zoom, column, row);
}
private List generateTileChildren() throws TileStoreException
{
final int childZoom = this.zoomLevel + 1;
final int childColumn = this.column * 2;
final int childRow = this.row * 2;
final Path origin = RawImageTileReader.this.cachedTiles.get(this.tileKey(childZoom, childColumn, childRow));
final Path columnShifted = RawImageTileReader.this.cachedTiles.get(this.tileKey(childZoom, childColumn + 1, childRow));
final Path rowShifted = RawImageTileReader.this.cachedTiles.get(this.tileKey(childZoom, childColumn, childRow + 1));
final Path bothShifted = RawImageTileReader.this.cachedTiles.get(this.tileKey(childZoom, childColumn + 1, childRow + 1));
final List children = new ArrayList<>();
children.add(new RawImageTileReader.RawImageTileHandle(childZoom, childColumn, childRow, origin));
children.add(new RawImageTileReader.RawImageTileHandle(childZoom, childColumn + 1, childRow, columnShifted));
children.add(new RawImageTileReader.RawImageTileHandle(childZoom, childColumn, childRow + 1, rowShifted));
children.add(new RawImageTileReader.RawImageTileHandle(childZoom, childColumn + 1, childRow + 1, bothShifted));
return children;
}
@Override
public String toString()
{
return this.tileKey(this.zoomLevel, this.column, this.row);
}
}
private CrsCoordinate tileToCrsCoordinate(final int column,
final int row,
final TileMatrixDimensions tileMatrixDimensions,
final TileOrigin corner)
{
if(corner == null)
{
throw new IllegalArgumentException("Corner may not be null");
}
return this.profile.tileToCrsCoordinate(column + corner.getHorizontal(),
row + corner.getVertical(),
this.profile.getBounds(), // RawImageTileReader uses absolute tiling, which covers the whole globe
tileMatrixDimensions,
RawImageTileReader.Origin);
}
private BoundingBox getTileBoundingBox(final int column,
final int row,
final TileMatrixDimensions tileMatrixDimensions)
{
return this.profile.getTileBounds(column,
row,
this.profile.getBounds(),
tileMatrixDimensions,
RawImageTileReader.Origin);
}
private final File rawImage;
private final CoordinateReferenceSystem coordinateReferenceSystem;
private final Dimensions tileSize;
//private final Color noDataColor; // TODO implement no-data color handling
private final Dataset dataset;
private final BoundingBox dataBounds;
private final Set zoomLevels;
private final ZoomTimesTwo tileScheme;
private final CrsProfile profile;
private final int tileCount;
private final Map>> tileRanges;
private final Map cachedTiles;
private static final int MAX_ZOOM_LEVEL = 31;
private static final double AFFINE_SCALE = 0.5;
private static final String NOT_SUPPORTED_MESSAGE = "Call to unsupported method.";
private static final TileOrigin Origin = TileOrigin.LowerLeft;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy