huaxingao commented on code in PR #12667: URL: https://github.com/apache/iceberg/pull/12667#discussion_r2476525048
########## api/src/main/java/org/apache/iceberg/geospatial/GeospatialPredicateEvaluators.java: ########## @@ -0,0 +1,224 @@ +/* + * 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 org.apache.iceberg.geospatial; + +import org.apache.iceberg.relocated.com.google.common.base.Preconditions; +import org.apache.iceberg.types.Type; + +public class GeospatialPredicateEvaluators { + private GeospatialPredicateEvaluators() {} + + public interface GeospatialPredicateEvaluator { + /** + * Determines whether the two bounding boxes intersect. + * + * @param bbox1 the first bounding box + * @param bbox2 the second bounding box + * @return true if this box intersects the other box + */ + boolean intersects(BoundingBox bbox1, BoundingBox bbox2); + } + + /** + * Create an evaluator for evaluating bounding box relationship for the given geospatial type. + * + * @param type the geospatial type, should be one of Type.TypeID.GEOMETRY or Type.TypeID.GEOGRAPHY + * @return the evaluator + */ + public static GeospatialPredicateEvaluator create(Type type) { + switch (type.typeId()) { + case GEOMETRY: + return new GeometryEvaluator(); + case GEOGRAPHY: + return new GeographyEvaluator(); + default: + throw new UnsupportedOperationException("Unsupported type for BoundingBox: " + type); + } + } + + public static class GeometryEvaluator implements GeospatialPredicateEvaluator { + + /** + * Check if two bounding boxes intersect + * + * @param bbox1 the first bounding box + * @param bbox2 the second bounding box + * @return true if the bounding boxes intersect + */ + @Override + public boolean intersects(BoundingBox bbox1, BoundingBox bbox2) { + validateBoundingBox(bbox1); + validateBoundingBox(bbox2); + + if (!intersectsYZM(bbox1, bbox2)) { + return false; + } + + // Check X dimension (longitude/easting) - no wrap-around + return rangeIntersects(bbox1.min().x(), bbox1.max().x(), bbox2.min().x(), bbox2.max().x()); + } + + /** + * For geometry types, xmin must not be greater than xmax, ymin must not be greater than ymax. + * + * @param bbox the bounding box to validate + * @throws IllegalArgumentException if the bounding box is invalid + */ + private void validateBoundingBox(BoundingBox bbox) { + Preconditions.checkArgument( + bbox.min().x() <= bbox.max().x(), + "Invalid X range: %s. xmin cannot be greater than xmax", + bbox); + Preconditions.checkArgument( + bbox.min().y() <= bbox.max().y(), + "Invalid Y range: %s. ymin cannot be greater than ymax", + bbox); + } + } + + public static class GeographyEvaluator implements GeospatialPredicateEvaluator { + /** + * Check if two bounding boxes intersect, taking wrap-around into account. + * + * <p>Wraparound (or antimeridian crossing) occurs when a geography crosses the 180°/-180° + * longitude line on a map. In these cases, the minimum X value is greater than the maximum X + * value (xmin > xmax). This represents a bounding box that wraps around the globe. + * + * <p>For example, a bounding box with xmin=170° and xmax=-170° represents an area that spans + * from 170° east to 190° east (or equivalently, -170° west). This is important for geometries + * that cross the antimeridian, like a path from Japan to Alaska. + * + * <p>When xmin > xmax, a point matches if its X coordinate is either X ≥ xmin OR X ≤ xmax, + * rather than the usual X ≥ xmin AND X ≤ xmax. In geographic terms, if the westernmost + * longitude is greater than the easternmost longitude, this indicates an antimeridian crossing. + * + * @param bbox1 the first bounding box + * @param bbox2 the second bounding box + * @return true if the bounding boxes intersect + */ + @Override + public boolean intersects(BoundingBox bbox1, BoundingBox bbox2) { + validateBoundingBox(bbox1); + validateBoundingBox(bbox2); + + if (!intersectsYZM(bbox1, bbox2)) { + return false; + } + + // Check X dimension (longitude/easting) - with wrap-around + return rangeIntersectsWithWrapAround( + bbox1.min().x(), bbox1.max().x(), bbox2.min().x(), bbox2.max().x()); + } + + /** + * For geography types, coordinates are restricted to the canonical ranges of [-180°, 180°] for + * longitude (X) and [-90°, 90°] for latitude (Y). + * + * @param bbox the bounding box to validate + * @throws IllegalArgumentException if the bounding box is invalid + */ + private void validateBoundingBox(BoundingBox bbox) { + Preconditions.checkArgument( + bbox.min().y() >= -90.0d + && bbox.min().y() <= 90.0d + && bbox.max().y() >= -90.0d + && bbox.max().y() <= 90.0d, + "Invalid latitude: %s. Out of range: [-90°, 90°]", + bbox); + Preconditions.checkArgument( + bbox.min().x() >= -180.0d + && bbox.min().x() <= 180.0d + && bbox.max().x() >= -180.0d + && bbox.max().x() <= 180.0d, + "Invalid longitude: %s. Out of range: [-180°, 180°]", + bbox); + Preconditions.checkArgument( + bbox.min().y() <= bbox.max().y(), + "Invalid latitude range: %s. ymin cannot be greater than ymax", + bbox); Review Comment: same as the previous comment -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
