Aggiunti tempi di albe e tramonti con twilight zones
This commit is contained in:
commit
bbb4680c0a
15 changed files with 1752 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/crps/
|
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
8
.idea/OCR_Dome.iml
generated
Normal file
8
.idea/OCR_Dome.iml
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/OCR_Dome.iml" filepath="$PROJECT_DIR$/.idea/OCR_Dome.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
Con ocr01.py acquisisci i dati dagli screenshot (in modo cumulativo su all_measures.json)
|
||||
Poi con reload_data.py ottieni la versione ripulita: clean_data.json
|
||||
Poi con show_data crei i file di visualizzazione.
|
1248
all_measurements_saved.json
Normal file
1248
all_measurements_saved.json
Normal file
File diff suppressed because it is too large
Load diff
39
json_extensions.py
Normal file
39
json_extensions.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
from decimal import Decimal
|
||||
# from task_row_x import TaskRow, SimpleTaskRow
|
||||
|
||||
|
||||
def load_from_json_file(infile):
|
||||
with open(infile, 'r') as fin:
|
||||
obj = json.load(fin)
|
||||
return obj
|
||||
|
||||
|
||||
class SpecialEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal):
|
||||
return str(obj) # Or you could return str(obj) depending on your needs
|
||||
# if isinstance(obj, TaskRow) or isinstance(obj, SimpleTaskRow):
|
||||
if hasattr(obj, 'get_dict') and callable(getattr(obj, 'get_dict')):
|
||||
return obj.get_dict()
|
||||
if isinstance(obj, set):
|
||||
return [str(v) for v in obj]
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
def save_to_json_file(out_name, obj):
|
||||
with open(out_name, 'w', encoding='utf-8', newline='\n') as fout:
|
||||
json.dump(obj, fout, indent=4, cls=SpecialEncoder)
|
||||
|
||||
|
||||
def save_to_json_str(obj):
|
||||
return json.dumps(obj, indent=4, cls=SpecialEncoder)
|
||||
|
||||
def main():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
83
ocr01.py
Normal file
83
ocr01.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytesseract
|
||||
from PIL import Image
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
# import numpy as np
|
||||
import colorsys
|
||||
from json_extensions import save_to_json_file, load_from_json_file
|
||||
|
||||
save_file = Path('all_measurements.json')
|
||||
|
||||
|
||||
# FOLDER = Path('C:/Mc/Dome/Telecamere/ScreenShots')
|
||||
# FOLDER = Path('C:/Mc/Dome/Telecamere/ScreenShots2')
|
||||
FOLDER = Path('C:/Mc/Dome/Telecamere/ScreenShots3')
|
||||
|
||||
|
||||
CROPS = {
|
||||
'batt_full': (351, 1109, 389, 101),
|
||||
'name': (175, 143, 750, 109),
|
||||
# 'batt': (48, 320, 25, 50),
|
||||
'batt': (56, 345, 9, 2),
|
||||
'perc': (83, 314, 123, 60),
|
||||
}
|
||||
|
||||
|
||||
def available_screenshot(base_folder: Path):
|
||||
for f in base_folder.iterdir():
|
||||
if f.is_file() and f.suffix == '.jpg' and f.stem.startswith('Screenshot') and f.stem.endswith('0a7d545a89c1640e78c89a2ce00d525b'):
|
||||
yield f
|
||||
|
||||
|
||||
def main(base_folder: Path, out_folder=Path('crps')):
|
||||
# for sc in sorted(available_screenshot(base_folder), key=lambda x:x.stem):
|
||||
found = False
|
||||
n = 0
|
||||
if save_file.is_file():
|
||||
measures = load_from_json_file(save_file)
|
||||
else:
|
||||
measures = []
|
||||
for sc in available_screenshot(base_folder):
|
||||
n += 1
|
||||
_, f_date, _ = sc.stem.split('_')
|
||||
dt = datetime.strptime(f_date[:-3], '%Y-%m-%d-%H-%M-%S')
|
||||
# obj = {'dt': dt}
|
||||
obj = {'epoch': dt.timestamp()}
|
||||
# if f_date != '2025-03-16-07-59-52-21':
|
||||
# continue
|
||||
# print(sc.stem)
|
||||
src_img = Image.open(sc)
|
||||
for k, crp in CROPS.items():
|
||||
x0, y0, w, h = crp
|
||||
x1 = x0 + w
|
||||
y1 = y0 + h
|
||||
cropped = src_img.crop((x0, y0, x1, y1))
|
||||
if k == 'batt':
|
||||
# new_name = f'{k}_{f_date}.png'
|
||||
# cropped.save(out_folder / new_name)
|
||||
tot, grey = 0, 0
|
||||
for p in cropped.getdata():
|
||||
tot += 1
|
||||
r, g, b = p
|
||||
h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)
|
||||
if s < 0.1 and v > 0.1:
|
||||
grey += 1
|
||||
v = grey / tot
|
||||
# print(f'{v:.1f}')
|
||||
obj['charging'] = v > 0.8
|
||||
# obj['charging'] = is_mostly_white(cropped)
|
||||
if k != 'batt':
|
||||
text = pytesseract.image_to_string(cropped)
|
||||
obj[k] = text
|
||||
if k == 'batt_full' and len(text) > 3:
|
||||
found = True
|
||||
print(f'{f_date}: {obj}')
|
||||
measures.append(obj)
|
||||
save_to_json_file('all_measurements.json', measures)
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(FOLDER)
|
39
plt_example.py
Normal file
39
plt_example.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
import datetime
|
||||
|
||||
|
||||
def main():
|
||||
# Sample data
|
||||
epoch_times = [1696000000, 1696086400, 1696172800, 1696259200] # Epoch seconds
|
||||
y_values = [10, 20, 15, 25] # Some y-axis values
|
||||
|
||||
# Convert epoch times to datetime objects
|
||||
dates = [datetime.datetime.fromtimestamp(ts) for ts in epoch_times]
|
||||
|
||||
# Create the plot
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(dates, y_values, marker='o', linestyle='-')
|
||||
|
||||
# Format the x-axis as date
|
||||
# ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d\n%H:%M:%S')) # Customize as needed
|
||||
ax.xaxis.set_major_locator(mdates.AutoDateLocator()) # Automatically adjusts for readability
|
||||
ax.xaxis.set_major_formatter(
|
||||
mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()))
|
||||
|
||||
# Rotate date labels for better visibility
|
||||
plt.xticks(rotation=45)
|
||||
|
||||
# Labels and title
|
||||
plt.xlabel("Date & Time")
|
||||
plt.ylabel("Values")
|
||||
plt.title("Epoch to Human Readable Time Plot")
|
||||
|
||||
# Show the plot
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
33
reload_data.py
Normal file
33
reload_data.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
from json_extensions import load_from_json_file, save_to_json_file
|
||||
|
||||
|
||||
def main():
|
||||
clean_data: dict[str, list] = {}
|
||||
obj = load_from_json_file('all_measurements.json')
|
||||
names = set()
|
||||
for row in obj:
|
||||
name = row.get('name', '').strip('\n')
|
||||
perc_s = row.get('perc', '').strip('\n')
|
||||
charging = row.get('charging')
|
||||
epoch = row.get('epoch')
|
||||
batt_full_s = row.get('batt_full', '').strip('\n')
|
||||
batt_full = 'The battery is full' in batt_full_s
|
||||
if '%' not in perc_s:
|
||||
raise ValueError(f'Missing % in {row}')
|
||||
prc = int(perc_s.split('%', maxsplit=1)[0])
|
||||
names.add(name)
|
||||
if name not in clean_data:
|
||||
clean_data[name] = []
|
||||
new_row = {'epoch': epoch, 'soc': prc, 'battery_full': batt_full, 'charging': charging}
|
||||
print(name, new_row)
|
||||
clean_data[name].append(new_row)
|
||||
print(names)
|
||||
for lst in clean_data.values():
|
||||
lst.sort(key=lambda x: x['epoch'])
|
||||
save_to_json_file('clean_data.json', clean_data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
207
show_data.py
Normal file
207
show_data.py
Normal file
|
@ -0,0 +1,207 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
import matplotlib.patches as mpatches
|
||||
|
||||
from astral import LocationInfo
|
||||
from astral.sun import sun
|
||||
|
||||
import datetime
|
||||
from json_extensions import load_from_json_file
|
||||
|
||||
|
||||
city = LocationInfo("Rome", "Italy", "Europe/Rome", 44.448066, 11.270639)
|
||||
|
||||
|
||||
def main(data):
|
||||
# Sample data
|
||||
# epoch_times = [1696000000, 1696086400, 1696172800, 1696259200] # Epoch seconds
|
||||
# y_values = [10, 20, 15, 25] # Some y-axis values
|
||||
|
||||
# Create the plot
|
||||
fig, ax = plt.subplots(tight_layout=False, figsize=(30, 10))
|
||||
earliest = None
|
||||
latest = None
|
||||
for name_raw, serie in data.items():
|
||||
if name_raw in ('Security Camera', 'Ext Front'):
|
||||
continue
|
||||
name = {'Front door': 'Sample #1 No PV panel', 'Ext2': 'Sample #2 with PV Panel', }.get(name_raw, name_raw)
|
||||
dates = [datetime.datetime.fromtimestamp(row['epoch']) for row in serie]
|
||||
soc_v = [row['soc'] for row in serie]
|
||||
if name_raw == 'Front door':
|
||||
dates = dates[3:]
|
||||
soc_v = soc_v[3:]
|
||||
mi = min(dates)
|
||||
ma = max(dates)
|
||||
if earliest is None:
|
||||
earliest = mi
|
||||
latest = ma
|
||||
else:
|
||||
earliest = min(earliest, mi)
|
||||
latest = max(latest, ma)
|
||||
|
||||
# charging = [row['charging'] for row in serie]
|
||||
# batt_full = [row['battery_full'] for row in serie]
|
||||
#
|
||||
# sizes = []
|
||||
# for c1, c2 in zip(charging, batt_full):
|
||||
# if c1 and c2:
|
||||
# sizes.append(100) # Example: Both conditions true
|
||||
# elif c1:
|
||||
# sizes.append(200) # Example: Only condition 1 true
|
||||
# elif c2:
|
||||
# sizes.append(300) # Example: Only condition 2 true
|
||||
# else:
|
||||
# sizes.append(400) # Example: Both conditions false
|
||||
#
|
||||
|
||||
ax.plot(dates, soc_v, '-o', lw=3, label=name)
|
||||
|
||||
# for x, y, color in zip(dates, soc_v, sizes):
|
||||
|
||||
# ax.plot(dates, y_values, marker='o', linestyle='-')
|
||||
|
||||
print(earliest, latest)
|
||||
current_day = earliest
|
||||
first_time = True
|
||||
while current_day < latest:
|
||||
# print(current_day)
|
||||
# Get solar times
|
||||
s = sun(city.observer, date=current_day, tzinfo=city.timezone)
|
||||
dawn = s['dawn']
|
||||
sunrise = s['sunrise']
|
||||
sunset = s['sunset']
|
||||
dusk = s['dusk']
|
||||
if first_time:
|
||||
ax.axvspan(dawn, sunrise, color='deepskyblue', alpha=0.3, label='Twilight (-6°)')
|
||||
ax.axvspan(sunset, dusk, color='deepskyblue', alpha=0.3)
|
||||
ax.axvline(sunrise, color='orange', linestyle='--', label='Sunrise')
|
||||
ax.axvline(sunset, color='red', linestyle='--', label='Sunset')
|
||||
first_time = False
|
||||
else:
|
||||
ax.axvspan(dawn, sunrise, color='deepskyblue', alpha=0.3)
|
||||
ax.axvspan(sunset, dusk, color='deepskyblue', alpha=0.3)
|
||||
ax.axvline(sunrise, color='orange', linestyle='--')
|
||||
ax.axvline(sunset, color='red', linestyle='--')
|
||||
|
||||
current_day += datetime.timedelta(days=1)
|
||||
|
||||
# Format the x-axis as date
|
||||
# ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d\n%H:%M:%S')) # Customize as needed
|
||||
ax.xaxis.set_major_locator(
|
||||
mdates.AutoDateLocator(minticks=30, maxticks=60)) # Automatically adjusts for readability
|
||||
ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()))
|
||||
|
||||
x_tail = datetime.datetime(year=2025, month=4, day=7, hour=7, minute=20)
|
||||
y_tail = 85
|
||||
x_head = datetime.datetime(year=2025, month=4, day=7, hour=6, minute=59)
|
||||
y_head = 100
|
||||
dx = x_head - x_tail
|
||||
dy = y_head - y_tail
|
||||
|
||||
# Rotate date labels for better visibility
|
||||
# plt.xticks(rotation=45)
|
||||
|
||||
arrow = mpatches.FancyArrowPatch((x_tail, y_tail), (x_head, y_head), mutation_scale=50)
|
||||
ax.add_patch(arrow)
|
||||
|
||||
# Labels and title
|
||||
ax.grid()
|
||||
ax.legend(loc='lower right', framealpha=1.0)
|
||||
plt.xlabel("Date & Time")
|
||||
plt.ylabel("Reported State of Charge [%]")
|
||||
plt.title('Camera battery behaviour')
|
||||
|
||||
img_num = 1
|
||||
out_name = f'{img_num}_f11.png'
|
||||
img_num += 1
|
||||
fig.savefig(out_name, dpi=150)
|
||||
|
||||
st1 = datetime.datetime(year=2025, month=3, day=29)
|
||||
end1 = datetime.datetime(year=2025, month=4, day=8, hour=12)
|
||||
ax.set_xlim(st1, end1)
|
||||
txt = f'Problems:\nSample#2: After FW upgrade the problem looks the same\nSample#1: Discharge is not monotonic.'
|
||||
annt = ax.text(0.3, 0.1, txt, ha='left', fontsize=20, bbox={'facecolor': 'lightgray', 'alpha': 0.85, 'pad': 5},
|
||||
transform=ax.transAxes)
|
||||
# annt.set_text(txt)
|
||||
# print(txt)
|
||||
out_name = f'{img_num}_overview.png'
|
||||
# out_name_svg = f'{img_num}_overview.svg'
|
||||
out_name_pdf = f'{img_num}_overview.pdf'
|
||||
# fig.savefig(out_name_svg)
|
||||
fig.savefig(out_name_pdf)
|
||||
img_num += 1
|
||||
fig.savefig(out_name, dpi=200)
|
||||
|
||||
#
|
||||
# st1 = datetime.datetime(year=2025, month=3, day=1)
|
||||
# end1 = datetime.datetime(year=2025, month=3, day=3)
|
||||
# ax.set_xlim(st1, end1)
|
||||
# txt = f'We noticed battery level anomaly'
|
||||
# # print(txt)
|
||||
# annt = ax.text(0.1, 0.2, txt, ha='left', fontsize=20, bbox={'facecolor': 'cyan', 'alpha': 0.7, 'pad': 5},
|
||||
# transform=ax.transAxes)
|
||||
# print(annt, type(annt))
|
||||
# out_name = f'{img_num}_first_anomaly.png'
|
||||
# img_num += 1
|
||||
# fig.savefig(out_name, dpi=150)
|
||||
# # annt.remove()
|
||||
#
|
||||
# st1 = datetime.datetime(year=2025, month=3, day=17)
|
||||
# end1 = datetime.datetime(year=2025, month=3, day=19)
|
||||
# ax.set_xlim(st1, end1)
|
||||
# txt = f'We noticed battery level anomaly Again!'
|
||||
# annt.set_text(txt)
|
||||
# # print(txt)
|
||||
# out_name = f'{img_num}_second_anomaly.png'
|
||||
# img_num += 1
|
||||
# fig.savefig(out_name, dpi=150)
|
||||
#
|
||||
# st1 = datetime.datetime(year=2025, month=3, day=19)
|
||||
# end1 = datetime.datetime(year=2025, month=4, day=1)
|
||||
# ax.set_xlim(st1, end1)
|
||||
# txt = f'We unplugged the PV panel as asked by supplier'
|
||||
# annt.set_text(txt)
|
||||
# # print(txt)
|
||||
# out_name = f'{img_num}_unplugged.png'
|
||||
# img_num += 1
|
||||
# fig.savefig(out_name, dpi=150)
|
||||
#
|
||||
# st1 = datetime.datetime(year=2025, month=3, day=29)
|
||||
# end1 = datetime.datetime(year=2025, month=4, day=1)
|
||||
# ax.set_xlim(st1, end1)
|
||||
# txt = f'We started testing a second sample'
|
||||
# annt.set_text(txt)
|
||||
# # print(txt)
|
||||
# out_name = f'{img_num}_Sample2.png'
|
||||
# img_num += 1
|
||||
# fig.savefig(out_name, dpi=150)
|
||||
#
|
||||
# st1 = datetime.datetime(year=2025, month=3, day=30, hour=17, minute=30)
|
||||
# end1 = datetime.datetime(year=2025, month=3, day=31, hour=9, minute=30)
|
||||
# ax.set_xlim(st1, end1)
|
||||
# txt = f'There are Anomalies in the evening and in the morning'
|
||||
# annt.set_text(txt)
|
||||
# # print(txt)
|
||||
# out_name = f'{img_num}_Sample2_detail1.png'
|
||||
# img_num += 1
|
||||
# fig.savefig(out_name, dpi=150)
|
||||
#
|
||||
# st1 = datetime.datetime(year=2025, month=3, day=31, hour=15, minute=30)
|
||||
# end1 = datetime.datetime(year=2025, month=4, day=1, hour=10)
|
||||
# ax.set_xlim(st1, end1)
|
||||
# txt = f'Second day Sample#2 still show anomaly\nSample#1 has increasing battery level (Impossible)'
|
||||
# annt.set_text(txt)
|
||||
# # print(txt)
|
||||
# out_name = f'{img_num}_Sample2_detail2.png'
|
||||
# img_num += 1
|
||||
# fig.savefig(out_name, dpi=150)
|
||||
|
||||
# Show the plot
|
||||
# plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cln_data = load_from_json_file('clean_data.json')
|
||||
main(cln_data)
|
61
test_twilight.py
Normal file
61
test_twilight.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import matplotlib.dates as mdates
|
||||
from datetime import datetime, timedelta
|
||||
from astral import LocationInfo
|
||||
from astral.sun import sun
|
||||
|
||||
|
||||
def main():
|
||||
# Set your location
|
||||
city = LocationInfo("Rome", "Italy", "Europe/Rome", 44.448066, 11.270639)
|
||||
|
||||
# Choose the date
|
||||
# date = datetime(2023, 3, 29)
|
||||
# dawn: 2023-03-29 06:32:45.166761+02:00
|
||||
# sunrise: 2023-03-29 07:02:31.106176+02:00
|
||||
# noon: 2023-03-29 13:19:52+02:00
|
||||
# sunset: 2023-03-29 19:37:45.931604+02:00
|
||||
# dusk: 2023-03-29 20:07:37.090448+02:00
|
||||
date = datetime(2023, 3, 29)
|
||||
|
||||
# Get solar times
|
||||
s = sun(city.observer, date=date, tzinfo=city.timezone)
|
||||
for k, v in s.items():
|
||||
print(f'{k}: {v}')
|
||||
dawn = s['dawn']
|
||||
sunrise = s['sunrise']
|
||||
sunset = s['sunset']
|
||||
dusk = s['dusk']
|
||||
|
||||
# Generate time series data for one day
|
||||
times = [date + timedelta(minutes=15 * i) for i in range(96 * 1)]
|
||||
values = np.sin(np.linspace(0, 4 * np.pi, len(times))) # Some mock data
|
||||
|
||||
# Create the plot
|
||||
fig, ax = plt.subplots(figsize=(10, 5))
|
||||
ax.plot(times, values, label="Sensor Data", color='tab:blue')
|
||||
|
||||
# Plot twilight periods as vertical bands
|
||||
ax.axvspan(dawn, sunrise, color='lightskyblue', alpha=0.3, label='Morning Twilight')
|
||||
ax.axvspan(sunset, dusk, color='lightskyblue', alpha=0.3, label='Evening Twilight')
|
||||
|
||||
# Mark sunrise and sunset
|
||||
ax.axvline(sunrise, color='orange', linestyle='--', label='Sunrise')
|
||||
ax.axvline(sunset, color='red', linestyle='--', label='Sunset')
|
||||
|
||||
# Formatting
|
||||
ax.set_title(f"Daylight and Twilight - {city.name} on {date.strftime('%Y-%m-%d')}")
|
||||
ax.set_xlabel("Time")
|
||||
ax.set_ylabel("Sensor Value")
|
||||
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
||||
plt.xticks(rotation=45)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Reference in a new issue