SOLR-8814: Support GeoJSON response format

This commit is contained in:
Ryan McKinley 2016-03-16 09:49:46 -07:00
parent 24830b7f18
commit 36145d02cc
10 changed files with 927 additions and 2 deletions

View File

@ -34,6 +34,18 @@ Detailed Change List
New Features
----------------------
* SOLR-8814: Support GeoJSON response writer and general spatial formatting. Adding
&wt=geojson&geojson.field=<your geometry field>
Will return a FeatureCollection for each SolrDocumentList and a Feature with the
requested geometry for each SolrDocument. The requested geometry field needs
to either extend AbstractSpatialFieldType or store a GeoJSON string. This also adds
a [geo] DocumentTransformer that can return the Shape in a variety of formats:
&fl=[geo f=<your geometry field> w=(GeoJSON|WKT|POLY)]
The default format is GeoJSON. For information on the supported formats, see:
https://github.com/locationtech/spatial4j/blob/master/FORMATS.md
To return the FeatureCollection as the root element, add '&omitHeader=true" (ryan)
Bug Fixes
----------------------

View File

@ -2123,10 +2123,11 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
private final PluginBag<QueryResponseWriter> responseWriters = new PluginBag<>(QueryResponseWriter.class, this);
public static final Map<String ,QueryResponseWriter> DEFAULT_RESPONSE_WRITERS ;
static{
HashMap<String, QueryResponseWriter> m= new HashMap<>(14, 1);
HashMap<String, QueryResponseWriter> m= new HashMap<>(15, 1);
m.put("xml", new XMLResponseWriter());
m.put("standard", m.get("xml"));
m.put(CommonParams.JSON, new JSONResponseWriter());
m.put("geojson", new GeoJSONResponseWriter());
m.put("python", new PythonResponseWriter());
m.put("php", new PHPResponseWriter());
m.put("phps", new PHPSerializedResponseWriter());

View File

@ -0,0 +1,345 @@
/*
* 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.response;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;
import org.apache.lucene.index.IndexableField;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.transform.GeoTransformerFactory;
import org.apache.solr.response.transform.WriteableGeoJSON;
import org.apache.solr.schema.AbstractSpatialFieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.ReturnFields;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.io.ShapeWriter;
import org.locationtech.spatial4j.io.SupportedFormats;
import org.locationtech.spatial4j.shape.Shape;
/**
* Extend the standard JSONResponseWriter to support GeoJSON. This writes
* a {@link SolrDocumentList} with a 'FeatureCollection', following the
* specification in <a href="http://geojson.org/">geojson.org</a>
*/
public class GeoJSONResponseWriter extends JSONResponseWriter {
public static final String FIELD = "geojson.field";
@Override
public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
String geofield = req.getParams().get(FIELD, null);
if(geofield==null || geofield.length()==0) {
throw new SolrException(ErrorCode.BAD_REQUEST, "GeoJSON. Missing parameter: '"+FIELD+"'");
}
SchemaField sf = req.getSchema().getFieldOrNull(geofield);
if(sf==null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "GeoJSON. Unknown field: '"+FIELD+"'="+geofield);
}
SupportedFormats formats = null;
if(sf.getType() instanceof AbstractSpatialFieldType) {
SpatialContext ctx = ((AbstractSpatialFieldType)sf.getType()).getSpatialContext();
formats = ctx.getFormats();
}
JSONWriter w = new GeoJSONWriter(writer, req, rsp,
geofield,
formats);
try {
w.writeResponse();
} finally {
w.close();
}
}
}
class GeoJSONWriter extends JSONWriter {
final SupportedFormats formats;
final ShapeWriter geowriter;
final String geofield;
public GeoJSONWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp,
String geofield, SupportedFormats formats) {
super(writer, req, rsp);
this.geofield = geofield;
this.formats = formats;
if(formats==null) {
this.geowriter = null;
}
else {
this.geowriter = formats.getGeoJsonWriter();
}
}
@Override
public void writeResponse() throws IOException {
if(req.getParams().getBool(CommonParams.OMIT_HEADER, false)) {
if(wrapperFunction!=null) {
writer.write(wrapperFunction + "(");
}
rsp.removeResponseHeader();
NamedList<Object> vals = rsp.getValues();
Object response = vals.remove("response");
if(vals.size()==0) {
writeVal(null, response);
}
else {
throw new SolrException(ErrorCode.BAD_REQUEST,
"GeoJSON with "+CommonParams.OMIT_HEADER +
" can not return more than a result set");
}
if(wrapperFunction!=null) {
writer.write(')');
}
writer.write('\n'); // ending with a newline looks much better from the command line
}
else {
super.writeResponse();
}
}
@Override
public void writeSolrDocument(String name, SolrDocument doc, ReturnFields returnFields, int idx) throws IOException {
if( idx > 0 ) {
writeArraySeparator();
}
indent();
writeMapOpener(-1);
incLevel();
writeKey("type", false);
writeVal(null, "Feature");
Object val = doc.getFieldValue(geofield);
if(val != null) {
writeFeatureGeometry(val);
}
boolean first=true;
for (String fname : doc.getFieldNames()) {
if (fname.equals(geofield) || ((returnFields!= null && !returnFields.wantsField(fname)))) {
continue;
}
writeMapSeparator();
if (first) {
indent();
writeKey("properties", false);
writeMapOpener(-1);
incLevel();
first=false;
}
indent();
writeKey(fname, true);
val = doc.getFieldValue(fname);
// SolrDocument will now have multiValued fields represented as a Collection,
// even if only a single value is returned for this document.
if (val instanceof List) {
// shortcut this common case instead of going through writeVal again
writeArray(name,((Iterable)val).iterator());
} else {
writeVal(fname, val);
}
}
// GeoJSON does not really support nested FeatureCollections
if(doc.hasChildDocuments()) {
if(first == false) {
writeMapSeparator();
indent();
}
writeKey("_childDocuments_", true);
writeArrayOpener(doc.getChildDocumentCount());
List<SolrDocument> childDocs = doc.getChildDocuments();
for(int i=0; i<childDocs.size(); i++) {
writeSolrDocument(null, childDocs.get(i), null, i);
}
writeArrayCloser();
}
// check that we added any properties
if(!first) {
decLevel();
writeMapCloser();
}
decLevel();
writeMapCloser();
}
protected void writeFeatureGeometry(Object geo) throws IOException
{
// Support multi-valued geometries
if(geo instanceof Iterable) {
Iterator iter = ((Iterable)geo).iterator();
if(!iter.hasNext()) {
return; // empty list
}
else {
geo = iter.next();
// More than value
if(iter.hasNext()) {
writeMapSeparator();
indent();
writeKey("geometry", false);
incLevel();
// TODO: in the future, we can be smart and try to make this the appropriate MULTI* value
// if all the values are the same
// { "type": "GeometryCollection",
// "geometries": [
writeMapOpener(-1);
writeKey("type",false);
writeStr(null, "GeometryCollection", false);
writeMapSeparator();
writeKey("geometries", false);
writeArrayOpener(-1); // no trivial way to determine array size
incLevel();
// The first one
indent();
writeGeo(geo);
while(iter.hasNext()) {
// Each element in the array
writeArraySeparator();
indent();
writeGeo(iter.next());
}
decLevel();
writeArrayCloser();
writeMapCloser();
decLevel();
return;
}
}
}
// Single Value
if(geo!=null) {
writeMapSeparator();
indent();
writeKey("geometry", false);
writeGeo(geo);
}
}
protected void writeGeo(Object geo) throws IOException {
Shape shape = null;
String str = null;
if(geo instanceof Shape) {
shape = (Shape)geo;
}
else if(geo instanceof IndexableField) {
str = ((IndexableField)geo).stringValue();
}
else if(geo instanceof WriteableGeoJSON) {
shape = ((WriteableGeoJSON)geo).shape;
}
else {
str = geo.toString();
}
if(str !=null) {
// Assume it is well formed JSON
if(str.startsWith("{") && str.endsWith("}")) {
writer.write(str);
return;
}
if(formats==null) {
// The check is here and not in the constructor because we do not know if the
// *stored* values for the field look like JSON until we actually try to read them
throw new SolrException(ErrorCode.BAD_REQUEST,
"GeoJSON unable to write field: '&"+ GeoJSONResponseWriter.FIELD +"="+geofield+"' ("+str+")");
}
shape = formats.read(str);
}
if(geowriter==null) {
throw new SolrException(ErrorCode.BAD_REQUEST,
"GeoJSON unable to write field: '&"+ GeoJSONResponseWriter.FIELD +"="+geofield+"'");
}
if(shape!=null) {
geowriter.write(writer, shape);
}
}
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore) throws IOException
{
writeMapOpener((maxScore==null) ? 3 : 4);
incLevel();
writeKey("type",false);
writeStr(null, "FeatureCollection", false);
writeMapSeparator();
writeKey("numFound",false);
writeLong(null,numFound);
writeMapSeparator();
writeKey("start",false);
writeLong(null,start);
if (maxScore!=null) {
writeMapSeparator();
writeKey("maxScore",false);
writeFloat(null,maxScore);
}
writeMapSeparator();
// if can we get bbox of all results, we should write it here
// indent();
writeKey("features",false);
writeArrayOpener(size);
incLevel();
}
@Override
public void writeEndDocumentList() throws IOException
{
decLevel();
writeArrayCloser();
decLevel();
indent();
writeMapCloser();
}
}

