顯示具有 map 標籤的文章。 顯示所有文章
顯示具有 map 標籤的文章。 顯示所有文章

2022年5月16日 星期一

Go 開發筆記 - 地圖服務 查詢剩餘快篩劑販售地點 為例




之前一直想練習 Golang 卻一直偷懶,這次就找個題目練了一下。包括如何取得網路上的資料,以及處理 csv 的流程。然而,最後想找 Heroku 來發布,就...全部改成 HTML+JS 處理而已,但還是留點紀錄吧。

若單純想要使用 政府資料開放平臺 - 健保特約機構防疫家用快篩剩餘數量明細 來作為範例時,其實可以只有一個 HTML 檔案就搞定,因為政府資料開放平臺下載 CSV 的那隻,支援大家隨意呼叫?我是用 Golang 寫完 backend api 後,為了發布到 Heroku 備忘時,就乾脆多寫個直接呼叫的版本

// https://data.gov.tw/dataset/152408
% curl -I https://data.nhi.gov.tw/resource/Nhi_Fst/Fstdata.csv
HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: *
...

而 Golang 的部分,單純使用 gin framework 而已,主程式極短,單純吐 HTML 出去。

最後 templates/index.tmpl 就是 HTML/CSS/JS 的領域而已,分別使用了

- bootstrap@5.1.3
- leaflet@1.8.0
- leaflet.markercluster@1.5.3
- jquery@3.6.0
- jquery-csv@1.0.21

就簡單的桌面版型交給 Bootstrap (對的,沒有mobile版型),地圖則是使用 OpenStreetMap 服務,搭配 leaflet js sdk 管理,最後再用個 jQuery 處理 api 詢問,以及 jquery-csv 處理 CSV 格式。

原本用 Golang 開發 api 時,做了一些 api cache 管理跟 csv parsing 等,因發布到 Heroku 時,簡化成純前端任務就沒在使用了。不然在 Golang 還有規劃避免太頻繁詢問 data.nhi.gov.tw 要資料的架構。

2014年9月12日 星期五

[Python] 使用 Apache Web Server Access.log 把玩 CartoDB 視覺化地圖 @ Ubuntu 12.04



只要資料有 Geolocation,就能夠把玩 CartoDB 了 :P 若可以的話,再加上時間就更完美了。因此,最容易拿到的測資是 Apache web server log,把 access.log 挑點東西出來即可,至於 Geolocation 就用 ip 反查吧!

從 access.log 取出 ip list:

$ grep -v "^localhost\|::1" /var/log/apache2/access.log | awk '{print $1}' | uniq

首先,先到 Maxmind 下載最新的 GeoLite2-City 資訊:

$ wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz
$ gunzip GeoLite2-City.mmdb.gz


安裝 geoip-bin 工具:

$ sudo apt-get install geoip-bin
$ geoiplookup 8.8.8.8
GeoIP Country Edition: US, United States

$ geoiplookup -f GeoLite2-City.mmdb 8.8.8.8
Error Traversing Database for ipnum = 134744072 - Perhaps database is corrupt?
Segmentation fault (core dumped)


囧...只好裝一下新版 maxmind python sdk 寫一段小 code:

$ sudo pip install geoip2

$ vim t.py
mport sys
import geoip2.database
reader = geoip2.database.Reader('GeoLite2-City.mmdb')
try:
        response = reader.city(sys.argv[1])
        print str(response.location.latitude)+","+str(response.location.longitude)
except Exception, e:
        pass
$ python t.py 8.8.8.8
37.386,-122.0838


接著,就乾脆寫 python 來處理 access.log 吧 XDD 用 command line 好像太冗長了。

$ sudo cp /var/log/apache2/access.log /tmp/access.log
$ sudo chmod 644 /tmp/access.log
$ vim log.py
import geoip2.database
reader = geoip2.database.Reader('GeoLite2-City.mmdb')
try:
        log = open('/tmp/access.log','rb').read()
        for rec in log.split('\n'):
                fields = rec.split(' ')
                try:
                        if fields[0] == 'localhost' or fields[0] == '::1' :
                                continue
                        response = reader.city(fields[0])
                        print fields[3][1:]+","+str(response.location.latitude)+","+str(response.location.longitude)
                except Exception, e:
                        pass
except Exception, e:
        pass

$ python log.py > log.csv
$ cat log.csv
...
07/Sep/2014:22:12:43,35.685,139.7514
07/Sep/2014:22:13:53,39.4899,-74.4773
...


對於時間格式不用擔心,直接丟進 cartodb.com 請他幫你處理!




匯入後,預設都是 string,可以把 field1 設成 date type,field2 跟 field3 都設成 number type,弄完順便 rename 一下,接著再點選 geo 欄位,可以採用 field2 跟 field3 來生成,如此就完成 CartoDB  table 製作。








最後再去視覺化那邊,挑一下以 date 為基準的時間變化,就可以有不錯的視覺圖表。

2014年2月5日 星期三

[MongoDB] 透過 MapReduce 進行 Collections Join @ Ubuntu 12.04

使用 NOSQL 時,其先天設計無法提供像 MySQL Table Joins 等招數,此時的解法就是把兩個 tables 倒進一張 table 來解決。

以 MongoDB command line 操作,新增資料:

