models/weather.js

import Model from "./model.js";

/**
 * A weather report for a single day, including temperature, wind, precipitation,
 * sky conditions, sun/moon timing, and gameplay effect warnings. Here’s one presentation
 * of all this information:
 *
 * “Temp: low 73° to high 98° (including windchill). Wind: 17 to 29 mph. Sky: Moderate
 * drizzle today. Sun: Light from 5:04 to 20:24 (sunrise 5:33, sunset 19:55). Moon: 🌔
 * waxing gibbous moon (light from 20:24 to 3:37). Effects: Extra water rations, shade,
 * and inactivity at the height of the heat are needed to avoid heat stroke, heat
 * exhaustion, or death.”
 *
 * @property {number} tempMin - Minimum temperature in °F (accounts for windchill).
 * @property {number} tempMax - Maximum temperature in °F (accounts for windchill).
 * @property {number} windMax - Maximum sustained wind speed in mph.
 * @property {number} windGusts - Maximum wind gust speed in mph.
 * @property {number} rain - Total rainfall in inches.
 * @property {number} snowfall - Total snowfall in inches.
 * @property {string} sky - WMO weather code description of sky conditions.
 * @property {string} dawn - Dawn time in HH:MM format (local time).
 * @property {string} sunrise - Sunrise time in HH:MM format (local time).
 * @property {string} sunset - Sunset time in HH:MM format (local time).
 * @property {string} dusk - Dusk time in HH:MM format (local time).
 * @property {string} moonPhase - Moon phase emoji icon.
 * @property {string} moonText - Moon phase description (e.g. "full moon (bright at night)").
 * @property {string} moonLight - Moon illumination level description.
 * @property {string} moonRise - Effective moonrise time in HH:MM format, bounded by dusk.
 * @property {string} moonSet - Effective moonset time in HH:MM format, bounded by dawn.
 */
export default class Weather extends Model {
  constructor({
    tempMin,
    tempMax,
    windMax,
    windGusts,
    rain,
    snowfall,
    sky,
    dawn,
    sunrise,
    sunset,
    dusk,
    moonPhase,
    moonText,
    moonLight,
    moonRise,
    moonSet,
  } = {}) {
    super({});
    this.tempMin = tempMin;
    this.tempMax = tempMax;
    this.windMax = windMax;
    this.windGusts = windGusts;
    this.rain = rain;
    this.snowfall = snowfall;
    this.sky = sky;
    this.dawn = dawn;
    this.sunrise = sunrise;
    this.sunset = sunset;
    this.dusk = dusk;
    this.moonPhase = moonPhase;
    this.moonText = moonText;
    this.moonLight = moonLight;
    this.moonRise = moonRise;
    this.moonSet = moonSet;
    this._class = "Weather";
  }
  toString() {
    let warning = [];
    if (this.tempMin < 32) {
      warning.push("Cold weather gear or shelter required to avoid frostbite or death");
    } else if (this.tempMin < 45) {
      warning.push("Cold weather gear or shelter required to avoid exhaustion");
    } else if (this.tempMax > 97) {
      warning.push(
        "Extra water rations, shade, and inactivity at the height of the heat are needed to avoid heat stroke, heat exhaustion, or death",
      );
    } else if (this.tempMax > 80) {
      warning.push(
        "Warm weather limits strenuous outdoor activity and requires extra water rations to avoid exhaustion",
      );
    }
    if (this.snowfall > 1) {
      warning.push("Snow limits travel today");
    }
    if (warning.length === 0) {
      warning.push("Nothing about the weather is dangerous or impedes travel today");
    }
    const arr = [];
    if (this.tempMin < 32) {
      arr.push(["Temp", `low ${this.tempMin}° to high ${this.tempMax}° (including windchill)`]);
    } else {
      arr.push(["Temp", `low ${this.tempMin}° to high ${this.tempMax}°`]);
    }
    arr.push(["Wind", `${this.windMax} to ${this.windGusts} mph`]);

    // sky stuff
    const a = [this.sky];
    if (this.rain > 0.01) {
      a.push(`rain ${this.rain} in`);
    }
    if (this.snowfall > 0.01) {
      a.push(`snowfall ${this.snowfall} in`);
    }
    arr.push(["Sky", a.join(", ")]);
    arr.push([
      "Sun",
      `Light from ${this.dawn} to ${this.dusk} (sunrise ${this.sunrise}, sunset ${this.sunset})`,
    ]);
    // but if it’s dark out, moon rise and moon set don’t matter
    arr.push([
      "Moon",
      `${this.moonPhase} ${this.moonText} (${this.moonLight} from ${this.moonRise} to ${this.moonSet})`,
    ]);
    if (warning.length) {
      arr.push(["Effects", warning.join(". ")]);
    }
    // get wind chill information, it's more significant
    return "<p>" + arr.map((arr) => `<b>${arr[0]}:</b> ${arr[1]}`).join(". ") + ".</p>";
  }
}