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

世界上最快的服务器可视化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (2投票s)

2009年4月13日

CPOL

5分钟阅读

viewsIcon

23105

downloadIcon

138

Ruby on Rails 应用程序,用于处理世界上最快服务器的数据。

引言

在本篇文章中,我们将构建一个 Web 应用程序,用于消费和使用来自世界上最快的服务器 SOAP Web 服务的 OOO 数据。我们将使用流行的 Web 框架 Ruby on Rails 来构建此应用程序。Ruby 是一种高级、动态、面向对象的语言。在此过程中,我们还将加入一些 JavaScript 和 Perl。

目前,拖曳自行车的 Web 服务中数据量不多,但我们的应用程序会将 Web 服务中的所有数据加载到我们的本地数据库中,并允许将行程可视化在地图和 Google Street View 上。

我们的应用程序

我们的应用程序的主要功能是能够在一张 Google 地图上查看世界上最快的服务器的给定行程,以及附带的街景。

使用页面上的控件,用户可以向前/向后移动行程(每一步对应 Web 服务的一个航点)。我们会自动调整街景摄像头的角度(偏航角),使其指向下一个航点的方向。

开始

首先,我们需要消费 SOAP Web 服务中的数据。我们的应用程序主要将显示记录后的行程数据,因此我们将数据定期从 Web 服务加载,但将其本地存储以便更快、更方便地访问。

为此,我们将创建一个 Rails 数据库迁移。Rails 迁移是用简单的 Ruby DSL 编写的,可以轻松描述数据库模式的更改,从而方便地应用和撤销这些更改。这是我们创建“waypoints”表的迁移摘录。

create_table :waypoints do |t|
  t.integer :hms_id, :null => false
  t.integer :trip_id, :null => false
  t.datetime :recorded_at, :null => false
  t.datetime :created_at, :null => false
  t.decimal :lng, :precision => 15, :scale => 12
  t.decimal :lat, :precision => 15, :scale => 12
end

正如您所见,我们创建了几个表,它们反映了我们将从中加载数据的 Web 服务的结构,尽管我们进行了一些更改以使其遵循 Rails 约定。

加载数据

现在我们遇到了一个问题,那就是 Ruby 社区在很大程度上已经从 SOAP 转向了 REST Web 服务,现有的 SOAP 库已不再得到良好维护。与其将圆钉硬塞进方孔,不如转向一个更简单的解决方案:Perl 的 SOAP::Lite。我们将创建一个简单的 Perl 脚本,使用 SOAP::Lite 来加载我们的数据。

#!perl -w

use SOAP::Lite;
use Switch;

my $service = SOAP::Lite
  -> service('http://www.theworldsfastestserver.com/webservice/bikedata.cfc?wsdl');
$service->outputxml(1);

my $trip_id = SOAP::Data->name( 'TripID' => $ARGV[1] );

switch ($ARGV[0]) {
  case "trips"          { print $service->GetTripIDsXML() }
  case "types"          { print $service->GetTripTypesXML() }
  case "waypoints"      { print $service->GetWaypointsForTripXML($trip_id) }
  case "accelerations"  { print $service->GetAccelForTripXML($trip_id) }
}

我们可以调用此脚本,并传入命令行参数来指定要调用的 Web 服务以及可选的行程 ID。它只会输出原始 XML,我们将使用 Ruby XML 解析器 Nokogiri 进行解析。我们将创建一个 WfsImporter 类来处理所有这些。以下代码运行 Perl SOAP 脚本,加载给定类型(行程、航点或加速度)的 XML,并将其解析为子对象。

def self.records_xml(type, hms_id = nil)
  xml = load_xml(type, hms_id)
  
  case type
    when :trips           then xml/:Trip
    when :waypoints       then xml/:WayPoints/:WayPoints
    when :accelerations   then xml/:Accelerometer/:Accelerometer
  end
end

