Package org.vfny.geoserver.wms.responses.map.htmlimagemap

Source Code of org.vfny.geoserver.wms.responses.map.htmlimagemap.Decimator

/**
* A modified version of Decimator from Geotools renderer.lite.
*/

package org.vfny.geoserver.wms.responses.map.htmlimagemap;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

import org.geotools.geometry.jts.LiteCoordinateSequence;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

/**
* Accepts geometries and collapses all the vertices that will be rendered to
* the same pixel.
*
* @author jeichar
*/
public final class Decimator {

  private double spanx = -1;

  private double spany = -1;
 
 

  /**
   * djb - noticed that the old way of finding out the decimation is based on
   * the (0,0) location of the image. This is often wildly unrepresentitive of
   * the scale of the entire map.
   *
   * A better thing to do is to decimate this on a per-shape basis (and use
   * the shape's center). Another option would be to sample the image at
   * different locations (say 9) and choose the smallest spanx/spany you find.
   *
   * Also, if the xform is an affine Xform, you can be a bit more aggressive
   * in the decimation. If its not an affine xform (ie. its actually doing a
   * CRS xform), you may find this is a bit too aggressive due to any number
   * of mathematical issues.
   *
   * This is just a simple method that uses the centre of the given rectangle
   * instead of (0,0).
   *
   * NOTE: this could need more work based on CRS, but the rectangle is in
   * pixels so it should be fairly immune to all but crazy projections.
   *
   *
   * @param screenToWorld
   * @param paintArea
   */
  public Decimator(MathTransform screenToWorld, Rectangle paintArea) {
    if (screenToWorld != null) {
      double[] original = new double[] {
          paintArea.x + paintArea.width / 2.0,
          paintArea.y + paintArea.height / 2.0,
          paintArea.x + paintArea.width / 2.0 + 1,
          paintArea.y + paintArea.height / 2.0 + 1, };
      double[] coords = new double[4];
      try {
        screenToWorld.transform(original, 0, coords, 0, 2);
      } catch (TransformException e) {
        return;
      }
      this.spanx = Math.abs(coords[0] - coords[2]) * 0.8;
      // 0.8 is just so you dont decimate "too much". magic
      // number.
      this.spany = Math.abs(coords[1] - coords[3]) * 0.8;
    } else {
      this.spanx = 1;
      this.spany = 1;
    }
  }

  /**
   * @throws TransformException
   * @deprecated use the other constructor (with rectange) see javadox. This
   *             works fine, but it the results are often poor if you're also
   *             doing CRS xforms.
   */
  public Decimator(MathTransform screenToWorld) {
    this(screenToWorld, new Rectangle()); // do at (0,0)
  }

  public final void decimateTransformGeneralize(Geometry geometry,
      MathTransform transform) throws TransformException {
    if (geometry instanceof GeometryCollection) {
      GeometryCollection collection = (GeometryCollection) geometry;
      final int length = collection.getNumGeometries();
      for (int i = 0; i < length; i++) {
        decimateTransformGeneralize(collection.getGeometryN(i),
            transform);
      }
    } else if (geometry instanceof Point) {
      LiteCoordinateSequence seq = (LiteCoordinateSequence) ((Point) geometry)
          .getCoordinateSequence();
      decimateTransformGeneralize(seq, transform);
    } else if (geometry instanceof Polygon) {
      Polygon polygon = (Polygon) geometry;
      decimateTransformGeneralize(polygon.getExteriorRing(), transform);
      final int length = polygon.getNumInteriorRing();
      for (int i = 0; i < length; i++) {
        decimateTransformGeneralize(polygon.getInteriorRingN(i),
            transform);
      }
    } else if (geometry instanceof LineString) {
      LiteCoordinateSequence seq = (LiteCoordinateSequence) ((LineString) geometry)
          .getCoordinateSequence();
      decimateTransformGeneralize(seq, transform);
    }
  }

  private void forceClosed(Coordinate[] coords) {
    if(!coords[0].equals2D(coords[coords.length-1]))
      coords[coords.length-1]=coords[0]
  }
 
