-
Notifications
You must be signed in to change notification settings - Fork 3.1k
API, Core: Add geospatial predicates and bounding box literals #14101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0dcf718
9b4fdcf
d10d9d3
9ec3e0a
622a101
7f0c13f
6a0570b
8ac6daf
cb412b0
1b7dc93
5bfa1dc
c5ada66
4ee66e7
3785d53
0196496
4532bce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,10 @@ | |
| */ | ||
| package org.apache.iceberg.expressions; | ||
|
|
||
| import java.nio.ByteBuffer; | ||
| import java.util.stream.Stream; | ||
| import org.apache.iceberg.expressions.Expression.Operation; | ||
| import org.apache.iceberg.geospatial.BoundingBox; | ||
| import org.apache.iceberg.relocated.com.google.common.base.Preconditions; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.Lists; | ||
| import org.apache.iceberg.transforms.Transform; | ||
|
|
@@ -202,6 +204,14 @@ public static UnboundPredicate<String> notStartsWith(UnboundTerm<String> expr, S | |
| return new UnboundPredicate<>(Expression.Operation.NOT_STARTS_WITH, expr, value); | ||
| } | ||
|
|
||
| public static UnboundPredicate<ByteBuffer> stIntersects(String name, BoundingBox value) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If we want to provide some convenient constructor, should we do it in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'm fine either way. We could include an override here that produces a bounding box. I agree with not having 6 and 8 argument versions of the method. And we will need to consider what to name the factory methods in |
||
| return geospatialPredicate(Operation.ST_INTERSECTS, name, value); | ||
| } | ||
|
|
||
| public static UnboundPredicate<ByteBuffer> stDisjoint(String name, BoundingBox value) { | ||
| return geospatialPredicate(Operation.ST_DISJOINT, name, value); | ||
| } | ||
|
|
||
| public static <T> UnboundPredicate<T> in(String name, T... values) { | ||
| return predicate(Operation.IN, name, Lists.newArrayList(values)); | ||
| } | ||
|
|
@@ -280,6 +290,13 @@ public static <T> UnboundPredicate<T> predicate(Operation op, UnboundTerm<T> exp | |
| return new UnboundPredicate<>(op, expr); | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| static UnboundPredicate<ByteBuffer> geospatialPredicate( | ||
| Operation op, String name, BoundingBox value) { | ||
| return new UnboundPredicate<ByteBuffer>( | ||
| op, ref(name), (Literal<ByteBuffer>) (Literal<?>) Literal.of(value)); | ||
| } | ||
|
|
||
| public static True alwaysTrue() { | ||
| return True.INSTANCE; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,8 @@ | |
| import java.util.Locale; | ||
| import java.util.Objects; | ||
| import java.util.UUID; | ||
| import org.apache.iceberg.geospatial.BoundingBox; | ||
| import org.apache.iceberg.geospatial.GeospatialBound; | ||
| import org.apache.iceberg.relocated.com.google.common.base.Preconditions; | ||
| import org.apache.iceberg.relocated.com.google.common.io.BaseEncoding; | ||
| import org.apache.iceberg.types.Comparators; | ||
|
|
@@ -87,6 +89,8 @@ static <T> Literal<T> from(T value) { | |
| return (Literal<T>) new Literals.DecimalLiteral((BigDecimal) value); | ||
| } else if (value instanceof Variant) { | ||
| return (Literal<T>) new Literals.VariantLiteral((Variant) value); | ||
| } else if (value instanceof BoundingBox) { | ||
| return (Literal<T>) new Literals.BoundingBoxLiteral((BoundingBox) value); | ||
| } | ||
|
|
||
| throw new IllegalArgumentException( | ||
|
|
@@ -747,4 +751,96 @@ public String toString() { | |
| return "X'" + BASE16_ENCODING.encode(bytes) + "'"; | ||
| } | ||
| } | ||
|
|
||
| static class BoundingBoxLiteral implements Literal<BoundingBox> { | ||
| private static final Comparator<BoundingBox> CMP = | ||
| Comparators.<BoundingBox>nullsFirst().thenComparing(BoundingBoxLiteral::compare); | ||
|
|
||
| private final BoundingBox value; | ||
|
|
||
| BoundingBoxLiteral(BoundingBox value) { | ||
| Preconditions.checkNotNull(value, "Bounding box value cannot be null"); | ||
| this.value = value; | ||
| } | ||
|
|
||
| @Override | ||
| public BoundingBox value() { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public ByteBuffer toByteBuffer() { | ||
| return value.toByteBuffer(); | ||
| } | ||
|
|
||
| @Override | ||
| @SuppressWarnings("unchecked") | ||
| public <T> Literal<T> to(Type type) { | ||
| if (type.typeId() != Type.TypeID.GEOMETRY && type.typeId() != Type.TypeID.GEOGRAPHY) { | ||
| return null; | ||
| } | ||
|
|
||
| return (Literal<T>) this; | ||
| } | ||
|
|
||
| @Override | ||
| public Comparator<BoundingBox> comparator() { | ||
| return CMP; | ||
| } | ||
|
|
||
| Object writeReplace() throws ObjectStreamException { | ||
| return new SerializationProxies.BoundingBoxLiteralProxy(toByteBuffer()); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return value().toString(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object other) { | ||
| if (this == other) { | ||
| return true; | ||
| } | ||
| if (!(other instanceof BoundingBoxLiteral)) { | ||
| return false; | ||
| } | ||
|
|
||
| BoundingBoxLiteral that = (BoundingBoxLiteral) other; | ||
| return comparator().compare(value(), that.value()) == 0; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this works, then the comparator is incorrect for values. This is comparing serialized bounding boxes. |
||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hashCode(value()); | ||
| } | ||
|
|
||
| private static int compare(BoundingBox left, BoundingBox right) { | ||
| int cmp = compare(left.min(), right.min()); | ||
| if (cmp != 0) { | ||
| return cmp; | ||
| } | ||
|
|
||
| return compare(left.max(), right.max()); | ||
| } | ||
|
|
||
| private static int compare(GeospatialBound left, GeospatialBound right) { | ||
| int cmp = Double.compare(left.x(), right.x()); | ||
| if (cmp != 0) { | ||
| return cmp; | ||
| } | ||
|
|
||
| cmp = Double.compare(left.y(), right.y()); | ||
| if (cmp != 0) { | ||
| return cmp; | ||
| } | ||
|
|
||
| cmp = Double.compare(left.z(), right.z()); | ||
| if (cmp != 0) { | ||
| return cmp; | ||
| } | ||
|
|
||
| return Double.compare(left.m(), right.m()); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.