firms-sdk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
firms_sdk/__init__.py ADDED
@@ -0,0 +1,137 @@
1
+ from datetime import datetime
2
+ from urllib.error import HTTPError, URLError
3
+
4
+ from pandas import DataFrame, Series, concat, read_csv
5
+ from requests import get
6
+ from requests.exceptions import RequestException
7
+
8
+ from src.firms_sdk.utils import SensorType, format_table, datetime_and_ranges_in_year
9
+ from src.firms_sdk.exceptions import FetchException, check_sensor, check_day_range
10
+
11
+
12
+ class Utils:
13
+ """Utilities for data fetching."""
14
+
15
+ def __init__(self):
16
+ self.datetime_and_ranges_in_year = datetime_and_ranges_in_year
17
+
18
+
19
+ class FIRMSClient:
20
+ """FIRMS Client generator to fetch hotspots data from all sensors."""
21
+
22
+ def __init__(self, map_key: str, region: str = "world", region_name: str | None = None):
23
+ self.base_url = "https://firms.modaps.eosdis.nasa.gov/"
24
+ self.map_key = map_key
25
+ self.region = region
26
+ self.instance_date = datetime.now()
27
+ self.region_name = region_name
28
+ self.utils = Utils()
29
+
30
+ def __str__(self):
31
+ return f"FIRMS Client initiated at {self.instance_date}\
32
+ \nRegion: {self.region_name + " - " if self.region_name else ""}[{self.region}]"
33
+
34
+ def __repr__(self):
35
+ return self.__str__()
36
+
37
+ @property
38
+ def status(self) -> Series:
39
+ """Fetch MAP KEY status info.
40
+
41
+ Raises:
42
+ FetchException: unable to reach server for data.
43
+
44
+ Returns:
45
+ Series: transactions limit, transactions done, and refreshing time info
46
+ """
47
+ try:
48
+ response = get(self.base_url + "mapserver/mapkey_status/?MAP_KEY=" + self.map_key, timeout=3600)
49
+ data = response.json()
50
+ s = Series(data)
51
+ s.name = "Status"
52
+ return s
53
+ except RequestException as e:
54
+ raise FetchException(e.args)
55
+
56
+ @property
57
+ def data_available(self) -> DataFrame:
58
+ """Available sensors to fetch data.
59
+
60
+ Raises:
61
+ FetchException: unable to reach server for data.
62
+
63
+ Returns:
64
+ DataFrame: Sensors data.
65
+ """
66
+ try:
67
+ f_url = self.base_url + "api/data_availability/csv/" + self.map_key + "/all"
68
+ return read_csv(f_url)
69
+ except (HTTPError, URLError) as e:
70
+ raise FetchException(e.args)
71
+
72
+ def get_data(self, sensor: SensorType, day_range: int, date_init: datetime) -> DataFrame:
73
+ """Fetch hotspots from sensor in the specified date range.
74
+
75
+ Args:
76
+ sensor (SensorType): Sensor of interest.
77
+ day_range (int): Number of days in advance to search (between 1-5).
78
+ date_init (datetime): date of interest.
79
+
80
+ Raises:
81
+ FetchException: Data not available.
82
+
83
+ Returns:
84
+ DataFrame: hotspots dataframe.
85
+ """
86
+ check_day_range(day_range)
87
+ check_sensor(sensor)
88
+ f_url = f"{self.base_url}api/area/csv/{self.map_key}/{sensor}/{self.region}/{day_range}/{date_init.strftime("%Y-%m-%d")}"
89
+ try:
90
+ df = read_csv(f_url, parse_dates=["acq_date"])
91
+ d, hh, mm = df["acq_date"], df["acq_time"] // 100, df["acq_time"] % 100
92
+ dt_vector = [datetime(d.year, d.month, d.day, hour, minute) for d, hour, minute in zip(d, hh, mm)]
93
+ df = df.drop(columns=["acq_date", "acq_time"])
94
+ df["datetime"] = dt_vector
95
+ return format_table(df)
96
+ except (HTTPError, URLError) as e:
97
+ raise FetchException(e.args)
98
+
99
+ def get_sp_data(self, day_range: int, date_init: datetime) -> DataFrame:
100
+ """Fetch validate hotspots from FIRMS.
101
+
102
+ Args:
103
+ day_range (int): Number of days in advance to search (between 1-5).
104
+ date_init (datetime): date of interest.
105
+
106
+ Returns:
107
+ DataFrame: hotspots dataframe.
108
+ """
109
+ sensors: list[SensorType] = ["MODIS_SP", "VIIRS_NOAA20_SP", "VIIRS_SNPP_SP"]
110
+ dfs = [self.get_data(sensor, day_range, date_init) for sensor in sensors]
111
+ return concat(dfs, ignore_index=True)
112
+
113
+ def get_nrt_data(self, day_range: int, date_init: datetime) -> DataFrame:
114
+ """Fetch near-real-time hotspots from FIRMS (no validated by NASA).
115
+
116
+ Args:
117
+ day_range (int): Number of days in advance to search (between 1-5).
118
+ date_init (datetime): date of interest.
119
+
120
+ Returns:
121
+ DataFrame: hotspots dataframe.
122
+ """
123
+ sensors: list[SensorType] = ["MODIS_NRT", "VIIRS_NOAA20_NRT", "VIIRS_NOAA21_NRT", "VIIRS_SNPP_NRT"]
124
+ dfs = [self.get_data(sensor, day_range, date_init) for sensor in sensors]
125
+ return format_table(concat(dfs, ignore_index=True))
126
+
127
+ def get_all_data(self, day_range: int, date_init: datetime) -> DataFrame:
128
+ """Fetch near-real-time and validated hotspots data.
129
+
130
+ Args:
131
+ day_range (int): Number of days in advance to search (between 1-5).
132
+ date_init (datetime): date of interest.
133
+
134
+ Returns:
135
+ DataFrame: hotspots dataframe.
136
+ """
137
+ return format_table(concat([self.get_sp_data(day_range, date_init), self.get_nrt_data(day_range, date_init)], ignore_index=True))
@@ -0,0 +1,69 @@
1
+ from typing import Any
2
+
3
+ from src.firms_sdk.utils import SensorType
4
+
5
+
6
+ class FetchException(Exception):
7
+ """Fetch Exception message."""
8
+
9
+ def __init__(self, *args):
10
+ super().__init__(*args)
11
+
12
+
13
+ class SensorException(FetchException):
14
+ """No valid sensor error message."""
15
+
16
+ def __init__(self, value: Any):
17
+ message = f"Sensor must be one between: \
18
+ 'MODIS_NRT', 'MODIS_SP', 'VIIRS_NOAA20_NRT', 'VIIRS_NOAA20_SP', 'VIIRS_NOAA21_NRT', 'VIIRS_SNPP_NRT', 'VIIRS_SNPP_SP' \
19
+ , not {value}"
20
+ super().__init__(message)
21
+
22
+
23
+ class DayRangeException(FetchException):
24
+ """No valid day range."""
25
+
26
+ def __init__(self, value: Any):
27
+ message = f"Day range must be a value between 1 or 5 days in advance, not {value}"
28
+ super().__init__(message)
29
+
30
+
31
+ def check_sensor(sensor: SensorType):
32
+ """Validate sensor input.
33
+
34
+ Args:
35
+ sensor (SensorType): sensor to validate.
36
+
37
+ Raises:
38
+ TypeError: Sensor is not string.
39
+ SensorException: Sensor is not one of available.
40
+ """
41
+ available_sensors = [
42
+ "MODIS_NRT",
43
+ "MODIS_SP",
44
+ "VIIRS_NOAA20_NRT",
45
+ "VIIRS_NOAA20_SP",
46
+ "VIIRS_NOAA21_NRT",
47
+ "VIIRS_SNPP_NRT",
48
+ "VIIRS_SNPP_SP",
49
+ ]
50
+ if not isinstance(sensor, str):
51
+ raise TypeError(f"Sensor must be string type, not {type(sensor)}")
52
+ if sensor not in available_sensors:
53
+ raise SensorException(sensor)
54
+
55
+
56
+ def check_day_range(day_range: int):
57
+ """Validate day_range input.
58
+
59
+ Args:
60
+ day_range (int): day range to validate.
61
+
62
+ Raises:
63
+ TypeError: Day range is not int.
64
+ DayRangeException: Day range is not between 1-5 range.
65
+ """
66
+ if not isinstance(day_range, int):
67
+ raise TypeError(f"Day range must be integer, not {type(day_range)}")
68
+ if day_range <= 0 or day_range > 5:
69
+ raise DayRangeException(day_range)
firms_sdk/utils.py ADDED
@@ -0,0 +1,75 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import Literal
3
+ import numpy as np
4
+ from pandas import DataFrame, StringDtype
5
+
6
+ SensorType = Literal[
7
+ "MODIS_NRT",
8
+ "MODIS_SP",
9
+ "VIIRS_NOAA20_NRT",
10
+ "VIIRS_NOAA20_SP",
11
+ "VIIRS_NOAA21_NRT",
12
+ "VIIRS_SNPP_NRT",
13
+ "VIIRS_SNPP_SP",
14
+ "all",
15
+ ]
16
+
17
+
18
+ def format_table(df: DataFrame) -> DataFrame:
19
+ """Format FIRMS table for all available options.
20
+
21
+ Args:
22
+ df (DataFrame): base dataframe.
23
+
24
+ Returns:
25
+ (DataFrame): dataframe processed.
26
+ """
27
+ type_map = {
28
+ "latitude": np.dtype("float64"),
29
+ "longitude": np.dtype("float64"),
30
+ "datetime": np.dtype("datetime64[us]"),
31
+ "scan": np.dtype("float64"),
32
+ "track": np.dtype("float64"),
33
+ "satellite": StringDtype(),
34
+ "instrument": StringDtype(),
35
+ "type": np.dtype("int64"),
36
+ "version": np.dtype("float64"),
37
+ "confidence": StringDtype(),
38
+ "brightness": np.dtype("float64"),
39
+ "bright_t31": np.dtype("float64"),
40
+ "bright_ti4": np.dtype("float64"),
41
+ "bright_ti5": np.dtype("float64"),
42
+ }
43
+ for key, value in type_map.items():
44
+ try:
45
+ df[key] = df[key].astype(value)
46
+ except KeyError:
47
+ df[key] = None
48
+ df[key] = df[key].astype(value)
49
+ return df[type_map.keys()]
50
+
51
+
52
+ def datetime_and_ranges_in_year(year: int) -> list[tuple[datetime, int]]:
53
+ """Get datetime and day_ranges pairs list for the specified year.
54
+
55
+ Args:
56
+ year (int): year of interest.
57
+
58
+ Returns:
59
+ list[tuple[datetime, int]]: list of datetime-day_range pairs.
60
+ """
61
+ first_date = datetime(year, 1, 1)
62
+ final_date = datetime(year, 12, 31)
63
+ dates = [(first_date, 5)]
64
+ while True:
65
+ current_date = dates[len(dates) - 1][0]
66
+ current_day_range = dates[len(dates) - 1][1]
67
+ if current_date + timedelta(days=current_day_range) >= final_date:
68
+ break
69
+ next_date = current_date + timedelta(days=current_day_range)
70
+ to_final_date = final_date - next_date
71
+ if to_final_date.days > 5:
72
+ dates.append((next_date, 5))
73
+ else:
74
+ dates.append((next_date, to_final_date.days))
75
+ return dates
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: firms-sdk
3
+ Version: 0.1.0
4
+ Summary: Basic SDK Client to retrive hotspots data from FIRMS API
5
+ Author: Sebástian Andrés Narváez Salcedo
6
+ Author-email: sebnarvaez19@gmail.com
7
+ Requires-Python: >=3.14
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.14
10
+ Requires-Dist: pandas (>=3.0.0,<4.0.0)
11
+ Requires-Dist: requests (>=2.32.5,<3.0.0)
12
+ Description-Content-Type: text/markdown
13
+
14
+ # FIRMS SDK
15
+
16
+ Basic SDK Client to retrive hotspots data from FIRMS API.
17
+ The client requires the MAP_KEY from NASA and the region of interest to be instanciated.
18
+
19
+ ```py
20
+ from firms_sdk import FIRMSClient
21
+
22
+ MAP_KEY = "******************************c7"
23
+ REGION = "-75.2736908751,10.2543646415,-74.7146900698,11.1024970837"
24
+ client = FIRMSClient(MAP_KEY, REGION)
25
+ ```
26
+
@@ -0,0 +1,6 @@
1
+ firms_sdk/__init__.py,sha256=HhWnhZtQUXh-yG5YGIfdLCTQHegaErpC_zln6Ny3VqE,5247
2
+ firms_sdk/exceptions.py,sha256=zcwInoFoaCubuCZ5nLDmb9vv_oJvS4ntbRuwFmGP-3I,1967
3
+ firms_sdk/utils.py,sha256=0jM6-j0M81ucIiH7bQ1CyAQ0kqLcJBe6FDV1WfW1R0M,2299
4
+ firms_sdk-0.1.0.dist-info/METADATA,sha256=u7ETj-8uR-ySrbCITA_1WgqnM1Ntebgu3AJxxxKPDXY,804
5
+ firms_sdk-0.1.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
6
+ firms_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any