From 37c506249a6e2e910b898ff90b8ac189a59142a8 Mon Sep 17 00:00:00 2001 From: Flare Microsystems Date: Thu, 7 Mar 2024 14:15:34 -0800 Subject: [PATCH] Initial Commit --- .classpath | 7 + .gitignore | 26 +++ .gitlab-ci.yml | 13 ++ .project | 17 ++ .settings/org.eclipse.jdt.core.prefs | 11 + .../datamart/CanadaDatamartProvider.java | 92 ++++++++ .../datamart/DatamartTranslation.java | 117 ++++++++++ .../datamart/ForecastProcessor.java | 213 ++++++++++++++++++ .../visualforecast/datamart/TownInfo.java | 17 ++ .../visualforecast/datamart/XMLUtils.java | 56 +++++ src/provider.properties | 1 + src/translation.csv | 99 ++++++++ 12 files changed, 669 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 src/com/flaremicro/visualforecast/datamart/CanadaDatamartProvider.java create mode 100644 src/com/flaremicro/visualforecast/datamart/DatamartTranslation.java create mode 100644 src/com/flaremicro/visualforecast/datamart/ForecastProcessor.java create mode 100644 src/com/flaremicro/visualforecast/datamart/TownInfo.java create mode 100644 src/com/flaremicro/visualforecast/datamart/XMLUtils.java create mode 100644 src/provider.properties create mode 100644 src/translation.csv diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..d43cf97 --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2be9ec5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +#Compiled binary directory +bin/ + +# Package Files # +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +*.dat diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..fef13ef --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,13 @@ +# You can override the included template(s) by including variable overrides +# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings +# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings +# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings +# Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings +# Note that environment variables can be set in several places +# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence +stages: +- test +sast: + stage: test +include: +- template: Security/SAST.gitlab-ci.yml diff --git a/.project b/.project new file mode 100644 index 0000000..6a49f90 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + CanadaDatamartProvider + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..8000cd6 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/src/com/flaremicro/visualforecast/datamart/CanadaDatamartProvider.java b/src/com/flaremicro/visualforecast/datamart/CanadaDatamartProvider.java new file mode 100644 index 0000000..891640a --- /dev/null +++ b/src/com/flaremicro/visualforecast/datamart/CanadaDatamartProvider.java @@ -0,0 +1,92 @@ +package com.flaremicro.visualforecast.datamart; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import com.flaremicro.util.Util; +import com.flaremicro.visualforecast.PropertyManager; +import com.flaremicro.visualforecast.api.ForecastProvider; +import com.flaremicro.visualforecast.forecast.ForecastDetails; + +public class CanadaDatamartProvider extends ForecastProvider { + boolean ready = false; + PropertyManager propertyManager; + ForecastProcessor forecastProcessor; + + ArrayList towns = new ArrayList(); + + @Override + public void init() { + propertyManager = super.getOwnPropertyManager(); + String byCode = propertyManager.getString("towns-by-code", "").toLowerCase(); + String byNameAndProvince = propertyManager.getString("towns-by-name-and-province", "").toLowerCase(); + + if(byCode.isEmpty() && byNameAndProvince.isEmpty()) + { + ready = true; + return; + } + + BufferedReader bufferedReader = null; + try + { + HashSet byCodes = new HashSet(Arrays.asList(byCode.split(","))); + HashSet byNameAndProvinces = new HashSet(Arrays.asList(byNameAndProvince.split(";"))); + URL url = new URL("https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv"); + bufferedReader = new BufferedReader(new InputStreamReader(url.openStream())); + String line; + int skip = 2; + while ((line = bufferedReader.readLine()) != null) + { + if (skip > 0) + { + skip--; + continue; + } + String[] data = line.trim().split(","); + if (byCodes.contains(data[0].toLowerCase()) || byNameAndProvinces.contains(data[1].toLowerCase() + "," + data[2].toLowerCase())) + { + String code = data[0].trim(); + String town = data[1].trim(); + String province = data[2].trim(); + float latitude = Float.parseFloat(data[3].trim().substring(0, data[3].length()-1)); + float longitude = Float.parseFloat(data[4].trim().substring(0, data[4].length()-1)); + towns.add(new TownInfo(code, town, province, latitude, longitude)); + } + } + } + catch (IOException e) + { + e.printStackTrace(); + ready = true; + } + finally + { + Util.cleanClose(bufferedReader); + } + forecastProcessor = new ForecastProcessor(towns.toArray(new TownInfo[0])); + forecastProcessor.processForecasts(); + ready = true; + } + + @Override + public ForecastDetails getForecast() { + return forecastProcessor.getMostRecentForecast(); + } + + @Override + public boolean isForecastReady() { + return ready; + } + + @Override + public void deinit() { + propertyManager.store(); + } + +} diff --git a/src/com/flaremicro/visualforecast/datamart/DatamartTranslation.java b/src/com/flaremicro/visualforecast/datamart/DatamartTranslation.java new file mode 100644 index 0000000..dab7c34 --- /dev/null +++ b/src/com/flaremicro/visualforecast/datamart/DatamartTranslation.java @@ -0,0 +1,117 @@ +package com.flaremicro.visualforecast.datamart; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; + +import com.flaremicro.util.Util; +import com.flaremicro.visualforecast.icons.IconProvider; + +public class DatamartTranslation { + + private HashMap iconTranslation = new HashMap(); + private HashMap stringTranslation = new HashMap(); + + public DatamartTranslation() { + iconTranslation.put(0, IconProvider.SUN.id); + iconTranslation.put(1, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(2, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(3, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(4, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(5, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(6, IconProvider.SCATTERD_SHOWERS.id); + iconTranslation.put(7, IconProvider.RAIN_SNOW.id); + iconTranslation.put(8, IconProvider.SNOW.id); + iconTranslation.put(9, IconProvider.RAIN_STORM.id); + iconTranslation.put(10, IconProvider.CLOUD.id); + iconTranslation.put(11, IconProvider.INVALID.id); + iconTranslation.put(12, IconProvider.RAIN_LIGHT.id); + iconTranslation.put(13, IconProvider.RAIN_HEAVY.id); + iconTranslation.put(14, IconProvider.FREEZING_RAIN.id); + iconTranslation.put(15, IconProvider.SCATTERD_SHOWERS.id); + iconTranslation.put(16, IconProvider.SNOW.id); + iconTranslation.put(17, IconProvider.SNOW.id); + iconTranslation.put(18, IconProvider.BLIZZARD.id); + iconTranslation.put(19, IconProvider.RAIN_STORM.id); + iconTranslation.put(20, IconProvider.INVALID.id); + iconTranslation.put(21, IconProvider.INVALID.id); + iconTranslation.put(22, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(23, IconProvider.FOG.id); + iconTranslation.put(24, IconProvider.FOG.id); + iconTranslation.put(25, IconProvider.INVALID.id); + iconTranslation.put(26, IconProvider.INVALID.id); + iconTranslation.put(27, IconProvider.HAIL.id); + iconTranslation.put(28, IconProvider.RAIN_LIGHTEST.id); + iconTranslation.put(29, IconProvider.INVALID.id); + iconTranslation.put(30, IconProvider.SUN.id); //MOON + iconTranslation.put(31, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(32, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(33, IconProvider.CLOUD.id); + iconTranslation.put(34, IconProvider.CLOUDY_CLOUDY.id); + iconTranslation.put(35, IconProvider.PARTLY_CLOUDY.id); + iconTranslation.put(36, IconProvider.SCATTERD_SHOWERS.id); + iconTranslation.put(37, IconProvider.RAIN_LIGHT.id); + iconTranslation.put(38, IconProvider.SNOW.id); + iconTranslation.put(39, IconProvider.LIGHTNING_STORM.id); + iconTranslation.put(40, IconProvider.SNOW.id); + iconTranslation.put(41, IconProvider.INVALID.id); + iconTranslation.put(42, IconProvider.INVALID.id); + iconTranslation.put(43, IconProvider.INVALID.id); //WIND + iconTranslation.put(44, IconProvider.INVALID.id); //SMOKE + + BufferedReader reader = null; + try + { + reader = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/translation.csv"))); + String line; + while((line = reader.readLine()) != null) + { + String[] info = line.split(","); + if(info.length == 2) + { + stringTranslation.put(info[0].toLowerCase(), new WeatherLines(info[1], "")); + } + else if(info.length == 3) + { + stringTranslation.put(info[0].toLowerCase(), new WeatherLines(info[1], info[2])); + } + } + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + Util.cleanClose(reader); + } + + } + + public byte icon(int icon) { + if(!iconTranslation.containsKey(icon)) + return IconProvider.INVALID.id; + return iconTranslation.get(icon); + } + + public WeatherLines weatherName(String desc) { + WeatherLines lines = stringTranslation.get(desc.toLowerCase()); + if(lines == null) + { + System.out.println("FAILED:"+desc.toLowerCase()); + return new WeatherLines("TRANSLAT.", "FAILURE"); + } + else return lines; + } +} + +class WeatherLines { + public final String line1; + public final String line2; + + public WeatherLines(String line1, String line2) { + this.line1 = line1; + this.line2 = line2; + } +} diff --git a/src/com/flaremicro/visualforecast/datamart/ForecastProcessor.java b/src/com/flaremicro/visualforecast/datamart/ForecastProcessor.java new file mode 100644 index 0000000..8f06807 --- /dev/null +++ b/src/com/flaremicro/visualforecast/datamart/ForecastProcessor.java @@ -0,0 +1,213 @@ +package com.flaremicro.visualforecast.datamart; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.flaremicro.util.Util; +import com.flaremicro.visualforecast.forecast.DayForecast; +import com.flaremicro.visualforecast.forecast.ForecastDetails; +import com.flaremicro.visualforecast.forecast.TownForecast; +import com.flaremicro.visualforecast.forecast.ValueCheck; + +public class ForecastProcessor implements Runnable { + private final TownInfo[] towns; + private ForecastDetails mostRecentForecast = null; + private boolean running = false; + private Thread self; + private DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + + private DatamartTranslation dmt = new DatamartTranslation(); + + public ForecastProcessor(TownInfo[] towns) { + this.towns = towns; + + try + { + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } + catch (ParserConfigurationException e) + { + e.printStackTrace(); + } + } + + public void stringToOffset(String dayString) { + + } + + public int getDayIndex(String dayString) { + dayString = dayString.trim(); + if (dayString.equalsIgnoreCase("today") || dayString.equalsIgnoreCase("tonight")) + return 0; + dayString = dayString.toLowerCase().replace("night", "").trim(); + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + for (int i = 1; i < 8; i++) + { + calendar.add(Calendar.HOUR, 24); + if (calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.US).equalsIgnoreCase(dayString)) + return i; + } + return -1; + } + + public void processForecasts() { + ForecastDetails forecastDetails = new ForecastDetails(); + ArrayList townForecasts = new ArrayList(); + for (TownInfo townInfo : towns) + { + DayForecast[] dayForecasts = new DayForecast[8]; + InputStream is = null; + try + { + + URL url = new URL("https://dd.weather.gc.ca/citypage_weather/xml/BC/" + townInfo.code + "_e.xml"); + DocumentBuilder db = dbf.newDocumentBuilder(); + is = url.openStream(); + Document doc = db.parse(is); + NodeList nodeList = doc.getElementsByTagName("forecast"); + for (int i = 0; i < nodeList.getLength(); i++) + { + if (nodeList.item(1).getNodeType() == Node.ELEMENT_NODE) + { + Element node = (Element) nodeList.item(i); + int dayIndex = getDayIndex(XMLUtils.getStringFromTagAttribute(node, "period", "textForecastName")); + Element abbForecast = XMLUtils.getFistElement(node, "abbreviatedForecast"); + int iconIndex = XMLUtils.getIntFromTag(abbForecast, "iconCode", 0); + String textForecast = XMLUtils.getStringFromTag(abbForecast, "textSummary"); + + WeatherLines lines = dmt.weatherName(textForecast); + byte icon = dmt.icon(iconIndex); + + byte lo = ValueCheck.NO_DATA_BYTE; + byte hi = ValueCheck.NO_DATA_BYTE; + + Element element = XMLUtils.getFistElement(node, "temperatures"); + NodeList temps = element.getElementsByTagName("temperature"); + for (int j = 0; j < temps.getLength(); j++) + { + Node n = temps.item(j); + try + { + byte val = Byte.parseByte(n.getTextContent().trim()); + if(XMLUtils.getStringFromAttribute(n, "class").trim().equalsIgnoreCase("high")) + { + hi = val; + } + else if(XMLUtils.getStringFromAttribute(n, "class").trim().equalsIgnoreCase("low")) + { + lo = val; + } + } + catch (NumberFormatException ex) + { + + } + } + + if (dayIndex >= 0 && dayIndex <= 7) + { + if (dayForecasts[dayIndex] != null) + { + if (lo != ValueCheck.NO_DATA_BYTE && dayForecasts[dayIndex].loTemp == ValueCheck.NO_DATA_BYTE) + { + dayForecasts[dayIndex].loTemp = lo; + } + if (hi != ValueCheck.NO_DATA_BYTE && dayForecasts[dayIndex].hiTemp == ValueCheck.NO_DATA_BYTE) + { + dayForecasts[dayIndex].hiTemp = hi; + } + + } + else + { + dayForecasts[dayIndex] = new DayForecast(hi, lo, icon, lines.line1, lines.line2, ValueCheck.NO_DATA_FLOAT); + } + } + } + } + } + catch (IOException e) + { + e.printStackTrace(); + } + catch (ParserConfigurationException e) + { + e.printStackTrace(); + } + catch (SAXException e) + { + e.printStackTrace(); + } + finally + { + Util.cleanClose(is); + } + for (int i = 0; i < dayForecasts.length; i++) + { + if (dayForecasts[i] == null) + { + dayForecasts[i] = new DayForecast(); + } + } + townForecasts.add(new TownForecast(townInfo.townName + ", " + townInfo.province, dayForecasts)); + } + forecastDetails.setTownForecast(townForecasts.toArray(new TownForecast[0])); + setMostRecentForecast(forecastDetails); + } + + public void end() { + running = false; + self.interrupt(); + } + + public ForecastDetails getMostRecentForecast() { + synchronized (this) + { + return mostRecentForecast; + } + } + + private void setMostRecentForecast(ForecastDetails forecast) { + synchronized (this) + { + mostRecentForecast = forecast; + } + } + + @Override + public void run() { + self = Thread.currentThread(); + while (running) + { + try + { + Thread.sleep(TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)); + processForecasts(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } +} diff --git a/src/com/flaremicro/visualforecast/datamart/TownInfo.java b/src/com/flaremicro/visualforecast/datamart/TownInfo.java new file mode 100644 index 0000000..f619d2b --- /dev/null +++ b/src/com/flaremicro/visualforecast/datamart/TownInfo.java @@ -0,0 +1,17 @@ +package com.flaremicro.visualforecast.datamart; + +public class TownInfo { + public final String code; + public final String townName; + public final String province; + public final float northLat; + public final float westLong; + + public TownInfo(String code, String townName, String province, float northLat, float westLong) { + this.code = code; + this.townName = townName; + this.province = province; + this.northLat = northLat; + this.westLong = westLong; + } +} diff --git a/src/com/flaremicro/visualforecast/datamart/XMLUtils.java b/src/com/flaremicro/visualforecast/datamart/XMLUtils.java new file mode 100644 index 0000000..317f269 --- /dev/null +++ b/src/com/flaremicro/visualforecast/datamart/XMLUtils.java @@ -0,0 +1,56 @@ +package com.flaremicro.visualforecast.datamart; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class XMLUtils { + public static String getStringFromTag(Element parent, String tag) { + NodeList l = parent.getElementsByTagName(tag); + if (l.getLength() > 0) + return l.item(0).getTextContent(); + return null; + } + + public static String getStringFromTagAttribute(Element parent, String tag, String attribute) { + NodeList l = parent.getElementsByTagName(tag); + if (l.getLength() > 0) + { + Node attrib = l.item(0).getAttributes().getNamedItem(attribute); + if(attrib != null) + return attrib.getTextContent(); + } + return null; + } + + + public static String getStringFromAttribute(Node n, String attribute) { + Node attrib = n.getAttributes().getNamedItem(attribute); + if(attrib != null) + return attrib.getTextContent(); + return null; + } + + public static Element getFistElement(Element parent, String tag) { + NodeList l = parent.getElementsByTagName(tag); + for (int i = 0; i < l.getLength(); i++) + { + if (l.item(i).getNodeType() == Node.ELEMENT_NODE) + return (Element) l.item(i); + } + return null; + } + + public static Integer getIntFromTag(Element abbForecast, String tag, Integer fallback) { + String content = getStringFromTag(abbForecast, tag); + try + { + return Integer.parseInt(content); + } + catch (NumberFormatException ex) + { + + } + return fallback; + } +} diff --git a/src/provider.properties b/src/provider.properties new file mode 100644 index 0000000..30866c5 --- /dev/null +++ b/src/provider.properties @@ -0,0 +1 @@ +main-class=com.flaremicro.visualforecast.datamart.CanadaDatamartProvider \ No newline at end of file diff --git a/src/translation.csv b/src/translation.csv new file mode 100644 index 0000000..926378c --- /dev/null +++ b/src/translation.csv @@ -0,0 +1,99 @@ +Mainly sunny,Mainly,Sunny +Mainly cloudy,Mainly, Cloudy +Sunny,Sunny, +A few clouds,Few,Clouds +A mix of sun and cloud,Partly,Cloudy +Cloudy periods,Cloudy,Periods +Sunny with cloudy periods,Cloudy,Periods +Cloudy with sunny periods,Sunny,Periods +Increasing cloudiness,Increasing,Clouds +Clearing,Clearing, +Clear,Sun, +Chance of showers,Chance,Showers +A few showers,Few,Showers +Chance of drizzle or rain,Chance,Drizzle +A few flurries or rain showers,Flurries/,Showers +Chance of flurries or rain showers,Flurries/,Showers +Chance of rain showers or flurries,Flurries/,Showers +A few flurries,Flurries, +Chance of flurries,Chance,Flurries +A few wet flurries,Wet,Flurries +Chance of showers at times heavy or thundershowers,Scattered,T'Storms +Chance of showers at times heavy or thunderstorms,Scattered,T'Storms +Chance of thundershowers,Thunder,Storms +Chance of thunderstorms,Thunder,Storms +Chance of showers or thundershowers,Thunder,Storms +Chance of showers or thunderstorms,Thunder,Storms +A few flurries or thundershowers,Thunder,Storms +Cloudy,Cloudy, +Overcast,Overcast, +Showers,Showers, +Chance of showers,Chance,Showers +Periods of rain,Scattered,Rain +Mostly Cloudy,Mostly,Cloudy +Rain at times heavy,Heavy,Rain +A few showers or drizzle,Drizzle +Rain,Rain +Chance of freezing rain,Chance,Ice Rain +Freezing rain,Freezing,Rain +Chance of freezing rain or rain,Chance,Ice Rain, +Chance of rain or freezing rain,Chance,Ice Rain, +A few rain showers or flurries,Scattered,Flurries +Periods of rain or snow,Rain or,Snow +A few rain showers or wet flurries,Rain or,Flurries +Snow mixed with rain,Rain,Snow +Chance of snow mixed with freezing rain,Rain,Snow +Wet snow or rain,Wet,Snow +Flurries,Flurries, +Light snow,Light,Snow +Wet flurries,Wet,Flurries +Wet snow,Wet,Snow +Snow at times heavy,Heavy,Snow +Periods of snow,Scattered,Snow +Snow,Snow, +Chance of snow,Chance,Snow +Snow squalls,Snow,Squalls +Local snow squalls,Snow,Squalls +Blizzard,Blizzard, +Near blizzard,Near,Blizzard +Snow and blizzard,Blizzard, +Snow at times heavy and blowing snow,Blowing,Snow +A few showers or thundershowers,Thunder,Showers +A few showers or thunderstorms,Thunder,Storms +Showers or thundershowers,Thunder,Showers +Showers or thunderstorms,Thunder,Storms +Rain or thunderstorms,Thunder,Storms +Rain or thundershowers,Thunder,Showers +Chance of thunderstorms and possible hail,Hail,T'Storms +Thunderstorms and possible hail,Hail,T'Storms +A mix of sun and cloud,Partly,Cloudy +Haze,Haze, +Fog,Fog, +Fog Patches,Patchy,Fog +Fog dissipating,Fog +Fog developing,Fog +Ice fog,Ice,Fog +Ice fog developing,Ice,Fog +Ice fog dissipating,Ice,Fog +A few flurries mixed with ice pellets,Sleet,Flurries +Ice pellet,Sleet +Ice pellet mixed with freezing rain,Sleet,Rain +Ice pellet mixed with snow,Sleet,Snow +Ice pellet or snow,Sleet,Snow +Possibility of drizzle mixed with freezing drizzle,Ice,Drizzle +Drizzle,Drizzle +Freezing drizzle,Freezing,Drizzle +Possibility of drizzle,Drizzle +Drizzle or freezing drizzle,Freezing,Drizzle +Not available,MISSING,DATA +Chance of drizzle or rain,Chance,Rain +A few flurries or showers,Flurries/,Showers +Chance of snow or rain,Chance,Snow +A few flurries,Scattered,Flurries +Chance of light snow,Light,Snow +A few wet flurries,Wet,Flurries +Chance of showers at times heavy or thunderstorms,Thunder,Storms +Chance of thunderstorms,Thunder,Storms +Snow and blowing snow,Blowing,Snow +Windy,Windy +Smoke,Smoke