Skip to content

Commit

Permalink
SOLR-8814: add GeoJSONResponseWriter
Browse files Browse the repository at this point in the history
  • Loading branch information
ryantxu committed Mar 9, 2016
1 parent 4e911f2 commit 4792f0c
Show file tree
Hide file tree
Showing 4 changed files with 410 additions and 1 deletion.
3 changes: 2 additions & 1 deletion solr/core/src/java/org/apache/solr/core/SolrCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -2123,10 +2123,11 @@ public PluginBag<QueryResponseWriter> getResponseWriters() {
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());
Expand Down
290 changes: 290 additions & 0 deletions solr/core/src/java/org/apache/solr/response/GeoJSONResponseWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
/*
* 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.request.SolrQueryRequest;
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.context.jts.JtsSpatialContext;
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 {
@Override
public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {

SpatialContext ctx = null;
String geofield = req.getParams().get("geojson.field", "geometry");
SchemaField sf = req.getSchema().getFieldOrNull(geofield);
if(sf != null && sf.getType() instanceof AbstractSpatialFieldType) {
AbstractSpatialFieldType sftype = (AbstractSpatialFieldType)sf.getType();
ctx = sftype.getSpatialContext();
}

if(ctx==null) {
try {
Class.forName("com.vividsolutions.jts.geom.Geometry");
ctx = JtsSpatialContext.GEO;
}
catch(Exception ex) {
ctx = SpatialContext.GEO;
}
}
JSONWriter w = new GeoJSONWriter(writer, req, rsp,
geofield,
ctx.getFormats());

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;
this.geowriter = formats.getGeoJsonWriter();
}

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 {
str = geo.toString();
}
if(str !=null) {
// Assume it is well formed JSON
if(str.startsWith("{") && str.endsWith("}")) {
shape = null;
}
else {
// try reading all format types
shape = formats.read(str);
str = null;
}
}

if(str!=null) {
writer.write(str);
}
else if(shape!=null) {
geowriter.write(writer, shape);
}
}

@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 geo = doc.getFieldValue(geofield);
if(geo != null) {
// Support multi-valued geometries
if(geo instanceof Iterable) { // multi-valued fields
Iterator iter = ((Iterable)geo).iterator();
if(!iter.hasNext()) {
geo = null;
}
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();
geo = null;
}
}
}

// Single Value
if(geo!=null) {
writeMapSeparator();
indent();
writeKey("geometry", false);
writeGeo(geo);
}
}

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);
Object 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();
}

@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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,13 @@ public T call() throws Exception {
throw Throwables.propagate(e.getCause());
}
}

/**
* @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 {
Expand Down
Loading

0 comments on commit 4792f0c

Please sign in to comment.