Commit f9fbbec3 authored by gmantele's avatar gmantele Committed by Grégory Mantelet
Browse files

[ADQL] Allow also `CENTROID` and UDF as arguments of `DISTANCE` (2-arg form)

& Allow UDF as valid geometry argument (a UDF being able to return geometries)
like in functions `AREA`, `CENTROID`, `DISTANCE`, ...
parent ade0302b
Loading
Loading
Loading
Loading
+11 −4
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package adql.parser;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

@@ -92,7 +92,7 @@ import adql.query.operand.function.string.LowerFunction;
 * </p>
 *
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.0 (11/2019)
 * @version 2.0 (04/2020)
 *
 * @see ADQLParser
 */
@@ -349,11 +349,18 @@ public class ADQLQueryFactory {
		return new DefaultUDF(name, params);
	}

	/** @deprecated Since 2.0, prefer to use directly {@link #createDistance(GeometryFunction, GeometryFunction)} */
	@Deprecated
	public DistanceFunction createDistance(PointFunction point1, PointFunction point2) throws Exception {
		return new DistanceFunction(new GeometryValue<PointFunction>(point1), new GeometryValue<PointFunction>(point2));
		return createDistance((GeometryFunction)point1, (GeometryFunction)point2);
	}

	/** @since 2.0 */
	public DistanceFunction createDistance(GeometryFunction point1, GeometryFunction point2) throws Exception {
		return new DistanceFunction(new GeometryValue<GeometryFunction>(point1), new GeometryValue<GeometryFunction>(point2));
	}

	public DistanceFunction createDistance(GeometryValue<PointFunction> point1, GeometryValue<PointFunction> point2) throws Exception {
	public DistanceFunction createDistance(GeometryValue<GeometryFunction> point1, GeometryValue<GeometryFunction> point2) throws Exception {
		return new DistanceFunction(point1, point2);
	}

+5 −5
Original line number Diff line number Diff line
@@ -1183,7 +1183,7 @@ ADQLOperand[] Coordinates(): {ADQLOperand[] ops = new ADQLOperand[2];} {
	{return ops;}
}

GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<GeometryFunction> gvf1, gvf2; GeometryValue<PointFunction> gvp1, gvp2; GeometryFunction gf = null; PointFunction p1=null, p2=null; ADQLColumn col1 = null, col2 = null;} {
GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<GeometryFunction> gvf1, gvf2; GeometryValue<GeometryFunction> gvp1, gvp2; GeometryFunction gf = null; PointFunction p1=null, p2=null; ADQLColumn col1 = null, col2 = null;} {
	try{
		// predicate_geometry_function
		(
@@ -1203,20 +1203,20 @@ GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<Geometr
				(p1=Point()|col1=Column()) 
				{
					if (p1 != null)
						gvp1 = new GeometryValue<PointFunction>(p1);
						gvp1 = new GeometryValue<GeometryFunction>(p1);
					else{
						col1.setExpectedType('G');
						gvp1 = new GeometryValue<PointFunction>(col1);
						gvp1 = new GeometryValue<GeometryFunction>(col1);
					}
				}
				<COMMA>
				(p2=Point()|col2=Column())
				{
					if (p2 != null)
						gvp2 = new GeometryValue<PointFunction>(p2);
						gvp2 = new GeometryValue<GeometryFunction>(p2);
					else{
						col2.setExpectedType('G');
						gvp2 = new GeometryValue<PointFunction>(col2);
						gvp2 = new GeometryValue<GeometryFunction>(col2);
					}
				} 
				end=<RIGHT_PAR>
+37 −17
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@
* ParseException is thrown.
*
* Author:  Gr&eacute;gory Mantelet (CDS)
* Version: 2.0 (03/2020)
* Version: 2.0 (04/2020)
*/

							/* ########### */
@@ -107,7 +107,7 @@ import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;
 * @see ADQLParser
 *
 * @author Gr&eacute;gory Mantelet (CDS)
 * @version 2.0 (03/2020)
 * @version 2.0 (04/2020)
 * @since 2.0
 */
public class ADQLGrammar201 extends ADQLGrammarBase {
@@ -1030,12 +1030,15 @@ ADQLOperand StringFactor(): {ADQLOperand op;} {
	{return op;}
}

GeometryValue<GeometryFunction> GeometryExpression(): {ADQLColumn col = null; GeometryFunction gf = null;} {
	(col=Column() | gf=GeometryValueFunction())
GeometryValue<GeometryFunction> GeometryExpression(): {ADQLColumn col = null; UserDefinedFunction udf = null; GeometryFunction gf = null;} {
	( gf=GeometryValueFunction() | LOOKAHEAD(2) udf=UserDefinedFunction() | col=Column())
	{
		if (col != null){
		  	col.setExpectedType('G');
			return new GeometryValue<GeometryFunction>(col);
		}else if (udf != null){
			udf.setExpectedType('G');
			return new GeometryValue<GeometryFunction>(udf);
		}else
			return new GeometryValue<GeometryFunction>(gf);
	}
@@ -1277,7 +1280,7 @@ GeometryFunction GeometryFunction(): {Token fct=null, end=null; GeometryValue<Ge
	}
}

DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunction gf; ADQLOperand lon, lat; GeometryValue<PointFunction> gvp1, gvp2; } {
DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunction gf; ADQLOperand lon, lat; GeometryValue<GeometryFunction> gvp1, gvp2; } {
	try {
		// DISTANCE(POINT,POINT)
		(LOOKAHEAD(DistanceFunction2())
@@ -1286,10 +1289,10 @@ DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunctio
		// DISTANCE(lon1, lat1, lon2, lat2)
			fct=<DISTANCE> <LEFT_PAR>
			lon=NumericExpression() <COMMA> lat=NumericExpression() 
			{ gvp1 = new GeometryValue<PointFunction>(queryFactory.createPoint(null, lon, lat)); }
			{ gvp1 = new GeometryValue<GeometryFunction>(queryFactory.createPoint(null, lon, lat)); }
			<COMMA>
			lon=NumericExpression() <COMMA> lat=NumericExpression() 
			{ gvp2 = new GeometryValue<PointFunction>(queryFactory.createPoint(null, lon, lat)); } 
			{ gvp2 = new GeometryValue<GeometryFunction>(queryFactory.createPoint(null, lon, lat)); } 
			end=<RIGHT_PAR>
			{
				gf = queryFactory.createDistance(gvp1, gvp2);
@@ -1302,26 +1305,32 @@ DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunctio
	}
}

DistanceFunction DistanceFunction2(): { Token fct=null, end=null; DistanceFunction gf; GeometryValue<PointFunction> gvp1, gvp2; PointFunction p1=null, p2=null; ADQLColumn col1=null, col2=null; } {
DistanceFunction DistanceFunction2(): { Token fct=null, end=null; DistanceFunction gf; GeometryValue<GeometryFunction> gvp1, gvp2; GeometryFunction p1=null, p2=null; ADQLColumn col1=null, col2=null; UserDefinedFunction udf1=null, udf2=null; } {
	try {
		fct=<DISTANCE> <LEFT_PAR>
		(p1=Point()|col1=Column()) 
		(p1=Point()|p1=Centroid()|LOOKAHEAD(2) udf1=UserDefinedFunction()|col1=Column()) 
		{
			if (p1 != null)
				gvp1 = new GeometryValue<PointFunction>(p1);
			else{
				gvp1 = new GeometryValue<GeometryFunction>(p1);
			else if (udf1 != null){
			  	udf1.setExpectedType('G');
				gvp1 = new GeometryValue<GeometryFunction>(udf1);
			}else{
				col1.setExpectedType('G');
				gvp1 = new GeometryValue<PointFunction>(col1);
				gvp1 = new GeometryValue<GeometryFunction>(col1);
			}
		}
		<COMMA>
		(p2=Point()|col2=Column())
		(p2=Point()|p2=Centroid()|LOOKAHEAD(2) udf2=UserDefinedFunction()|col2=Column())
		{
			if (p2 != null)
				gvp2 = new GeometryValue<PointFunction>(p2);
			else{
				gvp2 = new GeometryValue<GeometryFunction>(p2);
			else if (udf2 != null){
			  	udf2.setExpectedType('G');
				gvp2 = new GeometryValue<GeometryFunction>(udf2);
			}else{
				col2.setExpectedType('G');
				gvp2 = new GeometryValue<PointFunction>(col2);
				gvp2 = new GeometryValue<GeometryFunction>(col2);
			}
		} 
		end=<RIGHT_PAR>
@@ -1353,7 +1362,7 @@ GeometryFunction GeometryValueFunction(): {Token fct=null, end=null; ADQLOperand
		 {gf = queryFactory.createBox(null, coords[0], coords[1], width, height);}
		
		// CENTROID:
		| (fct=<CENTROID> <LEFT_PAR> gvf=GeometryExpression() end=<RIGHT_PAR>) {gf = queryFactory.createCentroid(gvf);}
		| gf=Centroid()

		// CIRCLE (deprecated since ADQL-2.1)
		| LOOKAHEAD(CircleWithCooSys()) gf=CircleWithCooSys()
@@ -1424,6 +1433,17 @@ GeometryFunction CircleWithCooSys(): { Token fct=null, end=null; ADQLOperand coo
	}
}

CentroidFunction Centroid(): {Token fct=null, end=null; GeometryValue<GeometryFunction> gvf = null; } {
	(fct=<CENTROID> <LEFT_PAR> gvf=GeometryExpression() end=<RIGHT_PAR>)
	{
		try {
	  		return queryFactory.createCentroid(gvf);
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

PointFunction Point(): {Token start, end; ADQLOperand[] coords; PointFunction pf;} {
	// POINT (depecrated since ADQL-2.1)
	(LOOKAHEAD(PointWithCooSys())
+29 −20
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package adql.query.operand.function.geometry;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

@@ -39,6 +39,15 @@ import adql.query.operand.ADQLOperand;
 * 	accepts four separate numeric values.
 * </p>
 *
 * <p><i><b>Implementation note:</b>
 * 	In this current implementation, the 2-argument form allows 2 geometries
 * 	instead of 2 points. The goal is to be more generic. POINT is supposed to
 * 	be the main expected type of argument, but it could also be a CENTROID
 * 	(which returns a POINT). Moreover, some extension of this library might
 * 	want to support DISTANCE between any type of geometries instead of just
 * 	points.
 * </i></p>
 *
 * <p>
 * 	If an ADQL service implementation declares support for DISTANCE, then it
 * 	must implement both the two parameter and four parameter forms of the
@@ -67,8 +76,8 @@ import adql.query.operand.ADQLOperand;
 * <i>
 * <p><b>Example:</b></p>
 * <p>
 * 	The distance between to points stored in the database could be calculated as
 * 	follows:
 * 	The distance between two points stored in the database could be calculated
 * 	as follows:
 * </p>
 * <pre>DISTANCE(t1.base, t2.target)</pre>
 * <p>
@@ -92,7 +101,7 @@ import adql.query.operand.ADQLOperand;
 * </p>
 *
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.0 (07/2019)
 * @version 2.0 (04/2020)
 */
public class DistanceFunction extends GeometryFunction {

@@ -101,10 +110,10 @@ public class DistanceFunction extends GeometryFunction {
	public static final LanguageFeature FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_GEO, "DISTANCE", true, "Compute the arc length along a great circle between two points and returns a numeric value expression in degrees.");

	/** The first point. */
	private GeometryValue<PointFunction> p1;
	private GeometryValue<GeometryFunction> p1;

	/** The second point. */
	private GeometryValue<PointFunction> p2;
	private GeometryValue<GeometryFunction> p2;

	/**
	 * Builds a DISTANCE function.
@@ -113,7 +122,7 @@ public class DistanceFunction extends GeometryFunction {
	 * @param point2				The second point.
	 * @throws NullPointerException	If one of the parameters are incorrect.
	 */
	public DistanceFunction(GeometryValue<PointFunction> point1, GeometryValue<PointFunction> point2) throws NullPointerException {
	public DistanceFunction(GeometryValue<GeometryFunction> point1, GeometryValue<GeometryFunction> point2) throws NullPointerException {
		super();
		if (point1 == null || point2 == null)
			throw new NullPointerException("All parameters of the DISTANCE function must be different from null!");
@@ -131,8 +140,8 @@ public class DistanceFunction extends GeometryFunction {
	@SuppressWarnings("unchecked")
	public DistanceFunction(DistanceFunction toCopy) throws Exception {
		super(toCopy);
		p1 = (GeometryValue<PointFunction>)(toCopy.p1.getCopy());
		p2 = (GeometryValue<PointFunction>)(toCopy.p2.getCopy());
		p1 = (GeometryValue<GeometryFunction>)(toCopy.p1.getCopy());
		p2 = (GeometryValue<GeometryFunction>)(toCopy.p2.getCopy());
	}

	@Override
@@ -175,7 +184,7 @@ public class DistanceFunction extends GeometryFunction {
	 *
	 * @return A point.
	 */
	public final GeometryValue<PointFunction> getP1() {
	public final GeometryValue<GeometryFunction> getP1() {
		return p1;
	}

@@ -184,7 +193,7 @@ public class DistanceFunction extends GeometryFunction {
	 *
	 * @param p1 A point.
	 */
	public final void setP1(GeometryValue<PointFunction> p1) {
	public final void setP1(GeometryValue<GeometryFunction> p1) {
		this.p1 = p1;
		setPosition(null);
	}
@@ -194,7 +203,7 @@ public class DistanceFunction extends GeometryFunction {
	 *
	 * @return A point.
	 */
	public final GeometryValue<PointFunction> getP2() {
	public final GeometryValue<GeometryFunction> getP2() {
		return p2;
	}

@@ -203,7 +212,7 @@ public class DistanceFunction extends GeometryFunction {
	 *
	 * @param p2 A point.
	 */
	public final void setP2(GeometryValue<PointFunction> p2) {
	public final void setP2(GeometryValue<GeometryFunction> p2) {
		this.p2 = p2;
		setPosition(null);
	}
@@ -235,23 +244,23 @@ public class DistanceFunction extends GeometryFunction {
	public ADQLOperand setParameter(int index, ADQLOperand replacer) throws ArrayIndexOutOfBoundsException, NullPointerException, Exception {
		if (replacer == null)
			throw new NullPointerException("Impossible to remove a parameter from the function " + getName() + "!");
		else if (!(replacer instanceof GeometryValue || replacer instanceof ADQLColumn || replacer instanceof PointFunction))
			throw new Exception("Impossible to replace a GeometryValue/Column/PointFunction by " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
		else if (!(replacer instanceof GeometryValue || replacer instanceof ADQLColumn || replacer instanceof GeometryFunction))
			throw new Exception("Impossible to replace a GeometryValue/Column/GeometryFunction by " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");

		ADQLOperand replaced = null;
		GeometryValue<PointFunction> toUpdate = null;
		GeometryValue<GeometryFunction> toUpdate = null;
		switch(index) {
			case 0:
				replaced = p1.getValue();
				if (replacer instanceof GeometryValue)
					p1 = (GeometryValue<PointFunction>)replacer;
					p1 = (GeometryValue<GeometryFunction>)replacer;
				else
					toUpdate = p1;
				break;
			case 1:
				replaced = p2.getValue();
				if (replacer instanceof GeometryValue)
					p2 = (GeometryValue<PointFunction>)replacer;
					p2 = (GeometryValue<GeometryFunction>)replacer;
				else
					toUpdate = p2;
				break;
@@ -262,8 +271,8 @@ public class DistanceFunction extends GeometryFunction {
		if (toUpdate != null) {
			if (replacer instanceof ADQLColumn)
				toUpdate.setColumn((ADQLColumn)replacer);
			else if (replacer instanceof PointFunction)
				toUpdate.setGeometry((PointFunction)replacer);
			else if (replacer instanceof GeometryFunction)
				toUpdate.setGeometry((GeometryFunction)replacer);
		}

		setPosition(null);
+35 −8
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package adql.query.operand.function.geometry;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

@@ -29,6 +29,7 @@ import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand;
import adql.query.operand.StringConstant;
import adql.query.operand.function.ADQLFunction;
import adql.query.operand.function.UserDefinedFunction;

/**
 * It represents any geometric function of ADQL.
@@ -109,14 +110,20 @@ public abstract class GeometryFunction extends ADQLFunction {

	/**
	 * This class represents a parameter of a geometry function
	 * which, in general, is either a GeometryFunction or a Column.
	 * which, in general, is either a GeometryFunction, a Column or a
	 * UserDefinedFunction.
	 *
	 * @author Gr&eacute;gory Mantelet (CDS;ARI)
	 * @version 2.0 (07/2019)
	 * @version 2.0 (04/2020)
	 */
	public static final class GeometryValue<F extends GeometryFunction> implements ADQLOperand {

		private ADQLColumn column;
		private F geomFunct;

		/** @since 2.0 */
		private UserDefinedFunction udf;

		/** Position of this {@link GeometryValue} in the ADQL query string.
		 * @since 1.4 */
		private TextPosition position = null;
@@ -125,16 +132,19 @@ public abstract class GeometryFunction extends ADQLFunction {
			if (col == null)
				throw new NullPointerException("Impossible to build a GeometryValue without a column or a geometry function!");
			setColumn(col);
			if (col.getPosition() != null)
				position = col.getPosition();
		}

		public GeometryValue(F geometry) throws NullPointerException {
			if (geometry == null)
				throw new NullPointerException("Impossible to build a GeometryValue without a column or a geometry function!");
			setGeometry(geometry);
			if (geometry.getPosition() != null)
				position = geometry.getPosition();
		}

		/** @since 2.0 */
		public GeometryValue(UserDefinedFunction udf) throws NullPointerException {
			if (udf == null)
				throw new NullPointerException("Impossible to build a GeometryValue without a column, a geometry function or User Defined Function!");
			setUDF(udf);
		}

		@SuppressWarnings("unchecked")
@@ -151,6 +161,7 @@ public abstract class GeometryFunction extends ADQLFunction {

		public void setColumn(ADQLColumn col) {
			if (col != null) {
				udf = null;
				geomFunct = null;
				column = col;
				position = (column.getPosition() != null) ? column.getPosition() : null;
@@ -159,14 +170,30 @@ public abstract class GeometryFunction extends ADQLFunction {

		public void setGeometry(F geometry) {
			if (geometry != null) {
				udf = null;
				column = null;
				geomFunct = geometry;
				position = (geomFunct.getPosition() != null) ? geomFunct.getPosition() : null;
			}
		}

		/** @since 2.0 */
		public void setUDF(UserDefinedFunction udf) {
			if (udf != null) {
				column = null;
				geomFunct = null;
				this.udf = udf;
				position = (udf.getPosition() != null) ? udf.getPosition() : null;
			}
		}

		public ADQLOperand getValue() {
			return (column != null) ? column : geomFunct;
			if (column != null)
				return column;
			else if (geomFunct != null)
				return geomFunct;
			else
				return udf;
		}

		public boolean isColumn() {
Loading