blob: d7ae442ddefbf177387956588b10f6931836b971 [file] [log] [blame]
using Lucene.Net.Benchmarks.ByTask.Utils;
using Lucene.Net.Documents;
using Lucene.Net.Spatial;
using Lucene.Net.Spatial.Prefix;
using Lucene.Net.Spatial.Prefix.Tree;
using Spatial4n.Core.Context;
using Spatial4n.Core.Shapes;
using System;
using System.Collections.Generic;
using Console = Lucene.Net.Util.SystemConsole;
namespace Lucene.Net.Benchmarks.ByTask.Feeds
{
/*
* 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>
/// Indexes spatial data according to a configured <see cref="SpatialStrategy"/> with optional
/// shape transformation via a configured <see cref="IShapeConverter"/>. The converter can turn points into
/// circles and bounding boxes, in order to vary the type of indexing performance tests.
/// Unless it's subclass-ed to do otherwise, this class configures a <see cref="SpatialContext"/>,
/// <see cref="SpatialPrefixTree"/>, and <see cref="RecursivePrefixTreeStrategy"/>. The Strategy is made
/// available to a query maker via the static method <see cref="GetSpatialStrategy(int)"/>.
/// See spatial.alg for a listing of spatial parameters, in particular those starting with "spatial."
/// and "doc.spatial".
/// </summary>
public class SpatialDocMaker : DocMaker
{
public static readonly string SPATIAL_FIELD = "spatial";
//cache spatialStrategy by round number
private static readonly IDictionary<int, SpatialStrategy> spatialStrategyCache = new Dictionary<int, SpatialStrategy>(); // LUCENENET: marked readonly
private SpatialStrategy strategy;
private IShapeConverter shapeConverter;
/// <summary>
/// Looks up the <see cref="SpatialStrategy"/> from the given round --
/// <see cref="Config.RoundNumber"/>. It's an error
/// if it wasn't created already for this round -- when <see cref="SpatialDocMaker"/> is initialized.
/// </summary>
public static SpatialStrategy GetSpatialStrategy(int roundNumber)
{
if (!spatialStrategyCache.TryGetValue(roundNumber, out SpatialStrategy result) || result == null)
{
throw new InvalidOperationException("Strategy should have been init'ed by SpatialDocMaker by now");
}
return result;
}
/// <summary>
/// Builds a <see cref="SpatialStrategy"/> from configuration options.
/// </summary>
protected virtual SpatialStrategy MakeSpatialStrategy(Config config)
{
//A Map view of Config that prefixes keys with "spatial."
var configMap = new DictionaryAnonymousClass(config);
SpatialContext ctx = SpatialContextFactory.MakeSpatialContext(configMap /*, null*/); // LUCENENET TODO: What is this extra param?
//Some day the strategy might be initialized with a factory but such a factory
// is non-existent.
return MakeSpatialStrategy(config, configMap, ctx);
}
private class DictionaryAnonymousClass : Dictionary<string, string>
{
private readonly Config config;
public DictionaryAnonymousClass(Config config)
{
this.config = config;
}
// LUCENENET TODO: EntrySet not supported. Should we throw on GetEnumerator()?
new public string this[string key] => config.Get("spatial." + key, null);
}
protected virtual SpatialStrategy MakeSpatialStrategy(Config config, IDictionary<string, string> configMap,
SpatialContext ctx)
{
//A factory for the prefix tree grid
SpatialPrefixTree grid = SpatialPrefixTreeFactory.MakeSPT(configMap, /*null,*/ ctx); // LUCENENET TODO: What is this extra param?
RecursivePrefixTreeStrategy strategy = new RecursivePrefixTreeStrategyAnonymousClass(grid, SPATIAL_FIELD, config);
int prefixGridScanLevel = config.Get("query.spatial.prefixGridScanLevel", -4);
if (prefixGridScanLevel < 0)
prefixGridScanLevel = grid.MaxLevels + prefixGridScanLevel;
strategy.PrefixGridScanLevel = prefixGridScanLevel;
double distErrPct = config.Get("spatial.distErrPct", .025);//doc & query; a default
strategy.DistErrPct = distErrPct;
return strategy;
}
private class RecursivePrefixTreeStrategyAnonymousClass : RecursivePrefixTreeStrategy
{
public RecursivePrefixTreeStrategyAnonymousClass(SpatialPrefixTree grid, string fieldName, Config config)
: base(grid, fieldName)
{
this.m_pointsOnly = config.Get("spatial.docPointsOnly", false);
}
}
public override void SetConfig(Config config, ContentSource source)
{
base.SetConfig(config, source);
if (!spatialStrategyCache.TryGetValue(config.RoundNumber, out SpatialStrategy existing) || existing == null)
{
//new round; we need to re-initialize
strategy = MakeSpatialStrategy(config);
spatialStrategyCache[config.RoundNumber] = strategy;
//TODO remove previous round config?
shapeConverter = MakeShapeConverter(strategy, config, "doc.spatial.");
Console.WriteLine("Spatial Strategy: " + strategy);
}
}
/// <summary>
/// Optionally converts points to circles, and optionally bbox'es result.
/// </summary>
public static IShapeConverter MakeShapeConverter(SpatialStrategy spatialStrategy,
Config config, string configKeyPrefix)
{
//by default does no conversion
double radiusDegrees = config.Get(configKeyPrefix + "radiusDegrees", 0.0);
double plusMinus = config.Get(configKeyPrefix + "radiusDegreesRandPlusMinus", 0.0);
bool bbox = config.Get(configKeyPrefix + "bbox", false);
return new ShapeConverterAnonymousClass(spatialStrategy, radiusDegrees, plusMinus, bbox);
}
private class ShapeConverterAnonymousClass : IShapeConverter
{
private readonly SpatialStrategy spatialStrategy;
private readonly double radiusDegrees;
private readonly double plusMinus;
private readonly bool bbox;
public ShapeConverterAnonymousClass(SpatialStrategy spatialStrategy, double radiusDegrees, double plusMinus, bool bbox)
{
this.spatialStrategy = spatialStrategy;
this.radiusDegrees = radiusDegrees;
this.plusMinus = plusMinus;
this.bbox = bbox;
}
public IShape Convert(IShape shape)
{
if ((radiusDegrees != 0.0 || plusMinus != 0.0) && shape is IPoint point)
{
double radius = radiusDegrees;
if (plusMinus > 0.0)
{
Random random = new Random(point.GetHashCode());//use hashCode so it's reproducibly random
radius += random.NextDouble() * 2 * plusMinus - plusMinus;
radius = Math.Abs(radius);//can happen if configured plusMinus > radiusDegrees
}
shape = spatialStrategy.SpatialContext.MakeCircle(point, radius);
}
if (bbox)
{
shape = shape.BoundingBox;
}
return shape;
}
}
// LUCENENET specific: de-nested IShapeConverter
public override Document MakeDocument()
{
DocState docState = GetDocState();
Document doc = base.MakeDocument();
// Set SPATIAL_FIELD from body
DocData docData = docState.docData;
// makeDocument() resets docState.getBody() so we can't look there; look in Document
string shapeStr = doc.GetField(DocMaker.BODY_FIELD).GetStringValue();
IShape shape = MakeShapeFromString(strategy, docData.Name, shapeStr);
if (shape != null)
{
shape = shapeConverter.Convert(shape);
//index
foreach (Field f in strategy.CreateIndexableFields(shape))
{
doc.Add(f);
}
}
return doc;
}
public static IShape MakeShapeFromString(SpatialStrategy strategy, string name, string shapeStr)
{
if (shapeStr != null && shapeStr.Length > 0)
{
try
{
return strategy.SpatialContext.ReadShapeFromWkt(shapeStr);
}
catch (Exception e)
{//InvalidShapeException TODO
Console.Error.WriteLine("Shape " + name + " wasn't parseable: " + e + " (skipping it)");
return null;
}
}
return null;
}
public override Document MakeDocument(int size)
{
//TODO consider abusing the 'size' notion to number of shapes per document
throw new NotSupportedException();
}
}
/// <summary>
/// Converts one shape to another. Created by
/// <see cref="SpatialDocMaker.MakeShapeConverter(SpatialStrategy, Config, string)"/>.
/// </summary>
public interface IShapeConverter
{
IShape Convert(IShape shape);
}
}