msi.gama.common.geometry.GamaCoordinateSequence.java Source code

Java tutorial

Introduction

Here is the source code for msi.gama.common.geometry.GamaCoordinateSequence.java

Source

/*******************************************************************************************************
 *
 * msi.gama.common.geometry.GamaCoordinateSequence.java, in plugin msi.gama.core, is part of the source code of the GAMA
 * modeling and simulation platform (v. 1.8)
 * 
 * (c) 2007-2018 UMI 209 UMMISCO IRD/SU & Partners
 *
 * Visit https://github.com/gama-platform/gama for license information and contacts.
 * 
 ********************************************************************************************************/
package msi.gama.common.geometry;

import static com.google.common.collect.Iterators.forArray;
import static com.vividsolutions.jts.algorithm.CGAlgorithms.signedArea;
import static msi.gama.common.geometry.GamaGeometryFactory.isRing;

import java.util.Arrays;
import java.util.Iterator;

import org.apache.commons.lang.ArrayUtils;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;

import msi.gama.metamodel.shape.GamaPoint;

/**
 * Clockwise sequence of points. Supports several computations (rotation, etc.) and a cheap visitor pattern. Be aware
 * that CW property is not maintained if individual points are modified via the setOrdinate() or replaceWith() method
 * and if the sequence is not a ring. All other methods should however maintain it.
 * 
 * @author A. Drogoul
 *
 */

public class GamaCoordinateSequence implements ICoordinates {

    /**
     * The final array of GamaPoint, considered to be internally mutable (i.e. points can be changed inside)
     */
    final GamaPoint[] points;

    /**
     * Creates a sequence from an array of points. The points will be cloned before being added (to prevent side
     * effects). The order of the points will not necessarily remain the same if the sequence is a ring (as this class
     * enforces a clockwise direction of the sequence)
     * 
     * @param points2
     *            an array of points
     */
    GamaCoordinateSequence(final Coordinate... points2) {
        this(true, points2);
    }

    /**
     * Creates a sequence from an array of points. If copy is true, the points are cloned before being added to the
     * sequence (to prevent side effects, for instance). The sequence will be modified to enforce a clockwise direction
     * if the array represents a ring
     * 
     * @param copy
     *            whether or not to copy the points or to add them directly
     * @param points2
     *            an array of points
     */
    GamaCoordinateSequence(final boolean copy, final Coordinate... points2) {
        if (copy) {
            final int size = points2.length;
            points = new GamaPoint[size];
            for (int i = 0; i < size; i++) {
                points[i] = new GamaPoint(points2[i]);
            }
            ensureClockwiseness();
        } else {
            points = (GamaPoint[]) points2;
        }
    }

    /**
     * Creates a sequence of points with a given size (that may be altered after)
     * 
     * @param size
     *            an int > 0 (negative sizes will be treated as 0)
     */
    GamaCoordinateSequence(final int size) {
        points = new GamaPoint[size < 0 ? 0 : size];
        for (int i = 0; i < size; i++) {
            points[i] = new GamaPoint(0d, 0d, 0d);
        }
    }

    /**
     * Method getDimension(). Always 3 for these sequences
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#getDimension()
     */
    @Override
    public int getDimension() {
        return 3;
    }

    /**
     * Makes a complete copy of this sequence (incl. cloning the points themselves)
     */
    @Override
    public GamaCoordinateSequence clone() {
        return new GamaCoordinateSequence(true, points);
    }

    @Override
    public String toString() {
        return Arrays.toString(points);
    }

    /**
     * Method getCoordinate(). The coordinate is *not* a copy of the original one, so any modification to it will
     * directly affect the sequence of points
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinate(int)
     */
    @Override
    public GamaPoint getCoordinate(final int i) {
        return points[i];
    }

    /**
     * Method getCoordinateCopy()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinateCopy(int)
     */
    @Override
    public GamaPoint getCoordinateCopy(final int i) {
        return new GamaPoint((Coordinate) points[i]);
    }

    /**
     * Method getCoordinate()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinate(int, com.vividsolutions.jts.geom.Coordinate)
     */
    @Override
    public void getCoordinate(final int index, final Coordinate coord) {
        coord.setCoordinate(points[index]);
    }

    /**
     * Method getX()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#getX(int)
     */
    @Override
    public double getX(final int index) {
        return points[index].x;
    }

    /**
     * Method getY()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#getY(int)
     */
    @Override
    public double getY(final int index) {
        return points[index].y;
    }

