diff --git a/libgweather/Makefile.am b/libgweather/Makefile.am
index c806414..47fb303 100644
--- a/libgweather/Makefile.am
+++ b/libgweather/Makefile.am
@@ -8,7 +8,7 @@ pkgconfig_DATA = gweather.pc
 
 libgweather_la_SOURCES = \
 	weather.c weather.h weather-priv.h \
-	weather-metar.c weather-iwin.c weather-met.c \
+	weather-metar.c weather-forecast-noaa.c weather-met.c \
 	weather-bom.c weather-wx.c \
 	weather-sun.c \
 	gweather-prefs.c gweather-prefs.h \
diff --git a/libgweather/weather-bom.c b/libgweather/weather-bom.c
index 661eb86..eb3dca4 100644
--- a/libgweather/weather-bom.c
+++ b/libgweather/weather-bom.c
@@ -63,6 +63,11 @@ bom_start_open (WeatherInfo *info)
     SoupMessage *msg;
     WeatherLocation *loc;
 
+    if (info->forecast) {
+	g_free (info->forecast);
+	info->forecast = NULL;
+    }
+
     loc = info->location;
 
     url = g_strdup_printf ("http://www.bom.gov.au/cgi-bin/wrap_fwo.pl?%s.txt",
diff --git a/libgweather/weather-forecast-noaa.c b/libgweather/weather-forecast-noaa.c
new file mode 100644
index 0000000..91ff7ec
--- /dev/null
+++ b/libgweather/weather-forecast-noaa.c
@@ -0,0 +1,136 @@
+/* weather-forecast-noaa.c - Weather forecast data from NOAA
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <string.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
+#include <libgweather/weather.h>
+#include "weather-priv.h"
+
+static void
+append_period (GString *forecast, xmlNode *node)
+{
+    node = node->children;
+
+    do {
+	while (node && node->type != XML_ELEMENT_NODE)
+	    node = node->next;
+	if (!node)
+	    return;
+
+	if (!strcmp ((char *)node->name, "valid"))
+	    g_string_append_printf (forecast, "%s: ", node->children->content);
+	else if (!strcmp ((char *)node->name, "text")) {
+	    g_string_append (forecast, (char *)node->children->content);
+	    g_string_append (forecast, "\n\n");
+	}
+	node = node->next;
+    } while (node);
+}
+
+static void
+forecast_noaa_finish (SoupSession *session, SoupMessage *msg, gpointer data)
+{
+    WeatherInfo *info = (WeatherInfo *)data;
+    xmlDoc *doc;
+    xmlNode *node;
+    char *name;
+    GString *forecast;
+
+    g_return_if_fail (info != NULL);
+
+    if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+        /* forecast data is not really interesting anyway ;) */
+	g_warning ("Failed to get NOAA forecast data: %d %s\n",
+		   msg->status_code, msg->reason_phrase);
+        request_done (info, FALSE);
+	return;
+    }
+
+    doc = xmlParseMemory (msg->response_body->data,
+			  msg->response_body->length);
+    if (!doc) {
+	g_warning ("Failed to parse NOAA forecast data\n");
+        request_done (info, FALSE);
+	return;
+    }
+    node = xmlDocGetRootElement (doc);
+    if (strcmp ((char *)node->name, "Forecast") != 0) {
+	g_warning ("NOAA forecast data had unexpected format\n");
+	xmlFreeDoc (doc);
+        request_done (info, FALSE);
+	return;
+    }
+
+    forecast = g_string_new (NULL);
+    name = (char *) xmlGetProp (node, (xmlChar *) "location");
+    /* There's no reason to localize this string, given that the
+     * forecast data itself will always be in English.
+     */
+    g_string_append_printf (forecast, "Forecast for %s (%s)\n\n",
+			    info->location->name, name);
+    xmlFree (name);
+
+    node = node->children;
+    do {
+	while (node && (node->type != XML_ELEMENT_NODE || strcmp ((char *)node->name, "period") != 0))
+	    node = node->next;
+	if (node) {
+	    append_period (forecast, node);
+	    node = node->next;
+	}
+    } while (node);
+
+    xmlFreeDoc (doc);
+
+    info->forecast = g_string_free (forecast, FALSE);
+    request_done (info, TRUE);
+}
+
+/* Get forecast into newly alloc'ed string */
+void
+forecast_noaa_start_open (WeatherInfo *info)
+{
+    char *url;
+    WeatherLocation *loc;
+    SoupMessage *msg;
+
+    g_return_if_fail (info != NULL);
+    loc = info->location;
+    g_return_if_fail (loc != NULL);
+
+    if (info->forecast) {
+	g_free (info->forecast);
+	info->forecast = NULL;
+    }
+
+    url = g_strdup_printf ("http://forecast.weather.gov/MapClick.php?textField1=%f&textField2=%f&TextType=3",
+			   RADIANS_TO_DEGREES (loc->latitude),
+			   RADIANS_TO_DEGREES (loc->longitude));
+
+    msg = soup_message_new ("GET", url);
+    g_free (url);
+    soup_session_queue_message (info->session, msg, forecast_noaa_finish, info);
+}
diff --git a/libgweather/weather-iwin.c b/libgweather/weather-iwin.c
deleted file mode 100644
index a5b91f6..0000000
--- a/libgweather/weather-iwin.c
+++ /dev/null
@@ -1,132 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* $Id: weather-iwin.c 10523 2007-11-16 08:03:22Z callum $ */
-
-/*
- *  Papadimitriou Spiros <spapadim+@cs.cmu.edu>
- *
- *  This code released under the GNU GPL.
- *  Read the file COPYING for more information.
- *
- *  Weather server functions (IWIN)
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#  include <config.h>
-#endif
-
-#include <ctype.h>
-#include <stdlib.h>
-#include <string.h>
-#include <glib/gi18n-lib.h>
-
-#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
-#include <libgweather/weather.h>
-#include "weather-priv.h"
-
-/**
- *  Humans don't deal well with .MONDAY...SUNNY AND BLAH BLAH.TUESDAY...THEN THIS AND THAT.WEDNESDAY...RAINY BLAH BLAH.
- *  This function makes it easier to read.
- */
-static gchar *
-formatWeatherMsg (gchar *forecast)
-{
-    gchar *ptr = forecast;
-    gchar *startLine = NULL;
-
-    while (0 != *ptr) {
-        if (ptr[0] == '\n' && ptr[1] == '.') {
-  	    /* This removes the preamble by shifting the relevant data
-	     * down to the start of the buffer. */
-            if (NULL == startLine) {
-                memmove (forecast, ptr, strlen (ptr) + 1);
-                ptr = forecast;
-                ptr[0] = ' ';
-            }
-            ptr[1] = '\n';
-            ptr += 2;
-            startLine = ptr;
-        } else if (ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '.' && NULL != startLine) {
-            memmove (startLine + 2, startLine, (ptr - startLine) * sizeof (gchar));
-            startLine[0] = ' ';
-            startLine[1] = '\n';
-            ptr[2] = '\n';
-
-            ptr += 3;
-
-        } else if (ptr[0] == '$' && ptr[1] == '$') {
-            ptr[0] = ptr[1] = ' ';
-
-        } else {
-            ptr++;
-        }
-    }
-
-    return forecast;
-}
-
-static void
-iwin_finish (SoupSession *session, SoupMessage *msg, gpointer data)
-{
-    WeatherInfo *info = (WeatherInfo *)data;
-
-    g_return_if_fail (info != NULL);
-
-    if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
-        /* forecast data is not really interesting anyway ;) */
-	g_warning ("Failed to get IWIN forecast data: %d %s\n",
-		   msg->status_code, msg->reason_phrase);
-        request_done (info, FALSE);
-	return;
-    }
-
-    info->forecast = formatWeatherMsg (g_strdup (msg->response_body->data));
-    request_done (info, TRUE);
-}
-
-/* Get forecast into newly alloc'ed string */
-void
-iwin_start_open (WeatherInfo *info)
-{
-    gchar *url, *state, *zone;
-    WeatherLocation *loc;
-    SoupMessage *msg;
-
-    g_return_if_fail (info != NULL);
-    loc = info->location;
-    g_return_if_fail (loc != NULL);
-
-    if (loc->zone[0] == '-')
-        return;
-
-    if (info->forecast) {
-	g_free (info->forecast);
-	info->forecast = NULL;
-    }
-
-    if (loc->zone[0] == ':') {
-	/* Met Office Region Names */
-    	metoffice_start_open (info);
-    	return;
-    } else if (loc->zone[0] == '@') {
-	/* Australian BOM forecasts */
-    	bom_start_open (info);
-    	return;
-    }
-
-    /* The zone for Pittsburgh (for example) is given as PAZ021 in the locations
-    ** file (the PA stands for the state pennsylvania). The url used wants the state
-    ** as pa, and the zone as lower case paz021.
-    */
-    zone = g_ascii_strdown (loc->zone, -1);
-    state = g_strndup (zone, 2);
-
-    url = g_strdup_printf ("http://weather.noaa.gov/pub/data/forecasts/zone/%s/%s.txt",
-        		   state, zone);
-    g_free (zone);
-    g_free (state);
-
-    msg = soup_message_new ("GET", url);
-    g_free (url);
-    soup_session_queue_message (info->session, msg, iwin_finish, info);
-}
diff --git a/libgweather/weather-met.c b/libgweather/weather-met.c
index 3a99e79..e082115 100644
--- a/libgweather/weather-met.c
+++ b/libgweather/weather-met.c
@@ -164,6 +164,11 @@ metoffice_start_open (WeatherInfo *info)
     SoupMessage *msg;
     WeatherLocation *loc;
 
