home *** CD-ROM | disk | FTP | other *** search
/ linuxmafia.com 2016 / linuxmafia.com.tar / linuxmafia.com / pub / palmos / bart-1.3b2.tar.gz / bart-1.3b2.tar / bart-1.3b2 / data / make-pdb-file < prev    next >
Text File  |  2000-01-12  |  28KB  |  921 lines

  1. #!/usr/bin/perl -w
  2.  
  3. # Perl script to generate pdb file containing information, schedules,
  4. # and fares from various text files.
  5.  
  6. # Copyright (c) 1999, 2000 Michael Wittman
  7. # Permission is hereby granted, free of charge, to any person obtaining a
  8. # copy of this software and associated documentation files (the "Software"),
  9. # to deal in the Software without restriction, including without limitation
  10. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  11. # and/or sell copies of the Software, and to permit persons to whom the
  12. # Software is furnished to do so, subject to the following conditions:
  13. # The above copyright notice and this permission notice shall be included
  14. # in all copies or substantial portions of the Software.
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  18. # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  19. # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  20. # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  21. # OTHER DEALINGS IN THE SOFTWARE.
  22.  
  23. use strict;
  24.  
  25. my $pdb_file = "bartdata.pdb";
  26. my $header_file = "bartdata.h";
  27.  
  28. # the following line sets $data_dir to the directory that this script is in
  29. my $data_dir = ($0 =~ m@^(?:(.*)/)?[^/]+$@)[0] || ".";
  30. my $stations_file = "$data_dir/stations";
  31. my $transfers_file = "$data_dir/transfers";
  32. my $fares_file = "$data_dir/fares";
  33. my $bike_names_file = "$data_dir/bike-names";
  34. my $station_bike_restrictions_file = "$data_dir/bike-restrictions";
  35. my $sched_dir = "$data_dir/schedules";
  36. my (@days) = ("weekday", "saturday", "sunday");
  37.  
  38. my (@station_abbrevs);
  39. my (%station_name,%station_number,%pilot_abbrev);
  40. my (%transfers);
  41. my (%fare);
  42. my (%bike_name);
  43. my (%station_bike_restrictions);
  44. my (@weekday_lines,@saturday_lines,@sunday_lines,@weekday_scheds,@saturday_scheds,@sunday_scheds);
  45.  
  46. $| = 1;
  47.  
  48. # pdb file constants
  49. my $PDB_HEADER_SIZE = 78;
  50. my $RECORD_LIST_STRUCTURE_SIZE = 8;
  51.  
  52. # time_t value of db creation date; the corresponding date is displayed in
  53. # the "About" form so users know how current their database is.
  54. my $CREATION_DATE = `date -d "12:00pm 11/3/99" +%s`+0;
  55.  
  56. # use these two now to possibly save bits later if the fare range
  57. # becomes greater
  58.  
  59. # number by which to multply the encoded fares to get the actual fares
  60. my $FARE_MULTIPLIER = 0.05;
  61. # minimum fare
  62. my $MIN_FARE = 1.10;
  63.  
  64. # schedule days end at 2:30 AM
  65. my $DAY_END_TIME = 150;
  66.  
  67. my $CREATOR_ID = "BART";
  68. my $DB_ID = "DATA";
  69. my $DB_NAME = "BART SchedulerDB";
  70. my $DB_VERSION = 2;
  71.  
  72. my $GENERAL_CATEGORY = 0;
  73. my $WEEKDAY_CATEGORY = 1;
  74. my $SATURDAY_CATEGORY = 2;
  75. my $SUNDAY_CATEGORY = 3;
  76. # don't exhibit general category to user
  77. my (@CATEGORY_NAMES) = ("Weekday","Saturday","Sunday");
  78.  
  79. my $INFO_RECORD_NUMBER = 0;
  80. my $FARES_RECORD_NUMBER = 1;
  81. my $TRANSFER_RECORD_NUMBER = 2;
  82. my $INITIAL_LINE_RECORD_NUMBER = 8;
  83.  
  84.  
  85. sub max {
  86.    my $max = shift;
  87.    local($_);
  88.    for (@_) { $max = $_ if $max < $_; }
  89.    $max;
  90. }
  91.  
  92. sub min {
  93.    my $min = shift;
  94.    local($_);
  95.    for (@_) { $min = $_ if $min > $_; }
  96.    $min;
  97. }
  98.  
  99. sub sum {
  100.    my $sum = 0;
  101.    local($_);
  102.    for (@_) { $sum += $_; }
  103.    $sum;
  104. }
  105.  
  106. sub print_header_file {
  107.    my $file = shift;
  108.    my $oldfh;
  109.  
  110.    open(HEADER_FILE,"> $file") || die "can't open header file: $file\n";
  111.    $oldfh = select HEADER_FILE;
  112.    print "/* This file automatically generated by $0 */\n\n";
  113.    print "#define DB_ID                      '$DB_ID'\n";
  114.    print "#define DB_VERSION                 $DB_VERSION\n\n";
  115.    print "#define GENERAL_CATEGORY           $GENERAL_CATEGORY\n\n";
  116.    print "#define INFO_RECORD_NUMBER         $INFO_RECORD_NUMBER\n";
  117.    print "#define FARES_RECORD_NUMBER        $FARES_RECORD_NUMBER\n";
  118.    print "#define TRANSFER_RECORD_NUMBER     $TRANSFER_RECORD_NUMBER\n";
  119.    print "#define INITIAL_LINE_RECORD_NUMBER $INITIAL_LINE_RECORD_NUMBER\n";
  120.    close(HEADER_FILE);
  121.  
  122.    select $oldfh;
  123. }
  124.  
  125. sub read_stations {
  126.    my ($file,$station_abbrevs_ref,$station_name_ref,$station_number_ref,$pilot_abbrev_ref) = @_;
  127.    my (@station_abbrevs,%station_name,%station_number,%pilot_abbrev);
  128.    my (%station_abbrev);
  129.    local($_);
  130.  
  131.    open(FILE,$file) || die "can't open file $file\n";
  132.    while (defined ($_ = <FILE>)) {
  133.       chomp;
  134.       my ($abbrev,$pabbrev,$name) = split /\t/, $_;
  135.       $station_name{$abbrev} = $name;
  136.       $station_abbrev{$name} = $abbrev;
  137.       $pilot_abbrev{$abbrev} = $pabbrev;
  138.       push @station_abbrevs, $abbrev;
  139.       $station_number{$abbrev} = $#station_abbrevs;
  140.    }
  141.    close(FILE);
  142.  
  143.    @$station_abbrevs_ref = @station_abbrevs;
  144.    %$station_name_ref = %station_name;
  145.    %$pilot_abbrev_ref = %pilot_abbrev;
  146.    %$station_number_ref = %station_number;
  147. }
  148.  
  149. sub read_transfers {
  150.    my $file = shift;
  151.    my (%transfer);
  152.    local($_);
  153.    local($/) = "";
  154.  
  155.    open(FILE,$file) || die "can't open file $file\n";
  156.    while (defined ($_ = <FILE>)) {
  157.       my ($line, @stations) = split /\n/, $_;
  158.       $line =~ s/^\s*(\S+)\s*:\s*$/$1/;
  159.       $transfer{$line} = {};
  160.       my $station_info;
  161.       for $station_info (@stations) {
  162.      my ($station,@lines) = split ' ', $station_info;
  163.      my (@line,@time);
  164.      my $i;
  165.      for $i (@lines) {
  166.         my ($transfer_line,$transfer_time) = ($i =~ /^(\S+?)(?::(\d+))?$/);
  167.         push @line, $transfer_line;
  168.         $transfer_time = 0 unless defined $transfer_time;
  169.         push @time, $transfer_time;
  170.      }
  171.      $transfer{$line}{$station} = {"line" => [@line], "time" => [@time]};
  172.       }
  173.    }
  174.    close(FILE);
  175.  
  176.    %transfer;
  177. }
  178.  
  179. sub read_fares {
  180.    my ($file,$station_abbrevs_ref) = @_;
  181.    my (%station_exists,%fare);
  182.    my (@fare_stations,@fare_fares);
  183.    local($_);
  184.  
  185.    %station_exists = map +($_,1), @$station_abbrevs_ref;
  186.    
  187.    open(FILE,$file) || die "can't open file $file\n";
  188.    while (defined ($_ = <FILE>)) {
  189.       my (@temp,$station);
  190.       chomp;
  191.       @temp = split ' ', $_;
  192.       $station = shift @temp;
  193.       die "bad station name: $station in $file\n"
  194.     unless $station_exists{$station};
  195.       push @fare_stations, $station;
  196.       push @fare_fares, [@temp];
  197.    }
  198.    close(FILE);
  199.  
  200.    my ($i,$j);
  201.  
  202.    for ($i = 0; $i < @fare_stations; $i++) {
  203.       for ($j = 0; $j < @{$fare_fares[$i]}; $j++) {
  204.      $fare{$fare_stations[$i],$fare_stations[$i+$j]} = $fare_fares[$i][$j];
  205.      $fare{$fare_stations[$i+$j],$fare_stations[$i]} = $fare_fares[$i][$j];
  206.       }
  207.    }
  208.  
  209.    %fare;
  210. }
  211.  
  212. sub read_bike_names {
  213.    my ($file) = shift;
  214.    my (%bike_name);
  215.    local($_);
  216.  
  217.    open(FILE,$file) || die "can't open file $file\n";
  218.    while (defined ($_ = <FILE>)) {
  219.       chomp;
  220.       my ($bike_name,$abbrev) = split ' ', $_;
  221.       $bike_name{$bike_name} = $abbrev;
  222.    }
  223.    close(FILE);
  224.  
  225.    %bike_name;
  226. }
  227.  
  228. sub read_station_bike_restrictions {
  229.    my ($file) = shift;
  230.    my ($day);
  231.    my (%restrictions);
  232.    local($_);
  233.  
  234.    for (@days) {
  235.       $restrictions{$_} = {};
  236.    }
  237.    
  238.    open(FILE,$file) || die "can't open file $file\n";
  239.    while (defined ($_ = <FILE>)) {
  240.       chomp;
  241.  
  242.       if (/^\s*(\S+)\s*:\s*$/) {
  243.      $day = $1;
  244.       }
  245.       elsif (!/^\s*\#?\s*$/) {
  246.      die "syntax error in file: $file\n" unless defined $day;
  247.      my ($station,@times) = split ' ', $_;
  248.      $restrictions{$day}->{$station} = [map &time_to_int($_), @times];
  249.       }
  250.    }
  251.    close(FILE);
  252.  
  253.    %restrictions;
  254. }
  255.  
  256. sub ls_dir {
  257.    my $dir = shift;
  258.    my @temp;
  259.    opendir(LS_DIR,$dir);
  260.    @temp = grep !/(\.|\.\.)/, readdir(LS_DIR);
  261.    closedir(LS_DIR);
  262.  
  263.    @temp;
  264. }
  265.  
  266. sub line_cmp {
  267.    my ($a1,$a2) = ($a =~ /^(\w+)-(\w+)$/);
  268.    my ($b1,$b2) = ($b =~ /^(\w+)-(\w+)$/);
  269.    my ($a3,$a4,$b3,$b4) = ($a1,$a2,$b1,$b2);
  270.  
  271.    if ($a1 le $a2) {
  272.       $a3 = $a2; $a4 = $a1;
  273.    }
  274.    if ($b1 le $b2) {
  275.       $b3 = $b2; $b4 = $b1;
  276.    }
  277.    "$a3-$a4" eq "$b3-$b4" ? $a cmp $b : "$a3-$a4" cmp "$b3-$b4";
  278. }
  279.  
  280. # Gets a reference to an ordered list of lines in an associative array
  281. # for each day.  Common lines appear first.  Assumes that weekday lines
  282. # == saturday lines and sunday lines are a subset of both.
  283. sub get_lines {
  284.    my ($weekday_lines,$saturday_lines,$sunday_lines) = @_;
  285.    my (%recorded,@extra);
  286.    local($_);
  287.  
  288.    @$weekday_lines = &ls_dir("$sched_dir/weekday");
  289.    @$saturday_lines = &ls_dir("$sched_dir/saturday");
  290.    @$sunday_lines = &ls_dir("$sched_dir/sunday");
  291.  
  292.    # order lines to get common (sunday) lines first
  293.    @$sunday_lines = sort line_cmp @$sunday_lines;
  294.    %recorded = map +($_,1), @$sunday_lines;
  295.    for (@$weekday_lines) {
  296.       push @extra, $_ unless $recorded{$_};
  297.    }
  298.    @$weekday_lines = (@$sunday_lines, sort line_cmp @extra);
  299.    @$saturday_lines = (@$sunday_lines, sort line_cmp @extra);
  300. }
  301.  
  302. sub time_to_int {
  303.    local($_) = shift;
  304.    my ($int);
  305.  
  306.    if (/^--$/) {
  307.       $int = -1;
  308.    }
  309.    else {
  310.       my ($hour,$min,$ap) = /^(\d\d?):(\d\d)([ap])$/;
  311.       die "don't understand time specification: $_\n" unless defined $ap;
  312.  
  313.       $hour = 0 if $hour == 12;
  314.       $int = ($hour + ($ap eq "a" ? 0 : 12))*60 + $min;
  315.  
  316.       $int += 24*60 if $int < $DAY_END_TIME;
  317.    }
  318.  
  319.    $int;
  320. }
  321.  
  322. # sets the three sched variables to 3d arrays of [line][train][time].  entries
  323. # for train == 0 are station abbrevs
  324. sub get_schedules {
  325.    my ($weekday_lines,$saturday_lines,$sunday_lines,$weekday_scheds,$saturday_scheds,$sunday_scheds,$bike_name) = @_;
  326.    my (@day_line) = ($weekday_lines,$saturday_lines,$sunday_lines);
  327.    my (@day_sched) = ($weekday_scheds,$saturday_scheds,$sunday_scheds);
  328.    my (%recorded,@extra);
  329.    my (%station_idx);
  330.    my ($i,$j);
  331.    local($_);
  332.  
  333.    for ($i = 0; $i < @days; $i++) {
  334.       my ($lines,$scheds,$day) = ($day_line[$i],$day_sched[$i],$days[$i]);
  335.       my ($line);
  336.  
  337.       print STDOUT " $days[$i]:";
  338.       for $line (@$lines) {
  339.      my ($sched) = [];
  340.      print STDOUT " $line";
  341.      open(FILE,"$sched_dir/$day/$line")
  342.        || die "can't open file $sched_dir/$day/$line\n";
  343.      my $header = <FILE>;
  344.      $header =~ s/BIKES NOT ALLOWED BETWEEN//;
  345.      my @stations = split ' ', $header;
  346.      for ($j = 0; $j < @stations; $j++) {
  347.         $station_idx{$stations[$j]} = $j;
  348.      }
  349.      push @$sched, [@stations];
  350.      while (defined ($_ = <FILE>)) {
  351.         my (@fields) = split ' ', $_;
  352.         my (@times) = grep /^(\d?\d:\d\d[ap]|--)$/, @fields;
  353.         my (@nobikesbtw) = grep !/^(\d?\d:\d\d[ap]|--)$/, @fields;
  354.  
  355.         # correct for different station abbrev between bike name and abbrev
  356.         my $station;
  357.         for $station (@nobikesbtw) {
  358.            if (!defined $station_idx{$station} && !exists $$bike_name{$station}) {
  359.           die "I don't know what station $station is. Please add it to $bike_names_file\n";
  360.            }
  361.            else {
  362.           $station = $$bike_name{$station}
  363.             if exists $$bike_name{$station};
  364.            }
  365.            $station = $station_idx{$station};
  366.         }
  367.         
  368.         # add "no stop" indicators for -- left off at end of line
  369.         while (@times < @stations) { push @times, '--'; }
  370.         push @$sched, [map(&time_to_int($_),@times), @nobikesbtw];
  371.      }
  372.      close(FILE);
  373.      push @$scheds, $sched;
  374.       }
  375.       print STDOUT "\n";
  376.    }
  377.    
  378. }
  379.  
  380. sub first_valid_elt {
  381.    my $i;
  382.    for ($i = 0; $i < @_; $i++) {
  383.       return $_[$i]
  384.     if ($_[$i] != -1);
  385.    }
  386. }
  387.  
  388. sub get_inc_vector {
  389.    my ($first_time) = &first_valid_elt(@_);
  390.    local($_);
  391.  
  392.    map +($_ == -1 ? -1 : $_-$first_time), @_;
  393. }
  394.  
  395. sub vector_eq {
  396.    my ($vec1_ref,$vec2_ref) = @_;
  397.    my ($i);
  398.  
  399.    warn "vectors are different lengths!\n"
  400.      unless @$vec1_ref == @$vec2_ref;
  401.    
  402.    for ($i = 0; $i < @$vec1_ref; $i++) {
  403.       return 0 if $$vec1_ref[$i] != $$vec2_ref[$i];
  404.    }
  405.    
  406.    1;
  407. }
  408.  
  409. sub match_vector {
  410.    my ($newvec_ref, @vec_refs) = @_;
  411.    my ($i);
  412.  
  413.    for ($i = 0; $i < @vec_refs; $i++) {
  414.       return $i
  415.     if &vector_eq($newvec_ref,$vec_refs[$i]);
  416.    }
  417.  
  418.    $i;
  419. }
  420.  
  421. sub gen_line_schedule {
  422.    my ($line_sched_ref,$station_number_ref) = @_;
  423.    my (@inc_vectors,@nobikes_pairs);
  424.    my (@train_vector,@time_diff,@train_bikepair);
  425.    my ($vec_ref,$pair_ref);
  426.    my ($buf);
  427.    my ($compress_level) = 2;    # BART Scheduler expects level 2
  428.    my $i;
  429.  
  430.    my $stations = @{$$line_sched_ref[0]};
  431.  
  432.    # only needed for debugging
  433.    my $line = $$line_sched_ref[0][0]." to ".
  434.      $$line_sched_ref[0][$#{$$line_sched_ref[0]}];
  435.    
  436.    # find increment vector for each train; insert into @inc_vectors if unique
  437.    for ($i = 1; $i < @$line_sched_ref; $i++) {
  438.       my @vec = &get_inc_vector(@{$$line_sched_ref[$i]}[0..$stations-1]);
  439.       my @pair;
  440.       my $iv_loc = &match_vector(\@vec,@inc_vectors);
  441.       my $nb_loc = 0xff; # "none" value
  442.       my $time_diff =
  443.     &first_valid_elt(@{$$line_sched_ref[$i]})
  444.       - &first_valid_elt(@{$$line_sched_ref[max($i-1,1)]});
  445.  
  446.       # handle bike restrictions on this train if there are any
  447.       if (@{$$line_sched_ref[$i]} > $stations) {
  448.      @pair = @{$$line_sched_ref[$i]}[$stations,$stations+1];
  449.      $nb_loc = &match_vector(\@pair,@nobikes_pairs);
  450.      push @nobikes_pairs, \@pair
  451.        if $nb_loc > $#nobikes_pairs;
  452.       }
  453.       
  454.       push @inc_vectors, \@vec
  455.     if $iv_loc > $#inc_vectors;
  456.       
  457.       push @train_vector, $iv_loc;
  458.       push @train_bikepair, $nb_loc;
  459.       push @time_diff, $time_diff;
  460.    }
  461.  
  462.    printf STDOUT " %2d trains", scalar(@train_vector);
  463.    printf STDOUT " %2d iv", scalar(@inc_vectors);
  464.    printf STDOUT " %2d bp", scalar(@nobikes_pairs);
  465.    $buf .= pack("C",scalar(@inc_vectors)); # number of increment vectors
  466.    # print STDOUT "$line line: ", scalar(@inc_vectors), " inc. vectors\n";
  467.    for $vec_ref (@inc_vectors) {
  468.       my $elt;
  469.       for $elt (@$vec_ref) {
  470.      $buf .= pack("C",($elt == -1 ? 0xff : $elt)); # -1 becomes 255
  471.       }
  472.       die "vector is wrong size (", scalar(@$vec_ref), " vs. ", $stations,
  473.       ") in line ", $$line_sched_ref[0][0], " to ",
  474.       $$line_sched_ref[0][$#{$$line_sched_ref[0]}], "!\n"
  475.     unless @$vec_ref == $stations;
  476.    }
  477.  
  478.    $buf .= pack("C",scalar(@nobikes_pairs)); # number of bike restriction pairs
  479.    for $pair_ref (@nobikes_pairs) {
  480.       my $elt;
  481.       for $elt (@$pair_ref) {
  482.      $buf .= pack("C",$elt);
  483.       }
  484.    }
  485.  
  486.    # first valid time of first train (alignment of this value not guaranteed!)
  487.    $buf .= pack("n",&first_valid_elt(@{$$line_sched_ref[1]}));
  488.  
  489.    # put the train blocks in a buffer so we can count them and print the
  490.    # number of blocks to the pdb file before the blocks themselves
  491.    my $blocks_buffer = "";
  492.    my $blocks = 0;
  493.  
  494.    if ($compress_level == 0) {
  495.       for ($i = 0; $i < @train_vector; $i++) {
  496.      $blocks_buffer .= pack("N",$$line_sched_ref[$i+1][0]);
  497.      # this row's increment vector
  498.      $blocks_buffer .= pack("C",$train_vector[$i]);
  499.      # we use 0xff to mark no restrictions
  500.      $blocks_buffer .= pack("C",$train_bikepair[$i]);
  501.      $blocks++;
  502.       }
  503.    }
  504.    elsif ($compress_level == 1) {
  505.       for ($i = 0; $i < @train_vector; $i++) {
  506.      # use signed char since trains may not start at the same station
  507.      die "time_diff out of range!\n"
  508.        if $time_diff[$i] < -128 || $time_diff[$i] > 127;
  509.      $blocks_buffer .= pack("c",$time_diff[$i]);
  510.      # this row's increment vector
  511.      $blocks_buffer .= pack("C",$train_vector[$i]);
  512.      # we use 0xff to mark no restrictions
  513.      $blocks_buffer .= pack("C",$train_bikepair[$i]);
  514.      $blocks++;
  515.       }
  516.    }
  517.    elsif ($compress_level == 2) {
  518.       $i = 0;
  519.       while ($i < @time_diff) {
  520.      my $j = $i+1;
  521.      while ($j < @time_diff &&
  522.         $time_diff[$j] == $time_diff[$i] &&
  523.         $train_vector[$j] == $train_vector[$i] &&
  524.             $train_bikepair[$j] == $train_bikepair[$i]) {
  525.         $j++;
  526.      }
  527.      # number of repetitions of same time diff and train vector
  528.      $blocks_buffer .= pack("C",$j-$i);
  529.      # time difference from first valid entry of last row (or zero if this
  530.      # is the first row)
  531.      die "time_diff out of range!\n"
  532.        if $time_diff[$i] < -128 || $time_diff[$i] > 127;
  533.      $blocks_buffer .= pack("c",$time_diff[$i]);
  534.      # this row's increment vector
  535.      $blocks_buffer .= pack("C",$train_vector[$i]);
  536.      # we use 0xff to mark no restrictions
  537.      $blocks_buffer .= pack("C",$train_bikepair[$i]);
  538.      $blocks++;
  539.      $i = $j;
  540.       }
  541.    }
  542.  
  543.    printf STDOUT " %2d blocks", $blocks;
  544.    $buf .= pack("C",$blocks);    # number of train blocks
  545.    $buf .= $blocks_buffer;
  546.  
  547.    $buf;
  548. }
  549.  
  550. sub gen_line_info {
  551.    my ($initial_record_number,$station_number_ref,$lines_ref,$scheds_ref,$restrictions_ref) = @_;
  552.    my (%station_number) = %$station_number_ref;
  553.    my (@lines) = @$lines_ref;
  554.    my (@scheds) = @$scheds_ref;
  555.    my (%restrictions) = %$restrictions_ref;
  556.    my ($line);
  557.    my (@bufs,$buf);
  558.    my (@record_lengths);
  559.  
  560.    $buf .= pack("C",$INFO_RECORD_NUMBER);    # info record for this category
  561.    $buf .= pack("C",scalar(@lines)); # number of lines in this category
  562.    $buf .= pack("C",&sum(map +(scalar @$_)/2, values %restrictions));
  563.    $buf .= pack("x");        # null byte for alignment
  564.  
  565.    my ($key,$i);
  566.    for $key (sort {$station_number{$a} <=> $station_number{$b}} keys %restrictions) {
  567.       my @array = @{$restrictions{$key}};
  568.       for ($i = 0; $i < @array; $i += 2) {
  569.      $buf .= pack("C",$station_number{$key});
  570.      $buf .= pack("x");    # null byte for alignment
  571.      $buf .= pack("nn",@array[$i,$i+1]);
  572.       }
  573.    }
  574.  
  575.    push @bufs, $buf;
  576.    $buf = "";
  577.    
  578.  
  579.    for ($line = 0; $line < @lines; $line++) {
  580.       my $station;
  581.       
  582.       $buf .= pack("C",$initial_record_number+$line); # number of this record
  583.       # number of stations on this line
  584.       $buf .= pack("C",scalar(@{$scheds[$line][0]}));
  585.       
  586.       for $station (@{$scheds[$line][0]}) {
  587.      $buf .= pack("C",$station_number{$station});
  588.       }
  589.       print STDOUT "  line $line ($lines[$line])\t", scalar(@{$scheds[$line][0]}), " stations";
  590.  
  591.       $buf .= &gen_line_schedule($scheds[$line],$station_number_ref);
  592.  
  593.       print STDOUT "\n";
  594.  
  595.       push @bufs, $buf;
  596.       $buf = "";
  597.    }
  598.  
  599.    @bufs;
  600. }
  601.  
  602. # prints station names/abbrev's and category names to a record
  603. sub gen_general_info {
  604.    my ($category_names_ref,$station_abbrevs_ref,$station_name_ref,$pilot_abbrev_ref) = @_;
  605.    my (@category_names) = @$category_names_ref;
  606.    my (@station_abbrevs) = @$station_abbrevs_ref;
  607.    my (%station_name) = %$station_name_ref;
  608.    my (%pilot_abbrev) = %$pilot_abbrev_ref;
  609.    my (%station_abbrev);
  610.    my ($buf);
  611.    local($_);
  612.  
  613.    for (keys %station_name) {
  614.       $station_abbrev{$station_name{$_}} = $_;
  615.    }
  616.  
  617.    $buf .= pack("C",$INFO_RECORD_NUMBER); # number of this record
  618.    $buf .= pack("x3");        # 3 null bytes for alignment
  619.  
  620.    # creation date of the database; displayed in the "About" form
  621.    $buf .= pack("N",&unix_to_pilot_date($CREATION_DATE));
  622.    
  623.    # time at which one day's schedule ends and another's begins.  times are
  624.    # represented as the number of minutes since 12am on the first day of the
  625.    # schedule
  626.    $buf .= pack("n",$DAY_END_TIME);
  627.    $buf .= pack("C",scalar(@category_names)); # number of categories
  628.    for (@category_names) {
  629.       $buf .= pack("a*",$_."\0");
  630.    }
  631.    
  632.    $buf .= pack("C",scalar(keys %station_name)); # number of stations
  633.    # station names
  634.    for (map $station_name{$_}, @station_abbrevs) {
  635.       $buf .= pack("a*",$_."\0");
  636.    }
  637.    # station abbreviations for the pilot
  638.    for (map $pilot_abbrev{$_}, @station_abbrevs) {
  639.       $buf .= pack("a*",$_."\0");
  640.    }
  641.  
  642.    $buf;
  643. }
  644.  
  645. sub gen_fares_info {
  646.    my ($station_abbrevs_ref,$fare_ref) = @_;
  647.    my (@stations) = @$station_abbrevs_ref;
  648.    my (%fare) = %$fare_ref;
  649.    my ($buf);
  650.    my ($i,$j);
  651.    
  652.    $buf .= pack("C",$FARES_RECORD_NUMBER); # number of this record
  653.    $buf .= pack("C",int($FARE_MULTIPLIER*100+.5)); # in cents
  654.    # in multiples of the multiplier
  655.    $buf .= pack("C",int($MIN_FARE/$FARE_MULTIPLIER+.5));
  656.    for ($i = 0; $i < @stations; $i++) {
  657.       for ($j = $i; $j < @stations; $j++) {
  658.      # (fare-$MIN_FARE)/$FARE_MULTIPLIER for a trip in either direction
  659.      # between these two stations
  660.      $buf .= pack("C",int(($fare{$stations[$i],$stations[$j]}-$MIN_FARE)/$FARE_MULTIPLIER+.5));
  661.       }
  662.    }
  663.  
  664.    $buf;
  665. }
  666.  
  667. sub gen_transfer_info {
  668.    my ($transfers_ref,$weekday_lines_ref,$station_number_ref) = @_;
  669.    my (%transfers) = %$transfers_ref;
  670.    my (@weekday_lines) = @$weekday_lines_ref;
  671.    my (%station_number) = %$station_number_ref;
  672.    my (%line_number);
  673.    my ($buf);
  674.    my ($i);
  675.    local($_);
  676.  
  677.    for ($i = 0; $i < @weekday_lines; $i++) {
  678.       $line_number{$weekday_lines[$i]} = $i;
  679.    }
  680.    
  681.    $buf .= pack("C",$TRANSFER_RECORD_NUMBER); # number of this record
  682.    $buf .= pack("C",scalar(keys %transfers)); # number of lines with transfers
  683.  
  684.    for ($i = 0; $i < @weekday_lines; $i++) {
  685.       my $line = $weekday_lines[$i];
  686.       
  687.       if (exists $transfers{$line}) {
  688.      # line number
  689.      $buf .= pack ("C",$i);
  690.      # number of (station, line) transfer pairs on this line
  691.      $buf .= pack ("C",&sum(map scalar(@{$transfers{$line}{$_}{"line"}}), keys %{$transfers{$line}}));
  692.  
  693.      my $station;
  694.      for $station (keys %{$transfers{$line}}) {
  695.         my $j;
  696.         my (%line_info) = %{$transfers{$line}{$station}};
  697.         for ($j = 0; $j < @{$line_info{"line"}}; $j++) {
  698.            # (station, line) pair
  699.            $buf .= pack ("C",$station_number{$station});
  700.            $buf .= pack ("C",$line_number{${$line_info{"line"}}[$j]});
  701.            $buf .= pack ("n",${$line_info{"time"}}[$j]);
  702.         }
  703.      }
  704.       }
  705.    }
  706.  
  707.    $buf;
  708. }
  709.  
  710.  
  711. sub unix_to_pilot_date {
  712.    my ($date) = shift;
  713.    my $pilot_time_delta = 2082844800;
  714.    $date + $pilot_time_delta;
  715. }
  716.  
  717. sub gen_pdb_header {
  718.    my ($name,$version,$db_type,$creator_id,$appinfo_length,$records) = @_;
  719.    my $time = time;
  720.    my $attributes;
  721.    my $appinfo_offset;
  722.    my ($buf);
  723.  
  724.    if ($appinfo_length == 0) {
  725.       $appinfo_offset = 0;
  726.    }
  727.    else {
  728.       $appinfo_offset = $PDB_HEADER_SIZE+$RECORD_LIST_STRUCTURE_SIZE*$records;
  729.    }
  730.  
  731.    $buf .= pack("a32",$name);
  732.    $attributes = 0x0008 |    # backup database
  733.          0x0010;    # OK to install new version of DB over this one
  734.    $buf .= pack("n",$attributes);
  735.    $buf .= pack("n",$version);
  736.    $buf .= pack("N",&unix_to_pilot_date($time)); # creation date
  737.    $buf .= pack("N",&unix_to_pilot_date($time)); # modification date
  738.    $buf .= pack("N",0);        # last backup date
  739.    $buf .= pack("N",0);        # modification number
  740.    $buf .= pack("N",$appinfo_offset); # offset of AppInfoArea (0 == does not exist)
  741.    $buf .= pack("N",0);        # offset of SortInfoArea (0 == does not exist)
  742.    $buf .= pack("A4",$db_type);    # database type
  743.    $buf .= pack("A4",$creator_id); # creator id
  744.    $buf .= pack("N",0);        # unique ID seed (set to zero)
  745.    $buf .= pack("N",0);        # next record list ID (set to zero)
  746.    $buf .= pack("n",$records);    # number of records
  747.  
  748.    $buf;
  749. }
  750.  
  751. sub gen_pdb_record_list {
  752.    my ($offset, $record_sizes_ref, $categories_ref) = @_;
  753.    my (@record_sizes) = @$record_sizes_ref;
  754.    my (@categories) = @$categories_ref;
  755.    my (@record_offsets);
  756.    my $attributes = 0;        # leave all attributes unset
  757.    my ($buf);
  758.    my $i;
  759.  
  760.    $record_offsets[0] = $offset;
  761.    for ($i = 1; $i < @record_sizes; $i++) {
  762.       $record_offsets[$i] = $record_offsets[$i-1] + $record_sizes[$i-1];
  763.    }
  764.  
  765.    for ($i = 0; $i < @record_offsets; $i++) {
  766.       $buf .= pack("N",$record_offsets[$i]); # byte offset of record within file
  767.       $buf .= pack("C",$attributes | $categories[$i]); # record attributes and category value
  768.       $buf .= pack("C3",0,0,0);    # unique ID (set to zero)
  769.    }
  770.    
  771.    # number of bytes written
  772.    $buf;
  773. }
  774.  
  775. # this is for testing purposes only; BART Scheduler does not use the
  776. # appinfo area
  777. sub gen_appinfo_area {
  778.    my (@category_names) = @_;
  779.    my ($appinfo_strings,$appinfo_strlen) = (16,16);
  780.    my ($pad) = 0;
  781.    my ($uniq_id) = 0;
  782.    my ($buf);
  783.    my $i;
  784.  
  785.    my $rename_bits = unpack("S",pack("b16","1" x @category_names));
  786.  
  787.    $buf .= pack("n",$rename_bits); # set a bit for each category name
  788.    for ($i = 0; $i < @category_names; $i++) {
  789.       $buf .= pack("a$appinfo_strlen",$category_names[$i]);
  790.    }
  791.    for (; $i < $appinfo_strings; $i++) {
  792.       $buf .= pack("a$appinfo_strlen","");
  793.    }
  794.    for ($i = 0; $i < $appinfo_strings; $i++) {
  795.       # $buf .= pack("C",($i < @category_names ? $uniq_id++ : 128));
  796.       $buf .= pack("C",$uniq_id++);
  797.    }
  798.    $buf .= pack("C",$uniq_id-1); # highest uniq_id
  799.  
  800.    if ($pad) {
  801.       # pad to 512 bytes (due to bugs mentioned in PDB file format document)
  802.       $buf .= pack("x".(512-(length $buf)));
  803.    }
  804.  
  805.    $buf;
  806. }
  807.  
  808. # parse cmd line args
  809. my $use_appinfo_area = 0;
  810. if (@ARGV && $ARGV[0] =~ /^-a$/) {
  811.    $use_appinfo_area = 1;
  812. }
  813.  
  814. # read data in here
  815. print STDOUT "Reading stations...\n";
  816. &read_stations($stations_file,\@station_abbrevs,\%station_name,\%station_number,\%pilot_abbrev);
  817. print STDOUT "Reading transfers...\n";
  818. %transfers = &read_transfers($transfers_file);
  819. print STDOUT "Reading fares...\n";
  820. %fare = &read_fares($fares_file,\@station_abbrevs);
  821. print STDOUT "Reading bike names...\n";
  822. %bike_name = &read_bike_names($bike_names_file);
  823. print STDOUT "Reading station bike restrictions...\n";
  824. %station_bike_restrictions = &read_station_bike_restrictions($station_bike_restrictions_file);
  825. print STDOUT "Reading line names...\n";
  826. &get_lines(\@weekday_lines,\@saturday_lines,\@sunday_lines);
  827. print STDOUT "Reading schedules...\n";
  828. &get_schedules(\@weekday_lines,\@saturday_lines,\@sunday_lines,
  829.            \@weekday_scheds,\@saturday_scheds,\@sunday_scheds,
  830.            \%bike_name);
  831.  
  832.  
  833. my ($data_area_buf, @new_records);
  834. my ($appinfo_length) = 0;
  835. my (@record_lengths,@categories);
  836. my $records = 0;
  837. my (@lengths);
  838.  
  839. print STDOUT "Generating pdb file...\n";
  840. if ($use_appinfo_area) {
  841.    print STDOUT " appinfo\n";
  842.    $data_area_buf .= &gen_appinfo_area(@CATEGORY_NAMES);
  843. }
  844.  
  845. print STDOUT " general info\n";
  846. @new_records = &gen_general_info(\@CATEGORY_NAMES,\@station_abbrevs,
  847.                    \%station_name,\%pilot_abbrev);
  848. $data_area_buf .= join '', @new_records;
  849. push @record_lengths, map length, @new_records;
  850. push @categories, ($GENERAL_CATEGORY) x @new_records;
  851. $records += @new_records;
  852.  
  853. print STDOUT " fares\n";
  854. @new_records = &gen_fares_info(\@station_abbrevs,\%fare);
  855. $data_area_buf .= join '', @new_records;
  856. push @record_lengths, map length, @new_records;
  857. push @categories, ($GENERAL_CATEGORY) x @new_records;
  858. $records += @new_records;
  859.  
  860. print STDOUT " transfers\n";
  861. @new_records = &gen_transfer_info(\%transfers,\@weekday_lines,
  862.                     \%station_number);
  863. $data_area_buf .= join '', @new_records;
  864. push @record_lengths, map length, @new_records;
  865. push @categories, ($GENERAL_CATEGORY) x @new_records;
  866. $records += @new_records;
  867.  
  868. print STDOUT " weekday schedules\n";
  869. @new_records = &gen_line_info($INITIAL_LINE_RECORD_NUMBER,\%station_number,
  870.                 \@weekday_lines,\@weekday_scheds,
  871.                 $station_bike_restrictions{$days[0]});
  872. $data_area_buf .= join '', @new_records;
  873. push @record_lengths, map length, @new_records;
  874. push @categories, ($WEEKDAY_CATEGORY) x @new_records;
  875. $records += @new_records;
  876.  
  877. print STDOUT " saturday schedules\n";
  878. @new_records = &gen_line_info($INITIAL_LINE_RECORD_NUMBER,\%station_number,
  879.                 \@saturday_lines,\@saturday_scheds,
  880.                 $station_bike_restrictions{$days[1]});
  881. $data_area_buf .= join '', @new_records;
  882. push @record_lengths, map length, @new_records;
  883. push @categories, ($SATURDAY_CATEGORY) x @new_records;
  884. $records += @new_records;
  885.  
  886. print STDOUT " sunday schedules\n";
  887. @new_records = &gen_line_info($INITIAL_LINE_RECORD_NUMBER,\%station_number,
  888.                 \@sunday_lines,\@sunday_scheds,
  889.                 $station_bike_restrictions{$days[2]});
  890. $data_area_buf .= join '', @new_records;
  891. push @record_lengths, map length, @new_records;
  892. push @categories, ($SUNDAY_CATEGORY) x @new_records;
  893. $records += @new_records;
  894.  
  895.  
  896. my ($pdb);
  897.  
  898. print STDOUT " header\n";
  899. $pdb .= &gen_pdb_header($DB_NAME,$DB_VERSION,$DB_ID,$CREATOR_ID,
  900.               $appinfo_length,$records);
  901. print STDOUT " record list\n";
  902. # arguments are: offset (== start of records), record lengths, categories.
  903. # +2 and following pack are for filler to ensure 4-byte alignment (seems
  904. # to be necessary on Macs)
  905. $pdb .= &gen_pdb_record_list($PDB_HEADER_SIZE+$RECORD_LIST_STRUCTURE_SIZE*$records+$appinfo_length+2,\@record_lengths,\@categories);
  906. $pdb .= pack("xx");
  907.  
  908. $pdb .= $data_area_buf;
  909.  
  910. open(PDB_FILE,"> $pdb_file")
  911.   || die "can't open file $pdb_file for writing!\n";
  912. print PDB_FILE $pdb;
  913. close(PDB_FILE);
  914.  
  915. &print_header_file($header_file);
  916.  
  917. exit 0;
  918.