package org.apache.blur.analysis.type.spatial;
/*
* 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.
*/
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Shape;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
/**
* Parses a string that usually looks like "OPERATION(SHAPE)" into a
* {@link SpatialArgs} object. The set of operations supported are defined in
* {@link SpatialOperation}, such as "Intersects" being a common one. The shape
* portion is defined by {@link ShapeReadWriter#readShape(String)}. There are
* some optional name-value pair parameters that follow the closing parenthesis.
* Example:
*
* <pre>
* Intersects(-10,20,-8,22) distErrPct=0.025
* </pre>
* <p/>
* In the future it would be good to support something at least
* semi-standardized like a variant of <a href=
* "http://docs.geoserver.org/latest/en/user/filter/ecql_reference.html#spatial-predicate"
* > [E]CQL</a>.
*
* @lucene.experimental
*/
public class SpatialArgsParser {
public static final String DIST_ERR_PCT = "distErrPct";
public static final String DIST_ERR = "distErr";
/** Writes a close approximation to the parsed input format. */
public static String writeSpatialArgs(SpatialArgs args, ShapeReadWriter<SpatialContext> shapeReadWriter) {
StringBuilder str = new StringBuilder();
str.append(args.getOperation().getName());
str.append('(');
str.append(shapeReadWriter.writeShape(args.getShape()));
if (args.getDistErrPct() != null)
str.append(" distErrPct=").append(String.format(Locale.ROOT, "%.2f%%", args.getDistErrPct() * 100d));
if (args.getDistErr() != null)
str.append(" distErr=").append(args.getDistErr());
str.append(')');
return str.toString();
}
/**
* Parses a string such as "Intersects(-10,20,-8,22) distErrPct=0.025".
*
* @param v
* The string to parse. Mandatory.
* @param shapeReadWriter
* The spatial shapeReadWriter. Mandatory.
* @return Not null.
* @throws IllegalArgumentException
* If there is a problem parsing the string.
* @throws InvalidShapeException
* Thrown from {@link ShapeReadWriter#readShape(String)}
*/
public static SpatialArgs parse(String v, ShapeReadWriter<SpatialContext> shapeReadWriter) throws IllegalArgumentException, InvalidShapeException {
int idx = v.indexOf('(');
int edx = v.lastIndexOf(')');
if (idx < 0 || idx > edx) {
throw new IllegalArgumentException("missing parens: " + v, null);
}
SpatialOperation op = SpatialOperation.get(v.substring(0, idx).trim());
String body = v.substring(idx + 1, edx).trim();
if (body.length() < 1) {
throw new IllegalArgumentException("missing body : " + v, null);
}
Shape shape = shapeReadWriter.readShape(body);
SpatialArgs args = new SpatialArgs(op, shape);
if (v.length() > (edx + 1)) {
body = v.substring(edx + 1).trim();
if (body.length() > 0) {
Map<String, String> aa = parseMap(body);
args.setDistErrPct(readDouble(aa.remove(DIST_ERR_PCT)));
args.setDistErr(readDouble(aa.remove(DIST_ERR)));
if (!aa.isEmpty()) {
throw new IllegalArgumentException("unused parameters: " + aa, null);
}
}
}
args.validate();
return args;
}
protected static Double readDouble(String v) {
return v == null ? null : Double.valueOf(v);
}
protected static boolean readBool(String v, boolean defaultValue) {
return v == null ? defaultValue : Boolean.parseBoolean(v);
}
/**
* Parses "a=b c=d f" (whitespace separated) into name-value pairs. If there
* is no '=' as in 'f' above then it's short for f=f.
*/
protected static Map<String, String> parseMap(String body) {
Map<String, String> map = new HashMap<String, String>();
StringTokenizer st = new StringTokenizer(body, " \n\t");
while (st.hasMoreTokens()) {
String a = st.nextToken();
int idx = a.indexOf('=');
if (idx > 0) {
String k = a.substring(0, idx);
String v = a.substring(idx + 1);
map.put(k, v);
} else {
map.put(a, a);
}
}
return map;
}
}