view client/src/components/fairway/AvailableFairwayDepthDialogue.vue @ 4474:063f7a48904e

FWA_dialogue: date picking more robust
author Thomas Junk <thomas.junk@intevation.de>
date Tue, 24 Sep 2019 17:03:25 +0200
parents 130e929bab8f
children c1a02623f043
line wrap: on
line source

<template>
  <div
    :class="[
      'box ui-element rounded bg-white text-nowrap',
      { expanded: showFairwayDepth }
    ]"
  >
    <div style="width: 18rem">
      <UIBoxHeader icon="chart-line" :title="label" :closeCallback="close" />
      <div class="box-body">
        <UISpinnerOverlay v-if="loading" />
        <div class="mb-2 d-flex justify-content-between align-items-center">
          <div class="custom-control custom-radio custom-control-inline mr-2">
            <input
              :value="$options.BOTTLENECK"
              type="radio"
              v-model="type"
              id="type-bottleneck"
              class="custom-control-input"
            />
            <label
              class="custom-control-label small d-flex align-items-center"
              for="type-bottleneck"
            >
              <translate>Bottlenecks</translate>
            </label>
          </div>
          <div class="custom-control custom-radio custom-control-inline mr-2">
            <input
              :value="$options.STRETCH"
              type="radio"
              v-model="type"
              id="type-stretch"
              class="custom-control-input"
            />
            <label
              class="custom-control-label small d-flex align-items-center"
              for="type-stretch"
            >
              <translate>Stretches</translate>
            </label>
          </div>
          <div class="custom-control custom-radio custom-control-inline">
            <input
              :value="$options.SECTION"
              type="radio"
              v-model="type"
              id="type-section"
              class="custom-control-input"
            />
            <label
              class="custom-control-label small d-flex align-items-center"
              for="type-section"
            >
              <translate>Sections</translate>
            </label>
          </div>
        </div>
        <select
          v-if="type === $options.BOTTLENECK"
          @change="entrySelected"
          class="form-control font-weight-bold"
          v-model="selectedEntry"
        >
          <option :value="null">{{ placeholder }}</option>
          <optgroup
            v-for="(bottlenecksForCountry, cc) in orderedBottlenecks"
            :key="cc"
            :label="cc"
          >
            <option
              v-for="bn in bottlenecksForCountry"
              :key="bn.properties.id"
              :value="bn"
            >
              {{ bn.properties.name }}
            </option>
          </optgroup>
        </select>
        <select
          v-else-if="type === $options.STRETCH"
          @change="entrySelected"
          class="form-control font-weight-bold"
          v-model="selectedEntry"
        >
          <option :value="null">{{ placeholder }}</option>
          <option
            v-for="stretch in stretches"
            :value="stretch"
            :key="stretch.id"
          >
            {{ stretch.properties.name }}
          </option>
        </select>
        <select
          v-else-if="type === $options.SECTION"
          @change="entrySelected"
          class="form-control font-weight-bold"
          v-model="selectedEntry"
        >
          <option :value="null">{{ placeholder }}</option>
          <option
            v-for="section in sections"
            :value="section"
            :key="section.id"
          >
            {{ section.properties.name }}
          </option>
        </select>
        <div class="d-flex mt-2">
          <div class="d-flex flex-column w-50 mr-1">
            <small class="my-auto text-muted">
              <translate>Type</translate>
            </small>
            <select
              v-model="selectedFrequency"
              class="form-control form-control-sm"
            >
              <option
                v-for="(option, index) in $options.FREQUENCIES"
                :value="index"
                :key="index"
              >
                {{ option }}
              </option>
            </select>
          </div>
          <div class="d-flex flex-column w-50 ml-1">
            <small class="my-auto text-muted"><translate>LOS</translate></small>
            <select v-model="los" class="form-control form-control-sm">
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>
          </div>
        </div>
        <div class="d-flex mt-2">
          <div
            :key="new Date().getTime()"
            v-if="selectedFrequency === $options.FREQUENCIES.monthly"
            class="d-flex flex-column w-50 mr-1"
          >
            <small for="from" class="my-auto text-muted">
              <translate>Starting Month</translate>
            </small>
            <select v-model="startMonth" class="form-control form-control-sm">
              <option
                v-for="month in Object.keys($options.MONTHS)"
                :key="'start' + month"
                :value="month"
                >{{ $options.MONTHS[month] }}</option
              >
            </select>
          </div>
          <div
            :key="new Date().getTime()"
            v-if="selectedFrequency === $options.FREQUENCIES.quarterly"
            class="d-flex flex-column w-50 mr-1"
          >
            <small for="from" class="my-auto text-muted">
              <translate>Starting Quarter</translate>
            </small>
            <select v-model="startQuarter" class="form-control form-control-sm">
              <option
                v-for="month in [1, 2, 3, 4]"
                :key="'qstart' + month"
                :value="month"
                >Q{{ month }}</option
              >
            </select>
          </div>
          <div
            :class="[
              'd-flex',
              'flex-column',
              'w-50',
              { 'ml-1': selectedFrequency !== $options.FREQUENCIES.yearly }
            ]"
          >
            <small for="from" class="my-auto text-muted">
              <translate>Starting Year</translate>
            </small>
            <select v-model="startYear" class="form-control form-control-sm">
              <option
                v-for="year in $options.YEARS"
                :key="'start' + year"
                :value="year"
                >{{ year }}</option
              >
            </select>
          </div>
        </div>
        <div class="d-flex mt-2">
          <div
            :key="new Date().getTime()"
            class="d-flex flex-column w-50 mr-1"
            v-if="selectedFrequency === $options.FREQUENCIES.monthly"
          >
            <small for="to" class="my-auto text-muted">
              <translate>Ending Month</translate>
            </small>
            <select v-model="endMonth" class="form-control form-control-sm">
              <option
                v-for="month in Object.keys($options.MONTHS)"
                :key="'end' + month"
                :value="month"
                >{{ $options.MONTHS[month] }}</option
              >
            </select>
          </div>
          <div
            :key="new Date().getTime()"
            v-if="selectedFrequency === $options.FREQUENCIES.quarterly"
            class="d-flex flex-column w-50 mr-1"
          >
            <small for="from" class="my-auto text-muted">
              <translate>Ending Quarter</translate>
            </small>
            <select v-model="endQuarter" class="form-control form-control-sm">
              <option
                v-for="month in [1, 2, 3, 4]"
                :key="'qend' + month"
                :value="month"
                >Q{{ month }}</option
              >
            </select>
          </div>
          <div
            :class="[
              'd-flex',
              'flex-column',
              'w-50',
              { 'ml-1': selectedFrequency !== $options.FREQUENCIES.yearly }
            ]"
          >
            <small for="to" class="my-auto text-muted">
              <translate>Ending Year</translate>
            </small>
            <select v-model="endYear" class="form-control form-control-sm">
              <option
                v-for="year in $options.YEARS"
                :key="'end' + year"
                :value="year"
                >{{ year }}</option
              >
            </select>
          </div>
        </div>
        <div v-if="depthLimitVisible" class="d-flex mt-2" :key="1">
          <div class="d-flex flex-column w-50 mr-1">
            <small for="from" class="my-auto text-muted">
              <translate>Depthlimit 1 [m]</translate>
            </small>
            <input
              id="depthlimit1"
              v-model.number="depthLimit1"
              class="form-control form-control-sm"
              type="number"
              min="0"
              step="0.1"
            />
          </div>
          <div
            v-if="depthLimitVisible"
            class="d-flex flex-column w-50 ml-1"
            :key="2"
          >
            <small for="to" class="my-auto text-muted">
              <translate>Depthlimit 2 [m]</translate>
            </small>
            <input
              id="depthlimit2"
              v-model.number="depthLimit2"
              class="form-control form-control-sm"
              type="number"
              min="0"
              step="0.1"
            />
          </div>
        </div>
        <div v-if="widthLimitVisible" class="d-flex mt-2" :key="3">
          <div class="d-flex flex-column w-50 mr-1">
            <small for="from" class="my-auto text-muted">
              <translate>Widthlimit 1 [m]</translate>
            </small>
            <input
              id="widthLimit"
              v-model.number="widthLimit1"
              class="form-control form-control-sm"
              type="number"
              min="0"
            />
          </div>
          <div
            v-if="widthLimitVisible"
            class="d-flex flex-column w-50 mr-1"
            :key="4"
          >
            <small for="from" class="my-auto text-muted">
              <translate>Widthlimit 2 [m]</translate>
            </small>
            <input
              id="widthLimit"
              v-model.number="widthLimit2"
              class="form-control form-control-sm"
              type="number"
              min="0"
            />
          </div>
        </div>
        <div class="d-flex mt-2">
          <div class="d-flex flex-column w-50 ml-1">
            <small for="from" class="my-auto text-muted">
              <translate>From</translate> {{ fromDate | surveyDate }}
            </small>
          </div>
          <div class="d-flex flex-column w-50 ml-1">
            <small for="to" class="my-auto text-muted">
              <translate>To</translate> {{ toDate | surveyDate }}
            </small>
          </div>
        </div>
        <div class="mt-3">
          <button
            @click="openFairwaydepthDiagram"
            :disabled="!isComplete"
            class="btn btn-info btn-sm d-block w-100"
          >
            <translate>Available fairway depth</translate>
          </button>
          <button
            @click="openFairwaydepthLNWLDiagram"
            :disabled="!isComplete"
            class="btn btn-info btn-sm d-block w-100 mt-2"
          >
            <translate>Available fairway depth vs LNWL</translate>
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="sass" scoped>
input,
select
  font-size: 0.8em

