Skip to content

Commit

Permalink
GIS #462: added $geoIntersects
Browse files Browse the repository at this point in the history
  • Loading branch information
danylokravchenko committed Dec 11, 2023
1 parent 96d0f6f commit ec21cda
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,11 @@ public enum Kind {
*/
MQL_EXISTS,

/**
* Document model {@code $geoIntersects} operator
*/
MQL_GEO_INTERSECTS,

/**
* Document model {@code $geoWithin} operator
*/
Expand Down Expand Up @@ -1589,6 +1594,7 @@ public enum Kind {
MQL_JSONIFY,
MQL_UPDATE,
MQL_EXISTS,
MQL_GEO_INTERSECTS,
MQL_GEO_WITHIN,
MQL_NEAR,
MQL_NEAR_SPHERE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ private void defineMongoMethods() {
defineMethod( OperatorRegistry.get( mongo, OperatorName.MQL_REPLACE_ROOT ), BuiltInMethod.MQL_REPLACE_ROOT.method, NullPolicy.STRICT );
defineMethod( OperatorRegistry.get( mongo, OperatorName.MQL_NOT_UNSET ), BuiltInMethod.MQL_NOT_UNSET.method, NullPolicy.STRICT );

defineImplementor( OperatorRegistry.get( mongo, OperatorName.MQL_GEO_INTERSECTS ), NullPolicy.NONE, new MethodImplementor( BuiltInMethod.MQL_GEO_INTERSECTS.method ), false );
defineImplementor( OperatorRegistry.get( mongo, OperatorName.MQL_GEO_WITHIN ), NullPolicy.NONE, new MethodImplementor( BuiltInMethod.MQL_GEO_WITHIN.method ), false );
defineImplementor( OperatorRegistry.get( mongo, OperatorName.MQL_NEAR ), NullPolicy.NONE, new MethodImplementor( BuiltInMethod.MQL_NEAR.method ), false );
defineImplementor( OperatorRegistry.get( mongo, OperatorName.MQL_NEAR_SPHERE ), NullPolicy.NONE, new MethodImplementor( BuiltInMethod.MQL_NEAR_SPHERE.method ), false );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,8 @@ public enum OperatorName {

MQL_NOT_UNSET( LangFunctionOperator.class ),

MQL_GEO_INTERSECTS( LangFunctionOperator.class ),

MQL_GEO_WITHIN( LangFunctionOperator.class ),

MQL_NEAR( LangFunctionOperator.class ),
Expand Down
46 changes: 36 additions & 10 deletions core/src/main/java/org/polypheny/db/functions/MqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -722,24 +722,36 @@ public static PolyBoolean docExists( PolyValue obj, PolyValue opIfExists, List<P
}


/**
* Tests if the object/document is $geoIntersects the provided geometry
*
* @param input the object/document
* @param geometry the $geometry to test if it is $geoIntersects. In GeoJSON format
* @return <code>TRUE</code> if the provided object/document is $geoIntersects the given geometry
*/
@SuppressWarnings("UnusedDeclaration")
public static PolyBoolean docGeoIntersects( PolyValue input, PolyValue geometry ) {
PolyGeometry inputGeom = validateAndCreateGeometry( input );
PolyGeometry geom = validateAndCreateGeometry( geometry );
if ( inputGeom == null || geom == null ) {
return PolyBoolean.FALSE;
}
return PolyBoolean.of( inputGeom.intersects( geom ) );
}


/**
* Tests if the object/document is $geoWithin the provided geometry
*
* @param input the object/document
* @param geometry the $geometry to test if it is $geoWithin. In GeoJSON format
* @return <code>TRUE</code> if the provided object/document is $near the given geometry
* @return <code>TRUE</code> if the provided object/document is $geoWithin the given geometry
*/
@SuppressWarnings("UnusedDeclaration")
public static PolyBoolean docGeoWithin( PolyValue input, PolyValue geometry ) {
if ( input == null || !input.isDocument() || !geometry.isDocument() ) {
return PolyBoolean.FALSE;
}
PolyGeometry inputGeom;
PolyGeometry geom;
try {
inputGeom = PolyGeometry.fromGeoJson( input.asDocument().toJson() );
geom = PolyGeometry.fromGeoJson( geometry.asDocument().toJson() );
} catch ( InvalidGeometryException e ) {
PolyGeometry inputGeom = validateAndCreateGeometry( input );
PolyGeometry geom = validateAndCreateGeometry( geometry );
if ( inputGeom == null || geom == null ) {
return PolyBoolean.FALSE;
}
return PolyBoolean.of( inputGeom.within( geom ) );
Expand Down Expand Up @@ -850,6 +862,20 @@ private static Object transformBsonToPrimitive( BsonValue doc ) {
}


private static PolyGeometry validateAndCreateGeometry( PolyValue input ) {
if ( input == null || !input.isDocument() ) {
return null;
}
PolyGeometry geom;
try {
geom = PolyGeometry.fromGeoJson( input.asDocument().toJson() );
} catch ( InvalidGeometryException e ) {
return null;
}
return geom;
}


/**
* Calculate the distance between 2 {@link PolyGeometry}.
* See {@link PolyGeometry#distance}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,16 @@ public static PolyGeometry fromTWKB( String twkb, int srid ) throws InvalidGeome
}


public static PolyGeometry fromNullableGeoJson( String geoJson ) {
try {
return geoJson == null ? null : fromGeoJson( geoJson );
} catch ( InvalidGeometryException e ) {
// hack to deal that InvalidGeometryException is not caught in code generation
return null;
}
}


public static PolyGeometry fromGeoJson( String geoJson ) throws InvalidGeometryException {
return fromGeoJson( geoJson, WGS_84 );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ public enum BuiltInMethod {
MQL_EXISTS( MqlFunctions.class, "docExists", PolyValue.class, PolyValue.class, List.class ),
MQL_MERGE( MqlFunctions.class, "mergeDocument", PolyValue.class, PolyList.class, PolyValue[].class ),
MQL_NOT_UNSET( MqlFunctions.class, "notUnset", PolyValue.class ),
MQL_GEO_INTERSECTS( MqlFunctions.class, "docGeoIntersects", PolyValue.class, PolyValue.class ),
MQL_GEO_WITHIN( MqlFunctions.class, "docGeoWithin", PolyValue.class, PolyValue.class ),
MQL_NEAR( MqlFunctions.class, "docNear", PolyValue.class, PolyValue.class, PolyValue.class, PolyValue.class ),
MQL_NEAR_SPHERE( MqlFunctions.class, "docNearSphere", PolyValue.class, PolyValue.class, PolyValue.class, PolyValue.class ),
Expand Down
29 changes: 29 additions & 0 deletions dbms/src/test/java/org/polypheny/db/mql/FindTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,35 @@ public void sizeTest() {
true ) );
}


// geoIntersects

@Test
public void geoIntersectsTest() {
insertMany( DATA_6 );

DocResult result = find(
document(
kv( string( "location" ), document(
kv( string( "$geoIntersects" ), document(
kv( string( "$geometry" ), document(
kv( string( "type" ), string( "Polygon" ) ),
kv( string( "coordinates" ), "[ [ [ 9.289382 48.741588 ], [10.289382 47.741588], [9.289382 47.741588], [9.289382 48.741588] ] ]" ) )
)
) ) ) ) )
, "{}" );

assertTrue(
MongoConnection.checkDocResultSet(
result,
ImmutableList.of(
"{\"location\": { \"type\": \"Point\", \"coordinates\": [ 7.852923, 47.998949 ] }, \"key\": 3}",
"{\"location\": { \"type\": \"Point\", \"coordinates\": [ 9.289382, 48.741588 ] }, \"key\": 2}" ),
true,
true ) );
}


// geoWithin

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,8 @@ private static Expression getOfPolyExpression( AlgDataType fieldType, Expression
poly = Expressions.call( PolyBlob.class, fieldType.isNullable() ? "ofNullable" : "of", Expressions.convert_( source, byte[].class ) );
break;
case GEOMETRY:
// TODO: check how it will work with GeoJson and SRID since we are using multiple params
if ( dialect.supportsGeoJson() ) {
poly = Expressions.call( PolyGeometry.class, "fromGeoJson", Expressions.convert_( source, String.class ), Expressions.convert_( Expressions.field( source, "SRID" ), Number.class ) );
poly = Expressions.call( PolyGeometry.class, fieldType.isNullable() ? "fromNullableGeoJson": "fromGeoJson", Expressions.convert_( source, String.class ) );
} else {
poly = Expressions.call( PolyGeometry.class, fieldType.isNullable() ? "ofNullable" : "of", Expressions.convert_( source, String.class ) );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,9 @@ private void translateMatch2( RexNode node ) {
case MQL_EXISTS:
translateExists( (RexCall) node );
return;
case MQL_GEO_INTERSECTS:
translateGeoIntersects( (RexCall) node );
return;
case MQL_GEO_WITHIN:
translateGeoWithin( (RexCall) node );
return;
Expand Down Expand Up @@ -686,6 +689,28 @@ private void translateSize( RexCall node ) {
}


/**
* Translates a {@link Kind#MQL_GEO_INTERSECTS } to its form:
* <pre>
* { <field>: { $geoIntersects: { $geometry: {<GeoJSON>} } }
* </pre>
*
* @param node the untranslated node
*/
private void translateGeoIntersects( RexCall node ) {
if ( node.operands.size() != 2 ) {
return;
}
// field
String left = getParamAsKey( node.operands.get( 0 ) );
// $geometry
BsonValue geometry = getParamAsValue( node.operands.get( 1 ) );
attachCondition( null, left, new BsonDocument()
.append( "$geoIntersects", new BsonDocument()
.append( "$geometry", geometry ) ) );
}


/**
* Translates a {@link Kind#MQL_GEO_WITHIN } to its form:
* <pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ public static void registerOperators() {

register( OperatorName.MQL_PROJECT_INCLUDES, new LangFunctionOperator( OperatorName.MQL_PROJECT_INCLUDES.name(), Kind.OTHER ) );

register( OperatorName.MQL_GEO_INTERSECTS, new LangFunctionOperator( "MQL_GEO_INTERSECTS", Kind.MQL_GEO_INTERSECTS ) );

register( OperatorName.MQL_GEO_WITHIN, new LangFunctionOperator( "MQL_GEO_WITHIN", Kind.MQL_GEO_WITHIN ) );

register( OperatorName.MQL_NEAR, new LangFunctionOperator( "MQL_NEAR", Kind.MQL_NEAR ) );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ public class MqlToAlgConverter {
operators.add( "$all" );
operators.add( "$elemMatch" );
operators.add( "$size" );
operators.add( "$geoIntersects" );
operators.add( "$geoWithin" );
operators.add( "$near" );
operators.add( "$nearSphere" );
Expand Down Expand Up @@ -1295,6 +1296,8 @@ private RexNode convertEntry( String key, String parentKey, BsonValue bsonValue,
return convertElemMatch( bsonValue, parentKey, rowType );
case "$size":
return convertSize( bsonValue, parentKey, rowType );
case "$geoIntersects":
return convertGeoIntersects( bsonValue, parentKey, rowType );
case "$geoWithin":
return convertGeoWithin( bsonValue, parentKey, rowType );
case "$near":
Expand Down Expand Up @@ -1919,6 +1922,37 @@ private RexNode convertType( BsonValue value, String parentKey, AlgDataType rowT
}


/**
* Converts a $geoIntersects filter field
* <pre>
* { <field>: { $geoIntersects: { $geometry: {<GeoJSON>} } }
* </pre>
*
* @param bson the $geoIntersects information as BSON,
* which is a document with the necessary keys ($geoIntersects.$geometry)
* @param parentKey the key of the parent document
* @param rowType the rowType of the node which is filtered by $geoIntersects
* @return the filtered node
*/
private RexNode convertGeoIntersects( BsonValue bson, String parentKey, AlgDataType rowType ) {
if ( !bson.isDocument() ) {
throw new GenericRuntimeException( "$geoIntersects has to be a document." );
}
BsonDocument geoIntersects = bson.asDocument();
if ( !geoIntersects.containsKey( "$geometry" ) || !geoIntersects.get( "$geometry" ).isDocument() ) {
throw new GenericRuntimeException( "$geoIntersects.$geometry has to be a document." );
}
BsonDocument geometry = geoIntersects.get( "$geometry" ).asDocument();

return new RexCall(
cluster.getTypeFactory().createPolyType( PolyType.BOOLEAN ),
OperatorRegistry.get( QueryLanguage.from( MONGO ), OperatorName.MQL_GEO_INTERSECTS ),
Arrays.asList(
getIdentifier( parentKey, rowType ),
convertLiteral( geometry ) ));
}


/**
* Converts a $geoWithin filter field
* <pre>
Expand Down

0 comments on commit ec21cda

Please sign in to comment.