org.geolatte.geom.DefaultMeasureGeometryOperations Maven / Gradle / Ivy
Show all versions of geolatte-geom Show documentation
/*
* This file is part of the GeoLatte project.
*
* GeoLatte 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 3 of the License, or
* (at your option) any later version.
*
* GeoLatte 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 GeoLatte. If not, see .
*
* Copyright (C) 2010 - 2012 and Ownership of code is shared by:
* Qmino bvba - Romeinsestraat 18 - 3001 Heverlee (http://www.qmino.com)
* Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
*/
package org.geolatte.geom;
import org.geolatte.geom.crs.CoordinateReferenceSystem;
import org.geolatte.geom.crs.Unit;
import static org.geolatte.geom.crs.CoordinateReferenceSystems.addLinearSystem;
import static org.geolatte.geom.crs.CoordinateReferenceSystems.hasMeasureAxis;
import static org.geolatte.geom.crs.CoordinateReferenceSystems.hasVerticalAxis;
/**
* Default implementation of {@link MeasureGeometryOperations}.
*
* This implementation conforms to the SQL/MM and SFA 1.2.1 specifications. See
* Simple Feature Access -
* Part 1: common architecture, sec. 6.1.2.6
*
* @author Karel Maesen, Geovise BVBA
* creation-date: 4/4/12
*/
public class DefaultMeasureGeometryOperations implements MeasureGeometryOperations {
private final static PositionEquality pntEq = new ExactPositionEquality();
@Override
public Geometry
locateAlong(final Geometry
geometry, final double mValue) {
return locateBetween(geometry, mValue, mValue);
}
@Override
public
Geometry
locateBetween(final Geometry
geometry, final double startMeasure, final double endMeasure) {
if (geometry == null) throw new IllegalArgumentException("Null geometries not allowed.");
if (geometry.isEmpty()) return new Point
(geometry.getCoordinateReferenceSystem());
if (C2D.class.isAssignableFrom(geometry.getPositionClass()) &&
Measured.class.isAssignableFrom(geometry.getPositionClass())) {
MeasureInterpolatingVisitor
visitor = new MeasureInterpolatingVisitor<>(geometry, startMeasure, endMeasure);
geometry.accept(visitor);
return visitor.result();
}
throw new IllegalArgumentException("Requires projected coordinates");
}
/**
* @inheritDoc
*/
@Override
public
double measureAt(final Geometry
geometry, final C2D pos, double tolerance) {
if (geometry == null || pos == null) throw new IllegalArgumentException("Parameters must not be NULL");
if (geometry.isEmpty()) return Double.NaN;
InterpolatingVisitor
visitor = new InterpolatingVisitor
(pos, tolerance);
geometry.accept(visitor);
return visitor.m();
}
/**
* @inheritDoc
*/
@Override
public
Geometry measureOnLength(
final Geometry geometry, final Class positionTypeMarker, final boolean keepBeginMeasure) {
if (geometry == null) throw new IllegalArgumentException("Geometry parameter must not be NULL");
if (positionTypeMarker == null)
throw new IllegalArgumentException("PositionTypeMarker parameter must not be NULL");
if (geometry.getGeometryType() != GeometryType.LINESTRING
&& geometry.getGeometryType() != GeometryType.MULTILINESTRING) {
throw new IllegalArgumentException("Geometry parameter must be of type LineString or MultiLineString");
}
final CoordinateReferenceSystem sourceCRS = geometry.getCoordinateReferenceSystem();
final CoordinateReferenceSystem measuredVariant = !hasMeasureAxis(sourceCRS) ?
(CoordinateReferenceSystem) addLinearSystem(sourceCRS, Unit.METER) :
(CoordinateReferenceSystem)sourceCRS;
if (!measuredVariant.getPositionClass().equals(positionTypeMarker)) {
throw new IllegalArgumentException(String.format(
"Inconsistent types: measured CRS has position type %s, but positionTypeMarker is %s.",
measuredVariant.getPositionClass().getName(),
positionTypeMarker.getName()));
}
return new OnLengthMeasureOp(geometry, measuredVariant, keepBeginMeasure).execute();
}
/**
* @inheritDoc
*/
@Override
public double minimumMeasure(Geometry
geometry) {
return createGetExtrMeasureOp(geometry, true).execute();
}
/**
* @inheritDoc
*/
@Override
public
double maximumMeasure(Geometry
geometry) {
return createGetExtrMeasureOp(geometry, false).execute();
}
private
GeometryOperation createGetExtrMeasureOp(final Geometry geometry, final boolean min) {
return new GeometryOperation() {
@Override
public Double execute() {
if (geometry == null) {
throw new IllegalArgumentException("Operation expects a non-empty geometry");
}
if (geometry.isEmpty()) {
return Double.NaN;
}
FindExtremumMeasureVisitor visitor = new FindExtremumMeasureVisitor
(min);
geometry.getPositions().accept(visitor);
return visitor.extremum;
}
};
}
private static class InterpolatingVisitor
implements GeometryVisitor
{
public static final String INVALID_TYPE_MSG = "Operation only valid on LineString, MultiPoint and MultiLineString Geometries.";
public static final String OUTSIDE_TOL_MSG = "Search point not within tolerance: distance to geometry is %f > %f";
final C2D searchPosition;
final double tolerance;
double mValue = Double.NaN;
double distToSearchPoint = Double.MAX_VALUE;
InterpolatingVisitor(C2D pnt, double tolerance) {
if (pnt == null) throw new IllegalArgumentException("Null point is not allowed.");
searchPosition = pnt;
this.tolerance = Math.abs(tolerance);
}
double m() {
if (distToSearchPoint <= tolerance) {
return mValue;
}
throw new IllegalArgumentException(String.format(OUTSIDE_TOL_MSG, distToSearchPoint, tolerance));
}
@Override
public void visit(Point
point) {
// Note that this is also used when visiting MultiPoints
P pos = point.getPosition();
double dts = Math.hypot(pos.getX() - searchPosition.getX(), pos.getY() - searchPosition.getY());
if (dts <= distToSearchPoint) {
mValue = point.getPosition().getM();
distToSearchPoint = dts;
}
}
@Override
public void visit(LineString
lineString) {
LineSegments
lineSegments = new LineSegments
(lineString.getPositions());
for (LineSegment
segment : lineSegments) {
P p0 = segment.getStartPosition();
P p1 = segment.getEndPosition();
double[] dAndR = Vector.positionToSegment2D(p0, p1, searchPosition);
double d = Math.sqrt(dAndR[0]);
if (d <= distToSearchPoint ) {
double r = dAndR[1];
if (r <= 0) {
mValue = p0.getM();
} else if (r >= 1) {
mValue = p1.getM();
} else {
mValue = p0.getM() + r * (p1.getM() - p0.getM());
}
distToSearchPoint = d;
}
}
}
@Override
public void visit(Polygon
polygon) {
throw new IllegalArgumentException(INVALID_TYPE_MSG);
}
@Override
public > void visit(AbstractGeometryCollection collection) {
}
}
private static class FindExtremumMeasureVisitor
implements PositionVisitor
{
final boolean findMinimum;
double extremum;
FindExtremumMeasureVisitor(boolean minimum) {
findMinimum = minimum;
extremum = minimum ? Double.MAX_VALUE : Double.MIN_VALUE;
}
@Override
public void visit(P position) {
//assume measure value is last component (must be guaranteed by calling method).
double m = position.getM();
if (findMinimum) {
extremum = m < extremum ? m : extremum;
} else {
extremum = m > extremum ? m : extremum;
}
}
}
private static class OnLengthMeasureOp implements GeometryOperation> {
private double length = 0;
final private Geometry> geometry;
final private CoordinateReferenceSystem measuredVariant;
final private boolean keepBeginMeasure;
OnLengthMeasureOp(Geometry> geometry, CoordinateReferenceSystem measuredVariant, boolean keepBeginMeasure) {
this.geometry = geometry;
this.measuredVariant = measuredVariant;
this.keepBeginMeasure = keepBeginMeasure;
}
@Override
public Geometry execute() {
Geometry measured = Geometry.forceToCrs(geometry, measuredVariant);
if (measured.isEmpty()) return measured;
if (keepBeginMeasure) {
double initialValue = measured.getPositionN(0).getM();
length = (Double.isNaN(initialValue) ? 0 : initialValue);
}
if (measured instanceof LineString) {
return measure((LineString) measured);
} else if (geometry instanceof MultiLineString) {
return measure((MultiLineString) measured);
} else {
throw new IllegalStateException(
String.format("Requires a LineString or MultiLineString, but received %s",
geometry.getClass().getName()));
}
}
//TODO -- the measure() functions can probably be simplified
@SuppressWarnings("unchecked")
private MultiLineString measure(MultiLineString geometry) {
LineString[] measuredParts = (LineString[]) new LineString[geometry.getNumGeometries()];
for (int part = 0; part < geometry.getNumGeometries(); part++) {
LineString lineString = geometry.getGeometryN(part);
measuredParts[part] = measure(lineString);
}
return new MultiLineString(measuredParts);
}
private LineString measure(LineString geometry) {
CoordinateReferenceSystem crs = geometry.getCoordinateReferenceSystem();
PositionSequence originalPoints = geometry.getPositions();
PositionSequenceBuilder builder = PositionSequenceBuilders.fixedSized(originalPoints.size(),
geometry.getPositionClass());
int mIdx = hasVerticalAxis(crs) ? 3 :2;
double[] coordinates = new double[geometry.getCoordinateDimension()];
double[] prevCoordinates = new double[geometry.getCoordinateDimension()];
for (int i = 0; i < originalPoints.size(); i++) {
originalPoints.getCoordinates(i, coordinates);
if (i > 0) {
length += Math.hypot(coordinates[0] - prevCoordinates[0], coordinates[1] - prevCoordinates[1]);
}
coordinates[mIdx] = length;
builder.add(coordinates);
prevCoordinates[0] = coordinates[0];
prevCoordinates[1] = coordinates[1];
}
return new LineString(builder.toPositionSequence(), crs);
}
}
}