内容


用 Perl 和 Google Earth 创建时间-可用性地图

通过提取并在 Google Earth 中显示邮件数据,清楚地了解团队成员、客户是否有空或系统何时可用

Comments

团队可能包括国内和国外的、灵活的工作时间和每周四天工作制都会改变团队协作的时间和地点。本文提供了工具和代码来帮助您找到使用 Google Earth 联系跨地理区域的各个团队成员的最佳时间。通过使用邮件时间跟踪(电子邮件标题)和具有适当设置的生成 Keyhole 标记语言(Keyhole Markup Language,KML)的程序,本文将演示使用 Google Earth 的 TimeSpan 特性和 “时间滑块” 实现的有用的可视化技术。

要求

硬件

能够用 3-D 加速运行 Google Earth 的任何硬件都有足够的能力处理本文中的代码。这里描述的 KML 仅为美国就使用了数以千计的多边形顶点,因此如果要在全球呈现或超越国家级的精确度,则可能需要速度更快的处理器。

软件

必须拥有 Google Earth V4 或更高版本才能支持对于实现可视化非常重要的 TimeSpan 功能。构建 KML 以及提取电子邮件标题信息需要使用 Perl。您需要来自 CPAN 的 Mail::IMAPClient 和 IO::Socket::SSL 模块(请参阅 参考资料)。

注意:本文中提供的代码是跨平台的,并且应当可以在运行 Google Earth 和 Perl 的任意平台中运行。

构建时间-可用性地图所采取的一般方法

时间-可用性地图提供了在特定时间特定地点最有可能取得联系的人员的清单。例如,图 1 和 2 显示了在特定时窗期间国内各地可能收到邮件的用户。即时消息日志、电话使用记录、组日历、标记阅读器(badge-reader)访问和任意数目的与时间相关的其他数据记录都可用于创建这些时间-可用性地图。

本文主要介绍如何提取最常见的可用性数据:电子邮件标题。人们在积极地发送邮件时最有可能联系到。系统给每个用户都设定相应的地理区域,并且基于每个小时的邮件计数用指示器衰减深度创建 KML。使用 Google Earth 的时间滑块功能(包括动画和总时窗选择)将帮助为整个地理区域的用户可视化得到的可用性地图。查看图 1,该图将演示一天中早些时候的可视化示例。

图 1. 可视化示例 —— 开始时间
可视化示例 —— 开始时间
可视化示例 —— 开始时间

图 2 显示了一天中稍晚时候的更宽的时间窗,帮助标识整个美国的可用用户区域。

图 2. 可视化示例 —— 随后的时间
可视化示例 —— 随后的时间
可视化示例 —— 随后的时间

提取州概要信息

Google 的许多优秀 KML 文档页面之一将使用 KML 列出美国各州的轮廓图。这些粗略图大约包含 13,000 个点,为高亮显示各州提供了不错的基础。获取 Google 美国各州示例文件 并阅读 extractStates.pl 程序如何分离州信息的详细信息。

清单 1. extractStates.pl 完整程序
#!/usr/bin/perl -w
# extractStates.pl write each state coordinates into a separate file
use strict;
my $str   = "";  # built output string
my $fname = "";  # current filename

my $cmd = `mkdir states/`;

while( my $line = <STDIN> )
{
  if( $line =~ /<name>/ )
  {
    # extract the state for file name
    $fname = substr( $line, index($line,"CDATA[")+6 );
    $fname = substr( $fname, 0, index($fname," (")  );

  }elsif( $line =~ /<TimeSpan>/ )
  {
    # change the TimeSpan designator for later processing
    $line = <STDIN>;  # begin tags
    $line = <STDIN>;  # close TimeSpan
    $line = qq{      <TimeSpan></TimeSpan>\n};

  }elsif( $line =~ /<\/Placemark/ )
  {
    # write out the file, reset variables after closing tag
    open( OUTFILE, "> states/$fname ") or die "Can't write state file";
      print OUTFILE $str;
      print OUTFILE $line;
    close(OUTFILE);

    $str = "";

  }#if closing tag

  # add a line if it's at the start or the built string is not blank
  if( $line =~ /<Placemark/ || $str ne "" ){ $str .= $line }

}#line in

在声明变量并且创建 states 目录后,extractStates.pl 程序将从 STDIN 中读取每一行。us_states.kml 文件包含粗略的州轮廓图,而 extractStates.pl 程序将把每个几何图形写入各自的文件。特定于 us_states.kml 文件的 TimeSpan 条目将被替换为更容易修改的占位符,并且每个州的全部信息被写出到 states 目录。

