com.datastax.dse.driver.internal.core.data.geometry.Distance Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.dse.driver.internal.core.data.geometry;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import com.datastax.dse.driver.api.core.data.geometry.Geometry;
import com.datastax.dse.driver.api.core.data.geometry.LineString;
import com.datastax.dse.driver.api.core.data.geometry.Point;
import com.datastax.dse.driver.api.core.data.geometry.Polygon;
import com.datastax.dse.driver.api.core.graph.predicates.Geo;
import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
import com.esri.core.geometry.MultiPath;
import com.esri.core.geometry.ogc.OGCGeometry;
import com.esri.core.geometry.ogc.OGCPoint;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.jcip.annotations.Immutable;
/**
* The driver-side representation of DSE's {@code Geo.distance}.
*
* This is a circle in a two-dimensional XY plane represented by its center point and radius. It
* is used as a search criteria to determine whether or not another geospatial object lies within a
* circular area.
*
*
Note that this shape has no equivalent in the OGC and GeoJSON standards: as a consequence,
* {@link #asWellKnownText()} returns a custom format, and {@link #getOgcGeometry()}, {@link
* #asWellKnownBinary()}, and {@link #asGeoJson()} throw {@link UnsupportedOperationException}.
*
*
Unlike other geo types, this class is never exposed directly to driver clients: it is used
* internally by {@linkplain Geo#inside(Point, double) geo predicates}, but cannot be a column type,
* nor appear in CQL or graph results. Therefore it doesn't have a public-facing interface, nor a
* built-in codec.
*/
@Immutable
public class Distance extends DefaultGeometry {
private static final Pattern WKT_PATTERN =
Pattern.compile(
"distance *\\( *\\( *([\\d\\.-]+) *([\\d+\\.-]+) *\\) *([\\d+\\.-]+) *\\)",
CASE_INSENSITIVE);
/**
* Creates a distance from its Well-known
* Text (WKT) representation.
*
* @param source the Well-known Text representation to parse.
* @return the point represented by the WKT.
* @throws IllegalArgumentException if the string does not contain a valid Well-known Text
* representation.
* @see Distance#asWellKnownText()
*/
@NonNull
public static Distance fromWellKnownText(@NonNull String source) {
Matcher matcher = WKT_PATTERN.matcher(source.trim());
if (matcher.matches() && matcher.groupCount() == 3) {
try {
return new Distance(
new DefaultPoint(
Double.parseDouble(matcher.group(1)), Double.parseDouble(matcher.group(2))),
Double.parseDouble(matcher.group(3)));
} catch (NumberFormatException var3) {
throw new IllegalArgumentException(String.format("Unable to parse %s", source));
}
} else {
throw new IllegalArgumentException(String.format("Unable to parse %s", source));
}
}
private final DefaultPoint center;
private final double radius;
/**
* Creates a new distance with the given center and radius.
*
* @param center The center point.
* @param radius The radius of the circle representing distance.
*/
public Distance(@NonNull Point center, double radius) {
super(((DefaultPoint) center).getOgcGeometry());
Preconditions.checkNotNull(center);
Preconditions.checkArgument(radius >= 0.0D, "Radius must be >= 0 (got %s)", radius);
this.center = ((DefaultPoint) center);
this.radius = radius;
}
/** @return The center point of the circle representing this distance. */
@NonNull
public Point getCenter() {
return center;
}
/** @return The radius of the circle representing this distance. */
public double getRadius() {
return radius;
}
/**
* Returns a Well-known Text (WKT)
* representation of this geospatial type.
*
*
Since there is no Well-known Text specification for Distance, this returns a custom format
* of: DISTANCE((center.x center.y) radius)
*
* @return a Well-known Text representation of this object.
*/
@NonNull
@Override
public String asWellKnownText() {
return String.format("DISTANCE((%s %s) %s)", this.center.X(), this.center.Y(), this.radius);
}
/**
* The distance type has no equivalent in the OGC standard: this method throws an {@link
* UnsupportedOperationException}.
*/
@NonNull
@Override
public OGCGeometry getOgcGeometry() {
throw new UnsupportedOperationException();
}
/**
* The distance type has no equivalent in the OGC standard: this method throws an {@link
* UnsupportedOperationException}.
*/
@NonNull
@Override
public ByteBuffer asWellKnownBinary() {
throw new UnsupportedOperationException();
}
/**
* The distance type has no equivalent in the GeoJSON standard: this method throws an {@link
* UnsupportedOperationException}.
*/
@Override
@NonNull
public String asGeoJson() {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other instanceof Distance) {
Distance that = (Distance) other;
return Objects.equals(this.center, that.center) && this.radius == that.radius;
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(center, radius);
}
@SuppressWarnings("SimplifiableConditionalExpression")
@Override
public boolean contains(@NonNull Geometry geometry) {
return geometry instanceof Distance
? this.containsDistance((Distance) geometry)
: geometry instanceof Point
? this.containsPoint((Point) geometry)
: geometry instanceof LineString
? this.containsLineString((LineString) geometry)
: geometry instanceof Polygon ? this.containsPolygon((Polygon) geometry) : false;
}
private boolean containsDistance(Distance distance) {
return this.center.getOgcGeometry().distance(distance.center.getOgcGeometry()) + distance.radius
<= this.radius;
}
private boolean containsPoint(Point point) {
return this.containsOGCPoint(((DefaultPoint) point).getOgcGeometry());
}
private boolean containsLineString(LineString lineString) {
MultiPath multiPath =
(MultiPath) ((DefaultLineString) lineString).getOgcGeometry().getEsriGeometry();
return containsMultiPath(multiPath);
}
private boolean containsPolygon(Polygon polygon) {
MultiPath multiPath =
(com.esri.core.geometry.Polygon)
((DefaultPolygon) polygon).getOgcGeometry().getEsriGeometry();
return containsMultiPath(multiPath);
}
private boolean containsMultiPath(MultiPath multiPath) {
int numPoints = multiPath.getPointCount();
for (int i = 0; i < numPoints; ++i) {
OGCPoint point = new OGCPoint(multiPath.getPoint(i), DefaultGeometry.SPATIAL_REFERENCE_4326);
if (!this.containsOGCPoint(point)) {
return false;
}
}
return true;
}
private boolean containsOGCPoint(OGCPoint point) {
return this.center.getOgcGeometry().distance(point) <= this.radius;
}
/**
* This object gets replaced by an internal proxy for serialization.
*
* @serialData Point (wkb) for center followed by double for radius
*/
private Object writeReplace() {
return new DistanceSerializationProxy(this);
}
}