+    if (info->forecast) {
+	g_free (info->forecast);
+	info->forecast = NULL;
+    }
+
     loc = info->location;
     url = g_strdup_printf ("http://www.metoffice.gov.uk/weather/europe/uk/%s.html", loc->zone + 1);
 
diff --git a/libgweather/weather-priv.h b/libgweather/weather-priv.h
index 2201cd9..359f6ff 100644
--- a/libgweather/weather-priv.h
+++ b/libgweather/weather-priv.h
@@ -185,7 +185,7 @@ const gchar *	weather_conditions_string	(WeatherConditions cond);
 #define RADIANS_TO_HOURS(rad)		((rad) * 12. / M_PI)
 
 void		metar_start_open	(WeatherInfo *info);
-void		iwin_start_open		(WeatherInfo *info);
+void		forecast_noaa_start_open(WeatherInfo *info);
 void		metoffice_start_open	(WeatherInfo *info);
 void		bom_start_open		(WeatherInfo *info);
 void		wx_start_open		(WeatherInfo *info);
diff --git a/libgweather/weather.c b/libgweather/weather.c
index bc6682e..0dab1b6 100644
--- a/libgweather/weather.c
+++ b/libgweather/weather.c
@@ -80,6 +80,13 @@ dmsh2rad (const gchar *latlon)
     return value;
 }
 
