com.esri.gpt.catalog.lucene.SpatialClauseAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.esri.gpt.catalog.lucene.SpatialClauseAdapter.java

Source

/* See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Esri Inc. licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.esri.gpt.catalog.lucene;

import com.esri.gpt.catalog.discovery.Discoverable;
import com.esri.gpt.catalog.discovery.DiscoveryException;
import com.esri.gpt.catalog.discovery.LogicalClause;
import com.esri.gpt.catalog.discovery.SpatialClause;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.geometry.Envelope;
import com.esri.gpt.framework.util.Val;

import java.util.logging.Logger;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.function.ValueSourceQuery;

/**
 * Adapts a catalog discovery SpatialClause to the Lucene model.
 */
public class SpatialClauseAdapter extends DiscoveryClauseAdapter {

    /** class variables ========================================================= */

    /** The Logger. */
    private static Logger LOGGER = Logger.getLogger(SpatialClauseAdapter.class.getName());

    /** instance variables ====================================================== */

    /** The input envelope. */
    private Envelope envelope;

    /** The field names. */
    private String docMinX = "envelope.minx";
    private String docMinY = "envelope.miny";
    private String docMaxX = "envelope.maxx";
    private String docMaxY = "envelope.maxy";
    private String docMinXLeft = "envelope.minx";
    private String docMaxXRight = "envelope.maxx";
    private String docXDL = "envelope.xdl";

    /** The query values */
    private boolean qryCrossedDateline = false;
    private double qryMinX;
    private double qryMinY;
    private double qryMaxX;
    private double qryMaxY;

    /** constructors ============================================================ */

    /**
     * Constructs with an associated query adapter.
     * @param queryAdapter the query adapter
     */
    protected SpatialClauseAdapter(LuceneQueryAdapter queryAdapter) {
        super(queryAdapter);
    }

    /** methods ================================================================= */

