keihan-tracker 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- keihan_tracker/__init__.py +5 -0
- keihan_tracker/bus/__init__.py +1 -0
- keihan_tracker/bus/schemes.py +58 -0
- keihan_tracker/bus/tracker.py +13 -0
- keihan_tracker/delay_tracker.py +153 -0
- keihan_tracker/keihan_train/__init__.py +1 -0
- keihan_tracker/keihan_train/position_calculation.py +129 -0
- keihan_tracker/keihan_train/schemes.py +214 -0
- keihan_tracker/keihan_train/stations_map.py +8 -0
- keihan_tracker/keihan_train/tracker.py +893 -0
- keihan_tracker-2.2.0.dist-info/METADATA +23 -0
- keihan_tracker-2.2.0.dist-info/RECORD +14 -0
- keihan_tracker-2.2.0.dist-info/WHEEL +5 -0
- keihan_tracker-2.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .tracker import get_khbus_info
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from pydantic import BaseModel, model_validator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Head(BaseModel):
|
|
6
|
+
errorcode: int
|
|
7
|
+
objid: str
|
|
8
|
+
|
|
9
|
+
class BusStatePrms(BaseModel):
|
|
10
|
+
order: int # 接近順位
|
|
11
|
+
lat: float # 緯度
|
|
12
|
+
lon: float # 経度
|
|
13
|
+
heading: int # 方位
|
|
14
|
+
guid: str # GUID
|
|
15
|
+
stop_id: int # 停留所ID?
|
|
16
|
+
platform_index: int # のりばインデックス?
|
|
17
|
+
route: str # 系統番号(例: [22])
|
|
18
|
+
destination: str # 行先
|
|
19
|
+
via: str # 経由
|
|
20
|
+
status: str # 状態(到着済など)
|
|
21
|
+
timetable: str # 時刻文字列(例: 17:30 到着予定)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_string(cls, raw: str):
|
|
25
|
+
parts = raw.split(":")
|
|
26
|
+
return cls(
|
|
27
|
+
order=int(parts[0]),
|
|
28
|
+
lat=float(parts[1]),
|
|
29
|
+
lon=float(parts[2]),
|
|
30
|
+
heading=int(parts[3]),
|
|
31
|
+
guid=parts[4],
|
|
32
|
+
stop_id=int(parts[5]),
|
|
33
|
+
platform_index=int(parts[6]),
|
|
34
|
+
route=parts[7],
|
|
35
|
+
destination=parts[8],
|
|
36
|
+
via=parts[9],
|
|
37
|
+
status=parts[10],
|
|
38
|
+
timetable=parts[11].replace("__________", ":"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
class BusState(BaseModel):
|
|
42
|
+
html: Optional[str] = None
|
|
43
|
+
html_sp: Optional[str] = None
|
|
44
|
+
busstateprms: BusStatePrms
|
|
45
|
+
|
|
46
|
+
@model_validator(mode="before")
|
|
47
|
+
def parse_prms(cls, values):
|
|
48
|
+
if isinstance(values, dict) and "busstateprms" in values and isinstance(values["busstateprms"], str):
|
|
49
|
+
values["busstateprms"] = BusStatePrms.from_string(values["busstateprms"])
|
|
50
|
+
return values
|
|
51
|
+
|
|
52
|
+
class Body(BaseModel):
|
|
53
|
+
datetimeStr: str
|
|
54
|
+
busstates: List[BusState]
|
|
55
|
+
|
|
56
|
+
class BusLocationResponse(BaseModel):
|
|
57
|
+
head: Head
|
|
58
|
+
body: Body
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from httpx import AsyncClient
|
|
2
|
+
import urllib.parse
|
|
3
|
+
from keihan_tracker.bus.schemes import BusLocationResponse
|
|
4
|
+
|
|
5
|
+
UPDATE_URL = "https://busnavi.keihanbus.jp/pc/busstateupd"
|
|
6
|
+
|
|
7
|
+
async def get_khbus_info(stop_name:str, stop_num:int=1):
|
|
8
|
+
async with AsyncClient() as client:
|
|
9
|
+
dgmpl = f"{stop_name}:{stop_num}::"
|
|
10
|
+
result = await client.post(UPDATE_URL,data={"dgmpl":urllib.parse.quote(dgmpl), "sort4":"0"})
|
|
11
|
+
result.raise_for_status()
|
|
12
|
+
res = BusLocationResponse.model_validate_json(result.text)
|
|
13
|
+
return res
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from pydantic import BaseModel, field_validator, BeforeValidator, Field
|
|
2
|
+
from typing import Optional, Union, Annotated, List, Any
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from zoneinfo import ZoneInfo
|
|
5
|
+
from bs4 import BeautifulSoup as bs
|
|
6
|
+
import asyncio
|
|
7
|
+
import httpx
|
|
8
|
+
import re
|
|
9
|
+
from urllib.parse import urljoin
|
|
10
|
+
|
|
11
|
+
JST = ZoneInfo("Asia/Tokyo")
|
|
12
|
+
BASE_URL = "https://transit.yahoo.co.jp/diainfo/area/"
|
|
13
|
+
|
|
14
|
+
class DelayLine(BaseModel):
|
|
15
|
+
LineName: str
|
|
16
|
+
status: str
|
|
17
|
+
detail: str
|
|
18
|
+
AnnouncedTime: datetime
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return f"{self.LineName}: {self.status}\n{self.detail}\n{self.AnnouncedTime} 発表"
|
|
22
|
+
|
|
23
|
+
def force_list(v: Any) -> List[Any]:
|
|
24
|
+
if v is None: return []
|
|
25
|
+
if isinstance(v, list): return v
|
|
26
|
+
return [v]
|
|
27
|
+
|
|
28
|
+
AsList = BeforeValidator(force_list)
|
|
29
|
+
|
|
30
|
+
# --- モデル定義 (クラス名を変更して衝突を回避) ---
|
|
31
|
+
|
|
32
|
+
class LineData(BaseModel):
|
|
33
|
+
Name: str = ""
|
|
34
|
+
code: str = ""
|
|
35
|
+
corporationIndex: str = ""
|
|
36
|
+
|
|
37
|
+
class CommentData(BaseModel):
|
|
38
|
+
text: str = ""
|
|
39
|
+
status: str = ""
|
|
40
|
+
|
|
41
|
+
class PrefectureData(BaseModel):
|
|
42
|
+
Name: str = ""
|
|
43
|
+
code: str = ""
|
|
44
|
+
|
|
45
|
+
class StationData(BaseModel):
|
|
46
|
+
Name: str = ""
|
|
47
|
+
code: str = ""
|
|
48
|
+
Type: str = ""
|
|
49
|
+
Yomi: str = ""
|
|
50
|
+
|
|
51
|
+
class PointData(BaseModel):
|
|
52
|
+
# フィールド名はJSONに合わせて "Prefecture" だが、型は "PrefectureData" にする
|
|
53
|
+
Prefecture: PrefectureData = Field(default_factory=PrefectureData)
|
|
54
|
+
Station: StationData = Field(default_factory=StationData)
|
|
55
|
+
|
|
56
|
+
class SectionData(BaseModel):
|
|
57
|
+
Point: Annotated[List[PointData], AsList] = []
|
|
58
|
+
|
|
59
|
+
class CorporationData(BaseModel):
|
|
60
|
+
Name: str = ""
|
|
61
|
+
code: str = ""
|
|
62
|
+
|
|
63
|
+
class InformationData(BaseModel):
|
|
64
|
+
status: str = ""
|
|
65
|
+
provider: str = ""
|
|
66
|
+
Title: str = ""
|
|
67
|
+
Datetime: datetime
|
|
68
|
+
|
|
69
|
+
# ここでもフィールド名と型名を区別する
|
|
70
|
+
Line: LineData = Field(default_factory=LineData)
|
|
71
|
+
|
|
72
|
+
Section: Annotated[List[SectionData], AsList] = []
|
|
73
|
+
Comment: Annotated[List[CommentData], AsList] = []
|
|
74
|
+
Prefecture: Annotated[List[PrefectureData], AsList] = []
|
|
75
|
+
|
|
76
|
+
class ResultSetData(BaseModel):
|
|
77
|
+
apiVersion: str = ""
|
|
78
|
+
engineVersion: str = ""
|
|
79
|
+
Information: Annotated[List[InformationData], AsList] = []
|
|
80
|
+
Corporation: Annotated[List[CorporationData], AsList] = []
|
|
81
|
+
|
|
82
|
+
class ResponseModel(BaseModel):
|
|
83
|
+
ResultSet: ResultSetData
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def get_yahoo_delay(area:int=6) -> list[DelayLine]:
|
|
87
|
+
async with httpx.AsyncClient() as crowler:
|
|
88
|
+
html = await crowler.get(f"{BASE_URL}{area}")
|
|
89
|
+
|
|
90
|
+
soup = bs(html.content, "html.parser")
|
|
91
|
+
|
|
92
|
+
table = soup.select("div.elmTblLstLine.trouble")[0].find_all("table")
|
|
93
|
+
table = table[0] if len(table) != 0 else None
|
|
94
|
+
if not table:
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
async def get_detail(url:str) -> tuple[str,str]:
|
|
98
|
+
res = await crowler.get(url,follow_redirects=True)
|
|
99
|
+
soup = bs(res.text, "html.parser")
|
|
100
|
+
info = soup.select("div#mdServiceStatus")[0]
|
|
101
|
+
title = info.select("dl > dt")[0].text
|
|
102
|
+
text = info.select("dl > dd > p")[0].text
|
|
103
|
+
|
|
104
|
+
return title, text
|
|
105
|
+
|
|
106
|
+
delays:list[DelayLine] = []
|
|
107
|
+
tasks:list = []
|
|
108
|
+
lines:list[tuple[str,str]] = []
|
|
109
|
+
for tr in table.find_all("tr")[1:]:
|
|
110
|
+
url = urljoin(BASE_URL, str(tr.find_all("td")[0].find_all("a")[0].get("href")))
|
|
111
|
+
line = tr.find_all("td")[0].text # ○○線
|
|
112
|
+
short_status = tr.find_all("td")[2].text # 17:00頃、宇都宮...
|
|
113
|
+
tasks.append(get_detail(url))
|
|
114
|
+
lines.append((line, short_status))
|
|
115
|
+
|
|
116
|
+
for i, line in zip(await asyncio.gather(*tasks), lines):
|
|
117
|
+
line, status = line
|
|
118
|
+
title, text = i
|
|
119
|
+
|
|
120
|
+
pattern = r"((?P<month>\d{1,2})月(?P<day>\d{1,2})日\s*(?P<hour>\d{1,2})時(?P<minute>\d{1,2})分掲載)"
|
|
121
|
+
m = re.search(pattern, text) or {}
|
|
122
|
+
|
|
123
|
+
dt = datetime(
|
|
124
|
+
year=datetime.now().year, # 年は別途補完
|
|
125
|
+
month=int(m["month"]),
|
|
126
|
+
day=int(m["day"]),
|
|
127
|
+
hour=int(m["hour"]),
|
|
128
|
+
minute=int(m["minute"]),
|
|
129
|
+
tzinfo=JST
|
|
130
|
+
)
|
|
131
|
+
delays.append(DelayLine(LineName=line, status=title, detail=text, AnnouncedTime=dt))
|
|
132
|
+
return delays
|
|
133
|
+
|
|
134
|
+
async def get_ekispert_delay(api_key:str, prefs:list[int]=[26,27,28]) -> list[DelayLine]:
|
|
135
|
+
async with httpx.AsyncClient() as web:
|
|
136
|
+
uri = f"http://api.ekispert.jp/v1/json/operationLine/service/rescuenow/information?key={api_key}"
|
|
137
|
+
uri += f"&prefectureCode={':'.join(map(str,prefs))}"
|
|
138
|
+
request = await web.get(uri)
|
|
139
|
+
request.raise_for_status()
|
|
140
|
+
res = ResponseModel.model_validate(request.json())
|
|
141
|
+
|
|
142
|
+
results:dict[str,DelayLine] = {}
|
|
143
|
+
for i in res.ResultSet.Information or []:
|
|
144
|
+
if i.Line.code in results:
|
|
145
|
+
if i.Datetime < (results[i.Line.code].AnnouncedTime or datetime.min):
|
|
146
|
+
continue
|
|
147
|
+
results[i.Line.code] = DelayLine(
|
|
148
|
+
LineName=i.Line.Name,
|
|
149
|
+
status=i.status,
|
|
150
|
+
detail=i.Comment[0].text,
|
|
151
|
+
AnnouncedTime=i.Datetime
|
|
152
|
+
)
|
|
153
|
+
return list(results.values())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .tracker import KHTracker
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# GEMINI VIBE CODING
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .schemes import LineLiteral
|
|
5
|
+
|
|
6
|
+
def calc_position(col: int, row: int) -> tuple[LineLiteral, int, Optional[int]]:
|
|
7
|
+
"""座標から、停車中の駅番号、もしくは2駅の駅番号を返します。"""
|
|
8
|
+
line: LineLiteral
|
|
9
|
+
|
|
10
|
+
# --- 1. 路線判定 ---
|
|
11
|
+
if 1 <= row <= 131:
|
|
12
|
+
# Row 118以降かつcol 1,2は中之島線
|
|
13
|
+
if row >= 118 and (col == 1 or col == 2):
|
|
14
|
+
line = "中之島線"
|
|
15
|
+
else:
|
|
16
|
+
line = "京阪本線・鴨東線"
|
|
17
|
+
elif 132 <= row <= 153:
|
|
18
|
+
line = "宇治線"
|
|
19
|
+
elif 154 <= row <= 175:
|
|
20
|
+
line = "交野線"
|
|
21
|
+
else:
|
|
22
|
+
raise ValueError(f"Inputed row {row} is out of range.")
|
|
23
|
+
|
|
24
|
+
# colのバリデーション
|
|
25
|
+
if col <= 0 or col >= 6:
|
|
26
|
+
raise ValueError(f"Inputed col {col} is out of range.")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# --- 2. 駅IDと状態の計算 ---
|
|
30
|
+
|
|
31
|
+
# === A. 京阪本線・鴨東線・中之島線 (Row 1-131) ===
|
|
32
|
+
if row <= 131:
|
|
33
|
+
# A-1. 通常区間 (Row 1-117)
|
|
34
|
+
if row <= 117:
|
|
35
|
+
block_index = (row - 1) // 3
|
|
36
|
+
current_station = 42 - block_index
|
|
37
|
+
pos_in_block = (row - 1) % 3 # 0:Top, 1:Mid, 2:Bot
|
|
38
|
+
|
|
39
|
+
is_stopped = False
|
|
40
|
+
|
|
41
|
+
# 基本: Top(0)は停車
|
|
42
|
+
if pos_in_block == 0:
|
|
43
|
+
is_stopped = True
|
|
44
|
+
|
|
45
|
+
# 特例: 2行目(Mid)以降も停車扱いになる駅 (HTMLの big-area/special-area 等に基づく)
|
|
46
|
+
# 出町柳(42): Row 2も停車
|
|
47
|
+
if current_station == 42 and pos_in_block == 1: is_stopped = True
|
|
48
|
+
# 枚方市(21): Row 65(Mid)も停車
|
|
49
|
+
if current_station == 21 and pos_in_block == 1: is_stopped = True
|
|
50
|
+
# 京橋(4): Row 116(Mid)も停車
|
|
51
|
+
if current_station == 4 and pos_in_block == 1: is_stopped = True
|
|
52
|
+
|
|
53
|
+
if is_stopped:
|
|
54
|
+
return (line, current_station, None)
|
|
55
|
+
else:
|
|
56
|
+
# 移動中 (当駅 -> 次駅)
|
|
57
|
+
# Next station is current - 1
|
|
58
|
+
return (line, current_station, current_station - 1)
|
|
59
|
+
|
|
60
|
+
# A-2. 地下・中之島線区間 (Row 118-131)
|
|
61
|
+
# この区間はHTML上隙間なく special-area が続くため、すべて停車扱いとする
|
|
62
|
+
else:
|
|
63
|
+
# 共通: 天満橋(3) Row 118-120
|
|
64
|
+
if row <= 120: return (line, 3, None)
|
|
65
|
+
|
|
66
|
+
if line == "中之島線":
|
|
67
|
+
if row <= 123: return (line, 51, None) # なにわ橋
|
|
68
|
+
elif row <= 126: return (line, 52, None) # 大江橋
|
|
69
|
+
elif row <= 129: return (line, 53, None) # 渡辺橋
|
|
70
|
+
else: return (line, 54, None) # 中之島
|
|
71
|
+
|
|
72
|
+
else: # 京阪本線 大阪側
|
|
73
|
+
if row <= 123: return (line, 2, None) # 北浜
|
|
74
|
+
elif row <= 126: return (line, 1, None) # 淀屋橋
|
|
75
|
+
# Row 127以降は本線には無し
|
|
76
|
+
else: raise ValueError(f"Invalid row {row} for Main Line")
|
|
77
|
+
|
|
78
|
+
# === B. 宇治線 (Row 132-153) ===
|
|
79
|
+
elif row <= 153:
|
|
80
|
+
# 中書島(28) Row 132-134 (始発)
|
|
81
|
+
if row <= 134:
|
|
82
|
+
# HTML: 132(special), 133(big) -> 停車
|
|
83
|
+
pos = row - 132
|
|
84
|
+
if pos <= 1: return (line, 28, None)
|
|
85
|
+
else: return (line, 28, 71) # 発車
|
|
86
|
+
|
|
87
|
+
# 終点: 宇治(77)
|
|
88
|
+
if row == 153: return (line, 77, None)
|
|
89
|
+
|
|
90
|
+
# 通常駅 (KH71-KH76)
|
|
91
|
+
block_idx = (row - 135) // 3
|
|
92
|
+
current_station = 71 + block_idx
|
|
93
|
+
pos_in_block = (row - 135) % 3
|
|
94
|
+
|
|
95
|
+
# 基本: Top(0)のみ停車
|
|
96
|
+
if pos_in_block == 0:
|
|
97
|
+
return (line, current_station, None)
|
|
98
|
+
else:
|
|
99
|
+
return (line, current_station, current_station + 1)
|
|
100
|
+
|
|
101
|
+
# === C. 交野線 (Row 154-175) ===
|
|
102
|
+
elif row <= 175:
|
|
103
|
+
# 枚方市(21) Row 154-156 (始発)
|
|
104
|
+
if row <= 156:
|
|
105
|
+
# HTML: 154(special), 155(big) -> 停車
|
|
106
|
+
pos = row - 154
|
|
107
|
+
if pos <= 1: return (line, 21, None)
|
|
108
|
+
else: return (line, 21, 61) # 発車
|
|
109
|
+
|
|
110
|
+
# 終点: 私市(67)
|
|
111
|
+
if row == 175: return (line, 67, None)
|
|
112
|
+
|
|
113
|
+
# 通常駅 (KH61-KH66)
|
|
114
|
+
block_idx = (row - 157) // 3
|
|
115
|
+
current_station = 61 + block_idx
|
|
116
|
+
pos_in_block = (row - 157) % 3
|
|
117
|
+
|
|
118
|
+
# 基本: Top(0)のみ停車
|
|
119
|
+
if pos_in_block == 0:
|
|
120
|
+
return (line, current_station, None)
|
|
121
|
+
else:
|
|
122
|
+
return (line, current_station, current_station + 1)
|
|
123
|
+
|
|
124
|
+
return (line, 0, None)
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
col,row=map(int,input().split())
|
|
128
|
+
r = calc_position(col,row)
|
|
129
|
+
print(r)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
京阪電車リアルタイム列車位置情報APIのデータを、Pythonで安全かつ型安全に扱うためのPydanticモデル群。
|
|
3
|
+
|
|
4
|
+
【特徴】
|
|
5
|
+
・APIから取得したJSONをバリデート(型検証)することが主目的。
|
|
6
|
+
・API仕様に忠実で、余計な変換やロジックを加えず「生に近い」データ構造を維持。
|
|
7
|
+
※臨時列車判定は例外的に実装。
|
|
8
|
+
|
|
9
|
+
【バリデートできるJSON APIと内容】
|
|
10
|
+
------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
1. 駅ごとの乗り換え情報
|
|
13
|
+
URL: https://www.keihan.co.jp/zaisen/transferGuideInfo.json
|
|
14
|
+
内容: 各駅で接続している他路線(京阪・地下鉄・モノレール等)の情報。駅番号をキーに、接続路線名(多言語対応)などが格納されている。
|
|
15
|
+
対応クラス: TransferGuideInfo > StationConnections > MultiLang_Lines
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
2. 路線ごとの駅名データ
|
|
19
|
+
URL: https://www.keihan.co.jp/zaisen/select_station.json
|
|
20
|
+
内容: 路線ごとに、駅番号・駅名(多言語対応)などをまとめたデータ。路線名をキーに、各駅の情報が格納されている。
|
|
21
|
+
対応クラス: SelectStation > LineDetail > MultiLang
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
3. 列車のリアルタイム走行位置
|
|
25
|
+
URL: https://www.keihan.co.jp/zaisen-up/trainPositionList.json
|
|
26
|
+
内容: 現在運行中の列車の位置・種別・遅延情報など。各列車の現在地や行先、種別、遅延分数などが含まれる。
|
|
27
|
+
対応クラス: trainPositionList > LocationObject > trainInfoObject
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
4. 列車ごとのダイヤ(停車駅・時刻表)
|
|
31
|
+
URL: https://www.keihan.co.jp/zaisen-up/startTimeList.json
|
|
32
|
+
内容: 各列車の停車駅・発車時刻などのダイヤ情報。列車ごとに、どの駅に何時何分に停車するか等が記載されている。
|
|
33
|
+
対応クラス: startTimeList > TrainInfo > diaStationInfoObject
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from pydantic import BaseModel, RootModel, Field, model_validator, field_validator
|
|
37
|
+
from typing import Optional, Literal
|
|
38
|
+
from enum import Enum
|
|
39
|
+
import datetime
|
|
40
|
+
from zoneinfo import ZoneInfo
|
|
41
|
+
|
|
42
|
+
JST = ZoneInfo("Asia/Tokyo")
|
|
43
|
+
|
|
44
|
+
# 列車種別をEnumで定義
|
|
45
|
+
class TrainType(str, Enum):
|
|
46
|
+
LOCAL = "普通"
|
|
47
|
+
SEMI_EXP = "区間急行"
|
|
48
|
+
SUB_EXP = "準急"
|
|
49
|
+
COMMUTER_SUB_EXP = "通勤準急"
|
|
50
|
+
EXPRESS = "急行"
|
|
51
|
+
COMMUTER_EXP = "通勤急行"
|
|
52
|
+
MIDNIGHT_EXP = "深夜急行"
|
|
53
|
+
RAPID_EXP = "快速急行"
|
|
54
|
+
COMMUTER_RAPID_EXP = "通勤快急"
|
|
55
|
+
LTD_EXP = "特急"
|
|
56
|
+
LINER = "ライナー"
|
|
57
|
+
RAPID_LTD_EXP = "快速特急 洛楽"
|
|
58
|
+
EXTRA_TRAIN = "臨時列車" # 臨時列車は臨時特急や臨時休校などとは別
|
|
59
|
+
|
|
60
|
+
LineLiteral = Literal["京阪本線・鴨東線","中之島線","交野線","宇治線"]
|
|
61
|
+
|
|
62
|
+
# 1. 言語別の路線名リストを表現するモデル
|
|
63
|
+
class MultiLang_Lines(BaseModel):
|
|
64
|
+
"""
|
|
65
|
+
各言語での路線名を保持するモデル。
|
|
66
|
+
"""
|
|
67
|
+
ja: list[str] = Field(description="日本語での名称")
|
|
68
|
+
en: list[str] = Field(description="英語での名称")
|
|
69
|
+
cn: list[str] = Field(description="中国語(簡体字)での名称")
|
|
70
|
+
tw: list[str] = Field(description="中国語(繁体字)での名称")
|
|
71
|
+
kr: list[str] = Field(description="韓国語での名称")
|
|
72
|
+
|
|
73
|
+
# 1. 言語名称モデル
|
|
74
|
+
class MultiLang(BaseModel):
|
|
75
|
+
"""
|
|
76
|
+
各言語での名称を保持するモデル。
|
|
77
|
+
"""
|
|
78
|
+
ja: str = Field(description="日本語での名称")
|
|
79
|
+
en: str = Field(description="英語での名称")
|
|
80
|
+
cn: str = Field(description="中国語(簡体字)での名称")
|
|
81
|
+
tw: str = Field(description="中国語(繁体字)での名称")
|
|
82
|
+
kr: str = Field(description="韓国語での名称")
|
|
83
|
+
|
|
84
|
+
# 2. 交通手段ごとの接続情報を表現するモデル
|
|
85
|
+
class StationConnections(BaseModel):
|
|
86
|
+
"""
|
|
87
|
+
駅で接続する交通手段(電車、地下鉄、モノレール)とその路線名を保持するモデル。
|
|
88
|
+
各交通手段は存在しない場合があるため、Optionalとして定義します。
|
|
89
|
+
"""
|
|
90
|
+
train: Optional[MultiLang_Lines] = None
|
|
91
|
+
subway: Optional[MultiLang_Lines] = None
|
|
92
|
+
monorail: Optional[MultiLang_Lines] = None
|
|
93
|
+
|
|
94
|
+
### 3. JSONのパーサー
|
|
95
|
+
class TransferGuideInfo(RootModel[dict[str, StationConnections]]):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
# 1. 駅名データ
|
|
99
|
+
class LineDetail(BaseModel):
|
|
100
|
+
lineName: MultiLang
|
|
101
|
+
stations: dict[str, MultiLang]
|
|
102
|
+
|
|
103
|
+
### 2. JSONのパーサー
|
|
104
|
+
class SelectStation(
|
|
105
|
+
RootModel[
|
|
106
|
+
dict[
|
|
107
|
+
LineLiteral,
|
|
108
|
+
LineDetail
|
|
109
|
+
]
|
|
110
|
+
]):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
# 1. trainInfoObjectsの要素を表現するモデル
|
|
114
|
+
|
|
115
|
+
class trainInfoObject(BaseModel):
|
|
116
|
+
"""
|
|
117
|
+
個々の電車の詳細情報を保持するモデル。
|
|
118
|
+
"""
|
|
119
|
+
wdfBlockNo: int
|
|
120
|
+
carsOfTrain: int
|
|
121
|
+
delayMinutes: str
|
|
122
|
+
delayMinutesEn: str
|
|
123
|
+
delayMinutesKo: str
|
|
124
|
+
delayMinutesZhCn: str
|
|
125
|
+
delayMinutesZhTw: str
|
|
126
|
+
destStationCode: int
|
|
127
|
+
destStationNameEn: str
|
|
128
|
+
destStationNameJp: str
|
|
129
|
+
destStationNameKo: str
|
|
130
|
+
destStationNameZhCn:str
|
|
131
|
+
destStationNameZhTw:str
|
|
132
|
+
destStationNumber: int
|
|
133
|
+
lastPassStation: int
|
|
134
|
+
trainNumber: str
|
|
135
|
+
trainTypeEn: str
|
|
136
|
+
trainTypeIcon: str
|
|
137
|
+
trainTypeJp: TrainType
|
|
138
|
+
is_special: bool
|
|
139
|
+
trainTypeKo: str
|
|
140
|
+
trainTypeZhCn: str
|
|
141
|
+
trainTypeZhTw: str
|
|
142
|
+
|
|
143
|
+
@model_validator(mode="before")
|
|
144
|
+
@classmethod
|
|
145
|
+
def check_type_special(cls, d:dict):
|
|
146
|
+
if "臨時" in d["trainTypeJp"]:
|
|
147
|
+
d["is_special"] = True
|
|
148
|
+
d["trainTypeJp"] = d["trainTypeJp"].replace("臨時 ", "")
|
|
149
|
+
else:
|
|
150
|
+
d["is_special"] = False
|
|
151
|
+
return d
|
|
152
|
+
|
|
153
|
+
# 2. locationObjectsの要素を表現するモデル
|
|
154
|
+
class LocationObject(BaseModel):
|
|
155
|
+
"""
|
|
156
|
+
路線図上での電車の位置と基本情報を保持するモデル。
|
|
157
|
+
"""
|
|
158
|
+
delay: str
|
|
159
|
+
delayEn: str
|
|
160
|
+
delayKo: str
|
|
161
|
+
delayZhCn: str
|
|
162
|
+
delayZhTw: str
|
|
163
|
+
locationCol: int
|
|
164
|
+
locationRow: int
|
|
165
|
+
trainDirection: int
|
|
166
|
+
trainIconTypeImageJp: str
|
|
167
|
+
trainInfoObjects: list[trainInfoObject]
|
|
168
|
+
trainTypeVisIconVis: str
|
|
169
|
+
|
|
170
|
+
### 3. JSONのパーサー
|
|
171
|
+
class trainPositionList(BaseModel):
|
|
172
|
+
fileCreatedTime: datetime.datetime
|
|
173
|
+
fileVersion: str
|
|
174
|
+
linkNum: str
|
|
175
|
+
locationObjects: list[LocationObject]
|
|
176
|
+
|
|
177
|
+
@field_validator("fileCreatedTime", mode="before")
|
|
178
|
+
def validate_time(cls,value) -> datetime.datetime:
|
|
179
|
+
return datetime.datetime.strptime(value,"%Y%m%d%H%M%S").replace(tzinfo=JST)
|
|
180
|
+
|
|
181
|
+
# 1.
|
|
182
|
+
class diaStationInfoObject(BaseModel):
|
|
183
|
+
stationNumber: str = Field(description="駅ナンバリング2桁+ホーム番号1桁 ")
|
|
184
|
+
stationDepTime: str = Field(description="00:00の形式。深夜は25時などで表す。出発駅の場合は-(ハイフン)、不明な場合は99:99。")
|
|
185
|
+
stationNameJp: str
|
|
186
|
+
stationNameEn: str
|
|
187
|
+
stationNameZhTw:str
|
|
188
|
+
stationNameZhCn:str
|
|
189
|
+
stationNameKo: str
|
|
190
|
+
|
|
191
|
+
class TrainInfo(BaseModel):
|
|
192
|
+
wdfBlockNo:int
|
|
193
|
+
extTrain:bool
|
|
194
|
+
premiumCar:int = Field(description="プレミアムカーがあるかどうか",)
|
|
195
|
+
trainCar:str = Field(description="車両番号")
|
|
196
|
+
diaStationInfoObjects:list[diaStationInfoObject]
|
|
197
|
+
|
|
198
|
+
### 3. JSONのパーサー
|
|
199
|
+
class startTimeList(BaseModel):
|
|
200
|
+
fileCreatedTime: datetime.datetime
|
|
201
|
+
fileVersion: str
|
|
202
|
+
TrainInfo: list[TrainInfo]
|
|
203
|
+
|
|
204
|
+
@field_validator("fileCreatedTime", mode="before")
|
|
205
|
+
def validate_time(cls,value) -> datetime.datetime:
|
|
206
|
+
return datetime.datetime.strptime(value,"%Y%m%d%H%M%S").replace(tzinfo=JST)
|
|
207
|
+
|
|
208
|
+
# FileList.xmlのモデル
|
|
209
|
+
class FileList(BaseModel):
|
|
210
|
+
time: datetime.datetime
|
|
211
|
+
traininfo: str
|
|
212
|
+
image_PC: str
|
|
213
|
+
image_SP: str
|
|
214
|
+
html_FP: str
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# ./tracker.py next_stop_station関数の探索用
|
|
2
|
+
# 路線の方面別の駅リスト
|
|
3
|
+
KYOBASHI_TO_DEMACHIYANAGI=[i for i in range(4,42+1)]
|
|
4
|
+
|
|
5
|
+
UJI_UP = [77,76,75,74,73,72,71,28]
|
|
6
|
+
KATANO_UP = [67,66,65,64,63,62,61,21]
|
|
7
|
+
HONNSEN_UP = [1,2,3] +KYOBASHI_TO_DEMACHIYANAGI
|
|
8
|
+
NAKANOSHIMA_UP = [54,53,52,51,3] +KYOBASHI_TO_DEMACHIYANAGI
|