View File

@ -70,8 +70,8 @@ public class JSONResponseWriter implements QueryResponseWriter {
}
class JSONWriter extends TextResponseWriter {
protected String wrapperFunction;
private String namedListStyle;
private String wrapperFunction;
private static final String JSON_NL_STYLE="json.nl";
private static final String JSON_NL_MAP="map";

View File

@ -0,0 +1,224 @@
/*
* 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.response.transform;
import java.io.IOException;
import java.util.Iterator;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.JSONResponseWriter;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.schema.AbstractSpatialFieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.QParser;
import org.apache.solr.search.SyntaxError;
import org.locationtech.spatial4j.io.GeoJSONWriter;
import org.locationtech.spatial4j.io.ShapeWriter;
import org.locationtech.spatial4j.io.SupportedFormats;
import org.locationtech.spatial4j.shape.Shape;
/**
* This DocumentTransformer will write a {@link Shape} to the SolrDocument using
* the requested format. Supported formats include:
* <ul>
* <li>GeoJSON</li>
* <li>WKT</li>
* <li>Polyshape</li>
* </ul>
* For more information see: <a href="https://github.com/locationtech/spatial4j/blob/master/FORMATS.md">spatial4j/FORMATS.md</a>
*
* The shape is either read from a stored field, or a ValueSource.
*
* This transformer is useful when:
* <ul>
* <li>You want to return a format different than the stored encoding (WKT vs GeoJSON)</li>
* <li>The {@link Shape} is stored in a {@link ValueSource}, not a stored field</li>
* <li>the value is not stored in a format the output understands (ie, raw GeoJSON)</li>
* </ul>
*
*/
public class GeoTransformerFactory extends TransformerFactory
{
@Override
public DocTransformer create(String display, SolrParams params, SolrQueryRequest req) {
String fname = params.get("f", display);
if(fname.startsWith("[") && fname.endsWith("]")) {
fname = display.substring(1,display.length()-1);
}
SchemaField sf = req.getSchema().getFieldOrNull(fname);
if(sf==null) {
throw new SolrException(ErrorCode.BAD_REQUEST,
this.getClass().getSimpleName() +" using unknown field: "+fname);
}
if(!(sf.getType() instanceof AbstractSpatialFieldType)) {
throw new SolrException(ErrorCode.BAD_REQUEST,
"GeoTransformer requested non-spatial field: "+fname + " ("+sf.getType().getClass().getSimpleName()+")");
}
final GeoFieldUpdater updater = new GeoFieldUpdater();
updater.field = fname;
updater.display = display;
updater.display_error = display+"_error";
ValueSource shapes = null;
AbstractSpatialFieldType<?> sdv = (AbstractSpatialFieldType<?>)sf.getType();
SpatialStrategy strategy = sdv.getStrategy(fname);
if(strategy instanceof CompositeSpatialStrategy) {
shapes = ((CompositeSpatialStrategy)strategy)
.getGeometryStrategy().makeShapeValueSource();
}
else if(strategy instanceof SerializedDVStrategy) {
shapes = ((SerializedDVStrategy)strategy)
.makeShapeValueSource();
}
String writerName = params.get("w", "GeoJSON");
updater.formats = strategy.getSpatialContext().getFormats();
updater.writer = updater.formats.getWriter(writerName);
if(updater.writer==null) {
StringBuilder str = new StringBuilder();
str.append( "Unknown Spatial Writer: " ).append(writerName);
str.append(" [");
for(ShapeWriter w : updater.formats.getWriters()) {
str.append(w.getFormatName()).append(' ');
}
str.append("]");
throw new SolrException(ErrorCode.BAD_REQUEST, str.toString());
}
QueryResponseWriter qw = req.getCore().getQueryResponseWriter(req);
updater.isJSON =
(qw.getClass() == JSONResponseWriter.class) &&
(updater.writer instanceof GeoJSONWriter);
// Using ValueSource
if(shapes!=null) {
// we don't really need the qparser... just so we can reuse valueSource
QParser parser = new QParser(null,null,params, req) {
@Override
public Query parse() throws SyntaxError {
return new MatchAllDocsQuery();
}
};
return new ValueSourceAugmenter(display, parser, shapes) {
@Override
protected void setValue(SolrDocument doc, Object val) {
updater.setValue(doc, val);
}
};
}
// Using the raw stored values
return new DocTransformer() {
@Override
public void transform(SolrDocument doc, int docid, float score) throws IOException {
Object val = doc.remove(updater.field);
if(val!=null) {
updater.setValue(doc, val);
}
}
@Override
public String getName() {
return updater.display;
}
@Override
public String[] getExtraRequestFields() {
return new String[] {updater.field};
}
};
}
}
class GeoFieldUpdater {
String field;
String display;
String display_error;
boolean isJSON;
ShapeWriter writer;
SupportedFormats formats;
void addShape(SolrDocument doc, Shape shape) {
if(isJSON) {
doc.addField(display, new WriteableGeoJSON(shape, writer));
}
else {
doc.addField(display, writer.toString(shape));
}
}
void setValue(SolrDocument doc, Object val) {
doc.remove(display);
if(val != null) {
if(val instanceof Iterable) {
Iterator iter = ((Iterable)val).iterator();
while(iter.hasNext()) {
addValue(doc, iter.next());
}
}
else {
addValue(doc, val);
}
}
}
void addValue(SolrDocument doc, Object val) {
if(val == null) {
return;
}
if(val instanceof Shape) {
addShape(doc, (Shape)val);
}
// Don't explode on 'InvalidShpae'
else if( val instanceof Exception) {
doc.setField( display_error, ((Exception)val).toString() );
}
else {
// Use the stored value
if(val instanceof IndexableField) {
val = ((IndexableField)val).stringValue();
}
try {
addShape(doc, formats.read(val.toString()));
}
catch(Exception ex) {
doc.setField( display_error, ex.toString() );
}
}
}
}

View File

@ -49,5 +49,6 @@ public abstract class TransformerFactory implements NamedListInitializedPlugin
defaultFactories.put( "child", new ChildDocTransformerFactory() );
defaultFactories.put( "json", new RawValueTransformerFactory("json") );
defaultFactories.put( "xml", new RawValueTransformerFactory("xml") );
defaultFactories.put( "geo", new GeoTransformerFactory() );
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.response.transform;
import java.io.IOException;
import org.apache.solr.common.util.JavaBinCodec;
import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.response.WriteableValue;
import org.locationtech.spatial4j.io.ShapeWriter;
import org.locationtech.spatial4j.shape.Shape;
/**
* This will let the writer add values to the response directly
*/
public class WriteableGeoJSON extends WriteableValue {
public final Shape shape;
public final ShapeWriter jsonWriter;
public WriteableGeoJSON(Shape shape, ShapeWriter jsonWriter) {
this.shape = shape;
this.jsonWriter = jsonWriter;
}
@Override
public Object resolve(Object o, JavaBinCodec codec) throws IOException {
codec.writeStr(jsonWriter.toString(shape));
return null; // this means we wrote it
}
@Override
public void write(String name, TextResponseWriter writer) throws IOException {
jsonWriter.write(writer.getWriter(), shape);
}
@Override
public String toString() {
return jsonWriter.toString(shape);
}
}

View File

@ -390,6 +390,13 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
}
}
/**
* @return The Spatial Context for this field type
*/
public SpatialContext getSpatialContext() {
return ctx;
}
@Override
public void write(TextResponseWriter writer, String name, IndexableField f) throws IOException {
writer.writeStr(name, f.stringValue(), true);

View File

@ -70,6 +70,7 @@
<field name="bbox" type="bbox" />
<dynamicField name="bboxD_*" type="bbox" indexed="true" />
<dynamicField name="str_*" type="string" indexed="true" stored="true"/>
</fields>

View File

@ -0,0 +1,279 @@
/*
* 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.response;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Map;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.junit.BeforeClass;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.io.SupportedFormats;
import org.locationtech.spatial4j.shape.Shape;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestGeoJSONResponseWriter extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
final ObjectMapper jsonmapper = new ObjectMapper();
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig-basic.xml","schema-spatial.xml");
createIndex();
}
public static void createIndex() {
// <field name="srpt_geohash" type="srpt_geohash" multiValued="true" />
// <field name="" type="srpt_quad" multiValued="true" />
// <field name="" type="srpt_packedquad" multiValued="true" />
// <field name="" type="stqpt_geohash" multiValued="true" />
// multiple valued field
assertU(adoc("id","H.A", "srpt_geohash","POINT( 1 2 )"));
assertU(adoc("id","H.B", "srpt_geohash","POINT( 1 2 )",
"srpt_geohash","POINT( 3 4 )"));
assertU(adoc("id","H.C", "srpt_geohash","LINESTRING (30 10, 10 30, 40 40)"));
assertU(adoc("id","Q.A", "srpt_quad","POINT( 1 2 )"));
assertU(adoc("id","Q.B", "srpt_quad","POINT( 1 2 )",
"srpt_quad","POINT( 3 4 )"));
assertU(adoc("id","Q.C", "srpt_quad","LINESTRING (30 10, 10 30, 40 40)"));
assertU(adoc("id","P.A", "srpt_packedquad","POINT( 1 2 )"));
assertU(adoc("id","P.B", "srpt_packedquad","POINT( 1 2 )",
"srpt_packedquad","POINT( 3 4 )"));
assertU(adoc("id","P.C", "srpt_packedquad","LINESTRING (30 10, 10 30, 40 40)"));
// single valued field
assertU(adoc("id","R.A", "srptgeom","POINT( 1 2 )"));
// non-spatial field
assertU(adoc("id","S.X", "str_shape","POINT( 1 2 )"));
assertU(adoc("id","S.A", "str_shape","{\"type\":\"Point\",\"coordinates\":[1,2]}"));
assertU(commit());
}
protected Map<String,Object> readJSON(String json) {
try {
return jsonmapper.readValue(json, Map.class);
}
catch(Exception ex) {
log.warn("Unable to read GeoJSON From: {}", json);
log.warn("Error", ex);
fail("Unable to parse JSON GeoJSON Response");
}
return null;
}
protected Map<String,Object> getFirstFeatureGeometry(Map<String,Object> json)
{
Map<String,Object> rsp = (Map<String,Object>)json.get("response");
assertEquals("FeatureCollection", rsp.get("type"));
List<Object> vals = (List<Object>)rsp.get("features");
assertEquals(1, vals.size());
Map<String,Object> feature = (Map<String,Object>)vals.get(0);
assertEquals("Feature", feature.get("type"));
return (Map<String,Object>)feature.get("geometry");
}
@Test
public void testRequestExceptions() throws Exception {
// Make sure we select the field
try {
h.query(req(
"q","*:*",
"wt","geojson",
"fl","*"));
fail("should Require a parameter to select the field");
}
catch(SolrException ex) {}
// non-spatial fields *must* be stored as JSON
try {
h.query(req(
"q","id:S.X",
"wt","geojson",
"fl","*",
"geojson.field", "str_shape"));
fail("should complain about bad shape config");
}
catch(SolrException ex) {}
}
@Test
public void testGeoJSONAtRoot() throws Exception {
// Try reading the whole resposne
String json = h.query(req(
"q","*:*",
"wt","geojson",
"rows","2",
"fl","*",
"geojson.field", "stqpt_geohash",
"indent","true"));
// Check that we have a normal solr response with 'responseHeader' and 'response'
Map<String,Object> rsp = readJSON(json);
assertNotNull(rsp.get("responseHeader"));
assertNotNull(rsp.get("response"));
json = h.query(req(
"q","*:*",
"wt","geojson",
"rows","2",
"fl","*",
"omitHeader", "true",
"geojson.field", "stqpt_geohash",
"indent","true"));
// Check that we have a normal solr response with 'responseHeader' and 'response'
rsp = readJSON(json);
assertNull(rsp.get("responseHeader"));
assertNull(rsp.get("response"));
assertEquals("FeatureCollection", rsp.get("type"));
assertNotNull(rsp.get("features"));
}
@Test
public void testGeoJSONOutput() throws Exception {
// Try reading the whole resposne
readJSON(h.query(req(
"q","*:*",
"wt","geojson",
"fl","*",
"geojson.field", "stqpt_geohash",
"indent","true")));
// Multivalued Valued Point
Map<String,Object> json = readJSON(h.query(req(
"q","id:H.B",
"wt","geojson",
"fl","*",
"geojson.field", "srpt_geohash",
"indent","true")));
Map<String,Object> geo = getFirstFeatureGeometry(json);
assertEquals( // NOTE: not actual JSON, it is Map.toString()!
"{type=GeometryCollection, geometries=["
+ "{type=Point, coordinates=[1, 2]}, "
+ "{type=Point, coordinates=[3, 4]}]}", ""+geo);
// Check the same value encoded on different field types
String[][] check = new String[][] {
{ "id:H.A", "srpt_geohash" },
{ "id:Q.A", "srpt_quad" },
{ "id:P.A", "srpt_packedquad" },
{ "id:R.A", "srptgeom" },
{ "id:S.A", "str_shape" },
};
for(String[] args : check) {
json = readJSON(h.query(req(
"q",args[0],
"wt","geojson",
"fl","*",
"geojson.field", args[1])));
geo = getFirstFeatureGeometry(json);
assertEquals(
"Error reading point from: "+args[1] + " ("+args[0]+")",
// NOTE: not actual JSON, it is Map.toString()!
"{type=Point, coordinates=[1, 2]}", ""+geo);
}
}
protected Map<String,Object> readFirstDoc(String json)
{
List docs = (List)((Map)readJSON(json).get("response")).get("docs");
return (Map)docs.get(0);
}
public static String normalizeMapToJSON(String val) {
val = val.replace("\"", ""); // remove quotes
val = val.replace(':', '=');
val = val.replace(", ", ",");
return val;
}
@Test
public void testTransformToAllFormats() throws Exception {
String wkt = "POINT( 1 2 )";
SupportedFormats fmts = SpatialContext.GEO.getFormats();
Shape shape = fmts.read(wkt);
String[] check = new String[] {
"srpt_geohash",
"srpt_geohash",
"srpt_quad",
"srpt_packedquad",
"srptgeom",
// "str_shape", // NEEDS TO BE A SpatialField!
};
String[] checkFormats = new String[] {
"GeoJSON",
"WKT",
"POLY"
};
for(String field : check) {
// Add a document with the given field
assertU(adoc("id","test",
field, wkt));
assertU(commit());
for(String fmt : checkFormats) {
String json = h.query(req(
"q","id:test",
"wt","json",
"indent", "true",
"fl","xxx:[geo f="+field+" w="+fmt+"]"
));
Map<String,Object> doc = readFirstDoc(json);
Object v = doc.get("xxx");
String expect = fmts.getWriter(fmt).toString(shape);
if(!(v instanceof String)) {
v = normalizeMapToJSON(v.toString());
expect = normalizeMapToJSON(expect);
}
assertEquals("Bad result: "+field+"/"+fmt, expect, v.toString());
}
}
}
}