    /**
     * Adapts a catalog discovery SpatialClause to the Lucene model.
     * @param activeBooleanQuery the active Lucene boolean query
     * @param activeLogicalClause the active discovery logical clause
     * @param spatialClause the spatial clause to adapt
     * @throws DiscoveryException if an invalid clause is encountered
     */
    protected void adaptSpatialClause(BooleanQuery activeBooleanQuery, LogicalClause activeLogicalClause,
            SpatialClause spatialClause) throws DiscoveryException {
        LOGGER.finer("Adapting SpatialClause...\n" + spatialClause);

        // determine the discoverable target, set names
        String sErr;
        Discoverable discoverable = spatialClause.getTarget();
        if (discoverable == null) {
            sErr = "The SpatialClause.target is null.";
            throw new DiscoveryException(sErr);
        }
        if (discoverable.getStorable() == null) {
            sErr = "The SpatialClause.target.storeable is null.";
            throw new DiscoveryException(sErr);
        } else {
            Storeable storeable = (Storeable) discoverable.getStorable();
            if (!(storeable instanceof GeometryProperty)) {
                sErr = "The SpatialClause.target.storeable is not a GeometryProperty.";
                throw new DiscoveryException(sErr);
            }
        }

        // check the envelope
        envelope = spatialClause.getBoundingEnvelope();
        if ((envelope == null) || envelope.isEmpty()) {
            sErr = "The SpatialClause.boundingEnvelope is empty.";
            throw new DiscoveryException(sErr);
        }

        // initialize the values of the input query envelope
        qryMinX = envelope.getMinX();
        qryMinY = envelope.getMinY();
        qryMaxX = envelope.getMaxX();
        qryMaxY = envelope.getMaxY();
        if (qryMinX > qryMaxX) {
            qryCrossedDateline = true;
        }

        // determine spatialRelevance parameters 
        // (original defaults were queryPower=2.0, targetPower=0.5)
        RequestContext rc = this.getQueryAdapter().getIndexAdapter().getRequestContext();
        StringAttributeMap params = rc.getCatalogConfiguration().getParameters();
        double queryPower = Val.chkDbl(params.getValue("spatialRelevance.queryPower"), 1.0);
        double targetPower = Val.chkDbl(params.getValue("spatialRelevance.targetPower"), 1.0);
        String rankingOption = Val.chkStr(params.getValue("spatialRelevance.ranking.enabled"));
        int rankingMaxDoc = Val.chkInt(params.getValue("spatialRelevance.ranking.maxDoc"), 50000);
        boolean bUseSpatialRanking = false;
        if (rankingOption.equalsIgnoreCase("true")) {
            bUseSpatialRanking = true;
        } else if (rankingOption.equalsIgnoreCase("false")) {
            bUseSpatialRanking = false;
        } else {

            // default spatialRelevance.ranking.enabled option is "auto"
            if (this.getQueryAdapter() != null) {
                int maxDoc = this.getQueryAdapter().getMaxDoc();
                if ((maxDoc > 0) && (maxDoc <= rankingMaxDoc)) {
                    bUseSpatialRanking = true;
                }
            }
        }

        // Handle each operation - Beyond, Crosses, DWithin and Touches are not implemented

        if (bUseSpatialRanking) {
            Query spatialQuery = null;
            if (spatialClause instanceof SpatialClause.GeometryBBOXIntersects) {
                spatialQuery = this.makeIntersects();
            } else if (spatialClause instanceof SpatialClause.GeometryContains) {
                spatialQuery = this.makeContains();
            } else if (spatialClause instanceof SpatialClause.GeometryIntersects) {
                spatialQuery = this.makeIntersects();
            } else if (spatialClause instanceof SpatialClause.GeometryIsDisjointTo) {
                bUseSpatialRanking = false;
            } else if (spatialClause instanceof SpatialClause.GeometryIsEqualTo) {
                bUseSpatialRanking = false;
            } else if (spatialClause instanceof SpatialClause.GeometryIsWithin) {
                spatialQuery = this.makeWithin();
            } else if (spatialClause instanceof SpatialClause.GeometryOverlaps) {
                spatialQuery = this.makeIntersects();
            } else {
                sErr = "Unrecognized spatial clause type: ";
                throw new DiscoveryException(sErr + spatialClause.getClass().getName());
            }

            if (bUseSpatialRanking) {
                SpatialRankingValueSource srvs = new SpatialRankingValueSource(envelope, queryPower, targetPower);
                Query spatialRankingQuery = new ValueSourceQuery(srvs);
                BooleanQuery bq = new BooleanQuery();
                bq.add(spatialQuery, BooleanClause.Occur.MUST);
                bq.add(spatialRankingQuery, BooleanClause.Occur.MUST);
                appendQuery(activeBooleanQuery, activeLogicalClause, bq);
                this.getQueryAdapter().setHasScoredExpression(true);
            }
        }

        if (!bUseSpatialRanking) {
            if (spatialClause instanceof SpatialClause.GeometryBBOXIntersects) {
                appendQuery(activeBooleanQuery, activeLogicalClause, makeIntersects());
            } else if (spatialClause instanceof SpatialClause.GeometryContains) {
                appendQuery(activeBooleanQuery, activeLogicalClause, makeContains());
            } else if (spatialClause instanceof SpatialClause.GeometryIntersects) {
                appendQuery(activeBooleanQuery, activeLogicalClause, makeIntersects());
            } else if (spatialClause instanceof SpatialClause.GeometryIsDisjointTo) {
                appendQuery(activeBooleanQuery, activeLogicalClause, makeDisjoint());
            } else if (spatialClause instanceof SpatialClause.GeometryIsEqualTo) {
                appendQuery(activeBooleanQuery, activeLogicalClause, makeEquals());
            } else if (spatialClause instanceof SpatialClause.GeometryIsWithin) {
                appendQuery(activeBooleanQuery, activeLogicalClause, makeWithin());
            } else if (spatialClause instanceof SpatialClause.GeometryOverlaps) {
                appendQuery(activeBooleanQuery, activeLogicalClause, makeIntersects());
            } else {
                sErr = "Unrecognized spatial clause type: ";
                throw new DiscoveryException(sErr + spatialClause.getClass().getName());
            }
        }

    }