  /**
   * decimates JTS geometries.
   */
  public final Geometry decimate(Geometry geom) {
    GeometryFactory gFac=new GeometryFactory(geom.getPrecisionModel(),geom.getSRID());
    if (spanx == -1)
      return geom;
    if (geom instanceof MultiPoint) {
      // TODO check geometry and if its bbox is too small turn it into a 1
      // point geom
      return geom;
    }
    if (geom instanceof GeometryCollection) {
      // TODO check geometry and if its bbox is too small turn it into a
      // 1-2 point geom
      // takes a bit of work because the geometry will need to be
      // recreated.
      GeometryCollection collection = (GeometryCollection) geom;
      Geometry[] result=new Geometry[collection.getDimension()];
      final int numGeometries = collection.getNumGeometries();
      for (int i = 0; i < numGeometries; i++) {
        result[i]=decimate(collection.getGeometryN(i));
      }
      return gFac.createGeometryCollection(result);
     
    } else if (geom instanceof LineString) {
      LineString line = (LineString) geom;
      CoordinateSequence seq = (CoordinateSequence) line
          .getCoordinateSequence();
      LiteCoordinateSequence lseq=new LiteCoordinateSequence(seq.toCoordinateArray());
     
      if (decimateOnEnvelope(line, lseq)) {
        if(lseq.size()>=2)
          return gFac.createLineString(lseq);
      }
      if(lseq.size()>=2)
        return gFac.createLineString(decimate(lseq));
      return null;
    } else if (geom instanceof Polygon) {
      Polygon line = (Polygon) geom;
      Coordinate[] exterior=decimate(line.getExteriorRing()).getCoordinates();
      forceClosed(exterior);
      if(exterior.length>3) {
        LinearRing ring=gFac.createLinearRing(exterior);
             
        final int numRings = line.getNumInteriorRing();       
        List<LinearRing> rings=new ArrayList<LinearRing>();
       
        for (int i = 0; i < numRings; i++) {
          Coordinate[] interior=decimate(line.getInteriorRingN(i)).getCoordinates();
          forceClosed(interior);
          if(interior.length>3)
            rings.add(gFac.createLinearRing(interior));
        }
        return gFac.createPolygon(ring, rings.toArray(new LinearRing[] {}));
      }
      return null;
    }
    return geom;
  }

  /**
   * @param geom
   * @param seq
   */
  private boolean decimateOnEnvelope(Geometry geom, LiteCoordinateSequence seq) {
    Envelope env = geom.getEnvelopeInternal();
    if (env.getWidth() <= spanx && env.getHeight() <= spany) {
      double[] coords = seq.getArray();
      int dim = seq.getDimension();
      double[] newcoords = new double[dim * 2];
      for (int i = 0; i < dim; i++) {
        newcoords[i] = coords[i];
        newcoords[dim + i] = coords[coords.length - dim + i];
      }
      seq.setArray(coords);
      return true;
    }
    return false;
  }