def self.load_xml(name, hms_id = nil)
  Nokogiri::XML(%x[perl #{RAILS_ROOT}/script/wfs.pl #{name} #{hms_id}])
end

现在,我们有了 Web 服务数据的漂亮对象,但我们实际上只需要其中一些数据,并且需要对其进行一些处理,使其符合我们喜欢的命名和约定。我们将创建一个简单的哈希,定义我们想要使用的属性,以及这些属性在我们的对象中应该具有的名称(有时是相同的)。

ATTR_MAPS = {    
  :trips          => {  :trip_type_id => :trip_type_id, :name => :name, 
                        :description => :description, :start_datetime => :started_at, 
                        :end_datetime => :ended_at },
  :waypoints      => {  :Timestamp => :recorded_at, :latitude_decimal => :lat, 
                        :longitude_decimal => :lng, :utc_time => :soap_time, 
                        :utc_date => :soap_date },
                  
  :accelerations  => {  :timestamp => :recorded_at, :miliseconds => :milliseconds, 
                        :utc_date => :soap_date, :utc_time => :soap_time, 
                        :axis => :axis, :value => :g_force},
}

现在,我们将结合使用 XML 加载方法和我们的属性映射来解析 Web 服务中的 XML 并将其加载到我们的数据库中。

def self.import_records(type, parent = nil)
  records_xml(type, parent.try(:hms_id)).each do |record_xml|
    hms_id = record_xml['ID']
    attrs = ATTR_MAPS[type].map_to_hash{ |xml_attr, db_attr| 
                 {db_attr => record_xml.at(xml_attr).content } }
    
    record = (parent ? parent.send(type) : Trip).find_or_initialize_by_hms_id(hms_id)
    record.update_attributes(attrs)
  end
end

我们在每个对象中添加了一个“hms_id”,以便在多次导入时避免重复创建相同的对象。我们数据库中的实际键将由 Rails 生成,并且与 HMS 的不同。

最后,这里有一些简单的代码来批量处理所有行程及其子对象的导入过程。

def self.import_all
  import_records(:trips)
  Trip.all.each do |trip|
    import_records(:waypoints, trip)
    import_records(:accelerations, trip)
  end
end

创建应用程序

我们的应用程序将允许浏览行程,并查看每个行程的航点/加速度的详细信息,但这相当标准——如果您感兴趣,可以查看代码。独特之处在于一个页面,允许在 Google 地图上查看行程,并使用 Google Street View(如果可用)显示自行车的位置,甚至在每个记录的航点处的朝向。

我们需要一些 JavaScript 代码来执行设置地图等功能。在 visualize.html.erb 页面本身,我们只放入这两个函数。

function initialize() {
  load_data();
  setup_map();
    move(0);
}

function load_data(){
  <% @trip.waypoints.each_with_index do |waypoint, i| % >
      positions[<%= i % >] = new GLatLng(<%= waypoint.lat_lng % >);

      <%= "yaws[#{i - 1}] = #{@last.heading_to(waypoint)};" if @last % >
      <% @last = waypoint % >
    <% end % >
    yaws.push(yaws[yaws.length - 1]);
}

初始化程序会简单地调用我们定义的其他函数。load_data 在这里定义,以便我们混合 eRb 和 JavaScript。在这种情况下,我们遍历 @trip.waypoints 的每个元素,并使用它来创建两个数组:positions,一个由每个航点的 lat_lng 属性创建的 GLatLng 对象数组;以及 yaws,一个基于每个航点之间前进方向的假定方向数组。我们正在使用每个航点上的 heading_to 方法,该方法通过 GeoKitacts_as_mappable 混入对象中。

现在,在 maps.js 中,我们将定义所需的其他函数。

function setup_map(){
  my_pano = new GStreetviewPanorama($('pano'));
  my_map = new GMap2($('map_canvas'));      
  
  var polyline = new GPolyline(positions, "#ff0000", 10, 0.7);
  my_map.addOverlay(polyline);
}

这将创建新的街景和地图对象,并使用我们的 GLatLng 数组在地图上绘制整个行程的路线,使用 GPolyline

现在,我们只需要一个函数来让用户向前和向后移动地图。我们还将输出一些调试数据。

function move(increment){
  step = step + increment;
  write_position();

  if(positions[step])
  {
    my_pano.setLocationAndPOV(positions[step], {yaw:yaws[step]});
    my_map.setCenter(positions[step], 15);
    
    if(current_marker)
      my_map.removeOverlay(current_marker);

    current_marker = new GMarker(positions[step]);
    my_map.addOverlay(current_marker);
  }
}

function write_position()
{
   position_string = positions[step] ? "lat/lng: " + 
            positions[step] : "no data remaining";
  $('current_pos').innerHTML = "step: " + step + 
            " | " + position_string;
}

move 函数允许我们向前或向后移动任意步数,使用 write_position 输出有关我们当前位置的一些调试数据。如果我们要跳转到的位置存在,我们将更新我们的街景到新位置和摄像机方向,并将我们的地图居中在新位置。然后,我们将检查地图上是否有旧标记,如果存在则删除它,然后为我们当前显示的航点添加一个新标记。

我们在页面中添加了链接,允许用户向前和向后移动地图。

<%= link_to_function 'step back', 'move(-1)' %> | 
  <%= link_to_function 'step forward', 'move(1)' %>

完成了。您可以在此处查看最终结果。

© . All rights reserved.