/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.solr.search;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.spatial.geometry.LatLng;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.SpatialParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.util.GeotargetAdapter;
/**
* This class parses and executes a spatial query
*/
public class SpatialQParser extends QParser {
private static final String NAME_FIELD = "asciiname";
private static final String STATE_FIELD = "admin1code";
private static final String COUNTRY_FIELD = "countrycode";
private static final String LOCATION_FIELD = "location";
private static final String POPULATION_FIELD = "population";
public static final String SEARCH_RADIUS = "20";
public static final String CITY = "ct";
public static final String STATE = "s";
public static final String COUNTRY = "c";
public static final String DISTANCE = "d";
private GeotargetAdapter geoTargeter;
/**
* Constructor
*
* @param qstr
* the query string
* @param localParams
* solr localParams
* @param params
* other params
* @param req
* the query request object
* @param geoTargeter
* the adapter to use to geotarget client ip if no location is
* provided
*/
public SpatialQParser(String qstr, SolrParams localParams, SolrParams params,
SolrQueryRequest req, GeotargetAdapter geoTargeter) {
super(qstr, localParams, params, req);
this.geoTargeter = geoTargeter;
}
@Override
public Query parse() throws ParseException {
// this will combine the results that are found for each
// administrative level
BooleanQuery allQuery = new BooleanQuery();
// attempt to create a query on city (low level administrative division)
String city = localParams.get(CITY);
if (city != null) {
city = city.toLowerCase();
SchemaField nameField = req.getSchema().getField(NAME_FIELD);
FieldType nameFieldType = nameField.getType();
Query cityQuery = nameFieldType.getFieldQuery(this, nameField, city);
allQuery.add(cityQuery, Occur.MUST);
}
// attempt to create a query on state (mid level administrative division)
String state = localParams.get(STATE);
if (state != null) {
state = state.toLowerCase();
SchemaField stateField = req.getSchema().getField(STATE_FIELD);
FieldType stateFieldType = stateField.getType();
Query stateQuery = stateFieldType.getFieldQuery(this, stateField, state);
allQuery.add(stateQuery, Occur.MUST);
}
// attempt to create a query on city (high level administrative division)
String country = localParams.get(COUNTRY);
if (country != null) {
country = country.toLowerCase();
SchemaField countryField = req.getSchema().getField(COUNTRY_FIELD);
FieldType countryFieldType = countryField.getType();
Query countryQuery = countryFieldType.getFieldQuery(this, countryField,
country);
allQuery.add(countryQuery, Occur.MUST);
}
String latitude = null;
String longitude = null;
// no location provided, computer user's location via reverse-ip lookup
if (allQuery.getClauses().length == 0) {
HttpServletRequest httpreq = req.getHttpServletRequest();
String ip = httpreq.getRemoteAddr();
LatLng currLoc = geoTargeter.getCurrentLocation(ip);
if (currLoc != null) {
latitude = Double.toString(currLoc.getLat());
longitude = Double.toString(currLoc.getLng());
}
} else {
SolrIndexSearcher searcher = req.getSearcher();
Document geocodeDoc = null;
try {
Sort s = new Sort(new SortField(POPULATION_FIELD, SortField.LONG, true));
DocList docs = searcher.getDocList(allQuery, new ArrayList<Query>(), s,
0, 1, 0);
if (docs == null)
return query;
DocIterator iter = docs.iterator();
int geocodeDocId = iter.nextDoc();
geocodeDoc = searcher.doc(geocodeDocId);
} catch (Exception e) {
e.printStackTrace();
return query;
}
latitude = geocodeDoc.get("latitude");
longitude = geocodeDoc.get("longitude");
}
// combine the spatial and free-text queries
BooleanQuery finalQuery = new BooleanQuery();
// if no location is provided and user's location cannot be determined,
// do not search location
if (latitude != null && longitude != null) {
String distance = localParams.get(DISTANCE);
try {
Double.parseDouble(distance);
} catch (Exception e) {
distance = SEARCH_RADIUS;
}
SpatialFilterQParserPlugin spatialFilter = new SpatialFilterQParserPlugin();
ModifiableSolrParams spatialParams = new ModifiableSolrParams();
spatialParams.add(SpatialParams.POINT, latitude + "," + longitude);
spatialParams.add(SpatialParams.DISTANCE, distance);
spatialParams.add(CommonParams.FL, LOCATION_FIELD);
Query spatialQuery = spatialFilter.createParser(qstr, spatialParams,
spatialParams, req).parse();
finalQuery.add(spatialQuery, Occur.MUST);
}
// get results from default LuceneQParser
Query defQuery = new LuceneQParserPlugin().createParser(qstr, localParams,
params, req).parse();
finalQuery.add(defQuery, Occur.MUST);
return finalQuery;
}
}