diff --git a/.github/workflows/build-and-push-image.yaml b/.github/workflows/build-and-push-image.yaml
index 08013287..7736580c 100644
--- a/.github/workflows/build-and-push-image.yaml
+++ b/.github/workflows/build-and-push-image.yaml
@@ -2,7 +2,7 @@ name: Create and publish Docker image
on:
push:
- branches: ['main', 'release', 'project_structure']
+ branches: ['develop', 'release']
env:
REGISTRY: ghcr.io
diff --git a/bitbot.dockerfile b/bitbot.dockerfile
index 9a6e6b81..7a70d1ac 100644
--- a/bitbot.dockerfile
+++ b/bitbot.dockerfile
@@ -1,35 +1,45 @@
-FROM python:3.11-slim-bookworm
+# Stage 1: Build stage
+FROM python:3.11-slim-bookworm AS builder
-# avoid bytecode baggage
+# Avoid bytecode baggage
ENV PYTHONDONTWRITEBYTECODE=1
-# install OS reqs
-RUN apt-get update -y && \
- apt-get install -y \
- --no-install-recommends \
- libopenblas-dev libopenjp2-7 libtiff6 libxcb1 libfreetype6-dev \
- && rm -rf /var/lib/apt/lists/*
-
-# venv to keep python happy
+# Create a virtual environment
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
-# update pip
+# Update pip
RUN python3 -m pip install --upgrade pip --no-cache-dir
-# download python reqs
+# Copy and install Python dependencies
COPY requirements.txt .
-RUN python3 -m pip download \
- -r requirements.txt \
- --extra-index-url https://www.piwheels.org/simple
-# install python reqs
RUN python3 -m pip install -v \
--prefer-binary \
-r requirements.txt \
- --extra-index-url https://www.piwheels.org/simple
+ --extra-index-url https://www.piwheels.org/simple \
+ --no-cache-dir
-# app code
+# Stage 2: Final stage
+FROM python:3.11-slim-bookworm
+
+# Avoid bytecode baggage
+ENV PYTHONDONTWRITEBYTECODE=1
+
+# Install only the necessary runtime dependencies
+RUN apt-get update -y && \
+ apt-get install -y \
+ --no-install-recommends \
+ libopenblas-dev libopenjp2-7 libtiff6 libxcb1 libfreetype6-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy the virtual environment from the builder stage
+COPY --from=builder /opt/venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# Copy only the necessary application code
WORKDIR /code
COPY . .
+
+# Set the default command
CMD ["python", "run.py"]
\ No newline at end of file
diff --git a/config/config.ini b/config/config.ini
index 2667eee5..80845995 100644
--- a/config/config.ini
+++ b/config/config.ini
@@ -32,7 +32,7 @@ enabled = false
[tide_times]
enabled = false
-location_id = 9414290
+location_id = 0184
[comments]
up = moon,yolo,pump it,gentlemen
diff --git a/docs/development.md b/docs/development.md
index 4ec9d0c8..7cccb4da 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -41,7 +41,7 @@ UML diagram of broad [package interactions](http://www.plantuml.com/plantuml/svg
- [`Pillow`](https://github.com/python-pillow/Pillow) draws **drawing overlay** text onto the graph
## 🐳 Docker
-**Github actions** builds and tests and publishes a **container image** on each commit to `main` and `release`
+**Github actions** builds and tests and publishes a **container image** on each commit to `develop` and `release`
🐳 `x86` faster than the Pi.
```sh
diff --git a/index.html b/index.html
deleted file mode 100644
index 6e7fdfe8..00000000
--- a/index.html
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
- 🤖 Bitbot
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 🤖 BitBot Config
-
- 🖼️ Last refresh
-
-
-
- ⚙️ Application Config
-
- Edit 'config.ini' to set the exchange, currency, screen refresh, overlay and layout options
-
-
-
-
-
- 📈 Chart Styles
-
- These files manage the colours and layout of the chart plot, they are matplolib style files and follow the example defined here
-
-
-
- 🌳 Logs
-
- Check here if something isn't working!
-
-
-
-
-
\ No newline at end of file
diff --git a/src/bitbot.py b/src/bitbot.py
index 5f799178..120338b1 100644
--- a/src/bitbot.py
+++ b/src/bitbot.py
@@ -2,7 +2,6 @@
from os.path import exists
import io
-from matplotlib import font_manager
from src.drawing.market_charts.mpf_plotted_chart import MplFinanceChart
from src.exchanges import crypto_exchanges, stock_exchanges
from src.configuration.log_decorator import info_log
@@ -15,10 +14,6 @@
class BitBot():
- def __init__(self, config, display):
- self.config = config
- self.display = display
-
def __init__(self, config, files):
self.config = config
self.files = files
@@ -58,9 +53,9 @@ def display_chart(self):
@info_log
def display_message(self, message):
- img = Image.new("P", self.display.size())
+ img = Image.new("RGBA", self.display.size(), (255, 0, 0, 0))
draw = ImageDraw.Draw(img)
- centered_text(draw, message, self.display.title_font, self.display.size(), border=True)
+ centered_text(draw, message, self.display.title_font, img.size, border=True, pos="centre")
self.display.show(img)
return img
@@ -89,13 +84,13 @@ def display_tide_times(self):
@info_log
def display_connection_error(self):
self.display_message("""
- NO INTERNET CONNECTION
- ----------------------------
- Please check your WIFI
- ----------------------------
- To configure WiFi access,
- connect to 'bitbot-' WiFi AP
- and follow the instructions""")
+NO INTERNET CONNECTION
+----------------------------
+Please check your WIFI
+----------------------------
+To configure WiFi access,
+connect to 'comitup-' WiFi AP
+and follow the instructions""")
def __repr__(self):
return f''
diff --git a/src/configuration/bitbot_config.py b/src/configuration/bitbot_config.py
index 655b75f9..740884bc 100644
--- a/src/configuration/bitbot_config.py
+++ b/src/configuration/bitbot_config.py
@@ -220,9 +220,10 @@ def tide_times_enabled(self):
def tide_location_id(self):
return self.config['tide_times']["location_id"]
- # timed meaages
+ # timed messages
def today_has_special_message(self, datetime):
return self.special_message(datetime) != None
def special_message(self, datetime):
return self.config.get('special_messages', datetime.strftime("%YYYY-%MM-%DD"), fallback=None)
+
diff --git a/src/drawing/image_utils/Align.py b/src/drawing/image_utils/Align.py
index 6f7c1793..b9312732 100644
--- a/src/drawing/image_utils/Align.py
+++ b/src/drawing/image_utils/Align.py
@@ -2,21 +2,24 @@
class Align:
- def TopRight(display, message_size):
- return (display.size[0] - message_size[0] - padding - 1, padding)
+ def TopRight(display_size, message_size):
+ return (display_size[0] - message_size[0] - padding - 1, padding)
- def BottomRight(display, message_size):
- return (display.size[0] - message_size[0], display.size[1] - message_size[1])
+ def BottomRight(display_size, message_size):
+ display_width_minus_text_width = display_size[0] - message_size[0]
+ display_height_minus_text_height = display_size[1] - message_size[1]
+
+ return (display_width_minus_text_width, display_height_minus_text_height)
- def BottomLeft(display, message_size):
- return (0, display.size[1] - message_size[1])
+ def BottomLeft(display_size, message_size):
+ return (0, display_size[1] - message_size[1])
- def TopLeft(display, message_size):
+ def TopLeft(display_size, message_size):
return (0 + padding + 1, 0 + padding + 1)
- def Centre(display, message_size):
- message_y = (display.size[1][1] - message_size[1]) / 2
- message_x = (display.size[1][0] - message_size[0]) / 2
+ def Centre(display_size, message_size):
+ message_y = (display_size[0] - message_size[0]) / 2
+ message_x = (display_size[1] - message_size[1]) / 2
return (message_y, message_x)
# 🏳️ select image area with the most white pixels
diff --git a/src/drawing/image_utils/CenteredText.py b/src/drawing/image_utils/CenteredText.py
index fc8479b1..59a24a9e 100644
--- a/src/drawing/image_utils/CenteredText.py
+++ b/src/drawing/image_utils/CenteredText.py
@@ -8,15 +8,15 @@ def centered_text(draw, text, font, container_size, pos='centre', border=False):
# 📏 where to position the message
if pos == 'centre':
- message_x, message_y = Align.Centre(container_size, message_size)
+ message_y, message_x = Align.Centre(container_size, message_size)
elif pos == 'topright':
- message_x, message_y = Align.TopRight(container_size, message_size)
+ message_y, message_x = Align.TopRight(container_size, message_size)
elif pos == 'topleft':
- message_x, message_y = Align.TopLeft(container_size, message_size)
+ message_y, message_x = Align.TopLeft(container_size, message_size)
# 🖊️ draw the message at position
draw.multiline_text(
- (message_x, message_y),
+ (message_y, message_x),
text,
fill='black',
font=font,
@@ -24,8 +24,8 @@ def centered_text(draw, text, font, container_size, pos='centre', border=False):
# 📏 measure border box
if border:
- x0, y0 = (message_x - padding, message_y - padding)
- x1 = message_x + message_size[0] + padding
- y1 = message_y + message_size[1] + padding
+ y0, x0 = (message_y - padding, message_x - padding)
+ y1 = message_y + message_size[0] + padding
+ x1 = message_x + message_size[1] + padding
# 🖊️ draw box at position
- draw.rectangle([(x0, y0), (x1, y1)], outline='red')
+ draw.rectangle([(y0, x0), (y1, x1)], outline='red')
diff --git a/src/drawing/image_utils/DrawText.py b/src/drawing/image_utils/DrawText.py
index 1fa98df6..b7080408 100644
--- a/src/drawing/image_utils/DrawText.py
+++ b/src/drawing/image_utils/DrawText.py
@@ -59,5 +59,5 @@ def __init__(self, text, font, colour='black', align=None):
self.align = align
def draw_on(self, draw, pos=(0, 0)):
- pos = self.align(draw.im, self.size) if self.align else pos
+ pos = self.align(draw.im.size, self.size) if self.align else pos
draw.text(pos, self.text, self.colour, self.font)
\ No newline at end of file
diff --git a/src/drawing/image_utils/TextBlock.py b/src/drawing/image_utils/TextBlock.py
index f0d2eb2a..8e0be54d 100644
--- a/src/drawing/image_utils/TextBlock.py
+++ b/src/drawing/image_utils/TextBlock.py
@@ -1,6 +1,6 @@
from .DrawText import DrawText
-
+# texts is array of drawtext
class TextBlock:
def __init__(self, texts, align=None):
self.texts = texts
@@ -26,5 +26,5 @@ def draw_on(self, draw, pos=(0, 0)):
def draw_text_row(self, draw, x_pos, y_pos, row):
for text in row:
- text.draw_on(draw, (x_pos, y_pos))
+ text.draw_on(draw, pos=(x_pos, y_pos))
x_pos += DrawText.width(text)
\ No newline at end of file
diff --git a/src/drawing/intro.py b/src/drawing/intro.py
index 1d1b1524..1f246735 100644
--- a/src/drawing/intro.py
+++ b/src/drawing/intro.py
@@ -2,6 +2,9 @@
import time
from .image_utils.CenteredText import centered_text
from ..configuration.network_utils import wait_for_internet_connection
+
+from src.configuration.network_utils import get_ip
+
page1 = '''I'm Bitbot.
I can chart crypto and stock markets.
@@ -20,10 +23,10 @@
Once I have internet access,
I will load the next page...'''
-page3 = '''Good job! I'm connected :D
+page3 = f'''Good job! I'm connected :D
To change my config,
-visit 'http://bitbot:8080'
+visit 'http://{get_ip()}:8080'
from any device in your network.
In 30 seconds time,
diff --git a/src/drawing/market_charts/chart_overlay.py b/src/drawing/market_charts/chart_overlay.py
index 18f251bd..f81c9801 100644
--- a/src/drawing/market_charts/chart_overlay.py
+++ b/src/drawing/market_charts/chart_overlay.py
@@ -14,12 +14,11 @@
class ChartOverlay():
def __init__(self, config, display, chart_data):
self.config = config
- self.display = display
self.chart_data = chart_data
- self.title_font = self.display.title_font
- self.price_font = self.display.price_font
- self.medium_font = self.display.medium_font
- self.tiny_font = self.display.tiny_font
+ self.title_font = display.title_font
+ self.price_font = display.price_font
+ self.medium_font = display.medium_font
+ self.tiny_font = display.tiny_font
@info_log
def draw_on(self, chart_image):
@@ -104,7 +103,7 @@ def price_increasing(self, chartdata):
return chartdata.start_price() < chartdata.last_close()
def format_time(self):
- return datetime.now().strftime("%-H:%M%b%-d")
+ return datetime.now().strftime("%-H:%M %b%-d")
def ai_comments(self):
return self.config.get_price_action_comments()
diff --git a/src/drawing/tide_times/tidal_graph.py b/src/drawing/tide_times/tidal_graph.py
index fe9433f7..7436cd5f 100644
--- a/src/drawing/tide_times/tidal_graph.py
+++ b/src/drawing/tide_times/tidal_graph.py
@@ -2,42 +2,52 @@
import matplotlib.pyplot as plt
from datetime import datetime
from PIL import Image
+import matplotlib as mpl
-def get_noaa_tide_data(station_id):
-# date = datetime.date.today().strftime("%Y-%m-%d")
+easytide_base_url = "https://easytide.admiralty.co.uk/Home/GetPredictionData"
-# # Using NOAA Tides & Currents API
-# # station_id = '9414290' # Example station ID for San Francisco
-# api_url = f"https://environment.data.gov.uk/flood-monitoring/id/stations/{station_id}/readings?_sorted&date={date}"
-# response = requests.get(api_url)
-# response.raise_for_status() # Raise an exception for bad status codes
-# data = response.json()
+def get_tide_data(station_id):
+ params = {
+ 'stationId': station_id,
+ }
- url = f'https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?product=predictions&application=NOS.COOPS.TAC.WL&begin_date={datetime.now().strftime("%Y%m%d")}&range=168&datum=MLLW&station={station_id}&time_zone=lst_ldt&units=metric&interval=h&format=json'
+ response = requests.get(easytide_base_url, params=params)
+ if response.status_code != 200:
+ raise Exception(f"Failed to fetch data: HTTP {response.status_code}")
+
+ data = response.json()
+
+ if not data["tidalEventList"]:
+ raise Exception("No tide data found in the response")
- try:
- response = requests.get(url)
- response.raise_for_status()
- data = response.json()
-
- tide_data = []
- for prediction in data['predictions']:
- tide_data.append({
- 'date': prediction['t'],
- 'height': float(prediction['v'])
- })
- return tide_data
- except Exception as e:
- print(f"Error fetching tide data: {e}")
- return []
+ tide_data = []
+ for entry in data["tidalEventList"]:
+ date = datetime.strptime(entry['dateTime'], "%Y-%m-%dT%H:%M:%S").strftime('%Y-%m-%d %H:%M')
+ height = entry['height']
+ tide_data.append({'date': date, 'height': height})
+
+ return tide_data
def render_tide_chart(location_id, img_buf):
- tide_data = get_noaa_tide_data(location_id)
+ tide_data = get_tide_data(location_id)
from datetime import datetime
dates = [datetime.strptime(d['date'], '%Y-%m-%d %H:%M') for d in tide_data]
heights = [d['height'] for d in tide_data]
+ mpl.rcParams["text.hinting_factor"] = "1"
+ mpl.rcParams["text.hinting"] = "native"
+ mpl.rcParams["text.antialiased"] = "False"
+ mpl.rcParams["patch.antialiased"] = "False"
+ mpl.rcParams["lines.antialiased"] = "False"
+ mpl.rcParams["font.family"] = "sans-serif"
+ mpl.rcParams["font.sans-serif"] = "basis33"
+ mpl.rcParams["font.size"] = "11"
+ mpl.rcParams["axes.linewidth"] = "0.5"
+ mpl.rcParams["grid.linestyle"] = "--"
+ mpl.rcParams["grid.linewidth"] = "0.5"
+ mpl.rcParams["grid.color"] = "red"
+
plt.figure(figsize=(4, 3))
plt.plot(dates, heights)
plt.title('Next 7 Days')
@@ -62,14 +72,13 @@ def render_tide_chart(location_id, img_buf):
for date, height in zip(dates, heights):
day = date.strftime('%Y-%m-%d')
- if day_counts[day] == 24: # Only consider days with 24 data points
- if day not in daily_min_max:
- daily_min_max[day] = {'min': (date, height), 'max': (date, height)}
- else:
- if height < daily_min_max[day]['min'][1]:
- daily_min_max[day]['min'] = (date, height)
- if height > daily_min_max[day]['max'][1]:
- daily_min_max[day]['max'] = (date, height)
+ if day not in daily_min_max:
+ daily_min_max[day] = {'min': (date, height), 'max': (date, height)}
+ else:
+ if height < daily_min_max[day]['min'][1]:
+ daily_min_max[day]['min'] = (date, height)
+ if height > daily_min_max[day]['max'][1]:
+ daily_min_max[day]['max'] = (date, height)
# Add labels for min and max
for day, values in daily_min_max.items():
@@ -77,20 +86,20 @@ def render_tide_chart(location_id, img_buf):
plt.annotate(
f"{values['min'][0].strftime('%-I:%p')}",
xy=(values['min'][0], values['min'][1]),
- xytext=(-15, 0),
+ xytext=(-15, -5),
textcoords='offset points',
ha='center',
- fontsize=8,
+ fontsize=11,
color='red'
)
# Label for maximum
plt.annotate(
f"{values['max'][0].strftime('%-I:%p')}",
xy=(values['max'][0], values['max'][1]),
- xytext=(14, 0),
+ xytext=(14, 5),
textcoords='offset points',
ha='center',
- fontsize=8,
+ fontsize=11,
color='blue'
)
diff --git a/src/drawing/youtube_stats/subscriber_counter.py b/src/drawing/youtube_stats/subscriber_counter.py
index c724b36a..2baa344e 100644
--- a/src/drawing/youtube_stats/subscriber_counter.py
+++ b/src/drawing/youtube_stats/subscriber_counter.py
@@ -23,5 +23,5 @@ def play(self):
text_to_draw = f"{subscriber_count} Subscribers"
img = Image.new("RGBA", self.display_size.size, transparent)
draw = ImageDraw.Draw(img)
- centered_text(draw, text_to_draw, self.font, self.display_size, 'centre')
+ centered_text(draw, text_to_draw, self.font, self.display_size.size, 'centre')
return img
\ No newline at end of file
diff --git a/test.dockerfile b/test.dockerfile
deleted file mode 100644
index 7a70d1ac..00000000
--- a/test.dockerfile
+++ /dev/null
@@ -1,45 +0,0 @@
-# Stage 1: Build stage
-FROM python:3.11-slim-bookworm AS builder
-
-# Avoid bytecode baggage
-ENV PYTHONDONTWRITEBYTECODE=1
-
-# Create a virtual environment
-RUN python3 -m venv /opt/venv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# Update pip
-RUN python3 -m pip install --upgrade pip --no-cache-dir
-
-# Copy and install Python dependencies
-COPY requirements.txt .
-
-RUN python3 -m pip install -v \
- --prefer-binary \
- -r requirements.txt \
- --extra-index-url https://www.piwheels.org/simple \
- --no-cache-dir
-
-# Stage 2: Final stage
-FROM python:3.11-slim-bookworm
-
-# Avoid bytecode baggage
-ENV PYTHONDONTWRITEBYTECODE=1
-
-# Install only the necessary runtime dependencies
-RUN apt-get update -y && \
- apt-get install -y \
- --no-install-recommends \
- libopenblas-dev libopenjp2-7 libtiff6 libxcb1 libfreetype6-dev \
- && rm -rf /var/lib/apt/lists/*
-
-# Copy the virtual environment from the builder stage
-COPY --from=builder /opt/venv /opt/venv
-ENV PATH="/opt/venv/bin:$PATH"
-
-# Copy only the necessary application code
-WORKDIR /code
-COPY . .
-
-# Set the default command
-CMD ["python", "run.py"]
\ No newline at end of file
diff --git a/tests/data/whitby_tides.json b/tests/data/whitby_tides.json
new file mode 100644
index 00000000..11570c9e
--- /dev/null
+++ b/tests/data/whitby_tides.json
@@ -0,0 +1 @@
+{"tidalEventList":[{"eventType":1,"dateTime":"2025-02-08T06:18:00","height":2.250927077638894,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-08T00:00:00"},{"eventType":0,"dateTime":"2025-02-08T12:33:00","height":4.50918972731683,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-08T00:00:00"},{"eventType":1,"dateTime":"2025-02-08T19:01:00","height":2.0853662323155526,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-08T00:00:00"},{"eventType":0,"dateTime":"2025-02-09T01:15:00","height":4.6573249943865145,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-09T00:00:00"},{"eventType":1,"dateTime":"2025-02-09T07:41:00","height":2.1723152205092107,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-09T00:00:00"},{"eventType":0,"dateTime":"2025-02-09T13:46:00","height":4.730628059134607,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-09T00:00:00"},{"eventType":1,"dateTime":"2025-02-09T20:15:00","height":1.763520214165164,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-09T00:00:00"},{"eventType":0,"dateTime":"2025-02-10T02:24:00","height":4.858152287952238,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-10T00:00:00"},{"eventType":1,"dateTime":"2025-02-10T08:41:00","height":1.9814674061220303,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-10T00:00:00"},{"eventType":0,"dateTime":"2025-02-10T14:42:00","height":5.004950966474468,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-10T00:00:00"},{"eventType":1,"dateTime":"2025-02-10T21:11:00","height":1.4278918078855554,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-10T00:00:00"},{"eventType":0,"dateTime":"2025-02-11T03:16:00","height":5.059063440048395,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-11T00:00:00"},{"eventType":1,"dateTime":"2025-02-11T09:28:00","height":1.779085496410402,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-11T00:00:00"},{"eventType":0,"dateTime":"2025-02-11T15:27:00","height":5.247440375380359,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-11T00:00:00"},{"eventType":1,"dateTime":"2025-02-11T21:56:00","height":1.16184957015816,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-11T00:00:00"},{"eventType":0,"dateTime":"2025-02-12T03:59:00","height":5.200586351290798,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-12T00:00:00"},{"eventType":1,"dateTime":"2025-02-12T10:07:00","height":1.6071006495793392,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-12T00:00:00"},{"eventType":0,"dateTime":"2025-02-12T16:07:00","height":5.425011191464042,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-12T00:00:00"},{"eventType":1,"dateTime":"2025-02-12T22:36:00","height":0.9928605605025923,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-12T00:00:00"},{"eventType":0,"dateTime":"2025-02-13T04:36:00","height":5.270786648463542,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-13T00:00:00"},{"eventType":1,"dateTime":"2025-02-13T10:42:00","height":1.484266893296131,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-13T00:00:00"},{"eventType":0,"dateTime":"2025-02-13T16:43:00","height":5.528284041729102,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-13T00:00:00"},{"eventType":1,"dateTime":"2025-02-13T23:11:00","height":0.9233890962693343,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-13T00:00:00"},{"eventType":0,"dateTime":"2025-02-14T05:10:00","height":5.275528669494031,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-14T00:00:00"},{"eventType":1,"dateTime":"2025-02-14T11:14:00","height":1.4205558364075426,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-14T00:00:00"},{"eventType":0,"dateTime":"2025-02-14T17:16:00","height":5.557999002562121,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-14T00:00:00"},{"eventType":1,"dateTime":"2025-02-14T23:42:00","height":0.944651202343493,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-14T00:00:00"},{"eventType":0,"dateTime":"2025-02-15T05:42:00","height":5.226291012611799,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-15T00:00:00"},{"eventType":1,"dateTime":"2025-02-15T11:43:00","height":1.419590908938737,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-15T00:00:00"},{"eventType":0,"dateTime":"2025-02-15T17:47:00","height":5.519299591512786,"isApproximateTime":null,"isApproximateHeight":null,"date":"2025-02-15T00:00:00"}],"tidalHeightOccurrenceList":[{"dateTime":"2025-02-08T23:00:00Z","height":3.916645},{"dateTime":"2025-02-08T23:30:00Z","height":4.185804},{"dateTime":"2025-02-09T00:00:00Z","height":4.404631},{"dateTime":"2025-02-09T00:30:00Z","height":4.562275},{"dateTime":"2025-02-09T01:00:00Z","height":4.645841},{"dateTime":"2025-02-09T01:30:00Z","height":4.648231},{"dateTime":"2025-02-09T02:00:00Z","height":4.570677},{"dateTime":"2025-02-09T02:30:00Z","height":4.422385},{"dateTime":"2025-02-09T03:00:00Z","height":4.21686},{"dateTime":"2025-02-09T03:30:00Z","height":3.969473},{"dateTime":"2025-02-09T04:00:00Z","height":3.695627},{"dateTime":"2025-02-09T04:30:00Z","height":3.40792},{"dateTime":"2025-02-09T05:00:00Z","height":3.116596},{"dateTime":"2025-02-09T05:30:00Z","height":2.835423},{"dateTime":"2025-02-09T06:00:00Z","height":2.584642},{"dateTime":"2025-02-09T06:30:00Z","height":2.384243},{"dateTime":"2025-02-09T07:00:00Z","height":2.246322},{"dateTime":"2025-02-09T07:30:00Z","height":2.178079},{"dateTime":"2025-02-09T08:00:00Z","height":2.189037},{"dateTime":"2025-02-09T08:30:00Z","height":2.287581},{"dateTime":"2025-02-09T09:00:00Z","height":2.469514},{"dateTime":"2025-02-09T09:30:00Z","height":2.716126},{"dateTime":"2025-02-09T10:00:00Z","height":3.005216},{"dateTime":"2025-02-09T10:30:00Z","height":3.319045},{"dateTime":"2025-02-09T11:00:00Z","height":3.640694},{"dateTime":"2025-02-09T11:30:00Z","height":3.949819},{"dateTime":"2025-02-09T12:00:00Z","height":4.22665},{"dateTime":"2025-02-09T12:30:00Z","height":4.4562},{"dateTime":"2025-02-09T13:00:00Z","height":4.624896},{"dateTime":"2025-02-09T13:30:00Z","height":4.717224},{"dateTime":"2025-02-09T14:00:00Z","height":4.72096},{"dateTime":"2025-02-09T14:30:00Z","height":4.634716},{"dateTime":"2025-02-09T15:00:00Z","height":4.467853},{"dateTime":"2025-02-09T15:30:00Z","height":4.234976},{"dateTime":"2025-02-09T16:00:00Z","height":3.952256},{"dateTime":"2025-02-09T16:30:00Z","height":3.63539},{"dateTime":"2025-02-09T17:00:00Z","height":3.297398},{"dateTime":"2025-02-09T17:30:00Z","height":2.95044},{"dateTime":"2025-02-09T18:00:00Z","height":2.612124},{"dateTime":"2025-02-09T18:30:00Z","height":2.3066},{"dateTime":"2025-02-09T19:00:00Z","height":2.056223},{"dateTime":"2025-02-09T19:30:00Z","height":1.875637},{"dateTime":"2025-02-09T20:00:00Z","height":1.777164},{"dateTime":"2025-02-09T20:30:00Z","height":1.776093},{"dateTime":"2025-02-09T21:00:00Z","height":1.882673},{"dateTime":"2025-02-09T21:30:00Z","height":2.090288},{"dateTime":"2025-02-09T22:00:00Z","height":2.37758},{"dateTime":"2025-02-09T22:30:00Z","height":2.720788},{"dateTime":"2025-02-09T23:00:00Z","height":3.098193},{"dateTime":"2025-02-09T23:30:00Z","height":3.485305},{"dateTime":"2025-02-10T00:00:00Z","height":3.855635},{"dateTime":"2025-02-10T00:30:00Z","height":4.188873},{"dateTime":"2025-02-10T01:00:00Z","height":4.471927},{"dateTime":"2025-02-10T01:30:00Z","height":4.689947},{"dateTime":"2025-02-10T02:00:00Z","height":4.82313},{"dateTime":"2025-02-10T02:30:00Z","height":4.856639},{"dateTime":"2025-02-10T03:00:00Z","height":4.790144},{"dateTime":"2025-02-10T03:30:00Z","height":4.635666},{"dateTime":"2025-02-10T04:00:00Z","height":4.410172},{"dateTime":"2025-02-10T04:30:00Z","height":4.131722},{"dateTime":"2025-02-10T05:00:00Z","height":3.817236},{"dateTime":"2025-02-10T05:30:00Z","height":3.480236},{"dateTime":"2025-02-10T06:00:00Z","height":3.13385},{"dateTime":"2025-02-10T06:30:00Z","height":2.79753},{"dateTime":"2025-02-10T07:00:00Z","height":2.496298},{"dateTime":"2025-02-10T07:30:00Z","height":2.25152},{"dateTime":"2025-02-10T08:00:00Z","height":2.077567},{"dateTime":"2025-02-10T08:30:00Z","height":1.989033},{"dateTime":"2025-02-10T09:00:00Z","height":2.002977},{"dateTime":"2025-02-10T09:30:00Z","height":2.127473},{"dateTime":"2025-02-10T10:00:00Z","height":2.351999},{"dateTime":"2025-02-10T10:30:00Z","height":2.653826},{"dateTime":"2025-02-10T11:00:00Z","height":3.009054},{"dateTime":"2025-02-10T11:30:00Z","height":3.392711},{"dateTime":"2025-02-10T12:00:00Z","height":3.775395},{"dateTime":"2025-02-10T12:30:00Z","height":4.13041},{"dateTime":"2025-02-10T13:00:00Z","height":4.44282},{"dateTime":"2025-02-10T13:30:00Z","height":4.703554},{"dateTime":"2025-02-10T14:00:00Z","height":4.895762},{"dateTime":"2025-02-10T14:30:00Z","height":4.995301},{"dateTime":"2025-02-10T15:00:00Z","height":4.985936},{"dateTime":"2025-02-10T15:30:00Z","height":4.86869},{"dateTime":"2025-02-10T16:00:00Z","height":4.656865},{"dateTime":"2025-02-10T16:30:00Z","height":4.36836},{"dateTime":"2025-02-10T17:00:00Z","height":4.022693},{"dateTime":"2025-02-10T17:30:00Z","height":3.638112},{"dateTime":"2025-02-10T18:00:00Z","height":3.229167},{"dateTime":"2025-02-10T18:30:00Z","height":2.811358},{"dateTime":"2025-02-10T19:00:00Z","height":2.40807},{"dateTime":"2025-02-10T19:30:00Z","height":2.047507},{"dateTime":"2025-02-10T20:00:00Z","height":1.75306},{"dateTime":"2025-02-10T20:30:00Z","height":1.542703},{"dateTime":"2025-02-10T21:00:00Z","height":1.436488},{"dateTime":"2025-02-10T21:30:00Z","height":1.454511},{"dateTime":"2025-02-10T22:00:00Z","height":1.603301},{"dateTime":"2025-02-10T22:30:00Z","height":1.869819},{"dateTime":"2025-02-10T23:00:00Z","height":2.230223},{"dateTime":"2025-02-10T23:30:00Z","height":2.657026},{"dateTime":"2025-02-11T00:00:00Z","height":3.116361},{"dateTime":"2025-02-11T00:30:00Z","height":3.570096},{"dateTime":"2025-02-11T01:00:00Z","height":3.989617},{"dateTime":"2025-02-11T01:30:00Z","height":4.362221},{"dateTime":"2025-02-11T02:00:00Z","height":4.677421},{"dateTime":"2025-02-11T02:30:00Z","height":4.912627},{"dateTime":"2025-02-11T03:00:00Z","height":5.04041},{"dateTime":"2025-02-11T03:30:00Z","height":5.047109},{"dateTime":"2025-02-11T04:00:00Z","height":4.938651},{"dateTime":"2025-02-11T04:30:00Z","height":4.732241},{"dateTime":"2025-02-11T05:00:00Z","height":4.449186},{"dateTime":"2025-02-11T05:30:00Z","height":4.112058},{"dateTime":"2025-02-11T06:00:00Z","height":3.740195},{"dateTime":"2025-02-11T06:30:00Z","height":3.34784},{"dateTime":"2025-02-11T07:00:00Z","height":2.951386},{"dateTime":"2025-02-11T07:30:00Z","height":2.5761},{"dateTime":"2025-02-11T08:00:00Z","height":2.250409},{"dateTime":"2025-02-11T08:30:00Z","height":1.996583},{"dateTime":"2025-02-11T09:00:00Z","height":1.832776},{"dateTime":"2025-02-11T09:30:00Z","height":1.779357},{"dateTime":"2025-02-11T10:00:00Z","height":1.852867},{"dateTime":"2025-02-11T10:30:00Z","height":2.05306},{"dateTime":"2025-02-11T11:00:00Z","height":2.361905},{"dateTime":"2025-02-11T11:30:00Z","height":2.752959},{"dateTime":"2025-02-11T12:00:00Z","height":3.194082},{"dateTime":"2025-02-11T12:30:00Z","height":3.645204},{"dateTime":"2025-02-11T13:00:00Z","height":4.067942},{"dateTime":"2025-02-11T13:30:00Z","height":4.441997},{"dateTime":"2025-02-11T14:00:00Z","height":4.763241},{"dateTime":"2025-02-11T14:30:00Z","height":5.02215},{"dateTime":"2025-02-11T15:00:00Z","height":5.192696},{"dateTime":"2025-02-11T15:30:00Z","height":5.247205},{"dateTime":"2025-02-11T16:00:00Z","height":5.175469},{"dateTime":"2025-02-11T16:30:00Z","height":4.986029},{"dateTime":"2025-02-11T17:00:00Z","height":4.696961},{"dateTime":"2025-02-11T17:30:00Z","height":4.330944},{"dateTime":"2025-02-11T18:00:00Z","height":3.91202},{"dateTime":"2025-02-11T18:30:00Z","height":3.459476},{"dateTime":"2025-02-11T19:00:00Z","height":2.987861},{"dateTime":"2025-02-11T19:30:00Z","height":2.516825},{"dateTime":"2025-02-11T20:00:00Z","height":2.076074},{"dateTime":"2025-02-11T20:30:00Z","height":1.696682},{"dateTime":"2025-02-11T21:00:00Z","height":1.403069},{"dateTime":"2025-02-11T21:30:00Z","height":1.217291},{"dateTime":"2025-02-11T22:00:00Z","height":1.162925},{"dateTime":"2025-02-11T22:30:00Z","height":1.255717},{"dateTime":"2025-02-11T23:00:00Z","height":1.492775},{"dateTime":"2025-02-11T23:30:00Z","height":1.854879},{"dateTime":"2025-02-12T00:00:00Z","height":2.312951},{"dateTime":"2025-02-12T00:30:00Z","height":2.826648},{"dateTime":"2025-02-12T01:00:00Z","height":3.346474},{"dateTime":"2025-02-12T01:30:00Z","height":3.831488},{"dateTime":"2025-02-12T02:00:00Z","height":4.26437},{"dateTime":"2025-02-12T02:30:00Z","height":4.639998},{"dateTime":"2025-02-12T03:00:00Z","height":4.941285},{"dateTime":"2025-02-12T03:30:00Z","height":5.136496},{"dateTime":"2025-02-12T04:00:00Z","height":5.200514},{"dateTime":"2025-02-12T04:30:00Z","height":5.130329},{"dateTime":"2025-02-12T05:00:00Z","height":4.940622},{"dateTime":"2025-02-12T05:30:00Z","height":4.654385},{"dateTime":"2025-02-12T06:00:00Z","height":4.298944},{"dateTime":"2025-02-12T06:30:00Z","height":3.900402},{"dateTime":"2025-02-12T07:00:00Z","height":3.476757},{"dateTime":"2025-02-12T07:30:00Z","height":3.04179},{"dateTime":"2025-02-12T08:00:00Z","height":2.61727},{"dateTime":"2025-02-12T08:30:00Z","height":2.23485},{"dateTime":"2025-02-12T09:00:00Z","height":1.92481},{"dateTime":"2025-02-12T09:30:00Z","height":1.710017},{"dateTime":"2025-02-12T10:00:00Z","height":1.611236},{"dateTime":"2025-02-12T10:30:00Z","height":1.647735},{"dateTime":"2025-02-12T11:00:00Z","height":1.826699},{"dateTime":"2025-02-12T11:30:00Z","height":2.136713},{"dateTime":"2025-02-12T12:00:00Z","height":2.552635},{"dateTime":"2025-02-12T12:30:00Z","height":3.038908},{"dateTime":"2025-02-12T13:00:00Z","height":3.547795},{"dateTime":"2025-02-12T13:30:00Z","height":4.028984},{"dateTime":"2025-02-12T14:00:00Z","height":4.451726},{"dateTime":"2025-02-12T14:30:00Z","height":4.811598},{"dateTime":"2025-02-12T15:00:00Z","height":5.108093},{"dateTime":"2025-02-12T15:30:00Z","height":5.321733},{"dateTime":"2025-02-12T16:00:00Z","height":5.420922},{"dateTime":"2025-02-12T16:30:00Z","height":5.385852},{"dateTime":"2025-02-12T17:00:00Z","height":5.218269},{"dateTime":"2025-02-12T17:30:00Z","height":4.934187},{"dateTime":"2025-02-12T18:00:00Z","height":4.557319},{"dateTime":"2025-02-12T18:30:00Z","height":4.11595},{"dateTime":"2025-02-12T19:00:00Z","height":3.635074},{"dateTime":"2025-02-12T19:30:00Z","height":3.130761},{"dateTime":"2025-02-12T20:00:00Z","height":2.618574},{"dateTime":"2025-02-12T20:30:00Z","height":2.12573},{"dateTime":"2025-02-12T21:00:00Z","height":1.688176},{"dateTime":"2025-02-12T21:30:00Z","height":1.337798},{"dateTime":"2025-02-12T22:00:00Z","height":1.099268},{"dateTime":"2025-02-12T22:30:00Z","height":0.99557},{"dateTime":"2025-02-12T23:00:00Z","height":1.045485},{"dateTime":"2025-02-12T23:30:00Z","height":1.253304},{"dateTime":"2025-02-13T00:00:00Z","height":1.605684},{"dateTime":"2025-02-13T00:30:00Z","height":2.075385},{"dateTime":"2025-02-13T01:00:00Z","height":2.620353},{"dateTime":"2025-02-13T01:30:00Z","height":3.184525},{"dateTime":"2025-02-13T02:00:00Z","height":3.715564},{"dateTime":"2025-02-13T02:30:00Z","height":4.187116},{"dateTime":"2025-02-13T03:00:00Z","height":4.595242},{"dateTime":"2025-02-13T03:30:00Z","height":4.930825},{"dateTime":"2025-02-13T04:00:00Z","height":5.165338},{"dateTime":"2025-02-13T04:30:00Z","height":5.267649},{"dateTime":"2025-02-13T05:00:00Z","height":5.22614},{"dateTime":"2025-02-13T05:30:00Z","height":5.050913},{"dateTime":"2025-02-13T06:00:00Z","height":4.764492},{"dateTime":"2025-02-13T06:30:00Z","height":4.396677},{"dateTime":"2025-02-13T07:00:00Z","height":3.979223},{"dateTime":"2025-02-13T07:30:00Z","height":3.535709},{"dateTime":"2025-02-13T08:00:00Z","height":3.07973},{"dateTime":"2025-02-13T08:30:00Z","height":2.628179},{"dateTime":"2025-02-13T09:00:00Z","height":2.211477},{"dateTime":"2025-02-13T09:30:00Z","height":1.865727},{"dateTime":"2025-02-13T10:00:00Z","height":1.619983},{"dateTime":"2025-02-13T10:30:00Z","height":1.495726},{"dateTime":"2025-02-13T11:00:00Z","height":1.511069},{"dateTime":"2025-02-13T11:30:00Z","height":1.675811},{"dateTime":"2025-02-13T12:00:00Z","height":1.983533},{"dateTime":"2025-02-13T12:30:00Z","height":2.411912},{"dateTime":"2025-02-13T13:00:00Z","height":2.925269},{"dateTime":"2025-02-13T13:30:00Z","height":3.47313},{"dateTime":"2025-02-13T14:00:00Z","height":3.997753},{"dateTime":"2025-02-13T14:30:00Z","height":4.457571},{"dateTime":"2025-02-13T15:00:00Z","height":4.842366},{"dateTime":"2025-02-13T15:30:00Z","height":5.156649},{"dateTime":"2025-02-13T16:00:00Z","height":5.390425},{"dateTime":"2025-02-13T16:30:00Z","height":5.515104},{"dateTime":"2025-02-13T17:00:00Z","height":5.505734},{"dateTime":"2025-02-13T17:30:00Z","height":5.357577},{"dateTime":"2025-02-13T18:00:00Z","height":5.083133},{"dateTime":"2025-02-13T18:30:00Z","height":4.705009},{"dateTime":"2025-02-13T19:00:00Z","height":4.253072},{"dateTime":"2025-02-13T19:30:00Z","height":3.757084},{"dateTime":"2025-02-13T20:00:00Z","height":3.236836},{"dateTime":"2025-02-13T20:30:00Z","height":2.705785},{"dateTime":"2025-02-13T21:00:00Z","height":2.186551},{"dateTime":"2025-02-13T21:30:00Z","height":1.715845},{"dateTime":"2025-02-13T22:00:00Z","height":1.331941},{"dateTime":"2025-02-13T22:30:00Z","height":1.063799},{"dateTime":"2025-02-13T23:00:00Z","height":0.93318},{"dateTime":"2025-02-13T23:30:00Z","height":0.957303},{"dateTime":"2025-02-14T00:00:00Z","height":1.142843},{"dateTime":"2025-02-14T00:30:00Z","height":1.480485},{"dateTime":"2025-02-14T01:00:00Z","height":1.945671},{"dateTime":"2025-02-14T01:30:00Z","height":2.497936},{"dateTime":"2025-02-14T02:00:00Z","height":3.080584},{"dateTime":"2025-02-14T02:30:00Z","height":3.635272},{"dateTime":"2025-02-14T03:00:00Z","height":4.126637},{"dateTime":"2025-02-14T03:30:00Z","height":4.54732},{"dateTime":"2025-02-14T04:00:00Z","height":4.893714},{"dateTime":"2025-02-14T04:30:00Z","height":5.144017},{"dateTime":"2025-02-14T05:00:00Z","height":5.266717},{"dateTime":"2025-02-14T05:30:00Z","height":5.244201},{"dateTime":"2025-02-14T06:00:00Z","height":5.081393},{"dateTime":"2025-02-14T06:30:00Z","height":4.798646},{"dateTime":"2025-02-14T07:00:00Z","height":4.425803},{"dateTime":"2025-02-14T07:30:00Z","height":3.997786},{"dateTime":"2025-02-14T08:00:00Z","height":3.54384},{"dateTime":"2025-02-14T08:30:00Z","height":3.080312},{"dateTime":"2025-02-14T09:00:00Z","height":2.620638},{"dateTime":"2025-02-14T09:30:00Z","height":2.190947},{"dateTime":"2025-02-14T10:00:00Z","height":1.829},{"dateTime":"2025-02-14T10:30:00Z","height":1.569301},{"dateTime":"2025-02-14T11:00:00Z","height":1.435354},{"dateTime":"2025-02-14T11:30:00Z","height":1.443015},{"dateTime":"2025-02-14T12:00:00Z","height":1.600909},{"dateTime":"2025-02-14T12:30:00Z","height":1.904661},{"dateTime":"2025-02-14T13:00:00Z","height":2.334357},{"dateTime":"2025-02-14T13:30:00Z","height":2.855702},{"dateTime":"2025-02-14T14:00:00Z","height":3.419277},{"dateTime":"2025-02-14T14:30:00Z","height":3.965878},{"dateTime":"2025-02-14T15:00:00Z","height":4.447281},{"dateTime":"2025-02-14T15:30:00Z","height":4.845658},{"dateTime":"2025-02-14T16:00:00Z","height":5.165169},{"dateTime":"2025-02-14T16:30:00Z","height":5.403238},{"dateTime":"2025-02-14T17:00:00Z","height":5.537823},{"dateTime":"2025-02-14T17:30:00Z","height":5.543587},{"dateTime":"2025-02-14T18:00:00Z","height":5.411343},{"dateTime":"2025-02-14T18:30:00Z","height":5.149827},{"dateTime":"2025-02-14T19:00:00Z","height":4.779215},{"dateTime":"2025-02-14T19:30:00Z","height":4.328419},{"dateTime":"2025-02-14T20:00:00Z","height":3.829596},{"dateTime":"2025-02-14T20:30:00Z","height":3.306886},{"dateTime":"2025-02-14T21:00:00Z","height":2.774556},{"dateTime":"2025-02-14T21:30:00Z","height":2.25121},{"dateTime":"2025-02-14T22:00:00Z","height":1.770913},{"dateTime":"2025-02-14T22:30:00Z","height":1.37501},{"dateTime":"2025-02-14T23:00:00Z","height":1.096842},{"dateTime":"2025-02-14T23:30:00Z","height":0.957915},{"dateTime":"2025-02-15T00:00:00Z","height":0.972001},{"dateTime":"2025-02-15T00:30:00Z","height":1.144268},{"dateTime":"2025-02-15T01:00:00Z","height":1.466656},{"dateTime":"2025-02-15T01:30:00Z","height":1.916511},{"dateTime":"2025-02-15T02:00:00Z","height":2.456002},{"dateTime":"2025-02-15T02:30:00Z","height":3.031801},{"dateTime":"2025-02-15T03:00:00Z","height":3.585913},{"dateTime":"2025-02-15T03:30:00Z","height":4.077999},{"dateTime":"2025-02-15T04:00:00Z","height":4.495594},{"dateTime":"2025-02-15T04:30:00Z","height":4.836565},{"dateTime":"2025-02-15T05:00:00Z","height":5.085383},{"dateTime":"2025-02-15T05:30:00Z","height":5.213825},{"dateTime":"2025-02-15T06:00:00Z","height":5.201706},{"dateTime":"2025-02-15T06:30:00Z","height":5.049507},{"dateTime":"2025-02-15T07:00:00Z","height":4.774529},{"dateTime":"2025-02-15T07:30:00Z","height":4.40486},{"dateTime":"2025-02-15T08:00:00Z","height":3.975799},{"dateTime":"2025-02-15T08:30:00Z","height":3.520497},{"dateTime":"2025-02-15T09:00:00Z","height":3.059675},{"dateTime":"2025-02-15T09:30:00Z","height":2.606321},{"dateTime":"2025-02-15T10:00:00Z","height":2.182011},{"dateTime":"2025-02-15T10:30:00Z","height":1.822306},{"dateTime":"2025-02-15T11:00:00Z","height":1.564142},{"dateTime":"2025-02-15T11:30:00Z","height":1.433103},{"dateTime":"2025-02-15T12:00:00Z","height":1.443041},{"dateTime":"2025-02-15T12:30:00Z","height":1.599433},{"dateTime":"2025-02-15T13:00:00Z","height":1.897374},{"dateTime":"2025-02-15T13:30:00Z","height":2.318429},{"dateTime":"2025-02-15T14:00:00Z","height":2.830258},{"dateTime":"2025-02-15T14:30:00Z","height":3.386507},{"dateTime":"2025-02-15T15:00:00Z","height":3.931085},{"dateTime":"2025-02-15T15:30:00Z","height":4.414811},{"dateTime":"2025-02-15T16:00:00Z","height":4.814346},{"dateTime":"2025-02-15T16:30:00Z","height":5.130128},{"dateTime":"2025-02-15T17:00:00Z","height":5.36268},{"dateTime":"2025-02-15T17:30:00Z","height":5.496328},{"dateTime":"2025-02-15T18:00:00Z","height":5.508614},{"dateTime":"2025-02-15T18:30:00Z","height":5.388634},{"dateTime":"2025-02-15T19:00:00Z","height":5.142147},{"dateTime":"2025-02-15T19:30:00Z","height":4.786613},{"dateTime":"2025-02-15T20:00:00Z","height":4.348474},{"dateTime":"2025-02-15T20:30:00Z","height":3.859572},{"dateTime":"2025-02-15T21:00:00Z","height":3.34699},{"dateTime":"2025-02-15T21:30:00Z","height":2.827535},{"dateTime":"2025-02-15T22:00:00Z","height":2.317969},{"dateTime":"2025-02-15T22:30:00Z","height":1.848494},{"dateTime":"2025-02-15T23:00:00Z","height":1.459909},{"dateTime":"2025-02-15T23:30:00Z","height":1.188069}],"lunarPhaseList":[{"lunarPhaseType":3,"dateTime":"2025-02-12T13:53:00"}],"footerNote":""}
\ No newline at end of file
diff --git a/tests/images/intro0.png b/tests/images/intro0.png
new file mode 100644
index 00000000..5a688aba
Binary files /dev/null and b/tests/images/intro0.png differ
diff --git a/tests/images/intro1.png b/tests/images/intro1.png
new file mode 100644
index 00000000..c569d965
Binary files /dev/null and b/tests/images/intro1.png differ
diff --git a/tests/images/intro2.png b/tests/images/intro2.png
new file mode 100644
index 00000000..4f30e372
Binary files /dev/null and b/tests/images/intro2.png differ
diff --git a/tests/images/test_centered.png b/tests/images/test_centered.png
new file mode 100644
index 00000000..0b3209b0
Binary files /dev/null and b/tests/images/test_centered.png differ
diff --git a/tests/images/tide_times.png b/tests/images/tide_times.png
new file mode 100644
index 00000000..a1fa898b
Binary files /dev/null and b/tests/images/tide_times.png differ
diff --git a/tests/test_chart_rendering.py b/tests/test_chart_rendering.py
index c2af6c2e..bf52bf2c 100644
--- a/tests/test_chart_rendering.py
+++ b/tests/test_chart_rendering.py
@@ -6,6 +6,7 @@
import os
import pathlib
import unittest
+import glob
from src.drawing.market_charts.mpf_plotted_chart import MplFinanceChart
from src.exchanges.CandleData import CandleData
@@ -59,7 +60,8 @@ def test(self):
return test
- for file_name in os.listdir(test_data_path):
+ for file_path in glob.glob(f"{test_data_path}/*.pkl"):
+ file_name = os.path.basename(file_path)
test_name = f"test_{file_name}".replace(".pkl","")
this_tests_images_path=os.path.join(test_images_path, chart_size)
os.makedirs(this_tests_images_path, exist_ok=True)
diff --git a/tests/test_overlay_drawing.py b/tests/test_overlay_drawing.py
index 5561cbf1..20f3cf18 100644
--- a/tests/test_overlay_drawing.py
+++ b/tests/test_overlay_drawing.py
@@ -1,17 +1,17 @@
from PIL import ImageFont, Image, ImageDraw, ImageChops
from src.drawing.image_utils.DrawText import DrawText
from src.drawing.image_utils.TextBlock import TextBlock
+from src.drawing.image_utils.CenteredText import centered_text
from src.configuration.bitbot_files import use_config_dir
import unittest
import os
import pathlib
-curdir = pathlib.Path(__file__).parent.resolve()
-files = use_config_dir(os.path.join(curdir, "../"))
+current_directory = pathlib.Path(__file__).parent.resolve()
+files = use_config_dir(os.path.join(current_directory, "../"))
transparent = (0, 0, 0, 0)
white = (255, 255, 255)
-image_file_name = 'tests/images/title_block.png'
class TestTextBlocks(unittest.TestCase):
@@ -20,6 +20,7 @@ class TestTextBlocks(unittest.TestCase):
price_font = ImageFont.truetype(fontPath, 32)
def test_text_block(self):
+ image_file_name = 'tests/images/title_block.png'
block = TextBlock([
[
DrawText('GBP' + ' (' + 'Sterling' + ') ', self.title_font, colour=white),
@@ -32,18 +33,35 @@ def test_text_block(self):
image = Image.new('RGBA', block.size(), transparent)
image_drawing = ImageDraw.Draw(image)
-
block.draw_on(image_drawing)
- image.save(image_file_name)
previous_image = Image.open(image_file_name) if os.path.isfile(image_file_name) else None
if previous_image is None:
assert False, f"New image result, re-run the test to accept: '{image_file_name}'"
+ image.save(image_file_name)
diff = ImageChops.difference(image, previous_image)
-
if diff.getbbox():
file_name = f'tests/images/overlay_diff.png'
diff.save(file_name)
assert False, f"images were different, see '{file_name}'"
# os.system(f"code 'diff.png'")
+
+
+ def test_centered(self):
+ image_file_name = 'tests/images/test_centered.png'
+ image = Image.new("RGBA", (400, 300), transparent)
+ draw = ImageDraw.Draw(image)
+ centered_text(draw, "test", self.price_font, image.size, 'centre', border=True)
+
+ previous_image = Image.open(image_file_name) if os.path.isfile(image_file_name) else None
+ if previous_image is None:
+ assert False, f"New image result, re-run the test to accept: '{image_file_name}'"
+
+ image.save(image_file_name)
+ diff = ImageChops.difference(image, previous_image)
+ if diff.getbbox():
+ file_name = f'tests/images/test_centered_diff.png'
+ diff.save(file_name)
+ assert False, f"images were different, see '{image_file_name}'"
+ # os.system(f"code '{image_file_name}'")
diff --git a/tests/test_tides.py b/tests/test_tides.py
new file mode 100644
index 00000000..d4b6cbb6
--- /dev/null
+++ b/tests/test_tides.py
@@ -0,0 +1,10 @@
+import io
+import unittest
+from src.drawing.tide_times import tidal_graph
+from PIL import Image, ImageDraw
+
+class TestTidalGraph(unittest.TestCase):
+ def test_data_fetch_and_render(self):
+ with io.BytesIO() as img_buf:
+ image = tidal_graph.render_tide_chart("0184", img_buf)
+ image.save(f'tests/images/tide_times.png')