    /**
     * Constructs a query to retrieve documents that fully contain the input envelope.
     * @return the spatial query
     */
    private Query makeContains() {

        /*
        // the original contains query does not work for envelopes that cross the date line
        // docMinX <= qryMinX, docMinY <= qryMinY, docMaxX >= qryMaxX, docMaxY >= qryMaxY
        Query qMinX = NumericRangeQuery.newDoubleRange(docMinX,null,qryMinX,false,true);
        Query qMinY = NumericRangeQuery.newDoubleRange(docMinY,null,qryMinY,false,true);
        Query qMaxX = NumericRangeQuery.newDoubleRange(docMaxX,qryMaxX,null,true,false);
        Query qMaxY = NumericRangeQuery.newDoubleRange(docMaxY,qryMaxY,null,true,false);
        BooleanQuery bq = new BooleanQuery();
        bq.add(qMinX,BooleanClause.Occur.MUST);
        bq.add(qMinY,BooleanClause.Occur.MUST);
        bq.add(qMaxX,BooleanClause.Occur.MUST);
        bq.add(qMaxY,BooleanClause.Occur.MUST);
        return bq;
        */

        // general case
        // docMinX <= qryMinX AND docMinY <= qryMinY AND docMaxX >= qryMaxX AND docMaxY >= qryMaxY

        // Y conditions
        // docMinY <= qryMinY AND docMaxY >= qryMaxY
        Query qMinY = NumericRangeQuery.newDoubleRange(docMinY, null, qryMinY, false, true);
        Query qMaxY = NumericRangeQuery.newDoubleRange(docMaxY, qryMaxY, null, true, false);
        Query yConditions = this.makeQuery(new Query[] { qMinY, qMaxY }, BooleanClause.Occur.MUST);

        // X conditions
        Query xConditions = null;

        // queries that do not cross the date line
        if (!qryCrossedDateline) {

            // X Conditions for documents that do not cross the date line,
            // documents that contain the min X and max X of the query envelope, 
            // docMinX <= qryMinX AND docMaxX >= qryMaxX    
            Query qMinX = NumericRangeQuery.newDoubleRange(docMinX, null, qryMinX, false, true);
            Query qMaxX = NumericRangeQuery.newDoubleRange(docMaxX, qryMaxX, null, true, false);
            Query qMinMax = this.makeQuery(new Query[] { qMinX, qMaxX }, BooleanClause.Occur.MUST);
            Query qNonXDL = this.makeXDL(false, qMinMax);

            // X Conditions for documents that cross the date line,
            // the left portion of the document contains the min X of the query
            // OR the right portion of the document contains the max X of the query,
            // docMinXLeft <= qryMinX OR docMaxXRight >= qryMaxX
            Query qXDLLeft = NumericRangeQuery.newDoubleRange(docMinXLeft, null, qryMinX, false, true);
            Query qXDLRight = NumericRangeQuery.newDoubleRange(docMaxXRight, qryMaxX, null, true, false);
            Query qXDLLeftRight = this.makeQuery(new Query[] { qXDLLeft, qXDLRight }, BooleanClause.Occur.SHOULD);
            Query qXDL = this.makeXDL(true, qXDLLeftRight);

            // apply the non-XDL and XDL conditions
            xConditions = this.makeQuery(new Query[] { qNonXDL, qXDL }, BooleanClause.Occur.SHOULD);

            // queries that cross the date line
        } else {

            // No need to search for documents that do not cross the date line

            // X Conditions for documents that cross the date line,
            // the left portion of the document contains the min X of the query
            // AND the right portion of the document contains the max X of the query,
            // docMinXLeft <= qryMinX AND docMaxXRight >= qryMaxX
            Query qXDLLeft = NumericRangeQuery.newDoubleRange(docMinXLeft, null, qryMinX, false, true);
            Query qXDLRight = NumericRangeQuery.newDoubleRange(docMaxXRight, qryMaxX, null, true, false);
            Query qXDLLeftRight = this.makeQuery(new Query[] { qXDLLeft, qXDLRight }, BooleanClause.Occur.MUST);
            Query qXDL = this.makeXDL(true, qXDLLeftRight);

            xConditions = qXDL;
        }

        // both X and Y conditions must occur
        Query xyConditions = this.makeQuery(new Query[] { xConditions, yConditions }, BooleanClause.Occur.MUST);
        return xyConditions;
    }