+/* Macro to check if a NOAA forecast is available for a location. We
+ * should be checking that location->country_code is "US", but
+ * gweather-gconf tends to lose that information. So we check the ICAO
+ * prefix instead
+ */
+#define LOCATION_HAS_NOAA_FORECAST(loc) ((loc)->latlon_valid && ((loc)->code[0] == 'K' || (loc)->code[0] == 'P'))
+
 WeatherLocation *
 weather_location_new (const gchar *name, const gchar *code,
 		      const gchar *zone, const gchar *radar,
@@ -107,12 +114,6 @@ weather_location_new (const gchar *name, const gchar *code,
         location->radar = g_strdup ("---");
     }
 
-    if (location->zone[0] == '-') {
-        location->zone_valid = FALSE;
-    } else {
-        location->zone_valid = TRUE;
-    }
-
     location->coordinates = NULL;
     if (coordinates)
     {
@@ -137,10 +138,14 @@ weather_location_new (const gchar *name, const gchar *code,
         location->longitude = DBL_MAX;
     }
 
-    location->latlon_valid = (location->latitude < DBL_MAX && location->longitude < DBL_MAX);
-
     location->country_code = g_strdup (country_code);
     location->tz_hint = g_strdup (tz_hint);
+    
+    if (location->zone[0] == '-' && !LOCATION_HAS_NOAA_FORECAST (location)) {
+        location->zone_valid = FALSE;
+    } else {
+        location->zone_valid = TRUE;
+    }
 
     return location;
 }
@@ -485,8 +490,18 @@ _weather_info_fill (WeatherInfo *info,
 
     metar_start_open (info);
     info->requests_pending++;
-    iwin_start_open (info);
-    info->requests_pending++;
+
+    /* forecast */
+    if (location->zone[0] == ':') {
+       metoffice_start_open (info);
+       info->requests_pending++;
+    } else if (location->zone[0] == '@') {
+       bom_start_open (info);
+       info->requests_pending++;
+    } else if (LOCATION_HAS_NOAA_FORECAST (location)) {
+	forecast_noaa_start_open (info);
+	info->requests_pending++;
+    }
 
     if (prefs->radar) {
         wx_start_open (info);
