blob: 2b6ed3f51a189cbd8facad17cf10e83dc05d03d8 [file] [log] [blame]
/*
* 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.
*/
using System;
using Lucene.Net.Search;
using Spatial4n.Core.Context;
using Spatial4n.Core.Shapes;
namespace Lucene.Net.Spatial.BBox
{
/// <summary>
/**
* The algorithm is implemented as envelope on envelope overlays rather than
* complex polygon on complex polygon overlays.
* <p/>
* <p/>
* Spatial relevance scoring algorithm:
* <p/>
* <br/> queryArea = the area of the input query envelope
* <br/> targetArea = the area of the target envelope (per Lucene document)
* <br/> intersectionArea = the area of the intersection for the query/target envelopes
* <br/> queryPower = the weighting power associated with the query envelope (default = 1.0)
* <br/> targetPower = the weighting power associated with the target envelope (default = 1.0)
* <p/>
* <br/> queryRatio = intersectionArea / queryArea;
* <br/> targetRatio = intersectionArea / targetArea;
* <br/> queryFactor = Math.pow(queryRatio,queryPower);
* <br/> targetFactor = Math.pow(targetRatio,targetPower);
* <br/> score = queryFactor * targetFactor;
* <p/>
* Based on Geoportal's
* <a href="http://geoportal.svn.sourceforge.net/svnroot/geoportal/Geoportal/trunk/src/com/esri/gpt/catalog/lucene/SpatialRankingValueSource.java">
* SpatialRankingValueSource</a>.
*
* @lucene.experimental
*/
/// </summary>
public class AreaSimilarity : BBoxSimilarity
{
/**
* Properties associated with the query envelope
*/
private readonly SpatialContext ctx;
private readonly Rectangle queryExtent;
private readonly double queryArea;
private readonly double targetPower;
private readonly double queryPower;
public AreaSimilarity(SpatialContext ctx, Rectangle queryExtent, double queryPower, double targetPower)
{
this.ctx = ctx;
this.queryExtent = queryExtent;
this.queryArea = queryExtent.GetArea(ctx);
this.queryPower = queryPower;
this.targetPower = targetPower;
// if (this.qryMinX > queryExtent.getMaxX()) {
// this.qryCrossedDateline = true;
// this.qryArea = Math.abs(qryMaxX + 360.0 - qryMinX) * Math.abs(qryMaxY - qryMinY);
// } else {
// this.qryArea = Math.abs(qryMaxX - qryMinX) * Math.abs(qryMaxY - qryMinY);
// }
}
public AreaSimilarity(SpatialContext ctx, Rectangle queryExtent)
: this(ctx, queryExtent, 2.0, 0.5)
{
}
public String GetDelimiterQueryParameters()
{
return queryExtent + ";" + queryPower + ";" + targetPower;
}
public double Score(Rectangle target, Explanation exp)
{
if (target == null || queryArea <= 0)
{
return 0;
}
double targetArea = target.GetArea(ctx);
if (targetArea <= 0)
{
return 0;
}
double score = 0;
double top = Math.Min(queryExtent.GetMaxY(), target.GetMaxY());
double bottom = Math.Max(queryExtent.GetMinY(), target.GetMinY());
double height = top - bottom;
double width = 0;
// queries that cross the date line
if (queryExtent.GetCrossesDateLine())
{
// documents that cross the date line
if (target.GetCrossesDateLine())
{
double left = Math.Max(queryExtent.GetMinX(), target.GetMinX());
double right = Math.Min(queryExtent.GetMaxX(), target.GetMaxX());
width = right + 360.0 - left;
}
else
{
double qryWestLeft = Math.Max(queryExtent.GetMinX(), target.GetMaxX());
double qryWestRight = Math.Min(target.GetMaxX(), 180.0);
double qryWestWidth = qryWestRight - qryWestLeft;
if (qryWestWidth > 0)
{
width = qryWestWidth;
}
else
{
double qryEastLeft = Math.Max(target.GetMaxX(), -180.0);
double qryEastRight = Math.Min(queryExtent.GetMaxX(), target.GetMaxX());
double qryEastWidth = qryEastRight - qryEastLeft;
if (qryEastWidth > 0)
{
width = qryEastWidth;
}
}
}
}
else
{ // queries that do not cross the date line
if (target.GetCrossesDateLine())
{
double tgtWestLeft = Math.Max(queryExtent.GetMinX(), target.GetMinX());
double tgtWestRight = Math.Min(queryExtent.GetMaxX(), 180.0);
double tgtWestWidth = tgtWestRight - tgtWestLeft;
if (tgtWestWidth > 0)
{
width = tgtWestWidth;
}
else
{
double tgtEastLeft = Math.Max(queryExtent.GetMinX(), -180.0);
double tgtEastRight = Math.Min(queryExtent.GetMaxX(), target.GetMaxX());
double tgtEastWidth = tgtEastRight - tgtEastLeft;
if (tgtEastWidth > 0)
{
width = tgtEastWidth;
}
}
}
else
{
double left = Math.Max(queryExtent.GetMinX(), target.GetMinX());
double right = Math.Min(queryExtent.GetMaxX(), target.GetMaxX());
width = right - left;
}
}
// calculate the score
if ((width > 0) && (height > 0))
{
double intersectionArea = width * height;
double queryRatio = intersectionArea / queryArea;
double targetRatio = intersectionArea / targetArea;
double queryFactor = Math.Pow(queryRatio, queryPower);
double targetFactor = Math.Pow(targetRatio, targetPower);
score = queryFactor * targetFactor * 10000.0;
if (exp != null)
{
// StringBuilder sb = new StringBuilder();
// sb.append("\nscore=").append(score);
// sb.append("\n query=").append();
// sb.append("\n target=").append(target.toString());
// sb.append("\n intersectionArea=").append(intersectionArea);
//
// sb.append(" queryArea=").append(queryArea).append(" targetArea=").append(targetArea);
// sb.append("\n queryRatio=").append(queryRatio).append(" targetRatio=").append(targetRatio);
// sb.append("\n queryFactor=").append(queryFactor).append(" targetFactor=").append(targetFactor);
// sb.append(" (queryPower=").append(queryPower).append(" targetPower=").append(targetPower).append(")");
exp.Value = (float) score;
exp.Description = GetType().Name;
Explanation e = null;
exp.AddDetail(e = new Explanation((float)intersectionArea, "IntersectionArea"));
e.AddDetail(new Explanation((float)width, "width; Query: " + queryExtent));
e.AddDetail(new Explanation((float)height, "height; Target: " + target));
exp.AddDetail(e = new Explanation((float)queryFactor, "Query"));
e.AddDetail(new Explanation((float)queryArea, "area"));
e.AddDetail(new Explanation((float)queryRatio, "ratio"));
e.AddDetail(new Explanation((float)queryPower, "power"));
exp.AddDetail(e = new Explanation((float)targetFactor, "Target"));
e.AddDetail(new Explanation((float)targetArea, "area"));
e.AddDetail(new Explanation((float)targetRatio, "ratio"));
e.AddDetail(new Explanation((float)targetPower, "power"));
}
}
else if (exp != null)
{
exp.Value = 0;
exp.Description = "Shape does not intersect";
}
return score;
}
public override bool Equals(object obj)
{
var other = obj as AreaSimilarity;
if (other == null) return false;
return GetDelimiterQueryParameters().Equals(other.GetDelimiterQueryParameters());
}
public override int GetHashCode()
{
return GetDelimiterQueryParameters().GetHashCode();
}
}
}