package com.spatial4j.demo.solr;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFilter;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.update.AddUpdateCommand;
import org.apache.solr.update.processor.UpdateRequestProcessor;
import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class SpatialDemoUpdateProcessorFactory extends UpdateRequestProcessorFactory
{
private static final Logger log = LoggerFactory.getLogger(SpatialDemoUpdateProcessorFactory.class);
private SpatialContext ctx;
private String sourceFieldName;
@SuppressWarnings("unchecked")
@Override
public void init(NamedList args)
{
sourceFieldName = (String) args.get("shapeField");
final NamedList spatialContextNL = (NamedList<Object>) args.get("SpatialContext");
Map<String,String> ctxMap = new LinkedHashMap<>();
for (Map.Entry entry : (Iterable<Map.Entry>)spatialContextNL) {
ctxMap.put((String) entry.getKey(), entry.getValue().toString());
}
ctx = SpatialContextFactory.makeSpatialContext(ctxMap, null);
}
@Override
public DemoUpdateProcessor getInstance(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor next)
{
return new DemoUpdateProcessor(next, req.getSchema());
}
class DemoUpdateProcessor extends UpdateRequestProcessor
{
private final IndexSchema schema;
public DemoUpdateProcessor(UpdateRequestProcessor next, IndexSchema schema) {
super(next);
this.schema = schema;
}
@Override
public void processAdd(AddUpdateCommand cmd) throws IOException
{
// This parses the shape string into a shape object, and it copies them to the various
// spatial fields we have, making some modifications as needed.
final SolrInputField shapeField = cmd.solrDoc.get( sourceFieldName );
if( shapeField != null ) {
if( shapeField.getValueCount() > 1 ) {
throw new RuntimeException( "multiple values found for 'geometry' field: "+shapeField.getValue() );
}
final String wkt = shapeField.getValue().toString();
Shape shape;
try {
shape = ctx.readShapeFromWkt(wkt);
} catch (Exception e) {
log.error("Couldn't parse shape for doc "+cmd.getPrintableId(), e);
return;//skip doc
}
final float boost = shapeField.getBoost();
//We check existence for the following two fields since they require LSE which is optional
if (schema.getFieldOrNull("geo") != null) {
//The "geo" shape only accepts JtsGeometry
JtsGeometry jtsGeom = toJtsGeom(shape);
//log.warn("Couldn't index into 'geo' field for doc {}; got class {}", cmd.getPrintableId(), shape.getClass());
addField(cmd, "geo", jtsGeom, boost);
}
if (schema.getFieldOrNull("bbox") != null) {
addField(cmd, "bbox", shape.getBoundingBox(), boost);
}
addField(cmd, "ptvector", shape.getCenter(), boost);
//Work-around that SolrInputField treats Collection as multi-value due to ShapeCollection
if (shape instanceof Collection)
shape = new ShapeWrapper(shape); // http://issues.apache.org/jira/browse/SOLR-4329
addField(cmd, "geohash", shape, boost);
addField(cmd, "quad", shape, boost);
}
super.processAdd(cmd);
}
private JtsGeometry toJtsGeom(Shape shape) {
//TODO move this to a Spatial4j utility class?
if (shape instanceof JtsGeometry)
return (JtsGeometry) shape;
JtsSpatialContext jtsCtx = (JtsSpatialContext) ctx;
final Geometry geom;
if (shape instanceof ShapeCollection) {
ShapeCollection coll = (ShapeCollection) shape;
//assume a collection of Polygons... convert to JtsGeometry
//TODO handle other MULTI shapes?
final List<Polygon> polygonList = new ArrayList<>(coll.size());
for (int i = 0; i < coll.size(); i++) {
final Geometry geomI = jtsCtx.getGeometryFrom(coll.get(i));
if (geomI instanceof Polygon)
polygonList.add((Polygon)geomI);
else if (geomI instanceof MultiPolygon) {//happens when a polygon crosses the dateline; it gets split
MultiPolygon multiPolygon = (MultiPolygon) geomI;
multiPolygon.apply(new GeometryFilter() {
@Override
public void filter(Geometry geom) {
if (geom instanceof MultiPolygon)
return;//caller will recurse
polygonList.add((Polygon)geom);
}
});
}
}
geom = jtsCtx.getGeometryFactory().createMultiPolygon(polygonList.toArray(new Polygon[polygonList.size()]));
} else {
geom = jtsCtx.getGeometryFrom(shape);
}
return jtsCtx.makeShape(geom, true, true);
}
private void addField(AddUpdateCommand cmd, String name, Object shape, float boost) {
SolrInputField field = new SolrInputField(name);
field.setValue(shape, boost);
cmd.solrDoc.put(field.getName(), field);
}
}
}