pvlib/location.rs
1use chrono::DateTime;
2use chrono_tz::Tz;
3
4use crate::atmosphere;
5use crate::clearsky;
6use crate::solarposition::{self, SolarPosition};
7
8/// Represents a physical location on Earth.
9///
10/// `pvlib-python` equivalently uses `Location` class with attributes:
11/// latitude, longitude, tz, altitude, and name.
12#[derive(Debug, Clone, PartialEq)]
13pub struct Location {
14 pub latitude: f64,
15 pub longitude: f64,
16 pub tz: Tz,
17 pub altitude: f64,
18 pub name: String,
19}
20
21impl Location {
22 /// Create a new Location instance.
23 ///
24 /// # Arguments
25 ///
26 /// * `latitude` - Latitude in decimal degrees. Positive north of equator, negative to south.
27 /// * `longitude` - Longitude in decimal degrees. Positive east of prime meridian, negative to west.
28 /// * `tz` - Timezone as a `chrono_tz::Tz` enum variant.
29 /// * `altitude` - Altitude from sea level in meters.
30 /// * `name` - Name of the location.
31 pub fn new(latitude: f64, longitude: f64, tz: Tz, altitude: f64, name: &str) -> Self {
32 Self {
33 latitude,
34 longitude,
35 tz,
36 altitude,
37 name: name.to_string(),
38 }
39 }
40
41 /// Calculate the solar position for this location at the given time.
42 ///
43 /// Convenience wrapper around `solarposition::get_solarposition`.
44 pub fn get_solarposition(&self, time: DateTime<Tz>) -> Result<SolarPosition, spa::SpaError> {
45 solarposition::get_solarposition(self, time)
46 }
47
48 /// Calculate clear sky irradiance for this location at the given time.
49 ///
50 /// # Arguments
51 /// * `time` - Date and time with timezone.
52 /// * `model` - Clear sky model: "ineichen", "haurwitz", or "simplified_solis".
53 ///
54 /// # Returns
55 /// GHI, DNI, DHI in W/m^2. Returns zeros if the sun is below the horizon.
56 pub fn get_clearsky(&self, time: DateTime<Tz>, model: &str) -> clearsky::ClearSkyIrradiance {
57 let solar_pos = match self.get_solarposition(time) {
58 Ok(sp) => sp,
59 Err(_) => return clearsky::ClearSkyIrradiance { ghi: 0.0, dni: 0.0, dhi: 0.0 },
60 };
61
62 match model {
63 "haurwitz" => {
64 let ghi = clearsky::haurwitz(solar_pos.zenith);
65 clearsky::ClearSkyIrradiance { ghi, dni: 0.0, dhi: 0.0 }
66 }
67 "simplified_solis" => {
68 let apparent_elevation = 90.0 - solar_pos.zenith;
69 clearsky::simplified_solis(apparent_elevation, 0.1, 1.0, atmosphere::alt2pres(self.altitude))
70 }
71 _ => {
72 // Default to ineichen
73 let (_am_rel, am_abs) = self.get_airmass(time);
74 if am_abs.is_nan() || am_abs <= 0.0 {
75 return clearsky::ClearSkyIrradiance { ghi: 0.0, dni: 0.0, dhi: 0.0 };
76 }
77 let month = {
78 use chrono::Datelike;
79 time.month()
80 };
81 let linke_turbidity = clearsky::lookup_linke_turbidity(self.latitude, self.longitude, month);
82 clearsky::ineichen(solar_pos.zenith, am_abs, linke_turbidity, self.altitude, 1364.0)
83 }
84 }
85 }
86
87 /// Calculate relative and absolute airmass for this location at the given time.
88 ///
89 /// Uses Kasten-Young model for relative airmass and site pressure derived
90 /// from altitude for absolute airmass.
91 ///
92 /// # Returns
93 /// `(airmass_relative, airmass_absolute)`. Values may be NaN if the sun is
94 /// below the horizon.
95 pub fn get_airmass(&self, time: DateTime<Tz>) -> (f64, f64) {
96 let solar_pos = match self.get_solarposition(time) {
97 Ok(sp) => sp,
98 Err(_) => return (f64::NAN, f64::NAN),
99 };
100
101 let am_rel = atmosphere::get_relative_airmass(solar_pos.zenith);
102 let pressure = atmosphere::alt2pres(self.altitude);
103 let am_abs = atmosphere::get_absolute_airmass(am_rel, pressure);
104 (am_rel, am_abs)
105 }
106}
107
108/// Lookup altitude for a given latitude and longitude.
109///
110/// This is a simplified approximation. Most populated areas are near sea level,
111/// so this returns 0.0 as a default. For accurate altitude data, use SRTM or
112/// similar elevation datasets.
113///
114/// # Arguments
115/// * `_latitude` - Latitude in decimal degrees.
116/// * `_longitude` - Longitude in decimal degrees.
117///
118/// # Returns
119/// Estimated altitude in meters above sea level.
120pub fn lookup_altitude(_latitude: f64, _longitude: f64) -> f64 {
121 // A proper implementation would query SRTM or similar elevation data.
122 // For now, return 0.0 (sea level) as a safe default.
123 0.0
124}