> use mydb
switched to db mydb
> db.t1.insert({"data":1, "data2":2, "data3":3})
> db.t1.insert({"data":4, "data2":5, "data3":6})
> db.t1.insert({"data":7, "data2":8, "data3":9})
> db.t1.find()
{ "_id" : ObjectId("52f1f9c7bf6317bb2615b216"), "data" : 1, "data2" : 2, "data3" : 3 }
{ "_id" : ObjectId("52f1f9cbbf6317bb2615b217"), "data" : 4, "data2" : 5, "data3" : 6 }
{ "_id" : ObjectId("52f1f9cebf6317bb2615b218"), "data" : 7, "data2" : 8, "data3" : 9 }
> db.t2.insert({"data4":3, "data5":2, "data6":1})
> db.t2.insert({"data4":6, "data5":5, "data6":4})
> db.t2.insert({"data4":9, "data5":8, "data6":7})
> db.t2.find()
{ "_id" : ObjectId("52f1f9d4bf6317bb2615b219"), "data4" : 3, "data5" : 2, "data6" : 1 }
{ "_id" : ObjectId("52f1f9d7bf6317bb2615b21a"), "data4" : 6, "data5" : 5, "data6" : 4 }
{ "_id" : ObjectId("52f1f9dbbf6317bb2615b21b"), "data4" : 9, "data5" : 8, "data6" : 7 }


定義 t1 跟 t2 的 mapper function:

> t1_map = function() {
emit(this.data3, { "data": this.data, "data2": this.data2, "data3": this.data3 });
}
> t2_map = function() {
emit(this.data4, { "data4": this.data4, "data5": this.data5, "data6": this.data6 });
}


定義 reducer function:

> reducer = function(key, values){
var result = {} ;
values.forEach( function(value){
var field;
for(field in value)
if( value.hasOwnProperty(field) )
result[field] = value[field];
});
return result;
}


進行 map-reduce:

> db.tmp.drop()
true
> db.t1.mapReduce(t1_map, reducer , {"out":{"reduce":"tmp"}} )
{
        "result" : "tmp",
        "timeMillis" : 10,
        "counts" : {
                "input" : 3,
                "emit" : 3,
                "reduce" : 0,
                "output" : 3
        },
        "ok" : 1,
}
> db.tmp.find()
{ "_id" : 3, "value" : { "data" : 1, "data2" : 2, "data3" : 3 } }
{ "_id" : 6, "value" : { "data" : 4, "data2" : 5, "data3" : 6 } }
{ "_id" : 9, "value" : { "data" : 7, "data2" : 8, "data3" : 9 } }
> db.t2.mapReduce(t2_map, reducer , {"out":{"reduce":"tmp"}} )
{
        "result" : "tmp",
        "timeMillis" : 6,
        "counts" : {
                "input" : 3,
                "emit" : 3,
                "reduce" : 0,
                "output" : 3
        },
        "ok" : 1,
}
> db.tmp.find()
{ "_id" : 3, "value" : { "data4" : 3, "data5" : 2, "data6" : 1, "data" : 1, "data2" : 2, "data3" : 3 } }
{ "_id" : 6, "value" : { "data4" : 6, "data5" : 5, "data6" : 4, "data" : 4, "data2" : 5, "data3" : 6 } }
{ "_id" : 9, "value" : { "data4" : 9, "data5" : 8, "data6" : 7, "data" : 7, "data2" : 8, "data3" : 9 } }


以上的概念就是 t1 採用 data3 作為 join key,而 t2 採用 data4 ,分別跑 map-reduce 後儲存在 tmp 這張表(collection),如此 tmp 即為常用的 SQL Join 結果。

以上須留意的是跑 map-reduce 時,要指定 out 為 reduce 形態,細節可以在 out Options 查看,可分成 replace, reduce, merge ,若沒指定為 reduce 的話,上述最後結果僅有 t2 的資料。

此外,在 pymongo 上也能達成,有興趣可以參考 map-reduce-join.py,寫的彈性一點,所以閱讀性較差,用法:

$ python map-reduce-join.py --host localhost --database mydb --reset-result --result tmp --show-result --select-out-1 data data2 data3 --join-key-1 data3 --select-out-2 data4 data5 data6 --join-key-2 data4

結果:

Mapper 1 Code:
                function() {
                        emit(this.data3, {"data": this.data, "data2": this.data2, "data3": this.data3});
                }

Reducer 1 Code:
                function(key, values) {
                        var out = {};
                        values.forEach( function(value) {
                                for ( field in value )
                                        if( value.hasOwnProperty(field) )
                                                out[field] = value[field];

                        });
                        return out
                }

{u'counts': {u'input': 3, u'reduce': 0, u'emit': 3, u'output': 3}, u'timeMillis': 34, u'ok': 1.0, u'result': u'tmp'}
{u'_id': 3.0, u'value': {u'data': 1.0, u'data3': 3.0, u'data2': 2.0}}
{u'_id': 6.0, u'value': {u'data': 4.0, u'data3': 6.0, u'data2': 5.0}}
{u'_id': 9.0, u'value': {u'data': 7.0, u'data3': 9.0, u'data2': 8.0}}
Mapper 2 Code:
                function() {
                        emit(this.data4, {"data4": this.data4, "data5": this.data5, "data6": this.data6});
                }

Reducer 2 Code:
                function(key, values) {
                        var out = {};
                        values.forEach( function(value) {
                                for ( field in value )
                                        if( value.hasOwnProperty(field) )
                                                out[field] = value[field];
                        });
                        return out
                }

{u'counts': {u'input': 3, u'reduce': 0, u'emit': 3, u'output': 3}, u'timeMillis': 5, u'ok': 1.0, u'result': u'tmp'}
{u'_id': 3.0, u'value': {u'data5': 2.0, u'data4': 3.0, u'data6': 1.0, u'data': 1.0, u'data3': 3.0, u'data2': 2.0}}
{u'_id': 6.0, u'value': {u'data5': 5.0, u'data4': 6.0, u'data6': 4.0, u'data': 4.0, u'data3': 6.0, u'data2': 5.0}}
{u'_id': 9.0, u'value': {u'data5': 8.0, u'data4': 9.0, u'data6': 7.0, u'data': 7.0, u'data3': 9.0, u'data2': 8.0}}