SOLR-8859: read/write Shapes to String

This commit is contained in:
Ryan McKinley 2016-03-16 12:52:00 -07:00
parent 36145d02cc
commit 022877fefa
4 changed files with 95 additions and 29 deletions

View File

@ -45,6 +45,8 @@ New Features
https://github.com/locationtech/spatial4j/blob/master/FORMATS.md
To return the FeatureCollection as the root element, add '&omitHeader=true" (ryan)
* SOLR-8859: AbstractSpatialFieldType will now convert Shapes to/from Strings
using the SpatialContext. (ryan)
Bug Fixes
----------------------

View File

@ -28,7 +28,6 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexableField;
@ -44,6 +43,7 @@ import org.apache.lucene.spatial.query.SpatialArgsParser;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.uninverting.UninvertingReader.Type;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.search.QParser;
@ -60,6 +60,9 @@ import com.google.common.cache.CacheBuilder;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.io.ShapeReader;
import org.locationtech.spatial4j.io.ShapeWriter;
import org.locationtech.spatial4j.io.SupportedFormats;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
@ -83,11 +86,17 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
public static final String RECIP_DISTANCE = "recipDistance";
public static final String NONE = "none";
/** Optional param to pick the string conversion */
public static final String FORMAT = "format";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected SpatialContext ctx;
protected SpatialArgsParser argsParser;
protected ShapeWriter shapeWriter;
protected ShapeReader shapeReader;
private final Cache<String, T> fieldStrategyCache = CacheBuilder.newBuilder().build();
protected DistanceUnits distanceUnits;
@ -130,6 +139,25 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
" on field types with class "+getClass().getSimpleName());
}
final SupportedFormats fmts = ctx.getFormats();
final String format = args.remove(FORMAT);
if (format != null) {
shapeWriter = fmts.getWriter(format);
shapeReader = fmts.getReader(format);
if(shapeWriter==null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unknown Shape Format: "+ format);
}
if(shapeReader==null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unknown Shape Format: "+ format);
}
}
else {
// Otherwise, pick the first supported reader/writer
shapeWriter = fmts.getWriters().get(0);
shapeReader = fmts.getReaders().get(0);
}
argsParser = newSpatialArgsParser();
}
@ -203,38 +231,38 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
return (shapeStr == null) ? shapeToString(shape) : shapeStr;
}
protected Shape parseShape(String str) {
/** Create a {@link Shape} from the input string */
public Shape parseShape(String str) {
if (str.length() == 0)
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "empty string shape");
if (Character.isLetter(str.charAt(0))) {//WKT starts with a letter
try {
return ctx.readShapeFromWkt(str);
} catch (Exception e) {
String message = e.getMessage();
if (!message.contains(str))
message = "Couldn't parse shape '" + str + "' because: " + message;
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, message, e);
}
} else {
return SpatialUtils.parsePointSolrException(str, ctx);
Shape shape = null;
if(shapeReader!=null) {
shape = shapeReader.readIfSupported(str);
}
if(shape==null) {
// Try all supported formats
shape = ctx.getFormats().read(str);
}
if(shape==null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unable to parse shape from: "+str);
}
return shape;
}
/**
* Returns a String version of a shape to be used for the stored value. This method in Solr is only called if for some
* reason a Shape object is passed to the field type (perhaps via a custom UpdateRequestProcessor),
* *and* the field is marked as stored. <em>The default implementation throws an exception.</em>
* <p>
* Spatial4j 0.4 is probably the last release to support SpatialContext.toString(shape) but it's deprecated with no
* planned replacement. Shapes do have a toString() method but they are generally internal/diagnostic and not
* standard WKT.
* The solution is subclassing and calling ctx.toString(shape) or directly using LegacyShapeReadWriterFormat or
* passing in some sort of custom wrapped shape that holds a reference to a String or can generate it.
* Returns a String version of a shape to be used for the stored value.
*
* The format can be selected using the initParam <code>format={WKT|GeoJSON}</code>
*/
protected String shapeToString(Shape shape) {
// return ctx.toString(shape);
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Getting a String from a Shape is no longer possible. See javadocs for commentary.");
public String shapeToString(Shape shape) {
if(shapeWriter!=null) {
return shapeWriter.toString(shape);
}
// This will only happen if the context does not have any writers
throw new SolrException(ErrorCode.SERVER_ERROR, "ShapeWriter not configured");
}
/** Called from {@link #getStrategy(String)} upon first use by fieldName. } */

View File

@ -82,7 +82,7 @@ public class DateRangeField extends AbstractSpatialPrefixTreeFieldType<NumberRan
}
@Override
protected NRShape parseShape(String str) {
public NRShape parseShape(String str) {
if (str.contains(" TO ")) {
//TODO parsing range syntax doesn't support DateMath on either side or exclusive/inclusive
try {
@ -121,7 +121,7 @@ public class DateRangeField extends AbstractSpatialPrefixTreeFieldType<NumberRan
}
@Override
protected String shapeToString(Shape shape) {
public String shapeToString(Shape shape) {
if (shape instanceof UnitNRShape) {
UnitNRShape unitShape = (UnitNRShape) shape;
if (unitShape.getLevel() == tree.getMaxLevels()) {

View File

@ -24,6 +24,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.solr.core.AbstractBadConfigTestBase;
import org.junit.After;
import org.junit.Before;
import org.locationtech.spatial4j.shape.Shape;
public class SpatialRPTFieldTypeTest extends AbstractBadConfigTestBase {
@ -201,7 +202,35 @@ public class SpatialRPTFieldTypeTest extends AbstractBadConfigTestBase {
);
}
private void setupRPTField(String distanceUnits, String geo) throws Exception {
public void testShapeToFromStringWKT() throws Exception {
// Check WKT
setupRPTField("miles", "true", "WKT");
AbstractSpatialFieldType ftype = (AbstractSpatialFieldType)
h.getCore().getLatestSchema().getField("geo").getType();
String wkt = "POINT (1 2)";
Shape shape = ftype.parseShape(wkt);
String out = ftype.shapeToString(shape);
assertEquals(wkt, out);
}
public void testShapeToFromStringGeoJSON() throws Exception {
// Check WKT
setupRPTField("miles", "true", "GeoJSON");
AbstractSpatialFieldType ftype = (AbstractSpatialFieldType)
h.getCore().getLatestSchema().getField("geo").getType();
String json = "{\"type\":\"Point\",\"coordinates\":[1,2]}";
Shape shape = ftype.parseShape(json);
String out = ftype.shapeToString(shape);
assertEquals(json, out);
}
private void setupRPTField(String distanceUnits, String geo, String format) throws Exception {
deleteCore();
File managedSchemaFile = new File(tmpConfDir, "managed-schema");
Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema
@ -220,6 +249,9 @@ public class SpatialRPTFieldTypeTest extends AbstractBadConfigTestBase {
rptMap.put("distanceUnits", distanceUnits);
if(geo!=null)
rptMap.put("geo", geo);
if(format!=null) {
rptMap.put("format", format);
}
rptFieldType.init(oldSchema, rptMap);
rptFieldType.setTypeName("location_rpt");
SchemaField newField = new SchemaField("geo", rptFieldType, SchemaField.STORED | SchemaField.INDEXED, null);
@ -229,4 +261,8 @@ public class SpatialRPTFieldTypeTest extends AbstractBadConfigTestBase {
assertU(delQ("*:*"));
}
private void setupRPTField(String distanceUnits, String geo) throws Exception {
setupRPTField(distanceUnits, geo, null);
}
}