Quickstart
pysurfline
offers a simple interface to get the forecast for any spot listed on Surfline.
In order to request the data for the correct spot to the SurflineAPI, we must provide the spot with its spotID
argument. This can be found easily from the URL of the surfline webpage of each spot.
Retriving the spotId
of a Surfline spot
For example, going to Anchor Point webpage, the URL is:
https://www.surfline.com/surf-report/anchor-point/5842041f4e65fad6a7708cfd
after a few seconds of loading the page, the URL could change to something like this:
https://www.surfline.com/surf-report/anchor-point/5842041f4e65fad6a7708cfd?camId=5fd1e2a2bdd08a3999f23aa4&view=table
By examining both URLs, we recognize 5842041f4e65fad6a7708cfd is actually Anchor Point’s spotId
.
Getting the Surfline forecast of a spot
The get_spot_forecasts()
function taks the additional parameters: - days
(int): the number of days to forecast - intervalHours
(int): the interval between each forecast
[1]:
import pysurfline
[2]:
spot_forecasts = pysurfline.get_spot_forecasts(
spotId='5842041f4e65fad6a7708cfd',
days=2,
intervalHours=3,
)
SpotForecast
This data received from the API is unpacked into a SpotForecasts
object.
To understand the logic behind the Surfline API and subsequent implementation of
pysufline
objects, please refer to Surfline API Schema.
class SpotForecasts:
spotId: int
name: str
waves: List[Wave]
wind: List[Wind]
tides: List[Tides]
weather: List[Weather]
sunlightTimes: List[SunlightTimes]
[3]:
spot_forecasts.name
[3]:
'Anchor Point'
Waves
[4]:
spot_forecasts.waves[:5]
[4]:
[Wave(timestamp=Time(2023-12-31 23:00:00), probability=60, utcOffset=1, surf=Surf(min=0.9, max=1.5, optimalScore=2, plus=False, humanRelation='Waist to head', raw={'min': 0.90473, 'max': 1.41365}), power=369.76912, swells=[Swell(height=1.23, period=11, impact=1, power=369.76912, direction=303.36, directionMin=295.815, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0)]),
Wave(timestamp=Time(2024-01-01 02:00:00), probability=100, utcOffset=1, surf=Surf(min=0.9, max=1.4, optimalScore=2, plus=False, humanRelation='Waist to shoulder', raw={'min': 0.83651, 'max': 1.30705}), power=269.26941, swells=[Swell(height=1.11, period=11, impact=0.5456, power=164.00688, direction=303.43, directionMin=295.91, optimalScore=0), Swell(height=0.72, period=15, impact=0.4544, power=105.26253, direction=304.68, directionMin=297.47, optimalScore=1), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0)]),
Wave(timestamp=Time(2024-01-01 05:00:00), probability=100, utcOffset=1, surf=Surf(min=1.2, max=1.8, optimalScore=2, plus=False, humanRelation='Chest to overhead', raw={'min': 1.02848, 'max': 1.607}), power=442.12028, swells=[Swell(height=0.9, period=11, impact=0.4045, power=74.71935, direction=307.33, directionMin=299.905, optimalScore=0), Swell(height=1.19, period=15, impact=0.5955, power=367.40093, direction=302.68, directionMin=295.34000000000003, optimalScore=2), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0)]),
Wave(timestamp=Time(2024-01-01 08:00:00), probability=100, utcOffset=1, surf=Surf(min=1.2, max=2, optimalScore=2, plus=False, humanRelation='Chest to 0.3m overhead', raw={'min': 1.25571, 'max': 1.96205}), power=1031.61297, swells=[Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=1.59, period=14, impact=1, power=1031.61297, direction=304.04, directionMin=296.62, optimalScore=2), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0)]),
Wave(timestamp=Time(2024-01-01 11:00:00), probability=100, utcOffset=1, surf=Surf(min=1.5, max=2.1, optimalScore=2, plus=False, humanRelation='Head to 0.6m overhead', raw={'min': 1.303, 'max': 2.03594}), power=1133.35151, swells=[Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=1.69, period=14, impact=1, power=1133.35151, direction=303.83, directionMin=296.375, optimalScore=2), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0), Swell(height=0, period=0, impact=0, power=0, direction=0, directionMin=0, optimalScore=0)])]
Wind
[5]:
spot_forecasts.wind[:5]
[5]:
[Wind(timestamp=Time(2023-12-31 23:00:00), utcOffset=1, speed=4.55097, direction=114.47656, directionType='Offshore', gust=4.6808, optimalScore=2),
Wind(timestamp=Time(2024-01-01 02:00:00), utcOffset=1, speed=5.88996, direction=126.73413, directionType='Offshore', gust=6.22659, optimalScore=0),
Wind(timestamp=Time(2024-01-01 05:00:00), utcOffset=1, speed=1.01431, direction=315.08246, directionType='Onshore', gust=2.3332, optimalScore=2),
Wind(timestamp=Time(2024-01-01 08:00:00), utcOffset=1, speed=5.83969, direction=330.14504, directionType='Cross-shore', gust=6.0273, optimalScore=0),
Wind(timestamp=Time(2024-01-01 11:00:00), utcOffset=1, speed=2.39737, direction=289.40235, directionType='Onshore', gust=2.39737, optimalScore=2)]
Tides
[6]:
spot_forecasts.tides[:5]
[6]:
[Tides(timestamp=Time(2023-12-31 23:00:00), utcOffset=1, type='NORMAL', height=1.46),
Tides(timestamp=Time(2024-01-01 00:00:00), utcOffset=1, type='NORMAL', height=1.67),
Tides(timestamp=Time(2024-01-01 01:00:00), utcOffset=1, type='NORMAL', height=2.03),
Tides(timestamp=Time(2024-01-01 02:00:00), utcOffset=1, type='NORMAL', height=2.46),
Tides(timestamp=Time(2024-01-01 03:00:00), utcOffset=1, type='NORMAL', height=2.85)]
Weather
[7]:
spot_forecasts.weather[:5]
[7]:
[Weather(timestamp=Time(2023-12-31 23:00:00), utcOffset=1, temperature=17.34355, condition='NIGHT_CLEAR', pressure=1021),
Weather(timestamp=Time(2024-01-01 02:00:00), utcOffset=1, temperature=16.6358, condition='NIGHT_CLEAR', pressure=1020),
Weather(timestamp=Time(2024-01-01 05:00:00), utcOffset=1, temperature=16.46528, condition='NIGHT_OVERCAST', pressure=1019),
Weather(timestamp=Time(2024-01-01 08:00:00), utcOffset=1, temperature=17.62935, condition='MOSTLY_CLOUDY', pressure=1021),
Weather(timestamp=Time(2024-01-01 11:00:00), utcOffset=1, temperature=19.67752, condition='CLEAR', pressure=1021)]
Sunlight Times
[8]:
spot_forecasts.sunlightTimes[:]
[8]:
[SunlightTimes(midnight=Time(2023-12-31 23:00:00), midnightUTCOffset=1, dawn=Time(2024-01-01 07:10:40), dawnUTCOffset=1, sunrise=Time(2024-01-01 07:37:07), sunriseUTCOffset=1, sunset=Time(2024-01-01 17:49:27), sunsetUTCOffset=1, dusk=Time(2024-01-01 18:15:55), duskUTCOffset=1),
SunlightTimes(midnight=Time(2024-01-01 23:00:00), midnightUTCOffset=1, dawn=Time(2024-01-02 07:10:55), dawnUTCOffset=1, sunrise=Time(2024-01-02 07:37:21), sunriseUTCOffset=1, sunset=Time(2024-01-02 17:50:09), sunsetUTCOffset=1, dusk=Time(2024-01-02 18:16:35), duskUTCOffset=1)]
Get data as pandas Dataframe
The different SpotForecasts
attributes can be accessed as pandas DataFrame for easy data manipulation.
As default, it will return a dataframe that is a join of the data contained in the single waves
, wind
,weather
attributes.
To select data to convert to Dataframe, use a string argument: - surf
(Default) - Attribute name: - waves
, - wind
, - tides
, - weather
, - sunlightTimes
.
Get surf data as a single Dataframe
The surf
Dataframe is a join of the data contained in the waves
, wind
, tides
, weather
attributes.
[9]:
# show all columns in HTML
import pandas as pd
pd.set_option('display.max_columns', None)
[10]:
spot_forecasts.get_dataframe().head() # "surf"
[10]:
timestamp_dt | timestamp_timestamp | probability | utcOffset | surf_min | surf_max | surf_optimalScore | surf_plus | surf_humanRelation | surf_raw_min | surf_raw_max | power | swells_0_height | swells_0_period | swells_0_impact | swells_0_power | swells_0_direction | swells_0_directionMin | swells_0_optimalScore | swells_1_height | swells_1_period | swells_1_impact | swells_1_power | swells_1_direction | swells_1_directionMin | swells_1_optimalScore | swells_2_height | swells_2_period | swells_2_impact | swells_2_power | swells_2_direction | swells_2_directionMin | swells_2_optimalScore | swells_3_height | swells_3_period | swells_3_impact | swells_3_power | swells_3_direction | swells_3_directionMin | swells_3_optimalScore | swells_4_height | swells_4_period | swells_4_impact | swells_4_power | swells_4_direction | swells_4_directionMin | swells_4_optimalScore | swells_5_height | swells_5_period | swells_5_impact | swells_5_power | swells_5_direction | swells_5_directionMin | swells_5_optimalScore | speed | direction | directionType | gust | optimalScore | temperature | condition | pressure | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2023-12-31 23:00:00 | 1704063600 | 60 | 1 | 0.9 | 1.5 | 2 | False | Waist to head | 0.90473 | 1.41365 | 369.76912 | 1.23 | 11 | 1.0000 | 369.76912 | 303.36 | 295.815 | 0 | 0.00 | 0 | 0.0000 | 0.00000 | 0.00 | 0.000 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 4.55097 | 114.47656 | Offshore | 4.68080 | 2 | 17.34355 | NIGHT_CLEAR | 1021 |
1 | 2024-01-01 02:00:00 | 1704074400 | 100 | 1 | 0.9 | 1.4 | 2 | False | Waist to shoulder | 0.83651 | 1.30705 | 269.26941 | 1.11 | 11 | 0.5456 | 164.00688 | 303.43 | 295.910 | 0 | 0.72 | 15 | 0.4544 | 105.26253 | 304.68 | 297.470 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5.88996 | 126.73413 | Offshore | 6.22659 | 0 | 16.63580 | NIGHT_CLEAR | 1020 |
2 | 2024-01-01 05:00:00 | 1704085200 | 100 | 1 | 1.2 | 1.8 | 2 | False | Chest to overhead | 1.02848 | 1.60700 | 442.12028 | 0.90 | 11 | 0.4045 | 74.71935 | 307.33 | 299.905 | 0 | 1.19 | 15 | 0.5955 | 367.40093 | 302.68 | 295.340 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1.01431 | 315.08246 | Onshore | 2.33320 | 2 | 16.46528 | NIGHT_OVERCAST | 1019 |
3 | 2024-01-01 08:00:00 | 1704096000 | 100 | 1 | 1.2 | 2.0 | 2 | False | Chest to 0.3m overhead | 1.25571 | 1.96205 | 1031.61297 | 0.00 | 0 | 0.0000 | 0.00000 | 0.00 | 0.000 | 0 | 1.59 | 14 | 1.0000 | 1031.61297 | 304.04 | 296.620 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5.83969 | 330.14504 | Cross-shore | 6.02730 | 0 | 17.62935 | MOSTLY_CLOUDY | 1021 |
4 | 2024-01-01 11:00:00 | 1704106800 | 100 | 1 | 1.5 | 2.1 | 2 | False | Head to 0.6m overhead | 1.30300 | 2.03594 | 1133.35151 | 0.00 | 0 | 0.0000 | 0.00000 | 0.00 | 0.000 | 0 | 1.69 | 14 | 1.0000 | 1133.35151 | 303.83 | 296.375 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2.39737 | 289.40235 | Onshore | 2.39737 | 2 | 19.67752 | CLEAR | 1021 |
Single attribute as DataFrame
In order to get a single attribute as a DataFrame, the attribute name must be specified as an argument. - waves
- wind
- weather
- tides
- sunlightTimes
[11]:
spot_forecasts.get_dataframe("wind").head()
[11]:
index | timestamp_timestamp | timestamp_dt | utcOffset | speed | direction | directionType | gust | optimalScore | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1704063600 | 2023-12-31 23:00:00 | 1 | 4.55097 | 114.47656 | Offshore | 4.68080 | 2 |
1 | 1 | 1704074400 | 2024-01-01 02:00:00 | 1 | 5.88996 | 126.73413 | Offshore | 6.22659 | 0 |
2 | 2 | 1704085200 | 2024-01-01 05:00:00 | 1 | 1.01431 | 315.08246 | Onshore | 2.33320 | 2 |
3 | 3 | 1704096000 | 2024-01-01 08:00:00 | 1 | 5.83969 | 330.14504 | Cross-shore | 6.02730 | 0 |
4 | 4 | 1704106800 | 2024-01-01 11:00:00 | 1 | 2.39737 | 289.40235 | Onshore | 2.39737 | 2 |