WikibaseDistanceBOp.java
package org.wikidata.query.rdf.blazegraph.constraints;
import java.util.Map;
import org.wikidata.query.rdf.common.WikibasePoint;
import com.bigdata.bop.BOp;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IValueExpression;
import com.bigdata.rdf.error.SparqlTypeErrorException;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.internal.constraints.INeedsMaterialization;
import com.bigdata.rdf.internal.constraints.IVValueExpression;
import com.bigdata.rdf.internal.gis.CoordinateDD;
import com.bigdata.rdf.internal.gis.ICoordinate.UNITS;
import com.bigdata.rdf.model.BigdataLiteral;
import com.bigdata.rdf.sparql.ast.GlobalAnnotations;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Implementation of geof:distance function.
* FIXME: units not supported yet.
*/
@SuppressWarnings("rawtypes")
public class WikibaseDistanceBOp extends IVValueExpression<IV> implements INeedsMaterialization {
/**
*
*/
@SuppressFBWarnings(
value = "IMC_IMMATURE_CLASS_BAD_SERIALVERSIONUID",
justification = "We need to keep serialVersionUID for blazegraph correctness sake.")
private static final long serialVersionUID = 2909300288279424402L;
/**
* Required shallow copy constructor.
*/
public WikibaseDistanceBOp(final BOp[] args,
final Map<String, Object> anns) {
super(args, anns);
if (args.length < 2 || args[0] == null || args[1] == null)
throw new IllegalArgumentException();
}
@SuppressWarnings("rawtypes")
public WikibaseDistanceBOp(//
final IValueExpression<? extends IV> left,
final IValueExpression<? extends IV> right,
final GlobalAnnotations globals) {
this(new BOp[] {left, right}, anns(globals));
}
/**
* Constructor required for {@link com.bigdata.bop.BOpUtility#deepCopy(BOp)}.
*/
public WikibaseDistanceBOp(final WikibaseDistanceBOp op) {
super(op);
}
@Override
public IV get(IBindingSet bindingSet) {
final IV left = getAndCheckLiteral(0, bindingSet);
final IV right = getAndCheckLiteral(1, bindingSet);
final CoordinateDD leftPoint = getCoordinateFromIV(left);
final CoordinateDD rightPoint = getCoordinateFromIV(right);
// TODO: allow to supply Units
final double distance;
if (leftPoint.equals(rightPoint) || veryClose(leftPoint, rightPoint)) {
distance = 0;
} else {
distance = leftPoint.distance(rightPoint, UNITS.Kilometers);
}
final BigdataLiteral dist = getValueFactory().createLiteral(distance);
return super.asIV(dist, bindingSet);
}
/**
* Distance where the points are considered to be the same.
* Distance equivalent of degrees (on equator):
* 0.001 is about 110 m
* 0.0001 is about 11 m
* 0.00001 is about 1 m
* See: https://en.wikipedia.org/wiki/WP:OPCOORD
*/
public static final double SMALL_DISTANCE = 0.00001;
/**
* Check whether two points are very close to each other, so distance between them
* can be considered zero.
* Current CoordinateDD.distance does not work well with small distances,
* so we take a shortcut here in order not to get the NaN.
*
* @return Whether the points are very close.
*/
protected boolean veryClose(CoordinateDD p1, CoordinateDD p2) {
double dLon = p1.eastWest - p2.eastWest;
double dLat = p1.northSouth - p2.northSouth;
return Math.abs(dLon) < SMALL_DISTANCE && Math.abs(dLat) < SMALL_DISTANCE;
}
/**
* Get coordinate from IV value.
*
* @return Coordinate
*/
@SuppressFBWarnings(value = "LEST_LOST_EXCEPTION_STACK_TRACE", justification = "Converting to SPARQL exception")
protected CoordinateDD getCoordinateFromIV(IV iv) {
final WikibasePoint point = new WikibasePoint(asLiteral(iv).stringValue());
try {
return new CoordinateDD(Double.parseDouble(point.getLatitude()),
Double.parseDouble(point.getLongitude()));
} catch (IllegalArgumentException e) {
throw new SparqlTypeErrorException();
}
}
@Override
public Requirement getRequirement() {
return Requirement.ALWAYS;
}
}