在WPF中,透過資原始檔實現主題切換是個常見的功能,有不少文章介紹瞭如何實現手動切換主題。那如何實現自動切換主題呢?通常有兩種機制:一是跟隨系統明暗主題切換,二是像手機作業系統那樣根據日出日落時間自動切換。本文將以終為始,採用倒推法一步步介紹如何使用.NET免費獲取日出日落時間。
獲取日出日落時間
日出日落時間會隨季節及各地方經度緯度的不同而不同,此外還會受到大氣折射等因素的影響。計算日出和日落時間通常需要考慮以下因素:
- 日期:具體的年、月、日,用於確定太陽相對於地球的位置。
- 經度:地理位置的經度,影響日出和日落的具體時刻。
- 緯度:地理位置的緯度,影響日出和日落時間的早晚以及全年日照時間的長短。
- 海拔:較高的海拔會影響大氣折射,從而略微影響日出和日落時間。
獲取日出日落時間可以使用線上API或者公式計算。最簡單的方法就是付費API,其中有不少API提供免費試用(每天限額請求次數)。公式計算則不受網路限制,但準確度要低一點。
線上API
查詢日出日落時間的線上API比較多,這裡介紹一個無需註冊,無需AccessKey的免費API
sunrise-sunset ,它是一個簡單的RESTful API,只需要透過GET
請求https://api.sunrise-sunset.org/json
即可。最少只用提供經度和緯度引數。詳細引數說明如下:
- lat(float):十進位制的緯度,例如22.5559。必要引數
- lng(float):十進位制的經度,例如114.0577。必要引數
- date(string):
YYYY-MM-DD
格式的日期,也可以是其他的日期格式或者相對日期格式,預設值是當天。可選引數 - callback (string):JSONP回撥函式名稱。可選引數
- formatted (integer):0 or 1 (預設值是1)。值為0時,API響應結果中時間型別的值將按照 ISO 8601顯示,並且晝長(day_length)以秒為單位顯示。可選引數
- tzid (string):時區識別符號。例如:UTC,Asia/Shanghai。可用的時區識別符號參見支援的時區列表,如果設定了該引數,響應結果中的時間將根據該引數中時區作為基準。預設是國際協調時間UTC。可選引數
以下是獲取日出日落時間最基本引數的示例和響應結果:
https://api.sunrise-sunset.org/json?lat=23.1181&lng=113.2539
由於引數只提供了最基本的緯度和經度資訊,響應結果預設是當天的日出日落資訊,並且資料進行了預設的格式化,所有的時間都是以UTC作為基準且沒有進行夏令時調整。
{
"results": {
"sunrise": "9:48:35 PM",
"sunset": "10:58:28 AM",
"solar_noon": "4:23:32 AM",
"day_length": "13:09:53",
"civil_twilight_begin": "9:25:53 PM",
"civil_twilight_end": "11:21:10 AM",
"nautical_twilight_begin": "8:57:33 PM",
"nautical_twilight_end": "11:49:30 AM",
"astronomical_twilight_begin": "8:28:26 PM",
"astronomical_twilight_end": "12:18:37 PM"
},
"status": "OK",
"tzid": "UTC"
}
以下是提供了時區引數的請求示例和響應結果:
https://api.sunrise-sunset.org/json?lat=23.1181&lng=113.2539&tzid=Asia/shanghai
響應結果中的時間都是以東八區作為基準:
{
"results": {
"sunrise": "5:48:35 AM",
"sunset": "6:58:28 PM",
"solar_noon": "12:23:32 PM",
"day_length": "13:09:53",
"civil_twilight_begin": "5:25:53 AM",
"civil_twilight_end": "7:21:10 PM",
"nautical_twilight_begin": "4:57:33 AM",
"nautical_twilight_end": "7:49:30 PM",
"astronomical_twilight_begin": "4:28:26 AM",
"astronomical_twilight_end": "8:18:37 PM"
},
"status": "OK",
"tzid": "Asia/shanghai"
}
公式計算日出日落時間
除了透過線上API獲取日出日落時間,還可以透過天文演算法計算,這些演算法通常基於地球自轉、公轉、地球橢圓軌道、黃赤交角以及大氣折射等因素。下邊這個公式沒有涉及大氣折射因素,但依舊有較高的精度。
前邊透過線上API獲取的時間與多個付費API比較結果一致,姑且以線上API作為參照基準,此處公式計算結果偏差有幾分鐘。
計算機一般採用弧度制,公式為:
日出時間=(180+時區*15-經度-arccos(tan(10547π/81000*cos(2π*(日期+9)/365))*tan(緯度*π/180))*180/π)/15
日落時間=(180+時區*15-經度+arccos(tan(10547π/81000*cos(2π*(日期+9)/365))*tan(緯度*π/180))*180/π)/15
請注意,使用這些公式時應確保:
- 日期:通常表示為距離當年1月1日的天數。(例如:1月1日表示日期=1,2月15日表示日期=46)
- 時區:以小時為單位,東時區為正,西時區為負。(例如:我國時區為東八區,時區=8)
- 經度、緯度:以度為單位,東經、北緯為正,西經、南緯為負。(例如:東經100º13′30″,北緯35º20′15″,則表示經度=100.225,緯度=35.3375;西經25º15′54″,南緯50º45′18″,則表示經度=-25.265,緯度=-50.755)
經度緯度查詢
不管是線上API還是公式計算的方式獲取日出日落時間,都需要輸入經度緯度資訊,直接獲取經度緯度資訊並不容易。但是,我們可以輕鬆地獲取到另一個和地理位置有關的網路資訊:IP地址。然後透過IP地址與地理位置的對映(包括經度緯度)得到想要的資訊。通常可以透過線上API服務或者離線資料庫完成IP地址到經度緯度資訊地轉換。
線上API服務
除了百度地圖,還有許多其他提供IP到經緯度轉換的服務,如 MaxMind GeoIP、IPinfo.io、IPGeolocation.io 等。這些服務通常提供免費和付費版本,使用方式類似,通常包括註冊、獲取API金鑰、按照文件指示構造請求URL並解析響應。
使用本地資料庫或API庫
如果需要在本地處理大量IP到經緯度的轉換,或者希望減少對外部API的依賴,可以考慮使用如IP2Location、GeoIP等提供的資料庫產品。這些資料庫包含了IP地址與地理位置資訊的對映,可以直接在本地進行查詢,無需每次請求都透過網路傳送到第三方伺服器。
IP2Location
IP2Location提供了付費的版本IP2Location和免費版本IP2Location Lite,他們的區別在於付費版本資料更多更準確,詳細對比參見版本比較。IP2Location Lite提供了CSV和Bin兩種格式的資料庫,並根據資料豐富性分為多個不同的版本。這裡以包含了國家、地區、城市、經度緯度、郵政編碼、時區的DB11為例介紹如何使用。
首先,下載IP2LOCATION-LITE-DB11.BIN資料庫檔案,然後在專案中透過Nuget引用IP2Location.IPGeolocation
包。呼叫程式碼如下:
Component oIP2Location = new Component();
IPResult oIPResult = new IPResult();
oIP2Location.Open(@"C:\Users\John\Downloads\IP2LOCATION-LITE-DB11.BIN");
oIPResult = oIP2Location.IPQuery("120.236.111.205");
if (oIPResult.Status == "OK")
{
Console.WriteLine(oIPResult.Latitude); //23.12736
Console.WriteLine(oIPResult.Longitude); //113.2646
Console.WriteLine(oIPResult.CountryLong); //"China"
Console.WriteLine(oIPResult.CountryShort); //"CN"
Console.WriteLine(oIPResult.Region); //"Guangdong"
Console.WriteLine(oIPResult.City); //"Guangzhou"
Console.WriteLine(oIPResult.TimeZone); //"+08:00"
Console.WriteLine(oIPResult.ZipCode); //"510140"
}
oIP2Location.Close();
GeoIP
GeoIP也是提供了付費版本GeoIP2和免費版本GeoLite2,付費版本除了資料更多更準確,更新頻率也更高一些。GeoLite2也是提供了CSV和mmdb兩種格式資料庫,並根據內容不同分為GeoLite2 Country
、GeoLite2 City
、GeoLite2 ASN
三個版本,詳細資訊參見GeoLite2 IP後設資料資料庫對比。這裡以GeoLite2 City.mmdb為例介紹如何使用。
首先,下載GeoLite2-City.mmdb資料庫檔案,然後在專案中透過Nuget引用MaxMind.GeoIP2
包。呼叫程式碼如下:
using (var reader = new DatabaseReader(@"C:\Users\John\Downloads\GeoLite2-City.mmdb"))
{
var city = reader.City("120.236.111.205");
Console.WriteLine(city.Country.IsoCode); // "CN"
Console.WriteLine(city.Country.Name); // "China"
Console.WriteLine(city.Country.Names["zh-CN"]); // "中國"
Console.WriteLine(city.MostSpecificSubdivision.Name); // null
Console.WriteLine(city.MostSpecificSubdivision.IsoCode); // null
Console.WriteLine(city.City.Name); // null
Console.WriteLine(city.Postal.Code); // null
Console.WriteLine(city.Location.Latitude); // 34.7732
Console.WriteLine(city.Location.Longitude); // 113.722
}
不太幸運的是,這個IP並沒有查詢到城市資訊,只返回了國家資訊,因此結果中的經度緯度資訊也不準確。國內所有未查詢到城市資訊的IP,返回都是這個經度緯度資訊(透過百度地圖查詢到該座標處於鄭州)。但是GeoIP2的線上服務查詢到了準確的城市及經度緯度資訊。
最新(2024-5-7下載)的GeoLite2資料庫中查詢到20071箇中國的IP網段,其中有11270條是未查詢到具體城市資訊的記錄。
IP2Location Lite和GeoLite2資料庫的覆蓋情況並沒有一個確切的資料,兩個資料庫結合使用或許能提高查詢命中率。
獲取公網IP
自己的公網IP非常容易獲取,比如使用線上IP查詢網站,或者搜尋引擎中搜尋關鍵詞“IP”,搜尋結果中通常會顯示自己的公網IP地址。在程式中也可以透過特定API獲取公網IP,比如下邊這個API:
Get https://ipecho.net/plain
小結
在計算日出日落和經度緯度資訊的環節都介紹了線上API服務和離線獲取兩種方式。線上API服務的優勢是結果更準確,離線方式的優勢是無需依賴第三方服務,缺點就是結果沒那麼精準。當然,在根據日出日落時間實現自動切換主題的需求上,準確度要求沒那麼高,離線計算方式足矣。