一触即发

用您的 Windows Phone 观赏夜空

Charles Petzold

下载代码示例

Charles Petzold在他的经典历史,"哥白尼革命"(哈佛大学出版社,1957年),Thomas S.的开幕页中库恩唤起人们在夜晚的天空会非常熟悉世界许多世纪以前。尚未受人工照明的持久性光照,这些观察员亲身经历怎样的星星圆顶似乎旋转,夜间在北星星周围长弧中,以及如何通过 12 生肖的步骤每年周期的日出和日落进展的恒星的配置。

他们还观察到如何几个明星,没有遵循这个相同的模式。这些古怪似乎全景的恒星,有时甚至扭转与天体穹顶的议案中漫步。这些一般规则的例外情况仍然是已知的流浪者古希腊语中:Πλανήτης 或行星。

跟踪并预测这些行星运动的尝试是科学的历史,从宇宙的中心最终推翻原始宇宙和地球的位移的最著名动荡的开始。在人类获得更好地了解在宇宙中的地位的同时,它也开始失去这种特别的关系,夜晚的天空。

使用 AstroPhone

这些天我们大多数人有没有时间、 耐心或想在盯着夜空足够长的时间来感受其周期性的模式或检测流浪行星的宁静。相反,我们依赖明星图表或基于计算机的天文工具来帮帮忙。

此列描述体裁,为 Windows Phone 与 AstroPhone 的名字很傻的小程序我自己微薄的贡献。可下载的应用程序的源代码包含一个名为 AstroPhone,并命名为 Petzold.Astronomy,其中包含很多数据和打号库 XNA 程序。

此程序已没有用户界面除了努力所需手机背面指向夜晚的天空中的位置。这款手机的屏幕然后显示星、 星座和位于存在的行星。图 1 显示了典型的屏幕,在 3:45 下午西飞举行关于 2012 年 7 月 15 日,该地区纽约城,在手机的本地时间。

A Typical AstroPhone Screen
图 1 典型 AstroPhone 屏幕

绿线是用指南针点标记的地平线。(通知西 W)。红线是黄道,这是通过它所有行星约轨道,每 15 ° 的刻度的平面。您可以看到木星和金星适当设置在接下来的 90 分钟左右。也可见 — — 屏幕上若不在中期-­下午的天空 — — 是金牛座。

要在屏幕上正确放置恒星和行星,该程序需要结合几个来源的信息:

  • 数据和算法派生的恒星和行星 (以下各节中所述) 的位置。
  • 在这些算法中使用的当前日期和时间。
  • 这款手机的 GPS,这样该程序知道电话在地球表面上的位置。
  • 手机的运动传感器所以程序知道电话在 3D 空间中的方向如何,因此天葬台的哪一部分球形它指着。

由于严重依赖手机的传感器,该程序将无法运行 Windows Phone 仿真器上。

在此列中的几个前几期我上面讨论的一些使用运动传感器,并将这些信息转换为不同的坐标系统的概念。这些早期的文章都可用在 MSDN 杂志 》 网站上 (bit.ly/NoMc8R)。很多我的位置天文学的方法基于这本书"天文算法"(1998年-贝尔 Willmann),让 · meeus 虽然我有时使用某种程度上简化的计算,我已经消除所有错误的任何保证。

恒星及星座

如果你看起来在维基百科上的某个明星,你会发现它的位置就有两个数字 — — 权利阿森松岛和偏差。例如,参宿四星有 5 小时 55 分钟,10.3053 秒,右阿森松和衰落 7 ° 24 分 25.426 秒。这一立场是相对于赤道的坐标系统中,地球的赤道将宇宙划分为积极角偏角向北和向南的负角偏角。(北极星,也称为北辰有偏差非常接近 90 °)。

传统上,右阿森松岛是以小时,每小时相当于 15 ° 来衡量。零权阿森松对应于在当地时间的午夜北春分日相同的方向。因为地球的赤道平面仍然在整个一年中 (和整个世纪),大体不变,星星是如此遥远,因为它们被视为具有相同的赤道坐标地球的位置无关。这些恒星的赤道坐标指定为一个特定的时期 (例如,2000 年),和小年度调整可以应用于帐户的星星的轻微运动。

许多明星目录存在,最的与许多更多明星比我想要在我的程序中显示它们。我决定要使用的一个目录,起源于 100 多年前的更新的版本:光明之星目录中,第 5 次修订本,我从 FTP 站点维护的斯特拉斯堡天文数据中心取得 (bit.ly/T51GE5)。