    /**
     * Constructs a query to retrieve documents that are disjoint to the input envelope.
     * @return the spatial query
     */
    private Query makeDisjoint() {

        /*
        // the original disjoint query does not work for envelopes that cross the date line
        // docMinX > qryMaxX OR docMaxX < qryMinX OR docMinY > qryMaxY OR docMaxY < qryMinY
        Query qMinX = NumericRangeQuery.newDoubleRange(docMinX,qryMaxX,null,false,false);
        Query qMaxX = NumericRangeQuery.newDoubleRange(docMaxX,null,qryMinX,false,false);
        Query qMinY = NumericRangeQuery.newDoubleRange(docMinY,qryMaxY,null,false,false);
        Query qMaxY = NumericRangeQuery.newDoubleRange(docMaxY,null,qryMinY,false,false);    
        BooleanQuery bq = new BooleanQuery();
        bq.add(qMinX,BooleanClause.Occur.SHOULD);
        bq.add(qMinY,BooleanClause.Occur.SHOULD);
        bq.add(qMaxX,BooleanClause.Occur.SHOULD);
        bq.add(qMaxY,BooleanClause.Occur.SHOULD);
        */

        // general case
        // docMinX > qryMaxX OR docMaxX < qryMinX OR docMinY > qryMaxY OR docMaxY < qryMinY

        // Y conditions
        // docMinY > qryMaxY OR docMaxY < qryMinY
        Query qMinY = NumericRangeQuery.newDoubleRange(docMinY, qryMaxY, null, false, false);
        Query qMaxY = NumericRangeQuery.newDoubleRange(docMaxY, null, qryMinY, false, false);
        Query yConditions = this.makeQuery(new Query[] { qMinY, qMaxY }, BooleanClause.Occur.SHOULD);

        // X conditions
        Query xConditions = null;

        // queries that do not cross the date line
        if (!qryCrossedDateline) {

            // X Conditions for documents that do not cross the date line,
            // docMinX > qryMaxX OR docMaxX < qryMinX 
            Query qMinX = NumericRangeQuery.newDoubleRange(docMinX, qryMaxX, null, false, false);
            Query qMaxX = NumericRangeQuery.newDoubleRange(docMaxX, null, qryMinX, false, false);
            Query qMinMax = this.makeQuery(new Query[] { qMinX, qMaxX }, BooleanClause.Occur.SHOULD);
            Query qNonXDL = this.makeXDL(false, qMinMax);

            // X Conditions for documents that cross the date line,
            // both the left and right portions of the document must be disjoint to the query
            // (docMinXLeft > qryMaxX OR docMaxXLeft < qryMinX) AND
            // (docMinXRight > qryMaxX OR docMaxXRight < qryMinX) 
            // where: docMaxXLeft = 180.0, docMinXRight = -180.0
            // (docMaxXLeft  < qryMinX) equates to (180.0  < qryMinX) and is ignored
            // (docMinXRight > qryMaxX) equates to (-180.0 > qryMaxX) and is ignored
            Query qMinXLeft = NumericRangeQuery.newDoubleRange(docMinXLeft, qryMaxX, null, false, false);
            Query qMaxXRight = NumericRangeQuery.newDoubleRange(docMaxXRight, null, qryMinX, false, false);
            Query qLeftRight = this.makeQuery(new Query[] { qMinXLeft, qMaxXRight }, BooleanClause.Occur.MUST);
            Query qXDL = this.makeXDL(true, qLeftRight);

            // apply the non-XDL and XDL conditions
            xConditions = this.makeQuery(new Query[] { qNonXDL, qXDL }, BooleanClause.Occur.SHOULD);

            // queries that cross the date line
        } else {

            // X Conditions for documents that do not cross the date line,
            // the document must be disjoint to both the left and right query portions
            // (docMinX > qryMaxXLeft OR docMaxX < qryMinX) AND (docMinX > qryMaxX OR docMaxX < qryMinXLeft) 
            // where: qryMaxXLeft = 180.0, qryMinXLeft = -180.0
            Query qMinXLeft = NumericRangeQuery.newDoubleRange(docMinX, 180.0, null, false, false);
            Query qMaxXLeft = NumericRangeQuery.newDoubleRange(docMaxX, null, qryMinX, false, false);
            Query qMinXRight = NumericRangeQuery.newDoubleRange(docMinX, qryMaxX, null, false, false);
            Query qMaxXRight = NumericRangeQuery.newDoubleRange(docMaxX, null, -180.0, false, false);
            Query qLeft = this.makeQuery(new Query[] { qMinXLeft, qMaxXLeft }, BooleanClause.Occur.SHOULD);
            Query qRight = this.makeQuery(new Query[] { qMinXRight, qMaxXRight }, BooleanClause.Occur.SHOULD);
            Query qLeftRight = this.makeQuery(new Query[] { qLeft, qRight }, BooleanClause.Occur.MUST);
            Query qNonXDL = this.makeXDL(false, qLeftRight);

            // No need to search for documents that do not cross the date line

            xConditions = qNonXDL;
        }

        // either X or Y conditions should occur
        Query xyConditions = this.makeQuery(new Query[] { xConditions, yConditions }, BooleanClause.Occur.SHOULD);
        return xyConditions;
    }

