Skip to content
Merged
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
8 changes: 8 additions & 0 deletions presto-docs/src/main/sphinx/functions/geospatial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,14 @@ Accessors

Returns ``null`` if a LineString or a Point is empty or ``null``.

.. function:: line_interpolate_point(LineString, double) -> Geometry

Returns the Point on the LineString at a fractional distance given by the
double argument. Throws an exception if the distance is not between 0 and 1.

Returns an empty Point if the LineString is empty. Returns ``null`` if
either the LineString or double is null.

.. function:: geometry_invalid_reason(Geometry) -> varchar

Returns the reason for why the input geometry is not valid.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@
import com.esri.core.geometry.ogc.OGCGeometry;
import com.esri.core.geometry.ogc.OGCPoint;
import com.esri.core.geometry.ogc.OGCPolygon;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequenceFactory;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;

import java.util.HashSet;
import java.util.Set;

public final class GeometryUtils
{
private static final CoordinateSequenceFactory COORDINATE_SEQUENCE_FACTORY = new PackedCoordinateSequenceFactory();
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(COORDINATE_SEQUENCE_FACTORY);

private GeometryUtils() {}

/**
Expand Down Expand Up @@ -212,4 +219,14 @@ public static boolean isPointOrRectangle(OGCGeometry ogcGeometry, Envelope envel

return true;
}

public static org.locationtech.jts.geom.Point makeJtsEmptyPoint()
{
return GEOMETRY_FACTORY.createPoint();
}

public static org.locationtech.jts.geom.Point makeJtsPoint(Coordinate coordinate)
{
return GEOMETRY_FACTORY.createPoint(coordinate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.airlift.slice.Slice;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.linearref.LengthIndexedLine;

import java.util.ArrayDeque;
Expand Down Expand Up @@ -81,6 +83,8 @@
import static com.facebook.presto.geospatial.GeometryType.POINT;
import static com.facebook.presto.geospatial.GeometryType.POLYGON;
import static com.facebook.presto.geospatial.GeometryUtils.getPointCount;
import static com.facebook.presto.geospatial.GeometryUtils.makeJtsEmptyPoint;
import static com.facebook.presto.geospatial.GeometryUtils.makeJtsPoint;
import static com.facebook.presto.geospatial.serde.GeometrySerde.deserialize;
import static com.facebook.presto.geospatial.serde.GeometrySerde.deserializeEnvelope;
import static com.facebook.presto.geospatial.serde.GeometrySerde.deserializeType;
Expand Down Expand Up @@ -547,6 +551,27 @@ public static Double lineLocatePoint(@SqlType(GEOMETRY_TYPE_NAME) Slice lineSlic
return new LengthIndexedLine(line).indexOf(point.getCoordinate()) / line.getLength();
}

@Description("Returns the point in the line at the fractional length.")
@ScalarFunction("line_interpolate_point")
@SqlType(GEOMETRY_TYPE_NAME)
public static Slice lineInterpolatePoint(@SqlType(GEOMETRY_TYPE_NAME) Slice lineSlice, @SqlType(DOUBLE) double fraction)
{
if (!(0.0 <= fraction && fraction <= 1.0)) {
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, format("line_interpolate_point: Fraction must be between 0 and 1, but is %s", fraction));
}

Geometry geometry = JtsGeometrySerde.deserialize(lineSlice);
validateType("line_interpolate_point", geometry, ImmutableSet.of(LINE_STRING));
LineString line = (LineString) geometry;

if (line.isEmpty()) {
return JtsGeometrySerde.serialize(makeJtsEmptyPoint());
}

org.locationtech.jts.geom.Coordinate coordinate = new LengthIndexedLine(line).extractPoint(fraction * line.getLength());
return JtsGeometrySerde.serialize(makeJtsPoint(coordinate));
}

@SqlNullable
@Description("Returns X maxima of a bounding box of a Geometry")
@ScalarFunction("ST_XMax")
Expand Down Expand Up @@ -1327,6 +1352,14 @@ private static void validateType(String function, OGCGeometry geometry, Set<Geom
}
}

private static void validateType(String function, Geometry geometry, Set<GeometryType> validTypes)
{
GeometryType type = GeometryType.getForJtsGeometryType(geometry.getGeometryType());
if (!validTypes.contains(type)) {
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, format("%s only applies to %s. Input type is: %s", function, OR_JOINER.join(validTypes), type));
}
}

private static void verifySameSpatialReference(OGCGeometry leftGeometry, OGCGeometry rightGeometry)
{
checkArgument(Objects.equals(leftGeometry.getEsriSpatialReference(), rightGeometry.getEsriSpatialReference()), "Input geometries must have the same spatial reference");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,30 @@ public void testLineLocatePoint()
assertInvalidFunction("line_locate_point(ST_GeometryFromText('LINESTRING (0 0, 0 1, 2 1)'), ST_GeometryFromText('POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))'))", "Second argument to line_locate_point must be a Point. Got: Polygon");
}

@Test
public void testLineInterpolatePoint()
{
assertLineInterpolatePoint("LINESTRING EMPTY", 0.5, "POINT EMPTY");
assertLineInterpolatePoint("LINESTRING (0 0, 0 1)", 0.2, "POINT (0 0.2)");
assertLineInterpolatePoint("LINESTRING (0 0, 0 1)", 0.0, "POINT (0 0)");
assertLineInterpolatePoint("LINESTRING (0 0, 0 1)", 1.0, "POINT (0 1)");
assertLineInterpolatePoint("LINESTRING (0 0, 0 1, 3 1)", 0.0625, "POINT (0 0.25)");
assertLineInterpolatePoint("LINESTRING (0 0, 0 1, 3 1)", 0.75, "POINT (2 1)");
assertLineInterpolatePoint("LINESTRING (1 3, 5 4)", 0.0, "POINT (1 3)");
assertLineInterpolatePoint("LINESTRING (1 3, 5 4)", 0.25, "POINT (2 3.25)");
assertLineInterpolatePoint("LINESTRING (1 3, 5 4)", 1.0, "POINT (5 4)");

assertInvalidFunction("line_interpolate_point(ST_GeometryFromText('POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))'), 0.5)", "line_interpolate_point only applies to LINE_STRING. Input type is: POLYGON");
assertInvalidFunction("line_interpolate_point(ST_GeometryFromText('MULTILINESTRING ((0 0, 0 1), (2 2, 4 2))'), 0.0)", "line_interpolate_point only applies to LINE_STRING. Input type is: MULTI_LINE_STRING");
assertInvalidFunction("line_interpolate_point(ST_GeometryFromText('LINESTRING (0 0, 0 1, 2 1)'), -1)", "line_interpolate_point: Fraction must be between 0 and 1, but is -1.0");
assertInvalidFunction("line_interpolate_point(ST_GeometryFromText('LINESTRING (0 0, 0 1, 2 1)'), 1.5)", "line_interpolate_point: Fraction must be between 0 and 1, but is 1.5");
}

private void assertLineInterpolatePoint(String sourceWkt, double distance, String expectedWkt)
{
assertFunction(format("ST_AsText(line_interpolate_point(ST_GeometryFromText('%s'), %s))", sourceWkt, distance), VARCHAR, expectedWkt);
}

@Test
public void testSTMax()
{
Expand Down