Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
*/
package org.locationtech.jts.densify;

import java.util.Iterator;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateList;
import org.locationtech.jts.geom.CoordinateSequence;
Expand Down Expand Up @@ -56,15 +58,31 @@ public static Geometry densify(Geometry geom, double distanceTolerance) {
return densifier.getResultGeometry();
}

/**
* Densifies a geometry using a given distance tolerance, and respecting the
* input geometry's {@link PrecisionModel}.
*
* @param geom the geometry to densify
* @param distanceTolerance the distance tolerance to densify
* @param interpolator The segment interpolator to use
* @return the densified geometry
*/
public static Geometry densify(Geometry geom, double distanceTolerance, SegmentInterpolator interpolator) {
Densifier densifier = new Densifier(geom);
densifier.setInterpolator(interpolator);
densifier.setDistanceTolerance(distanceTolerance);
return densifier.getResultGeometry();
}

/**
* Densifies a list of coordinates.
*
* @param pts the coordinate list
* @param distanceTolerance the densify tolerance
* @return the densified coordinate sequence
*/
private static Coordinate[] densifyPoints(Coordinate[] pts,
double distanceTolerance, PrecisionModel precModel) {
private static Coordinate[] densifyPoints(Coordinate[] pts, double distanceTolerance, PrecisionModel precModel,
SegmentInterpolator interpolator) {
LineSegment seg = new LineSegment();
CoordinateList coordList = new CoordinateList();
for (int i = 0; i < pts.length - 1; i++) {
Expand All @@ -78,15 +96,10 @@ private static Coordinate[] densifyPoints(Coordinate[] pts,
continue;

// densify the segment
int densifiedSegCount = (int) Math.ceil(len / distanceTolerance);
double densifiedSegLen = len / densifiedSegCount;
for (int j = 1; j < densifiedSegCount; j++) {
double segFract = (j * densifiedSegLen) / len;
Coordinate p = seg.pointAlong(segFract);
if(!Double.isNaN(seg.p0.z) && !Double.isNaN(seg.p1.z)) {
p.setZ(seg.p0.z + segFract * (seg.p1.z - seg.p0.z));
}
precModel.makePrecise(p);
Iterator<Coordinate> it = interpolator.densifySegment(seg, pts, i, distanceTolerance);
while (it.hasNext()) {
Coordinate p = it.next();
precModel.makePrecise(p);
coordList.add(p, false);
}
}
Expand All @@ -100,6 +113,8 @@ private static Coordinate[] densifyPoints(Coordinate[] pts,

private double distanceTolerance;

private SegmentInterpolator interpolator = new StraightSteppingSegmentInterpolator();

/**
* Indicates whether areas should be topologically validated.
*/
Expand Down Expand Up @@ -136,30 +151,44 @@ public void setDistanceTolerance(double distanceTolerance) {
public void setValidate(boolean isValidated) {
this.isValidated = isValidated;
}


/**
* Sets the interpolator used to generate new points for each segment
*
* @param interpolator the interpolator to use
*/
public void setInterpolator(SegmentInterpolator interpolator) {
this.interpolator = interpolator;
}

/**
* Gets the densified geometry.
*
* @return the densified geometry
*/
public Geometry getResultGeometry() {
return (new DensifyTransformer(distanceTolerance, isValidated)).transform(inputGeom);
return (new DensifyTransformer(distanceTolerance, isValidated, interpolator)).transform(inputGeom);
}

static class DensifyTransformer extends GeometryTransformer {
double distanceTolerance;
private boolean isValidated;

DensifyTransformer(double distanceTolerance, boolean isValidated) {
this.distanceTolerance = distanceTolerance;
this.isValidated = isValidated;
}

protected CoordinateSequence transformCoordinates(
CoordinateSequence coords, Geometry parent) {
double distanceTolerance;
private boolean isValidated;
SegmentInterpolator interpolator;

DensifyTransformer(double distanceTolerance, boolean isValidated) {
this(distanceTolerance, isValidated, new StraightSteppingSegmentInterpolator());
}

DensifyTransformer(double distanceTolerance, boolean isValidated, SegmentInterpolator interpolator) {
this.distanceTolerance = distanceTolerance;
this.isValidated = isValidated;
this.interpolator = interpolator;
}

protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent) {
Coordinate[] inputPts = coords.toCoordinateArray();
Coordinate[] newPts = Densifier
.densifyPoints(inputPts, distanceTolerance, parent.getPrecisionModel());
Coordinate[] newPts = Densifier.densifyPoints(inputPts, distanceTolerance, parent.getPrecisionModel(),
interpolator);
// prevent creation of invalid linestrings
if (parent instanceof LineString && newPts.length == 1) {
newPts = new Coordinate[0];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2026 Kevin Smith.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/

package org.locationtech.jts.densify;

import java.util.Random;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineSegment;

/**
* Produces points by dividing the segment in two, offsetting perpendicularly by
* a random amount proportional to the length of the segment and repeating until
* the threshold is met.
* <p>
* The result is a random fractal shape resembling a natural coastline or river.
*
* @author Kevin Smith
*/
public class FractalSegmentInterpolator extends SubdividingSegmentInterpolator {

private static final double MIDWAY = 0.5;

/**
* Create a FractalSegmentInterpolator
*
* @param proportion Maximum offset as a proportion of the segment length
* @param rand Random number generator to use
*/
public FractalSegmentInterpolator(double proportion, Random rand) {
this.rand = rand;
this.proportion = proportion;
}

private final Random rand;
private final double proportion;

@Override
public Coordinate midpoint(LineSegment seg) {
Coordinate p = seg.pointAlongOffset(MIDWAY, seg.getLength() * (rand.nextDouble() * 2 - 1) * proportion);
fillZ(seg, p);
return p;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2026 Kevin Smith.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/

package org.locationtech.jts.densify;

import java.util.Iterator;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineSegment;

/**
* A scheme used to generate intermediate points
*
* @author Kevin Smith
*/
@FunctionalInterface
public interface SegmentInterpolator {
/**
* Generate the additional points along the given segment of a coordinate array
*
* @param seg The current segment being interpolated
* @param pts The full sequence of original points
* @param i The index of the starting element of the current
* segment in the full sequence
* @param distanceTolerance The maximum length allowable between consecutive
* result points
* @return iterator over the intermediate points to divide the given segment
*/
Iterator<Coordinate> densifySegment(LineSegment seg, Coordinate[] pts, int i, double distanceTolerance);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2016 Vivid Solutions.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/

package org.locationtech.jts.densify;

import java.util.Iterator;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineSegment;

/**
* Interpolator which generates points using a function of segFract, where
* segFract is evenly stepped along the length of the segment.
*
* @author Kevin Smith
*/
public abstract class SteppingSegmentInterpolator implements SegmentInterpolator {
@Override
public Iterator<Coordinate> densifySegment(LineSegment seg, Coordinate[] pts, int i, double distanceTolerance) {
return new Iterator<Coordinate>() {
int j = 1;
double len = seg.getLength();
int densifiedSegCount = (int) Math.ceil(len / distanceTolerance);
double densifiedSegLen = len / densifiedSegCount;

@Override
public boolean hasNext() {
return j < densifiedSegCount;
}

@Override
public Coordinate next() {
double segFract = (j * densifiedSegLen) / len;
j++;
return pointAlong(seg, segFract);
}

};

}

/**
* Generate a point a given proportion of the way along a segment.
* @param seg A segment to interpolate along
* @param segFract A value between 0 and 1 representing a position along the
* segment
* @return A point along the segment
*/
public abstract Coordinate pointAlong(LineSegment seg, double segFract);

/**
* Fill in the z value of a point along a segment if the both endpoints have z set.
*
* @param seg
* @param segFract
* @param p
*/
protected final void fillZ(LineSegment seg, double segFract, Coordinate p) {
if (!Double.isNaN(seg.p0.z) && !Double.isNaN(seg.p1.z)) {
p.setZ(seg.p0.z + segFract * (seg.p1.z - seg.p0.z));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2026 Kevin Smith.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/

package org.locationtech.jts.densify;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineSegment;

/**
* Produces a minimal set of intermediate points evenly spaced along the
* segment.
*
* @author Kevin Smith
*/
public class StraightSteppingSegmentInterpolator extends SteppingSegmentInterpolator {

@Override
public Coordinate pointAlong(LineSegment seg, double segFract) {
Coordinate p = seg.pointAlong(segFract);
fillZ(seg, segFract, p);
return p;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2026 Kevin Smith.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/

package org.locationtech.jts.densify;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineSegment;

/**
* Produces points dividing the segment into a power of two in a straight line.
*
* @author Kevin Smith
*/
public class StraightSubdividingSegmentInterpolator extends SubdividingSegmentInterpolator {

@Override
public Coordinate midpoint(LineSegment seg) {
Coordinate p = seg.midPoint();
fillZ(seg, p);
return p;
}

}
Loading