zelloe
zelloe

Reputation: 21

Converting Wind direction from degrees to text

I'm building this weatherapp for a project and the api is returning the wind direction in degrees and i want to convert it into to text Nort,northeast etc.

This is the code that im using

   const api = {
        key: "",
        base: "https://api.openweathermap.org/data/2.5/"
      }

      const searchbox = document.querySelector('.search-box');
      searchbox.addEventListener('keypress', setQuery);

      function setQuery(evt) {
        if (evt.keyCode == 13) {
          getResults(searchbox.value);
        }
      }

      function getResults (query) {
        fetch(`${api.base}weather?q=${query}&units=metric&APPID=${api.key}`)
          .then(weather => {
            return weather.json();
          }).then(displayResults);
      }

      function displayResults (weather) {
        console.log(weather);
        let city = document.querySelector('.location .city');
        city.innerText = `${weather.name}, ${weather.sys.country}`;

        let now = new Date();
        let date = document.querySelector('.location .date');
        date.innerText = dateBuilder(now);

        let temp = document.querySelector('.current .temp');
        temp.innerHTML = `${Math.round(weather.main.temp)}<span>°c</span>`;

        let weather_el = document.querySelector('.current .weather');
        weather_el.innerText = weather.weather[0].main;

        let hilow = document.querySelector('.hi-low');
        hilow.innerText = `${Math.round(weather.main.temp_min)}°c / ${Math.round(weather.main.temp_max)}°c`;

        let pressure = document.querySelector('.current .pressure');
        pressure.innerHTML =`${weather.main.pressure}<span> </span><span>PSi</span>`;

        let wind = document.querySelector('.current .wind');
        wind.innerHTML= `${weather.wind.speed} m/s`;

        let deg = document.querySelector('.current .deg')
        deg.innerText=`${weather.wind.deg}`;
      }

      function dateBuilder (d) {
        let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
        let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

        let day = days[d.getDay()];
        let date = d.getDate();
        let month = months[d.getMonth()];
        let year = d.getFullYear();

        return `${day} ${date} ${month} ${year}`;
      }

This line is returning the degrees

let deg = document.querySelector('.current .deg')
        deg.innerText=`${weather.wind.deg}`;

Is there an easy way to convert it?

Upvotes: 2

Views: 2676

Answers (3)

Colin Wray
Colin Wray

Reputation: 1

This 'C' function returns text for degrees in 8 point or 16 point notation. Parameters:

  • bearing set between 0 and 360, and
  • m16 set to 1 for 16 point notation or 0 for 8 point notation.
char *bearing_text(int bearing, int m16) {
    int v;
    static char* points[17] = {
        "NNE"," NE","ENE","  E","ESE"," SE","SSE","  S",
        "SSW"," SW","WSW","  W","WNW"," NW","NNW","  N", "   "
    };
    if (bearing >= 0) {
        double d = (double)bearing / 11.25;
        v = (int)floor(d);
        if (v==0 || (v==1 && m16==0)) v = 32;  // always North
        v = m16 ? (v-1)/2 : (((v-2)/4)*2)+1;
    } else v = 16;   // yields 3 spaces for negative bearings
    return(points[v]);
}

Upvotes: 0

zer00ne
zer00ne

Reputation: 43920

8, 16, or 32 Compass Points

Cardinals() Function

This function has a 2D array containing 3 sub-arrays:

/*
 | Intercardinal -  sub08 has 8 compass points 
 | Meteorological - sub16 has 8 compass points
 | Mariner -        sub32 has 16 compass points
 */
