Rev 139 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*** $Id$** This file is part of Hibernate Spatial, an extension to the* hibernate ORM solution for geographic data.** Copyright © 2007 Geovise BVBA* Copyright © 2007 K.U. Leuven LRD, Spatial Applications Division, Belgium** This work was partially supported by the European Commission,* under the 6th Framework Programme, contract IST-2-004688-STP.** 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; either* version 2.1 of the License, or (at your option) any later version.** 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.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the Free Software* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA** For more information, visit: http://www.hibernatespatial.org/*/package org.hibernatespatial.mgeom;import com.vividsolutions.jts.geom.*;import org.hibernatespatial.mgeom.MCoordinate;import java.util.*;/*** An implementation of the LineString class with the addition that the* containing CoordinateSequence can carry measure. Note that this is not a* strict requirement of the class, and can interact with non-measure geometries* for JTS topological comparisons regardless.** @author Karel Maesen*/@Deprecatedpublic class MLineString extends LineString implements MGeometry {/****/private static final long serialVersionUID = 1L;private boolean monotone = false;private boolean strictMonotone = false;public MLineString(CoordinateSequence points, GeometryFactory factory) {super(points, factory);determineMonotone();}public Object clone() {LineString ls = (LineString) super.clone();return new MLineString(ls.getCoordinateSequence(), this.getFactory());}/*** Calculates whether the measures in the CoordinateSequence are monotone* and strict monotone. The strict parameter indicates whether the* determination should apply the definition of "strict monotonicity" or* non-strict.*/private void determineMonotone() {this.monotone = true;this.strictMonotone = true;if (!this.isEmpty()) {double m[] = this.getMeasures();// short circuit if the first value is NaNif (Double.isNaN(m[0])) {this.monotone = false;this.strictMonotone = false;} else {int result = 0;int prevResult = 0;for (int i = 1; i < m.length && this.monotone; i++) {result = Double.compare(m[i - 1], m[i]);this.monotone = !(result * prevResult < 0 || Double.isNaN(m[i]));this.strictMonotone &= this.monotone && result != 0;prevResult = result;}}}// if not monotone, then certainly not strictly monotoneassert (!(this.strictMonotone && !this.monotone));}protected void geometryChangedAction() {determineMonotone();}/*** @param co* input coordinate in the neighbourhood of the MLineString* @param tolerance* max. distance that co may be from this MLineString* @return an MCoordinate on this MLineString with appropriate M-value*/public MCoordinate getClosestPoint(Coordinate co, double tolerance)throws MGeometryException {if (!this.isMonotone(false)) {throw new MGeometryException(MGeometryException.OPERATION_REQUIRES_MONOTONE);}if (!this.isEmpty()) {LineSegment seg = new LineSegment();Coordinate[] coAr = this.getCoordinates();seg.p0 = coAr[0];double d = 0.0;double projfact = 0.0;double minDist = Double.POSITIVE_INFINITY;MCoordinate mincp = null;for (int i = 1; i < coAr.length; i++) {seg.p1 = coAr[i];Coordinate cp = seg.closestPoint(co);d = cp.distance(co);if (d <= tolerance && d <= minDist) {MCoordinate testcp = new MCoordinate(cp);projfact = seg.projectionFactor(cp);testcp.m = ((MCoordinate) coAr[i - 1]).m+ projfact* (((MCoordinate) coAr[i]).m - ((MCoordinate) coAr[i - 1]).m);if (d < minDist || testcp.m < mincp.m) {mincp = testcp;minDist = d;}}seg.p0 = seg.p1;}if (minDist > tolerance) {return null;} else {return mincp;}} else {return null;}}/** (non-Javadoc)** @see org.hibernatespatial.mgeom.MGeometry#getCoordinateAtM(double)*/public Coordinate getCoordinateAtM(double m) throws MGeometryException {if (!this.isMonotone(false)) {throw new MGeometryException(MGeometryException.OPERATION_REQUIRES_MONOTONE);}if (this.isEmpty()) {return null;} else {double mval[] = this.getMeasures();double lb = getMinM();double up = getMaxM();if (m < lb || m > up) {return null;} else {// determine linesegment that contains m;for (int i = 1; i < mval.length; i++) {if ((mval[i - 1] <= m && m <= mval[i])|| (mval[i] <= m && m <= mval[i - 1])) {MCoordinate p0 = (MCoordinate) this.getCoordinateN(i - 1);MCoordinate p1 = (MCoordinate) this.getCoordinateN(i);// r indicates how far in this segment the M-values liesdouble r = (m - mval[i - 1]) / (mval[i] - mval[i - 1]);double dx = r * (p1.x - p0.x);double dy = r * (p1.y - p0.y);double dz = r * (p1.z - p0.z);MCoordinate nc = new MCoordinate(p0.x + dx, p0.y + dy,p0.z + dz, m);return nc;}}}}return null;}/** (non-Javadoc)** @see com.vividsolutions.jts.geom.Geometry#getGeometryType()*/public String getGeometryType() {return "MLineString";}/** (non-Javadoc)** @see com.vividsolutions.jts.geom.Geometry#getMeasureatCoordinate(com.vividsolutions.jts.geom.Coordinate,* double)*/public double getMatCoordinate(Coordinate c, double tolerance)throws MGeometryException {MCoordinate mco = this.getClosestPoint(c, tolerance);if (mco == null) {return Double.NaN;} else {return (mco.m);}}/*** get the measure of the specified coordinate** @param n* index of the coordinate* @return The measure of the coordinate. If the coordinate does not exists* it returns Double.NaN*/public double getMatN(int n) {return ((MCoordinate) (this.getCoordinates()[n])).m;}/** (non-Javadoc)** @see org.hibernatespatial.mgeom.MGeometry##MGeometry#getMaxM()*/public double getMaxM() {if (this.isEmpty()) {return Double.NaN;} else {double[] measures = this.getMeasures();if (this.getMeasureDirection() == MGeometry.INCREASING) {return measures[measures.length - 1];} else if (this.getMeasureDirection() == MGeometry.DECREASING|| this.getMeasureDirection() == MGeometry.CONSTANT) {return measures[0];} else {double ma = Double.NEGATIVE_INFINITY;for (int i = 0; i < measures.length; i++) {if (ma < measures[i]) {ma = measures[i];}}return ma;}}}/*** Copies the coordinates of the specified array that fall between fromM and toM to a CoordinateSubSequence.** The CoordinateSubSequence also contains the array indices of the first and last coordinate in firstIndex, resp.* lastIndex. If there are no coordinates between fromM and toM, then firstIndex will contain -1, and lastIndex* will point to the coordinate that is close to fromM or toM.** This function expects that fromM is less than or equal to toM, and that the coordinates in the array are* sorted monotonic w.r.t. to their m-values.** @param mcoordinates* @param fromM* @param toM* @param direction INCREASING or DECREASING* @return a CoordinateSubSequence containing the coordinates between fromM and toM*/private CoordinateSubSequence copyCoordinatesBetween(MCoordinate[] mcoordinates, double fromM, double toM, int direction){CoordinateSubSequence sseq = new CoordinateSubSequence();sseq.firstIndex = -1;sseq.lastIndex = -1;for (int i = 0; i < mcoordinates.length; i++){double m = mcoordinates[i].m;if (m >= fromM && m <= toM){sseq.vertices.add(mcoordinates[i]);if (sseq.firstIndex == -1 ) {sseq.firstIndex = i;}}if (direction == MGeometry.INCREASING) {if (m > toM) break;sseq.lastIndex = i;} else {if (m < fromM) break;sseq.lastIndex = i;}}return sseq;}/*** Interpolates a coordinate between mco1, mco2, based on the measured value m*/private MCoordinate interpolate(MCoordinate mco1, MCoordinate mco2, double m) {if (mco1.m > mco2.m){MCoordinate h = mco1;mco1 = mco2;mco2 = h;}if (m < mco1.m || m > mco2.m) throw new IllegalArgumentException("Internal Error: m-value not in interval mco1.m/mco2.m");double r = (m - mco1.m) / (mco2.m - mco1.m);MCoordinate interpolated = new MCoordinate(mco1.x + r*(mco2.x - mco1.x),mco1.y + r*(mco2.y - mco1.y),mco1.z + r*(mco2.z - mco1.z),m);this.getPrecisionModel().makePrecise(interpolated);return interpolated;}public CoordinateSequence[] getCoordinatesBetween(double fromM, double toM) throws MGeometryException {if (!this.isMonotone(false)){throw new MGeometryException(MGeometryException.OPERATION_REQUIRES_MONOTONE,"Operation requires geometry with monotonic measures");}if (fromM > toM){return getCoordinatesBetween(toM, fromM);}MCoordinateSequence mc;if (!isOverlapping(fromM, toM)){mc = new MCoordinateSequence(new MCoordinate[]{});} else {MCoordinate[] mcoordinates = (MCoordinate[])this.getCoordinates();CoordinateSubSequence subsequence = copyCoordinatesBetween(mcoordinates, fromM, toM, this.getMeasureDirection());addInterpolatedEndPoints(fromM, toM, mcoordinates, subsequence);MCoordinate[] ra = subsequence.vertices.toArray(new MCoordinate[subsequence.vertices.size()]);mc = new MCoordinateSequence(ra);}return new MCoordinateSequence[] { mc };}private boolean isOverlapping(double fromM, double toM) {//WARNING: this assumes a monotonic increasing or decreasing measuresMCoordinate beginCo = (MCoordinate)this.getCoordinateN(0);MCoordinate endCo = (MCoordinate)this.getCoordinateN(this.getNumPoints()-1);return !(Math.min(fromM, toM) > Math.max(beginCo.m, endCo.m) ||Math.max(fromM, toM) < Math.min(beginCo.m, endCo.m));}private void addInterpolatedEndPoints(double fromM, double toM, MCoordinate[] mcoordinates, CoordinateSubSequence subsequence) {boolean increasing = this.getMeasureDirection() == MGeometry.INCREASING;double fM , lM;if (increasing) {fM =fromM; lM = toM;} else {fM = toM; lM = fromM;}if (subsequence.firstIndex == -1) {MCoordinate fi = interpolate(mcoordinates[subsequence.lastIndex], mcoordinates[subsequence.lastIndex+1],fM);subsequence.vertices.add(fi);MCoordinate li = interpolate(mcoordinates[subsequence.lastIndex], mcoordinates[subsequence.lastIndex+1], lM);subsequence.vertices.add(li);} else {//interpolate a first vertex if necessaryif ( subsequence.firstIndex > 0 && (increasing && mcoordinates[subsequence.firstIndex].m > fromM ||!increasing && mcoordinates[subsequence.firstIndex].m < toM)){MCoordinate fi = interpolate(mcoordinates[subsequence.firstIndex-1], mcoordinates[subsequence.firstIndex],fM);subsequence.vertices.add(0,fi);}//interpolate a last vertex if necessaryif (subsequence.lastIndex < (mcoordinates.length - 1) && (increasing && mcoordinates[subsequence.lastIndex].m < toM ||!increasing && mcoordinates[subsequence.lastIndex].m > fromM)){MCoordinate li = interpolate(mcoordinates[subsequence.lastIndex], mcoordinates[subsequence.lastIndex+1], lM);subsequence.vertices.add(li);}}}private MCoordinate[] inverse(MCoordinate[] mcoordinates) {for (int i = 0; i < mcoordinates.length / 2; i++){MCoordinate h = mcoordinates[i];mcoordinates[i] = mcoordinates[mcoordinates.length - 1 - i];mcoordinates[mcoordinates.length -1 - i] = h;}return mcoordinates;}/*** determine the direction of the measures w.r.t. the direction of the line** @return MGeometry.NON_MONOTONE<BR>* MGeometry.INCREASING<BR>* MGeometry.DECREASING<BR>* MGeometry.CONSTANT*/public int getMeasureDirection() {if (!this.monotone) {return MGeometry.NON_MONOTONE;}MCoordinate c1 = (MCoordinate) this.getCoordinateN(0);MCoordinate c2 = (MCoordinate) this.getCoordinateN(this.getNumPoints() - 1);if (c1.m < c2.m) {return MGeometry.INCREASING;} else if (c1.m > c2.m) {return MGeometry.DECREASING;} else {return MGeometry.CONSTANT;}}/*** @return the array with measure-values of the vertices*/public double[] getMeasures() {// return the measures of all verticesif (!this.isEmpty()) {Coordinate[] co = this.getCoordinates();double[] a = new double[co.length];for (int i = 0; i < co.length; i++) {a[i] = ((MCoordinate) co[i]).m;}return a;} else {return null;}}public double getMinM() {if (this.isEmpty()) {return Double.NaN;} else {double[] a = this.getMeasures();if (this.getMeasureDirection() == MGeometry.INCREASING) {return a[0];} else if (this.getMeasureDirection() == MGeometry.DECREASING|| this.getMeasureDirection() == MGeometry.CONSTANT) {return a[a.length - 1];} else {double ma = Double.POSITIVE_INFINITY;for (int i = 0; i < a.length; i++) {if (ma > a[i]) {ma = a[i];}}return ma;}}}/*** Assigns the first coordinate in the CoordinateSequence to the* <code>beginMeasure</code> and the last coordinate in the* CoordinateSequence to the <code>endMeasure</code>. Measure values for* intermediate coordinates are then interpolated proportionally based on* their 2d offset of the overall 2d length of the LineString.* <p>* If the beginMeasure and endMeasure values are equal it is assumed that* all intermediate coordinates shall be the same value.** @param beginMeasure* Measure value for first coordinate* @param endMeasure* Measure value for last coordinate*/public void interpolate(double beginMeasure, double endMeasure) {if (this.isEmpty()) {return;}// interpolate with first vertex = beginMeasure; last vertex =// endMeasureCoordinate[] coordinates = this.getCoordinates();double length = this.getLength();double mLength = endMeasure - beginMeasure;double d = 0;boolean continuous = DoubleComparator.equals(beginMeasure, endMeasure);double m = beginMeasure;MCoordinate prevCoord = MCoordinate.convertCoordinate(coordinates[0]);prevCoord.m = m;MCoordinate curCoord;for (int i = 1; i < coordinates.length; i++) {curCoord = MCoordinate.convertCoordinate(coordinates[i]);if (continuous) {curCoord.m = beginMeasure;} else {d += curCoord.distance(prevCoord);m = beginMeasure + (d / length) * mLength;curCoord.m = m;prevCoord = curCoord;}}this.geometryChanged();assert (this.isMonotone(false)) : "interpolate function should always leave MGeometry monotone";}/*** Returns the measure length of the segment. This method assumes that the* length of the LineString is defined by the absolute value of (last* coordinate - first coordinate) in the CoordinateSequence. If either* measure is not defined or the CoordinateSequence contains no coordinates,* then Double.NaN is returned. If there is only 1 element in the* CoordinateSequence, then 0 is returned.** @return The measure length of the LineString*/public double getMLength() {if (getCoordinateSequence().size() == 0)return Double.NaN;if (getCoordinateSequence().size() == 1)return 0.0D;else {int lastIndex = getCoordinateSequence().size() - 1;double begin = getCoordinateSequence().getOrdinate(0,CoordinateSequence.M);double end = getCoordinateSequence().getOrdinate(lastIndex,CoordinateSequence.M);return (Double.isNaN(begin) || Double.isNaN(end)) ? Double.NaN: Math.abs(end - begin);}}/*** Indicates whether the MLineString has monotone increasing or decreasing* M-values** @return <code>true if MLineString is empty or M-values are increasing (NaN) values, false otherwise</code>*/public boolean isMonotone(boolean strict) {return strict ? this.strictMonotone : this.monotone;}public Geometry asGeometry() {return this;}// TODO get clear on function and implications of normalize// public void normalize(){//// }public void measureOnLength(boolean keepBeginMeasure) {Coordinate[] co = this.getCoordinates();if (!this.isEmpty()) {double d = 0.0;MCoordinate pco = (MCoordinate) co[0];if (!keepBeginMeasure || Double.isNaN(pco.m)) {pco.m = 0.0d;}MCoordinate mco;for (int i = 1; i < co.length; i++) {mco = (MCoordinate) co[i];d += mco.distance(pco);mco.m = d;pco = mco;}this.geometryChanged();}}/*** This method reverses the measures assigned to the Coordinates in the* CoordinateSequence without modifying the positional (x,y,z) values.*/public void reverseMeasures() {if (!this.isEmpty()) {double m[] = this.getMeasures();MCoordinate[] coar = (MCoordinate[]) this.getCoordinates();double nv;for (int i = 0; i < m.length; i++) {nv = m[m.length - 1 - i];coar[i].m = nv;}this.geometryChanged();}}public void setMeasureAtIndex(int index, double m) {getCoordinateSequence().setOrdinate(index, CoordinateSequence.M, m);this.geometryChanged();}/*** Shift all measures by the amount parameter. A negative amount shall* subtract the amount from the measure. Note that this can make for* negative measures.** @param amount* the positive or negative amount by which to shift the measures* in the CoordinateSequence.*/public void shiftMeasure(double amount) {Coordinate[] coordinates = this.getCoordinates();MCoordinate mco;if (!this.isEmpty()) {for (int i = 0; i < coordinates.length; i++) {mco = (MCoordinate) coordinates[i];mco.m = mco.m + amount;}}this.geometryChanged();}/** (non-Javadoc)** @see java.lang.Object#toString()*/public String toString() {Coordinate[] ar = this.getCoordinates();StringBuffer buf = new StringBuffer(ar.length * 17 * 3);for (int i = 0; i < ar.length; i++) {buf.append(ar[i].x);buf.append(" ");buf.append(ar[i].y);buf.append(" ");buf.append(((MCoordinate) ar[i]).m);buf.append("\n");}return buf.toString();}public MLineString unionM(MLineString l) throws MGeometryException {if (!this.monotone || !l.monotone) {throw new MGeometryException(MGeometryException.OPERATION_REQUIRES_MONOTONE);}Coordinate[] linecoar = l.getCoordinates();if (l.getMeasureDirection() == MGeometry.DECREASING) {CoordinateArrays.reverse(linecoar);}Coordinate[] thiscoar = this.getCoordinates();if (this.getMeasureDirection() == MGeometry.DECREASING) {CoordinateArrays.reverse(thiscoar);}// either the last coordinate in thiscoar equals the first in linecoar;// or the last in linecoar equals the first in thiscoar;MCoordinate lasttco = (MCoordinate) thiscoar[thiscoar.length - 1];MCoordinate firsttco = (MCoordinate) thiscoar[0];MCoordinate lastlco = (MCoordinate) linecoar[linecoar.length - 1];MCoordinate firstlco = (MCoordinate) linecoar[0];MCoordinate[] newcoar = new MCoordinate[thiscoar.length+ linecoar.length - 1];if (lasttco.equals2D(firstlco)&& DoubleComparator.equals(lasttco.m, firstlco.m)) {System.arraycopy(thiscoar, 0, newcoar, 0, thiscoar.length);System.arraycopy(linecoar, 1, newcoar, thiscoar.length,linecoar.length - 1);} else if (lastlco.equals2D(firsttco)&& DoubleComparator.equals(lastlco.m, firsttco.m)) {System.arraycopy(linecoar, 0, newcoar, 0, linecoar.length);System.arraycopy(thiscoar, 1, newcoar, linecoar.length,thiscoar.length - 1);} else {throw new MGeometryException(MGeometryException.UNIONM_ON_DISJOINT_MLINESTRINGS);}CoordinateSequence mcs = this.getFactory().getCoordinateSequenceFactory().create(newcoar);MLineString returnmlinestring = new MLineString(mcs, this.getFactory());assert (returnmlinestring.isMonotone(false)) : "new unionM-ed MLineString is not monotone";return returnmlinestring;}static class CoordinateSubSequence {private int firstIndex;private int lastIndex;private List<MCoordinate> vertices = new ArrayList<MCoordinate>();}}