65.9K
CodeProject 正在变化。 阅读更多。
Home

天气雷达

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (7投票s)

2014年12月5日

CPOL

2分钟阅读

viewsIcon

38428

downloadIcon

778

一个精简版系列文章。

代码下载

这个项目需要您单独下载 HtmlAgilityPack 并将 HtmlAgilityPack.dll 放置在适当的 Debug 和/或 Release 文件夹中。

引言

据我所知,NOAA 天气网络服务没有提供获取特定位置天气雷达图像的功能,其他天气网络服务也没有轻易提供此功能。因此,我在这里展示一个“最简实现”,使用 HtmlAgilityPack 进行一些简单的 XPath 查询。

radar.weather.gov 网站

如果您检查此网站上显示雷达图像的页面,您会注意到该图像实际上是 8 个可选择部分的组合。

第一张图像是 JPG,其余都是 GIF。

地形

多普勒雷达

河流

道路

Cities

预警(目前没有)

图例

因此,给定您最近的雷达站(在 http://radar.weather.gov 上选择一个),我们需要获取这些图像并将它们组合在一起以显示最终的合成图像。有一个小小的变化 - 两张图像来自不同的来源,并且由页面上的 Javascript 计算,因此我们为这些图像提供了特殊的处理程序。有五个步骤:

步骤 1:获取页面 HTML

给定文本框中输入的 URL,我们首先获取页面的 HTML。

protected string GetPageHtml(string url)
{
  using (WebClient client = new WebClient())
  {
    return client.DownloadString(url);
  }
}

步骤 2:确定图像 URL

这是流程中最复杂的部分,因为我们需要以编程方式生成两个图像 URL。您还将看到我们如何使用 HtmlAgilityPack 提取您之前看到的 div 部分的特定节点值。

protected List<string> GetPageImageUrls(string html, string url)
{
  List<string> ret = new List<string>();

  HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
  doc.LoadHtml(html);
  WebClient webClient = new WebClient();
  int n = 0;

  while (true)
  {
    var nodes = doc.DocumentNode.SelectNodes(String.Format("//div[@id='image{0}']/img", n++));

    if ((nodes != null) && (nodes.Count == 1))
    {
      string imageUrl = nodes.Select(node => node.Attributes["src"].Value).Single();

      // This is a computed image.
      if (imageUrl == "#")
      {
        string name = nodes.Select(node => node.Attributes["name"].Value).Single();
        string rid = url.Between("rid=", "&").ToUpper();
        string product = url.Between("product=", "&").ToUpper();

        switch (name)
        {
          case "conditionalimage":
          // Example: http://radar.weather.gov/RadarImg/N0R/ENX_N0R_0.gif
          imageUrl = String.Format("RadarImg/{0}/{1}_{0}_0.gif", product, rid);
          break;

        case "conditionallegend":
          // Example: http://radar.weather.gov/Legend/N0R/ENX_N0R_Legend_0.gif
          imageUrl = String.Format("Legend/{0}/{1}_{0}_Legend_0.gif", product, rid);
          break;
        }
      }

      ret.Add(imageUrl);
    }
    else
    {
      break;
    }
  }

  webClient.Dispose();

  return ret;
}

步骤 3:下载图像

在这里,我们只需下载图像,保存 Image 及其关联的 MemoryStream 实例。虽然我可以克隆图像并在此时释放内存流和原始图像,但我选择将两者作为一个包保留以供以后清理。

protected List<ImageData> DownloadImages(List<string> imageUrls)
{
  List<ImageData> ret = new List<ImageData>();
  WebClient webClient = new WebClient();

  foreach (string url in imageUrls)
  {
    byte[] data = webClient.DownloadData("http://radar.weather.gov/" + url);
    // Memory stream CANNOT be disposed of!
    MemoryStream stream = new MemoryStream(data);
    Image image = Image.FromStream(stream);
    ret.Add(new ImageData() { Image = image, BackingStream = stream });
  }

  webClient.Dispose();

  return ret;
}

步骤 4:为了好玩,我们保存图像

您会注意到我对第一张图像是 JPG,其余都是 GIF 的假设。是的,我们可以通过检查原始图像格式来弄清楚这一点,但这比我想投入到代码中的工作量要多。

protected void WriteImages(List<ImageData> images)
{
  int n=0;
  images.ForEach(img => img.Image.Save("img" + n++ + (n==1 ? ".jpg" : ".gif")));
}

步骤 5:组合图像

在这里,我们将图像组合在一起,返回包含合成图像数据的新的图像。

protected Image CombineImages(Graphics gr, List<ImageData> images, Size size)
{
  Image baseImage = (Image)images[0].Image.Clone();
  gr = Graphics.FromImage(baseImage);

  for (int i=1; i<images.Count; i++)
  {
    gr.DrawImage(images[i].Image, new Point(0, 0));
  }

  gr.Dispose();

  return baseImage;
}

步骤 6:清理

在这里,我们处置图像及其关联的内存流。

protected void Cleanup(List<ImageData> images)
{
  images.ForEach(img =>
  {
    img.BackingStream.Dispose();
    img.Image.Dispose();
  });
}

整合起来

当您单击“Go”按钮时,将启动一个异步过程,我们提供一个回调来在状态栏上显示进度。

protected async void btnGo_Click(object sender, EventArgs e)
{
  btnGo.Enabled = false;
  string url = tbUrl.Text;
  Graphics gr = pbRadar.CreateGraphics();
  Image bitmap = await Task.Run(() => GetRadarImage(gr, 
    url, 
    (progress) => this.BeginInvoke(() => tsLabel.Text = progress)));
  pbRadar.Image = bitmap;
  tsLabel.Text = "Done";
  btnGo.Enabled = true;
}

GetRadarImage 的实现将我们描述的流程连接在一起。

protected Image GetRadarImage(Graphics gr, string url, Action<string> progressCallback)
{
  progressCallback("Acquiring page...");
  string html = GetPageHtml(url);

  progressCallback("Scraping page...");
  List<string> imageUrls = GetPageImageUrls(html, url);

  progressCallback("Downloading images...");
  List<ImageData> images = DownloadImages(imageUrls);

  progressCallback("Writing images...");
  WriteImages(images);

  progressCallback("Combining images...");
  Image bitmap = CombineImages(gr, images, pbRadar.Size);


  progressCallback("Cleanup...");
  Cleanup(images);

  return bitmap;
}

扩展方法

为了让我的生活更轻松,我借用了一些我在其他项目中使用过的扩展方法。

public static class Extensions
{
  public static string Between(this String src, string s1, string s2)
  {
    return src.RightOf(s1).LeftOf(s2);
  }

  public static string RightOf(this String src, string s)
  {
    string ret = String.Empty;
    int idx = src.IndexOf(s);

    if (idx != -1)
    {
      ret = src.Substring(idx + s.Length);
    }

    return ret;
  }

  public static string LeftOf(this String src, string s)
  {
    string ret = src;
    int idx = src.IndexOf(s);
  
    if (idx != -1)
    {
      ret = src.Substring(0, idx);
    }

    return ret;
  }

  public static void BeginInvoke(this Control control, Action action)
  {
    if (control.InvokeRequired)
    {
      control.BeginInvoke((Delegate)action);
    }
    else
    {
      action();
    }
  }
}

结论

就这样,一种获取应用程序中雷达图像的最简方法!

© . All rights reserved.