    /**
     * Method getOrdinate()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#getOrdinate(int, int)
     */
    @Override
    public double getOrdinate(final int index, final int ordinateIndex) {
        return points[index].getOrdinate(ordinateIndex);
    }

    /**
     * Method size()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#size()
     */
    @Override
    public int size() {
        return points.length;
    }

    /**
     * Method setOrdinate(). Be aware that CW property is not maintained in case of direct modifications like this
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#setOrdinate(int, int, double)
     */
    @Override
    public void setOrdinate(final int index, final int ordinateIndex, final double value) {
        points[index].setOrdinate(ordinateIndex, value);
    }

    /**
     * Method toCoordinateArray()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#toCoordinateArray()
     */
    @Override
    public GamaPoint[] toCoordinateArray() {
        return points;
    }

    /**
     * Method expandEnvelope()
     * 
     * @see com.vividsolutions.jts.geom.CoordinateSequence#expandEnvelope(com.vividsolutions.jts.geom.Envelope)
     */
    @Override
    public Envelope expandEnvelope(final Envelope env) {
        // TODO Create an Envelope3D ??
        for (final GamaPoint p : points) {
            env.expandToInclude(p);
        }
        return env;
    }

    @Override
    public Iterator<GamaPoint> iterator() {
        return forArray(points);
    }

    @Override
    public void addCenterTo(final GamaPoint other) {
        final int size = isRing(points) ? points.length - 1 : points.length;
        double x = 0, y = 0, z = 0;
        for (int i = 0; i < size; i++) {
            final GamaPoint p = points[i];
            x += p.x;
            y += p.y;
            z += p.z;
        }
        x /= size;
        y /= size;
        z /= size;
        other.x += x;
        other.y += y;
        other.z += z;
    }

    /*
     * (non-Javadoc)
     * 
     * @see msi.gama.common.util.ICoordinates#yNegated()
     */
    @Override
    public ICoordinates yNegated() {
        final int size = points.length;
        final GamaPoint[] points2 = new GamaPoint[size];
        for (int i = 0; i < size; i++) {
            // CW property is ensured by reversing the resulting array
            points2[i] = points[size - i - 1].yNegated();
        }
        final GamaCoordinateSequence result = new GamaCoordinateSequence(false, points2);
        return result;
    }

    @Override
    public void visit(final IndexedVisitor v, final int max, final boolean clockwise) {
        final int limit = max < 0 || max > points.length ? points.length : max;
        final boolean reversed = isRing(points) && !clockwise;
        if (reversed) {
            reverseVisit(v, limit);
        } else {
            visit(v, limit);
        }
    }

    private void visit(final IndexedVisitor v, final int max) {
        for (int i = 0; i < max; i++) {
            final GamaPoint p = points[i];
            v.process(i, p.x, p.y, p.z);
        }
    }