甚至提供更多的信息,不是我需要此目录,也有点少。大部分想要我的程序来显示仅与现代天文学中使用的 88 标准星座相关主要星星。这是很简单的因为星星有型号 (开发的第一个小约翰 · 拜耳 400 多年前),指示明星与三个字母的缩写词和唯一的希腊语字母属于哪个星座。

但我还想要提高每个星座与恒星所以,Leo 的本来面目如棒图狮子之间那些熟悉线路连接。星座的星星的连线不标准,而且我不知道明星的目录,其中包括他们。

所以我添加了他们自己。我写了一个小小的 Windows 演示文稿基金会 (WPF) 程序访问光明之星目录中并显示每个 88 星座的星星。该程序响应鼠标单击对两个星星,并记录结果。一般我根据线路的连接,贷记国际天文学联合会和天空上维基百科,星座图 & 望远镜杂志。

WPF 程序合并到一个名为 Constellations.xml,您可以找到 Petzold.Astronomy 库项目的数据目录中文件的数据。每个星座标记包含星标记的集合。每颗星是从光明明星目录编号标识。在每个星座的星标记之后,定义行编号明星对之间的连接器标记的集合。

行星

作为实现甚至是原始观星,行星的位置是比星星的复杂得多。行星围绕太阳的椭圆轨道但相互引力相互作用产生的违规行为。一种算法,在某一特定时间确定地球上的位置是通常称为"理论",和通常基于傅里叶系列。

我用的行星理论称为 VSOP87,变化 Séculaires des Orbites Planétaires 1987 版,由主席团在巴黎 des 经度维护。包含的 VSOP87 部分数据的文件还包括 Petzold.Astronomy 项目的数据目录中。VsopCruncher 类中的时间为每个行星为某一特定点执行计算。

行星的位置通常计算在黄道坐标,但与太阳的中心 — — 也就是说,金星的日心,而不是地心。对于每个星球,这些坐标包含的内容:

  • 在其某一年度期间是从 0 ° 到 360 ° 作为地球黄道经度使得完成绕太阳轨道
  • 是接近于零,因为所有的行星轨道在大致相同的平面中的太阳黄道纬度
  • 半径是地球离太阳的距离。

这些坐标是伟大绘制行星围绕太阳的运动。不过,如果您要计算从地球看作为地球上的位置,很方便,将这些球面坐标转换成三维矩形坐标,其中 X 指示行星的位置,Y 和 Z 值与占领中心处 (0,0,0) 的原点的太阳。减去直角坐标任何从矩形坐标的地球的星球,你一个 3D 向量从地球到那的星球上,然后可以转换为右阿森松岛和地心黄道坐标的偏差值 — — 用于星星的相同坐标系统。

月球的运动比恒星、 行星、 复杂得多,并已作为单独的案例来处理。我选择不包括在此版本的 AstroPhone 程序的月亮。此外,如果我是以包括月球,我会觉得要显示的当前阶段,,将使更多的工作复杂化。

链的计算

要有条理的方式执行必要的计算,我定义类层次的结构:

CelestialBody
        Constellation
        Star
            SolarSystemBody
                Sun
                Planet
                    Earth

星和星座类也发挥作为 Constellations.xaml 文件反序列化对象的双重角色。

如中所示图 2,CelestialBody 类负责计算从 EquatorialCoordinate HorizontalCoordinate 基于电话的当前日期和时间的地理位置。 子代类计算中的 EquatorialCoordinate 属性的 OnTimeChanged 方法将重写。

图 2 父 CelestialBody 类

public abstract class CelestialBody
{
  // Only used internally
  private GeographicCoordinate Location { set; get; }
  // Set here, used by descendant classes
  protected Time Time { private set; get; }
  // Set by descendant classes, used here
  protected EquatorialCoordinate EquatorialCoordinate { set; private get; }
  // Set here, used external to library
  public HorizontalCoordinate HorizontalCoordinate { private set; get; }
  // Used externally to retain screen location
  public float ScreenX;
  public float ScreenY;
  // Called external to update HorizontalCoordinate
  public void Update(Time time, GeographicCoordinate location)
  {
    bool needsUpdate = false;
    if (!this.Time.Equals(time))
    {
      this.Time = time;
      needsUpdate = true;
      OnTimeChanged();
    }
    if (!this.Location.Equals(location))
    {
      this.Location = location;
      needsUpdate = true;
    }
    if (needsUpdate)
    {
      this.HorizontalCoordinate =
        HorizontalCoordinate.From(this.EquatorialCoordinate,
                                  this.Location, this.Time);
    }
  }
  // Overridden by descendant classes to update EquatorialCoordinate
  protected virtual void OnTimeChanged()
  {
  }
}