把上面的清单 1 中的代码保存到名为 extractStates.pl 的文件中,并且用 cat us_states.kml | perl extractStates.pl 命令运行该程序。检查 states 目录查看文件列表,如下所示:

清单 2. states 目录列表
ls -la states/* | head
-rw-r--r-- 1 nathan nathan  6708 2008-07-08 17:11 states/Alabama
-rw-r--r-- 1 nathan nathan 85426 2008-07-08 17:11 states/Alaska
...
-rw-r--r-- 1 nathan nathan 15804 2008-07-08 17:11 states/West Virginia
-rw-r--r-- 1 nathan nathan 11536 2008-07-08 17:11 states/Wisconsin
-rw-r--r-- 1 nathan nathan  3298 2008-07-08 17:11 states/Wyoming

提取电子邮件时间信息

用来自 CPAN 的 Mail::IMAPClient 和 IO::Socket:SSL 模块提取电子邮件标题和处理发送时间相对简单一些。以下示例将使用 Google Internet Message Access Protocol(IMAP)界面,但是本文中提供的代码应当可以在任意数目的邮件服务器上运行。您可能必须消除 SSL 连接,这取决于服务器设置。

清单 3. extractEmails.pl 模块,连接设置
#!/usr/bin/perl -w 
# extractEmails.pl get all e-mails, print listing of from at what hour
use strict;
use Mail::IMAPClient;
use IO::Socket::SSL;
my %timeHash = ();    # data structure for whom at what time

# create a SSL socket to the imap server
my $socket = IO::Socket::SSL->new( PeerAddr => 'imap.gmail.com',
                                   PeerPort => 993
                                 ) or die "can't create socket";

# create an imap connection through the ssl socket
my $imap = Mail::IMAPClient->new( Socket   => $socket,
                                  User     => 'yourEmailID@gmail.com',
                                  Password => 'yourPassword'
                                ) or die "can't connect imap";
$imap->select("INBOX");
my @messages = $imap->search('ALL');

创建套接字和 IMAP 连接十分简单。清单 4 将创建和输出电子邮件和时间数据结构。

清单 4. 提取小时,输出数据结构
my $msgCount = 0;
for my $msg ( @messages )
{
  my $from = $imap->get_header($msg,"From");
  my $date = $imap->get_header($msg,"Date");

  # set date to main hour
  $date = substr($date, index($date,":")-2,2);

  # increment the hour's count for that id
  $timeHash{$from}{$date}++;

  $msgCount++;
  if( $msgCount % 10 == 0 ){ print STDERR "$msgCount\n" }

}#for each message

$imap->logout();

# print all of the hour/from combinations for later processing
for my $from( keys %timeHash )
{
  for my $time( keys %{ $timeHash{$from} } )
  {
    print "$from TIME $time $timeHash{$from}{$time}\n";
  }#for time
}#for id

注意在本例中,所选时间是发送每封电子邮件的小时数。您会发现为特定的可用性场景选择小时、分钟、天数或星期十分有用。用 perl extractEmails.pl > emailHours 命令运行 extractEmails.pl 程序。在将进度指示器(以每 10 个标题为单位)输出到 STDERR 中后,以上命令将生成 emailHours 文件,如下所示:

清单 5. emailHours 示例文件
Dave <dave@ibmdevworks.com> TIME 11 6
Dave <dave@ibmdevworks.com> TIME 21 8
...
Bob <bob@ibmdevworks.com> TIME 07 6
Bob <bob@ibmdevworks.com> TIME 11 36

emailHours 文件的格式为名称(如果可用)、电子邮件地址、TIME 分隔符、小时。

分配电子邮件可以寻找地理位置

emailHours 文件现在包含所有电子邮件地址的列表和在给定小时中发送邮件的次数。您可能需要处理一些特定的联系人,或者创建发送电子邮件最多的人的列表。考虑使用下面一行代码在 emailHours 文件中创建前 50 个电子邮件发送者的列表。

清单 6. 生成前 50 个电子邮件发送者的命令
cat emailHours | \
  perl -lane '@a=split "TIME";$h{$a[0]}+=$F[@F-1]; \
    END{for(keys %h){print "$h{$_} $_"}}' | sort -nr | head -n50  > top50emails

注意,清单 6 中的 \ 字符仅用于设定格式,而不应当在运行命令时包括。运行以上命令将生成如下所示的列表:

清单 7. top50emails 示例文件
44 Bob <bob@ibmdevworks.com>
38 Dave <dave@ibmdevworks.com>
34 Tom <tom@ibmdevworks.com>
30 Mike <mike@ibmdevworks.com>
...

通过插入州名,然后在每个文件的开头位置插入 STATE 分隔符,修改 top50emails 文件。您可以手动执行此操作,也可以将州指示器与 geo-ip 定位程序、员工地址数据库或其他来源的地理位置数据链接起来。将已修改的文件另存为 stateMapping,如下所示:

清单 8. stateMapping 示例文件
New York STATE 44 Bob <bob@ibmdevworks.com>
North Carolina STATE 38 Dave <dave@ibmdevworks.com>
Virginia STATE 34 Tom <tom@ibmdevworks.com>
Georgia STATE 30 Mike <mike@ibmdevworks.com>
...

用 createKml.pl 生成 KML 标记

提取了州坐标、计算了完整电子邮件标题和时间并且将每个相关电子邮件 ID 与州名关联在一起之后,可以生成 KML 文件来实现所需的可视化。清单 9 显示了 createKml.pl 程序的开头。

清单 9. createKml.pl 程序头,主循环
#!/usr/bin/perl -w
#createKml.pl build google earth kml, fade states based on entries per hour
use strict;
die "specify state mapping file, maximum, intervals " unless ( @ARGV == 3 );
my( $inFile, $max, $interval ) = @ARGV;
my %state = ();

loadStateMapping();
kmlHeader();
kmlStyles();

while( my $line = <STDIN> )
{
  # for bogus entry elimination
  next unless length( $line) > 20 ;
  chomp($line);

  #change person@ibm.com TIME 11 2 into components
  my( $mail, $time ) = split "TIME ", $line;
  my( $stHour, $countVal ) = split " ", $time;

  # continue if a state defined for that mail
  next unless exists($state{$mail});

  open( INFILE,"states/$state{$mail}") or die "no state input file";
    while( my $line = <INFILE> )
    {
      if   ( $line =~ /<name>/     ){ print "<name><![CDATA[$mail]]></name>\n" }
      elsif( $line =~ /<TimeSpan>/ ){ getTimes( $stHour )   }
      elsif( $line =~ /Style_/ )    { getStyle( $countVal ) }
      else                          { print $line }
    }#while line in

  close(INFILE);

}#line in

print qq{</Document>\n</kml>\n};

在确保正确使用并声明变量之后,将调用 loadStateMapping 子例程。将读取为每个电子邮件地址设定的州,并且调用 kmlHeaderkmlStyles 子例程来输出指定阈值与间隔的相应 KML 标记。

进入主循环以读取 STDIN 中的每一行并提取相关信息。系统将把电子邮件地址指定为位置标记名称,计算 TimeSpan 开始点和结束点,并且相应的样式都是基于指定的阈值和间隔编写的。

清单 10 将显示这些子例程中的第一个子例程 loadStateMapping 的详细信息。

清单 10. loadStateMappingkmlHeader 子例程
sub loadStateMapping
{
  # create a hash storing which mail corresponds to which state

  open( INFILE,"$inFile" ) or die "no in state file";
    while( my $line = <INFILE> )
    {
      chomp($line);
      my( $sname, $mail )  = split "STATE ", $line;

      # skip the total count
      $mail = substr($mail, index($mail," ")+1);

      $state{$mail} = $sname;

    }#stateMapping lines

  close(INFILE);

}#loadStateMapping

sub kmlHeader
{
  print qq{<?xml version="1.0" encoding="UTF-8"?>\n};
  print qq{<kml xmlns="http://earth.google.com/kml/2.2">\n};
  print qq{<Document>\n};
  print qq{  <name><![CDATA[Time Availability]]></name>\n};
  print qq{  <open>1</open>\n};

}#kmlHeader

为了更加快速地处理,loadStateMapping 文件只读取州分配文件。创建与每个州名的电子邮件地址绑定的散列,将在主程序循环中检查该散列。如果尚未分配州,这将允许跳过某些条目。kmlHeader 子例程将输出 KML 文档的主要头标记。清单 11 显示了 getTimes 子例程。

清单 11. getTimes 子例程
sub getTimes
{
  my $endHour = $inHour + 1;
  if( length($endHour) == 1 ){ $endHour = "0$endHour" }

  print  qq{    <TimeSpan>\n};
  print  qq{        <begin>2008-07-01T$inHour:00Z</begin>\n};
  print  qq{        <end>2008-07-01T$endHour:00Z</end>\n};
  print  qq{    </TimeSpan>\n};

}#getTimes

上面列出的 getTimes 子例程中的代码将指定正确的 TimeSpan 标记。下面的清单 12 显示了更加复杂的 getStyle 子例程。

清单 12. getStyle 子例程
sub getStyle
{
  # find the appropriate style based on the input value
  my $inputVal = $_[0];
  my $decInc = $max / $interval;

  my $count = $decInc;
  my $styleCount = 0;

  # move through each interval, exit when the input value no longer fits
  while( $count <= $max )
  {
    if( $count > $inputVal ){ last }
    $styleCount++;
    $count += $decInc;

  }#While count less than max

  # default to the last style if interval is outside the boundary 
  if( $styleCount >= $interval ){ $styleCount-- }

  print qq{        <styleUrl>#style} . $styleCount . qq{</styleUrl>\n};

}#getStyle

如下面所示的 kmlStyles 子例程,getStyle 子例程将首先创建最大指定值除以间隔得到的增量机(incrementer)。例如,最大值 100 和间隔 5 将生成一系列相差 20 个单元的 “bucket”,每个输入值都将落入这些 bucket 中的一个。例如,输入值 40 将对应中低衰减样式,而大于等于 80 的值将对应最不透明的衰减设置。清单 13 显示了 kmlStyles 子例程。

清单 13. kmlStyles
sub kmlStyles
{
  # create a incremented "fade range" according to the number of intervals
  my $hexInc = 255/$interval;
  my $count = $hexInc;
  my $styleNum = 0;

  while( $count <= 255 )
  {
    my $fade = sprintf("%X", $count );

    print qq{  <Style id="style} . $styleNum . qq{">\n};
    print qq{    <IconStyle>\n};
    print qq{      <scale>0.4</scale>\n};
    print qq{      <Icon>\n};
    print qq{        <href>http://maps.google.com/mapfiles/kml/};
    print qq{shapes/star.png</href>\n};
    print qq{      </Icon>\n};
    print qq{    </IconStyle>\n};
    print qq{    <LabelStyle>\n};
    print qq{      <color>9900ffff</color>\n};
    print qq{      <scale>1</scale>\n};
    print qq{    </LabelStyle>\n};
    print qq{    <LineStyle>\n};
    print qq{      <color>99FFFF99</color>\n};
    print qq{      <width>2</width>\n};
    print qq{    </LineStyle>\n};
    print qq{    <PolyStyle>\n};
    print qq{      <color>} . $fade . qq{FF9933</color>\n};
    print qq{      <fill>1</fill>\n};
    print qq{      <outline>1</outline>\n};
    print qq{    </PolyStyle>\n};
    print qq{  </Style>\n};

    $styleNum++;
    $count += $hexInc;

  }#while count

}#kmlStyles

不管最大值或指定的间隔数是多少,每个定义的样式都需要有 00-ff 衰减百分比。在 sprintf 命令中使用 %X 修改符执行十进制间隔与十六进制衰减百分比之间的相应转换。将以上代码保存到名为 createKml.pl 的文件中并继续阅读用法信息。

用法

如何选择适当的最大值和间隔变量在很大程度上依赖于具体数据。尝试比 top50emails 中记录的最大值小 20% 至 40% 的最大值。选择间隔实际上是一种权衡:是使用较高的间隔值来呈现大量信息,还是使用较低的间隔值来显示非常少量的更改。用 cat emailHours | perl createKml.pl stateMapping 20 2 > timeMap.kml 命令尝试一个简单示例。

在打开 Google Earth 并载入 timeMap.kml 文件后,在屏幕中心上方查找 “时间滑块”。通过拖动滑块按时间浏览可视化地图,或者按下播放按钮显示动画。另请尝试扩展可视时窗以扩展某些州可见的时间范围。

结束语

使用令人难以置信的 Google Earth 界面以及上面用于创建 KML 的自定义代码,您可以为各种应用程序构建您自己的时间-可用性地图。考虑从即时消息日志、电话记录或其他来源中提取登录时间及活动时间,构建附加数据集及重叠数据集。扩展时窗,或者将更多注意力集中到公司或客户群中每时每刻传播的信息。提取 Web 服务器访问者信息并构建市政专用的地点标记和指示器集,研究 Web 站点访问者在何时何地可以看到内容。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=343445
ArticleTitle=用 Perl 和 Google Earth 创建时间-可用性地图
publish-date=10062008