.custom-control
  padding-left: 1.2rem
  .custom-control-label
    &::before,
    &::after
      left: -1.2rem
</style>

<script>
/* This is Free Software under GNU Affero General Public License v >= 3.0
 * without warranty, see README.md and license for details.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * License-Filename: LICENSES/AGPL-3.0.txt
 *
 * Copyright (C) 2018 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * Markus Kottländer <markus.kottlaender@intevation.de>
 * Thomas Junk <thomas.junk@intevation.de>
 */

import app from "@/main";
import { displayError } from "@/lib/errors";
import { mapState, mapGetters } from "vuex";
import { LIMITINGFACTORS } from "@/store/fairwayavailability";
import {
  subYears,
  getMonth,
  startOfYear,
  startOfMonth,
  endOfMonth,
  endOfYear,
  format,
  getQuarter,
  startOfQuarter,
  endOfQuarter,
  setQuarter,
  getYear
} from "date-fns";

const isoFormat = date => {
  return format(date, "YYYY-MM-DD");
};

export default {
  data() {
    return {
      loading: false,
      startMonth: null,
      endMonth: null,
      startQuarter: null,
      endQuarter: null,
      startYear: null,
      endYear: null
    };
  },
  computed: {
    ...mapState("application", [
      "showFairwayDepth",
      "paneSetup",
      "showProfiles"
    ]),
    ...mapState("fairwayavailability", [
      "selectedFairwayAvailabilityFeature",
      "from",
      "to",
      "frequency",
      "LOS",
      "depthlimit1",
      "depthlimit2",
      "widthlimit1",
      "widthlimit2"
    ]),
    ...mapState("imports", [
      "stretches",
      "sections",
      "selectedStretchId",
      "selectedSectionId"
    ]),
    ...mapState("bottlenecks", ["bottlenecksList", "selectedBottleneck"]),
    ...mapGetters("map", ["openLayersMap"]),
    ...mapGetters("bottlenecks", [
      "orderedBottlenecks",
      "limitingFactorsPerBottleneck"
    ]),
    depthLimitVisible() {
      if (this.type !== this.$options.BOTTLENECK) return true;
      if (
        this.selectedEntry &&
        this.limitingFactorsPerBottleneck[this.selectedEntry.properties.name] ==
          this.$options.LIMITINGFACTORS.DEPTH
      )
        return true;
      return false;
    },
    widthLimitVisible() {
      if (this.type !== this.$options.BOTTLENECK) return true;
      if (
        this.selectedEntry &&
        this.limitingFactorsPerBottleneck[this.selectedEntry.properties.name] ==
          this.$options.LIMITINGFACTORS.WIDTH
      )
        return true;
    },
    limitingFactor() {
      if (this.type !== this.$options.BOTTLENECK) return;
      if (this.selectedEntry)
        return this.limitingFactorsPerBottleneck[
          this.selectedEntry.properties.name
        ];
    },
    isComplete() {
      return this.selectedFairwayAvailabilityFeature !== null;
    },
    type: {
      get() {
        return this.$store.state.fairwayavailability.type;
      },
      set(type) {
        this.$store.commit("fairwayavailability/type", type);
      }
    },
    los: {
      get() {
        return this.LOS;
      },
      set(value) {
        this.$store.commit("fairwayavailability/setLOS", value);
      }
    },
    fromDate: {
      get() {
        return this.from;
      },
      set(value) {
        this.$store.commit("fairwayavailability/setFrom", value);
      }
    },
    toDate: {
      get() {
        return this.to;
      },
      set(value) {
        this.$store.commit("fairwayavailability/setTo", value);
      }
    },
    depthLimit1: {
      get() {
        return this.depthlimit1 / 100;
      },
      set(value) {
        this.$store.commit(
          "fairwayavailability/setDepthlimit1",
          Math.round(value * 100)
        );
      }
    },
    depthLimit2: {
      get() {
        return this.depthlimit2 / 100;
      },
      set(value) {
        this.$store.commit(
          "fairwayavailability/setDepthlimit2",
          Math.round(value * 100)
        );
      }
    },
    widthLimit1: {
      get() {
        return this.widthlimit1;
      },
      set(value) {
        this.$store.commit("fairwayavailability/setWidthlimit1", value);
      }
    },
    widthLimit2: {
      get() {
        return this.widthlimit2;
      },
      set(value) {
        this.$store.commit("fairwayavailability/setWidthlimit2", value);
      }
    },
    selectedFrequency: {
      get() {
        return this.frequency;
      },
      set(value) {
        this.$store.commit("fairwayavailability/setFrequency", value);
      }
    },
    selectedEntry: {
      get() {
        return this.selectedFairwayAvailabilityFeature;
      },
      set(feature) {
        this.$store.commit(
          "fairwayavailability/setSelectedFairwayAvailability",
          feature
        );
      }
    },
    label() {
      return this.$gettext("Available fairway depth");
    },
    placeholder() {
      if (this.type === this.$options.BOTTLENECK)
        return this.$gettext("Select bottleneck");
      if (this.type === this.$options.STRETCH)
        return this.$gettext("Select stretch");
      return this.$gettext("Select section");
    }
  },
  watch: {
    selectedFrequency() {
      const now = new Date();
      switch (this.selectedFrequency) {
        case this.$options.FREQUENCIES.monthly:
          this.startMonth = getMonth(now) + 1;
          this.endMonth = getMonth(now) + 1;
          this.startYear = getYear(subYears(now, 1));
          this.endYear = getYear(now);
          this.fromDate = isoFormat(subYears(startOfMonth(now), 1));
          this.toDate = isoFormat(endOfMonth(now));
          break;
        case this.$options.FREQUENCIES.quarterly:
          this.startQuarter = this.dateToQuarter(now);
          this.endQuarter = this.dateToQuarter(now);
          this.startYear = getYear(subYears(now, 1));
          this.endYear = getYear(now);
          this.fromDate = isoFormat(subYears(startOfQuarter(now), 1));
          this.toDate = this.toDate = isoFormat(endOfQuarter(now));
          break;
        case this.$options.FREQUENCIES.yearly:
          this.startYear = getYear(subYears(now, 1));
          this.endYear = getYear(now);
          this.fromDate = isoFormat(subYears(startOfYear(now), 1));
          this.toDate = isoFormat(endOfYear(now));
          break;
        default:
          throw new Error("undefined frequency");
      }
    },
    startQuarter() {
      this.fromDate = isoFormat(
        startOfQuarter(this.quarterToDate(this.startYear, this.startQuarter))
      );
    },
    endQuarter() {
      this.toDate = isoFormat(
        endOfQuarter(this.quarterToDate(this.endYear, this.endQuarter))
      );
    },
    startMonth() {
      this.calcStart();
    },
    startYear() {
      this.calcStart();
    },
    endMonth() {
      this.calcEnd();
    },
    endYear() {
      this.calcEnd();
    },
    depthLimitVisible() {
      if (this.depthLimitVisible) {
        this.depthLimit1 = 2.3;
        this.depthLimit2 = 2.5;
      }
    },
    selectedBottleneck() {
      this.type = this.$options.BOTTLENECK;
      this.setSelectedBottleneck();
    },
    selectedStretchId() {
      this.type = this.$options.STRETCH;
      this.setSelectedStretch();
    },
    selectedSectionId() {
      this.type = this.$options.SECTION;
      this.setSelectedSection();
    },
    type(type) {
      if (type === this.$options.BOTTLENECK && this.selectedBottleneck) {
        this.openLayersMap()
          .getLayer("BOTTLENECKS")
          .setVisible(true);
        this.setSelectedBottleneck();
      } else if (type === this.$options.STRETCH && this.selectedStretchId) {
        this.openLayersMap()
          .getLayer("STRETCHES")
          .setVisible(true);
        this.setSelectedStretch();
      } else if (type === this.$options.SECTION && this.selectedSectionId) {
        this.openLayersMap()
          .getLayer("SECTIONS")
          .setVisible(true);
        this.setSelectedSection();
      } else {
        this.$store.commit(
          "fairwayavailability/setSelectedFairwayAvailability",
          null
        );
      }
    },
    showFairwayDepth() {
      if (this.showFairwayDepth) {
        this.loading = true;
        Promise.all([
          this.$store.dispatch("bottlenecks/loadBottlenecks"),
          this.$store.dispatch("bottlenecks/loadBottlenecksList"),
          this.$store.dispatch("imports/loadStretches"),
          this.$store.dispatch("imports/loadSections")
        ])
          .then(() => {
            if (this.selectedBottleneck) this.setSelectedBottleneck();
          })
          .finally(() => (this.loading = false));
      }
    }
  },
  methods: {
    dateToQuarter(date) {
      return getQuarter(date);
    },
    quarterToDate(year, quarter) {
      return setQuarter(new Date(year, 1, 1), quarter);
    },
    calcStart() {
      switch (this.selectedFrequency) {
        case this.$options.FREQUENCIES.monthly:
          this.fromDate = isoFormat(
            startOfMonth(new Date(this.startYear, this.startMonth - 1, 1))
          );
          break;
        case this.$options.FREQUENCIES.quarterly:
          this.fromDate = isoFormat(
            startOfQuarter(
              this.quarterToDate(this.startYear, this.startQuarter)
            )
          );
          break;
        case this.$options.FREQUENCIES.yearly:
          this.fromDate = isoFormat(new Date(this.startYear, 0, 1));
          break;
        default:
          throw new Error("undefined frequency");
      }
    },
    calcEnd() {
      switch (this.selectedFrequency) {
        case this.$options.FREQUENCIES.monthly:
          this.toDate = isoFormat(
            endOfMonth(new Date(this.endYear, this.endMonth - 1, 1))
          );
          break;
        case this.$options.FREQUENCIES.quarterly:
          this.toDate = isoFormat(
            isoFormat(
              endOfQuarter(this.quarterToDate(this.endYear, this.endQuarter))
            )
          );
          break;
        case this.$options.FREQUENCIES.yearly:
          this.toDate = isoFormat(endOfYear(new Date(this.endYear, 11, 31)));
          break;
        default:
          throw new Error("undefined frequency");
      }
    },
    initDates() {
      const endDate = new Date();
      const startDate = subYears(new Date(), 1);
      this.startMonth = getMonth(startDate) + 1;
      this.endMonth = getMonth(endDate) + 1;
      this.startYear = getYear(startDate);
      this.endYear = getYear(endDate);
      this.fromDate = isoFormat(startOfMonth(startDate));
      this.toDate = isoFormat(endOfMonth(endDate));
      this.startQuarter = this.dateToQuarter(startDate);
      this.endQuarter = this.dateToQuarter(endDate);
    },
    openFairwaydepthLNWLDiagram() {
      this.clearInvisibleFormValues();
      this.loading = true;
      this.$store
        .dispatch("fairwayavailability/loadAvailableFairwayDepthLNWLDiagram", {
          feature: this.selectedFairwayAvailabilityFeature,
          from: this.from,
          to: this.to,
          frequency: this.frequency,
          LOS: this.los,
          type: this.type,
          depthLimit1: this.depthlimit1,
          depthLimit2: this.depthlimit2,
          widthLimit1: this.widthLimit1,
          widthLimit2: this.widthLimit2,
          limitingFactor: this.limitingFactor
        })
        .then(() => {
          this.$store.commit(
            "application/paneSetup",
            "AVAILABLEFAIRWAYDEPTHLNWL"
          );
        })
        .catch(error => {
          const { status, data } = error.response;
          displayError({
            title: this.$gettext("Backend Error"),
            message: `${status}: ${data.message || data}`
          });
        })
        .finally(() => {
          this.loading = false;
        });
    },
    clearInvisibleFormValues() {
      if (!this.widthLimitVisible) {
        this.widthLimit1 = null;
        this.widthLimit2 = null;
      }
      if (!this.depthLimitVisible) {
        this.depthLimit1 = null;
        this.depthLimit2 = null;
      }
    },
    openFairwaydepthDiagram() {
      this.loading = true;
      this.clearInvisibleFormValues();
      this.$store
        .dispatch("fairwayavailability/loadAvailableFairwayDepth", {
          feature: this.selectedFairwayAvailabilityFeature,
          from: this.from,
          to: this.to,
          frequency: this.frequency,
          LOS: this.los,
          type: this.type,
          depthLimit1: this.depthlimit1,
          depthLimit2: this.depthlimit2,
          widthLimit1: this.widthLimit1,
          widthLimit2: this.widthLimit2,
          limitingFactor: this.limitingFactor
        })
        .then(() => {
          this.$store.commit("application/paneSetup", "AVAILABLEFAIRWAYDEPTH");
        })
        .catch(error => {
          const { status, data } = error.response;
          displayError({
            title: this.$gettext("Backend Error"),
            message: `${status}: ${data.message || data}`
          });
        })
        .finally(() => {
          this.loading = false;
        });
    },
    close() {
      this.$store.commit("application/showFairwayDepth", false);
      this.$store.commit("application/showFairwayDepthLNWL", false);
    },
    entrySelected() {
      if (this.type === this.$options.BOTTLENECK) {
        this.openLayersMap()
          .getLayer("BOTTLENECKS")
          .setVisible(true);
        if (this.showProfiles) {
          this.$store.dispatch(
            "bottlenecks/setSelectedBottleneck",
            this.selectedFairwayAvailabilityFeature.properties.name
          );
        }
      }
      if (this.type === this.$options.STRETCH) {
        this.openLayersMap()
          .getLayer("STRETCHES")
          .setVisible(true);
      }
      if (this.type === this.$options.SECTION) {
        this.openLayersMap()
          .getLayer("SECTIONS")
          .setVisible(true);
      }
      if (this.selectedFairwayAvailabilityFeature) {
        this.$store.dispatch("map/moveToFeauture", {
          feature: this.selectedFairwayAvailabilityFeature,
          zoom: 17,
          preventZoomOut: true
        });
      }
    },
    setSelectedBottleneck() {
      const bn = this.bottlenecksList.filter(
        x => x.properties.name === this.selectedBottleneck
      )[0];
      this.$store.commit(
        "fairwayavailability/setSelectedFairwayAvailability",
        bn
      );
    },
    setSelectedStretch() {
      const stretch = this.stretches.find(x => x.id === this.selectedStretchId);
      this.$store.commit(
        "fairwayavailability/setSelectedFairwayAvailability",
        stretch
      );
    },
    setSelectedSection() {
      const section = this.sections.find(x => x.id === this.selectedSectionId);
      this.$store.commit(
        "fairwayavailability/setSelectedFairwayAvailability",
        section
      );
    }
  },
  mounted() {
    this.initDates();
  },
  BOTTLENECK: "bottleneck",
  SECTION: "section",
  STRETCH: "stretch",
  AVAILABLEFAIRWAYDEPTH: app.$gettext("Available Fairway Depth"),
  FREQUENCIES: {
    monthly: app.$gettext("monthly"),
    quarterly: app.$gettext("quarterly"),
    yearly: app.$gettext("yearly")
  },
  YEARS: [
    2015,
    2016,
    2017,
    2018,
    2019,
    2020,
    2021,
    2022,
    2023,
    2024,
    2025,
    2026,
    2027,
    2028,
    2029,
    2030,
    2031,
    2032,
    2033,
    2034,
    2035,
    2036,
    2037,
    2038,
    2039,
    2040,
    2041,
    2042,
    2043,
    2044,
    2045,
    2046,
    2047,
    2048,
    2049,
    2050
  ],
  MONTHS: {
    1: "January",
    2: "February",
    3: "March",
    4: "April",
    5: "May",
    6: "June",
    7: "July",
    8: "August",
    9: "September",
    10: "October",
    11: "November",
    12: "December"
  },
  LIMITINGFACTORS: LIMITINGFACTORS
};
</script>