pycoustic 0.1.1__py3-none-any.whl → 0.1.2__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.
- pycoustic/log.py +6 -0
- pycoustic/survey.py +6 -0
- pycoustic/tkgui.py +348 -0
- pycoustic/weather.py +94 -0
- {pycoustic-0.1.1.dist-info → pycoustic-0.1.2.dist-info}/METADATA +5 -2
- pycoustic-0.1.2.dist-info/RECORD +8 -0
- {pycoustic-0.1.1.dist-info → pycoustic-0.1.2.dist-info}/WHEEL +1 -1
- pycoustic-0.1.1.dist-info/RECORD +0 -6
pycoustic/log.py
CHANGED
pycoustic/survey.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import pandas as pd
|
2
2
|
import numpy as np
|
3
|
+
from .weather import WeatherHistory
|
3
4
|
|
4
5
|
|
5
6
|
class Survey:
|
@@ -281,6 +282,11 @@ class Survey:
|
|
281
282
|
combi = combi.transpose()
|
282
283
|
return combi
|
283
284
|
|
285
|
+
def get_start_end(self):
|
286
|
+
starts = [self._logs[key].get_start() for key in self._logs.keys()]
|
287
|
+
ends = [self._logs[key].get_end() for key in self._logs.keys()]
|
288
|
+
return min(starts), max(ends)
|
289
|
+
|
284
290
|
# def typical_leq_spectra(self, leq_cols=None):
|
285
291
|
# """
|
286
292
|
# DEPRECATED 2025/06/05. Replaced by .leq_spectra() **TT**
|
pycoustic/tkgui.py
ADDED
@@ -0,0 +1,348 @@
|
|
1
|
+
import tkinter as tk
|
2
|
+
from tkinter import filedialog ,Toplevel, IntVar, Checkbutton, Button
|
3
|
+
from tkinter import ttk
|
4
|
+
from tkinter import messagebox
|
5
|
+
from log import Log
|
6
|
+
from survey import Survey
|
7
|
+
import os
|
8
|
+
import pandas as pd
|
9
|
+
from tkinter import StringVar
|
10
|
+
#import matplotlib
|
11
|
+
#from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
12
|
+
|
13
|
+
#from src import log # Assuming log is an instance of a class with the required methods
|
14
|
+
|
15
|
+
class Application(tk.Tk):
|
16
|
+
|
17
|
+
def __init__(self):
|
18
|
+
super().__init__()
|
19
|
+
self.title("src Log Viewer")
|
20
|
+
self.geometry("1200x800")
|
21
|
+
import tkinter as tk
|
22
|
+
|
23
|
+
# Create an instance of Survey
|
24
|
+
self.survey = Survey()
|
25
|
+
|
26
|
+
self.create_widgets()
|
27
|
+
|
28
|
+
def create_widgets(self):
|
29
|
+
|
30
|
+
self.grid_columnconfigure((0,1,2), weight=1)
|
31
|
+
|
32
|
+
self.grid_columnconfigure((3,4,5), weight=2)
|
33
|
+
|
34
|
+
self.log_label = ttk.Label(self, text="csv file name:")
|
35
|
+
self.log_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
|
36
|
+
|
37
|
+
self.log_file = ttk.Label(self, text="Click Browse to select")
|
38
|
+
self.log_file.grid(row=0, column=1, padx=5, pady=5, sticky="w")
|
39
|
+
|
40
|
+
self.browse_button1 = ttk.Button(self, text="Browse", command=self.browse_log)
|
41
|
+
self.browse_button1.grid(row=0, column=2, padx=5, pady=5, sticky="w")
|
42
|
+
|
43
|
+
self.analysis_label = ttk.Label(self, text="Select Analysis Type:")
|
44
|
+
self.analysis_label.grid(row=3, column=0, padx=5, pady=10, sticky="e")
|
45
|
+
|
46
|
+
self.analysis_var = StringVar()
|
47
|
+
self.analysis_combobox = ttk.Combobox(self, textvariable=self.analysis_var, values=["resi_summary", "modal_l90", "lmax_spectra", "Typical_leq_spectra"])
|
48
|
+
self.analysis_combobox.set("resi_summary")
|
49
|
+
self.analysis_combobox.grid(row=3, column=1, padx=5, pady=10, sticky="w")
|
50
|
+
self.analysis_var.trace("w", self.on_analysischange)
|
51
|
+
|
52
|
+
self.parameters_label = ttk.Label(self, text="Parameters")
|
53
|
+
self.parameters_label.grid(row=4, column=0, padx=5, pady=5, sticky="e")
|
54
|
+
|
55
|
+
self.parameters_entry = ttk.Entry(self, width=20)
|
56
|
+
self.parameters_entry.insert(0, 'None,None,10,2min')
|
57
|
+
self.parameters_entry.grid(row=4, column=1, padx=5, pady=10, sticky="w")
|
58
|
+
|
59
|
+
self.parameters_label = ttk.Label(self, text="(leq_cols,max_cols,lmax_n,lmax_t )")
|
60
|
+
self.parameters_label.grid(row=4, column=2, padx=5, pady=5, sticky="w")
|
61
|
+
|
62
|
+
self.execute_button = ttk.Button(self, text="Select Columns", command=self.Column_Selection_Modal)
|
63
|
+
self.execute_button.grid(row=5, column=0, padx=5, pady=10, sticky="e")
|
64
|
+
|
65
|
+
self.execute_button = ttk.Button(self, text="Execute", command=self.execute_code)
|
66
|
+
self.execute_button.grid(row=5, column=2, padx=5, pady=10, sticky="w")
|
67
|
+
|
68
|
+
self.tree = ttk.Treeview(self, show="headings")
|
69
|
+
self.tree_scroll_y = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
|
70
|
+
self.tree_scroll_y.grid(row=6, column=6, sticky="ns")
|
71
|
+
|
72
|
+
self.tree_scroll_x = ttk.Scrollbar(self, orient="horizontal", command=self.tree.xview)
|
73
|
+
self.tree_scroll_x.grid(row=7, column=0, columnspan=3, sticky="ew")
|
74
|
+
|
75
|
+
self.tree.configure(yscrollcommand=self.tree_scroll_y.set, xscrollcommand=self.tree_scroll_x.set)
|
76
|
+
|
77
|
+
self.tree.grid(row=6, column=0, columnspan=4, padx=20, pady=20, sticky="nsew")
|
78
|
+
"""
|
79
|
+
# Button to open the modal dialog
|
80
|
+
open_dialog_button = ttk.Button(self, text="Show Time Series", command=self.open_modal_dialog)
|
81
|
+
open_dialog_button.grid(row=5, column=2, padx=5, pady=10, sticky="w")
|
82
|
+
|
83
|
+
|
84
|
+
# Commented out section that displays a graph
|
85
|
+
def open_modal_dialog(self):
|
86
|
+
dialog = Toplevel(self)
|
87
|
+
dialog.title("Modal Dialog")
|
88
|
+
|
89
|
+
# Make the dialog modal
|
90
|
+
dialog.transient(self)
|
91
|
+
dialog.grab_set()
|
92
|
+
|
93
|
+
# Center the dialog on the screen
|
94
|
+
window_width = self.winfo_width()
|
95
|
+
window_height = self.winfo_height()
|
96
|
+
window_x = self.winfo_x()
|
97
|
+
window_y = self.winfo_y()
|
98
|
+
|
99
|
+
dialog_width = 900
|
100
|
+
dialog_height = 600
|
101
|
+
|
102
|
+
position_right = int(window_x + (window_width / 2) - (dialog_width / 2))
|
103
|
+
position_down = int(window_y + (window_height / 2) - (dialog_height / 2))
|
104
|
+
|
105
|
+
dialog.geometry(f"{dialog_width}x{dialog_height}+{position_right}+{position_down}")
|
106
|
+
import matplotlib.pyplot as plt
|
107
|
+
|
108
|
+
# Create a figure and axis
|
109
|
+
fig, ax = plt.subplots()
|
110
|
+
|
111
|
+
# Plot the data
|
112
|
+
#print ("----",self.df.columns[1][0],self.df.columns[1][1] )
|
113
|
+
#print ("----",type(self.df.columns[1]))
|
114
|
+
|
115
|
+
leq_a_column = [col for col in self.df.columns if isinstance(col, tuple) and col[0] == "Leq" and col[1] == "A"]
|
116
|
+
if not leq_a_column:
|
117
|
+
messagebox.showerror("Error", "No columns found with ('Leq', 'A') in the header.")
|
118
|
+
return
|
119
|
+
|
120
|
+
l90_a_columns = [col for col in self.df.columns if isinstance(col, tuple) and col[0] == "L90" and col[1] == "A"]
|
121
|
+
if not l90_a_columns:
|
122
|
+
messagebox.showerror("Error", "No columns found with ('L90', 'A') in the header.")
|
123
|
+
return
|
124
|
+
|
125
|
+
|
126
|
+
self.df["Date"] = pd.to_datetime(self.df.index)
|
127
|
+
#print(self.df)
|
128
|
+
|
129
|
+
ax.plot(self.df["Date"], self.df[leq_a_column[0]], label='LAeq')
|
130
|
+
ax.plot(self.df["Date"], self.df[l90_a_column[0]], label='LA90')
|
131
|
+
#ax.plot(self.df['Index'], self.df['L90 A'], label='la90')
|
132
|
+
|
133
|
+
# Format the x-axis to show dates properly
|
134
|
+
fig.autofmt_xdate()
|
135
|
+
|
136
|
+
# Add labels and title
|
137
|
+
ax.set_xlabel('Date')
|
138
|
+
ax.set_ylabel('Values')
|
139
|
+
ax.set_title('Time Series Line Chart')
|
140
|
+
ax.legend()
|
141
|
+
|
142
|
+
# Create a canvas to display the plot in the Tkinter dialog
|
143
|
+
canvas = FigureCanvasTkAgg(fig, master=dialog)
|
144
|
+
canvas.draw()
|
145
|
+
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
146
|
+
#else:
|
147
|
+
# messagebox.showerror("Error", "DataFrame does not contain required columns: 'date', 'Laeq', 'la90'")
|
148
|
+
|
149
|
+
# OK button to close the dialog
|
150
|
+
ok_button = ttk.Button(dialog, text="OK", command=dialog.destroy)
|
151
|
+
ok_button.pack(pady=20)
|
152
|
+
|
153
|
+
"""
|
154
|
+
|
155
|
+
def on_analysischange(self, *args):
|
156
|
+
analysis_type = self.analysis_combobox.get()
|
157
|
+
if analysis_type == "resi_summary":
|
158
|
+
self.parameters_entry.config(state='normal')
|
159
|
+
else:
|
160
|
+
self.parameters_entry.config(state='disabled')
|
161
|
+
return
|
162
|
+
|
163
|
+
|
164
|
+
def browse_log(self):
|
165
|
+
self.logpath = os.getcwd ()
|
166
|
+
file_path = tk.filedialog.askopenfilename(initialdir=self.logpath, filetypes=[("CSV files", "*.csv")])
|
167
|
+
if file_path:
|
168
|
+
self.logname = os.path.basename(file_path)
|
169
|
+
self.logpath = os.path.dirname(file_path)
|
170
|
+
self.log_file.config(text=self.logname)
|
171
|
+
|
172
|
+
strPath = self.logpath + "\\" + self.logname
|
173
|
+
self.log = Log(path=strPath)
|
174
|
+
self.survey.add_log(data=self.log, name="Position 1")
|
175
|
+
|
176
|
+
self.df = self.log.get_data()
|
177
|
+
|
178
|
+
# Clear the treeview
|
179
|
+
for item in self.tree.get_children():
|
180
|
+
self.tree.delete(item)
|
181
|
+
|
182
|
+
# Round the dataframe values to 1 decimal place, except for the column headers
|
183
|
+
self.df = self.df.round(1)
|
184
|
+
|
185
|
+
# Insert new data into the treeview
|
186
|
+
self.tree["columns"] = ["Index"] + list(self.df.columns)
|
187
|
+
self.tree.heading("Index", text="Index")
|
188
|
+
self.tree.column("Index", width=150, stretch=True)
|
189
|
+
|
190
|
+
i = 0
|
191
|
+
for col in self.df.columns:
|
192
|
+
self.tree.heading(col, text=col)
|
193
|
+
i=i+1
|
194
|
+
if i < 12:
|
195
|
+
self.tree.column(col, width=75,stretch=True, anchor="center")
|
196
|
+
else:
|
197
|
+
self.tree.column(col, width=0,stretch=False, anchor="center")
|
198
|
+
|
199
|
+
for index, row in self.df.iterrows():
|
200
|
+
self.tree.insert("", "end", values=[index] + list(row))
|
201
|
+
return
|
202
|
+
|
203
|
+
def execute_code(self):
|
204
|
+
analysis_type = self.analysis_combobox.get()
|
205
|
+
parameters = self.parameters_entry.get()
|
206
|
+
|
207
|
+
try:
|
208
|
+
df = pd.DataFrame()
|
209
|
+
if analysis_type == "resi_summary":
|
210
|
+
params = parameters.split(",")
|
211
|
+
p = [None if x == "None" else x for x in params]
|
212
|
+
df =self.survey.resi_summary(leq_cols=p[0], max_cols=p[1], lmax_n=int(params[2]), lmax_t=params[3])
|
213
|
+
print ("df id a ",type(df))
|
214
|
+
elif analysis_type == "modal_l90":
|
215
|
+
df = self.survey.modal()
|
216
|
+
elif analysis_type == "lmax_spectra":
|
217
|
+
df = self.survey.lmax_spectra()
|
218
|
+
elif analysis_type == "Leq_spectra":
|
219
|
+
df = self.survey.leq_spectra()
|
220
|
+
else:
|
221
|
+
messagebox.showerror("Error", "Please select an analysis type.")
|
222
|
+
return
|
223
|
+
except Exception as e:
|
224
|
+
messagebox.showerror("Error", f"An error occurred: {e}")
|
225
|
+
return
|
226
|
+
|
227
|
+
# Clear the treeview
|
228
|
+
for item in self.tree.get_children():
|
229
|
+
self.tree.delete(item)
|
230
|
+
|
231
|
+
# Set new column headers based on the dataframe
|
232
|
+
#self.tree["columns"] = list(df.columns)
|
233
|
+
|
234
|
+
# Insert new data into the treeview
|
235
|
+
self.tree["columns"] = ["Index"] + list(df.columns)
|
236
|
+
self.tree.heading("Index", text="Index")
|
237
|
+
self.tree.column("Index", width=150, anchor="center", stretch=True)
|
238
|
+
|
239
|
+
|
240
|
+
for col in df.columns:
|
241
|
+
self.tree.heading(col, text=col)
|
242
|
+
self.tree.column(col, width=75, anchor="center", stretch=True)
|
243
|
+
|
244
|
+
# Insert new data into the treeview
|
245
|
+
for index, row in df.iterrows():
|
246
|
+
self.tree.insert("", "end", values=[index] + list(row))
|
247
|
+
|
248
|
+
# Copy the DataFrame to the clipboard
|
249
|
+
df.to_clipboard(index=True)
|
250
|
+
messagebox.showinfo("Success", "DataFrame copied to clipboard.")
|
251
|
+
|
252
|
+
|
253
|
+
def Column_Selection_Modal(self):
|
254
|
+
# Create a modal dialog
|
255
|
+
#print ("def Column_Selection_Modal(self):")
|
256
|
+
|
257
|
+
dialog = Toplevel(self)
|
258
|
+
dialog.title("Select Columns")
|
259
|
+
|
260
|
+
# Make the dialog modal
|
261
|
+
dialog.transient(self)
|
262
|
+
dialog.grab_set()
|
263
|
+
|
264
|
+
#self.window.update_idletasks()
|
265
|
+
window_width = self.winfo_width()
|
266
|
+
window_height = self.winfo_height()
|
267
|
+
window_x = self.winfo_x()
|
268
|
+
window_y = self.winfo_y()
|
269
|
+
|
270
|
+
dialog_width = 300
|
271
|
+
dialog_height = 600
|
272
|
+
|
273
|
+
position_right = int(window_x + (window_width / 2) - (dialog_width / 2))
|
274
|
+
position_down = int(window_y + (window_height / 2) - (dialog_height / 2))
|
275
|
+
|
276
|
+
dialog.geometry(f"{dialog_width}x{dialog_height}+{position_right}+{position_down}")
|
277
|
+
|
278
|
+
# Configure grid rows and columns
|
279
|
+
for i in range(12): # Adjust the range as needed
|
280
|
+
dialog.rowconfigure(i,weight =1,uniform = 'a')
|
281
|
+
|
282
|
+
for i in range(3): # Adjust the range as needed
|
283
|
+
dialog.grid_columnconfigure(i, weight =1,minsize = 20,uniform = 'a')
|
284
|
+
|
285
|
+
# Get the column identifiers
|
286
|
+
column_ids = self.tree["columns"]
|
287
|
+
#print ("col ids",column_ids)
|
288
|
+
|
289
|
+
# Get the column headers
|
290
|
+
column_headers = [self.tree.heading(col)["text"] for col in column_ids]
|
291
|
+
|
292
|
+
# Get the column widths
|
293
|
+
column_widths = [self.tree.column(col)["width"] for col in column_ids]
|
294
|
+
|
295
|
+
|
296
|
+
self.column_vars = {}
|
297
|
+
for i, column in enumerate(column_headers):
|
298
|
+
# Check if the column is visible in self.tree
|
299
|
+
self.column_vars[column] = IntVar(master=dialog, value=1 if column_widths[i] > 0 else 0)
|
300
|
+
self.cb = []
|
301
|
+
|
302
|
+
# Create checkboxes for each column
|
303
|
+
for i, column in enumerate(column_headers):
|
304
|
+
#print(f"Column: {column}, Value: {self.column_vars[column].get()}")
|
305
|
+
self.cb.append( Checkbutton(dialog, text=column, variable=self.column_vars[column]))
|
306
|
+
#self.cb[i].grid(row=i+1, column=(i-1)/10, sticky='w')
|
307
|
+
self.cb[i].grid(row=i%10, column=i//10, sticky='w')
|
308
|
+
|
309
|
+
for i, column in enumerate(column_headers):
|
310
|
+
self.column_vars[column].set(1 if column_widths[i] > 0 else 0)
|
311
|
+
|
312
|
+
# OK button to apply the selection
|
313
|
+
ttk.Button(dialog, text="OK", command=lambda: self.apply_column_selection(dialog)).grid(row=13, column=1, ipady=10)
|
314
|
+
# Cancel button
|
315
|
+
def on_cancel():
|
316
|
+
#print("Cancel clicked")
|
317
|
+
dialog.destroy()
|
318
|
+
|
319
|
+
cancel_button = ttk.Button(dialog, text="Cancel", command=on_cancel)
|
320
|
+
cancel_button.grid(row=13,column = 2, sticky = "w",ipady = 10)
|
321
|
+
|
322
|
+
return
|
323
|
+
|
324
|
+
def apply_column_selection(self, dialog):
|
325
|
+
|
326
|
+
#print("def apply_column_selection")
|
327
|
+
|
328
|
+
# Get the selected columns
|
329
|
+
selected_columns = [column for column, var in self.column_vars.items() if var.get() == 1]
|
330
|
+
|
331
|
+
# Set the column widths based on the selection
|
332
|
+
for column in self.tree["columns"]:
|
333
|
+
column_text = self.tree.heading(column)["text"]
|
334
|
+
if column_text in selected_columns:
|
335
|
+
self.tree.column(column, width=250,stretch=True, anchor="center") # Set to a default width
|
336
|
+
else:
|
337
|
+
self.tree.column(column, width=0,stretch=False, anchor="center") # Hide the column
|
338
|
+
|
339
|
+
# Display the selected columns in the listbox
|
340
|
+
#for item in self.tree:
|
341
|
+
# display_text = " | ".join(str(item[column]) for column in selected_columns)
|
342
|
+
# self.listbox.insert(END, display_text)
|
343
|
+
# Close the dialog
|
344
|
+
dialog.destroy()
|
345
|
+
|
346
|
+
if __name__ == "__main__":
|
347
|
+
app = Application()
|
348
|
+
app.mainloop()
|
pycoustic/weather.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
import requests
|
2
|
+
import pandas as pd
|
3
|
+
import datetime as dt
|
4
|
+
|
5
|
+
|
6
|
+
appid = ""
|
7
|
+
with open("tests/openweather_app_id.txt") as f:
|
8
|
+
appid = f.readlines()[0]
|
9
|
+
|
10
|
+
w_dict = {
|
11
|
+
"start": "2022-09-16 12:00:00",
|
12
|
+
"end": "2022-09-17 18:00:00",
|
13
|
+
"interval": 6,
|
14
|
+
"api_key": appid,
|
15
|
+
"country": "GB",
|
16
|
+
"postcode": "WC1",
|
17
|
+
"tz": "GB"
|
18
|
+
}
|
19
|
+
|
20
|
+
def test_weather_obj(weather_test_dict):
|
21
|
+
hist = WeatherHistory(start=w_dict["start"], end=w_dict["end"], interval=w_dict["interval"],
|
22
|
+
api_key=w_dict["api_key"], country=w_dict["country"], postcode=w_dict["postcode"],
|
23
|
+
tz=w_dict["tz"])
|
24
|
+
hist.compute_weather_history()
|
25
|
+
return hist
|
26
|
+
|
27
|
+
#TODO: Make this take the start and end times of a Survey object.
|
28
|
+
#TODO: Implement post codes instead of coordinates
|
29
|
+
class WeatherHistory:
|
30
|
+
def __init__(self, start=None, end=None, interval=6, api_key="", country="GB", postcode="WC1", tz="",
|
31
|
+
units="metric"):
|
32
|
+
# self._history_df = pd.DataFrame()
|
33
|
+
if type(start) == str:
|
34
|
+
self._start = dt.datetime.strptime(start, "%Y-%m-%d %H:%M:%S")
|
35
|
+
else:
|
36
|
+
self._start = start
|
37
|
+
if type(end) == str:
|
38
|
+
self._end = dt.datetime.strptime(end, "%Y-%m-%d %H:%M:%S")
|
39
|
+
else:
|
40
|
+
self._end = end
|
41
|
+
self._interval = interval
|
42
|
+
self._api_key = str(api_key)
|
43
|
+
self._lat, self._lon = self.get_latlon(api_key=api_key, country=country, postcode=postcode)
|
44
|
+
self._hist = None
|
45
|
+
self._units = units
|
46
|
+
|
47
|
+
def get_latlon(self, api_key="", country="GB", postcode=""):
|
48
|
+
query = str("http://api.openweathermap.org/geo/1.0/zip?zip=" + postcode + "," + country + "&appid=" + api_key)
|
49
|
+
resp = requests.get(query)
|
50
|
+
return resp.json()["lat"], resp.json()["lon"]
|
51
|
+
|
52
|
+
def _construct_api_call(self, timestamp):
|
53
|
+
base = "https://api.openweathermap.org/data/3.0/onecall/timemachine?"
|
54
|
+
query = str(base + "lat=" + str(self._lat) + "&" + "lon=" + str(self._lon) + "&" + "units=" + self._units + \
|
55
|
+
"&" + "dt=" + str(timestamp) + "&" + "appid=" + self._api_key)
|
56
|
+
print(query)
|
57
|
+
return query
|
58
|
+
|
59
|
+
def _construct_timestamps(self):
|
60
|
+
next_time = (self._start + dt.timedelta(hours=self._interval))
|
61
|
+
timestamps = [int(self._start.timestamp())]
|
62
|
+
while next_time < self._end:
|
63
|
+
timestamps.append(int(next_time.timestamp()))
|
64
|
+
next_time += dt.timedelta(hours=self._interval)
|
65
|
+
return timestamps
|
66
|
+
|
67
|
+
def _make_and_parse_api_call(self, query):
|
68
|
+
response = requests.get(query)
|
69
|
+
print(response.json())
|
70
|
+
# This drops some unwanted cols like lat, lon, timezone and tz offset.
|
71
|
+
resp_dict = response.json()["data"][0]
|
72
|
+
del resp_dict["weather"] # delete weather key as not useful.
|
73
|
+
# TODO: parse 'weather' nested dict.
|
74
|
+
return resp_dict
|
75
|
+
|
76
|
+
def compute_weather_history(self):
|
77
|
+
# construct timestamps
|
78
|
+
timestamps = self._construct_timestamps()
|
79
|
+
# make calls to API
|
80
|
+
responses = []
|
81
|
+
for ts in timestamps:
|
82
|
+
print(f"ts: {ts}")
|
83
|
+
query = self._construct_api_call(timestamp=ts)
|
84
|
+
response_dict = self._make_and_parse_api_call(query=query)
|
85
|
+
responses.append(pd.Series(response_dict))
|
86
|
+
df = pd.concat(responses, axis=1).transpose()
|
87
|
+
for col in ["dt", "sunrise", "sunset"]:
|
88
|
+
df[col] = df[col].apply(lambda x: dt.datetime.fromtimestamp(int(x))) # convert timestamp into datetime
|
89
|
+
print(df)
|
90
|
+
self._hist = df
|
91
|
+
return df
|
92
|
+
|
93
|
+
def get_weather_history(self):
|
94
|
+
return self._hist
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.3
|
2
2
|
Name: pycoustic
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.2
|
4
4
|
Summary:
|
5
5
|
Author: thumpercastle
|
6
6
|
Author-email: tony.ryb@gmail.com
|
@@ -8,9 +8,12 @@ Requires-Python: >=3.10,<4.0
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
9
|
Classifier: Programming Language :: Python :: 3.10
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
11
13
|
Requires-Dist: numpy (==2.2.6)
|
12
14
|
Requires-Dist: openpyxl (==3.1.5)
|
13
15
|
Requires-Dist: pandas (==2.2.3)
|
16
|
+
Requires-Dist: streamlit (==1.46.0)
|
14
17
|
Description-Content-Type: text/markdown
|
15
18
|
|
16
19
|
|
@@ -0,0 +1,8 @@
|
|
1
|
+
pycoustic/__init__.py,sha256=Midt6cul_fLEpsI002sx-Yi_-gZV2Hs36mxIlnDs82I,48
|
2
|
+
pycoustic/log.py,sha256=pXSm01MHJ3gNGpJq14_lRV_NrF9S1ruHTK_77CDhsHs,17160
|
3
|
+
pycoustic/survey.py,sha256=SqttTArZuldrafOf7fMNSQLEuHo8oqM89KYxqTAtQ88,17897
|
4
|
+
pycoustic/tkgui.py,sha256=SGTAvJg1OzUcmfdi97TwThRvAPBJHPKKJi-jyrshors,13975
|
5
|
+
pycoustic/weather.py,sha256=bKdCQrn_Dg6WHIXXKUOvgfVn7n6r6AoVBBMcdlu7ViM,3739
|
6
|
+
pycoustic-0.1.2.dist-info/METADATA,sha256=t2f3wkScvHapwUHK5cElbmO4aR34oclGB2gEpNxvto8,574
|
7
|
+
pycoustic-0.1.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
8
|
+
pycoustic-0.1.2.dist-info/RECORD,,
|
pycoustic-0.1.1.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
pycoustic/__init__.py,sha256=Midt6cul_fLEpsI002sx-Yi_-gZV2Hs36mxIlnDs82I,48
|
2
|
-
pycoustic/log.py,sha256=TagYIfubFl5BnHwSPunELYejbr13mJvLoENCkHkSaJ4,17054
|
3
|
-
pycoustic/survey.py,sha256=fv5K7s-fOYks8ei-NRa19b-zWlT_ZQuDJMjKgYesXXM,17639
|
4
|
-
pycoustic-0.1.1.dist-info/METADATA,sha256=WQMdpIRk-aLBHE0efTyRMv29mlvPLWPNys5U_-r8GFA,436
|
5
|
-
pycoustic-0.1.1.dist-info/WHEEL,sha256=kLuE8m1WYU0Ig0_YEGrXyTtiJvKPpLpDEiChiNyei5Y,88
|
6
|
-
pycoustic-0.1.1.dist-info/RECORD,,
|