You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用StackExchange.Redis优化C#点在多边形校验 实现经纬度城市归属查询

基于StackExchange.Redis实现点在多边形快速匹配的解决方案

方案一:使用RediSearch模块(性能最优,推荐)

Redis原生不支持多边形空间查询,加载RediSearch模块后可直接支持GeoShape级别的多边形包含查询,无需自行遍历所有城市多边形,单次查询耗时可稳定在1ms以内。

实现步骤

  • 环境准备:确保Redis实例已加载RediSearch、ReJSON模块,主流云厂商Redis实例均支持一键开启,自建实例可直接加载官方编译好的模块文件。
  • 数据入库&索引构建:将所有城市的多边形以GeoJSON格式存入Redis JSON结构,同时为空间字段建立GEOSHAPE类型索引
// 连接Redis
var multiplexer = ConnectionMultiplexer.Connect("你的Redis连接串");
var db = multiplexer.GetDatabase();
var ft = db.FT(); // 对应RediSearch命令
var json = db.JSON();

// 第一步:创建索引
ft.Create("idx:cities", new FTCreateParams()
    .On(IndexDataType.Json)
    .Prefix("city:"),
    new Schema()
        .AddGeoshapeField("$.polygon", "polygon"));

// 第二步:存城市数据,Polygon转GeoJSON格式
var city1GeoJson = new {
    type = "Polygon",
    coordinates = new [] {
        new [] { 1.11865, 5.9581 }, // 注意RediSearch坐标是[经度,纬度]顺序
        new [] { -1.47411, 4.99572 },
        new [] { -2.00145, 6.19298 },
        new [] { 0.38257, 6.98422 },
        new [] { 1.11865, 5.9581 }
    }
};
json.Set("city:1", "$", new {
    name = "City1",
    polygon = city1GeoJson
});
// 按同样逻辑存入所有城市数据
  • 查询逻辑:直接传入待判断的经纬度点,一次查询即可返回包含该点的城市
// 待判断点:纬度39.1,经度-6.5
var query = new Query("@polygon:[CONTAINS $POINT]")
    .SetParam("POINT", "POINT(-6.5 39.1)"); // 格式为POINT(经度 纬度)
var res = ft.Search("idx:cities", query);
// res.Documents里就是所有包含该点的城市数据,直接取即可

方案二:原生Redis无模块适配方案

如果无法为Redis加载额外模块,可通过「GEO预筛选+本地计算+结果缓存」的架构提升性能,相比当前全量遍历的逻辑性能可提升10倍以上。

实现步骤

  • 预计算每个城市多边形的最小外接矩形(MBR),记录每个城市MBR的最大/最小经纬度、中心点坐标,将城市的多边形序列化后和MBR数据一同存入Redis Hash结构,同时将所有城市的中心点存入Redis GEO集合。
// 示例:计算多边形MBR
public (double minLng, double maxLng, double minLat, double maxLat, double centerLng, double centerLat) CalcMBR(List<GeoPoints> poly)
{
    var lngs = poly.Select(p => p.Lg).ToList();
    var lats = poly.Select(p => p.Lt).ToList();
    var minLng = lngs.Min();
    var maxLng = lngs.Max();
    var minLat = lats.Min();
    var maxLat = lats.Max();
    return (minLng, maxLng, minLat, maxLat, (minLng+maxLng)/2, (minLat+maxLat)/2);
}

// 存GEO集合
db.GeoAdd("geo:cities", new GeoEntry(centerLng, centerLat, cityId));
// 存城市元数据
db.HashSet($"city:meta:{cityId}", new HashEntry[] {
    new ("poly", JsonSerializer.Serialize(poly)),
    new ("minLng", minLng),
    new ("maxLng", maxLng),
    new ("minLat", minLat),
    new ("maxLat", maxLat)
});
  • 查询时先通过GEORADIUS筛出可能匹配的城市,再做精确判断:
// 第一步:GEO预筛选,半径按你所在区域最大城市的直径设置,比如500km
var candidateCityIds = db.GeoRadius("geo:cities", point.Lg, point.Lt, 500, GeoUnit.Kilometers)
    .Select(x => x.Member.ToString())
    .ToList();

foreach(var cityId in candidateCityIds)
{
    // 先判断点是否在城市MBR内,过滤无效候选
    var meta = db.HashGetAll($"city:meta:{cityId}");
    var minLng = double.Parse(meta.First(x => x.Name == "minLng").Value);
    var maxLng = double.Parse(meta.First(x => x.Name == "maxLng").Value);
    var minLat = double.Parse(meta.First(x => x.Name == "minLat").Value);
    var maxLat = double.Parse(meta.First(x => x.Name == "maxLat").Value);
    if(point.Lg < minLng || point.Lg > maxLng || point.Lt < minLat || point.Lt > maxLat)
        continue;
    
    // 再用你原有的IsPointInPolygon方法判断
    var poly = JsonSerializer.Deserialize<List<GeoPoints>>(meta.First(x => x.Name == "poly").Value);
    if(IsPointInPolygon(poly, point))
    {
        // 匹配到城市,把结果缓存1小时,相同点下次直接返回
        db.StringSet($"geo:point:{point.Lg:F6}:{point.Lt:F6}", cityId, TimeSpan.FromHours(1));
        return cityId;
    }
}

额外优化建议

  • 你原有IsPointInPolygon方法可先增加MBR判断逻辑,再执行射线法计算,减少无效循环。
  • 如果城市数量过万,可增加行政区划层级过滤,先匹配省份再匹配城市,进一步缩小候选范围。

内容的提问来源于stack exchange,提问作者user2921359

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 16:54:04