中所示的 SolarSystemBody 类图 3 计算从及其子代的类和示的星球类提供的 HeliocentricLocation 矢量的 EquatorialCoordinate 属性图 4,HeliocentricLocation 基于对 VsopCruncher 的调用,计算。

图 3 SolarSystemBody 类

public class SolarSystemBody : CelestialBody
{
  protected SolarSystemBody()
  {
  }
  protected SolarSystemBody(string name, Color color)
  {
    this.Name = name;
    this.Color = color;
  }
  public string Name { protected set; get; }
  public Color Color { protected set; get; }
  // Set by descendant classes, used here
  protected Vector3 HeliocentricLocation { set; private get; }
  protected override void OnTimeChanged()
  {
    // Calculate geocentric coordinates
    Vector3 bodyLocation = this.HeliocentricLocation -
                           Earth.Instance.HeliocentricLocation;
    EclipticCoordinate geocentricCoordinate =
      new EclipticCoordinate(bodyLocation);
    this.EquatorialCoordinate =
      EquatorialCoordinate.From(geocentricCoordinate, this.Time);
    base.OnTimeChanged();
  }
}

图 4 星球类

public class Planet : SolarSystemBody
{
  VsopCruncher vsop;
  public Planet(string strPlanetAbbreviation, 
    string name, Color color) :
    this(strPlanetAbbreviation)
  {
    this.Name = name;
    this.Color = color;
  }
  protected Planet(string strPlanetAbbreviation)
  {
    vsop = new VsopCruncher(strPlanetAbbreviation);
  }
  protected override void OnTimeChanged()
  {
    Angle latitude = Angle.FromRadians(vsop.GetLatitude(this.Time.Tau));
    Angle longitude = Angle.FromRadians(vsop.GetLongitude(this.Time.Tau));
    double radius = vsop.GetRadius(this.Time.Tau);
    this.HeliocentricLocation =
      new EclipticCoordinate(longitude, 
          latitude, radius).RectangularCoordinates;
    base.OnTimeChanged();
  }

明星类是更简单,因为它可以计算赤道几内亚­只是小的调整,到 2000 年的立场与协调。

XNA 前端

我开始写使用 Silverlight,工作得非常棒与第一个几个星座的 AstroPhone 程序。 但更多的星座补充,它变得越慢。

请记住时使用此类程序的 Silverlight,所有文本、 线条和点都是在面板中的某种 TextBlock、 线和路径元素。 要得到流体的运动,如弧扫电话,这需要将只是一个面板,和那在它拥有的一切。 性能降低更多如果你开始删除项目或使他们当他们不是在视图中不可见。

我花了很多时间试图改善此 Silverlight 程序的性能。 我发现使用画布比单个单元格网格更好地工作。 我发现当显示多行元素,您可以通过将一个坐标的行设置为点 (0,0)、 调整其他坐标的金额和使用 TranslateTransform 将其移动到的位置提高性能。

但与所有 88 星座中的地方,掉到单个帧每秒的视频帧速率和根本没有选择除要放弃 Silverlight。 这就是为什么 AstroPhone 是一个 XNA 程序。

有趣的是,AstroPhone 的最慢部分根本不是图形。 反序列化该 Constellations.xml 文件,这会发生在 OnActivated 的游戏类重写它。 该程序然后生成更新和呈现天体物体的几个集合。 用于更新坐标的主要集合称为 celestialBodies。 顾名思义,这是从 CelestialBody,派生的类的实例的集合,它包含 832 对象 — — 735 对象的类型明星的 88 键入星座,一个太阳和地球上的所有八个对象。 (冥王星不是列入 VSOP87)。

在 Windows Phone XNA 程序中,在游戏类的更新和绘制覆盖称为每秒 30 倍的速度。 负责从传感器 (GPS 和议案,在本例中的) 获取输入和准备用于绘制方法的数据更新。

为了实现对流动电话的平滑响应,更新覆盖整个 celestialBodies 集合的循环、 从每个对象,获取的 HorizontalCoordinate 属性并使用当前议案矩阵,转换为二维的点,在屏幕上,然后将存储在 ScreenX 和 ScreenY 的 CelestialBody 对象的属性。 然后绘制方法访问这些 ScreenX 和 ScreenY 的属性,以在屏幕上绘制对象。

但是,计算只是占屏幕的运动。 它也是有必要为每个 CelestialBody 对象定期更新其 HorizontalCoordinate 属性,随着时间的推移和地球和其他行星移一点。 仍然,此更新不是该程序的顺利运作的关键。 HorizontalCoordinate 属性基于当前日期和时间以及用户的地理位置,但不能对这些项目的更改速度不够快,在短期内影响恒星和行星的位置。

为此,游戏类的更新替代慢悠悠地处理 CelestialBody 对象的更新方法。 CelestialBodies 集合中的只有一项被更新游戏类更新重写中,需要约 28 秒 832 对象的整个集合中循环周期的每个调用。

为了呈现,其他集合维护因为不同类型的天体在以不同的方式呈现。 VisibleStars 集合中包含屏幕呈现 735 明星对象、 星座集合了 88 星座和 systemBodies 集合了太阳和七个行星,包括地球。

像是从 CelestialBody 派生的每个其他类,星座类设置 EquatorialCoordinate 属性,但这是仅用于定位的星座名称。 每个星座实例以弥补星座的连接星星的平均计算这一立场。

连接线条本身是相当棘手。 每个星座对象具有的连接器对象的集合,每个引用明星星座中一对。 但这些连接器对象来自原始的 Constellations.xml 文件,而它们引用连接明星 ID 号的一对。 加快线绘图,该程序花费补充每对 StarConnector 对象时,这是一对实际明星对象的 ID 号的初始化过程的一部分。 因此,该程序可以通过引用实际的明星对象的 ScreenX 和 ScreenY 属性绘制连接线线条。

图 5 显示呈现星座名称、 连接线条和恒星本身的绘制方法的一部分。

图 5 绘制方法来呈现星座和星星

protected override void Draw(GameTime gameTime)
{
  // Dark blue sky
  GraphicsDevice.Clear(new Color(0, 0, 0x20));
  spriteBatch.Begin(SpriteSortMode.Immediate, 
    null, null, null, null, null,
    displayMatrix);
  ...
// Loop through constellations
  foreach (Constellation constellation in 
    constellations.ConstellationList)
  {
    if (!float.IsNaN(constellation.ScreenX))
    {
      // Display constellation name
      Vector2 textSize = 
        labelFont.MeasureString(constellation.LongName);
      spriteBatch.DrawString(labelFont, constellation.LongName,
        new Vector2(constellation.ScreenX - textSize.X / 2,
        constellation.ScreenY - textSize.Y / 2), Color.Gray);
    }
    // Display constellation connectors
    if (constellation.StarConnectors != null)
    {
      foreach (StarConnector starConnector in 
        constellation.StarConnectors)
      {
        Vector2 point1 = new Vector2((float)starConnector.From.ScreenX,
          (float)starConnector.From.ScreenY);
        Vector2 point2 = new Vector2((float)starConnector.To.ScreenX,
          (float)starConnector.To.ScreenY);
        if (!float.IsNaN(point1.X) && !float.IsNaN(point2.X))
          lineRenderer.Draw(spriteBatch, point1, point2, 
           1, Color.White);
      }
    }
  }
  // Now display the stars themselves
  foreach (Star star in visibleStars)
    if (!float.IsNaN(star.ScreenX))
      starDotRenderer.Draw(spriteBatch,
        new Vector2(star.ScreenX, star.ScreenY), Color.White);
  ...
spriteBatch.End();
  base.Draw(gameTime);
}

虽然程序如 AstroPhone 提供的恒星和行星的帮助指南,都没有在现实生活中其实看看他们的经验。 也许随着人们获得更多的知识,这类程序的帮助下,他们将再一次制定夜晚的天空会非常熟悉。

查尔斯 · Petzold 是 MSDN 杂志长期贡献和目前正在更新他的经典著作"编程窗口"(微软出版社,1998 年),Windows 8. 他的网站是 charlespetzold.com

衷心感谢以下技术专家对本文的审阅:音为摩斯