Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add all-active ingestion as docrep equivalent in pull-based ingestion ([#19316](https://github.com/opensearch-project/OpenSearch/pull/19316))
- Adding logic for histogram aggregation using skiplist ([#19130](https://github.com/opensearch-project/OpenSearch/pull/19130))
- Add skip_list param for date, scaled float and token count fields ([#19142](https://github.com/opensearch-project/OpenSearch/pull/19142))
- Implement GRPC GeoBoundingBox, GeoDistance queries ([#19451](https://github.com/opensearch-project/OpenSearch/pull/19451))

### Changed
- Refactor `if-else` chains to use `Java 17 pattern matching switch expressions`(([#18965](https://github.com/opensearch-project/OpenSearch/pull/18965))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
package org.opensearch.transport.grpc.proto.request.common;

import org.opensearch.common.geo.GeoPoint;
import org.opensearch.common.geo.GeoUtils;
import org.opensearch.protobufs.GeoLocation;

/**
* Utility class for parsing Protocol Buffer GeoLocation objects into OpenSearch GeoPoint objects.
* This class provides shared functionality for converting protobuf geo location representations
* into their corresponding OpenSearch GeoPoint implementations.
*
* @opensearch.internal
*/
public class GeoPointProtoUtils {

private GeoPointProtoUtils() {
// Utility class, no instances
}

/**
* Parses a Protocol Buffer GeoLocation into an OpenSearch GeoPoint.
* Supports multiple geo location formats:
* <ul>
* <li>Lat/Lon objects: {@code {lat: 37.7749, lon: -122.4194}}</li>
* <li>Geohash: {@code "9q8yyk0"}</li>
* <li>Double arrays: {@code [lon, lat]} or {@code [lon, lat, z]}</li>
* <li>Text formats: {@code "37.7749, -122.4194"} or {@code "POINT(-122.4194 37.7749)"}</li>
* </ul>
*
* @param geoLocation The Protocol Buffer GeoLocation to parse
* @return A GeoPoint object representing the parsed location
* @throws IllegalArgumentException if the geo location format is invalid or unsupported
*/
public static GeoPoint parseGeoPoint(GeoLocation geoLocation) {
GeoPoint point = new GeoPoint();
return parseGeoPoint(geoLocation, point, false, GeoUtils.EffectivePoint.BOTTOM_LEFT);
}

/**
* Parses a GeoLocation protobuf into a GeoPoint, following the same pattern as GeoUtils.parseGeoPoint().
* This method modifies the provided GeoPoint in-place and returns it.
*
* @param geoLocation the protobuf GeoLocation to parse
* @param point the GeoPoint to modify in-place
* @param ignoreZValue whether to ignore Z values (elevation)
* @param effectivePoint the effective point interpretation for coordinate ordering
* @return the same GeoPoint instance that was passed in (modified)
* @throws IllegalArgumentException if the GeoLocation format is invalid
*/
public static GeoPoint parseGeoPoint(
GeoLocation geoLocation,
GeoPoint point,
boolean ignoreZValue,
GeoUtils.EffectivePoint effectivePoint
) {

if (geoLocation.hasLatlon()) {
org.opensearch.protobufs.LatLonGeoLocation latLon = geoLocation.getLatlon();
point.resetLat(latLon.getLat());
point.resetLon(latLon.getLon());

} else if (geoLocation.hasDoubleArray()) {
org.opensearch.protobufs.DoubleArray doubleArray = geoLocation.getDoubleArray();
int count = doubleArray.getDoubleArrayCount();
if (count < 2) {
throw new IllegalArgumentException("[geo_point] field type should have at least two dimensions");
} else if (count > 3) {
throw new IllegalArgumentException("[geo_point] field type does not accept more than 3 values");
} else {
double lon = doubleArray.getDoubleArray(0);
double lat = doubleArray.getDoubleArray(1);
point.resetLat(lat);
point.resetLon(lon);
if (count == 3 && !ignoreZValue) {
// Z value is ignored as GeoPoint doesn't support elevation
GeoPoint.assertZValue(ignoreZValue, doubleArray.getDoubleArray(2));
}
}

} else if (geoLocation.hasText()) {
// String format: "lat,lon", WKT, or geohash
String val = geoLocation.getText();
point.resetFromString(val, ignoreZValue, effectivePoint);

} else if (geoLocation.hasGeohash()) {
org.opensearch.protobufs.GeoHashLocation geohashLocation = geoLocation.getGeohash();
point.resetFromGeoHash(geohashLocation.getGeohash());

} else {
throw new IllegalArgumentException("geo_point expected");
}

return point;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
package org.opensearch.transport.grpc.proto.request.search.query;

import org.opensearch.index.query.GeoBoundingBoxQueryBuilder;
import org.opensearch.protobufs.QueryContainer;
import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverter;

/**
* Protocol Buffer converter for GeoBoundingBoxQuery.
* This converter handles the transformation of Protocol Buffer GeoBoundingBoxQuery objects
* into OpenSearch GeoBoundingBoxQueryBuilder instances for geo bounding box search operations.
*
*/
public class GeoBoundingBoxQueryBuilderProtoConverter implements QueryBuilderProtoConverter {

/**
* Default constructor for GeoBoundingBoxQueryBuilderProtoConverter.
*/
public GeoBoundingBoxQueryBuilderProtoConverter() {
// Default constructor
}

@Override
public QueryContainer.QueryContainerCase getHandledQueryCase() {
return QueryContainer.QueryContainerCase.GEO_BOUNDING_BOX;
}

@Override
public GeoBoundingBoxQueryBuilder fromProto(QueryContainer queryContainer) {
if (queryContainer == null || queryContainer.getQueryContainerCase() != QueryContainer.QueryContainerCase.GEO_BOUNDING_BOX) {
throw new IllegalArgumentException("QueryContainer must contain a GeoBoundingBoxQuery");
}

return GeoBoundingBoxQueryBuilderProtoUtils.fromProto(queryContainer.getGeoBoundingBox());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
package org.opensearch.transport.grpc.proto.request.search.query;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.common.geo.GeoBoundingBox;
import org.opensearch.common.geo.GeoPoint;
import org.opensearch.common.geo.GeoUtils;
import org.opensearch.geometry.Geometry;
import org.opensearch.geometry.Rectangle;
import org.opensearch.geometry.ShapeType;
import org.opensearch.geometry.utils.StandardValidator;
import org.opensearch.geometry.utils.WellKnownText;
import org.opensearch.index.query.AbstractQueryBuilder;
import org.opensearch.index.query.GeoBoundingBoxQueryBuilder;
import org.opensearch.index.query.GeoExecType;
import org.opensearch.index.query.GeoValidationMethod;
import org.opensearch.protobufs.GeoBoundingBoxQuery;
import org.opensearch.protobufs.GeoBounds;
import org.opensearch.transport.grpc.proto.request.common.GeoPointProtoUtils;

import java.util.Locale;

/**
* Utility class for converting GeoBoundingBoxQuery Protocol Buffers to OpenSearch objects.
* This class provides methods to transform Protocol Buffer representations of geo bounding box queries
* into their corresponding OpenSearch GeoBoundingBoxQueryBuilder implementations for search operations.
*
*/
class GeoBoundingBoxQueryBuilderProtoUtils {

private static final Logger logger = LogManager.getLogger(GeoBoundingBoxQueryBuilderProtoUtils.class);

private GeoBoundingBoxQueryBuilderProtoUtils() {
// Utility class, no instances
}

/**
* Converts a Protocol Buffer GeoBoundingBoxQuery to an OpenSearch GeoBoundingBoxQueryBuilder.
* Similar to {@link GeoBoundingBoxQueryBuilder#fromXContent(org.opensearch.core.xcontent.XContentParser)}, this method
* parses the Protocol Buffer representation and creates a properly configured
* GeoBoundingBoxQueryBuilder with the appropriate field name, bounding box, and other parameters.
*
* @param geoBoundingBoxQueryProto The Protocol Buffer GeoBoundingBoxQuery to convert
* @return A configured GeoBoundingBoxQueryBuilder instance
*/
static GeoBoundingBoxQueryBuilder fromProto(GeoBoundingBoxQuery geoBoundingBoxQueryProto) {
if (geoBoundingBoxQueryProto == null) {
throw new IllegalArgumentException("GeoBoundingBoxQuery cannot be null");
}

if (geoBoundingBoxQueryProto.getBoundingBoxMap().isEmpty()) {
throw new IllegalArgumentException("GeoBoundingBoxQuery must have at least one bounding box");
}
String fieldName = geoBoundingBoxQueryProto.getBoundingBoxMap().keySet().iterator().next();
GeoBounds geoBounds = geoBoundingBoxQueryProto.getBoundingBoxMap().get(fieldName);

float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null;
GeoValidationMethod validationMethod = null;
boolean ignoreUnmapped = GeoBoundingBoxQueryBuilder.DEFAULT_IGNORE_UNMAPPED;
GeoBoundingBox bbox = null;
GeoExecType type = GeoExecType.MEMORY;

bbox = parseBoundingBox(geoBounds);

if (geoBoundingBoxQueryProto.hasXName()) {
queryName = geoBoundingBoxQueryProto.getXName();
}

if (geoBoundingBoxQueryProto.hasBoost()) {
boost = geoBoundingBoxQueryProto.getBoost();
}

if (geoBoundingBoxQueryProto.hasValidationMethod()) {
validationMethod = parseValidationMethod(geoBoundingBoxQueryProto.getValidationMethod());
}

if (geoBoundingBoxQueryProto.hasIgnoreUnmapped()) {
ignoreUnmapped = geoBoundingBoxQueryProto.getIgnoreUnmapped();
}

if (geoBoundingBoxQueryProto.hasType()) {
type = parseExecutionType(geoBoundingBoxQueryProto.getType());
}
GeoBoundingBoxQueryBuilder builder = new GeoBoundingBoxQueryBuilder(fieldName);
builder.setCorners(bbox.topLeft(), bbox.bottomRight());
builder.queryName(queryName);
builder.boost(boost);
builder.type(type);
builder.ignoreUnmapped(ignoreUnmapped);
if (validationMethod != null) {
builder.setValidationMethod(validationMethod);
}

return builder;
}

/**
* Parses a GeoBounds protobuf into a GeoBoundingBox object.
*
* @param geoBounds The Protocol Buffer GeoBounds to parse
* @return A GeoBoundingBox object
*/
private static GeoBoundingBox parseBoundingBox(GeoBounds geoBounds) {
double top = Double.NaN;
double bottom = Double.NaN;
double left = Double.NaN;
double right = Double.NaN;

Rectangle envelope = null;

// Create GeoPoint instances for reuse (matching GeoBoundingBox.parseBoundingBox pattern)
GeoPoint sparse = new GeoPoint();

if (geoBounds.hasCoords()) {
org.opensearch.protobufs.CoordsGeoBounds coords = geoBounds.getCoords();
top = coords.getTop();
bottom = coords.getBottom();
left = coords.getLeft();
right = coords.getRight();

} else if (geoBounds.hasTlbr()) {
org.opensearch.protobufs.TopLeftBottomRightGeoBounds tlbr = geoBounds.getTlbr();
GeoPointProtoUtils.parseGeoPoint(tlbr.getTopLeft(), sparse, false, GeoUtils.EffectivePoint.BOTTOM_LEFT);
top = sparse.getLat();
left = sparse.getLon();
GeoPointProtoUtils.parseGeoPoint(tlbr.getBottomRight(), sparse, false, GeoUtils.EffectivePoint.BOTTOM_LEFT);
bottom = sparse.getLat();
right = sparse.getLon();

} else if (geoBounds.hasTrbl()) {
org.opensearch.protobufs.TopRightBottomLeftGeoBounds trbl = geoBounds.getTrbl();
GeoPointProtoUtils.parseGeoPoint(trbl.getTopRight(), sparse, false, GeoUtils.EffectivePoint.BOTTOM_LEFT);
top = sparse.getLat();
right = sparse.getLon();
GeoPointProtoUtils.parseGeoPoint(trbl.getBottomLeft(), sparse, false, GeoUtils.EffectivePoint.BOTTOM_LEFT);
bottom = sparse.getLat();
left = sparse.getLon();

} else if (geoBounds.hasWkt()) {
// WKT format bounds
org.opensearch.protobufs.WktGeoBounds wkt = geoBounds.getWkt();
try {
// Parse WKT using the same approach as GeoBoundingBox.parseBoundingBox
WellKnownText wktParser = new WellKnownText(true, new StandardValidator(true));
Geometry geometry = wktParser.fromWKT(wkt.getWkt());
if (!ShapeType.ENVELOPE.equals(geometry.type())) {
throw new IllegalArgumentException(
String.format(Locale.ROOT, GeoUtils.WKT_BOUNDING_BOX_TYPE_ERROR, geometry.type(), ShapeType.ENVELOPE)
);
}
envelope = (Rectangle) geometry;
} catch (Exception e) {
throw new IllegalArgumentException(GeoUtils.WKT_BOUNDING_BOX_PARSE_ERROR + ": " + wkt.getWkt(), e);
}

} else {
throw new IllegalArgumentException("GeoBounds must have one of: coords, tlbr, trbl, or wkt");
}
if (envelope != null) {
if (Double.isNaN(top) == false
|| Double.isNaN(bottom) == false
|| Double.isNaN(left) == false
|| Double.isNaN(right) == false) {
throw new IllegalArgumentException(
"failed to parse bounding box. Conflicting definition found " + "using well-known text and explicit corners."
);
}
GeoPoint topLeft = new GeoPoint(envelope.getMaxLat(), envelope.getMinLon());
GeoPoint bottomRight = new GeoPoint(envelope.getMinLat(), envelope.getMaxLon());
return new GeoBoundingBox(topLeft, bottomRight);
}

GeoPoint topLeft = new GeoPoint(top, left);
GeoPoint bottomRight = new GeoPoint(bottom, right);
return new GeoBoundingBox(topLeft, bottomRight);
}

/**
* Converts protobuf GeoExecution to OpenSearch GeoExecType.
*
* @param executionTypeProto the protobuf GeoExecution
* @return the converted OpenSearch GeoExecType
*/
private static GeoExecType parseExecutionType(org.opensearch.protobufs.GeoExecution executionTypeProto) {
switch (executionTypeProto) {
case GEO_EXECUTION_MEMORY:
return GeoExecType.MEMORY;
case GEO_EXECUTION_INDEXED:
return GeoExecType.INDEXED;
default:
return GeoExecType.MEMORY; // Default value
}
}

/**
* Converts protobuf GeoValidationMethod to OpenSearch GeoValidationMethod.
*
* @param validationMethodProto the protobuf GeoValidationMethod
* @return the converted OpenSearch GeoValidationMethod
*/
private static GeoValidationMethod parseValidationMethod(org.opensearch.protobufs.GeoValidationMethod validationMethodProto) {
switch (validationMethodProto) {
case GEO_VALIDATION_METHOD_COERCE:
return GeoValidationMethod.COERCE;
case GEO_VALIDATION_METHOD_IGNORE_MALFORMED:
return GeoValidationMethod.IGNORE_MALFORMED;
case GEO_VALIDATION_METHOD_STRICT:
return GeoValidationMethod.STRICT;
default:
return GeoValidationMethod.STRICT; // Default value
}
}

}
Loading
Loading