Package org.geotools.arcsde.raster.io

Source Code of org.geotools.arcsde.raster.io.NativeTileReader$QueryObjects

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2002-2009, Open Source Geospatial Foundation (OSGeo)
*
*    This library is free software; you can redistribute it and/or
*    modify it under the terms of the GNU Lesser General Public
*    License as published by the Free Software Foundation;
*    version 2.1 of the License.
*
*    This library is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    Lesser General Public License for more details.
*
*/
package org.geotools.arcsde.raster.io;

import java.awt.Dimension;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geotools.arcsde.ArcSdeException;
import org.geotools.arcsde.raster.info.RasterCellType;
import org.geotools.arcsde.raster.info.RasterDatasetInfo;
import org.geotools.arcsde.session.Command;
import org.geotools.arcsde.session.ISession;
import org.geotools.arcsde.session.ISessionPool;
import org.geotools.arcsde.session.UnavailableConnectionException;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.GridEnvelope;

import com.esri.sde.sdk.client.SeConnection;
import com.esri.sde.sdk.client.SeException;
import com.esri.sde.sdk.client.SeQuery;
import com.esri.sde.sdk.client.SeRaster;
import com.esri.sde.sdk.client.SeRasterConstraint;
import com.esri.sde.sdk.client.SeRasterTile;
import com.esri.sde.sdk.client.SeRow;
import com.esri.sde.sdk.client.SeSqlConstruct;

/**
* Offers an iterator like interface to fetch ArcSDE raster tiles.
*
* @author Gabriel Roldan (OpenGeo)
* @since 2.5.4
* @version $Id$
* @source $URL:
*         http://svn.osgeo.org/geotools/trunk/modules/plugin/arcsde/datastore/src/main/java/org
*         /geotools/arcsde/gce/NativeTileReader.java $
*/
@SuppressWarnings({ "nls" })
final class NativeTileReader implements TileReader {

    private static final Logger LOGGER = Logging.getLogger("org.geotools.arcsde.gce");

    private final RasterDatasetInfo rasterInfo;

    private final long rasterId;

    private final int pyramidLevel;

    private final GridEnvelope requestedTiles;

    private final ISessionPool sessionPool;

    private ISession session;

    private boolean started;

    private final int pixelsPerTile;

    private final int tileDataLength;

    private int bitsPerSample;

    private final RasterCellType nativeCellType;

    private QueryObjects queryObjects;

    private final TileDataFetcher dataFetcher;

    /**
     * @see DefaultTiledRasterReader#nextRaster()
     */
    private static class QueryRasterCommand extends Command<Void> {

        private SeQuery preparedQuery;

        private SeRow row;

        private final SeRasterConstraint rasterConstraint;

        private final String rasterColumn;

        private final String rasterTable;

        private final long rasterId;

        /**
         *
         * @param rConstraint
         *            indicates which bands, pyramid level and grid envelope to query
         * @param rasterTable
         *            indicates which raster table to query
         * @param rasterColumn
         *            indicates what raster column in the raster table to query
         * @param rasterId
         *            indicates which raster in the raster catalog to query
         */
        public QueryRasterCommand(final SeRasterConstraint rConstraint, final String rasterTable,
                final String rasterColumn, final long rasterId) {
            this.rasterConstraint = rConstraint;
            this.rasterTable = rasterTable;
            this.rasterColumn = rasterColumn;
            this.rasterId = rasterId;
        }

        @Override
        public Void execute(ISession session, SeConnection connection) throws SeException,
                IOException {

            final SeSqlConstruct sqlConstruct = new SeSqlConstruct(rasterTable);
            /*
             * Filter by the given raster id
             */
            final String rasterIdFilter = rasterColumn + " = " + rasterId;
            sqlConstruct.setWhere(rasterIdFilter);

            final String[] rasterColumns = { rasterColumn };
            preparedQuery = new SeQuery(connection, rasterColumns, sqlConstruct);
            preparedQuery.prepareQuery();
            preparedQuery.execute();

            this.row = preparedQuery.fetch();
            if (row == null) {
                return null;
            }

            preparedQuery.queryRasterTile(rasterConstraint);

            return null;
        }

        public SeQuery getPreparedQuery() {
            return preparedQuery;
        }

        public SeRow getSeRow() {
            return row;
        }
    }

