blob: 91758786db06c3d179551ed67b9c55515b1a60c6 [file] [log] [blame]
using J2N.Text;
using Spatial4n.Core.Context;
using Spatial4n.Core.Exceptions;
using Spatial4n.Core.Shapes;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Lucene.Net.Spatial.Queries
{
/*
* 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.
*/
/// <summary>
/// Parses a string that usually looks like "OPERATION(SHAPE)" into a <see cref="SpatialArgs"/>
/// object. The set of operations supported are defined in <see cref="SpatialOperation"/>, such
/// as "Intersects" being a common one. The shape portion is defined by WKT <see cref="Spatial4n.Core.IO.WktShapeParser"/>,
/// but it can be overridden/customized via <see cref="ParseShape(string, SpatialContext)"/>.
/// There are some optional name-value pair parameters that follow the closing parenthesis. Example:
/// <code>
/// Intersects(ENVELOPE(-10,-8,22,20)) distErrPct=0.025
/// </code>
/// <para/>
/// 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
/// </summary>
public class SpatialArgsParser
{
public const string DIST_ERR_PCT = "distErrPct";
public const string DIST_ERR = "distErr";
/// <summary>
/// Writes a close approximation to the parsed input format.
/// </summary>
public static string WriteSpatialArgs(SpatialArgs args)
{
var str = new StringBuilder();
str.Append(args.Operation.Name);
str.Append('(');
str.Append(args.Shape);
if (args.DistErrPct != null)
str.Append(" distErrPct=").Append(string.Format("{0:0.00}%", args.DistErrPct * 100d, CultureInfo.InvariantCulture));
if (args.DistErr != null)
str.Append(" distErr=").Append(args.DistErr);
str.Append(')');
return str.ToString();
}
/// <summary>
/// Parses a string such as "Intersects(ENVELOPE(-10,-8,22,20)) distErrPct=0.025".
/// </summary>
/// <param name="v">The string to parse. Mandatory.</param>
/// <param name="ctx">The spatial context. Mandatory.</param>
/// <returns>Not null.</returns>
/// <exception cref="ArgumentException">if the parameters don't make sense or an add-on parameter is unknown</exception>
/// <exception cref="ParseException">If there is a problem parsing the string</exception>
/// <exception cref="InvalidShapeException">When the coordinates are invalid for the shape</exception>
public virtual SpatialArgs Parse(string v, SpatialContext ctx)
{
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 - 0).Trim());
//Substring in .NET is (startPosn, length), But in Java it's (startPosn, endPosn)
//see http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/String.html#substring(int, int)
string body = v.Substring(idx + 1, edx - (idx + 1)).Trim();
if (body.Length < 1)
{
throw new ParseException("missing body : " + v, idx + 1);
}
var shape = ParseShape(body, ctx);
var args = NewSpatialArgs(op, shape);
if (v.Length > (edx + 1))
{
body = v.Substring(edx + 1).Trim();
if (body.Length > 0)
{
IDictionary<string, string> aa = ParseMap(body);
ReadNameValuePairs(args, aa);
if (aa.Count == 0)
{
throw new ArgumentException("unused parameters: " + aa);
}
}
}
args.Validate();
return args;
}
protected virtual SpatialArgs NewSpatialArgs(SpatialOperation op, IShape shape)
{
return new SpatialArgs(op, shape);
}
protected virtual void ReadNameValuePairs(SpatialArgs args, IDictionary<string, string> nameValPairs)
{
nameValPairs.TryGetValue(DIST_ERR_PCT, out string distErrPctStr);
nameValPairs.TryGetValue(DIST_ERR, out string distErrStr);
args.DistErrPct = ReadDouble(distErrPctStr);
nameValPairs.Remove(DIST_ERR_PCT);
args.DistErr = ReadDouble(distErrStr);
nameValPairs.Remove(DIST_ERR);
}
protected virtual IShape ParseShape(string str, SpatialContext ctx)
{
//return ctx.readShape(str);//still in Spatial4n 0.4 but will be deleted
return ctx.ReadShapeFromWkt(str);
}
protected static double? ReadDouble(string v)
{
return double.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out double val) ? val : (double?)null;
}
protected static bool ReadBool(string v, bool defaultValue)
{
return bool.TryParse(v, out bool ret) ? ret : defaultValue;
}
/// <summary>
/// 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.
/// </summary>
protected static IDictionary<string, string> ParseMap(string body)
{
var map = new Dictionary<string, string>();
StringTokenizer st = new StringTokenizer(body, " \n\t");
while (st.MoveNext())
{
string a = st.Current;
int idx = a.IndexOf('=');
if (idx > 0)
{
string k = a.Substring(0, idx - 0);
string v = a.Substring(idx + 1);
map[k] = v;
}
else
{
map[a] = a;
}
}
return map;
}
}
}