Add offline OSM fuel station database
- Add fuel_coords_db.py to download OSM fuel station data - 1299 CPC/related stations with coordinates - Query order: cache -> OSM database -> Nominatim API - Build database with: python3 fuel_coords_db.py --download Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ from typing import Dict, List, Optional
|
||||
BASE_URL = "https://vipmbr.cpc.com.tw/mbwebs/service_search.aspx"
|
||||
USER_AGENT = "Mozilla/5.0 (OpenClaw; cpc-direct-stations)"
|
||||
CACHE_FILE = Path.home() / ".cache" / "cpc_stations_coords.json"
|
||||
FUEL_DB_FILE = Path(__file__).parent / "fuel_stations.json" # 離線加油站座標庫
|
||||
CITY_MAP = {
|
||||
"台北市": "A ",
|
||||
"新北市": "B ",
|
||||
@@ -157,6 +158,53 @@ def save_coords_cache(cache: Dict[str, Dict[str, float]]) -> None:
|
||||
json.dump(cache, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def load_fuel_db() -> Dict[str, Dict[str, float]]:
|
||||
"""載入離線加油站座標資料庫(來源:OSM)。"""
|
||||
if FUEL_DB_FILE.exists():
|
||||
try:
|
||||
with open(FUEL_DB_FILE, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
def query_fuel_db(station_name: str, city: str, district: str, fuel_db: Dict) -> Optional[Dict[str, float]]:
|
||||
"""從離線資料庫查詢加油站座標。"""
|
||||
if not fuel_db:
|
||||
return None
|
||||
|
||||
# 清理站名(移除代號)
|
||||
clean_name = re.sub(r'\s+[A-Z]\d+[A-Z]?\s*$', '', station_name).strip()
|
||||
|
||||
# 嘗試多種查詢鍵
|
||||
queries = [
|
||||
clean_name,
|
||||
f"台灣中油 {clean_name}",
|
||||
f"中油 {clean_name}",
|
||||
f"{clean_name}站",
|
||||
f"台灣中油 {clean_name}站",
|
||||
f"中油 {clean_name}站",
|
||||
]
|
||||
if city:
|
||||
queries.extend([
|
||||
f"{city}{clean_name}",
|
||||
f"{city}{clean_name}站",
|
||||
f"{clean_name} ({city})",
|
||||
])
|
||||
|
||||
for q in queries:
|
||||
if q in fuel_db:
|
||||
return fuel_db[q]
|
||||
|
||||
# 模糊匹配
|
||||
for key, value in fuel_db.items():
|
||||
if clean_name in key or key in clean_name:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def make_cache_key(station: Dict[str, str]) -> str:
|
||||
"""產生快取鍵值:使用站名+縣市+完整地址。"""
|
||||
return f"{station['站名']}|{station['縣市']}|{station['鄉鎮區']}|{station['地址']}"
|
||||
@@ -200,7 +248,7 @@ def geocode(address: str, station_name: str, city: str, district: str) -> Option
|
||||
|
||||
|
||||
def add_coordinates(stations: List[Dict[str, str]], delay: float = 1.0, workers: int = 2) -> List[Dict[str, str]]:
|
||||
"""為站點資料加上座標資訊(使用快取+並發查詢)。
|
||||
"""為站點資料加上座標資訊(快取 -> OSM資料庫 -> Nominatim)。
|
||||
|
||||
Args:
|
||||
stations: 站點列表
|
||||
@@ -208,22 +256,34 @@ def add_coordinates(stations: List[Dict[str, str]], delay: float = 1.0, workers:
|
||||
workers: 並發查詢執行緒數(預設 2,避免 Nominatim 限制)
|
||||
"""
|
||||
cache = load_coords_cache()
|
||||
fuel_db = load_fuel_db()
|
||||
if fuel_db:
|
||||
print(f"載入離線資料庫: {len(fuel_db)} 筆", file=sys.stderr)
|
||||
|
||||
missing_coords = []
|
||||
|
||||
# 先從快取讀取
|
||||
# 先從快取讀取,再試離線資料庫
|
||||
for station in stations:
|
||||
key = make_cache_key(station)
|
||||
if key in cache:
|
||||
station["緯度"] = str(cache[key]["lat"])
|
||||
station["經度"] = str(cache[key]["lon"])
|
||||
else:
|
||||
station["緯度"] = ""
|
||||
station["經度"] = ""
|
||||
missing_coords.append(station)
|
||||
# 嘗試離線資料庫
|
||||
coords = query_fuel_db(station["站名"], station["縣市"], station["鄉鎮區"], fuel_db)
|
||||
if coords:
|
||||
station["緯度"] = str(coords["lat"])
|
||||
station["經度"] = str(coords["lon"])
|
||||
# 寫入快取
|
||||
cache[key] = coords
|
||||
else:
|
||||
station["緯度"] = ""
|
||||
station["經度"] = ""
|
||||
missing_coords.append(station)
|
||||
|
||||
# 並發查詢缺少的座標
|
||||
# 並發查詢缺少的座標(使用 Nominatim)
|
||||
if missing_coords:
|
||||
print(f"快取命中: {len(stations) - len(missing_coords)}/{len(stations)},需查詢: {len(missing_coords)}", file=sys.stderr)
|
||||
print(f"快取命中: {len(stations) - len(missing_coords)}/{len(stations)},需 API 查詢: {len(missing_coords)}", file=sys.stderr)
|
||||
|
||||
def query_one(station: Dict[str, str]) -> tuple[Dict[str, str], Optional[Dict[str, float]]]:
|
||||
full_address = f"{station['縣市']}{station['鄉鎮區']}{station['地址']}"
|
||||
|
||||
Reference in New Issue
Block a user