2026-03-22

【Metabase】套用台灣地圖進行分析(二) -- 區、里(村)級行政區

參考資料 ----
github.com/g0v/twgeojson


github.com/g0v/twgeojson/json,有數個 json 檔,其中 twTown1982.geo.json區級行政區,twVillage1982.geo.json 則是 里(村)級

另有 twTown1982.topo.json 及 twVillage1982.topo.json,是 geo.json 的後繼優化格式,簡略記一下主要的差異:就是相鄰的 2 個區中間的界線不重複記錄。
例如:A 區與 B 區相鄰,geo.json 是完整地記錄 A 區的座標點 [1,2,3,4,5] 及 B 區的座標點 [1,6,7,8,9,2],如此 點 1點 2 就被記錄了 2 次,而 topo.json 則透過拓撲方法只記錄 1 次 點1到點2 的弧線,相較之下,topo.json 的檔案就比較小。





Metabase 目前只支援 geojson 格式,所以我們就下載這 2 個檔,以 twTown1982.geo.json 為例
點擊 twTown1982.geo.json
→ 點擊【下載】圖示下載 json

→ 將 twTown1982.geo.json 放置在 /var/www/html/ 下,然後在 Metabase 設定 twTown1982.geo.json 的 url,在本例為 http://localhost/twTown1982.geo.json

瞄一下檔案內容
 
