mirror of https://github.com/apache/lucene.git
SOLR-8859: read/write Shapes to String
This commit is contained in:
parent
36145d02cc
commit
022877fefa
|
@ -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
|
||||
----------------------
|
||||
|
|
|
@ -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. } */
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue