atco 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|