MIT License

技术解读

以下内容由 GitRead 中文社区 编辑团队基于项目 README 与官方资料整理, 结合中文开发者的常见使用场景补充技术选型建议。具体以项目官方文档为准。

现代波浪浮标图表与综合冲浪预测:swrobel/meta-surf-forecast 技术分析

项目背景与痛点

swrobel/meta-surf-forecast 是一个集合了现代化波浪浮标数据与来自 Surfline 和 Spitcast 的综合冲浪预测服务的项目。波浪浮标是最直接获取海浪数据的仪器,而通过汇聚来自多个 API 的信息,项目能够更准确地为冲浪爱好者提供全球多地的实时冲浪条件。

对于冲浪者而言,不仅要了解目的地的当前海况,也需要对未来数天的预测有信心。通过此举,项目填补了数据碎片化的市场痛点,可以更高效地获取多源汇总的冲浪信息。

核心技术揭秘

该项目的架构设计巧妙地整合了前端展示与后端数据处理。前端用户界面接收来自 API 的原始数据,而后端服务则确保这些数据的处理和展示精确且高效。

graph TD A[用户请求] --> B{"参数校验"} B -->|"通过"| C[执行业务] C --> D("处理数据") D --> E[调用Surfline API] D --> F[调用Spitcast API] E --> G[返回Surf条件] F --> G G --> H[综合并处理结果] H --> I[生成图表] I --> J[响应用户] B -->|"失败"| K[返回错误]

此架构图展示了项目的核心数据流:用户发起请求,后端校验请求参数,校验通过后执行业务逻辑(调用多个API),最终将生成的图表响应给用户。

功能亮点与差异

相比其他单纯依赖单一数据源的冲浪预报服务,meta-surf-forecast 通过整合多个权威 API 来提供更有价值的信息。其独特的浮标图负责显示从部署在全球各地的浮标采集的实时海浪数据,从而让用户更全面地了解当前的海浪条件。聚合预测则进一步增强了数据的可靠性,使得用户可以更有信心地做出行动计划。

应用场景与落地建议

在生产环境下使用该服务,开发者应当着重考虑以下几个方面:

  • 网络稳定性 综合处理多个 API 的请求意味着更大的网络负担。网络稳定性可能会影响数据的及时性和准确性,因此确保可靠的网络服务是基础。
  • 数据安全 鉴于该项目涉及更敏感的用户请求信息,确保数据的安全传输和处理显得更为重要。采用 HTTPS 作为主要协议是一种标准的保护措施。
  • 性能优化 未来,随着用户基数的增长,系统处理能力需要有所升级。提前优化数据库结构、采用缓存机制和优化算法也成为系统性能的关键。

该项目最适合的应用场景有:

  • 冲浪爱好者社群,用于获取精准的未来海浪条件预测,决定冲浪时间与地点。
  • 海洋研究机构,作为研究波浪条件的数据支持系统,满足科学研究需求。

综合评价

swrobel/meta-surf-forecast 项目通过无缝整合多个数据源,为冲浪爱好者带来前所未有的信息服务。然而,全面依赖网络和多个 API 的数据传输也带来了网络延时和数据同步的问题,需要开发者投入额外精力来确保系统的稳定性和高效性能。整体而言,该项目在数据整合与展示上展现了卓越的技术实力,值得进一步关注和研究。

关于

GitHub 原始描述(英文)

🌊 Modern swell buoy charts + Aggregated surf forecast from Surfline & Spitcast APIs

描述取自 GitHub 官方 API,保持仓库作者书写的原文。

简要分析

热度分
518
价值分
227
活跃状态
活跃

语言占比

HTML
JavaScript
Procfile
Ruby
SCSS
Shell
Slim

README

Meta Surf Forecast

Purposes

  1. Display a chart of approximate wave height (swell height * period * 0.1) for the last 24 hours from NDBC Buoys Screenshot
  2. Query the Surfline & Spitcast APIs to display an aggregated surf forecast. Screenshot

Developer Setup

Prerequisites

  • ruby 4.0.x
  • node 26.x
  • yarn 1.x
  • postgresql

Steps

  1. Install dependencies using Homebrew: brew bundle
  2. If on Linux: pg_ctl -D /home/linuxbrew/.linuxbrew/var/postgres start
  3. gem install bundler -v=$(cat Gemfile.lock | tail -1 | tr -d " ")
  4. bundle
  5. yarn
  6. bin/rails db:create db:schema:load:with_data db:seed
  7. Grab some data: bin/rails buoys:update forecasts:update
  8. bin/foreman start -f Procfile.dev
  9. Open http://localhost:5001
  10. Any changes you make to view files will auto-reload the browser

Tips

  • You will not get Surfline forecast data without a valid Surfline premium login. Add your credentials to .env.development:
    SURFLINE_EMAIL=xxx
    SURFLINE_PASSWORD=yyy
    
    Then run bin/rails forecasts:update again
  • When running migrations, use bin/rails db:migrate:with_data to include Data Migrations

