Ocean's Watch:

A shelf-ready clock that graphs and displays the daily tides for any NOAA weather station.

Put this rustic driftwood clock on any shelf in your home and never buy a tide chart again! Perfect for any coastal residence, or for those missing the salty bite of the sea breeze, this product allows you to see the tides and time of any tide-reporting NOAA station.

CenterFrame.png

Side.png

orthoView.png

home.png

TitleCard.png

Code:

This clock is a Version 2 to a previous iteration. The skeleton is the same, so this documentation will cover new features. Please refer to this post for the base code and functionality.

Change Station and Numpad:

The first thing I tackled for this assignment was adding a numPad that could update the tides from other NOAA stations. So, I needed to come up with a way for the user to input data. Without a keyboard for the user to use, I had to create a retractable numPad.

So, for ease of implementation, I made an object for one.

The main thing to work on was spacing out the numbers and having them return a single digit to a string that updates. I looked into using some libraries for this kind of thing, but it was more trouble than it was worth and I ended up making it from scratch:

class numPad {
  int buffer;
  int dimensionX;
  int dimensionY;
  int size;
  int iconSize;
  String submission = "";
  int counter = 0;
  int iconPosX = width - width/20;
  int iconPosY = width/20;
  float clearX;
  float clearY;
  float subX;
  float subY;
  int radius = 20;
  //color color1 =

  numPad(int tempBuff, int tempDx, int tempDy, int tempSize) {
    buffer = tempBuff;
    dimensionX = tempDx;
    dimensionY = tempDy;
    size = tempSize;
    iconSize = size/2;
  }

  void display() {
    rectMode(CENTER);
    counter = 0;
    fill(255);
    if (padOn) {
      rect(width/2, height/2, dimensionX, dimensionY, radius*2);
      rectMode(CENTER);
      fill(255);
      textFont(mono, 30);
      text(submission, width/2, height/8-5);
      fill(dotB);
      for (int y = 0; y < 4; y++) {
        float tempY = map(dimensionY, 0, dimensionY, 0, height/2) + buffer*y;
        tempY -= (2*buffer) - (buffer/2);
        for (int x = 0; x < 3; x++) {
          float tempX = map(dimensionX, 0, dimensionX, 0, width/2);
          if (x == 0) {
            tempX -= buffer + size/4;
          } else if (x == 2) {
            tempX += buffer + size/4;
          }
          int numSize = 25;
          if (y == 3) {
            //textSize(numSize);
            textAlign(CENTER);
            if (x == 0) {
              clearX = tempX;
              clearY = tempY;
              rect (tempX, tempY, size+20, size, radius);
              push();
              translate(tempX, tempY);
              fill(255);
              textSize(numSize-5);
              fill(255);
              text("Clear", 0, numSize/3);
              //textSize(20);
              pop();
            } else if (x == 2) {
              subX = tempX;
              subY = tempY;
              rect (tempX, tempY, size+20, size, radius);
              push();
              translate(tempX, tempY);
              fill(255);
              textSize(numSize-5);
              fill(255);
              text("Submit", 0, numSize/3);
              //textSize(20);
              pop();
            } else {
              rect(tempX, tempY, size, size, radius);
              push();
              translate(tempX, tempY);
              fill(255);
              textSize(numSize);
              fill(255);
              text(counter, 0, numSize/3);
              pop();
              buttonPos[counter].x = tempX;
              buttonPos[counter].y = tempY;
              counter++;
            }
          } else {
            rect(tempX, tempY, size, size, radius);
            push();
            translate(tempX, tempY);
            fill(255);
            textSize(numSize);
            fill(255);
            text(counter, 0, numSize/3);
            pop();
            //println(counter);
            buttonPos[counter].x = tempX;
            buttonPos[counter].y = tempY;
            counter++;
          }
        }
      }
    }
    //Icon
    rect(iconPosX, iconPosY, iconSize, iconSize, radius);
  }

  void type() {
    boolean post = true;
    if (submission.length() == 12) {
      submission = "";
    }
    if (submission.length() < 7) {
      for (int i = 0; i < buttonPos.length; i++) {
        if (mouseX < buttonPos[i].x+size/2 && mouseX >= buttonPos[i].x - size/2) {
          if (mouseY < buttonPos[i].y+size/2 && mouseY >= buttonPos[i].y - size/2) {
            submission = submission.concat(str(i));
          }
        }
      }
    }
    //println(submission);
    if (mouseX < clearX+size/2 && mouseX > clearX-size/2) {
      if (mouseY < clearY+size/2 && mouseY > clearY-size/2) {
        submission = "";
      }
    }
    if (mouseX < subX+(size+20)/2 && mouseX > subX-(size+20)/2) {
      if (mouseY < subY+size/2 && mouseY > subY-size/2) {
        stationID = submission;
        url = "<https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?station=>" + stationID + "&date=today&product=predictions&units=english&time_zone=lst_ldt&interval=hilo&format=json&datum=MLLW";
        url_latLong = "<https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?station="+stationID+"&date=today&product=water_temperature&units=english&time_zone=lst_ldt&application=ports_screen&format=json>";
        try {
          getData();
        }
        catch (Throwable e) {
          post = false;
        }
        if (post) {
          getData();
          padOn = false;
          submission = "";
        } else {
          submission = "INVALID CODE";
        }
      }
    }
  };

  void checkClick() {
    if (mouseX < iconPosX+(iconSize/2) && mouseX >= iconPosX-(iconSize/2)) {
      if (mouseY < iconPosY+(iconSize/2) && mouseY >= iconPosY-(iconSize/2)) {
        padOn = !padOn;
        if (padOn == false) {
          submission = "";
        }
      }
    }
  }
}

Something to notice here is the use of try{}catch{}, which I included in order to make sure that the user is unable to input an invalid token without completely crashing the system. It will show "invalid code" when something doesn't work.

When the user submits something that works, the stationID variable is updated and then updated on the url variable. From here, getData() can parse as it usually does.

beforeChange.png

StationID.png

afterChange.png

Edgecases:

Ignoring the updated time and astronomical data, the last graph looks a little odd. This is an example of an unhandled edgecase. Due to the volatile nature of the API, a lot of different issues come up that could not be tackled in the timeframe of this project. However, to go back through and adjust them is totally possible. In this case, it would require updating how all of the arrays are initialized and make certain aspects of the variables more dynamic. For this final, however, I would need to start back at square one for a lot of this, so it will come in a future iteration.