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






4.20/5 (2投票s)
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
方法,该方法通过 GeoKit 的 acts_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)' %>
完成了。您可以在此处查看最终结果。
- 带模拟数据的行程示例
- 一个拖曳自行车行程示例
- 浏览行程(实际数据非常少,因此许多行程可视化并不有趣)