    private void reverseVisit(final IndexedVisitor v, final int max) {
        for (int i = max - 1, j = 0; i >= 0; i--, j++) {
            final GamaPoint p = points[i];
            v.process(j, p.x, p.y, p.z);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see msi.gama.common.util.ICoordinates#visitConsecutive(msi.gama.common.util.GamaCoordinateSequence.PairVisitor)
     */
    @Override
    public void visit(final PairVisitor v) {
        for (int i = 0; i < points.length - 1; i++) {
            v.process(points[i], points[i + 1]);
        }
    }

    /**
     * Computes the normal to this sequence of points based on Newell's algorithm, which has proved to be quite robust
     * even with self-intersecting sequences or non-convex polygons. Its downside is that it processes all the points
     * (instead of processing only 3 of them) but robustness has a price ! This algorithm only operates on rings (this
     * is ensured in the code by processing the first point in case the sequence is not a ring).
     * 
     * @param clockwise
     *            whether to obtain the normal facing up (for clockwise sequences) or down.
     * @param factor
     *            the factor to multiply the unit normal vector with
     * @param normal
     *            the returned vector
     */

    @Override
    public void getNormal(final boolean clockwise, final double factor, final GamaPoint normal) {
        normal.setLocation(0, 0, 0);
        if (points.length < 3) {
            return;
        }
        for (int i = 0; i < points.length - 1; i++) {
            final GamaPoint v0 = points[i];
            final GamaPoint v1 = points[i + 1];
            normal.x += (v0.y - v1.y) * (v0.z + v1.z);
            normal.y += (v0.z - v1.z) * (v0.x + v1.x);
            normal.z += (v0.x - v1.x) * (v0.y + v1.y);
        }
        if (!isRing(points)) {
            final GamaPoint v0 = points[0];
            final GamaPoint v1 = points[1];
            normal.x += (v0.y - v1.y) * (v0.z + v1.z);
            normal.y += (v0.z - v1.z) * (v0.x + v1.x);
            normal.z += (v0.x - v1.x) * (v0.y + v1.y);
        }
        final double norm = clockwise ? -normal.norm() : normal.norm();
        normal.divideBy(norm / factor);
    }

    @Override
    public Envelope3D getEnvelopeInto(final Envelope3D envelope) {
        envelope.setToNull();
        expandEnvelope(envelope);
        return envelope;
    }

    @Override
    public double averageZ() {
        double sum = 0d;
        if (points.length == 0) {
            return sum;
        }
        for (final GamaPoint p : points) {
            sum += p.z;
        }
        return sum / points.length;
    }

    @Override
    public ICoordinates setTo(final GamaPoint... points2) {
        final int size = Math.min(points2.length, points.length);
        for (int i = 0; i < size; i++) {
            points[i].setCoordinate(points2[i]);
        }
        ensureClockwiseness();
        return this;
    }

    @Override
    public ICoordinates setTo(final double... points2) {
        final int size = Math.min(points2.length, points.length * 3);
        for (int i = 0; i < size; i += 3) {
            final GamaPoint self = points[i / 3];
            self.x = points2[i];
            self.y = points2[i + 1];
            self.z = points2[i + 2];
        }
        ensureClockwiseness();
        return this;
    }

    @Override
    public GamaPoint directionBetweenLastPointAndOrigin() {
        final GamaPoint result = new GamaPoint();
        final GamaPoint origin = points[0];
        for (int i = points.length - 1; i > 0; i--) {
            if (!points[i].equals(origin)) {
                result.setLocation(points[i]).subtract(origin).normalize();
                return result;
            }
        }
        return result;
    }

    @Override
    public void applyRotation(final Rotation3D rotation) {
        for (final GamaPoint point : points) {
            rotation.applyTo(point);
        }
    }

    @Override
    public void replaceWith(final int i, final double x, final double y, final double z) {
        if (i < 0 || i >= points.length) {
            return;
        }
        points[i].setLocation(x, y, z);

    }

    @Override
    public boolean isHorizontal() {
        final double z = points[0].z;
        for (int i = 1; i < points.length; i++) {
            if (points[i].z != z) {
                return false;
            }
        }
        return true;
    }

    @Override
    public double getLength() {
        double result = 0;
        for (int i = 1; i < points.length; i++) {
            result += points[i].euclidianDistanceTo(points[i - 1]);
        }
        return result;
    }

    @Override
    public void setAllZ(final double elevation) {
        for (int i = 0; i < points.length; i++) {
            points[i].z = elevation;
        }

    }

    @Override
    public boolean isCoveredBy(final Envelope3D env) {
        for (int i = 0; i < points.length; i++) {
            if (!env.covers(points[i])) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void visitClockwise(final VertexVisitor v) {
        final int max = isRing(points) ? points.length - 1 : points.length;
        for (int i = 0; i < max; i++) {
            final GamaPoint p = points[i];
            v.process(p.x, p.y, p.z);
        }

    }

    /**
     * Same as clockwise, since it visits the coordinates with y-negated
     */
    @Override
    public void visitYNegatedCounterClockwise(final VertexVisitor v) {
        final int max = isRing(points) ? points.length - 1 : points.length;
        for (int i = 0; i < max; i++) {
            final GamaPoint p = points[i];
            v.process(p.x, -p.y, p.z);
        }

    }

    @Override
    public boolean isClockwise() {
        return signedArea(points) > 0;
    }

    @Override
    public void completeRing() {
        points[points.length - 1] = points[0];
    }

    @Override
    public void translateBy(final double x, final double y, final double z) {
        for (final GamaPoint p : points) {
            p.add(x, y, z);
        }

    }

    /**
     * Turns this sequence of coordinates into a clockwise orientation. Only done for rings (as it may change the
     * definition of line strings)
     * 
     * @param points
     * @return
     */
    @Override
    public void ensureClockwiseness() {
        if (!isRing(points)) {
            return;
        }
        if (signedArea(points) <= 0) {
            ArrayUtils.reverse(points);
        }
    }

    @Override
    public boolean equals(final Object object) {
        if (!(object instanceof GamaCoordinateSequence)) {
            return false;
        }
        final GamaCoordinateSequence other = (GamaCoordinateSequence) object;
        return Arrays.equals(points, other.points);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(points);
    }

}