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