  /**
   * 1. remove any points that are within the spanx,spany. We ALWAYS keep 1st
   * and last point 2. transform to screen coordinates 3. remove any points
   * that are close (span <1)
   *
   * @param seq
   * @param tranform
   */
  private final void decimateTransformGeneralize(LiteCoordinateSequence seq,
      MathTransform transform) throws TransformException {
    // decimates before XFORM
    int ncoords = seq.size();
    double originalOrds[] = seq.getXYArray(); // 2*#of points

    if (ncoords < 2) {
      if (ncoords == 1) // 1 coordinate -- just xform it
      {
        double[] newCoordsXformed2 = new double[2];
        transform.transform(originalOrds, 0, newCoordsXformed2, 0, 1);
        seq.setArray(newCoordsXformed2);
        return;
      } else
        return; // ncoords =0
    }

    // unfortunately, we have to keep things in double precion until after
    // the transform or we could move things.
    double[] allCoords = new double[ncoords * 2]; // preallocate -- might
    // not be full (throw
    // away Z)

    allCoords[0] = originalOrds[0]; // allways have 1st one
    allCoords[1] = originalOrds[1];

    int actualCoords = 1;
    double lastX = allCoords[0];
    double lastY = allCoords[1];
    for (int t = 1; t < (ncoords - 1); t++) {
      // see if this one should be added
      double x = originalOrds[t * 2];
      double y = originalOrds[t * 2 + 1];
      if ((Math.abs(x - lastX) > spanx) || (Math.abs(y - lastY)) > spany) {
        allCoords[actualCoords * 2] = x;
        allCoords[actualCoords * 2 + 1] = y;
        lastX = x;
        lastY = y;
        actualCoords++;
      }
    }
    allCoords[actualCoords * 2] = originalOrds[(ncoords - 1) * 2];
    // always have last one
    allCoords[actualCoords * 2 + 1] = originalOrds[(ncoords - 1) * 2 + 1];
    actualCoords++;

    double[] newCoordsXformed;
    // DO THE XFORM
    if ((transform == null) || (transform.isIdentity())) // no actual
    // xform
    {
      newCoordsXformed = allCoords;
    } else {
      newCoordsXformed = new double[actualCoords * 2];
      transform
          .transform(allCoords, 0, newCoordsXformed, 0, actualCoords);
    }

    // GENERALIZE -- we should be in screen space so spanx=spany=1.0

    // unfortunately, we have to keep things in double precion until after
    // the transform or we could move things.
    double[] finalCoords = new double[ncoords * 2]; // preallocate -- might
    // not be full (throw
    // away Z)

    finalCoords[0] = newCoordsXformed[0]; // allways have 1st one
    finalCoords[1] = newCoordsXformed[1];

    int actualCoordsGen = 1;
    lastX = newCoordsXformed[0];
    lastY = newCoordsXformed[1];

    for (int t = 1; t < (actualCoords - 1); t++) {
      // see if this one should be added
      double x = newCoordsXformed[t * 2];
      double y = newCoordsXformed[t * 2 + 1];
      if ((Math.abs(x - lastX) > 0.75) || (Math.abs(y - lastY)) > 0.75) // 0.75
      // instead of 1 just because it tends to look nicer for slightly
      // more work. magic number.
      {
        finalCoords[actualCoordsGen * 2] = x;
        finalCoords[actualCoordsGen * 2 + 1] = y;
        lastX = x;
        lastY = y;
        actualCoordsGen++;
      }
    }
    finalCoords[actualCoordsGen * 2] = newCoordsXformed[(actualCoords - 1) * 2];
    // always have last one
    finalCoords[actualCoordsGen * 2 + 1] = newCoordsXformed[(actualCoords - 1) * 2 + 1];
    actualCoordsGen++;

    // stick back in
    double[] seqDouble = new double[2 * actualCoordsGen];
    System.arraycopy(finalCoords, 0, seqDouble, 0, actualCoordsGen * 2);
    seq.setArray(seqDouble);
  }

  private CoordinateSequence decimate(LiteCoordinateSequence seq) {
    double[] coords = seq.getArray();
    int numDoubles = coords.length;
    int dim = seq.getDimension();
    int readDoubles = 0;
    double prevx, currx, prevy, curry, diffx, diffy;
    for (int currentDoubles = 0; currentDoubles < numDoubles; currentDoubles += dim) {
      if (currentDoubles >= dim && currentDoubles < numDoubles - 1) {
        prevx = coords[readDoubles - dim];
        currx = coords[currentDoubles];
        diffx = Math.abs(prevx - currx);
        prevy = coords[readDoubles - dim + 1];
        curry = coords[currentDoubles + 1];
        diffy = Math.abs(prevy - curry);
        if (diffx > spanx || diffy > spany) {
          readDoubles = copyCoordinate(coords, dim, readDoubles,
              currentDoubles);
        }
      } else {
        readDoubles = copyCoordinate(coords, dim, readDoubles,
            currentDoubles);
      }
    }
    double[] newCoords = new double[readDoubles];
    System.arraycopy(coords, 0, newCoords, 0, readDoubles);
    seq.setArray(newCoords);
    return seq;
  }

  /**
   * @param coords
   * @param dimension
   * @param readDoubles
   * @param currentDoubles
   */
  private int copyCoordinate(double[] coords, int dimension, int readDoubles,
      int currentDoubles) {
    for (int i = 0; i < dimension; i++) {
      coords[readDoubles + i] = coords[currentDoubles + i];
    }
    readDoubles += dimension;
    return readDoubles;
  }
}



TOP

Related Classes of org.vfny.geoserver.wms.responses.map.htmlimagemap.Decimator

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.