美文网首页gis
「笔记」结合redis的地图点位聚合方案

「笔记」结合redis的地图点位聚合方案

作者: 花森文宝 | 来源:发表于2021-10-26 16:24 被阅读0次

    如果不懂地图聚合点,可以先看这个文章,了解各个算法:地图兴趣点聚合算法的探索与实践

    作为giser可能首先会考虑直接用 openlayers 或 cesium 实现点位聚合,但是面对庞大数据,显然 openlayers 和 cesium 都太吃性能了。
    简单介绍一下方案:将经纬度根据不同地图层级切割成一个个独立的小格子,类似以上链接中的这个图,不过实现过程跟他讲的不一样。反正结果就是每个格子的点聚合到一起。

    框架:redis 存储基础点和聚合点,go 将基础点算成聚合点,web 显示点位。

    redis

    点(经纬度)存在数据库中的,全取出来利用redis做聚合运算,因为 redis 的读/写性能远超数据库。
    首先要了解 redis 的数据类型命令语句,看官网就可以了。
    这里需要用到两个redis服务器,为了区分,一个叫 infoRes,一个叫 geoRes。infoRes 中存基础点和聚合点的所有详细信息,数据类型用的是 Hash,geoRes 根据不同层级存聚合点位置,另外基础点也存在这里面。具体用法继续看 Go。

    Go

    点位计算存储
    1. 从数据库里取出点后要把无效的经纬度筛掉,比如空的、不符合经纬度范围的等等。
    2. 获取地图层级数组。
    3. 每个层级下,分批获取 geoRes 中的基础点,主要是拿到每个点的 key。
    4. 拿 key 去 infoRes 去拿每个点的详细信息。
    5. 根据当前层级、经纬度,得出当前点所在格子的序列号 clusterKey。
    6. 这一步就在算聚合点的位置和数量了,有相同 clusterKey 的算一个格子,数量加起来,经纬度要根据数量比例算。
      代码有简化,仅供参考
    buffer := make(map[string]*clusterPoint)
    for i:= 0; ; i++ {
          // 分批获取 geoRes 中的基础点,主要是拿到每个点的 key
          arr, _ := redis.String(geoRes.Do("ZREVRANGEBYSCORE", "marker", "+inf", "-inf", "limit", i*1000, 1000 ))
          if arr == nil || len(arr) == 0 {
              break
          }
    
          for _, temp := range arr {
              // 拿 key 去 infoRes 去拿每个点的详细信息
              info, _ := redis.Strings(infoRes.Do("HGETALL", key))
              dic := make(map[string]string, len(info)/2)
              for i := 0; i < len(info); i = i + 2 {
                   dic[info[i]] = info[i+2]
              }
              // 根据层级、经纬度序列化一个聚合缓存 clusterKey,表示哪个格子
              clusterKey := encodeClusterKey(dic["lng"], dic["lat"], currentZoom)
             
              item := buffer[clusterKey]
              if item == nil {
                      item = &clusterPoint{}
                      ……
                      item.num, _ = strconv.Atoi(dic["num"])
                      item.clusterKey = clusterKey
                      item.lng, _ := strconv.ParseFloat(dic["lng"], 64)
                      item.lat, _ := strconv.ParseFloat(dic["lat"], 64)
                      ……
                      buffer[clusterKey] = item
              }
              // 数量加起来,经纬度要根据数量比例算
              item.lng = (item.lng * float64(item.num) + lng) / float64(item.num + 1)
              item.lat = (item.lat * float64(item.num) + lat) / float64(item.num + 1)
              item.num++
          }
    }
    // 聚合点存入 geoRes
    for key, value := range buffer {
            // 根据 clusterKey 算出一个格子的标准点,这个点只是参考位置
            centerLng, centerLat := encodeClusterKey(value.clusterKey)
            // 根据自己的需要弄个新的 key 出来,作为 redis geo 的 member,把 clusterKey 放进去
            tempKey := ……
            // 分层级往 geoRes 存聚合点位
            _, err := geoRes.Do("GEOADD", fmt.Sprintf("cluster%d", currentZoom), centerLng, centerLat, tempKey)
            // 往 infoRes 存点位具体信息
            _, err = infoRes.Do("HMSET", tempKey, ……)
    }
    
    获取点位
    1. 前端传来地图层级、视图中心点的经纬度、视图范围
    2. 拿到聚合点后不能直接给前端,要优化点的位置,避免点位重叠
    tempArr, _ := redis.Strings(geoRes.Do("GEORADIUS", fmt.Sprintf("cluster%d", currentZoom), lng, lat, distance, "m"))
    for i:= 0; i < len(tempArr); i++ {
             info, _ := redis.Strings(infoRes.Do("HGETALL", tempArr[i]))
              dic := make(map[string]string, len(info)/2)
              for i := 0; i < len(info); i = i + 2 {
                   dic[info[i]] = info[i+2]
              }
             item = &clusterPoint{}
             ……
             item.num, _ := strconv.Atoi(dic["num"])
             item.lng, _ := strconv.ParseFloat(dic["lng"], 64)
             item.lat, _ := strconv.ParseFloat(dic["lat"], 64)
             ……
    }
    

    web

    前端不能每次都靠后台传值刷新,毕竟点太多了,每次都全量刷新肯定需要一定时间加载,用户体验也很不好。所以前端需要缓存一份点位数据,每次获得新数据时先比较缓存,比较相同的聚合点,如果其中数量没变就不更新,数量变了就删掉重新渲染。

    大概就是这样的思路,原创文章,转载请注明出处。

    相关文章

      网友评论

        本文标题:「笔记」结合redis的地图点位聚合方案

        本文链接:https://www.haomeiwen.com/subject/nwwealtx.html