Package me.outofti.solrspatiallight

Source Code of me.outofti.solrspatiallight.Spatial

package me.outofti.solrspatiallight;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanFilter;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.FieldComparatorSource;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilterClause;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.search.SortField;
import org.apache.lucene.spatial.tier.DistanceFilter;
import org.apache.lucene.spatial.tier.DistanceFieldComparatorSource;
import org.apache.lucene.spatial.tier.LatLongDistanceFilter;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.QParser;

/**
* Encapsulates a spatial query component.
*/
public class Spatial extends QParser {
    /**
     * Valid query pattern.
     */
    private static final Pattern PATTERN =
        Pattern.compile(
                "^((\\w+): ?)?(-?\\d+(\\.\\d+)?)"
                + ",\\s*((\\w+): ?)?(-?\\d+(\\.\\d+)?)$");

    /**
     * Default field name for latitude.
     */
    public static final String DEFAULT_LAT_FIELD = "lat";

    /**
     * Default field name for longitude.
     */
    public static final String DEFAULT_LNG_FIELD = "lng";

    /**
     * Ratio between latitude degrees and statute miles.
     */
    private static final double DEGREES_TO_MILES = 69.047;

    /**
     * Default radius for search.
     *
     * Use half of earth's circumference, making the filter essentially a
     * no-op. We do this because the filter might still be used for sorting,
     * and it needs to be used for the distances to lazy-load.
     */
    private static final double DEFAULT_RADIUS = 24901.46;

    /**
     * Store memoized response of getDistanceFilter() method.
     */
    private DistanceFilter distanceFilter;

    /**
     * Store memoized response of getSortField() method.
     */
    private SortField sortField;

    /**
     * Construct the object using the superclass arguments.
     *
     * @param qstr        query string
     * @param localParams local parameters
     * @param params      global params
     * @param req         request
     */
    public Spatial(final String qstr, final SolrParams localParams,
                   final SolrParams params, final SolrQueryRequest req) {
        super(qstr, localParams, params, req);
    }

    /**
     * Return the distance filter as a Query.
     *
     * @throws ParseException if query formatting is bad
     * @return distance filter wrapped in Query
     */
    public final Query parse() throws ParseException {
        return new ConstantScoreQuery(getDistanceFilter());
    }

    /**
     * Build a distance filter out of a spatial query string.
     *
     * @throws ParseException if query formatting is bad
     * @return a distance filter
     */
    public final DistanceFilter getDistanceFilter() throws ParseException {
        if (distanceFilter == null) {
            String latLng;
            Float maybeMiles = null;
            if (localParams == null) {
                latLng = qstr;
            } else {
                latLng = localParams.get("v");
                maybeMiles = localParams.getFloat("radius");
            }

            final Matcher matcher = PATTERN.matcher(qstr);
            if (!matcher.matches()) {
                throw new ParseException(
                        "Spatial queries should be of the format LAT,LNG");
            }

            final double lat = Double.parseDouble(matcher.group(3));
            final double lng = Double.parseDouble(matcher.group(7));

            String latField = matcher.group(2);
            String lngField = matcher.group(6);
            if (latField == null) {
                latField = DEFAULT_LAT_FIELD;
            }
            if (lngField == null) {
                lngField = DEFAULT_LNG_FIELD;
            }

            double miles;
            Filter startingFilter;
            if (maybeMiles == null) {
                miles = DEFAULT_RADIUS;
                startingFilter =
                    new QueryWrapperFilter(new MatchAllDocsQuery());
            } else {
                miles = maybeMiles.doubleValue();
                startingFilter =
                    getBoundingBoxFilter(lat, lng, miles, latField, lngField);
            }
            distanceFilter = new LatLongDistanceFilter(
                    startingFilter, lat, lng, miles, latField, lngField);
        }
        return distanceFilter;
    }

    /**
     * Return a sort by distance based on the distance filter.
     *
     * @throws ParseException if params are malformed
     * @return sort by distance
     */
    public final SortField getSortField()
        throws ParseException {
        if (sortField == null) {
            final FieldComparatorSource dfcs =
                new DistanceFieldComparatorSource(getDistanceFilter());
            sortField = new SortField("dummy", dfcs);
        }
        return sortField;
    }

    /**
     * Get a bounding box filter restricting results to a given bounding box
     * around a coordinate pair.
     *
     * @param lat      The latitude of the centerpoint of the bounding box.
     * @param lng      The longitude of the centerpoint of the bounding box.
     * @param miles    The size of the bounding box "radius" in miles.
     * @param latField The name of the field in which latitude is indexed.
     * @param lngField The name of the field in which longitude is indexed.
     *
     * @return filter restricting results to the given bounding box
     */
    private Filter getBoundingBoxFilter(final double lat, final double lng,
                                        final double miles,
                                        final String latField,
                                        final String lngField) {
        final double latRadius = Math.abs(miles / DEGREES_TO_MILES);
        final double lngRadius = Math.abs(miles / (DEGREES_TO_MILES
                                          * Math.cos(Math.toRadians(lat))));

        final BooleanFilter filter = new BooleanFilter();

        filter.add(new FilterClause(getBoundingFilter(latField, lat, latRadius),
                                    BooleanClause.Occur.MUST));
        filter.add(new FilterClause(getBoundingFilter(lngField, lng, lngRadius),
                                    BooleanClause.Occur.MUST));

        return filter;
    }

    /**
     * Get a filter restricting results to a upper/lower boundary around a
     * given point.
     *
     * Uses Solr introspection to build the appropriate range query for the
     * given field.
     *
     * @param fieldName name of the field on which to construct the filter.
     * @param center    centerpoint of the bounding range.
     * @param radius    distance between the centerpoint and the upper/lower
     *                  bounds.
     *
     * @return filter restricting results to the given range around the center
     */
    private Filter getBoundingFilter(final String fieldName,
                                     final double center, final double radius) {
        final SchemaField field = req.getSchema().getField(fieldName);
        final Query boundingQuery = field.getType().getRangeQuery(
                this, field, new Double(center - radius).toString(),
                new Double(center + radius).toString(), true, true);
        return new QueryWrapperFilter(boundingQuery);
    }
}
TOP

Related Classes of me.outofti.solrspatiallight.Spatial

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.