    /**
     * Constructs a query to retrieve documents that equal the input envelope.
     * @return the spatial query
     */
    private Query makeEquals() {

        // docMinX = qryMinX AND docMinY = qryMinY AND docMaxX = qryMaxX AND docMaxY = qryMaxY
        Query qMinX = NumericRangeQuery.newDoubleRange(docMinX, qryMinX, qryMinX, true, true);
        Query qMinY = NumericRangeQuery.newDoubleRange(docMinY, qryMinY, qryMinY, true, true);
        Query qMaxX = NumericRangeQuery.newDoubleRange(docMaxX, qryMaxX, qryMaxX, true, true);
        Query qMaxY = NumericRangeQuery.newDoubleRange(docMaxY, qryMaxY, qryMaxY, true, true);
        BooleanQuery bq = new BooleanQuery();
        bq.add(qMinX, BooleanClause.Occur.MUST);
        bq.add(qMinY, BooleanClause.Occur.MUST);
        bq.add(qMaxX, BooleanClause.Occur.MUST);
        bq.add(qMaxY, BooleanClause.Occur.MUST);
        return bq;
    }

    /**
     * Constructs a query to retrieve documents that intersect the input envelope.
     * @return the spatial query
     */
    private Query makeIntersects() {

        // the original intersects query does not work for envelopes that cross the date line,
        // switch to a NOT Disjoint query

        // MUST_NOT causes a problem when it's the only clause type within a BooleanQuery,
        // to get round it we add all documents as a SHOULD

        // there must be an envelope, it must not be disjoint
        Query qDisjoint = makeDisjoint();
        Query qIsNonXDL = this.makeXDL(false);
        Query qIsXDL = this.makeXDL(true);
        Query qHasEnv = this.makeQuery(new Query[] { qIsNonXDL, qIsXDL }, BooleanClause.Occur.SHOULD);
        BooleanQuery qNotDisjoint = new BooleanQuery();
        qNotDisjoint.add(qHasEnv, BooleanClause.Occur.MUST);
        qNotDisjoint.add(qDisjoint, BooleanClause.Occur.MUST_NOT);

        //Query qDisjoint = makeDisjoint();
        //BooleanQuery qNotDisjoint = new BooleanQuery();
        //qNotDisjoint.add(new MatchAllDocsQuery(),BooleanClause.Occur.SHOULD);
        //qNotDisjoint.add(qDisjoint,BooleanClause.Occur.MUST_NOT);
        return qNotDisjoint;
    }

    /**
     * Makes a boolean query based upon a collection of queries and a logical operator.
     * @param queries the query collection
     * @param occur the logical operator
     * @return the query
     */
    private BooleanQuery makeQuery(Query[] queries, BooleanClause.Occur occur) {
        BooleanQuery bq = new BooleanQuery();
        for (Query query : queries) {
            bq.add(query, occur);
        }
        return bq;
    }