    /**
     * Creates a {@link TileReader} that reads tiles out of ArcSDE for the given
     * {@code preparedQuery} and {@code SeRow} using the given {@code session}, in the native raster
     * format.
     * <p>
     * As for any object that receives a {@link ISession session}, the same rule applies: this class
     * is not responsible of {@link ISession#dispose() disposing} the session, but the calling code
     * is.
     * </p>
     *
     * @param preparedQuery
     *            the query stream to close when done
     * @param row
     * @param imageDimensions
     *            the image size, x and y are the offsets, width and height the actual width and
     *            height, used to ignore incomming pixel data as appropriate to fit the image
     *            dimensions
     * @param bitsPerSample
     * @param numberOfBands2
     * @param tileRange
     */
    NativeTileReader(final ISessionPool sessionPool, final RasterDatasetInfo rasterInfo,
            final long rasterId, final int pyramidLevel, final GridEnvelope tileRange) {
        this.sessionPool = sessionPool;
        this.rasterInfo = rasterInfo;
        this.rasterId = rasterId;
        this.pyramidLevel = pyramidLevel;
        this.requestedTiles = tileRange;

        final Dimension tileSize = rasterInfo.getTileDimension(rasterId);

        this.pixelsPerTile = tileSize.width * tileSize.height;

        this.nativeCellType = rasterInfo.getNativeCellType();
        this.bitsPerSample = nativeCellType.getBitsPerSample();
        this.tileDataLength = (int) Math
                .ceil(((double) pixelsPerTile * (double) bitsPerSample) / 8D);

        final RasterCellType targetCellType = rasterInfo.getTargetCellType(rasterId);
        this.dataFetcher = TileDataFetcher.getTileDataFetcher(this.nativeCellType, targetCellType);

        int rasterIndex = rasterInfo.getRasterIndex(rasterId);
        int maxTileX = rasterInfo.getNumTilesWide(rasterIndex, pyramidLevel) - 1;
        int maxTileY = rasterInfo.getNumTilesHigh(rasterIndex, pyramidLevel) - 1;

        if (tileRange.getLow(0) < 0 || tileRange.getLow(1) < 0 || tileRange.getHigh(0) > maxTileX
                || tileRange.getHigh(1) > maxTileY) {
            throw new IllegalArgumentException("Invalid tile range for raster #" + rasterId + "/"
                    + pyramidLevel + ": " + tileRange + ". Valid range is 0.." + maxTileX + " 0.."
                    + maxTileY);
        }
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getBitsPerSample()
     */
    public int getBitsPerSample() {
        return bitsPerSample;
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getPixelsPerTile()
     */
    public int getPixelsPerTile() {
        return pixelsPerTile;
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getNumberOfBands()
     */
    public int getNumberOfBands() {
        return rasterInfo.getNumBands();
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTileWidth()
     */
    public int getTileWidth() {
        return rasterInfo.getTileWidth(rasterId);
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTileHeight()
     */
    public int getTileHeight() {
        return rasterInfo.getTileHeight(rasterId);
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTilesWide()
     */
    public int getTilesWide() {
        return requestedTiles.getSpan(0);
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTilesHigh()
     */
    public int getTilesHigh() {
        return requestedTiles.getSpan(1);
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getBytesPerTile()
     */
    public int getBytesPerTile() {
        return tileDataLength;
    }

    /**
     * Creates and executes the {@link SeQuery} that's used to fetch the required tiles from the
     * specified raster, and stores (as member variables) the {@link SeRow} to fetch the tiles from
     * and the {@link SeQuery} to be closed at the TileReader's disposal
     *
     * @throws IOException
     */
    private void execute() throws IOException {
        final GridEnvelope requestedTiles = this.requestedTiles;
        this.queryObjects = execute(requestedTiles);
        this.started = true;
        this.lastTileX = this.lastTileY = -1;
    }

    private QueryObjects execute(final GridEnvelope requestedTiles) throws IOException {

        final int rasterIndex = rasterInfo.getRasterIndex(rasterId);

        /*
         * Create the raster constraint to query the needed tiles out of the specified raster at the
         * given pyramid level
         */
        final SeRasterConstraint rConstraint;
        try {
            final int numberOfBands;
            numberOfBands = rasterInfo.getNumBands();

            int[] bandsToQuery = new int[numberOfBands];
            for (int bandN = 1; bandN <= numberOfBands; bandN++) {
                bandsToQuery[bandN - 1] = bandN;
            }

            int minTileX = requestedTiles.getLow(0);
            int minTileY = requestedTiles.getLow(1);
            int maxTileX = requestedTiles.getHigh(0);
            int maxTileY = requestedTiles.getHigh(1);
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("Requesting tiles [x=" + minTileX + "-" + maxTileX + ", y=" + minTileY
                        + "-" + maxTileY + "] from tile range [x=0-"
                        + (rasterInfo.getNumTilesWide(rasterIndex, pyramidLevel) - 1) + ", y=0-"
                        + (rasterInfo.getNumTilesHigh(rasterIndex, pyramidLevel) - 1) + "]");
            }
            // SDEPoint tileOrigin = rAttr.getTileOrigin();

            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("Tiled image size: " + requestedTiles);
            }

            final int interleaveType = SeRaster.SE_RASTER_INTERLEAVE_BIP;

            rConstraint = new SeRasterConstraint();
            rConstraint.setBands(bandsToQuery);
            rConstraint.setLevel(pyramidLevel);
            rConstraint.setEnvelope(minTileX, minTileY, maxTileX, maxTileY);
            rConstraint.setInterleave(interleaveType);
        } catch (SeException se) {
            throw new ArcSdeException(se);
        }

        /*
         * Obtain the ISession this tile reader will work with until exhausted
         */
        try {
            if (this.session == null) {
                // lets share connections as we're going to do read only operations
                final boolean transactional = false;
                this.session = sessionPool.getSession(transactional);

                if (LOGGER.isLoggable(Level.FINER)) {
                    LOGGER.finer("Using " + session + " to read raster #" + rasterId
                            + " on Thread " + Thread.currentThread().getName() + ". Tile set: "
                            + requestedTiles);
                }
            }
        } catch (UnavailableConnectionException e) {
            // really bad luck..
            throw new RuntimeException(e);
        }

        final String rasterTable = rasterInfo.getRasterTable();
        final String rasterColumn = rasterInfo.getRasterColumns()[0];
        final QueryRasterCommand queryCommand = new QueryRasterCommand(rConstraint, rasterTable,
                rasterColumn, rasterId);

        session.issue(queryCommand);

        final SeRow row = queryCommand.getSeRow();

        final SeQuery preparedQuery = queryCommand.getPreparedQuery();

        return new QueryObjects(preparedQuery, row);
    }

    private static class QueryObjects {
        SeQuery preparedQuery;

        SeRow row;

        public QueryObjects(SeQuery preparedQuery, SeRow row) {
            this.preparedQuery = preparedQuery;
            this.row = row;
        }
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTile(int, int, byte[][])
     */
    public void getTile(final int tileX, final int tileY, byte[][] data) throws IOException {
        assert data == null || data.length == getNumberOfBands();

        final int numberOfBands = getNumberOfBands();

        TileInfo[] tileInfo = getTileInfo();
        TileInfo t;
        for (int b = 0; b < numberOfBands; b++) {
            t = tileInfo[b];// new TileInfo(getPixelsPerTile());
            t.setTileData(data[b]);
            tileInfo[b] = t;
        }

        getTile(tileX, tileY, tileInfo);

    }

    TileInfo[] tileInfo;

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTile(int, int, short[][])
     */
    public void getTile(int tileX, int tileY, short[][] data) throws IOException {
        assert data == null || data.length == getNumberOfBands();

        final int numberOfBands = getNumberOfBands();

        TileInfo[] tileInfo = getTileInfo();
        TileInfo t;
        for (int b = 0; b < numberOfBands; b++) {
            t = tileInfo[b];// new TileInfo(getPixelsPerTile());
            t.setTileData(data[b]);
            tileInfo[b] = t;
        }

        getTile(tileX, tileY, tileInfo);
    }

    private TileInfo[] getTileInfo() {
        if (tileInfo == null) {
            final int numberOfBands = getNumberOfBands();
            tileInfo = new TileInfo[numberOfBands];
            for (int b = 0; b < numberOfBands; b++) {
                TileInfo t = new TileInfo(getPixelsPerTile());
                tileInfo[b] = t;
            }
        }
        return tileInfo;
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTile(int, int, int[][])
     */
    public void getTile(int tileX, int tileY, int[][] data) throws IOException {
        assert data == null || data.length == getNumberOfBands();
        final int numberOfBands = getNumberOfBands();

        TileInfo[] tileInfo = getTileInfo();
        TileInfo t;
        for (int b = 0; b < numberOfBands; b++) {
            t = tileInfo[b];// new TileInfo(getPixelsPerTile());
            t.setTileData(data[b]);
            tileInfo[b] = t;
        }

        getTile(tileX, tileY, tileInfo);
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTile(int, int, float[][])
     */
    public void getTile(int tileX, int tileY, float[][] data) throws IOException {
        assert data == null || data.length == getNumberOfBands();
        final int numberOfBands = getNumberOfBands();

        TileInfo[] tileInfo = getTileInfo();
        TileInfo t;
        for (int b = 0; b < numberOfBands; b++) {
            t = tileInfo[b];// new TileInfo(getPixelsPerTile());
            t.setTileData(data[b]);
            tileInfo[b] = t;
        }

        getTile(tileX, tileY, tileInfo);

    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getTile(int, int, double[][])
     */
    public void getTile(int tileX, int tileY, double[][] data) throws IOException {
        assert data == null || data.length == getNumberOfBands();
        final int numberOfBands = getNumberOfBands();

        TileInfo[] tileInfo = getTileInfo();
        TileInfo t;
        for (int b = 0; b < numberOfBands; b++) {
            t = tileInfo[b];// new TileInfo(getPixelsPerTile());
            t.setTileData(data[b]);
            tileInfo[b] = t;
        }

        getTile(tileX, tileY, tileInfo);
    }

    private void getTile(final int tileX, final int tileY, TileInfo[] target) throws IOException {
        // System.out.printf("fetchTile %d, %d\n", tileX, tileY);
        try {
            fetchTile(tileX, tileY, target);
        } catch (IOException e) {
            dispose();
            throw e;
        } catch (RuntimeException e) {
            dispose();
            throw (RuntimeException) new RuntimeException("Error geting tile " + tileX + ","
                    + tileY + " on raster " + rasterInfo.getRasterTable() + "#" + this.rasterId)
                    .initCause(e);
        }
    }

    private int lastTileX = -1;

    private int lastTileY = -1;

    /**
     * How many random tiles requested (ie, not consecutive) before re executing the original
     * request as a rewind
     */
    private static final int RANDOM_THRESHOLD = Integer.MAX_VALUE;

    private int nonConsecutiveCallCount;

    private void fetchTile(final int tileX, final int tileY, TileInfo[] target) throws IOException {
        SeRasterTile[] seTile = null;

        if (isConsecutive(tileX, tileY)) {
            while (lastTileX != tileX || lastTileY != tileY) {
                seTile = nextTile();
            }
        } else {
            if (nonConsecutiveCallCount == RANDOM_THRESHOLD) {
                nonConsecutiveCallCount = 0;
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.info("Number of random (non consecutive) tile request exceeded"
                            + " predefined threshold. Rewind by executing original request again");
                }
                dispose();
                fetchTile(tileX, tileY, target);
                return;
            } else {
                nonConsecutiveCallCount++;
                seTile = fetchSingleTile(tileX, tileY);
            }
        }

        if (lastTileX == getMaxTileX() && lastTileY == getMaxTileY()) {
            dispose();
        }

        extractTile(seTile, target);
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getMaxTileX()
     */
    public int getMaxTileX() {
        return requestedTiles.getHigh(0);
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#getMaxTileY()
     */
    public int getMaxTileY() {
        return requestedTiles.getHigh(1);
    }

    /**
     * Executes a separate request to fetch this single tile
     *
     * @throws IOException
     */
    private SeRasterTile[] fetchSingleTile(final int tileX, final int tileY) throws IOException {
        if (LOGGER.isLoggable(Level.INFO)) {
            LOGGER.info("fetchSingleTile raster #" + this.rasterId + ", plevel " + pyramidLevel
                    + ", tile " + tileX + ", " + tileY);
        }
        final int width = 1;
        final int height = 1;
        final GridEnvelope requestTiles = new GridEnvelope2D(tileX, tileY, width, height);

        final QueryObjects singleTileQueryObjects = execute(requestTiles);
        final SeQuery query = singleTileQueryObjects.preparedQuery;
        final SeRow row = singleTileQueryObjects.row;
        final TileFetchCommand command = new TileFetchCommand(row, nativeCellType);
        SeRasterTile[] tileData;

        try {
            tileData = readTile(command);
        } finally {
            session.close(query);
        }

        return tileData;
    }

    /**
     * Determines whether the tile defined by {@code tileX, tileY} is consecutive to the original
     * request, whether it is exactly the next in the stream or any other one that follows the last
     * tile fetched from the original request.
     */
    private boolean isConsecutive(final int tileX, final int tileY) {
        if (tileX > lastTileX && tileY >= lastTileY) {
            return true;
        }
        if (tileX <= lastTileX && tileY > lastTileY) {
            return true;
        }
        return false;
    }

    private TileFetchCommand sequentialFetchCommand;

    private SeRasterTile[] nextTile() throws IOException {
        if (!started) {
            execute();
            SeRow row = queryObjects.row;
            RasterCellType nativeType = nativeCellType;
            sequentialFetchCommand = new TileFetchCommand(row, nativeType);
        }

        SeRasterTile[] tileData = readTile(sequentialFetchCommand);

        if (lastTileX == -1 && lastTileY == -1) {
            lastTileX = getMinTileX();
            lastTileY = getMinTileY();
        } else {
            lastTileX++;
            if (lastTileX > getMaxTileX()) {
                lastTileX = getMinTileX();
                lastTileY++;
            }
        }

        return tileData;
    }

    private SeRasterTile[] readTile(final TileFetchCommand fetchCommand) throws IOException {

        final int numBands = getNumberOfBands();

        SeRasterTile[] tile = new SeRasterTile[numBands];

        SeRasterTile bandTile;
        for (int i = 0; i < numBands; i++) {
            bandTile = session.issue(fetchCommand);
            if (bandTile == null) {
                throw new IllegalStateException("There are no more tiles to fetch");
            }
            tile[i] = bandTile;
        }
        return tile;
    }

    /**
     * @see org.geotools.arcsde.raster.io.TileReader#dispose()
     */
    public void dispose() {
        if (session != null) {
            // System.err.println("TileReader disposing " + session + " on Thread "
            // + Thread.currentThread().getName());
            if (LOGGER.isLoggable(Level.FINER)) {
                LOGGER.finer("TileReader disposing " + session + " on Thread "
                        + Thread.currentThread().getName());
            }
            if (queryObjects != null) {
                try {
                    SeQuery preparedQuery = queryObjects.preparedQuery;
                    session.close(preparedQuery);
                } catch (Exception e) {
                    LOGGER.log(Level.WARNING, "Closing tile reader's prepared Query", e);
                }
            }
            if (LOGGER.isLoggable(Level.FINER)) {
                LOGGER.finer("Disposing " + session + " on thread "
                        + Thread.currentThread().getName());
            }
            session.dispose();
            session = null;

            // get ready for more invocations
            queryObjects = null;
            started = false;
            lastTileX = lastTileY = -1;
        }
    }

    /**
     * Disposes as to make sure the {@link ISession session} is returned to the pool even if a
     * failing or non careful client left this object hanging around
     *
     * @see #dispose()
     * @see java.lang.Object#finalize()
     */
    @Override
    protected void finalize() {
        dispose();
    }

    public int getMinTileX() {
        return requestedTiles.getLow(0);
    }

    public int getMinTileY() {
        return requestedTiles.getLow(1);
    }

    public String getServerName() {
        return sessionPool.getConfig().getServerName();
    }

    public String getRasterTableName() {
        return rasterInfo.getRasterTable();
    }

    public int getPyramidLevel() {
        return pyramidLevel;
    }

    public long getRasterId() {
        return rasterId;
    }

    private void extractTile(final SeRasterTile[] seTile, TileInfo[] target) {
        final int numberOfBands = getNumberOfBands();
        assert numberOfBands == seTile.length;
        assert numberOfBands == target.length;

        SeRasterTile tile;
        TileInfo bandData;
        for (int bandN = 0; bandN < numberOfBands; bandN++) {
            tile = seTile[bandN];

            final byte[] bitMaskData = tile.getBitMaskData();
            final int numPixelsRead = tile.getNumPixels();
            final long bandId = tile.getBandId().longValue();
            final int colIndex = tile.getColumnIndex();
            final int rowIndex = tile.getRowIndex();
            final Number noData = rasterInfo.getNoDataValue(rasterId, bandN);
            bandData = target[bandN];
            bandData.setBandId(bandId);
            bandData.setColumnIndex(colIndex);
            bandData.setRowIndex(rowIndex);
            bandData.setNumPixelsRead(numPixelsRead);
            bandData.setBitmaskData(bitMaskData);
            bandData.setNoDataValue(noData);

            dataFetcher.setTileData(tile, bandData);
        }
    }
}
TOP

Related Classes of org.geotools.arcsde.raster.io.NativeTileReader$QueryObjects

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.