mirror of
https://github.com/apache/lucene.git
synced 2025-02-09 19:45:22 +00:00
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
|
https://github.com/locationtech/spatial4j/blob/master/FORMATS.md
|
||||||
To return the FeatureCollection as the root element, add '&omitHeader=true" (ryan)
|
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
|
Bug Fixes
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -28,7 +28,6 @@ import java.util.Set;
|
|||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import org.apache.lucene.document.Field;
|
import org.apache.lucene.document.Field;
|
||||||
import org.apache.lucene.document.StoredField;
|
import org.apache.lucene.document.StoredField;
|
||||||
import org.apache.lucene.index.IndexableField;
|
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.spatial.query.SpatialOperation;
|
||||||
import org.apache.lucene.uninverting.UninvertingReader.Type;
|
import org.apache.lucene.uninverting.UninvertingReader.Type;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.common.SolrException.ErrorCode;
|
||||||
import org.apache.solr.common.params.SolrParams;
|
import org.apache.solr.common.params.SolrParams;
|
||||||
import org.apache.solr.response.TextResponseWriter;
|
import org.apache.solr.response.TextResponseWriter;
|
||||||
import org.apache.solr.search.QParser;
|
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.SpatialContext;
|
||||||
import org.locationtech.spatial4j.context.SpatialContextFactory;
|
import org.locationtech.spatial4j.context.SpatialContextFactory;
|
||||||
import org.locationtech.spatial4j.distance.DistanceUtils;
|
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.Point;
|
||||||
import org.locationtech.spatial4j.shape.Rectangle;
|
import org.locationtech.spatial4j.shape.Rectangle;
|
||||||
import org.locationtech.spatial4j.shape.Shape;
|
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 RECIP_DISTANCE = "recipDistance";
|
||||||
public static final String NONE = "none";
|
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());
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
protected SpatialContext ctx;
|
protected SpatialContext ctx;
|
||||||
protected SpatialArgsParser argsParser;
|
protected SpatialArgsParser argsParser;
|
||||||
|
|
||||||
|
protected ShapeWriter shapeWriter;
|
||||||
|
protected ShapeReader shapeReader;
|
||||||
|
|
||||||
private final Cache<String, T> fieldStrategyCache = CacheBuilder.newBuilder().build();
|
private final Cache<String, T> fieldStrategyCache = CacheBuilder.newBuilder().build();
|
||||||
|
|
||||||
protected DistanceUnits distanceUnits;
|
protected DistanceUnits distanceUnits;
|
||||||
@ -130,6 +139,25 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
|||||||
" on field types with class "+getClass().getSimpleName());
|
" 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();
|
argsParser = newSpatialArgsParser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,38 +231,38 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
|||||||
return (shapeStr == null) ? shapeToString(shape) : shapeStr;
|
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)
|
if (str.length() == 0)
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "empty string shape");
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "empty string shape");
|
||||||
if (Character.isLetter(str.charAt(0))) {//WKT starts with a letter
|
|
||||||
try {
|
Shape shape = null;
|
||||||
return ctx.readShapeFromWkt(str);
|
if(shapeReader!=null) {
|
||||||
} catch (Exception e) {
|
shape = shapeReader.readIfSupported(str);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* Returns a String version of a shape to be used for the stored value.
|
||||||
* 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>
|
* The format can be selected using the initParam <code>format={WKT|GeoJSON}</code>
|
||||||
* <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.
|
|
||||||
*/
|
*/
|
||||||
protected String shapeToString(Shape shape) {
|
public String shapeToString(Shape shape) {
|
||||||
// return ctx.toString(shape);
|
if(shapeWriter!=null) {
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
return shapeWriter.toString(shape);
|
||||||
"Getting a String from a Shape is no longer possible. See javadocs for commentary.");
|
}
|
||||||
|
// 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. } */
|
/** Called from {@link #getStrategy(String)} upon first use by fieldName. } */
|
||||||
|
@ -82,7 +82,7 @@ public class DateRangeField extends AbstractSpatialPrefixTreeFieldType<NumberRan
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NRShape parseShape(String str) {
|
public NRShape parseShape(String str) {
|
||||||
if (str.contains(" TO ")) {
|
if (str.contains(" TO ")) {
|
||||||
//TODO parsing range syntax doesn't support DateMath on either side or exclusive/inclusive
|
//TODO parsing range syntax doesn't support DateMath on either side or exclusive/inclusive
|
||||||
try {
|
try {
|
||||||
@ -121,7 +121,7 @@ public class DateRangeField extends AbstractSpatialPrefixTreeFieldType<NumberRan
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String shapeToString(Shape shape) {
|
public String shapeToString(Shape shape) {
|
||||||
if (shape instanceof UnitNRShape) {
|
if (shape instanceof UnitNRShape) {
|
||||||
UnitNRShape unitShape = (UnitNRShape) shape;
|
UnitNRShape unitShape = (UnitNRShape) shape;
|
||||||
if (unitShape.getLevel() == tree.getMaxLevels()) {
|
if (unitShape.getLevel() == tree.getMaxLevels()) {
|
||||||
|
@ -24,6 +24,7 @@ import org.apache.commons.io.FileUtils;
|
|||||||
import org.apache.solr.core.AbstractBadConfigTestBase;
|
import org.apache.solr.core.AbstractBadConfigTestBase;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.locationtech.spatial4j.shape.Shape;
|
||||||
|
|
||||||
public class SpatialRPTFieldTypeTest extends AbstractBadConfigTestBase {
|
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();
|
deleteCore();
|
||||||
File managedSchemaFile = new File(tmpConfDir, "managed-schema");
|
File managedSchemaFile = new File(tmpConfDir, "managed-schema");
|
||||||
Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new 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);
|
rptMap.put("distanceUnits", distanceUnits);
|
||||||
if(geo!=null)
|
if(geo!=null)
|
||||||
rptMap.put("geo", geo);
|
rptMap.put("geo", geo);
|
||||||
|
if(format!=null) {
|
||||||
|
rptMap.put("format", format);
|
||||||
|
}
|
||||||
rptFieldType.init(oldSchema, rptMap);
|
rptFieldType.init(oldSchema, rptMap);
|
||||||
rptFieldType.setTypeName("location_rpt");
|
rptFieldType.setTypeName("location_rpt");
|
||||||
SchemaField newField = new SchemaField("geo", rptFieldType, SchemaField.STORED | SchemaField.INDEXED, null);
|
SchemaField newField = new SchemaField("geo", rptFieldType, SchemaField.STORED | SchemaField.INDEXED, null);
|
||||||
@ -229,4 +261,8 @@ public class SpatialRPTFieldTypeTest extends AbstractBadConfigTestBase {
|
|||||||
|
|
||||||
assertU(delQ("*:*"));
|
assertU(delQ("*:*"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupRPTField(String distanceUnits, String geo) throws Exception {
|
||||||
|
setupRPTField(distanceUnits, geo, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user