    /**
     * Constructs a query to retrieve documents are fully within the input envelope.
     * @return the spatial query
     */
    private Query makeWithin() {

        /*
        // the original within query does not work for envelopes that cross the date line
        // docMinX >= qryMinX AND docMinY >= qryMinY AND docMaxX <= qryMaxX AND docMaxY <= qryMaxY
        Query qMinX = NumericRangeQuery.newDoubleRange(docMinX,qryMinX,null,true,false);
        Query qMinY = NumericRangeQuery.newDoubleRange(docMinY,qryMinY,null,true,false);
        Query qMaxX = NumericRangeQuery.newDoubleRange(docMaxX,null,qryMaxX,false,true);
        Query qMaxY = NumericRangeQuery.newDoubleRange(docMaxY,null,qryMaxY,false,true);
        BooleanQuery bq = new BooleanQuery();
        bq.add(qMinX,BooleanClause.Occur.MUST);
        bq.add(qMinY,BooleanClause.Occur.MUST);
        bq.add(qMaxX,BooleanClause.Occur.MUST);
        bq.add(qMaxY,BooleanClause.Occur.MUST);
        return bq;
        */

        // general case
        // docMinX >= qryMinX AND docMinY >= qryMinY AND docMaxX <= qryMaxX AND docMaxY <= qryMaxY

        // Y conditions
        // docMinY >= qryMinY AND docMaxY <= qryMaxY
        Query qMinY = NumericRangeQuery.newDoubleRange(docMinY, qryMinY, null, true, false);
        Query qMaxY = NumericRangeQuery.newDoubleRange(docMaxY, null, qryMaxY, false, true);
        Query yConditions = this.makeQuery(new Query[] { qMinY, qMaxY }, BooleanClause.Occur.MUST);

        // X conditions
        Query xConditions = null;

        // X Conditions for documents that cross the date line,
        // the left portion of the document must be within the left portion of the query,
        // AND the right portion of the document must be within the right portion of the query
        // docMinXLeft >= qryMinX AND docMaxXLeft <= 180.0 
        // AND docMinXRight >= -180.0 AND docMaxXRight <= qryMaxX
        Query qXDLLeft = NumericRangeQuery.newDoubleRange(docMinXLeft, qryMinX, null, true, false);
        Query qXDLRight = NumericRangeQuery.newDoubleRange(docMaxXRight, null, qryMaxX, false, true);
        Query qXDLLeftRight = this.makeQuery(new Query[] { qXDLLeft, qXDLRight }, BooleanClause.Occur.MUST);
        Query qXDL = this.makeXDL(true, qXDLLeftRight);

        // queries that do not cross the date line
        if (!qryCrossedDateline) {

            // X Conditions for documents that do not cross the date line,
            // docMinX >= qryMinX AND docMaxX <= qryMaxX
            Query qMinX = NumericRangeQuery.newDoubleRange(docMinX, qryMinX, null, true, false);
            Query qMaxX = NumericRangeQuery.newDoubleRange(docMaxX, null, qryMaxX, false, true);
            Query qMinMax = this.makeQuery(new Query[] { qMinX, qMaxX }, BooleanClause.Occur.MUST);
            Query qNonXDL = this.makeXDL(false, qMinMax);

            // apply the non-XDL or XDL X conditions
            if ((qryMinX <= -180.0) && qryMaxX >= 180.0) {
                xConditions = this.makeQuery(new Query[] { qNonXDL, qXDL }, BooleanClause.Occur.SHOULD);
            } else {
                xConditions = qNonXDL;
            }

            // queries that cross the date line
        } else {

            // X Conditions for documents that do not cross the date line

            // the document should be within the left portion of the query
            // docMinX >= qryMinX AND docMaxX <= 180.0
            Query qMinXLeft = NumericRangeQuery.newDoubleRange(docMinX, qryMinX, null, true, false);
            Query qMaxXLeft = NumericRangeQuery.newDoubleRange(docMaxX, null, 180.0, false, true);
            Query qLeft = this.makeQuery(new Query[] { qMinXLeft, qMaxXLeft }, BooleanClause.Occur.MUST);

            // the document should be within the right portion of the query
            // docMinX >= -180.0 AND docMaxX <= qryMaxX
            Query qMinXRight = NumericRangeQuery.newDoubleRange(docMinX, -180.0, null, true, false);
            Query qMaxXRight = NumericRangeQuery.newDoubleRange(docMaxX, null, qryMaxX, false, true);
            Query qRight = this.makeQuery(new Query[] { qMinXRight, qMaxXRight }, BooleanClause.Occur.MUST);

            // either left or right conditions should occur,
            // apply the left and right conditions to documents that do not cross the date line
            Query qLeftRight = this.makeQuery(new Query[] { qLeft, qRight }, BooleanClause.Occur.SHOULD);
            Query qNonXDL = this.makeXDL(false, qLeftRight);

            // apply the non-XDL and XDL conditions
            xConditions = this.makeQuery(new Query[] { qNonXDL, qXDL }, BooleanClause.Occur.SHOULD);
        }

        // both X and Y conditions must occur
        Query xyConditions = this.makeQuery(new Query[] { xConditions, yConditions }, BooleanClause.Occur.MUST);
        return xyConditions;
    }

    /**
     * Constructs a query to retrieve documents that do or do not cross the date line. 
     * @param crossedDateLine <code>true</true> for documents that cross the date line
     * @return the query
     */
    private Query makeXDL(boolean crossedDateLine) {
        return new TermQuery(new Term(docXDL, "" + crossedDateLine));
    }

    /**
     * Constructs a query to retrieve documents that do or do not cross the date line
     * and match the supplied spatial query. 
     * @param crossedDateLine <code>true</true> for documents that cross the date line
     * @param query the spatial query
     * @return the query
     */
    private Query makeXDL(boolean crossedDateLine, Query query) {
        BooleanQuery bq = new BooleanQuery();
        bq.add(this.makeXDL(crossedDateLine), BooleanClause.Occur.MUST);
        bq.add(query, BooleanClause.Occur.MUST);
        return bq;
    }

}