{"type": "FeatureCollection",
 "features": [

 ...
 ...
 
    {"properties":{"TOWNSN":"64000002","TOWNID":"6400003","COUNTYNAME":"高雄市","TOWNNAME":"左營區","name":"高雄市/左營區"},
				   "type":"Feature",
				   "geometry":{"type": "Polygon", 
							   "coordinates": [[[ 120.274766995608132, 22.706593189102705 ], 
                                   ...
                                   ...
                               ]] 
							}
				},
    ...
    ...
 

登入 Metabase,點擊【齒輪】圖示
→ 管理員設定

→ 地圖 → 新增一個地圖


→【URL】輸入 http://localhost/twTown1982.geo.json
→【區域識別符】選擇 TOWNID
→【區域顯示名稱】選擇 TOWNNAME


g0v/twgeojson 的是 2014 年的資料,理論上這幾年並沒有重新劃分區級行政區,所以資料應該仍可信、準確。


g0v/twgeojson/json/twVillage1982.geo.json 則更細, 行政區範圍小至 "村/里",村/里的劃分就可能不定期會變動了,例如老人家居住的里聽說最慢 2027 年就將要拆成 4 個里。


建立區級資料表存放 geojson 的資料,在本筆記,資料表命名為 town_geo
 
CREATE TABLE `town_geo` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `TOWNSN` VARCHAR(20),
    `TOWNID` VARCHAR(20),
    `COUNTYNAME` VARCHAR(50),
    `TOWNNAME` VARCHAR(50),
    `name` VARCHAR(100),
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
 


寫一個 php 讀取 twTown1982.geo.json 並存入 town_geo
 
<?php
ini_set('display_errors', 1);
ini_set('memory_limit', '512M');    // 放寬記憶體限制, 才能讀大檔案

// 資料庫連線設定
$host = 'localhost';
$dbname = '資料庫名稱';
$username = '帳號';
$password = '密碼';

try {
    // 1. 建立 PDO 連線
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // 2. 讀取並解析 GeoJSON 檔案
    $jsonFile = 'twTown1982.geo.json';
    if (!file_exists($jsonFile)) {
        die("錯誤:找不到檔案 $jsonFile");
    }

    $jsonContent = file_get_contents($jsonFile);
    $data = json_decode($jsonContent, true);

    if ($data === null) {
        die("錯誤:JSON 解析失敗");
    }

    // 3. 準備 SQL 插入語句
    $sql = "INSERT INTO town_geo (TOWNSN, TOWNID, COUNTYNAME, TOWNNAME, name) 
            VALUES (:townsn, :townid, :countyname, :townname, :name)";
    $stmt = $pdo->prepare($sql);

    // 4. 開始交易 (Transaction) 以提升大量寫入的速度
    $pdo->beginTransaction();

    $count = 0;
    foreach ($data['features'] as $feature) {
        $props = $feature['properties'];

        // 綁定參數並執行
        $stmt->execute([
            ':townsn'     => $props['TOWNSN'] ?? null,
            ':townid'     => $props['TOWNID'] ?? null,
            ':countyname' => $props['COUNTYNAME'] ?? null,
            ':townname'   => $props['TOWNNAME'] ?? null,
            ':name'       => $props['name'] ?? null
        ]);
        
        $count++;
    }

    // 5. 提交交易
    $pdo->commit();

    echo "成功!共匯入 $count 筆資料。";

} catch (PDOException $e) {
    // 發生錯誤時回滾
    if (isset($pdo) && $pdo->inTransaction()) {
        $pdo->rollBack();
    }
    echo "資料庫錯誤:" . $e->getMessage();
} catch (Exception $e) {
    echo "一般錯誤:" . $e->getMessage();
}
?>
 

如此就能結合自己的資料表,建立區級行政區的圖表了。




再建立里(村)級資料表,在本例為 village_geo
 
CREATE TABLE `village_geo` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `VILLAGESN` VARCHAR(20),
    `VILLAGEID` VARCHAR(20),
    `COUNTYNAME` VARCHAR(50),
    `TOWNNAME` VARCHAR(50),
    `VILLAGENAM` VARCHAR(50),
    `name` VARCHAR(150),
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
 


寫一個 php 讀取 twVillage1982.geo.json 並存入 village_geo
 
<?php
// 1. 提高記憶體限制(針對較大的村里 GeoJSON 檔案)
ini_set('memory_limit', '512M');
set_time_limit(0); // 取消執行時間限制

// 資料庫連線設定
$host = 'localhost';
$dbname = '資料庫名稱';
$username = '帳號';
$password = '密碼';

try {
    // 2. 建立 PDO 連線
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // 3. 讀取 GeoJSON 檔案
    $jsonFile = 'twVillage1982.geo.json';
    if (!file_exists($jsonFile)) {
        die("錯誤:找不到檔案 $jsonFile");
    }

    echo "正在讀取檔案...\n";
    $jsonContent = file_get_contents($jsonFile);
    
    echo "正在解析 JSON...\n";
    $data = json_decode($jsonContent, true);
    
    // 解析完立刻釋放字串記憶體
    unset($jsonContent); 

    if ($data === null) {
        die("錯誤:JSON 解析失敗,請檢查檔案格式。");
    }

    // 4. 準備 SQL 插入語句
    $sql = "INSERT INTO village_geo (VILLAGESN, VILLAGEID, COUNTYNAME, TOWNNAME, VILLAGENAM, name) 
            VALUES (:v_sn, :v_id, :c_name, :t_name, :v_name, :full_name)";
    $stmt = $pdo->prepare($sql);

    // 5. 開始交易處理 (大量資料寫入必備)
    $pdo->beginTransaction();

    echo "正在匯入資料...\n";
    $count = 0;
    foreach ($data['features'] as $feature) {
        $props = $feature['properties'];

        $stmt->execute([
            ':v_sn'      => $props['VILLAGESN'] ?? null,
            ':v_id'      => $props['VILLAGEID'] ?? null,
            ':c_name'    => $props['COUNTYNAME'] ?? null,
            ':t_name'    => $props['TOWNNAME'] ?? null,
            ':v_name'    => $props['VILLAGENAM'] ?? null,
            ':full_name' => $props['name'] ?? null
        ]);
        
        $count++;
    }

    // 6. 提交交易
    $pdo->commit();
    echo "匯入完成!成功存入 $count 筆村里資料。";

} catch (PDOException $e) {
    if (isset($pdo) && $pdo->inTransaction()) {
        $pdo->rollBack();
    }
    echo "資料庫錯誤:" . $e->getMessage();
} catch (Exception $e) {
    echo "一般錯誤:" . $e->getMessage();
}
?>
 
匯入的記錄如下圖


如果您需要的圖資精細顆粒度要小到村/里國土測繪中心政府資料開放平臺(opendata) 也有提供圖資,感覺有提續在更新,可能會較適合您,但國土測繪中心沒有直接提供 geo.json 格式,我們需要轉換程式幫忙。

到 村里界圖(TWD97經緯度),點擊【資料資源下載網址】,下載的是壓縮檔,找個目錄解壓縮存放,會看到解壓出來的檔案中有副檔名 .shp 的檔案,這是主檔。



到 QGIS 下載格式轉換程式並安裝,藉助程式將 shp 轉換為 geo.json 格式。


選擇 shp 檔,在本例為 VILLAGE_NLSC_1150306.shp



功能表 【Layer】→【Save As】→【Format】欄位選擇 GeoJSON → 輸入檔名,在本例為 town_twd97 → 按下【確定】鈕


開啟檔案,會發現 properties 內的欄位跟上面 g0v 的不同,由此可知,properties 的欄位是各自定義的;所以,若您圖資是選擇國土測繪中心的,需注意匯入資料的欄位也會不同。



相關筆記 ----


2026-03-16

【Metabase】限制/區隔一般 user 的查詢權限

有時,基於工作/職務劃分,公司的資料會進行可視資料權限的控管;例如:行銷部的可以看到有關客戶名單,但不能看業務部有關銷售的業績、人事資料...等,反之亦然。

Metabase 社群版沒有限制使用者可視資料的功能(付費版才有),也就是說,當系統管理員建立了一個資料庫連線,則所有 Metabase 的一般使用者都能看到該連線的所有 table。

不過我們有變通做法 -- 從資料庫分別設定幾個限制權限的帳號,本筆記以 MySQL8 做範例,例如建立帳號 user1(代表業務部),只授予 user1 能查詢檢視 table1,建立帳號 user2(代表行銷部),只能查詢檢視 table2


root 帳號登入 phpMyAdmin 或 登入 Linux 開一個命令列視窗然後建立一個 MySQL 連線,本例示範在 phpMyAdmin 操作
 
-- 建立使用者
CREATE  USER  'user1'@'localhost' IDENTIFIED BY '密碼';

-- 授予 USER1 查詢資料庫 db1 的 table1 的權限
GRANT  SELECT  ON  db1.table1  TO  'user1'@'localhost';

-- 刷新權限(選擇性命令, 通常會自動生效)
FLUSH  PRIVILEGES;
 


驗證:登出 phpMyAdmin,以 user1 登入,就能看到 user1 只能看到 db1table1 的資料。


以管理員帳號登入 Metabase  設定 → 管理員設定

資料庫 → 新增資料庫(以 MySQL8 為例)


輸入在前面為行銷建立的帳號 user1,並在【顯示名稱】輸入 sales(Linuxmint22.2MySQL8 設定安裝可參考相關筆記),重複步驟建立 user2,【顯示名稱】輸入 marketing


到【權限管理】→【所有使用者群組】,這個群組預設是可查詢所有資料庫的,而且會高過其他群組的設定,所以要把 marketing、sales 的【建立提問】取消


設定業務部只能查詢 sales 資料庫


而行銷部只能查詢 marketing 資料庫



相關筆記 ----





2026-01-26

【MySQL8】匯入大容量 .sql 檔

phpMyAdmin 只能匯入容量 2M 以內的 .sql 檔,若要匯入大容量 .sql,則需要以命令列操作
 
# 切換到放置 .sql 檔的目錄下,
# 本例中, aa.sql 置於桌面
user$  cd  桌面

# 進入 su 模式
user$  sudo  su

# 匯入檔案
root#  mysql  -u  root  -p  資料庫名 < aa.sql
 


2026-01-25

【Metabase】設定委託 Gmail 定時發送通知信件

如果您設定的儀表板的數據是每日更新的,或是希望在您關心的某個數據發生較大的變化時(例如某日營業額突然暴量)通知您,就需要有台 mail server 寄送通知信件。

若您也沒有自己架設的 mail server,則可以申請 Gmail,設定 Metabase 委託 Gmail 寄送通知信件;Gmail 這項服務基本上是免費的,不過 Google 為了避免被用來惡意寄發垃圾信,所以有每日 500 封、且單封信件大小不可超過 10M 的限制


登入 Google 帳戶


點擊右上角的帳戶圖示 → 【管理你的 Google 帳戶


確認帳戶已啟用【兩步驟驗證】 → 在上方的搜尋框輸入【應用程式密碼


為應用程式命名,在本例中,就叫做 metabase。


系統產生 16 字的密碼,複製/記下來。


管理員登入 metabase → 點擊右上角的齒輪圖示 → 【管理員設定


點擊【電子郵件設定】 → 【編輯配置


參考下圖,填入各個欄位。


寄送測試信,會寄到管理員的信箱。


由於我的管理員 emailGmail(但與申請 smtp.gmail.com 的帳號不同),所以去 Gmail 看有沒有收到信,若有信寄來,就表示設定成功了。


拿 Metabase 現成自帶的範例儀表板實驗


打開 E-commerce Inshights 後,點擊儀表板右上角的 【分享】圖示 → 【訂閱


可以從 Metabase 的使用者名單選擇要訂閱的人,也可以自己輸入非 Metabase 使用者 → 選擇發送頻率。
若是同一個儀表板,有不同的人在看,在【訂閱】所指定的收信者是各自獨立設定的。
例如:A 已訂閱了 E-commerce Inshights,B 在【訂閱】是看不到有哪些人已經訂閱了 E-commerce Inshights 的。


點擊【現在發送郵件】,確認是否設定正確。


大約過 5 分鐘,去檢查信箱,就可以看到信件內容了。


信件是響應式(RWD) 的,包含了所有分頁的圖表及數據,個人覺得手機看也夠清楚。


2026-01-01

【Metabase】套用台灣地圖進行分析(一) -- 市級行政區

參考資料 ----

simplemaps


Metabase 預設自帶 世界地圖 & 美國地圖,如果需要台灣地圖幫助我們進行資料分析,則要手動新增台灣圖資。


simplemaps 下載台灣的 GeoJSON 的檔案 -- 檔名 tw.json,瞄一下 json 檔的內容,包含了一級行政區,其中屬性 id, name 是我們需要的。

 
{
  "type": "FeatureCollection", 
  "features": [
    {
...
...

  "type": "Feature", 
      "properties": {
        "source": "https://simplemaps.com", 
        "id": "TWKIN", 
        "name": "Kinmen"
      }, 
      
...
...
 



因為在【LinuxMint 22.2 安裝設定 MySQL 8.0 + phpMyadmin】這篇筆記中,我們安裝了 phpMyadmin,所以 LinuxMint 一併安裝了 Apache,其網站根目錄位置為 /var/www/html/

tw.json 放置在 /var/www/html/ 下,然後在 Metabase 設定 tw.jsonurl,在本例為 http://localhost/tw.json

登入 Metabase,點擊【齒輪】圖示

→ 管理員設定


→ 地圖 → 新增一個地圖



要注意的是:通常我們的資料庫中 縣/市 欄位並不是遵循 GeoJSONISO 名稱定義,所以需要做適當的轉換,例如:另外建一個對照用的 table

 
對照資料表:city_geo
+--------+--------+-------+
|   id   |  英文  |  中文  |
+--------+--------+-------+
| TWKIN  | Kinmen | 金門  | 
+--------+--------+-------+
|  ...   |  ...   |  ...  |
+--------+--------+-------+
 




相關筆記 ----

2025-12-31

【Metabase】新增一般使用者

點擊右上角的齒輪圖示

管理員設定



點擊【邀請他人


Metabase 是以 e-mail 做帳號管理,輸入 user 的 e-mail 後,若您 或 貴公司有自己的 mail serverMetabase 就會寄送含該 user 的密碼的信件到 e-mail 的信箱;在本筆記中,因沒有架設 mail server,所以要手動記下 Metabase 自動產生的密碼。

===== 2026.01.25 =====
可參考 設定委託 Gmail 定時發送通知信件 這篇筆記,設定 Gmail 做為 mail server,
不過要注意:一旦設定了 mail server ,之後新建的 Metabase 一般使用者的帳號就必須是真實的 e-mail,因為 Metabase 會將初始密碼寄到該 email

若有設定 mail server,就看不到上圖的初始密碼,Metabase 會將密碼直接寄送到 e-mail

登出管理員,讓剛剛新建的 user 登入


點擊齒輪圖示
帳號設定


輸入剛才 Metabase 產生的密碼,再變更為自己希望的密碼,如果不符合 Metabase 的密碼規則,就會看到提示。

2025-12-28

【Metabase】LinuxMint22.2,設定為開機即啟動 Metabase 服務

參考資料 ----

metabase.jar 移至 /usr/share/metabase/ 目錄
 
# 原本 metabase.jar 是放在 使用者家目錄下的 metabase 目錄內
[user]~$ cd  metabase
[user]$ sudo  su
[root]# groupadd  -r  metabase
[root]# useradd  -r  -s  /bin/false  -g  metabase  metabase
[root]# mkdir  /usr/share/metabase
[root]# cp metabase.jar  /usr/share/metabase
[root]# chown  -R  metabase:metabase  /usr/share/metabase
[root]# touch  /var/log/metabase.log
[root]# chown  syslog:adm  /var/log/metabase.log
[root]# touch  /etc/default/metabase
[root]# chmod  640  /etc/default/metabase
 


建立 metabase.service 服務檔
 
[root]# cd  /etc/systemd/system/
[root]# vim metabase.service
# 輸入下述內容
[Unit]
Description=Metabase server
After=syslog.target
After=network.target

[Service]
WorkingDirectory=/usr/share/metabase/
ExecStart=/usr/bin/java  -jar  /usr/share/metabase/metabase.jar
EnvironmentFile=/etc/default/metabase
User=metabase
Type=simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=metabase
SuccessExitStatus=143
TimeoutStopSec=120
Restart=always

[Install]
WantedBy=multi-user.target

:x 存檔離開
 


建立 syslog conf
 
[root]# vim  /etc/rsyslog.d/metabase.conf
# 輸入下述內容
if $programname == 'metabase' then /var/log/metabase.log
& stop

:x 存檔離開

# 重啟 rsyslog.service 以使 metabase.conf 生效
[root]# systemctl  restart  rsyslog.service
 


編輯 Metabase 的環境變數
 
[root]# vim  /etc/default/metabase
MB_PASSWORD_COMPLEXITY=strong
MB_PASSWORD_LENGTH=8    # 要求最短的密碼字串長度
MB_JETTY_HOST=0.0.0.0
MB_JETTY_PORT=3000
MB_DB_CONNECTION_URI="jdbc:mysql://localhost:3306/metabase?user=metabase&password=密碼&verifyServerCertificate=false&allowPublicKeyRetrieval=true"

MB_EMOJI_IN_LOGS=true    # log 要不要有顏文字...這還蠻有趣的...若不要就填 false
# any other env vars you want available to Metabase

:x 存檔離開
 


設定 metabase 為開機啟動
 
# 先手動啟動 metabase.service
[root]# systemctl  daemon-reload
[root]# systemctl  start  metabase.service
[root]# systemctl  status  metabase.service    # 觀察是否已啟動

# 另外再開瀏覽器, 看看是否能登入 metabase
# 確定執行沒問題了, 就改為開機自動啟動
[root]# systemctl  enable  metabase.service
 



相關筆記 ----

2025-12-26

【Metabase】LinuxMint22.2 上,社群版(Community) 將系統資料移至大型資料庫 MySQL8

參考資料 ----


Metabase 自帶的系統資料庫為 H2,僅用於測試試用期間,若要轉為正式應用(production),官方強烈建議將系統資料移至大型資料庫。

建議版本為 MySQL 5.7.7(含)MariaDB 10.2.2(含) 或 PostgreSQL 9.4(含) 以上;本筆記為 MySQL 8.0.44

注意:避免同時做 Metabase 更新升級 及 資料庫移植


關閉 Metabase,目前我的練功主機是開終端機視窗,在家目錄以
 
[user]~$ java  -jar  metabase.jar
 
的指令啟動 Metabase
所以只要在瀏覽器的 Metabase 頁面登出,然後在終端機視窗 Ctrl+C 中斷 Metabase 即可。


備份 H2︰複製 metabase.db.mv.db 到另一個安全的目錄存放。


以 root 登入 phpMyAdmin
MySQL 新增 metabase 帳戶並建立同名資料庫 metabase。
注意:建議密碼自已設定,而不要由 phpMyAdmin 幫您產生,以免密碼中不知道有哪個字元踩到了雷!!目前已知的有 【@】、【:】、【/】、【?】、【#】,會造成 java 錯誤解析 URI 字串,若是一定要用時,則在組連線字串時,須將上述字元改成 URI 編碼,後面再進一步說明。





 
-- 重點在最後面的 "with grant option", phpMyAdmin 圖形界面似乎做不到賦予 grant 權限
GRANT ALL PRIVILEGES ON `metabase`.* TO 'metabase'@'localhost' with grant option;
 


執行移植指令
 
# 切換到 metabase.jar 所在目錄, 在本例為 [user]~/metabase/
[user]~/metabase$  export MB_DB_TYPE=mysql
[user]~/metabase$  export MB_DB_CONNECTION_URI="jdbc:mysql://localhost:3306/metabase?user=metabase&password=密碼&verifyServerCertificate=false&allowPublicKeyRetrieval=true"
[user]~/metabase$ java  -DMB_DB_TYPE=mysql  -DMB_DB_CONNECTION_URI="jdbc:mysql://localhost:3306/metabase?user=metabase&password=密碼&verifyServerCertificate=false&allowPublicKeyRetrieval=true"  -jar  metabase.jar  load-from-h2  metabase.db    # 注意檔名
 
前面提到,如果密碼非得要用那幾個特殊字元,則在組 javaJDBC 連線字串時,要改成 URI 編碼,如下:
@ → %40
: → %3A
/ → %2F
? → %3F
# → %23


因為有備份 H2,所以可以放心刪除 metabase 所在目錄下的 H2 檔案,以確認 metabase 存取的是 MySQL。

重新啟動 metabase
 
[user]~/metabase$  export MB_DB_TYPE=mysql
[user]~/metabase$  export MB_DB_CONNECTION_URI="jdbc:mysql://localhost:3306/metabase?user=metabase帳戶&password=密碼"
[user]~/metabase$  java  -DMB_DB_CONNECTION_URI="jdbc:mysql://localhost:3306/metabase?user=metabase&password=密碼&verifyServerCertificate=false&allowPublicKeyRetrieval=true"  -jar  metabase.jar
 
重登入 metabase,可以看到之前製作的儀表板都在。



相關筆記 ----