/*
 * 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.
 */
package org.apache.lucene.spatial.query;

import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Shape;

import java.text.ParseException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;

Parses a string that usually looks like "OPERATION(SHAPE)" into a SpatialArgs object. The set of operations supported are defined in SpatialOperation, such as "Intersects" being a common one. The shape portion is defined by WKT WktShapeParser, but it can be overridden/customized via parseShape(String, SpatialContext). There are some optional name-value pair parameters that follow the closing parenthesis. Example:
  Intersects(ENVELOPE(-10,-8,22,20)) distErrPct=0.025

In the future it would be good to support something at least semi-standardized like a variant of [E]CQL.

@lucene.experimental
/** * 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 WKT {@link org.locationtech.spatial4j.io.WktShapeParser}, * but it can be overridden/customized via {@link #parseShape(String, org.locationtech.spatial4j.context.SpatialContext)}. * There are some optional name-value pair parameters that follow the closing parenthesis. Example: * <pre> * Intersects(ENVELOPE(-10,-8,22,20)) 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.
/** Writes a close approximation to the parsed input format. */
static String writeSpatialArgs(SpatialArgs args) { StringBuilder str = new StringBuilder(); str.append(args.getOperation().getName()); str.append('('); str.append(args.getShape().toString()); 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(ENVELOPE(-10,-8,22,20)) distErrPct=0.025".
Params:
  • v – The string to parse. Mandatory.
  • ctx – The spatial context. Mandatory.
Throws:
Returns:Not null.
/** * Parses a string such as "Intersects(ENVELOPE(-10,-8,22,20)) distErrPct=0.025". * * @param v The string to parse. Mandatory. * @param ctx The spatial context. Mandatory. * @return Not null. * @throws IllegalArgumentException if the parameters don't make sense or an add-on parameter is unknown * @throws ParseException If there is a problem parsing the string * @throws InvalidShapeException When the coordinates are invalid for the shape */
public SpatialArgs parse(String v, SpatialContext ctx) throws ParseException, InvalidShapeException { int idx = v.indexOf('('); int edx = v.lastIndexOf(')'); if (idx < 0 || idx > edx) { throw new ParseException("missing parens: " + v, -1); } SpatialOperation op = SpatialOperation.get(v.substring(0, idx).trim()); String body = v.substring(idx + 1, edx).trim(); if (body.length() < 1) { throw new ParseException("missing body : " + v, idx + 1); } Shape shape = parseShape(body, ctx); SpatialArgs args = newSpatialArgs(op, shape); if (v.length() > (edx + 1)) { body = v.substring(edx + 1).trim(); if (body.length() > 0) { Map<String, String> aa = parseMap(body); readNameValuePairs(args, aa); if (!aa.isEmpty()) { throw new IllegalArgumentException("unused parameters: " + aa); } } } args.validate(); return args; } protected SpatialArgs newSpatialArgs(SpatialOperation op, Shape shape) { return new SpatialArgs(op, shape); } protected void readNameValuePairs(SpatialArgs args, Map<String, String> nameValPairs) { args.setDistErrPct(readDouble(nameValPairs.remove(DIST_ERR_PCT))); args.setDistErr(readDouble(nameValPairs.remove(DIST_ERR))); } protected Shape parseShape(String str, SpatialContext ctx) throws ParseException { //return ctx.readShape(str);//still in Spatial4j 0.4 but will be deleted return ctx.readShapeFromWkt(str); } 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 zScaling=d f" (whitespace separated) into name-value pairs. If there is no '=' as in 'f' above then it's short for f=f.
/** Parses "a=b zScaling=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<>(); 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; } }