Package mil.nga.giat.geowave.vector.query.row

Source Code of mil.nga.giat.geowave.vector.query.row.BasicRowIdStore

package mil.nga.giat.geowave.vector.query.row;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import mil.nga.giat.geowave.index.ByteArrayId;
import mil.nga.giat.geowave.index.NumericIndexStrategy;
import mil.nga.giat.geowave.store.GeometryUtils;
import mil.nga.giat.geowave.vector.plugin.GeoWaveGTDataStore;

import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Range;
import org.apache.hadoop.io.Text;
import org.apache.log4j.Logger;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.RendererUtilities;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

/**
* This encapsulates mappings of pixels on a spatially referenced image to/from
* row IDs. It can be given a pixelSize to track larger grids of pixels for
* performance purposes rather than tracking each individual pixel (eg. a
* pixelSize of 4 would break the image into a grid of 4x4 pixel cells and map
* row IDs to/from the larger cells). Also it maintains the painted ranges of
* Accumulo row IDs which is important to determine if the current row has been
* marked as painted and should be skipped. It does mark any row IDs outside of
* the image's ReferencedEnvelope as painted.
*
*/
public class BasicRowIdStore
{
  private static final Logger LOGGER = Logger.getLogger(BasicRowIdStore.class);
  private final int height;
  private final Map<Integer, Key> pixelToRowIdMap = new HashMap<Integer, Key>();
  private final TreeMap<Key, Set<Integer>> rowIdToPixelsMap = new TreeMap<Key, Set<Integer>>();
  private List<Range> paintedPixelRanges = new ArrayList<Range>();
  private final Integer pixelSize;
  protected Key currentRow;
  protected Key lastRow;

  public BasicRowIdStore(
      final int width,
      final int height,
      final NumericIndexStrategy indexStrategy,
      final ReferencedEnvelope env,
      final Integer pixelSize )
      throws NoninvertibleTransformException {
    this.height = height;
    this.pixelSize = (pixelSize != null) ? Math.max(
        1,
        pixelSize) : null;
    final AffineTransform worldToScreen = RendererUtilities.worldToScreenTransform(
        env,
        new Rectangle(
            width,
            height));

    MathTransform worldToData = null;
    try {
      worldToData = CRS.findMathTransform(
          env.getCoordinateReferenceSystem(),
          GeoWaveGTDataStore.DEFAULT_CRS);
    }
    catch (final FactoryException e) {
      LOGGER.warn(
          "Unable to transform from map projection to default data projection (EPSG:4326)",
          e);
    }
    int increment = 1;
    if (pixelSize != null) {
      increment = pixelSize;
    }
    for (int x = 0; x < width; x += increment) {
      for (int y = 0; y < height; y += increment) {
        final Key rowIdKey = getMinRowId(
            x,
            y,
            increment,
            indexStrategy,
            worldToScreen,
            worldToData);
        if (rowIdKey == null) {
          LOGGER.warn("Row ID cannot be determined for pixel [x=" + x + ", y=" + y);
          continue;
        }
        final int pixelIndex = getPixelIndex(
            x,
            y);
        pixelToRowIdMap.put(
            pixelIndex,
            rowIdKey);
        Set<Integer> pixels = rowIdToPixelsMap.get(rowIdKey);
        if (pixels == null) {
          pixels = new HashSet<Integer>();
          rowIdToPixelsMap.put(
              rowIdKey,
              pixels);
        }
        pixels.add(pixelIndex);

      }
    }
    // mark all rows prior to the first row ID as "painted"
    paintedPixelRanges.add(new Range(
        null,
        rowIdToPixelsMap.firstKey()));
  }

