【问题标题】:RavenDb Spatial QueryRavenDb 空间查询
【发布时间】:2021-04-18 13:18:38
【问题描述】:

我们正在从 MongoDb 迁移到 RavenDb,但遇到了问题。

我们有一个简单的模型(C# DotNet)

using System.Globalization;

public class Location 
{
    #region Properties

    public double Latitude { get; set; }
    public double Longitude { get; set; }

    #endregion Properties

    #region Methods

    public override string ToString()
    {
        var numberFormatInfo = new NumberFormatInfo { NumberDecimalSeparator = "." };

        return $"{Longitude.ToString(numberFormatInfo)} {Latitude.ToString(numberFormatInfo)}";
    }

    #endregion Methods
}

public class Place
{
    public Location[] Area {get;set;} // Please note the array
}

客户可以选择一个地点作为一对,也可以在交互式地图上绘制一个多边形来表示该地点,该多边形将存储在上面的 Area 属性中。

在 MongoDb 中,这再简单不过了

var locationQuery = new FilterDefinitionBuilder<Place>()
.GeoWithinBox(
 field: x => x.Area,
 lowerLeftX: query.Criteria.LongitudeBottomLeft,
 lowerLeftY: query.Criteria.LatitudeBottomLeft,
 upperRightX: query.Criteria.LongitudeTopRight,
 upperRightY: query.Criteria.LatitudeTopRight);

上面的查询类,代表了地图上当前可见的区域,客户正在查看。

尽管我尽了最大努力,但我无法破译有关如何实现相同目标的文档。 我希望有人能够对此事有所了解,关于如何使用 RavenDb Dotnet 客户端 (RavenDB.Client)

谢谢

更新和示例

在下面 Ayende Rahien 的精彩回答之后,我想我会在这里添加一些细节,以简化其他人登陆这里的旅程。

这就是索引的样子。

using System.Linq;

using Raven.Client.Documents.Indexes;

public class PlaceAreaIndex : AbstractIndexCreationTask<Place>
{
    public PlaceAreaIndex()
    {
        Map = places => from place in places
                        select new
                        {
                            Area = place.Area.Select(location => CreateSpatialField(location.Latitude, location.Longitude))
                        };
    }
}

在使用查询之前,您需要在服务器上注册您的索引。

new PlaceAreaIndex().Execute(store);

我添加了一些帮助类来帮助我进行测试,我在这里添加它们是为了让示例让您尽可能接近正在运行的代码

public class Box
{
    #region Constructor

    public Box(Location topRight, Location bottomLeft)
    {
        TopRight = topRight;
        BottomLeft = bottomLeft;

        TopLeft = new Location { Latitude = topRight.Latitude, Longitude = bottomLeft.Longitude };
        BottomRight = new Location { Latitude = bottomLeft.Latitude, Longitude = topRight.Longitude };
    }

    #endregion Constructor

    #region Properties

    public Location TopRight { get;}
    public Location TopLeft { get; }
    public Location BottomLeft { get; }
    public Location BottomRight { get; }

    #endregion Properties

    #region Methods

    public override string ToString()
    {
        return $"POLYGON (({TopRight}, {TopLeft}, {BottomLeft}, {BottomRight}, {TopRight}))";
    }

    #endregion Methods
}

然后使用助手

var topRight = Location.New(latitude: -22.674847351188916, longitude: 31.25061035156253);
var bottomLeft = Location.New(latitude: -28.9600886880069, longitude: 25.1422119140623);
var box = new Box(topRight: topRight, bottomLeft: bottomLeft);

之后就可以这样查询了

var places = await session
    .Query<Place, PlaceAreaIndex>()
    .Spatial(
        factory => factory.Area,
        criteria => criteria.RelatesToShape(
            shapeWkt: box.ToString(),
            relation: SpatialRelation.Within))
    .ToArrayAsync();

希望对你有帮助。

【问题讨论】:

  • 您可以在此处看到一个查询多边形区域的动态查询示例:github.com/ravendb/ravendb/blob/… 注意:多边形起点和终点位置必须相同,并且点必须按逆时针顺序
  • 嗨丹妮尔,谢谢你的链接。我确实看到了那个,但仍然无法弄清楚它如何应用于正在存储的数组。

标签: c# .net ravendb


【解决方案1】:

IIUC,问题是你可能有一个特定的位置或一个多边形,对吧? 在 RavenDB 中表示多边形时,需要使用 WKT。

例如,如下所示:

POLYGON((14.316406249999982 37.541615344157876,32.68554687499998 37.541615344157876,32.68554687499998 25.439901234431595,14.316406249999982 25.439901234431595,14.316406249999982 37.541615344157876))

这可以由 RavenDB 内部的空间引擎使用,并允许您使用 spatial.Contains 进行查询。

但是,这可能不是您想要的,具体取决于使用情况。

如果您想要的只是在一个矩形内找到所有具有 a 位置的Places,那么将其作为一组位置进行威胁会更容易。 另一方面,如果这真的是一个多边形,那就不同了。

可能更容易展示一些东西。 RavenDB 中的样本数据有空间数据,如下所示:

$wkt = "POLYGON((-1.0800308814195025 51.942980464942416,0.5789046654554975 51.942980464942416,0.5789046654554975 51.01935531918276,-1.0800308814195025 51.01935531918276,-1.0800308814195025 51.942980464942416))"
from Employees 
where spatial.within( spatial.point(Address.Location.Latitude , Address .Location.Longitude), 
    spatial.wkt($wkt))

然后您可以看到新的空间选项卡,它将向您显示结果:

这显示了矩形 (wkt) 和一个点。

但是,如果你有一个数组,你也可以使用:

    "Locations": [
        {
            "Latitude": 51.52384070000001,
            "Longitude": -0.0944233
        },
        {
            "Latitude": 51.48145355123356,
            "Longitude": -0.17475717569860105
        }
    ],

然而,这需要一个显式的索引来处理,你可以这样写:

from e in docs.Employees
select new 
{
    Locations = e.Locations.Select(l => CreateSpatialField(l.Latitude, l.Longitude))
}

这会索引员工的所有个人位置。

然后您可以使用以下方式查询它:

$wkt = "POLYGON((-1.0800308814195025 51.942980464942416,0.5789046654554975 51.942980464942416,0.5789046654554975 51.01935531918276,-1.0800308814195025 51.01935531918276,-1.0800308814195025 51.942980464942416))"
from index 'Employees/ByLocations'
where spatial.within( Locations, spatial.wkt($wkt))

但是,另一种选择是当您需要将位置列表转换为多边形时。 例如,考虑这个 WKT: POLYGON((-0.09345874990495329 51.7543103056377,-0.6823256691840518 51.56348306507395,-0.2252946874049533 51.30046869394702,0.09990102742639895 51.42120466591834,-0.09345874990495329 51.7543103056377))

这代表这个多边形:

然后您可以使用与以前相同的查询样式,但请注意:

  • 您的contains 查询现在需要包含整个 多边形。您也可以使用intersect,但使用单点选项可能会更好。
  • 您需要了解 WKT 中点的顺序。这很重要。你需要逆时针。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-09
    相关资源
    最近更新 更多