const sub08 = mtx[0] = ["n", "ne", "e",... "nw"];
const sub16 = mtx[1] = ["n-ne", "e-ne", "e-se",... "n-nw"];
const sub32 = mtx[2] = ["n by e", "ne by n", "ne by e",... "n by "w"];

Simply pass either 0, 1, or 2 and it will return an array of 8, 16, or 32 compass points. This function will be called within the primary function compass().

compass() Function

This function will convert a given number in the range of 0 to 359 to a compass point from an array of 8, 16, or 32 compass points provided by function cardinals(). In Demo A it has two parameters (deg and pts) that come from user input via an <input> and <select>. In Demo B it has a single parameter (deg) which comes from the OpenWeather API. The second value pts is predetermined by the developer by assigning 0, 1, or 2 to an object (cfg.compass). Other than those differences they are identical. The following are the expressions used to calculate the index number that corresponds to the given number of degrees (deg).

/**
 * Call cardinal() function pts = 0, 1, or 2 returns an array of 
 * 8, 16, or 32 compass points.
 */
const arr = cardinals(pts);
// 45, 22.5, or 11.25 degrees per compass point
const den = 360 / arr.length;
// Determine the index number of the array of compass points
let idx = Math.round(deg / den);
const pnt = arr[idx];

The rest of the function formats the compass point into a human readable string. In addition, it rotates the arrow icon that proceeds the compass point string when it's displayed by <output id="wind">.

Demo A

Standalone

const app = document.forms.app;
const io = app.elements;

const cardinals = (pts) => {
  pts = pts >= 0 && pts <= 2 ? pts : 1;
  const mtx = [
    ["n", "ne", "e", "se", "s", "sw", "w", "nw"],
    ["n-ne", "e-ne", "e-se", "s-se", "s-sw", "w-sw", "w-nw", "n-nw"],
    ["n by e", "ne by n", "ne by e", "e by n", "e by s", "se by e", "se by s", "s by e", "s by w", "sw by s", "sw by w", "w by s", "w by n", "nw by w", "nw by n", "n by w"]
  ].slice(0, pts + 1);
  if (mtx.length === 1) return mtx.flat();
  return mtx.reduce((zip, sub) => {
    return zip.map((c, i) => [c, sub[i]]).flat();
  });
};

const compass = (deg, pts) => {
  const crd = {
    n: "north",
    e: "east",
    s: "south",
    w: "west"
  };
  deg = deg >= 0 && deg <= 359 ? deg : 0;
  const arr = cardinals(pts);
  const den = 360 / arr.length;
  let idx = Math.round(deg / den);
  idx = idx >= 0 && idx < arr.length ? idx : 0;
  const pnt = arr[idx];
  const res = pnt.split("").map(chr => crd[chr] ? crd[chr] : chr).join("");
  const ico = io.wind.style;
  ico.setProperty("--rot", (deg + 180) + "deg");
  return res[0].toUpperCase() + res.slice(1);
};

app.addEventListener("input", (e) => {
  if (e.target.name === "cmp") {
    res = compass(io.deg.valueAsNumber, Number(io.car.value));
    io.wind.value = res;
  }
  if (io.deg.value === "360") {
    io.deg.value = "0";
  } else if (io.deg.value === "-1") {
    io.deg.value = "359";
  }
});

app.addEventListener("submit", (e) => e.preventDefault());
:root {
  font: 2ch/1.2 "Segoe UI"
}

fieldset {
  display: flex;
  flex-flow: column nowrap;
  align-items: center;
  width: max-content;
  padding: 0 1.5rem 1rem;
  border-radius: 6px;
}

legend {
  margin-bottom: 0.5rem;
  font-size: 1.15rem;
}

label {
  width: max-content;
  margin-top: 0.5rem;
}

input,
select {
  margin-top: 0.5rem;
  padding: 3px;
  border-radius: 4px;
  font: inherit;
}

select {
  text-align: center;
}

#wind {
  width: 90%;
}

#wind::before {
  content: "\0021ee";
  display: inline-block;
  height: 1.5rem;
  margin-right: 1rem;
  font-size: 2rem;
  line-height: 0.6;
  translate: 0 0.25rem;
  rotate: var(--rot);
}

#wind::after {
  content: "\00feff";
  display: inline-block;
}

#deg {
  width: 3rem;
  text-align: right;
}
<form id="app">
  <fieldset>
    <legend>Wind Direction</legend>
    <output id="wind"></output>
    <label>
      <input id="deg" name="cmp" type="number" min="-1" max="360" value="0"> Degrees
    </label>
    <label for="car">Compass Points</label>
    <select id="car" name="cmp">
      <option value="0">8 points Intercardinal</option>
      <option value="1" selected>16 points Meteorlogical</option>
      <option value="2">32 points Mariner</option>
    </select>
  </fieldset>

Since I'm not posting an API key the function getWeather() is bypassed and the json (weather) is hard coded. Just click the Search because <input id="find"> doesn't function because it provides the value for getWeather(). The object cfg is used to configure some internal values predetermined by the developer (e.g. you). View in Full page mode.

Demo B

Integrated with Weather App

const weather = {"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":300,"main":"Drizzle","description":"light intensity drizzle","icon":"09d"}],"base":"stations","main":{"temp":7.17,"pressure":1012,"humidity":81,"temp_min":6,"temp_max":8},"visibility":10000,"wind":{"speed":1.83286,"deg":318},"clouds":{"all":90},"dt":1485789600,"sys":{"type":1,"id":5091,"message":0.0103,"country":"GB","sunrise":1485762037,"sunset":1485794875},"id":2643743,"name":"London","cod":200};

let cfg = {
  base: "https://api.openweathermap.org/data/2.5/weather?q=",
  key: "&appid=",
  locale: "en-GB",
  format: {dateStyle: "full"},
  compass: 1 // 0 = 8 points, 1 = 16 points, 2 = 32 points
};

const modal = document.querySelector("dialog");
const app = document.forms.app;
const io = app.elements;

const formatDate = (date) => {
  return new Intl.DateTimeFormat(cfg.locale, cfg.format).format(new Date(date));
};

const cardinals = (pts) => {
  pts = pts >= 0 && pts <= 2 ? pts : 1;
  const mtx = [
    ["n", "ne", "e", "se", "s", "sw", "w", "nw"],
    ["n-ne", "e-ne", "e-se", "s-se", "s-sw", "w-sw", "w-nw", "n-nw"],
    ["n by e", "ne by n", "ne by e", "e by n", "e by s", "se by e", "se by s", "s by e", "s by w", "sw by s", "sw by w", "w by s", "w by n", "nw by w", "nw by n", "n by w"]
  ].slice(0, pts + 1);
  if (mtx.length === 1) return mtx.flat();
  return mtx.reduce((zip, sub) => {
    return zip.map((c, i) => [c, sub[i]]).flat();
  });
};

