Simple ocean tides widget

Hi all. I found myself wanting to know the next few high and low tides at a particular location. I couldn’t find a shortcut or widget that did what I wanted, so I made one. I thought I’d post it for anyone else who might be interested.

I built it by modifying an existing widget that showed covid rates by country (which I had saved as “getWebText”). Unfortunately, I don’t have a record of who wrote it — but a big thank-you to them!

I’ve set up (and commented) a variety of parameters as variables defined up high so they’re easy to change, including the NOAA tide station you use, the colors and the font sizes. The URL to look up tide station identifiers is also in a comment.

Unfortunately, I think NOAA only provides U.S. tide predictions.

Here’s what it looks like:

Here’s the code for the widget — enjoy!

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-green; icon-glyph: user-md;

// by T. Francis (theofrancis@gmail.com)
// adapted from "getWebText" covid monitoring script/widget
// modify ==> items for easy customization

// ==> change "stationID and "stationName"
// to a station number and name from 
// https://www.tidesandcurrents.noaa.gov/tide_predictions.html

// examples: 
// • "8728958" "St. Joe Point"
// • "9451439" "Petersburg, Alaska"
const stationID = "8728958"
const stationName = "St. Joe Point"

// ==> set colors 
// background color
const bkgdColor = Color.black()
// tide colors
const highColor = Color.blue()
const lowColor = Color.green()
// heading colors
const flashColor = Color.white()
const stationColor = Color.blue()
const todayColor = Color.white()
const dateColor = Color.gray()

// ==> set font sizes
const flashSize = 10
const stationSize = 14
const dateSize = 11
const lineSize = 10

// ==> set rounding for tide heights
// (number of decimal places to round to)
const roundPlaces = 0

// ==> set test to true to use the test data while modifying the widget or app, to avoid hitting the API unnecessarily
var test = false;

// generate dates (defaults to today & tomorrow)
const today = new Date()
let currentDate = new Date();
let cDay = addLeadingZeros(currentDate.getDate())
let cMonth = addLeadingZeros(currentDate.getMonth() + 1)
let cYear = currentDate.getFullYear()
let cDateFmt = cYear+"-"+cMonth+"-"+cDay
let cDateTod = cYear+""+cMonth+""+cDay
let cDateTom = cYear+""+cMonth+""+(cDay+1)

// create api query url with station id and dates
const url = `https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?product=predictions&application=NOS.COOPS.TAC.WL&begin_date=${cDateTod}&end_date=${cDateTom}&datum=MLLW&station=${stationID}&time_zone=lst_ldt&units=english&interval=hilo&format=json`
console.log(url)

// load live data unless in test mode
if (test == false) {  
  const req = new Request(url)
  var res = await req.loadJSON()
}
else {
  res = {"predictions":[{"t":"2021-04-13 01:52","v":"0.797","type":"H"},{"t":"2021-04-13 04:01","v":"0.701","type":"L"},{"t":"2021-04-13 08:49","v":"1.156","type":"H"},{"t":"2021-04-13 17:31","v":"0.092","type":"L"},{"t":"2021-04-14 09:00","v":"1.308","type":"H"},{"t":"2021-04-14 18:24","v":"0.029","type":"L"}]}
}

// create widget or run within Scriptable

if (config.runsInWidget) {
  // create and show widget
  let widget = createWidget(stationName, createPredictions(), bkgdColor)
  Script.setWidget(widget)
  Script.complete()
} else {
  console.log(createPredictions())
  // make table
  let table = new UITable()
  
  // add header
  let row = new UITableRow()
  row.isHeader = true
  row.addText(`Next tides for ${stationName}`)
  table.addRow(row)
  
  // fill data
  for (p in res.predictions) {
    if(res.predictions[p]["type"]=="H"){
      var ttype = "High"
    }
    else {
      var ttype = "Low"  
    }
    console.log(res.predictions[p])
    table.addRow(createRow(ttype, res.predictions[p]["t"]))
  }

  if (config.runsWithSiri)
    Speech.speak(`The next ${res.predictions[0]["type"]} tide is at ${res.predictions[0]["t"]}.`)
  
  // present table
  table.present()
}

// create list of lists used by widget
function createPredictions() {
  var result = ""
  var datetime = ""
  var ddate = ""
  var height = ""
  lines = []
  // make components more user friendly
  for (p in res.predictions) {
    // H/L to High/Low (tide)
    if(res.predictions[p]["type"]=="H"){
      var ttype = "High"
    }
    else {
      var ttype = "Low"  
    }
    // split data and time
    datetime = res.predictions[p]["t"]
    ddate = datetime.split(" ")[0]
    // replace today's date with "Today" 
    if (ddate == cDateFmt) {
      ddate = "Today"
    }
    ttime = datetime.split(" ")[1]
    height = res.predictions[p]["v"]
    height = Math.round(height * (10^roundPlaces))/(10^roundPlaces)
    // create day's entry to pass to widget
    let line = [ddate,ttime,ttype,height]
    lines.push(line)
  }
  return lines
}

function createRow(title, datetime) {
  let row = new UITableRow()
  row.addText(datetime)
  row.addText(title)
  return row
}

function addLeadingZeros(n) {
  if (n <= 9) {
    return "0" + n;
  }
  return n
}


function createWidget(pretitle, lines, color) {
  let w = new ListWidget()
  w.backgroundColor = bkgdColor
  // add Tides header
  let flash = w.addText("Tides")
  flash.textColor = Color.white()
  flash.textOpacity = 1
  flash.font = Font.systemFont(10)
  // add station name 
  let preTxt = w.addText(pretitle)
  preTxt.textColor = Color.red()
  preTxt.textOpacity = 0.8
  preTxt.font = Font.systemFont(14)
  w.addSpacer(5)
  // add prediction lines
  var refDate = ""
  var txt = ""
  var dateHeader = ""
  for (line in lines) {
    if (lines[line][0] != refDate) {
      refDate = lines[line][0]
      dateHeader = w.addText(lines[line][0])
      if (lines[line][0] == "Today") {
        dateHeader.textColor = todayColor
      }
      else {
        dateHeader.textColor = Color.gray()
      }
      dateHeader.font = Font.systemFont(11)
    }
    let txt = "• " + lines[line][1] + ": " + lines[line][2] + " (" + lines[line][3] + " ft.)"
    let text = w.addText(txt)
    if (lines[line][2] == "High") {
      text.textColor = highColor
    }
    else {
      text.textColor = lowColor
    }  
    text.font = Font.systemFont(10)
  }
  return w
}
2 Likes

Awesome, thanks! Most of the tide apps out there are not great. This is a simple way to monitor a pre-defined station.

Glad it could be useful. It occurred to me after posting that it would be relatively easy to modify it so the station ID and name is passed vis the widget parameter. Then you could display several widgets on a Home Screen, each showing a specific ride station.