WKTSerializer.java

package org.wikidata.query.rdf.blazegraph.inline.literal;

import org.openrdf.model.URI;
import org.openrdf.model.impl.URIImpl;
import org.wikidata.query.rdf.common.WikibasePoint;
import org.wikidata.query.rdf.common.WikibasePoint.CoordinateOrder;
import org.wikidata.query.rdf.common.uri.GeoSparql;
import org.wikidata.query.rdf.common.uri.UrisSchemeFactory;

import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.internal.gis.ICoordinate.UNITS;
import com.bigdata.rdf.internal.impl.literal.XSDNumericIV;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.sparql.ast.DummyConstantNode;
import com.bigdata.service.geospatial.GeoSpatialSearchException;
import com.bigdata.service.geospatial.IGeoSpatialLiteralSerializer;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Serializer class for WKT format.
 * See https://portal.opengeospatial.org/files/?artifact_id=47664
 * and http://postgis.refractions.net/documentation/manual-1.3SVN/ch04.html
 *
 * Internal storage follows longitude-latitude order for this format.
 */
@SuppressWarnings("rawtypes")
public class WKTSerializer implements IGeoSpatialLiteralSerializer {

    /**
     * Serializer ID.
     */
    @SuppressFBWarnings(
                value = "IMC_IMMATURE_CLASS_BAD_SERIALVERSIONUID",
                justification = "We need to keep serialVersionUID for blazegraph correctness sake.")
    private static final long serialVersionUID = -8893919944620967416L;

    /**
     * Prefix for globe URIs.
     */
    private static final String URL_PREFIX = UrisSchemeFactory.getURISystem().entityIdToURI("Q");
    // FIXME: we need a way to handle non-wikidata URIs

    /**
     * This is put in coordinate field when there's no globe.
     */
    public static final String NO_GLOBE = System.getProperty(
            WKTSerializer.class.getName() + ".noGlobe", "0");

    /**
     * Default globe URI.
     */
    public static final String DEFAULT_GLOBE = URL_PREFIX + NO_GLOBE;

    /**
     * URI of the wkt literal datatype.
     */
    private static final URI WKT_LITERAL_URI = new URIImpl(GeoSparql.WKT_LITERAL);

    @Override
    public String fromComponents(Object[] components) {
        if (components == null)
            return "";

        if (components.length != 3)
            throw new GeoSpatialSearchException(
                "Expected component string of lenth 3, but was " + components.length);

        String[] strComponents = new String[3];
        strComponents[0] = components[0].toString();
        strComponents[1] = components[1].toString();
        strComponents[2] = components[2].toString();

        WikibasePoint point;
        if (strComponents[2].equals(NO_GLOBE)) {
            point = new WikibasePoint(strComponents, null, CoordinateOrder.LONG_LAT);
        } else {
            point = new WikibasePoint(strComponents, URL_PREFIX + strComponents[2], CoordinateOrder.LONG_LAT);
        }

        return point.toString();
    }

    /**
     * Cut off prefix for the coordinate system URI.
     * E.g. http://www.wikidata.org/entity/Q
     */
    public String trimCoordURI(String uri) {
        if (!uri.startsWith(URL_PREFIX)) {
            throw new GeoSpatialSearchException("Invalid coordinate URI for the WKT value");
        }
        return uri.substring(URL_PREFIX.length());
    }

    @Override
    public String[] toComponents(String literalString) {
        if (literalString == null) {
            return new String[0];
        }

        String[] comps = new String[3];
        WikibasePoint point = new WikibasePoint(literalString);
        // Should be in sync with RWStore.properties config
        // and AbstractRandomizedBlazegraphStorageTestCase.java
        comps[0] = point.getLongitude();
        comps[1] = point.getLatitude();
        String globe = point.getGlobe();
        comps[2] = globe != null ? trimCoordURI(globe) : NO_GLOBE;

        return comps;
    }

    @Override
    public IV<?, ?> serializeCoordSystem(BigdataValueFactory vf, Object coordinateSystem) {
        // Returns URI with prefix, so you can match it with entities
        return DummyConstantNode.toDummyIV(vf.createURI(URL_PREFIX + coordinateSystem));
    }

    @Override
    public IV<?, ?> serializeCustomFields(BigdataValueFactory vf, Object... arg1) {
        throw new IllegalArgumentException("Custom fields are not supported for this format");
    }

    @Override
    public IV<?, ?> serializeLatitude(BigdataValueFactory vf, Object latitude) {
        return new XSDNumericIV((Double)latitude);
    }

    @Override
    public IV<?, ?> serializeLocation(BigdataValueFactory vf, Object lat, Object lon) {
        // FIXME: this does not look really useful. Maybe needs to produce exception too.
        WikibasePoint p = new WikibasePoint(new String[] {lat.toString(), lon.toString()},
                null, CoordinateOrder.LAT_LONG);
        return DummyConstantNode.toDummyIV(vf.createLiteral(p.toString(), WKT_LITERAL_URI));
    }

    @Override
    public IV<?, ?> serializeLocationAndTime(BigdataValueFactory vf, Object latitude,
                                             Object longitude, Object time) {
        throw new IllegalArgumentException("Time fields are not supported for this format");
    }

    @Override
    public IV<?, ?> serializeLongitude(BigdataValueFactory vf, Object longitude) {
        return new XSDNumericIV((Double)longitude);
    }

    @Override
    public IV<?, ?> serializeTime(BigdataValueFactory vf, Object time) {
        throw new IllegalArgumentException("Time fields are not supported for this format");
    }

    @Override
    public IV<?, ?> serializeDistance(final BigdataValueFactory vf, final Double distance, final UNITS unit) {
        // TODO: implement units support
        return new XSDNumericIV(Math.round(distance * 1000) / 1000.0);
    }
}