atco 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.mdown +27 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/lib/atco.rb +180 -0
- data/lib/location.rb +23 -0
- data/spec/atco_spec.rb +150 -0
- data/spec/spec_helper.rb +2 -0
- metadata +67 -0
data/README.mdown
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
## ATCO-CIF
|
2
|
+
|
3
|
+
ATCO-CIF is the format of choice for UK public transport authorities. This is a ruby library that reads **.cif** files and gives you JSON back.
|
4
|
+
|
5
|
+
* **Official spec:** [http://www.pti.org.uk/CIF/atco-cif-spec.pdf](http://www.pti.org.uk/CIF/atco-cif-spec.pdf)
|
6
|
+
|
7
|
+
### USAGE
|
8
|
+
|
9
|
+
Currently this library is under-development and has several things left to do before it is perfect (see the [todo.mdown](http://github.com/davidjrice/atco/blob/master/todo.mdown) list ).
|
10
|
+
|
11
|
+
* clone this library
|
12
|
+
* start an irb session
|
13
|
+
* put the cif file in ./data (needs to change from being hardcoded)
|
14
|
+
|
15
|
+
Code example, for more detailed internal api usage see the spec files.
|
16
|
+
|
17
|
+
require 'lib/atco'
|
18
|
+
|
19
|
+
result = Atco.parse('filename.cif')
|
20
|
+
result = Atco.parse('SVRTMAO009A-20091005.cif) # an example data file
|
21
|
+
|
22
|
+
=> {
|
23
|
+
:header => {...},
|
24
|
+
:locations => [...],
|
25
|
+
:journies => {...}
|
26
|
+
}
|
27
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |s|
|
4
|
+
s.name = "atco"
|
5
|
+
s.summary = "Simple and opinionated library for parsing ATCO-CIF files with Ruby."
|
6
|
+
s.email = "me@davidjrice.co.uk"
|
7
|
+
s.homepage = "http://github.com/davidjrice/atco"
|
8
|
+
s.description = "Simple and opinionated library for parsing ATCO-CIF files with Ruby."
|
9
|
+
s.authors = ["David Rice"]
|
10
|
+
s.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
|
11
|
+
end
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
|
14
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/atco.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'open3'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'location'
|
7
|
+
|
8
|
+
module Atco
|
9
|
+
VERSION = '0.0.1'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
@path = nil
|
14
|
+
@@methods = {
|
15
|
+
:bank_holiday => 'QH',
|
16
|
+
:operator => 'QP',
|
17
|
+
:additional_location_info => 'QB',
|
18
|
+
:location => 'QL',
|
19
|
+
:destination => 'QT',
|
20
|
+
:intermediate => 'QI',
|
21
|
+
:origin => 'QO',
|
22
|
+
:journey_header => 'QS'
|
23
|
+
}
|
24
|
+
|
25
|
+
def parse(file)
|
26
|
+
@path = File.expand_path(file)
|
27
|
+
data = File.readlines(@path)
|
28
|
+
|
29
|
+
objects = []
|
30
|
+
current_journey = nil
|
31
|
+
current_location = nil
|
32
|
+
locations = []
|
33
|
+
journeys = {}
|
34
|
+
header = nil
|
35
|
+
|
36
|
+
data.each do |line|
|
37
|
+
if line == data.first
|
38
|
+
header = parse_header(line)
|
39
|
+
next
|
40
|
+
end
|
41
|
+
@@methods.each do |method,identifier|
|
42
|
+
object = self.send("parse_#{method}", line)
|
43
|
+
if object[:record_identity] && object[:record_identity] == identifier
|
44
|
+
current_journey = object if object[:record_identity] && object[:record_identity] == @@methods[:journey_header]
|
45
|
+
if object[:record_identity] && ( object[:record_identity] == @@methods[:location] || object[:record_identity] == @@methods[:additional_location_info] )
|
46
|
+
if object[:record_identity] == @@methods[:location]
|
47
|
+
current_location = object
|
48
|
+
else
|
49
|
+
locations << Location.new(current_location, object)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if current_journey
|
54
|
+
if journeys[current_journey[:unique_journey_identifier]]
|
55
|
+
journeys[current_journey[:unique_journey_identifier]] << object
|
56
|
+
else
|
57
|
+
journeys[current_journey[:unique_journey_identifier]] = [object]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
objects << object
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return {:header => header, :locations => locations, :journeys => journeys}
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_header(string)
|
68
|
+
{
|
69
|
+
:file_type => string[0,8],
|
70
|
+
:version => "#{string[8,2].to_i}.#{string[10,2].to_i}",
|
71
|
+
:file_originator => string[12,32].strip!,
|
72
|
+
:source_product => string[44,16].strip!,
|
73
|
+
:production_datetime => string[60,14]
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_bank_holiday(string)
|
78
|
+
{
|
79
|
+
:record_identity => string[0,2],
|
80
|
+
:transaction_type => string[2,1],
|
81
|
+
:date_of_bank_holiday => string[3,8]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_operator(string)
|
86
|
+
{
|
87
|
+
:record_identity => string[0,2],
|
88
|
+
:transaction_type => string[2,1],
|
89
|
+
:operator => parse_value(string[3,4]),
|
90
|
+
:operator_short_form => parse_value(string[7,24]),
|
91
|
+
:operator_legal_name => parse_value(string[31,48])
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_additional_location_info(string)
|
96
|
+
{
|
97
|
+
:record_identity => string[0,2],
|
98
|
+
:transaction_type => string[2,1],
|
99
|
+
:location => string[3,12].strip,
|
100
|
+
:grid_reference_easting => parse_value(string[15,8]),
|
101
|
+
:grid_reference_northing => parse_value(string[23,8])
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_location(string)
|
106
|
+
{
|
107
|
+
:record_identity => string[0,2],
|
108
|
+
:transaction_type => string[2,1],
|
109
|
+
:location => parse_value(string[3,12]),
|
110
|
+
:full_location => parse_value(string[15,48]),
|
111
|
+
:gazetteer_code => string[63,1]
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_destination(string)
|
116
|
+
{
|
117
|
+
:record_identity => string[0,2],
|
118
|
+
:location => string[2,12],
|
119
|
+
:published_arrival_time => string[14,4],
|
120
|
+
:bay_number => parse_value(string[18,3]),
|
121
|
+
:timing_point_indicator => string[21,2],
|
122
|
+
:fare_stage_indicator => string[23,2]
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def parse_intermediate(string)
|
127
|
+
{
|
128
|
+
:record_identity => string[0,2],
|
129
|
+
:location => string[2,12],
|
130
|
+
:published_arrival_time => string[14,4],
|
131
|
+
:published_departure_time => string[18,4],
|
132
|
+
:activity_flag => string[22,1],
|
133
|
+
:bay_number => parse_value(string[23,3]),
|
134
|
+
:timing_point_indicator => string[26,2],
|
135
|
+
:fare_stage_indicator => string[28,2]
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
def parse_origin(string)
|
140
|
+
{
|
141
|
+
:record_identity => string[0,2],
|
142
|
+
:location => string[2,12],
|
143
|
+
:published_departure_time => string[14,4],
|
144
|
+
:bay_number => parse_value(string[18,3]),
|
145
|
+
:timing_point_indicator => string[21,2],
|
146
|
+
:fare_stage_indicator => string[23,2]
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse_journey_header(string)
|
151
|
+
{
|
152
|
+
:record_identity => string[0,2],
|
153
|
+
:transaction_type => string[2,1],
|
154
|
+
:operator => string[3,4].strip,
|
155
|
+
:unique_journey_identifier => string[7,6],
|
156
|
+
:first_date_of_operation => parse_value(string[13,8]),
|
157
|
+
:last_date_of_operation => parse_value(string[21,8]),
|
158
|
+
:operates_on_mondays => string[29,1],
|
159
|
+
:operates_on_tuesdays => string[30,1],
|
160
|
+
:operates_on_wednesdays => string[31,1],
|
161
|
+
:operates_on_thursdays => string[32,1],
|
162
|
+
:operates_on_fridays => string[33,1],
|
163
|
+
:operates_on_saturdays => string[34,1],
|
164
|
+
:operates_on_sundays => string[35,1],
|
165
|
+
:school_term_time => parse_value(string[36,1]),
|
166
|
+
:bank_holidays => parse_value(string[37,1]),
|
167
|
+
:route_number => parse_value(string[38,4]),
|
168
|
+
:running_board => parse_value(string[42,6]),
|
169
|
+
:vehicle_type => parse_value(string[48,8]),
|
170
|
+
:registration_number => parse_value(string[56,8]),
|
171
|
+
:route_direction => string[64,1]
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse_value(value)
|
176
|
+
return value.strip if value
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
data/lib/location.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Location
|
2
|
+
|
3
|
+
attr_accessor :name, :identifier, :easting, :northing, :gazeteer_code
|
4
|
+
|
5
|
+
def initialize(location_header, additional_location_information)
|
6
|
+
@name = location_header[:full_location]
|
7
|
+
@identifier = location_header[:record_identity]
|
8
|
+
@easting = additional_location_information[:grid_reference_easting]
|
9
|
+
@northing = additional_location_information[:grid_reference_northing]
|
10
|
+
@gazeteer_code = location_header[:gazetteer_code]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_json(*a)
|
14
|
+
{
|
15
|
+
:name => @name,
|
16
|
+
:identifier => @identifier,
|
17
|
+
:easting => @easting,
|
18
|
+
:northing => @northing,
|
19
|
+
:gazeteer_code => @gazeteer_code
|
20
|
+
}.to_json(*a)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/atco_spec.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe Atco do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should output file for debugging!" do
|
11
|
+
result = Atco.parse('spec/fixtures/example.cif')
|
12
|
+
File.open('test.output', 'w+') do |f|
|
13
|
+
f.flush
|
14
|
+
f.write(JSON.pretty_generate(result))
|
15
|
+
end
|
16
|
+
#fixture = JSON.parse(File.read('spec/fixtures/example.json'))
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should parse header from fixture" do
|
20
|
+
result = Atco.parse('spec/fixtures/example.cif')
|
21
|
+
result[:header].should == {
|
22
|
+
:file_type => "ATCO-CIF",
|
23
|
+
:file_originator => "Electronic Registration",
|
24
|
+
:source_product => "MIA 4.20.18",
|
25
|
+
:version => "5.0",
|
26
|
+
:production_datetime => "20090915113809"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should parse locations from fixture" do
|
31
|
+
result = Atco.parse('spec/fixtures/example.cif')
|
32
|
+
result[:header].should == {
|
33
|
+
:file_type => "ATCO-CIF",
|
34
|
+
:file_originator => "Electronic Registration",
|
35
|
+
:source_product => "MIA 4.20.18",
|
36
|
+
:version => "5.0",
|
37
|
+
:production_datetime => "20090915113809"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should parse header" do
|
42
|
+
Atco.parse_header("ATCO-CIF0500Electronic Registration MIA 4.20.18 20090915113809\r\n").should == {
|
43
|
+
:file_type => 'ATCO-CIF',
|
44
|
+
:version => '5.0',
|
45
|
+
:file_originator => 'Electronic Registration',
|
46
|
+
:source_product => 'MIA 4.20.18',
|
47
|
+
:production_datetime => '20090915113809'
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should parse bank holiday" do
|
52
|
+
Atco.parse_bank_holiday("QHN20061225").should == {
|
53
|
+
:record_identity => 'QH',
|
54
|
+
:transaction_type => 'N',
|
55
|
+
:date_of_bank_holiday => '20061225'
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should parse operator" do
|
60
|
+
Atco.parse_operator("QPNTM Translink Metro Translink Metro \r\n").should == {
|
61
|
+
:record_identity => 'QP',
|
62
|
+
:transaction_type => 'N',
|
63
|
+
:operator => 'TM',
|
64
|
+
:operator_short_form => 'Translink Metro',
|
65
|
+
:operator_legal_name => 'Translink Metro'
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should parse additional location information" do
|
70
|
+
Atco.parse_additional_location_info("QBN700000001252 328622 367433 \r\n").should == {
|
71
|
+
:record_identity => 'QB',
|
72
|
+
:transaction_type => 'N',
|
73
|
+
:location => '700000001252',
|
74
|
+
:grid_reference_easting => '328622',
|
75
|
+
:grid_reference_northing => '367433'
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should parse location" do
|
80
|
+
Atco.parse_location("QLN700000001252Conway (River Rd) 1\r\n").should == {
|
81
|
+
:record_identity => 'QL',
|
82
|
+
:transaction_type => 'N',
|
83
|
+
:location => '700000001252',
|
84
|
+
:full_location => 'Conway (River Rd)',
|
85
|
+
:gazetteer_code => '1'
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
# QT7000000012520605 T1F0
|
90
|
+
it "should parse destination" do
|
91
|
+
Atco.parse_destination("QT7000000012520605 T1F0\r\n").should == {
|
92
|
+
:record_identity => 'QT',
|
93
|
+
:location => '700000001252',
|
94
|
+
:published_arrival_time => '0605',
|
95
|
+
:bay_number => "",
|
96
|
+
:timing_point_indicator => 'T1',
|
97
|
+
:fare_stage_indicator => 'F0'
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should parse intermediate" do
|
102
|
+
Atco.parse_intermediate("QI70000000125607120712B T1F0\r\n").should == {
|
103
|
+
:record_identity => 'QI',
|
104
|
+
:location => '700000001256',
|
105
|
+
:published_arrival_time => '0712',
|
106
|
+
:published_departure_time => '0712',
|
107
|
+
:activity_flag => "B",
|
108
|
+
:bay_number => "",
|
109
|
+
:timing_point_indicator => 'T1',
|
110
|
+
:fare_stage_indicator => 'F0'
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should parse origin" do
|
115
|
+
Atco.parse_origin("QO7000000012520730 T1F0\r\n").should == {
|
116
|
+
:record_identity => 'QO',
|
117
|
+
:location => '700000001252',
|
118
|
+
:published_departure_time => '0730',
|
119
|
+
:bay_number => "",
|
120
|
+
:timing_point_indicator => 'T1',
|
121
|
+
:fare_stage_indicator => 'F0'
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should parse journey header" do
|
126
|
+
Atco.parse_journey_header("QSNTM 13986520091005 1111100 9A 9018 0 I\r\n").should == {
|
127
|
+
:record_identity => 'QS',
|
128
|
+
:transaction_type => 'N',
|
129
|
+
:operator => 'TM',
|
130
|
+
:unique_journey_identifier => '139865',
|
131
|
+
:first_date_of_operation => '20091005',
|
132
|
+
:last_date_of_operation => '',
|
133
|
+
:operates_on_mondays => '1',
|
134
|
+
:operates_on_tuesdays => '1',
|
135
|
+
:operates_on_wednesdays => '1',
|
136
|
+
:operates_on_thursdays => '1',
|
137
|
+
:operates_on_fridays => '1',
|
138
|
+
:operates_on_saturdays => '0',
|
139
|
+
:operates_on_sundays => '0',
|
140
|
+
:school_term_time => '',
|
141
|
+
:bank_holidays => '',
|
142
|
+
:route_number => '9A',
|
143
|
+
:running_board => '9018',
|
144
|
+
:vehicle_type => '0',
|
145
|
+
:registration_number => '',
|
146
|
+
:route_direction => 'I'
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: atco
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- David Rice
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-02-22 00:00:00 +00:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Simple and opinionated library for parsing ATCO-CIF files with Ruby.
|
22
|
+
email: me@davidjrice.co.uk
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.mdown
|
29
|
+
files:
|
30
|
+
- README.mdown
|
31
|
+
- Rakefile
|
32
|
+
- VERSION
|
33
|
+
- lib/atco.rb
|
34
|
+
- lib/location.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/davidjrice/atco
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --charset=UTF-8
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
segments:
|
49
|
+
- 0
|
50
|
+
version: "0"
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.6
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Simple and opinionated library for parsing ATCO-CIF files with Ruby.
|
65
|
+
test_files:
|
66
|
+
- spec/atco_spec.rb
|
67
|
+
- spec/spec_helper.rb
|