@@ -16,24 +16,131 @@ TROLLEY_IMAGE_2X = TROLLEY_IMAGE_2X_ASSET.readall()
1616SEPTA_API = "https://www3.septa.org/api/TransitView/index.php?route=G1"
1717TROLLEY_STOPS = {"21481" : "Haverford Av & 63rd St" , "12196" : "Richmond St & Allegheny Av" , "650" : "Richmond St & Cumberland St" , "20984" : "Richmond St & Huntingdon St" , "21071" : "Girard Av & 26th St" , "30292" : "Girard Av & 34th St - MBFS" , "21035" : "Girard Av & 54th St" , "21100" : "Girard Av & Columbia Av" , "21081" : "Girard Av & Ridge Av" , "21005" : "Girard Av & 16th St" , "21021" : "Girard Av & 31st St" , "21061" : "Girard Av & 42nd St" , "21030" : "Girard Av & 49th St" , "21038" : "Girard Av & 57th St" , "20983" : "Richmond St & Lehigh Av" , "21033" : "Girard Av & 52nd St" , "21051" : "Girard Av & 56th St" , "21040" : "Girard Av & 60th St" , "20986" : "Girard Av & Berks St" , "21042" : "Haverford Av & 62nd St" , "649" : "Richmond St & Cumberland St" , "21086" : "Girard Av & 12th St" , "21070" : "Girard Av & 27th St" , "21018" : "Girard Av & 28th St" , "21022" : "Girard Av & 33rd St" , "20995" : "Girard Av & 4th St" , "21032" : "Girard Av & 51st St" , "21050" : "Girard Av & 57th St" , "21028" : "Girard Av & Belmont Av" , "24038" : "Frankford Av & Richmond St" , "21108" : "Richmond St & Lehigh Av" , "21016" : "Girard Av & 26th St" , "21069" : "Girard Av & 28th St" , "20993" : "Girard Av & 2nd St" , "21098" : "Girard Av & Frankford Av- FS" , "20978" : "Girard Av & Front St" , "30605" : "Girard Av & Merion Av" , "21105" : "Girard Av & Richmond St" , "31540" : "Frankford Av & Girard Av - FS" , "21110" : "Richmond St & Cambria St" , "21001" : "Girard Av & 11th St" , "21068" : "Girard Av & 29th St" , "30291" : "Girard Av & 34th St - MBFS" , "344" : "Girard Av & 40th St - FS" , "21101" : "Girard Av & Palmer St" , "20982" : "Richmond St & Cambria St" , "21113" : "Richmond St & Clearfield St" , "21075" : "College Av & 24th St - FS" , "21017" : "Girard Av & 27th St" , "21067" : "Girard Av & 31st St" , "21025" : "Girard Av & 39th St" , "21062" : "Girard Av & 41st St" , "21055" : "Girard Av & 52nd St" , "31443" : "Girard Av & 62nd St" , "21091" : "Girard Av & 7th St" , "21078" : "Girard Av & Corinthian Av" , "342" : "Girard Av & Front St" , "30550" : "Girard Av & Merion Av" , "21008" : "Girard Av & Ridge Av" , "21073" : "Poplar St & Stillman St" , "31347" : "Frankford Av & Delaware Av Loop" , "21087" : "Girard Av & 11th St" , "21006" : "Girard Av & 17th St" , "30290" : "Girard Av & 19th St" , "21009" : "Girard Av & 20th St" , "21063" : "Girard Av & 39th St" , "21044" : "Girard Av & 63rd St - MBFS" , "21072" : "26th St & Poplar St" , "30791" : "Girard Av & 24th St" , "349" : "Girard Av & 59th St" , "20996" : "Girard Av & 5th St" , "21093" : "Girard Av & 5th St" , "20998" : "Girard Av & 7th St" , "20999" : "Girard Av & 8th St" , "20979" : "Richmond St & Clearfield St" , "21114" : "Richmond St & Allegheny Av" , "21079" : "Girard Av & 20th St" , "21026" : "Girard Av & 41st St" , "21037" : "Girard Av & 56th St" , "345" : "Girard Av & 59th St" , "21090" : "Girard Av & 8th St" , "343" : "Girard Av & Broad St" , "352" : "Girard Av & Broad St" , "20989" : "Girard Av & Columbia Av" , "21014" : "Poplar St & 25th St" , "481" : "Frankford Av & Girard Av" , "21111" : "Richmond St & Ann St" , "21107" : "Richmond St & Huntingdon St" , "21082" : "Girard Av & 17th St" , "21080" : "Girard Av & 19th St" , "21053" : "Girard Av & 54th St" , "20981" : "Richmond St & Ann St" , "25779" : "Richmond St & Girard Av" , "21019" : "Girard Av & 29th St" , "21095" : "Girard Av & 3rd St" , "21048" : "Girard Av & 60th St" , "21103" : "Girard Av & Berks St" , "21058" : "Girard Av & Lancaster Av" , "12218" : "Richmond St & Somerset St" , "21041" : "Haverford Av & 61st St" , "21083" : "Girard Av & 16th St" , "21010" : "Girard Av & Corinthian Av" , "20988" : "Girard Av & Palmer St" , "341" : "Richmond St & Westmoreland St Loop" , "21002" : "Girard Av & 12th St" , "21096" : "Girard Av & 2nd St" , "20994" : "Girard Av & 3rd St" , "350" : "Girard Av & 40th St" , "23992" : "Frankford Av & Richmond St" , "21109" : "Richmond St & Somerset St" , "21027" : "Girard Av & 42nd St" , "21056" : "Girard Av & 51st St" , "21047" : "Girard Av & 61st St" , "21060" : "Girard Av & Belmont Av - FS" }
1818
19+ def get_trolley_frames (id , stop_name , direction , scale , img , id_font , text_font ):
20+ frames = []
21+
22+ # 70 frames total per trolley (7 seconds at 100ms delay)
23+ for i in range (70 ):
24+ # Resting position x=1, w=39 ends at 40. ID starts at 40.
25+ x , w , h = 1 , 39 , 14
26+ y_pos = 0
27+
28+ # Continuous Entrance Sequence
29+ if i < 30 :
30+ # Phase 1: Slow slide from off-screen left (-40 to 1)
31+ x = int (- 40.0 + (41.0 * i / 30.0 ))
32+ elif i == 30 :
33+ # Phase 2: Overshoot peak and Horizontal Squish (Overlap ID by 2 pixels)
34+ # x + w = 42. ID starts at 40.
35+ x = - 1
36+ w = 43
37+ elif i == 31 :
38+ # Phase 3: Recoil
39+ x = - 1
40+ w = 39
41+ elif i == 32 :
42+ # Phase 4: Settle
43+ x = 1
44+ w = 39
45+ else :
46+ # Phase 5: Rest
47+ x = 1
48+
49+ string = direction + " at " + stop_name .replace ("& " , "&\n " )
50+
51+ # Root-level Stack ensures no "peel reveal" and correct off-screen positioning
52+ frame = render .Stack (
53+ children = [
54+ # Vehicle ID - Stationary at x=40
55+ render .Padding (
56+ pad = (40 * scale , 1 * scale , 0 , 0 ),
57+ child = render .Text (content = id , font = id_font ),
58+ ),
59+ # The Trolley - Animated slide, overshoot, and squish
60+ render .Padding (
61+ pad = (x * scale , y_pos * scale , 0 , 0 ),
62+ child = render .Image (src = img , width = w * scale , height = h * scale ),
63+ ),
64+ # Destination text - placed at y=14 to ensure clearance for 3 lines
65+ render .Padding (
66+ pad = (0 , 14 * scale , 0 , 0 ),
67+ child = render .Box (
68+ width = 64 * scale ,
69+ height = 18 * scale ,
70+ child = render .WrappedText (string , font = text_font , align = "center" , width = 64 * scale ),
71+ ),
72+ ),
73+ ],
74+ )
75+ frames .append (frame )
76+ return frames
77+
78+ def get_empty_frames (scale , img , text_font ):
79+ frames = []
80+
81+ # Centering 39px image in 64px width: (64-39)/2 = 12.5 -> 12
82+ resting_x = 12
83+
84+ # 200 frames total (20 seconds at 100ms delay)
85+ for i in range (200 ):
86+ x = resting_x
87+
88+ if i < 40 : # Phase 1: Slow roll in from off-screen left (-40 to 12)
89+ x = int (- 40.0 + (resting_x + 40.0 ) * i / 40.0 )
90+ elif i < 60 : # Phase 2: Pause centered
91+ x = resting_x
92+ elif i < 100 : # Phase 3: Gentle Whimsical Shimmy (1-pixel shifts)
93+ # 0,0,0,1,1,1,0,0,0,-1,-1,-1...
94+ shimmy = [0 , 0 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , - 1 , - 1 , - 1 ] * 4
95+ x = resting_x + shimmy [i - 60 ]
96+ elif i < 120 : # Phase 4: Pause centered
97+ x = resting_x
98+ elif i < 160 : # Phase 5: Slow roll off to right edge (12 to 64)
99+ x = int (resting_x + (64.0 - resting_x ) * (i - 120 ) / 40.0 )
100+ elif i < 180 : # Phase 6: Dead air pause before repeat
101+ x = 64
102+ else : # Extra buffer
103+ x = 64
104+
105+ frame = render .Stack (
106+ children = [
107+ # Trolley rolling across the screen at y=0
108+ render .Padding (
109+ pad = (x * scale , 0 , 0 , 0 ),
110+ child = render .Image (src = img , width = 39 * scale , height = 14 * scale ),
111+ ),
112+ # Text centered at bottom (starts at y=14)
113+ render .Padding (
114+ pad = (0 , 14 * scale , 0 , 0 ),
115+ child = render .Box (
116+ width = 64 * scale ,
117+ height = 18 * scale ,
118+ child = render .WrappedText ("No trolleys spotted.\n Check later!" , font = text_font , align = "center" , width = 64 * scale ),
119+ ),
120+ ),
121+ ],
122+ )
123+ frames .append (frame )
124+ return frames
125+
19126def get_route_15 (scale ):
20127 """Get trolley information for Route 15 (G1) using next_stop_id
21128
22129 Returns:
23- List of trolley render objects
130+ List of trolley render objects (frames)
24131 """
25132 trolley_ids = ["2320" , "2321" , "2322" , "2323" , "2324" , "2325" , "2326" , "2327" , "2328" , "2329" , "2330" , "2331" , "2332" , "2333" , "2334" , "2335" , "2336" , "2337" ]
26- trolleys_found = []
133+ trolley_frames = []
27134
28135 img = TROLLEY_IMAGE_2X if scale == 2 else TROLLEY_IMAGE
29136
30137 r = http .get (SEPTA_API , ttl_seconds = 300 )
31138 if r == None or r .status_code != 200 :
32- return trolleys_found
139+ return trolley_frames
33140
34141 result = r .json ()
35142 if result == None or result .get ("bus" ) == None :
36- return trolleys_found
143+ return trolley_frames
37144
38145 for i in result .get ("bus" ):
39146 id = i .get ("VehicleID" )
@@ -52,47 +159,31 @@ def get_route_15(scale):
52159 direction = "Eastbound"
53160 else :
54161 direction = "Now"
55- string = direction
56- string += " at " + stop_name .replace ("& " , "&\n " )
57162
58163 id_font = "6x13" if scale == 1 else "terminus-18"
59164 text_font = "tom-thumb" if scale == 1 else "terminus-12"
60165
61- output = render .Column (
62- children = [
63- render .Row (
64- children = [
65- render .Box (width = 39 * scale , height = 14 * scale , child = render .Image (src = img )),
66- render .Padding (child = render .WrappedText (id , font = id_font ), pad = (1 * scale , 1 * scale , 0 , 0 )),
67- ],
68- ),
69- render .Padding (child = render .WrappedText (string , font = text_font , align = "center" , width = 64 * scale ), pad = (0 , 1 * scale , 0 , 0 )),
70- ],
71- )
72- trolleys_found .append (output )
73- return trolleys_found
166+ frames = get_trolley_frames (id , stop_name , direction , scale , img , id_font , text_font )
167+ trolley_frames .extend (frames )
168+
169+ return trolley_frames
74170
75171def main ():
76172 scale = 2 if canvas .is2x () else 1
77173 img = TROLLEY_IMAGE_2X if scale == 2 else TROLLEY_IMAGE
78- trolleys = get_route_15 (scale )
174+ trolley_frames = get_route_15 (scale )
175+
176+ text_font = "tom-thumb" if scale == 1 else "terminus-12"
79177
80- if len (trolleys ) == 0 :
178+ if len (trolley_frames ) == 0 :
179+ empty_frames = get_empty_frames (scale , img , text_font )
81180 return render .Root (
82- child = render .Column (
83- children = [
84- render .Row (
85- children = [
86- render .Box (width = 64 * scale , height = 14 * scale , child = render .Image (src = img )),
87- ],
88- ),
89- render .Padding (child = render .WrappedText ("No trolleys spotted.\n Check later!" , font = "tom-thumb" if scale == 1 else "terminus-12" , align = "center" , width = 64 * scale ), pad = (0 , 1 * scale , 0 , 0 )),
90- ],
91- ),
181+ child = render .Animation (children = empty_frames ),
182+ show_full_animation = True ,
92183 )
93184 else :
94185 return render .Root (
95- child = render .Animation (children = trolleys ),
96- delay = 5000 ,
186+ child = render .Animation (children = trolley_frames ),
187+ delay = 100 ,
97188 show_full_animation = True ,
98189 )
0 commit comments