  protected Key getMinRowId(
      final int x,
      final int y,
      final int yOffset,
      final NumericIndexStrategy indexStrategy,
      final AffineTransform worldToScreen,
      final MathTransform worldToData )
      throws NoninvertibleTransformException,
      MismatchedDimensionException {
    Key minRowId = null;
    // add one to the pixel in height because pixel space starts at
    // the top and goes down so to get the lower left spatial
    // coordinate for a pixel you want the [x, y+1] in pixel space
    final Point2D pixel = new Point2D.Double(
        x,
        y + yOffset);
    final Point2D mapProjPt = worldToScreen.inverseTransform(
        pixel,
        null);
    final DirectPosition2D lonLat = new DirectPosition2D();
    if (worldToData != null) {
      try {
        worldToData.transform(
            new DirectPosition2D(
                mapProjPt),
            lonLat);
      }
      catch (final TransformException e) {
        LOGGER.warn(
            "Unable to transform point from map projection to default data projection (EPSG:4326)",
            e);
      }
    }
    else {
      lonLat.setLocation(mapProjPt);
    }
    final List<ByteArrayId> rowIdsForPixel = indexStrategy.getInsertionIds(GeometryUtils.basicConstraintsFromPoint(
        lonLat.getY(),
        lonLat.getX()).getIndexConstraints(
        indexStrategy));
    // this should be of size one if its a simple spatial index
    if (!rowIdsForPixel.isEmpty()) {
      final ByteArrayId rowId = rowIdsForPixel.get(0);
      final Key rowIdKey = new Key(
          new Text(
              rowId.getBytes()));
      if ((rowIdKey != null) && ((minRowId == null) || (minRowId.compareTo(rowIdKey) > 0))) {
        minRowId = rowIdKey;
      }
    }
    return minRowId;
  }

  protected int getPixelIndex(
      final int x,
      final int y ) {
    if ((pixelSize != null) && (pixelSize != 1)) {
      return ((x / pixelSize) * (int) Math.ceil((double) height / (double) pixelSize)) + (y / pixelSize);
    }
    return (x * height) + y;
  }

  public Key getNextRow(
      final Key currentRow ) {
    final Iterator<Key> rowIdIt = rowIdToPixelsMap.keySet().iterator();
    while (rowIdIt.hasNext()) {
      final Key rowId = rowIdIt.next();
      if (rowId.compareTo(currentRow) < 0) {
        rowIdIt.remove();
      }
      else {
        return rowId;
      }
    }
    return null;
  }

  public boolean isPainted(
      final Key rowId ) {
    for (final Range range : paintedPixelRanges) {
      if (range.contains(rowId)) {
        return true;
      }
    }
    if ((currentRow != null) && ((lastRow == null) || !lastRow.equals(currentRow))) {
      // this case occurs if the pixel has already been decimated, to
      // prevent over sampling when the rows do not match the pixels

      // tracking the last row just is a performance improvement to ensure
      // this doesn't happen many times continuously (typically a single
      // row/feature results in many pixels being painted, we only need to
      // perform this decimation once per row)
      final Set<Integer> pixelIds = rowIdToPixelsMap.get(currentRow);

      lastRow = currentRow;
      if ((pixelIds != null) && (pixelIds.size() > ((pixelSize != null) ? pixelSize : 1))) {
        // this row ID spans multiple pixels, and more pixels than our
        // threshold on pixel size

        // we cannot decimate
        return false;
      }
      // otherwise decimate the range of row IDs spanning the pixel for
      // the current row
      final Key start = rowIdToPixelsMap.floorKey(currentRow);
      final Key stop = rowIdToPixelsMap.higherKey(currentRow);
      final Range decimatedRange = new Range(
          start,
          true,
          stop,
          false);
      incorporatePaintedPixel(decimatedRange);
    }
    return false;
  }

  public void notifyPixelPainted(
      final int x,
      final int y,
      final boolean shouldDecimate ) {
    // pass all final decisions to decimate into this function so that if
    // counts need to be applied, they can be applied here
    if (shouldDecimate) {
      final int pixelIndex = getPixelIndex(
          x,
          y);
      decimate(pixelIndex);
    }
  }

  protected boolean decimate(
      final int pixelIndex ) {
    final Key rowId = pixelToRowIdMap.remove(pixelIndex);
    if (rowId != null) {
      final Set<Integer> pixelIds = rowIdToPixelsMap.get(rowId);
      if (pixelIds != null) {
        pixelIds.remove(pixelIndex);
        if (pixelIds.isEmpty()) {
          rowIdToPixelsMap.remove(rowId);
          // mark all rows between this pixel and the next pixel
          final Range decimatedRange = new Range(
              rowId,
              true,
              rowIdToPixelsMap.higherKey(rowId), // if this is
                                // null it
                                // will
              // represent positive infinite
              false);
          incorporatePaintedPixel(decimatedRange);
          return true;
        }
      }
    }
    return false;
  }

  protected void incorporatePaintedPixel(
      final Range newRange ) {
    paintedPixelRanges.add(newRange);
    paintedPixelRanges = Range.mergeOverlapping(paintedPixelRanges);
  }

  public boolean setCurrentRow(
      final Key currentRow ) {
    this.currentRow = currentRow;
    return true;
  }
}
TOP

Related Classes of mil.nga.giat.geowave.vector.query.row.BasicRowIdStore

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.