Odoo 中實現(xiàn)(LBS)基于地理位置的服務

Odoo 在 OCA 有一個 geospatial 項目,這個 addon 存在很多年了,但一直沒有進入 Odoo 官方的法眼蕴轨,始終在 OCA 游蕩,但是非澈Э裕活躍橙弱,第一時間跟進 Odoo 的版本進化。

隨著 Odoo 官方不顧后勁同學的死活每年更新一個大版本燥狰,OCA 的 addon 很多已經(jīng)放棄了更新棘脐,消沉于在茫茫代碼的海洋。這也是最近幾年我們常沉拢看到開源軟件的一種新模式蛀缝,通過商業(yè)公司的強力支持,不斷拋棄那些附著在開源軟件上的‘吸血者’净当。曾經(jīng) OCA 有成百上千大的 addon内斯,現(xiàn)在大多都不靈了蕴潦。比如 Google 的 Android 非常明顯像啼,不把追隨者累吐血而亡是絕對不能罷休的。

但是 OCA/geospatial 沒有淪落潭苞,它挺住了忽冻。

LBS 的簡單需求

一個 簡單的 LBS 服務需求,按照與用戶距離排序找一些飯店給客戶做選擇此疹,這樣就需要拿到用戶的地理位置僧诚,與數(shù)據(jù)庫中的已經(jīng)存在的飯店信息進行距離計算并且排序。

這個需求看上去如此簡單蝗碎,也如此常見湖笨,美團、點評之類的軟件都‘輕松’實現(xiàn)了蹦骑。但是實際上這個需求在一個普通數(shù)據(jù)庫里面是無法實現(xiàn)的慈省。

因為,客戶的位置是變的眠菇,每個客戶的請求都是不同的客戶位置边败,也就是數(shù)據(jù)庫的數(shù)據(jù)排序不是完全根據(jù)數(shù)據(jù)庫的存儲信息,而是要根據(jù)不同的客戶的位置捎废。

另外距離的計算笑窜,不是歐式距離那么簡單,如果你曾經(jīng)在‘石器時代’了解 GIS 系統(tǒng)登疗,那么你多少了解 GIS 中通過坐標計算距離的方法不是算個歐式距離那么簡單排截。參見
http://www.reibang.com/p/9ed3b4dcd32a

實現(xiàn)方法

PostgreSQL 通過 PostGIS 擴展支持 GIS,Odoo 通過 geospatial 支持 PostGIS,所以要先安裝 PostGIS匾寝,不同平臺安裝方法不一樣搬葬。我嘗試使用 Homebrew 在 Mac上安裝失敗,放棄了艳悔。直接在 Debian Linux 通過 apt 按裝 PostGIS 沒有問題急凰。

在 Github 上 clone OCA/geospatial 的代碼,geospatial 實際上包含了幾個 addons猜年,其中 base_geoengine 就夠滿足我們的需求了抡锈。

Odoo addon 會有一些 hook,可以在 addon 安裝之前之后或者完成加載之后運行乔外,base_geoengine 就利用了這個機制床三,為數(shù)據(jù)庫動態(tài)加載了 PostGIS 的擴展。

Model 定義

# 飯店位置
geo_point = fields.GeoPoint("地址位置", srid=4326)

# 當前用戶的距離
session_distance = fields.Float("距離", digits=(16,1), compute='_compute_session_distance')

其中的 srid 是所選取的空間參考系統(tǒng)ID杨幼。(A Spatial Reference System Identifier(SRID) is a unique value used to unambiguously identify projected, unprojected, and local spatial coordinate system definitions. These coordinate systems form the heart of all GIS applications.)

其中 GeoPoint 就是 geoengine base 提供的撇簿。

距離計算

    @api.depends()
    def _compute_session_distance(self):
        _longtitude = self._context.get("longtitude")
        _latitude = self._context.get("latitude")
        if not _longtitude or not _latitude:
            for model in self:
                model.session_distance = 0
            return
        
        cr = self.env.cr
        for model in self:
            if not model.geo_point:
                model.session_distance = 0
                continue
            sql = "select ST_X(geo_point) as lon, ST_Y(geo_point) as lat from %s where id=%s" % (self._table, model.id)
            cr.execute(sql)
            r = cr.fetchone()
            X = r[0]
            Y = r[1]
            sql = "SELECT ST_Distance(ST_GeomFromText('POINT(%s %s)', 4326)::geography, ST_GeomFromText('POINT(%s %s)', 4326)::geography) from %s" % (X, Y, _longtitude, _latitude, self._table)
            cr.execute(sql)
            model.session_distance = round(cr.fetchone()[0], 1)

