initial commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
# Basic hardening + caching
|
||||||
|
|
||||||
|
Options -Indexes
|
||||||
|
|
||||||
|
<IfModule mod_headers.c>
|
||||||
|
Header set X-Content-Type-Options "nosniff"
|
||||||
|
Header set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header set Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresByType text/css "access plus 7 days"
|
||||||
|
ExpiresByType application/javascript "access plus 7 days"
|
||||||
|
ExpiresByType image/svg+xml "access plus 30 days"
|
||||||
|
ExpiresByType image/png "access plus 30 days"
|
||||||
|
</IfModule>
|
||||||
Vendored
+103574
File diff suppressed because it is too large
Load Diff
Vendored
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"source": "CelesTrak",
|
||||||
|
"group": "noaa",
|
||||||
|
"groupName": "NOAA",
|
||||||
|
"upstream": "https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle",
|
||||||
|
"cached": false,
|
||||||
|
"fetchedAtUtc": "2026-06-19 21:20:44",
|
||||||
|
"cacheTtlSeconds": 7200,
|
||||||
|
"count": 0,
|
||||||
|
"upstreamHttpCode": 200,
|
||||||
|
"serverMs": 319
|
||||||
|
},
|
||||||
|
"satellites": []
|
||||||
|
}
|
||||||
Vendored
+70849
File diff suppressed because it is too large
Load Diff
Vendored
+233
@@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"source": "CelesTrak",
|
||||||
|
"group": "stations",
|
||||||
|
"groupName": "Space Stations",
|
||||||
|
"upstream": "https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle",
|
||||||
|
"cached": false,
|
||||||
|
"fetchedAtUtc": "2026-03-24 22:51:44",
|
||||||
|
"cacheTtlSeconds": 7200,
|
||||||
|
"count": 31,
|
||||||
|
"upstreamHttpCode": 200,
|
||||||
|
"serverMs": 313
|
||||||
|
},
|
||||||
|
"satellites": [
|
||||||
|
{
|
||||||
|
"name": "ISS (ZARYA)",
|
||||||
|
"norad": "25544",
|
||||||
|
"line1": "1 25544U 98067A 26083.48628161 .00013391 00000+0 25488-3 0 9991",
|
||||||
|
"line2": "2 25544 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558623",
|
||||||
|
"epoch": "2026-03-24 11:40:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "POISK",
|
||||||
|
"norad": "36086",
|
||||||
|
"line1": "1 36086U 09060A 26083.48628161 .00013391 00000+0 25488-3 0 9999",
|
||||||
|
"line2": "2 36086 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558390",
|
||||||
|
"epoch": "2026-03-24 11:40:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CSS (TIANHE)",
|
||||||
|
"norad": "48274",
|
||||||
|
"line1": "1 48274U 21035A 26083.49395739 .00019397 00000+0 22400-3 0 9993",
|
||||||
|
"line2": "2 48274 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279921",
|
||||||
|
"epoch": "2026-03-24 11:51:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ISS (NAUKA)",
|
||||||
|
"norad": "49044",
|
||||||
|
"line1": "1 49044U 21066A 26083.48628161 .00013391 00000+0 25488-3 0 9997",
|
||||||
|
"line2": "2 49044 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558433",
|
||||||
|
"epoch": "2026-03-24 11:40:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FREGAT DEB",
|
||||||
|
"norad": "49271",
|
||||||
|
"line1": "1 49271U 11037PF 26083.51009843 .00011351 00000+0 16086-1 0 9996",
|
||||||
|
"line2": "2 49271 51.6508 256.2575 0962852 93.4027 277.7281 12.40658271214534",
|
||||||
|
"epoch": "2026-03-24 12:14:33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CSS (WENTIAN)",
|
||||||
|
"norad": "53239",
|
||||||
|
"line1": "1 53239U 22085A 26083.49395739 .00019397 00000+0 22400-3 0 9996",
|
||||||
|
"line2": "2 53239 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279995",
|
||||||
|
"epoch": "2026-03-24 11:51:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CSS (MENGTIAN)",
|
||||||
|
"norad": "54216",
|
||||||
|
"line1": "1 54216U 22143A 26083.49395739 .00019397 00000+0 22400-3 0 9997",
|
||||||
|
"line2": "2 54216 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279991",
|
||||||
|
"epoch": "2026-03-24 11:51:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANZHOU-9",
|
||||||
|
"norad": "64786",
|
||||||
|
"line1": "1 64786U 25149A 26083.49395739 .00019397 00000+0 22400-3 0 9999",
|
||||||
|
"line2": "2 64786 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279994",
|
||||||
|
"epoch": "2026-03-24 11:51:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PROGRESS-MS 32",
|
||||||
|
"norad": "65586",
|
||||||
|
"line1": "1 65586U 25204A 26083.16360592 .00014170 00000+0 26927-3 0 9995",
|
||||||
|
"line2": "2 65586 51.6345 0.8084 0006267 226.2093 133.8378 15.48514008554509",
|
||||||
|
"epoch": "2026-03-24 03:55:36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YOTSUBA-KULOVER",
|
||||||
|
"norad": "65941",
|
||||||
|
"line1": "1 65941U 98067XN 26082.96327371 .00332191 00000+0 11506-2 0 9994",
|
||||||
|
"line2": "2 65941 51.6197 342.8745 0005099 174.2801 185.8263 15.88031375 25769",
|
||||||
|
"epoch": "2026-03-23 23:07:07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BOTAN",
|
||||||
|
"norad": "65943",
|
||||||
|
"line1": "1 65943U 98067XQ 26082.97212654 .00308939 00000+0 11098-2 0 9993",
|
||||||
|
"line2": "2 65943 51.6228 342.9564 0005691 173.8079 186.2997 15.87345825 25762",
|
||||||
|
"epoch": "2026-03-23 23:19:52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HRC MONOBLOCK CAMERA",
|
||||||
|
"norad": "66052",
|
||||||
|
"line1": "1 66052U 98067XR 26083.46970303 .00045505 00000+0 47981-3 0 9990",
|
||||||
|
"line2": "2 66052 51.6287 350.8514 0003822 160.3996 199.7146 15.63768342 24700",
|
||||||
|
"epoch": "2026-03-24 11:16:22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HTV-X1",
|
||||||
|
"norad": "66174",
|
||||||
|
"line1": "1 66174U 25241A 26083.32282862 .00005545 00000+0 18606-3 0 9992",
|
||||||
|
"line2": "2 66174 51.6376 2.5775 0007466 227.6504 132.3845 15.32402906 23181",
|
||||||
|
"epoch": "2026-03-24 07:44:52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SZ-21 MODULE",
|
||||||
|
"norad": "66515",
|
||||||
|
"line1": "1 66515U 25246C 26083.45739579 .00033703 00000+0 27749-3 0 9997",
|
||||||
|
"line2": "2 66515 41.4738 112.3746 0000853 213.1562 146.9227 15.69668398 20398",
|
||||||
|
"epoch": "2026-03-24 10:58:39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHENZHOU-22",
|
||||||
|
"norad": "66645",
|
||||||
|
"line1": "1 66645U 25272A 26083.49395739 .00019397 00000+0 22400-3 0 9992",
|
||||||
|
"line2": "2 66645 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279990",
|
||||||
|
"epoch": "2026-03-24 11:51:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SOYUZ-MS 28",
|
||||||
|
"norad": "66664",
|
||||||
|
"line1": "1 66664U 25275A 26083.48628161 .00013391 00000+0 25488-3 0 9990",
|
||||||
|
"line2": "2 66664 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558676",
|
||||||
|
"epoch": "2026-03-24 11:40:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DUPLEX",
|
||||||
|
"norad": "66906",
|
||||||
|
"line1": "1 66906U 98067XS 26083.47964882 .00027087 00000+0 37156-3 0 9992",
|
||||||
|
"line2": "2 66906 51.6302 355.4219 0003163 176.3649 183.7366 15.57139447 17439",
|
||||||
|
"epoch": "2026-03-24 11:30:42"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ISS OBJECT XT",
|
||||||
|
"norad": "66907",
|
||||||
|
"line1": "1 66907U 98067XT 26083.46050153 .00185654 00000+0 12240-2 0 9990",
|
||||||
|
"line2": "2 66907 51.6252 350.0134 0005402 175.0560 185.0493 15.74515234 17502",
|
||||||
|
"epoch": "2026-03-24 11:03:07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ISS OBJECT XU",
|
||||||
|
"norad": "66908",
|
||||||
|
"line1": "1 66908U 98067XU 26083.45733407 .00188153 00000+0 12296-2 0 9998",
|
||||||
|
"line2": "2 66908 51.6249 349.9877 0005644 175.3702 184.7351 15.74708720 17502",
|
||||||
|
"epoch": "2026-03-24 10:58:34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SILVERSAT",
|
||||||
|
"norad": "66909",
|
||||||
|
"line1": "1 66909U 98067XV 26082.98164823 .00288378 00000+0 15389-2 0 9992",
|
||||||
|
"line2": "2 66909 51.6221 351.3039 0006229 177.1516 182.9521 15.78994089 17444",
|
||||||
|
"epoch": "2026-03-23 23:33:34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ISS OBJECT XW",
|
||||||
|
"norad": "66910",
|
||||||
|
"line1": "1 66910U 98067XW 26083.46394234 .00165160 00000+0 11502-2 0 9995",
|
||||||
|
"line2": "2 66910 51.6240 350.0286 0005904 174.9033 185.2027 15.73305424 17503",
|
||||||
|
"epoch": "2026-03-24 11:08:05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ISS OBJECT XX",
|
||||||
|
"norad": "66911",
|
||||||
|
"line1": "1 66911U 98067XX 26083.44596692 .00391459 00000+0 15820-2 0 9995",
|
||||||
|
"line2": "2 66911 51.6199 347.6223 0007866 183.5979 176.4969 15.84695682 17523",
|
||||||
|
"epoch": "2026-03-24 10:42:12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ISS OBJECT XY",
|
||||||
|
"norad": "66912",
|
||||||
|
"line1": "1 66912U 98067XY 26083.45850020 .00094960 00000+0 88169-3 0 9999",
|
||||||
|
"line2": "2 66912 51.6275 352.2703 0004026 174.2260 185.8783 15.66654645 17464",
|
||||||
|
"epoch": "2026-03-24 11:00:14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "KNACKSAT-2",
|
||||||
|
"norad": "67683",
|
||||||
|
"line1": "1 67683U 98067XZ 26083.01837906 .00038683 00000+0 61191-3 0 9991",
|
||||||
|
"line2": "2 67683 51.6326 0.5913 0009822 237.4563 122.5479 15.53013062 7018",
|
||||||
|
"epoch": "2026-03-24 00:26:28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CORAL",
|
||||||
|
"norad": "67684",
|
||||||
|
"line1": "1 67684U 98067YA 26083.47622175 .00078164 00000+0 10629-2 0 9999",
|
||||||
|
"line2": "2 67684 51.6305 357.6492 0012273 228.4697 131.5242 15.56803499 7096",
|
||||||
|
"epoch": "2026-03-24 11:25:46"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GXIBA-1",
|
||||||
|
"norad": "67685",
|
||||||
|
"line1": "1 67685U 98067YB 26082.94566353 .00047226 00000+0 73089-3 0 9991",
|
||||||
|
"line2": "2 67685 51.6338 0.8611 0011822 234.4096 125.5793 15.53503062 7007",
|
||||||
|
"epoch": "2026-03-23 22:41:45"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "UITMSAT-2",
|
||||||
|
"norad": "67686",
|
||||||
|
"line1": "1 67686U 98067YC 26083.06244469 .00055892 00000+0 83755-3 0 9996",
|
||||||
|
"line2": "2 67686 51.6326 0.1380 0008812 205.0636 154.9928 15.54352229 7026",
|
||||||
|
"epoch": "2026-03-24 01:29:55"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HMU-SAT2",
|
||||||
|
"norad": "67687",
|
||||||
|
"line1": "1 67687U 98067YD 26083.01414055 .00041357 00000+0 64805-3 0 9998",
|
||||||
|
"line2": "2 67687 51.6328 0.5652 0008396 207.9190 152.1351 15.53258522 6937",
|
||||||
|
"epoch": "2026-03-24 00:20:22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LEOPARD",
|
||||||
|
"norad": "67688",
|
||||||
|
"line1": "1 67688U 98067YE 26083.06786206 .00050002 00000+0 76118-3 0 9991",
|
||||||
|
"line2": "2 67688 51.6323 0.1737 0008646 205.7190 154.3372 15.53969684 6943",
|
||||||
|
"epoch": "2026-03-24 01:37:43"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CREW DRAGON 12",
|
||||||
|
"norad": "67796",
|
||||||
|
"line1": "1 67796U 26031A 26083.48628161 .00013391 00000+0 25488-3 0 9998",
|
||||||
|
"line2": "2 67796 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558550",
|
||||||
|
"epoch": "2026-03-24 11:40:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PROGRESS-MS 33",
|
||||||
|
"norad": "68319",
|
||||||
|
"line1": "1 68319U 26058A 26082.05109224 .00200487 28256-4 35870-3 0 9993",
|
||||||
|
"line2": "2 68319 51.6358 6.8873 0011621 152.2903 207.8729 16.01170532 69",
|
||||||
|
"epoch": "2026-03-23 01:13:34"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+534
@@ -0,0 +1,534 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"source": "CelesTrak",
|
||||||
|
"group": "weather",
|
||||||
|
"groupName": "Weather",
|
||||||
|
"upstream": "https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle",
|
||||||
|
"cached": false,
|
||||||
|
"fetchedAtUtc": "2026-06-22 15:29:21",
|
||||||
|
"cacheTtlSeconds": 7200,
|
||||||
|
"count": 74,
|
||||||
|
"upstreamHttpCode": 200,
|
||||||
|
"serverMs": 355
|
||||||
|
},
|
||||||
|
"satellites": [
|
||||||
|
{
|
||||||
|
"name": "DMSP 5D-3 F16 (USA 172)",
|
||||||
|
"norad": "28054",
|
||||||
|
"line1": "1 28054U 03048A 26171.42689955 -.00000038 00000+0 37443-5 0 9996",
|
||||||
|
"line2": "2 28054 98.9889 195.2768 0007887 132.0538 331.9039 14.14483136169944",
|
||||||
|
"epoch": "2026-06-20 10:14:44"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOSAT-9 (MSG-2)",
|
||||||
|
"norad": "28912",
|
||||||
|
"line1": "1 28912U 05049B 26170.92883707 .00000115 00000+0 00000+0 0 9995",
|
||||||
|
"line2": "2 28912 9.3683 54.1432 0001774 346.4633 247.5699 1.00273646 7020",
|
||||||
|
"epoch": "2026-06-19 22:17:32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DMSP 5D-3 F17 (USA 191)",
|
||||||
|
"norad": "29522",
|
||||||
|
"line1": "1 29522U 06050A 26171.42241591 -.00000013 00000+0 15609-4 0 9995",
|
||||||
|
"line2": "2 29522 98.7378 177.9264 0011060 51.0273 309.1884 14.14993196 12885",
|
||||||
|
"epoch": "2026-06-20 10:08:17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3A",
|
||||||
|
"norad": "32958",
|
||||||
|
"line1": "1 32958U 08026A 26171.43054098 .00000055 00000+0 46755-4 0 9993",
|
||||||
|
"line2": "2 32958 98.6883 112.2031 0009703 115.2254 244.9929 14.19571712935868",
|
||||||
|
"epoch": "2026-06-20 10:19:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GOES 14",
|
||||||
|
"norad": "35491",
|
||||||
|
"line1": "1 35491U 09033A 26171.34840896 -.00000308 00000+0 00000+0 0 9996",
|
||||||
|
"line2": "2 35491 1.6993 83.0080 0001921 13.0479 77.9267 1.00110067 6626",
|
||||||
|
"epoch": "2026-06-20 08:21:43"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DMSP 5D-3 F18 (USA 210)",
|
||||||
|
"norad": "35951",
|
||||||
|
"line1": "1 35951U 09057A 26171.39400846 .00000210 00000+0 13184-3 0 9997",
|
||||||
|
"line2": "2 35951 98.9038 152.1561 0009964 239.2588 120.7604 14.14898526860044",
|
||||||
|
"epoch": "2026-06-20 09:27:22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EWS-G2 (GOES 15)",
|
||||||
|
"norad": "36411",
|
||||||
|
"line1": "1 36411U 10008A 26171.11943681 .00000015 00000+0 00000+0 0 9998",
|
||||||
|
"line2": "2 36411 1.2866 82.8036 0004924 42.0631 250.1257 1.00266729 59663",
|
||||||
|
"epoch": "2026-06-20 02:51:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "COMS 1",
|
||||||
|
"norad": "36744",
|
||||||
|
"line1": "1 36744U 10032A 26171.12316773 .00000087 00000+0 00000+0 0 9990",
|
||||||
|
"line2": "2 36744 4.8136 75.3217 0011650 12.3209 286.7612 0.99163259 51117",
|
||||||
|
"epoch": "2026-06-20 02:57:22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3B",
|
||||||
|
"norad": "37214",
|
||||||
|
"line1": "1 37214U 10059A 26172.56789564 .00000611 00000+0 33905-3 0 9990",
|
||||||
|
"line2": "2 37214 98.9613 218.6016 0022188 171.6777 356.3576 14.14844553808040",
|
||||||
|
"epoch": "2026-06-21 13:37:46"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SUOMI NPP",
|
||||||
|
"norad": "37849",
|
||||||
|
"line1": "1 37849U 11061A 26171.44192975 .00000061 00000+0 50150-4 0 9999",
|
||||||
|
"line2": "2 37849 98.7953 112.2958 0001348 359.0198 1.0975 14.19517985758904",
|
||||||
|
"epoch": "2026-06-20 10:36:23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOSAT-10 (MSG-3)",
|
||||||
|
"norad": "38552",
|
||||||
|
"line1": "1 38552U 12035B 26171.03561044 -.00000013 00000+0 00000+0 0 9997",
|
||||||
|
"line2": "2 38552 4.6793 61.0121 0001484 2.4454 217.8971 1.00266543 50884",
|
||||||
|
"epoch": "2026-06-20 00:51:17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METOP-B",
|
||||||
|
"norad": "38771",
|
||||||
|
"line1": "1 38771U 12049A 26171.42762080 .00000060 00000+0 47478-4 0 9997",
|
||||||
|
"line2": "2 38771 98.6503 222.8621 0002032 10.6441 349.4779 14.21440128713725",
|
||||||
|
"epoch": "2026-06-20 10:15:46"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "INSAT-3D",
|
||||||
|
"norad": "39216",
|
||||||
|
"line1": "1 39216U 13038B 26171.27504292 -.00000345 00000+0 00000+0 0 9995",
|
||||||
|
"line2": "2 39216 1.9351 82.5055 0000638 326.5441 87.9271 1.00267986 47115",
|
||||||
|
"epoch": "2026-06-20 06:36:04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3C",
|
||||||
|
"norad": "39260",
|
||||||
|
"line1": "1 39260U 13052A 26172.49624285 .00000036 00000+0 39296-4 0 9990",
|
||||||
|
"line2": "2 39260 98.4988 146.1733 0015000 15.7162 344.4476 14.15755814659172",
|
||||||
|
"epoch": "2026-06-21 11:54:35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOR-M 2",
|
||||||
|
"norad": "40069",
|
||||||
|
"line1": "1 40069U 14037A 26172.50978403 -.00000166 00000+0 -56554-4 0 9991",
|
||||||
|
"line2": "2 40069 98.5120 148.0953 0006595 33.6763 326.4832 14.21461368619931",
|
||||||
|
"epoch": "2026-06-21 12:14:05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HIMAWARI-8",
|
||||||
|
"norad": "40267",
|
||||||
|
"line1": "1 40267U 14060A 26171.46088416 -.00000280 00000+0 00000+0 0 9993",
|
||||||
|
"line2": "2 40267 0.0187 51.4118 0001168 42.2924 121.5465 1.00270806 42796",
|
||||||
|
"epoch": "2026-06-20 11:03:40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 2G",
|
||||||
|
"norad": "40367",
|
||||||
|
"line1": "1 40367U 14090A 26172.16642169 -.00000306 00000+0 00000+0 0 9995",
|
||||||
|
"line2": "2 40367 5.6502 72.7719 0003712 157.9383 198.2472 1.00270539 42031",
|
||||||
|
"epoch": "2026-06-21 03:59:39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOSAT-11 (MSG-4)",
|
||||||
|
"norad": "40732",
|
||||||
|
"line1": "1 40732U 15034A 26171.32244802 .00000057 00000+0 00000+0 0 9991",
|
||||||
|
"line2": "2 40732 3.2087 71.0883 0002400 346.3671 336.7454 1.00264042 7245",
|
||||||
|
"epoch": "2026-06-20 07:44:20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ELEKTRO-L 2",
|
||||||
|
"norad": "41105",
|
||||||
|
"line1": "1 41105U 15074A 26169.03651196 -.00000126 00000+0 00000+0 0 9991",
|
||||||
|
"line2": "2 41105 6.6975 70.5132 0000902 256.1704 298.2263 1.00270556 38515",
|
||||||
|
"epoch": "2026-06-18 00:52:35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SENTINEL-3A",
|
||||||
|
"norad": "41335",
|
||||||
|
"line1": "1 41335U 16011A 26171.44253080 .00000048 00000+0 37722-4 0 9994",
|
||||||
|
"line2": "2 41335 98.6248 238.7795 0001418 99.3701 260.7640 14.26733412538520",
|
||||||
|
"epoch": "2026-06-20 10:37:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "INSAT-3DR",
|
||||||
|
"norad": "41752",
|
||||||
|
"line1": "1 41752U 16054A 26171.40667204 -.00000081 00000+0 00000+0 0 9999",
|
||||||
|
"line2": "2 41752 0.1148 86.2468 0010425 187.4441 215.2920 1.00271300 35856",
|
||||||
|
"epoch": "2026-06-20 09:45:36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HIMAWARI-9",
|
||||||
|
"norad": "41836",
|
||||||
|
"line1": "1 41836U 16064A 26171.46088412 .00000000 00000+0 00000+0 0 9997",
|
||||||
|
"line2": "2 41836 0.0140 351.6731 0000932 111.3358 112.3453 1.00270283 35247",
|
||||||
|
"epoch": "2026-06-20 11:03:40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GOES 16",
|
||||||
|
"norad": "41866",
|
||||||
|
"line1": "1 41866U 16071A 26171.35461541 -.00000095 00000+0 00000+0 0 9999",
|
||||||
|
"line2": "2 41866 0.3504 85.5238 0000306 339.5740 226.4328 1.00271436 35133",
|
||||||
|
"epoch": "2026-06-20 08:30:39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 4A",
|
||||||
|
"norad": "41882",
|
||||||
|
"line1": "1 41882U 16077A 26172.55715684 -.00000359 00000+0 00000+0 0 9999",
|
||||||
|
"line2": "2 41882 2.3350 80.2401 0008569 153.4835 0.2982 1.00266450 34978",
|
||||||
|
"epoch": "2026-06-21 13:22:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CYGFM05",
|
||||||
|
"norad": "41884",
|
||||||
|
"line1": "1 41884U 16078A 26171.32171544 .00019452 00000+0 26251-3 0 9990",
|
||||||
|
"line2": "2 41884 34.9559 239.8312 0007362 181.9302 178.1395 15.56789762529244",
|
||||||
|
"epoch": "2026-06-20 07:43:16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CYGFM04",
|
||||||
|
"norad": "41885",
|
||||||
|
"line1": "1 41885U 16078B 26171.41040240 .00025123 00000+0 28119-3 0 9996",
|
||||||
|
"line2": "2 41885 34.9311 206.7708 0007079 223.5840 136.4326 15.61665288529500",
|
||||||
|
"epoch": "2026-06-20 09:50:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CYGFM02",
|
||||||
|
"norad": "41886",
|
||||||
|
"line1": "1 41886U 16078C 26171.40489205 .00028987 00000+0 28782-3 0 9991",
|
||||||
|
"line2": "2 41886 34.9392 201.2489 0008091 224.9077 135.0995 15.64678561529674",
|
||||||
|
"epoch": "2026-06-20 09:43:03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CYGFM01",
|
||||||
|
"norad": "41887",
|
||||||
|
"line1": "1 41887U 16078D 26171.30905894 .00020338 00000+0 26751-3 0 9994",
|
||||||
|
"line2": "2 41887 34.9389 240.9472 0007062 175.8238 184.2546 15.57471223529196",
|
||||||
|
"epoch": "2026-06-20 07:25:03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CYGFM08",
|
||||||
|
"norad": "41888",
|
||||||
|
"line1": "1 41888U 16078E 26171.45513382 .00022398 00000+0 26567-3 0 9991",
|
||||||
|
"line2": "2 41888 34.9419 219.4708 0008738 207.6434 152.3827 15.60162461529482",
|
||||||
|
"epoch": "2026-06-20 10:55:24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CYGFM07",
|
||||||
|
"norad": "41890",
|
||||||
|
"line1": "1 41890U 16078G 26171.41407463 .00022736 00000+0 26880-3 0 9996",
|
||||||
|
"line2": "2 41890 34.9433 208.0826 0007489 222.6573 137.3571 15.60258013529500",
|
||||||
|
"epoch": "2026-06-20 09:56:16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CYGFM03",
|
||||||
|
"norad": "41891",
|
||||||
|
"line1": "1 41891U 16078H 26171.44907869 .00021797 00000+0 25786-3 0 9995",
|
||||||
|
"line2": "2 41891 34.9390 209.7861 0006918 211.7965 148.2343 15.60252937529484",
|
||||||
|
"epoch": "2026-06-20 10:46:40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3D",
|
||||||
|
"norad": "43010",
|
||||||
|
"line1": "1 43010U 17072A 26172.51679571 -.00000158 00000+0 -52975-4 0 9991",
|
||||||
|
"line2": "2 43010 99.0133 147.0175 0002490 55.5625 304.5785 14.19739395445605",
|
||||||
|
"epoch": "2026-06-21 12:24:11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NOAA 20 (JPSS-1)",
|
||||||
|
"norad": "43013",
|
||||||
|
"line1": "1 43013U 17073A 26171.38467992 .00000046 00000+0 42637-4 0 9999",
|
||||||
|
"line2": "2 43013 98.7771 110.8189 0000383 117.6624 242.4591 14.19512290444925",
|
||||||
|
"epoch": "2026-06-20 09:13:56"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GOES 17",
|
||||||
|
"norad": "43226",
|
||||||
|
"line1": "1 43226U 18022A 26171.38933449 -.00000187 00000+0 00000+0 0 9995",
|
||||||
|
"line2": "2 43226 0.9463 85.6559 0000598 28.1563 205.4138 1.00269480 30451",
|
||||||
|
"epoch": "2026-06-20 09:20:38"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SENTINEL-3B",
|
||||||
|
"norad": "43437",
|
||||||
|
"line1": "1 43437U 18039A 26171.41568603 .00000074 00000+0 48499-4 0 9990",
|
||||||
|
"line2": "2 43437 98.6248 238.9017 0000727 94.6800 265.4463 14.26735540424583",
|
||||||
|
"epoch": "2026-06-20 09:58:35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 2H",
|
||||||
|
"norad": "43491",
|
||||||
|
"line1": "1 43491U 18050A 26172.54842275 -.00000131 00000+0 00000+0 0 9994",
|
||||||
|
"line2": "2 43491 3.0437 79.0286 0001805 136.1614 331.3565 1.00269411 29481",
|
||||||
|
"epoch": "2026-06-21 13:09:44"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METOP-C",
|
||||||
|
"norad": "43689",
|
||||||
|
"line1": "1 43689U 18087A 26171.44664144 .00000064 00000+0 49212-4 0 9995",
|
||||||
|
"line2": "2 43689 98.6643 230.9907 0001880 125.1991 234.9363 14.21512882395295",
|
||||||
|
"epoch": "2026-06-20 10:43:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GEO-KOMPSAT-2A",
|
||||||
|
"norad": "43823",
|
||||||
|
"line1": "1 43823U 18100A 26171.41741235 -.00000348 00000+0 00000+0 0 9998",
|
||||||
|
"line2": "2 43823 0.0473 151.2642 0002170 295.2838 100.6088 1.00272261 27669",
|
||||||
|
"epoch": "2026-06-20 10:01:04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOR-M2 2",
|
||||||
|
"norad": "44387",
|
||||||
|
"line1": "1 44387U 19038A 26172.52968670 .00000005 00000+0 22237-4 0 9992",
|
||||||
|
"line2": "2 44387 98.9151 159.8796 0001904 41.8346 318.2978 14.24366528361915",
|
||||||
|
"epoch": "2026-06-21 12:42:45"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ARKTIKA-M 1",
|
||||||
|
"norad": "47719",
|
||||||
|
"line1": "1 47719U 21016A 26168.49069990 -.00000210 00000+0 00000+0 0 9999",
|
||||||
|
"line2": "2 47719 63.2922 57.5948 7288528 269.9420 14.6012 2.00622504 38796",
|
||||||
|
"epoch": "2026-06-17 11:46:36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3E",
|
||||||
|
"norad": "49008",
|
||||||
|
"line1": "1 49008U 21062A 26172.50017913 .00000183 00000+0 10675-3 0 9999",
|
||||||
|
"line2": "2 49008 98.7495 175.8605 0002823 78.9678 281.1816 14.19904829257195",
|
||||||
|
"epoch": "2026-06-21 12:00:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GOES 18",
|
||||||
|
"norad": "51850",
|
||||||
|
"line1": "1 51850U 22021A 26170.93705689 .00000091 00000+0 00000+0 0 9997",
|
||||||
|
"line2": "2 51850 0.0372 171.7340 0000425 325.4670 331.2805 1.00271950 6625",
|
||||||
|
"epoch": "2026-06-19 22:29:22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NOAA 21 (JPSS-2)",
|
||||||
|
"norad": "54234",
|
||||||
|
"line1": "1 54234U 22150A 26171.41746469 .00000044 00000+0 41590-4 0 9996",
|
||||||
|
"line2": "2 54234 98.7059 110.1525 0001836 147.2077 212.9214 14.19565986187000",
|
||||||
|
"epoch": "2026-06-20 10:01:09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOSAT-12 (MTG-I1)",
|
||||||
|
"norad": "54743",
|
||||||
|
"line1": "1 54743U 22170C 26171.24937015 -.00000013 00000+0 00000+0 0 9996",
|
||||||
|
"line2": "2 54743 0.7421 23.5307 0003320 70.9892 263.3203 1.00273524 13009",
|
||||||
|
"epoch": "2026-06-20 05:59:06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 03",
|
||||||
|
"norad": "55973",
|
||||||
|
"line1": "1 55973U 23039A 26172.51236689 .00012760 00000+0 25107-3 0 9997",
|
||||||
|
"line2": "2 55973 97.5243 346.6508 0004082 88.7124 271.4593 15.47392330181500",
|
||||||
|
"epoch": "2026-06-21 12:17:48"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 04",
|
||||||
|
"norad": "55974",
|
||||||
|
"line1": "1 55974U 23039B 26172.50017186 .00013293 00000+0 25608-3 0 9994",
|
||||||
|
"line2": "2 55974 97.5177 345.7923 0004718 83.9821 276.1966 15.47992980181519",
|
||||||
|
"epoch": "2026-06-21 12:00:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 05",
|
||||||
|
"norad": "55975",
|
||||||
|
"line1": "1 55975U 23039C 26172.49333985 .00012617 00000+0 24893-3 0 9995",
|
||||||
|
"line2": "2 55975 97.5221 346.2447 0004106 104.9703 255.2001 15.47316256181481",
|
||||||
|
"epoch": "2026-06-21 11:50:25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 06",
|
||||||
|
"norad": "55976",
|
||||||
|
"line1": "1 55976U 23039D 26172.50381902 .00013184 00000+0 25487-3 0 9990",
|
||||||
|
"line2": "2 55976 97.5229 346.8955 0004324 99.6322 260.5416 15.47896063181511",
|
||||||
|
"epoch": "2026-06-21 12:05:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3G",
|
||||||
|
"norad": "56232",
|
||||||
|
"line1": "1 56232U 23055A 26172.48258118 .00019904 00000+0 32143-3 0 9996",
|
||||||
|
"line2": "2 56232 49.9935 323.8862 0007121 314.2701 45.7681 15.52798254180671",
|
||||||
|
"epoch": "2026-06-21 11:34:55"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOR-M2 3",
|
||||||
|
"norad": "57166",
|
||||||
|
"line1": "1 57166U 23091A 26172.52499583 -.00000006 00000+0 16221-4 0 9995",
|
||||||
|
"line2": "2 57166 98.6080 227.4146 0004589 144.4775 215.6710 14.24047159155136",
|
||||||
|
"epoch": "2026-06-21 12:36:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 07",
|
||||||
|
"norad": "57399",
|
||||||
|
"line1": "1 57399U 23101A 26172.48254884 .00004853 00000+0 17043-3 0 9998",
|
||||||
|
"line2": "2 57399 97.2589 223.0524 0004274 112.8221 247.3472 15.29848940162308",
|
||||||
|
"epoch": "2026-06-21 11:34:52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 08",
|
||||||
|
"norad": "57400",
|
||||||
|
"line1": "1 57400U 23101B 26172.51458675 .00005094 00000+0 17870-3 0 9995",
|
||||||
|
"line2": "2 57400 97.2578 222.8538 0005687 104.7184 255.4688 15.29854432162300",
|
||||||
|
"epoch": "2026-06-21 12:21:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 09",
|
||||||
|
"norad": "57401",
|
||||||
|
"line1": "1 57401U 23101C 26172.49791285 .00005141 00000+0 18018-3 0 9998",
|
||||||
|
"line2": "2 57401 97.2536 222.2510 0004785 117.1207 243.0522 15.29886210162300",
|
||||||
|
"epoch": "2026-06-21 11:57:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 10",
|
||||||
|
"norad": "57402",
|
||||||
|
"line1": "1 57402U 23101D 26172.46563515 .00005026 00000+0 17632-3 0 9992",
|
||||||
|
"line2": "2 57402 97.2615 223.2743 0006346 115.1029 245.0871 15.29857439162291",
|
||||||
|
"epoch": "2026-06-21 11:10:31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3F",
|
||||||
|
"norad": "57490",
|
||||||
|
"line1": "1 57490U 23111A 26172.48399582 .00000011 00000+0 25698-4 0 9994",
|
||||||
|
"line2": "2 57490 98.6835 242.4127 0001582 90.3874 269.7484 14.19939702149475",
|
||||||
|
"epoch": "2026-06-21 11:36:57"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ARKTIKA-M 2",
|
||||||
|
"norad": "58584",
|
||||||
|
"line1": "1 58584U 23198A 26168.75920439 .00000145 00000+0 00000+0 0 9993",
|
||||||
|
"line2": "2 58584 63.2243 154.2920 6902344 267.4286 18.7609 2.00612717 18330",
|
||||||
|
"epoch": "2026-06-17 18:13:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 11",
|
||||||
|
"norad": "58645",
|
||||||
|
"line1": "1 58645U 23205A 26172.50950514 .00004206 00000+0 16121-3 0 9992",
|
||||||
|
"line2": "2 58645 97.3448 193.9985 0008423 2.1524 357.9750 15.27032570138189",
|
||||||
|
"epoch": "2026-06-21 12:13:41"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 12",
|
||||||
|
"norad": "58646",
|
||||||
|
"line1": "1 58646U 23205B 26172.54550061 .00004499 00000+0 17246-3 0 9993",
|
||||||
|
"line2": "2 58646 97.3438 193.7888 0006195 347.6663 12.4424 15.26998880138178",
|
||||||
|
"epoch": "2026-06-21 13:05:31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 13",
|
||||||
|
"norad": "58647",
|
||||||
|
"line1": "1 58647U 23205C 26172.51256397 .00004285 00000+0 16517-3 0 9999",
|
||||||
|
"line2": "2 58647 97.3464 194.0296 0005433 323.2905 36.7961 15.26851187138160",
|
||||||
|
"epoch": "2026-06-21 12:18:06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 14",
|
||||||
|
"norad": "58648",
|
||||||
|
"line1": "1 58648U 23205D 26172.49764391 .00004278 00000+0 16465-3 0 9999",
|
||||||
|
"line2": "2 58648 97.3401 193.1933 0006382 328.1981 31.8872 15.26895930138163",
|
||||||
|
"epoch": "2026-06-21 11:56:36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 19",
|
||||||
|
"norad": "58660",
|
||||||
|
"line1": "1 58660U 23208A 26172.40296416 .00006532 00000+0 21646-3 0 9997",
|
||||||
|
"line2": "2 58660 97.4505 288.7245 0003546 308.1636 51.9285 15.31627133137977",
|
||||||
|
"epoch": "2026-06-21 09:40:16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 20",
|
||||||
|
"norad": "58661",
|
||||||
|
"line1": "1 58661U 23208B 26172.56804883 .00006296 00000+0 20107-3 0 9996",
|
||||||
|
"line2": "2 58661 97.4447 289.5303 0005732 315.3365 44.7415 15.32819357138096",
|
||||||
|
"epoch": "2026-06-21 13:37:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 21",
|
||||||
|
"norad": "58662",
|
||||||
|
"line1": "1 58662U 23208C 26172.48460245 .00007912 00000+0 23428-3 0 9993",
|
||||||
|
"line2": "2 58662 97.4507 292.1457 0005737 305.0266 55.0438 15.35141113138221",
|
||||||
|
"epoch": "2026-06-21 11:37:50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 22",
|
||||||
|
"norad": "58663",
|
||||||
|
"line1": "1 58663U 23208D 26172.51689759 .00005940 00000+0 20026-3 0 9991",
|
||||||
|
"line2": "2 58663 97.4501 288.7713 0002791 346.8495 13.2672 15.31108395137996",
|
||||||
|
"epoch": "2026-06-21 12:24:20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 15",
|
||||||
|
"norad": "58700",
|
||||||
|
"line1": "1 58700U 24004A 26172.55454425 .00004449 00000+0 17035-3 0 9991",
|
||||||
|
"line2": "2 58700 97.4674 358.7457 0006262 346.3791 13.7278 15.27060875136444",
|
||||||
|
"epoch": "2026-06-21 13:18:33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 16",
|
||||||
|
"norad": "58701",
|
||||||
|
"line1": "1 58701U 24004B 26172.54456803 .00004530 00000+0 17484-3 0 9992",
|
||||||
|
"line2": "2 58701 97.4622 357.9677 0005379 352.3663 7.7493 15.26785843136446",
|
||||||
|
"epoch": "2026-06-21 13:04:11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 17",
|
||||||
|
"norad": "58702",
|
||||||
|
"line1": "1 58702U 24004C 26172.53002447 .00004662 00000+0 17987-3 0 9997",
|
||||||
|
"line2": "2 58702 97.4648 358.4398 0005070 359.4602 0.6630 15.26784809136435",
|
||||||
|
"epoch": "2026-06-21 12:43:14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIANMU-1 18",
|
||||||
|
"norad": "58703",
|
||||||
|
"line1": "1 58703U 24004D 26172.50568463 .00004179 00000+0 16032-3 0 9993",
|
||||||
|
"line2": "2 58703 97.4666 358.6735 0003609 0.4275 359.6966 15.27041126136436",
|
||||||
|
"epoch": "2026-06-21 12:08:11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "INSAT-3DS",
|
||||||
|
"norad": "58990",
|
||||||
|
"line1": "1 58990U 24033A 26171.40823205 -.00000160 00000+0 00000+0 0 9990",
|
||||||
|
"line2": "2 58990 0.0479 106.3313 0001171 331.3573 59.9873 1.00271082 47109",
|
||||||
|
"epoch": "2026-06-20 09:47:51"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METEOR-M2 4",
|
||||||
|
"norad": "59051",
|
||||||
|
"line1": "1 59051U 24039A 26172.54463940 .00000004 00000+0 21415-4 0 9991",
|
||||||
|
"line2": "2 59051 98.7012 131.7880 0007535 149.0442 211.1182 14.22429890119885",
|
||||||
|
"epoch": "2026-06-21 13:04:17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GOES 19",
|
||||||
|
"norad": "60133",
|
||||||
|
"line1": "1 60133U 24119A 26171.37127980 -.00000253 00000+0 00000+0 0 9993",
|
||||||
|
"line2": "2 60133 0.0015 112.2371 0000407 49.5773 165.2311 1.00270517 6990",
|
||||||
|
"epoch": "2026-06-20 08:54:39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MTG-S1",
|
||||||
|
"norad": "64723",
|
||||||
|
"line1": "1 64723U 25143A 26171.39155049 -.00000005 00000+0 00000+0 0 9994",
|
||||||
|
"line2": "2 64723 0.6001 339.7327 0000829 294.1194 133.2187 1.00399694 3452",
|
||||||
|
"epoch": "2026-06-20 09:23:50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "METOP-SGA1",
|
||||||
|
"norad": "65159",
|
||||||
|
"line1": "1 65159U 25172A 26167.15332899 .00000049 00000+0 42000-4 0 9997",
|
||||||
|
"line2": "2 65159 98.6644 226.8998 0000902 93.8213 266.3067 14.21509720 43644",
|
||||||
|
"epoch": "2026-06-16 03:40:48"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FENGYUN 3H",
|
||||||
|
"norad": "65815",
|
||||||
|
"line1": "1 65815U 25219A 26171.42343481 .00000033 00000+0 36111-4 0 9991",
|
||||||
|
"line2": "2 65815 98.6768 112.1297 0001572 74.5092 285.6258 14.19920896 37843",
|
||||||
|
"epoch": "2026-06-20 10:09:45"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'groups' => [
|
||||||
|
[
|
||||||
|
'id' => 'starlink',
|
||||||
|
'name' => 'Starlink (CelesTrak)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'noaa',
|
||||||
|
'name' => 'NOAA (CelesTrak)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'weather',
|
||||||
|
'name' => 'Weather (CelesTrak)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'active',
|
||||||
|
'name' => 'Active (CelesTrak)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'stations',
|
||||||
|
'name' => 'Space Stations (CelesTrak)'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
<?php
|
||||||
|
// sat.thedarkelite.com - TLE proxy + cache (fast + stale fallback)
|
||||||
|
//
|
||||||
|
// CelesTrak provides 3-line sets (name + line1 + line2).[6](https://celestrak.org/norad/documentation/tle-fmt.php)
|
||||||
|
// CelesTrak recommends updated access patterns for GP data and supports multiple formats.[2](https://www.celestrak.org/NORAD/documentation/gp-data-formats.php)[1](https://celestrak.org/NORAD/elements/)
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
$start = microtime(true);
|
||||||
|
$group = isset($_GET['group']) ? strtolower(trim($_GET['group'])) : 'starlink';
|
||||||
|
|
||||||
|
function gpUrl(string $group, string $format = 'tle'): string {
|
||||||
|
// Modern group endpoint (preferred for reliability)
|
||||||
|
// See CelesTrak GP data format guidance + Current GP element sets index.[2](https://www.celestrak.org/NORAD/documentation/gp-data-formats.php)[1](https://celestrak.org/NORAD/elements/)
|
||||||
|
return 'https://celestrak.org/NORAD/elements/gp.php?GROUP=' . rawurlencode($group) . '&FORMAT=' . rawurlencode($format);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use gp.php for groups that have shown 404s on static .txt,
|
||||||
|
// keep starlink static since it commonly exists and is huge.
|
||||||
|
$allowed = [
|
||||||
|
'starlink' => ['name' => 'Starlink', 'url' => 'https://celestrak.org/NORAD/elements/gp.php?GROUP=starlink&FORMAT=tle'],
|
||||||
|
'noaa' => ['name' => 'NOAA', 'url' => 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle'],
|
||||||
|
'weather' => ['name' => 'Weather', 'url' => gpUrl('weather', 'tle')],
|
||||||
|
'active' => ['name' => 'Active', 'url' => gpUrl('active', 'tle')],
|
||||||
|
'stations' => ['name' => 'Space Stations', 'url' => gpUrl('stations', 'tle')],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isset($allowed[$group])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Invalid group'], JSON_PRETTY_PRINT);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
if (!is_dir($cacheDir)) @mkdir($cacheDir, 0775, true);
|
||||||
|
|
||||||
|
$cacheKey = preg_replace('/[^a-z0-9_\-]/', '_', $group);
|
||||||
|
$cacheFile = $cacheDir . "/tle_{$cacheKey}.json";
|
||||||
|
|
||||||
|
// TTL: 2 hours default (CelesTrak updates on a multi-hour cadence; frequent pulls can cause blocks).[7](https://usradioguy.com/wxtoimg-kepler-fix/)[1](https://celestrak.org/NORAD/elements/)
|
||||||
|
$ttlSeconds = 2 * 60 * 60;
|
||||||
|
$now = time();
|
||||||
|
|
||||||
|
$emitCache = function(array $cached, bool $isFresh) use ($ttlSeconds, $cacheFile, $now, $start) {
|
||||||
|
$age = is_file($cacheFile) ? ($now - filemtime($cacheFile)) : null;
|
||||||
|
$cached['meta']['cached'] = true;
|
||||||
|
$cached['meta']['cacheAgeSeconds'] = $age;
|
||||||
|
$cached['meta']['cacheTtlSeconds'] = $ttlSeconds;
|
||||||
|
$cached['meta']['servedFrom'] = $isFresh ? 'cache_fresh' : 'cache_stale';
|
||||||
|
$cached['meta']['serverMs'] = (int)round((microtime(true) - $start) * 1000);
|
||||||
|
|
||||||
|
header('Cache-Control: public, max-age=60');
|
||||||
|
echo json_encode($cached, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
exit;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Serve fresh cache
|
||||||
|
if (is_file($cacheFile)) {
|
||||||
|
$age = $now - filemtime($cacheFile);
|
||||||
|
if ($age < $ttlSeconds) {
|
||||||
|
$cached = json_decode(@file_get_contents($cacheFile), true);
|
||||||
|
if (is_array($cached)) $emitCache($cached, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch upstream (fail fast)
|
||||||
|
$url = $allowed[$group]['url'];
|
||||||
|
$ua = 'sat.thedarkelite.com (TLE proxy; contact: admin@thedarkelite.com)';
|
||||||
|
|
||||||
|
$text = null;
|
||||||
|
$httpCode = 0;
|
||||||
|
$curlErr = null;
|
||||||
|
|
||||||
|
$connectTimeout = 4;
|
||||||
|
$totalTimeout = 12;
|
||||||
|
|
||||||
|
if (function_exists('curl_init')) {
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => $connectTimeout,
|
||||||
|
CURLOPT_TIMEOUT => $totalTimeout,
|
||||||
|
CURLOPT_USERAGENT => $ua,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
|
||||||
|
CURLOPT_ENCODING => '',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$text = curl_exec($ch);
|
||||||
|
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curlErr = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($text === false || $httpCode >= 400) {
|
||||||
|
$text = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($text === null) {
|
||||||
|
// Serve stale cache if possible
|
||||||
|
if (is_file($cacheFile)) {
|
||||||
|
$cached = json_decode(@file_get_contents($cacheFile), true);
|
||||||
|
if (is_array($cached)) {
|
||||||
|
$cached['meta']['upstreamError'] = $curlErr ?: "Upstream fetch failed (HTTP $httpCode)";
|
||||||
|
$cached['meta']['upstream'] = $url;
|
||||||
|
$emitCache($cached, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(502);
|
||||||
|
echo json_encode([
|
||||||
|
'error' => 'Failed to fetch TLEs from upstream',
|
||||||
|
'meta' => [
|
||||||
|
'group' => $group,
|
||||||
|
'upstream' => $url,
|
||||||
|
'httpCode' => $httpCode,
|
||||||
|
'curlError' => $curlErr,
|
||||||
|
'serverMs' => (int)round((microtime(true) - $start) * 1000),
|
||||||
|
]
|
||||||
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = parseTleText($text);
|
||||||
|
|
||||||
|
$fetchedAtUtc = gmdate('Y-m-d H:i:s');
|
||||||
|
$out = [
|
||||||
|
'meta' => [
|
||||||
|
'source' => 'CelesTrak',
|
||||||
|
'group' => $group,
|
||||||
|
'groupName' => $allowed[$group]['name'],
|
||||||
|
'upstream' => $url,
|
||||||
|
'cached' => false,
|
||||||
|
'fetchedAtUtc' => $fetchedAtUtc,
|
||||||
|
'cacheTtlSeconds' => $ttlSeconds,
|
||||||
|
'count' => count($parsed),
|
||||||
|
'upstreamHttpCode' => $httpCode,
|
||||||
|
'serverMs' => (int)round((microtime(true) - $start) * 1000),
|
||||||
|
],
|
||||||
|
'satellites' => $parsed
|
||||||
|
];
|
||||||
|
|
||||||
|
@file_put_contents($cacheFile, json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
|
||||||
|
header('Cache-Control: public, max-age=60');
|
||||||
|
echo json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
|
||||||
|
// ---- Helpers ----
|
||||||
|
|
||||||
|
function parseTleText(string $text): array {
|
||||||
|
// TLE sets are typically name line + line1 + line2.[6](https://celestrak.org/norad/documentation/tle-fmt.php)
|
||||||
|
$lines = preg_split("/\r\n|\n|\r/", trim($text));
|
||||||
|
$lines = array_values(array_filter($lines, fn($l) => trim($l) !== ''));
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$i = 0;
|
||||||
|
|
||||||
|
while ($i < count($lines)) {
|
||||||
|
$name = trim($lines[$i] ?? '');
|
||||||
|
$l1 = trim($lines[$i+1] ?? '');
|
||||||
|
$l2 = trim($lines[$i+2] ?? '');
|
||||||
|
|
||||||
|
if ($name !== '' && $l1 !== '' && $l2 !== '' && $l1[0] === '1' && $l2[0] === '2') {
|
||||||
|
// NORAD number is columns 03-07 in line 1.[6](https://celestrak.org/norad/documentation/tle-fmt.php)
|
||||||
|
$norad = trim(substr($l1, 2, 5));
|
||||||
|
if ($norad === '') $norad = '0';
|
||||||
|
|
||||||
|
// Epoch year/day are in line1 columns 19-32.[6](https://celestrak.org/norad/documentation/tle-fmt.php)
|
||||||
|
$epochRaw = trim(substr($l1, 18, 14));
|
||||||
|
$epochStr = epochToIso($epochRaw);
|
||||||
|
|
||||||
|
$out[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'norad' => $norad,
|
||||||
|
'line1' => $l1,
|
||||||
|
'line2' => $l2,
|
||||||
|
'epoch' => $epochStr
|
||||||
|
];
|
||||||
|
$i += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function epochToIso(string $epochRaw): ?string {
|
||||||
|
if ($epochRaw === '' || !preg_match('/^(\d{2})(\d{3}\.\d+)$/', $epochRaw, $m)) return null;
|
||||||
|
|
||||||
|
$yy = intval($m[1], 10);
|
||||||
|
$day = floatval($m[2]);
|
||||||
|
$year = ($yy >= 57) ? (1900 + $yy) : (2000 + $yy);
|
||||||
|
|
||||||
|
$dayInt = (int)floor($day);
|
||||||
|
$frac = $day - $dayInt;
|
||||||
|
|
||||||
|
$dt = new DateTime("$year-01-01 00:00:00", new DateTimeZone("UTC"));
|
||||||
|
$dt->modify('+' . ($dayInt - 1) . ' days');
|
||||||
|
|
||||||
|
$seconds = (int)round($frac * 86400);
|
||||||
|
$dt->modify('+' . $seconds . ' seconds');
|
||||||
|
|
||||||
|
return $dt->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
+174
@@ -0,0 +1,174 @@
|
|||||||
|
:root{
|
||||||
|
--bg:#0b0f19;
|
||||||
|
--panel:#0f1626;
|
||||||
|
--panel2:#0d1322;
|
||||||
|
--text:#e7eaf0;
|
||||||
|
--muted:#9aa6bd;
|
||||||
|
--border:#1f2a44;
|
||||||
|
--accent:#6d5efc;
|
||||||
|
--good:#22c55e;
|
||||||
|
--warn:#f59e0b;
|
||||||
|
--shadow: 0 10px 30px rgba(0,0,0,.35);
|
||||||
|
--radius:18px;
|
||||||
|
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace;
|
||||||
|
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
||||||
|
}
|
||||||
|
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
html,body{height:100%;margin:0;background:var(--bg);color:var(--text);font-family:var(--sans)}
|
||||||
|
a{color:inherit}
|
||||||
|
|
||||||
|
.app{display:flex;height:100vh;width:100vw;overflow:hidden}
|
||||||
|
.sidebar{
|
||||||
|
width:360px; min-width:320px; max-width:420px;
|
||||||
|
background:linear-gradient(180deg, var(--panel) 0%, var(--panel2) 100%);
|
||||||
|
border-right:1px solid var(--border);
|
||||||
|
padding:16px;
|
||||||
|
display:flex; flex-direction:column; gap:14px;
|
||||||
|
}
|
||||||
|
.brand{display:flex; align-items:center; gap:12px; padding:8px 8px 2px}
|
||||||
|
.logo{
|
||||||
|
width:44px;height:44px;border-radius:14px;
|
||||||
|
background: radial-gradient(circle at 30% 20%, #8b7cff 0%, #6d5efc 40%, #3b33b8 100%);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
font-weight:800; letter-spacing:.6px;
|
||||||
|
}
|
||||||
|
.brandText .title{font-weight:700}
|
||||||
|
.brandText .subtitle{color:var(--muted);font-size:12px;margin-top:2px}
|
||||||
|
|
||||||
|
.panel{
|
||||||
|
background: rgba(255,255,255,.02);
|
||||||
|
border:1px solid var(--border);
|
||||||
|
border-radius:var(--radius);
|
||||||
|
padding:14px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label{display:block;margin:10px 2px 6px;color:var(--muted);font-size:12px}
|
||||||
|
.select,.input{
|
||||||
|
width:100%;
|
||||||
|
background:#0a1020;
|
||||||
|
border:1px solid var(--border);
|
||||||
|
color:var(--text);
|
||||||
|
border-radius:12px;
|
||||||
|
padding:10px 12px;
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
.select:focus,.input:focus{border-color:#2a3b68; box-shadow:0 0 0 3px rgba(109,94,252,.18)}
|
||||||
|
.hint{color:var(--muted);font-size:12px;margin-top:6px;line-height:1.3}
|
||||||
|
|
||||||
|
.row{display:flex; gap:12px; margin-top:8px}
|
||||||
|
.col{flex:1}
|
||||||
|
|
||||||
|
.toggles{margin-top:10px; display:flex; flex-direction:column; gap:8px}
|
||||||
|
.toggle{
|
||||||
|
display:flex; align-items:center; gap:10px;
|
||||||
|
padding:10px 10px;
|
||||||
|
border:1px solid rgba(31,42,68,.8);
|
||||||
|
background: rgba(0,0,0,.12);
|
||||||
|
border-radius:14px;
|
||||||
|
color:var(--text);
|
||||||
|
user-select:none;
|
||||||
|
}
|
||||||
|
.toggle input{accent-color:var(--accent); width:16px; height:16px}
|
||||||
|
|
||||||
|
.buttons{margin-top:12px}
|
||||||
|
.btn{
|
||||||
|
border:1px solid var(--border);
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
color:var(--text);
|
||||||
|
border-radius:14px;
|
||||||
|
padding:10px 12px;
|
||||||
|
cursor:pointer;
|
||||||
|
transition: transform .05s ease, background .15s ease, border-color .15s ease;
|
||||||
|
}
|
||||||
|
.btn:hover{background: rgba(255,255,255,.06); border-color:#2a3b68}
|
||||||
|
.btn:active{transform: translateY(1px)}
|
||||||
|
.btn.primary{
|
||||||
|
background: rgba(109,94,252,.18);
|
||||||
|
border-color: rgba(109,94,252,.45);
|
||||||
|
}
|
||||||
|
.btn.primary:hover{background: rgba(109,94,252,.26)}
|
||||||
|
.btn.icon{padding:6px 10px;border-radius:12px}
|
||||||
|
|
||||||
|
.stats{
|
||||||
|
margin-top:14px;
|
||||||
|
border-top:1px solid rgba(31,42,68,.7);
|
||||||
|
padding-top:12px;
|
||||||
|
display:flex; flex-direction:column; gap:6px;
|
||||||
|
}
|
||||||
|
.k{color:var(--muted);font-size:12px}
|
||||||
|
.v{font-weight:650}
|
||||||
|
.small{font-size:12px;color:var(--muted);line-height:1.35}
|
||||||
|
.mono{font-family:var(--mono)}
|
||||||
|
|
||||||
|
.main{flex:1; position:relative}
|
||||||
|
.globe{height:100%; width:100%}
|
||||||
|
|
||||||
|
/* Cesium container must fill */
|
||||||
|
#cesiumContainer, .cesium-viewer, .cesium-viewer-cesiumWidget {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default Cesium credits (we provide our own attribution elsewhere if you want) */
|
||||||
|
.cesium-widget-credits { display:none !important; }
|
||||||
|
|
||||||
|
/* Top chips */
|
||||||
|
.topbar{
|
||||||
|
position:absolute; top:14px; left:14px; right:14px;
|
||||||
|
display:flex; gap:10px; justify-content:flex-end;
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
.chip{
|
||||||
|
pointer-events:none;
|
||||||
|
padding:8px 10px;
|
||||||
|
border-radius:999px;
|
||||||
|
background: rgba(15,22,38,.75);
|
||||||
|
border:1px solid rgba(31,42,68,.75);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
font-size:12px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.chip.warn{color:#fff; background: rgba(245,158,11,.20); border-color: rgba(245,158,11,.45)}
|
||||||
|
|
||||||
|
/* Detail card */
|
||||||
|
.detail{
|
||||||
|
position:absolute; right:14px; bottom:14px;
|
||||||
|
width:min(520px, calc(100% - 28px));
|
||||||
|
background: rgba(15,22,38,.92);
|
||||||
|
border:1px solid rgba(31,42,68,.9);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding:12px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
.detailHeader{display:flex; align-items:center; justify-content:space-between; gap:12px; padding:4px 2px 10px}
|
||||||
|
.detailTitle{font-weight:750}
|
||||||
|
.detailGrid{
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0,1fr));
|
||||||
|
gap:8px 16px;
|
||||||
|
padding:8px 2px 10px;
|
||||||
|
border-top:1px solid rgba(31,42,68,.65);
|
||||||
|
border-bottom:1px solid rgba(31,42,68,.65);
|
||||||
|
}
|
||||||
|
.detailTle{
|
||||||
|
white-space:pre;
|
||||||
|
margin-top:10px;
|
||||||
|
background: rgba(0,0,0,.18);
|
||||||
|
border:1px solid rgba(31,42,68,.65);
|
||||||
|
border-radius:14px;
|
||||||
|
padding:10px;
|
||||||
|
overflow:auto;
|
||||||
|
max-height:140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Static crisp background image behind Cesium */
|
||||||
|
#cesiumContainer{
|
||||||
|
background: url("/assets/bg/space.jpg") center center / cover no-repeat;
|
||||||
|
background-color: #05060a; /* fallback */
|
||||||
|
}
|
||||||
+618
@@ -0,0 +1,618 @@
|
|||||||
|
/* sat.thedarkelite.com - Cesium globe client
|
||||||
|
- Static crisp background image behind Cesium (/assets/bg/space.jpg) with subtle drift
|
||||||
|
- Cesium canvas made transparent (alpha) so CSS background shows through
|
||||||
|
- Radar overlay (RainViewer) as an imagery layer (tiles)
|
||||||
|
- Satellites via TLE -> SGP4 (satellite.js)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API = {
|
||||||
|
groups: "/api/groups.php",
|
||||||
|
tle: (group) => `/api/tle.php?group=${encodeURIComponent(group)}`
|
||||||
|
};
|
||||||
|
|
||||||
|
// RainViewer Weather Maps API endpoint (public)
|
||||||
|
const RADAR = {
|
||||||
|
api: "https://api.rainviewer.com/public/weather-maps.json", // provides host + frames[1](https://www.rainviewer.com/api/weather-maps-api.html)
|
||||||
|
size: 256,
|
||||||
|
color: 2, // universal blue (good default)
|
||||||
|
options: "1_0", // smooth=1, snow=0 (see docs)[1](https://www.rainviewer.com/api/weather-maps-api.html)
|
||||||
|
opacity: 0.55,
|
||||||
|
refreshMs: 5 * 60 * 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
groups: [],
|
||||||
|
group: "noaa",
|
||||||
|
sats: [],
|
||||||
|
satsFiltered: [],
|
||||||
|
satrecs: new Map(), // norad -> satrec
|
||||||
|
entities: new Map(), // norad -> Cesium.Entity
|
||||||
|
selected: null, // norad
|
||||||
|
trackEntity: null,
|
||||||
|
lastMeta: null,
|
||||||
|
timer: null,
|
||||||
|
|
||||||
|
// background drift
|
||||||
|
drift: { enabled: true, raf: null },
|
||||||
|
|
||||||
|
// radar
|
||||||
|
radar: {
|
||||||
|
layer: null, // Cesium.ImageryLayer
|
||||||
|
provider: null, // Cesium.UrlTemplateImageryProvider
|
||||||
|
framePath: null, // current /v2/radar/##########
|
||||||
|
lastUpdate: 0,
|
||||||
|
timer: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const el = (id) => document.getElementById(id);
|
||||||
|
|
||||||
|
function fmt(n, d = 3) {
|
||||||
|
if (n === null || n === undefined || Number.isNaN(n)) return "—";
|
||||||
|
return Number(n).toFixed(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWarn(msg) {
|
||||||
|
const chip = el("chipWarn");
|
||||||
|
if (!chip) return;
|
||||||
|
if (!msg) {
|
||||||
|
chip.style.display = "none";
|
||||||
|
chip.textContent = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chip.style.display = "inline-flex";
|
||||||
|
chip.textContent = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function metaString(meta) {
|
||||||
|
if (!meta) return "—";
|
||||||
|
const parts = [];
|
||||||
|
if (meta.groupName) parts.push(meta.groupName);
|
||||||
|
if (meta.source) parts.push(meta.source);
|
||||||
|
if (meta.cached !== undefined) parts.push(meta.cached ? "cached" : "fresh");
|
||||||
|
if (meta.fetchedAtUtc) parts.push(`fetched ${meta.fetchedAtUtc} UTC`);
|
||||||
|
if (meta.cacheTtlSeconds) parts.push(`TTL ${Math.round(meta.cacheTtlSeconds / 60)}m`);
|
||||||
|
if (meta.count !== undefined) parts.push(`${meta.count} sats`);
|
||||||
|
return parts.join(" • ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Cesium init ---------- */
|
||||||
|
|
||||||
|
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||||
|
animation: false,
|
||||||
|
timeline: false,
|
||||||
|
baseLayerPicker: false,
|
||||||
|
homeButton: false,
|
||||||
|
geocoder: false,
|
||||||
|
sceneModePicker: false,
|
||||||
|
navigationHelpButton: false,
|
||||||
|
fullscreenButton: false,
|
||||||
|
infoBox: false,
|
||||||
|
selectionIndicator: false,
|
||||||
|
shouldAnimate: false,
|
||||||
|
|
||||||
|
// KEY: make the canvas support transparency so CSS background shows through
|
||||||
|
contextOptions: {
|
||||||
|
webgl: { alpha: true }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- BASEMAP (labeled, dark) ---
|
||||||
|
// Using CARTO dark_all with proper "@2x" support via customTags.
|
||||||
|
// CARTO documents optional @2x tiles as "{scale}" in URL pattern.[5](https://cdnjs.com/libraries/leaflet)
|
||||||
|
viewer.imageryLayers.removeAll();
|
||||||
|
const basemap = viewer.imageryLayers.addImageryProvider(
|
||||||
|
new Cesium.UrlTemplateImageryProvider({
|
||||||
|
url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{scale}.png",
|
||||||
|
subdomains: ["a", "b", "c", "d"],
|
||||||
|
maximumLevel: 20,
|
||||||
|
credit: "© OpenStreetMap contributors © CARTO",
|
||||||
|
customTags: {
|
||||||
|
scale: () => (window.devicePixelRatio > 1 ? "@2x" : "")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
); // UrlTemplateImageryProvider supports URL templates + customTags[4](https://usradioguy.com/wxtoimg-kepler-fix/)
|
||||||
|
|
||||||
|
// Minor “ops” tuning
|
||||||
|
basemap.brightness = 0.90;
|
||||||
|
basemap.contrast = 1.15;
|
||||||
|
basemap.saturation = 0.95;
|
||||||
|
basemap.gamma = 1.05;
|
||||||
|
|
||||||
|
// ---- CLEAN CRISP BACKGROUND IMAGE MODE ----
|
||||||
|
// Disable Cesium skybox entirely (prevents blurry/noisy stars). SkyBox can be assigned/removed.[6](https://github.com/CesiumGS/cesium/releases)
|
||||||
|
viewer.scene.skyBox = null;
|
||||||
|
viewer.scene.skyAtmosphere.show = false;
|
||||||
|
viewer.scene.fog.enabled = false;
|
||||||
|
|
||||||
|
// Make Cesium background transparent so the CSS image is visible
|
||||||
|
viewer.scene.backgroundColor = Cesium.Color.TRANSPARENT;
|
||||||
|
|
||||||
|
viewer.scene.globe.depthTestAgainstTerrain = false;
|
||||||
|
|
||||||
|
// Start camera over North America
|
||||||
|
viewer.camera.setView({
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(-98.5, 38.5, 12_500_000)
|
||||||
|
});
|
||||||
|
|
||||||
|
function applyLightingToggle() {
|
||||||
|
const t = el("toggleLighting");
|
||||||
|
viewer.scene.globe.enableLighting = !!(t && t.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Background drift (subtle parallax) ---------- */
|
||||||
|
function startBackgroundDrift() {
|
||||||
|
if (!state.drift.enabled) return;
|
||||||
|
|
||||||
|
const container = document.getElementById("cesiumContainer");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const maxShiftPx = 28;
|
||||||
|
const smooth = 0.10;
|
||||||
|
let curX = 50;
|
||||||
|
let curY = 50;
|
||||||
|
|
||||||
|
function clamp(v, min, max) { return Math.min(max, Math.max(min, v)); }
|
||||||
|
|
||||||
|
function tick() {
|
||||||
|
const heading = viewer.camera.heading || 0;
|
||||||
|
const pitch = viewer.camera.pitch || 0;
|
||||||
|
|
||||||
|
let hn = ((heading + Math.PI) % (Math.PI * 2)) - Math.PI;
|
||||||
|
|
||||||
|
const targetXpx = (hn / Math.PI) * maxShiftPx;
|
||||||
|
const targetYpx = (pitch / (Math.PI / 2)) * (maxShiftPx * 0.6);
|
||||||
|
|
||||||
|
const w = container.clientWidth || 1;
|
||||||
|
const h = container.clientHeight || 1;
|
||||||
|
|
||||||
|
const targetX = 50 + (targetXpx / w) * 100;
|
||||||
|
const targetY = 50 + (targetYpx / h) * 100;
|
||||||
|
|
||||||
|
curX = curX + (targetX - curX) * smooth;
|
||||||
|
curY = curY + (targetY - curY) * smooth;
|
||||||
|
|
||||||
|
curX = clamp(curX, 45, 55);
|
||||||
|
curY = clamp(curY, 46, 54);
|
||||||
|
|
||||||
|
container.style.backgroundPosition = `${curX.toFixed(2)}% ${curY.toFixed(2)}%`;
|
||||||
|
|
||||||
|
state.drift.raf = requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.drift.raf) cancelAnimationFrame(state.drift.raf);
|
||||||
|
state.drift.raf = requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Radar overlay (RainViewer tiles) ---------- */
|
||||||
|
/*
|
||||||
|
RainViewer provides:
|
||||||
|
- JSON listing frames, plus "host" and "path" fields
|
||||||
|
- Tile URL format: {host}{path}/{size}/{z}/{x}/{y}/{color}/{options}.png[1](https://www.rainviewer.com/api/weather-maps-api.html)
|
||||||
|
- Public API limitations include max zoom 7 in examples/docs[3](https://github.com/rainviewer/rainviewer-api-example/blob/master/README.md)[1](https://www.rainviewer.com/api/weather-maps-api.html)
|
||||||
|
*/
|
||||||
|
async function updateRadarLayer(force = false) {
|
||||||
|
const now = Date.now();
|
||||||
|
if (!force && now - state.radar.lastUpdate < RADAR.refreshMs) return;
|
||||||
|
|
||||||
|
state.radar.lastUpdate = now;
|
||||||
|
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
const res = await fetch(RADAR.api, { cache: "no-store" });
|
||||||
|
if (!res.ok) throw new Error(`RainViewer HTTP ${res.status}`);
|
||||||
|
data = await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
// don’t kill app if radar fails
|
||||||
|
console.warn("Radar fetch failed:", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = data.host;
|
||||||
|
const past = data?.radar?.past;
|
||||||
|
if (!host || !Array.isArray(past) || past.length === 0) return;
|
||||||
|
|
||||||
|
// Use latest past frame (stable)
|
||||||
|
const latest = past[past.length - 1];
|
||||||
|
const path = latest.path; // like "/v2/radar/##########"[1](https://www.rainviewer.com/api/weather-maps-api.html)
|
||||||
|
if (!path) return;
|
||||||
|
|
||||||
|
// If frame didn’t change, nothing to do
|
||||||
|
if (state.radar.framePath === path && state.radar.layer) return;
|
||||||
|
|
||||||
|
state.radar.framePath = path;
|
||||||
|
|
||||||
|
// Build tile template
|
||||||
|
const url =
|
||||||
|
`${host}${path}/${RADAR.size}/{z}/{x}/{y}/${RADAR.color}/${RADAR.options}.png`;
|
||||||
|
|
||||||
|
const provider = new Cesium.UrlTemplateImageryProvider({
|
||||||
|
url,
|
||||||
|
maximumLevel: 7, // public max zoom per RainViewer docs/examples[1](https://www.rainviewer.com/api/weather-maps-api.html)[3](https://github.com/rainviewer/rainviewer-api-example/blob/master/README.md)
|
||||||
|
credit: "Radar: RainViewer"
|
||||||
|
}); // UrlTemplateImageryProvider supports tile templates[4](https://usradioguy.com/wxtoimg-kepler-fix/)
|
||||||
|
|
||||||
|
// Remove old layer if exists
|
||||||
|
if (state.radar.layer) {
|
||||||
|
viewer.imageryLayers.remove(state.radar.layer, true);
|
||||||
|
state.radar.layer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.radar.provider = provider;
|
||||||
|
state.radar.layer = viewer.imageryLayers.addImageryProvider(provider);
|
||||||
|
|
||||||
|
// Put radar above basemap
|
||||||
|
// (it is added last so it’s already on top; keep explicit ordering safe)
|
||||||
|
state.radar.layer.alpha = RADAR.opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRadarAutoRefresh() {
|
||||||
|
// Load immediately, then every 5 min
|
||||||
|
updateRadarLayer(true);
|
||||||
|
if (state.radar.timer) clearInterval(state.radar.timer);
|
||||||
|
state.radar.timer = setInterval(() => updateRadarLayer(false), RADAR.refreshMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Click picking ---------- */
|
||||||
|
const clickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
||||||
|
clickHandler.setInputAction((movement) => {
|
||||||
|
const picked = viewer.scene.pick(movement.position);
|
||||||
|
if (Cesium.defined(picked) && picked.id && picked.id.id) {
|
||||||
|
selectSatellite(String(picked.id.id));
|
||||||
|
}
|
||||||
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
||||||
|
|
||||||
|
/* ---------- Data + propagation ---------- */
|
||||||
|
|
||||||
|
async function loadGroups() {
|
||||||
|
const res = await fetch(API.groups, { cache: "no-store" });
|
||||||
|
if (!res.ok) throw new Error(`groups.php HTTP ${res.status}`);
|
||||||
|
const data = await res.json();
|
||||||
|
state.groups = data.groups || [];
|
||||||
|
return state.groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillGroupSelect() {
|
||||||
|
const sel = el("groupSelect");
|
||||||
|
sel.innerHTML = "";
|
||||||
|
for (const g of state.groups) {
|
||||||
|
const opt = document.createElement("option");
|
||||||
|
opt.value = g.id;
|
||||||
|
opt.textContent = g.name;
|
||||||
|
if (g.id === state.group) opt.selected = true;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTLEs(group) {
|
||||||
|
setWarn("");
|
||||||
|
el("statMeta").textContent = "Loading…";
|
||||||
|
|
||||||
|
const res = await fetch(API.tle(group), { cache: "no-store" });
|
||||||
|
const txt = await res.text();
|
||||||
|
if (!res.ok) throw new Error(`tle.php HTTP ${res.status} ${txt}`);
|
||||||
|
const data = JSON.parse(txt);
|
||||||
|
|
||||||
|
state.lastMeta = data.meta || null;
|
||||||
|
|
||||||
|
state.sats = (data.satellites || []).map((s) => ({
|
||||||
|
name: s.name,
|
||||||
|
norad: String(s.norad),
|
||||||
|
line1: s.line1,
|
||||||
|
line2: s.line2,
|
||||||
|
epoch: s.epoch || null
|
||||||
|
}));
|
||||||
|
|
||||||
|
state.satrecs.clear();
|
||||||
|
for (const s of state.sats) {
|
||||||
|
try {
|
||||||
|
const satrec = satellite.twoline2satrec(s.line1, s.line2);
|
||||||
|
state.satrecs.set(s.norad, satrec);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
el("statLoaded").textContent = String(state.sats.length);
|
||||||
|
el("statMeta").textContent = metaString(state.lastMeta);
|
||||||
|
|
||||||
|
clearSelection();
|
||||||
|
applyFilterAndRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSats() {
|
||||||
|
const q = (el("searchBox").value || "").trim().toLowerCase();
|
||||||
|
const max = Math.max(50, Math.min(5000, parseInt(el("maxSats").value || "800", 10)));
|
||||||
|
|
||||||
|
let arr = state.sats;
|
||||||
|
|
||||||
|
if (q) {
|
||||||
|
arr = arr.filter(
|
||||||
|
(s) => s.name.toLowerCase().includes(q) || s.norad.toLowerCase() === q
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr = arr.slice(0, max);
|
||||||
|
|
||||||
|
state.satsFiltered = arr;
|
||||||
|
el("statShown").textContent = String(arr.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeLatLonAltKm(norad, date) {
|
||||||
|
const satrec = state.satrecs.get(norad);
|
||||||
|
if (!satrec) return null;
|
||||||
|
|
||||||
|
const pv = satellite.propagate(satrec, date);
|
||||||
|
if (!pv.position) return null;
|
||||||
|
|
||||||
|
const gmst = satellite.gstime(date);
|
||||||
|
const gd = satellite.eciToGeodetic(pv.position, gmst);
|
||||||
|
|
||||||
|
const lat = satellite.degreesLat(gd.latitude);
|
||||||
|
const lon = satellite.degreesLong(gd.longitude);
|
||||||
|
const altKm = gd.height;
|
||||||
|
|
||||||
|
let speed = null;
|
||||||
|
if (pv.velocity) {
|
||||||
|
const v = pv.velocity;
|
||||||
|
speed = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { lat, lon, altKm, speed };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Entities ---------- */
|
||||||
|
|
||||||
|
function clearEntities() {
|
||||||
|
for (const ent of state.entities.values()) viewer.entities.remove(ent);
|
||||||
|
state.entities.clear();
|
||||||
|
|
||||||
|
if (state.trackEntity) {
|
||||||
|
viewer.entities.remove(state.trackEntity);
|
||||||
|
state.trackEntity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSatEntity(sat) {
|
||||||
|
return viewer.entities.add({
|
||||||
|
id: sat.norad,
|
||||||
|
name: sat.name,
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(0, 0, 500000),
|
||||||
|
point: {
|
||||||
|
pixelSize: 7,
|
||||||
|
color: Cesium.Color.fromCssColorString("#9b8cff"),
|
||||||
|
outlineColor: Cesium.Color.fromCssColorString("#000000"),
|
||||||
|
outlineWidth: 2,
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilterAndRender() {
|
||||||
|
filterSats();
|
||||||
|
clearEntities();
|
||||||
|
|
||||||
|
for (const sat of state.satsFiltered) {
|
||||||
|
const ent = makeSatEntity(sat);
|
||||||
|
state.entities.set(sat.norad, ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectSatellite(norad) {
|
||||||
|
state.selected = norad;
|
||||||
|
|
||||||
|
const sat = state.sats.find((x) => x.norad === norad) || null;
|
||||||
|
el("statSelected").textContent = sat ? `${sat.name} (${sat.norad})` : "—";
|
||||||
|
|
||||||
|
for (const [id, ent] of state.entities.entries()) {
|
||||||
|
const active = id === norad;
|
||||||
|
|
||||||
|
if (ent.point) {
|
||||||
|
ent.point.pixelSize = active ? 11 : 7;
|
||||||
|
ent.point.color = active
|
||||||
|
? Cesium.Color.fromCssColorString("#22c55e")
|
||||||
|
: Cesium.Color.fromCssColorString("#9b8cff");
|
||||||
|
ent.point.outlineColor = active
|
||||||
|
? Cesium.Color.fromCssColorString("#ffffff")
|
||||||
|
: Cesium.Color.fromCssColorString("#000000");
|
||||||
|
ent.point.outlineWidth = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el("toggleLabels").checked && active && sat) {
|
||||||
|
ent.label = new Cesium.LabelGraphics({
|
||||||
|
text: `${sat.name} (${sat.norad})`,
|
||||||
|
font: "14px ui-sans-serif",
|
||||||
|
fillColor: Cesium.Color.WHITE,
|
||||||
|
outlineColor: Cesium.Color.BLACK,
|
||||||
|
outlineWidth: 3,
|
||||||
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
||||||
|
pixelOffset: new Cesium.Cartesian2(12, -12),
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ent.label = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sat) {
|
||||||
|
el("detail").style.display = "none";
|
||||||
|
if (state.trackEntity) {
|
||||||
|
viewer.entities.remove(state.trackEntity);
|
||||||
|
state.trackEntity = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
el("detailTitle").textContent = sat.name;
|
||||||
|
el("detailNorad").textContent = sat.norad;
|
||||||
|
el("detailTle").textContent = `${sat.line1}\n${sat.line2}`;
|
||||||
|
el("detailEpoch").textContent = sat.epoch || "—";
|
||||||
|
el("detail").style.display = "block";
|
||||||
|
|
||||||
|
const pos = computeLatLonAltKm(norad, new Date());
|
||||||
|
if (pos) {
|
||||||
|
const height = Math.max(1_500_000, pos.altKm * 1000 * 6);
|
||||||
|
viewer.camera.flyTo({
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, height),
|
||||||
|
duration: 0.6
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
drawTrackIfEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelection() {
|
||||||
|
state.selected = null;
|
||||||
|
el("statSelected").textContent = "—";
|
||||||
|
el("detail").style.display = "none";
|
||||||
|
|
||||||
|
for (const ent of state.entities.values()) {
|
||||||
|
if (ent.point) {
|
||||||
|
ent.point.pixelSize = 7;
|
||||||
|
ent.point.color = Cesium.Color.fromCssColorString("#9b8cff");
|
||||||
|
ent.point.outlineColor = Cesium.Color.fromCssColorString("#000000");
|
||||||
|
ent.point.outlineWidth = 2;
|
||||||
|
}
|
||||||
|
ent.label = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.trackEntity) {
|
||||||
|
viewer.entities.remove(state.trackEntity);
|
||||||
|
state.trackEntity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTrackIfEnabled() {
|
||||||
|
if (!el("toggleTracks").checked) {
|
||||||
|
if (state.trackEntity) {
|
||||||
|
viewer.entities.remove(state.trackEntity);
|
||||||
|
state.trackEntity = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!state.selected) return;
|
||||||
|
|
||||||
|
const norad = state.selected;
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const positions = [];
|
||||||
|
for (let t = -10 * 60; t <= 80 * 60; t += 30) {
|
||||||
|
const d = new Date(now.getTime() + t * 1000);
|
||||||
|
const p = computeLatLonAltKm(norad, d);
|
||||||
|
if (!p) continue;
|
||||||
|
positions.push(Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.altKm * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.trackEntity) {
|
||||||
|
viewer.entities.remove(state.trackEntity);
|
||||||
|
state.trackEntity = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.trackEntity = viewer.entities.add({
|
||||||
|
id: "__track__",
|
||||||
|
polyline: {
|
||||||
|
positions,
|
||||||
|
width: 2,
|
||||||
|
material: new Cesium.PolylineGlowMaterialProperty({
|
||||||
|
glowPower: 0.12,
|
||||||
|
color: Cesium.Color.fromCssColorString("#22c55e")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Tick loop ---------- */
|
||||||
|
|
||||||
|
function tick() {
|
||||||
|
const now = new Date();
|
||||||
|
el("chipTime").textContent = `UTC ${now.toISOString().slice(11, 19)}`;
|
||||||
|
|
||||||
|
viewer.clock.currentTime = Cesium.JulianDate.fromDate(now);
|
||||||
|
|
||||||
|
let selectedPos = null;
|
||||||
|
|
||||||
|
for (const sat of state.satsFiltered) {
|
||||||
|
const p = computeLatLonAltKm(sat.norad, now);
|
||||||
|
if (!p) continue;
|
||||||
|
|
||||||
|
const ent = state.entities.get(sat.norad);
|
||||||
|
if (ent) ent.position = Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.altKm * 1000);
|
||||||
|
|
||||||
|
if (state.selected === sat.norad) selectedPos = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedPos) {
|
||||||
|
el("detailLat").textContent = fmt(selectedPos.lat, 4);
|
||||||
|
el("detailLon").textContent = fmt(selectedPos.lon, 4);
|
||||||
|
el("detailAlt").textContent = fmt(selectedPos.altKm, 2);
|
||||||
|
el("detailSpeed").textContent = fmt(selectedPos.speed, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.selected && el("toggleTracks").checked) {
|
||||||
|
const sec = now.getUTCSeconds();
|
||||||
|
if (sec % 5 === 0) drawTrackIfEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- UI wiring ---------- */
|
||||||
|
|
||||||
|
function wireUI() {
|
||||||
|
el("groupSelect").addEventListener("change", async (e) => {
|
||||||
|
state.group = e.target.value;
|
||||||
|
clearSelection();
|
||||||
|
await loadTLEs(state.group);
|
||||||
|
});
|
||||||
|
|
||||||
|
el("btnReload").addEventListener("click", async () => {
|
||||||
|
clearSelection();
|
||||||
|
await loadTLEs(state.group);
|
||||||
|
});
|
||||||
|
|
||||||
|
el("btnClear").addEventListener("click", () => clearSelection());
|
||||||
|
el("detailClose").addEventListener("click", () => clearSelection());
|
||||||
|
|
||||||
|
el("searchBox").addEventListener("input", () => {
|
||||||
|
clearSelection();
|
||||||
|
applyFilterAndRender();
|
||||||
|
});
|
||||||
|
|
||||||
|
el("maxSats").addEventListener("change", () => {
|
||||||
|
clearSelection();
|
||||||
|
applyFilterAndRender();
|
||||||
|
});
|
||||||
|
|
||||||
|
el("toggleTracks").addEventListener("change", () => drawTrackIfEnabled());
|
||||||
|
|
||||||
|
el("toggleLabels").addEventListener("change", () => {
|
||||||
|
if (state.selected) selectSatellite(state.selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
el("toggleLighting").addEventListener("change", () => applyLightingToggle());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function boot() {
|
||||||
|
try {
|
||||||
|
wireUI();
|
||||||
|
applyLightingToggle();
|
||||||
|
|
||||||
|
await loadGroups();
|
||||||
|
fillGroupSelect();
|
||||||
|
|
||||||
|
await loadTLEs(state.group);
|
||||||
|
|
||||||
|
startBackgroundDrift();
|
||||||
|
|
||||||
|
// Start radar overlay updater
|
||||||
|
startRadarAutoRefresh();
|
||||||
|
|
||||||
|
if (state.timer) clearInterval(state.timer);
|
||||||
|
state.timer = setInterval(tick, 1000);
|
||||||
|
el("chipRate").textContent = "Update: 1s";
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setWarn("Load failed (see console)");
|
||||||
|
el("statMeta").textContent = String(err.message || err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boot();
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 988 KiB |
+115
@@ -0,0 +1,115 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>sat.thedarkelite.com — Satellite Tracker</title>
|
||||||
|
<meta name="description" content="Live satellite tracking on a 3D globe using TLE → SGP4." />
|
||||||
|
<meta name="theme-color" content="#0b0f19" />
|
||||||
|
|
||||||
|
<!-- CesiumJS (CDNJS) -->
|
||||||
|
<script>
|
||||||
|
// Required so Cesium can find its Workers/Assets/Widgets when loaded from CDN.
|
||||||
|
window.CESIUM_BASE_URL = "https://cdnjs.cloudflare.com/ajax/libs/cesium/1.133.1/";
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cesium/1.133.1/Widgets/widgets.min.css"> <!-- [1](https://cdnjs.com/libraries/cesium) -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cesium/1.133.1/Cesium.min.js"></script> <!-- [1](https://cdnjs.com/libraries/cesium) -->
|
||||||
|
|
||||||
|
<!-- satellite.js (SGP4) -->
|
||||||
|
<script src="https://unpkg.com/satellite.js@6.0.2/dist/satellite.min.js"></script> <!-- [2](https://github.com/shashwatak/satellite-js) -->
|
||||||
|
|
||||||
|
<!-- App styles -->
|
||||||
|
<link rel="stylesheet" href="/assets/app.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="app">
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="brand">
|
||||||
|
<div class="logo">SAT</div>
|
||||||
|
<div class="brandText">
|
||||||
|
<div class="title">sat.thedarkelite.com</div>
|
||||||
|
<div class="subtitle">TLE → SGP4 → Globe</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<label class="label" for="groupSelect">Satellite group</label>
|
||||||
|
<select id="groupSelect" class="select"></select>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<label class="label" for="maxSats">Max displayed</label>
|
||||||
|
<input id="maxSats" class="input" type="number" min="50" max="5000" step="50" value="800" />
|
||||||
|
<div class="hint">Starlink is huge—cap it for performance.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="label" for="searchBox">Search (name / NORAD)</label>
|
||||||
|
<input id="searchBox" class="input" type="text" placeholder="e.g. STARLINK-3000 or 25544" />
|
||||||
|
|
||||||
|
<div class="toggles">
|
||||||
|
<label class="toggle">
|
||||||
|
<input id="toggleLabels" type="checkbox" />
|
||||||
|
<span>Show labels (selected)</span>
|
||||||
|
</label>
|
||||||
|
<label class="toggle">
|
||||||
|
<input id="toggleTracks" type="checkbox" checked />
|
||||||
|
<span>Show orbit arc (selected)</span>
|
||||||
|
</label>
|
||||||
|
<label class="toggle">
|
||||||
|
<input id="toggleLighting" type="checkbox" checked />
|
||||||
|
<span>Day/Night lighting</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row buttons">
|
||||||
|
<button id="btnReload" class="btn primary">Reload TLEs</button>
|
||||||
|
<button id="btnClear" class="btn">Clear selection</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div><span class="k">Loaded</span> <span id="statLoaded" class="v">0</span></div>
|
||||||
|
<div><span class="k">Displayed</span> <span id="statShown" class="v">0</span></div>
|
||||||
|
<div><span class="k">Selected</span> <span id="statSelected" class="v">—</span></div>
|
||||||
|
<div class="small" id="statMeta">—</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<div class="small">
|
||||||
|
Positions are computed client-side from TLEs using SGP4, then plotted as Entities on a 3D globe. <!-- [4](https://cesium.com/learn/cesiumjs-learn/cesiumjs-creating-entities/)[2](https://github.com/shashwatak/satellite-js) -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="main">
|
||||||
|
<div id="cesiumContainer" class="globe"></div>
|
||||||
|
|
||||||
|
<div class="topbar">
|
||||||
|
<div class="chip" id="chipTime">UTC —</div>
|
||||||
|
<div class="chip" id="chipRate">Update: 1s</div>
|
||||||
|
<div class="chip warn" id="chipWarn" style="display:none"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail" id="detail" style="display:none">
|
||||||
|
<div class="detailHeader">
|
||||||
|
<div class="detailTitle" id="detailTitle">—</div>
|
||||||
|
<button class="btn icon" id="detailClose" title="Close">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="detailGrid">
|
||||||
|
<div><span class="k">NORAD</span> <span class="v" id="detailNorad">—</span></div>
|
||||||
|
<div><span class="k">Lat</span> <span class="v" id="detailLat">—</span></div>
|
||||||
|
<div><span class="k">Lon</span> <span class="v" id="detailLon">—</span></div>
|
||||||
|
<div><span class="k">Alt (km)</span> <span class="v" id="detailAlt">—</span></div>
|
||||||
|
<div><span class="k">Speed (km/s)</span> <span class="v" id="detailSpeed">—</span></div>
|
||||||
|
<div><span class="k">Epoch</span> <span class="v mono" id="detailEpoch">—</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="detailTle mono" id="detailTle">—</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/assets/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://sat.thedarkelite.com/sitemap.xml
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://sat.thedarkelite.com/</loc>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
Reference in New Issue
Block a user