Adding Spots

Contributing new spots is easy! Make sure you’re signed into your Github account and edit the seeds file:

  1. Create a new Region/Subregion if necessary. For example, Los Angeles is created like so:
    CA = Region.find_or_create_by(name: 'California')
    LA = Subregion.find_or_create_by(name: 'Los Angeles', region: CA)
    LA.timezone = 'America/Los_Angeles'
    LA.save!
    
    You can get valid timezone names from this list.
  2. Use this tool to draw a bounding box around the area you want to find spots for.
  3. Choose CSV from the dropdown at the bottom & copy the coordinates string.
  4. Run rails console, then run SpotFinder.new('{string}').formatted_spots.
  5. Copy the output and paste it into the spots array in seeds.rb, then make sure to assign each spot to the right subregion & delete extraneous fields (match_type, distance).
  6. It’s strongly encouraged to add all spots for a particular county or region rather than just a single one. Be a pal!
  7. Submit a pull request and I’ll get it on the site ASAP!

Use the following as a template. Add `spitcast_v2_id

  {
    name: 'County Line',
    lat: 34.051,
    lon: -118.964,
    surfline_v2_id: '590927576a2e4300134fbed8',
    subregion: LA,
  },

Data Sources

Surfline

New API (v2)
Responses

Surfline’s new API is undocumented but easy to reverse engineer using their new website’s code. Thankfully its structure is much more sane than the old API.

Requests

https://services.surfline.com/kbyg/spots/forecasts/{type}?{params}

For reference, I believe kbyg stands for “Know Before You Go,” which is their tagline.

Type Data
rating array of human-readable and numeric (0-6) ratings
wave array of min/max sizes & optimal scores
wind array of wind directions/speeds & optimal scores
tides array of types & heights
weather array of sunrise/set times, array of temperatures/weather conditions
Param Values Effect
spotId string Surfline spot id that you want data for. A typical Surfline URL is https://www.surfline.com/surf-report/venice-breakwater/590927576a2e4300134fbed8 where 590927576a2e4300134fbed8 is the spotId
days integer Number of forecast days to get (Max 6 w/o access token, Max 17 w/ premium token)
intervalHours integer Minimum of 1 (hour)
maxHeights boolean true seems to remove min & optimal values from the wave data output
accesstoken string Auth token to get premium data access (optional)

Anywhere there is an optimalScore the value can be interpreted as follows:

Value Meaning
0 Suboptimal
1 Good
2 Optimal

However, I have never seen a score of 1 in any of their API responses (only 0 or 2), which is unfortunate when it comes to granularity of ratings. Hopefully this changes in the future.

Old API (v1 - no longer supported)

Surfline’s old API is undocumented and unauthenticated, but was used via javascript on their website, so it was fairly easy to reverse-engineer. However, they have updated their site & apps to use the new API, and it appears that they’ve stopped including some critical data in the responses for the old API, so it’s disabled in this app for now (and probably forever).

It returned JSON, but with a very odd structure, with each item that is time-sensitive containing an array of daily arrays of values that correspond to timestamps provided in a separate set of arrays. For example (lots of data left out for brevity):

"Surf": {
  "dateStamp": [
      [
        "January 24, 2016 04:00:00",
        "January 24, 2016 10:00:00",
        "January 24, 2016 16:00:00",
        "January 24, 2016 22:00:00"
      ],
      [
        "January 25, 2016 04:00:00",
        "January 25, 2016 10:00:00",
        "January 25, 2016 16:00:00",
        "January 25, 2016 22:00:00"
      ]
    ],
  "surf_min": [
      [
        2.15,
        1.8,
        1.4,
        1
      ],
      [
        0.7,
        0.4,
        0.3,
        0.3
      ]
    ],
}

Requests are structured as follows:

https://api.surfline.com/v1/forecasts/{spot_id}?{params}

This is a breakdown of the params available:

Param Values Effect
spot_id integer Surfline spot id that you want data for. A typical legacy Surfline URL is https://www.surfline.com/surf-report/venice-beach-southern-california_4211/ where 4211 is the spot_id. You can also get this from a v2 API response’s legacyId property.
resources string Any comma-separated list of “surf,analysis,wind,weather,tide,sort”. There could be more available that I haven’t discovered. “Sort” gives an array of swells, periods & heights that are used for the tables on spot forecast pages. To see the whole list, just set ‘all’.
days integer Number of days of forecast to get. This seems to cap out at 16 for Wind and 25 for Surf.
getAllSpots boolean false returns an object containing the single spot you requested, true returns an array of data for all spots in the same region as your spot, in this case “South Los Angeles”
units string e returns American units (ft/mi), m uses metric
usenearshore boolean The best that I can gather, you want this set to true to use the more accurate nearshore models that take into account how each spot’s unique bathymetry affects the incoming swells.
interpolate boolean Provide “forecasts” every 3 hours instead of ever 6. These interpolations seem to be simple averages of the values of the 6-hour forecasts.
showOptimal boolean Includes arrays of 0’s & 1’s indicating whether each wind & swell forecast is optimal for this spot or not. Unfortunately the optimal swell data is only provided if you include the “sort” resource - it is not included in the “surf” resource.
callback string jsonp callback function name

Spitcast

Spitcast has a documented API.

I’ve also asked Jack from Spitcast a few questions and added his responses below:

  • Why does the site show a size range, but the API only returns one size value?

    I actually take the API number and create the max by adding 1/6 the height (in feet), and then create the min by subtracting 1/6 the height."

  • All possible values for shape:
    • Poor
    • Poor-Fair
    • Fair
    • Fair-Good
    • Good

MagicSeaweed (no longer supported)

MagicSeaweed was acquired by Surfline an shut down in 2023. Prior to this, MagicSeaweed had a well-documented JSON API that required requesting an API key via email. This was a straightforward process and they got back to me quickly with my key.

I’ve asked MagicSeaweed a few questions and added their responses below:

  • “Our API provides 5 days of forecast data, with segments of data provided for each 3 hour interval during that 5 day time span.”
  • “Our data is updated every 3 hours.”

The Magic

Surf quality ratings

All of the forecasting services (including Surfline v1 vs v2) use different systems for rating waves. I’ve attempted to normalize them all to a 0-6 (6-point) scale as best as possible, which is perhaps easier to understand when mapped onto the commonly-used Poor-Good scale. At the top-end, Very Good is the typical top end, while Epic is allowed for in the rare occasions where Surfline’s rating goes above 5:

Value Meaning
0 Poor
1 Poor - Fair
2 Fair
3 Fair - Good
4 Good
5 Very Good
6+ Epic

Each forecasting service is massaged onto that scale as follows:

Surfline v2

5-point ratings, which equate to decimal values (0-4). These are massaged by multiplying by 5/4 to get a 0-5 scale.

The scale can go above 4, but the top 2 values require a human forecaster override, so they are rarely seen. Spinal Tap - It Goes to 11

Value Meaning
0 Very Poor
1 Poor
2 Poor - Fair
3 Fair
4 Fair - Good
5 Good
6 Epic
Spitcast
Value Meaning
0.0 Poor
0.5 Poor-Fair
1.0 Fair
1.5 Good

These are massaged by multiplying by 5/1.5 (essentially 3.3̅) to get a 0-5 scale.

For record-keeping, these are the formulae for formerly-supported services:

  • MagicSeaweed: integer fadedRating (0-5) & solidRating (0-5). I simply subtract fadedRating (which is essentially the negative effect of wind) from solidRating.
  • Surfline v1: decimal ratings (0-1) for up to 6 different swells at each spot, as well as an optimalWind boolean. I take the max swell rating at any given time for that spot, multiply it by 5, and then halve it if the wind is not optimal.

Timestamps

It took me a long time to land on a solution here, but I’ve finally settled on storing all timestamps in the database in the spot’s local time. This defies Rails convention, but makes intuitive sense. If you pull up the forecasts table and look at the timestamp, that’s the actual local time at that spot that it’s forecast for (even though Rails & Postgres both think it’s being stored in UTC). This is typically the format that the forecasting service gives it to us in, and what users want to see it in, so there’s no point in doing all sorts of fancy conversion when it should be the same all the way through the pipeline. Now, you may ask, why am I still using the Rails default of TIMESTAMP WITHOUT TIMEZONE, and the answer is that shockingly enough, TIMESTAMP WITH TIMEZONE doesn’t actually store timezone data!

TODO

  • Use SoFar API to get data from additional buoys
  • Figure out a way to convey forecast certainty in charts (ie: most forecasts are in agreement, or they disagree by a wide margin)
  • Fetch & display tide/wind/water temperature data from NOAA (they actually have a decent API!)
  • Update Surfline v2 API to use their new 7-point rating scale (thanks @smmuirhead100!)
  • Improve charts:
    • Fix timestamp formatting.
    • Account for min/max size forecast. Currently charts just reflect the max.
    • Display forecast quality ratings. Perhaps color each bar different depending on how good the rating is. Surfline also has an optimal_wind boolean that is being crudely integrated into the display_swell_rating method - improvements welcome.
  • Fetch & display recent buoy trends that are relevant to each spot to give an idea of when swell is actually arriving.
  • Refresh data on a schedule based on when new data is available (refreshing all forecast sources hourly)
  • Support multiple timezones as opposed to Pacific Time only
  • New Surfline API
  • Stop manually seeding the db and figure out a way to pull all spots from each data source and automatically associate them to a canonical spot record (probably using geocoding)
  • Dark Theme
  • Remove asset pipeline & process CSS w/ webpacker

评论

暂无评论