session distance 這個 field 是通過計算而來(不是存儲而來),每次客戶請求這個數(shù)據(jù)的時候都根據(jù)當前的context 中客戶的經(jīng)緯度來計算差购。

按照距離排序

    @api.model
    def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):

        _longtitude = self._context.get("longtitude")
        _latitude = self._context.get("latitude")
        _logger.info("context ... %s" % self._context)

        # if order not default return default search
        if not _longtitude or not _latitude or order:
            return super(self.__class__, self)._search(args, offset, limit, order, count, access_rights_uid)
        
        if _longtitude and _latitude and not order:
            query = self._where_calc(args)
            order_by = "ORDER BY distance asc"
            from_clause, where_clause, where_clause_params = query.get_sql()
            where_str = where_clause and (" WHERE %s" % where_clause) or ''
            limit_str = limit and ' limit %d' % limit or ''
            offset_str = offset and ' offset %d' % offset or ''
            distance_str = "ST_distance(geo_point::geography, ST_GeomFromText('POINT(%s %s)', 4326)::geography) as distance" % (self._context.get("longtitude"), self._context.get("latitude"))
            query_str = 'SELECT %s, id FROM %s %s' % (distance_str, self._table, where_str + order_by + limit_str + offset_str)

            self.env.cr.execute(query_str, where_clause_params)
            res = self.env.cr.fetchall()

            # TDE note: with auto_join, we could have several lines about the same result
            # i.e. a lead with several unread messages; we uniquify the result using
            # a fast way to do it while preserving order (http://www.peterbe.com/plog/uniqifiers-benchmark)
            def _uniquify_list(seq):
                seen = set()
                return [x for x in seq if x not in seen and not seen.add(x)]

            return _uniquify_list([x[1] for x in res])

        return super(self.__class__, self)._search(args, offset, limit, order, count, access_rights_uid)

搜索排序 overload 了 _search 函數(shù)四瘫。 其中使用了 PostGIS 的函數(shù)計算距離,并且按照距離排序欲逃。

Model 初始數(shù)據(jù)

    <record id="location_thing_id_1" model="location.some_model">
    <field name="geo_longtitude">116.601144</field>
    <field name="geo_latitude">39.948574</field>
      ...
    <field name="geo_point">POINT(116.601144 39.948574)</field>
    </record>

如果在定義 geo point 的時候沒有指定 srid找蜜,那么這個 POINT 值也無法確定意義。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末稳析,一起剝皮案震驚了整個濱河市洗做,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彰居,老刑警劉巖诚纸,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異陈惰,居然都是意外死亡畦徘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門奴潘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旧烧,“玉大人,你說我怎么就攤上這事画髓【蚣簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵奈虾,是天一觀的道長夺谁。 經(jīng)常有香客問我廉赔,道長,這世上最難降的妖魔是什么匾鸥? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任蜡塌,我火速辦了婚禮,結果婚禮上勿负,老公的妹妹穿的比我還像新娘馏艾。我一直安慰自己,他們只是感情好奴愉,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布琅摩。 她就那樣靜靜地躺著,像睡著了一般锭硼。 火紅的嫁衣襯著肌膚如雪房资。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天檀头,我揣著相機與錄音轰异,去河邊找鬼。 笑死暑始,一個胖子當著我的面吹牛搭独,可吹牛的內容都是我干的。 我是一名探鬼主播蒋荚,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼戳稽,長吁一口氣:“原來是場噩夢啊……” “哼馆蠕!你這毒婦竟也來了期升?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤互躬,失蹤者是張志新(化名)和其女友劉穎播赁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年州弟,在試婚紗的時候發(fā)現(xiàn)自己被綠了甚负。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡音婶,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情得滤,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布盒犹,位于F島的核電站懂更,受9級特大地震影響眨业,放射性物質發(fā)生泄漏。R本人自食惡果不足惜沮协,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一龄捡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慷暂,春花似錦聘殖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蘑辑,卻和暖如春洋机,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洋魂。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工绷旗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人副砍。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓衔肢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親豁翎。 傳聞我的和親對象是個殘疾皇子角骤,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容