const compass = (deg) => {
  const crd = {n: "north", e: "east", s: "south", w: "west"};
  deg = deg >= 0 && deg <= 359 ? deg : 0;
  const arr = cardinals(cfg.compass);
  const den = 360 / arr.length;
  let idx = Math.round(deg / den);
  idx = idx >= 0 && idx < arr.length ? idx : 0;
  const pnt = arr[idx];
  const res = pnt.split("").map(chr => crd[chr] ? crd[chr] : chr).join("");
  const ico = io.wind.style;
  ico.setProperty("--rot", (deg + 180) + "deg");
  return res[0].toUpperCase() + res.slice(1);
};

const setWeather = (json, unit = false) => {
  const uni = {
    i: ["°F", "miles/hr"],
    m: ["°C", "meters/sec"]
  };
  let u = unit ? uni.i : uni.m;
  io.city.value = `${json.name}, ${json.sys.country}`;
  io.date.value = formatDate(new Date());
  io.temp.value = `${Math.round(json.main.temp)} ${u[0]}`;
  io.cond.value = json.weather[0].main;
  io.hilo.value = `${Math.round(json.main.temp_max)} / ${Math.round(json.main.temp_min)} ${u[0]}`;
  io.xatm.value = `${json.main.pressure} hPa`;
  io.wind.value = `${compass(json.wind.deg)} at ${json.wind.speed.toFixed(2)} ${u[1]}`;
};

const getWeather = async (qry) => {
  let imp = /-us|-lr|-mm/i.test(cfg.locale);
  let uni = imp ? "imperial" : "metric";
  const url = `${api.base}${qry}&units=${uni}${key}`;
  const resp = await fetch(url);
  const data = await resp.json();
  return setWeather(data, imp);
};

app.addEventListener("submit", (e) => {
  e.preventDefault();
  io.find.value;
  // getWeather(io.find.value);
  setWeather(weather);
  modal.showModal();
});

app.addEventListener("reset", (e) => {
  modal.close();
});
:root {
  font: 2ch/1.2 "Segoe UI"
}

dialog {
  padding: 0;
  border: 0;
  background: transparent;
}

form,
fieldset {
  border: 1.51515px outset rgb(128 128 128);
  border-radius: 6px;
  background: #fff;
}

form {
  width: max-content;
  padding: 0.5rem 0.5rem 1rem;
  box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
}

fieldset {
  display: flex;
  flex-flow: column nowrap;
  width: max-content;
  padding: 0 1.5rem 1rem;
}

legend {
  margin-bottom: 0.5rem;
  font-size: 1.15rem;
}

label {
  margin-top: 0.5rem;
}

input,
button {
  padding: 3px;
  border-radius: 4px;
  font: inherit;
}

input {
  border: 1.51515px inset rgb(128 128 128);
}

button {
  margin-top: 0.75rem;
  cursor: pointer
}

.split {
  display: flex;
  justify-content: space-between;
}

.block {
  margin-bottom: 0.5rem;
}

dialog fieldset {
  box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px;
}

#wind::before {
  content: "\0021ee";
  display: inline-block;
  height: 1.5rem;
  margin-right: 1rem;
  font-size: 2rem;
  line-height: 0.6;
  translate: 0 0.25rem;
  rotate: var(--rot);
}
<fieldset form="app">
  <legend>Weather</legend>
  <input id="find" type="search" form="app">
  <button form="app">Search</button>
</fieldset>

<dialog>
  <form id="app">
    <fieldset>
      <legend>
        <output id="city"></output>
      </legend>
      <output id="date"></output>
      <label class="split">
        <output id="cond"></output>
        <output id="temp"></output>
      </label>
      <label class="split">
        High / Low <output id="hilo"></output>
      </label>
      <label class="split">
        Barometric Pressure <output id="xatm"></output>
      </label>
      <label><div class="block">Wind Direction &amp; Speed</div>
        <output id="wind"></output>
      </label>
      <button type="reset">Close</button>
    </fieldset>
  </form>
</dialog>

Upvotes: 0

Daniel_I_Am
Daniel_I_Am

Reputation: 345

You can get the amount of degrees and turn it into the index of all directions. Basically turning 0-360 into 0-8, rounding to ensure a whole number. Then you have an index to use in an array of all directions.

// Insert the amount of degrees here
degrees = 10;

// Define array of directions
directions = ['north', 'northeast', 'east', 'southeast', 'south', 'southwest', 'west', 'northwest'];

// Split into the 8 directions
degrees = degrees * 8 / 360;

// round to nearest integer.
degrees = Math.round(degrees, 0);

// Ensure it's within 0-7
degrees = (degrees + 8) % 8

console.log(directions[degrees])

Upvotes: 7

Related Questions