像之前說的的例子,都是在HBase clinet 端作處理。
而我們使用HBase,當然希望是使用他整體Cluster 的運算能力來幫我們運算,所以我們想把運算的作業交由HBase的Region Server 幫我們作處理。


HBase 提供了兩種方式讓我們可以把運算功能實作在 Region Server
1) filter
2) coprocessor

我們來看一下要如何處理,首先我們先改變一下我們的問題,我們不再找離自己最近的wifi,問題改成:
找尋某個多邊型內的有哪些wifi

比如說我們想確認國父紀念館裡有多少wifi,這個還算簡單,因為國父紀念館是一個方形的範圍,但有可能我們是希望讓使用者手繪多邊形,所以希望是不規則的多邊形也可以處理。

undefined

之前的例子,我們是透過目標(所在的位置)為中心,掃描周圍geohash九宮格的資料。所以「中心點」很重要,而現在這個問題:

找尋某個多邊型內的有哪些wifi

我們就要了解多邊形的中心點(形心centroid),有一個Java 的library:JTS,可以幫忙建立多邊形及取得centroid ( Geometry.getCentroid() )

只要我們定義好polygon的點,JTS可以接收WKT(Well-known text),透過 getCentroid 方法就可以得到形心

POLYGON( -83.9 40,75,
      -82.5,40.6,
      -83.3,40,5,
      -83.9 40,75    
)
因為polygon是連起來的,所以最後一個點會和之前一個點相同

下面是找形心的示意碼
String wkt = POLYGON(...)
WKTReader reader = new WKTReader()
Geometry query = reader.read(wkt)
Poin queryCenter = query.getCentroid

想要找的多邊形範圍和形心都有了,現在的重點是,我們到底要掃描多大的geohash空間,才能把這個多邊形都掃描到?
方式其實很簡單,透過JTS的function,我們可以用contain 來判斷目前geohash 的convex hull(凸包,可以先假設成一個多邊形)是否包含我們想要的多邊形

程式碼邏輯大致如下
1.我們先把形心的座標轉成geohash,先判斷形心的geohash是否包含特定的polygon
2.如果上一步已經確定有包含,則結束,若無則抓出形心附近的九宮格 hashcode,再判斷一次是否有包含特定polygon
3.如果都還沒有包含,則調整 geohash的精確度,讓精確度變低(ex:原本7碼改成6碼)
4.若第3步仍沒有match,則重覆上述steps

程式碼大致如下
//取得某個GeoHash 的四個角作為建構多邊形的頂點
Set<Coordinate> getCoords(GeoHash hash)
{
    BoundingBox bbox = hash.getBoundingBox()
    Set<Coordinate> coords = new HashSet<Coordinate>(4);
    coords.add( new Coordinate(bbox.getMinLon , bbox.getMinLat))
    coords.add( new Coordinate(bbox.getMinLon , bbox.getMaxLat))
    coords.add( new Coordinate(bbox.getMaxLon , bbox.getMinLat))
    coords.add( new Coordinate(bbox.getMaxLon , bbox.getMaxLat))
    return coords;
}

//  依照Hashcode取得convex hull
public Geometry convexHumm( GeoHash[] hashes)
{
    Set<Coordinate> coords = new HashSet<Coordinate>()
    for ( GeoHash hash:hashes)
    {
        coords.addAll(getCoords(hash))
    }
    
    GeometryFactory factory = new GeometryFactory();
    Geometry geom = factory.createMultiPoint( coords.toArray(new Coordinate[0]))
    return  geom.convexHull()
}

//依傳入的polyoon,找尋含有 wifi的 geohash值
public GeoHash[] minimumBoundingPrefixes(Geometry query)
{
    GeoHash candidate
    Geometry candidateGeom
    // 取得目邊多邊形的形心
    Point queryCenter = query.getCentroid()
    
    // 形心從精確度7開始
    for( int precision = 7 ; precision  > 0 ; precision --)
    {
        candidate = GeoHash.withCharacterPrecision( queryCenter.getY , queryCenter.getX  precision)
        candidateGeom = convexHull( new GeoHash[](candidte) )
        
        // 如果形心已經有包含要查詢的多邊形,則直接回傳
        if( candidateGeom.contain(query))
        {
            return new GeoHash[]{candidate}
        }
        
        // 擴大範圍,把形心週遭的9宮格代入
        candidateGeom = convexHull( candidate.getAdjacent)
        if ( candidateGeom.contains( query))
        {
            GeoHash[] ret = Arrays.copyOf( candidate.getAajacent, 9)
            ret[8] = candidate; // 中心的hash也要加入
            return ret;
        }
        
    }
    
    // 如果都沒找到則丟出exception
    throw new IllegalArgumentException("can't be ound")
}

有了上述的邏輯後,我們先不要直接放在HBase Server端執行,先在HBase client端測試一下

// 先取得包含查詢多邊形範圍的GeoHash值
GeoHash[] prefixes = minimmBoundingPrefixes(query)
Set<QueryMatch> ret = new HashSet<QueryMatch>();
HTableInterface table = pool.getTable("wifi");
for(GeoHash prefix : prefixes)
{
    //以geohash 值查找rowkey 
}

table.close();
for( Iterator<QueryMatch> iter = ret.iterator() ; iter.hasNext)
{
    QueryMatch candidate = iter.next();
    Coordinate coord = new Coordindate( candidate.lon  ,candidate.lat)
    Geometry point = factory.createPoint(coord)
    //因為經緯度轉成geoHash後,geohash是較粗略的值
    //有可能某兩個點的geohash一樣,但其中一點並不落在要查找的多邊形內,故需排除
    if (!query.contains(point))
        iter.remove();
}

下一章我們再來看怎麼掛到HBase RegionServer來處理

arrow
arrow
    全站熱搜

    avseq 發表在 痞客邦 留言(0) 人氣()