|
#!/usr/bin/ruby -w |
|
# coding: UTF-8 |
|
|
|
=begin |
|
|
|
API TISSEO pour le langage Ruby |
|
auteur : Marc Quinton / décembre 2015 ; licence MIT |
|
version : 0.3 ; toute contribution est bienvenue. |
|
url:https://gist.github.com/mqu/cccbba03a4c1a4119176 |
|
key-words: toulouse, tisseo, ruby, rest |
|
|
|
Tisséo est la marque commerciale du réseau de transports en commun de Toulouse et sa région. |
|
|
|
Tisséo publie sur un serveur dédié (*1) des données permettant de réaliser |
|
des applications liées aux transports en commun. Ces données sont publiées sous forme |
|
de web-services et sont interrogeables en s'appuyant sur le format REST (*2). |
|
|
|
L'API est abondament décrite sur le site (*3). |
|
|
|
Cette initiative s'incrit dans le mouvement Open Data pour laquelle, la communauté urbaine |
|
de Toulouse est partie prenante (*4). |
|
|
|
L'API Tisséo possède les caractéristiques suivantes : |
|
- l'API est versionnée ; la version en cours au 11/2015 est la v1 |
|
- les requetes se font généralement avec un GET, via le protocole REST, |
|
- afin d'interroger le serveur, vous devrez demander une clé d'accès (*5). Elle se présente sous |
|
cette forme : 'xxxxxxxx-yyyy-zzzz-kkkk-vvvvvvvvvvvv' ; cette clé vous sera confiée à titre |
|
privé. Vous prendrez soin de lire le chapitre 2.2 et suivant, concernant les conditions |
|
d'utilisation de l'API, |
|
- l'API délivre des données statiques (lignes, arrets, horaires de passage des bus) et des |
|
données temps-réel : horaire estimé d'arrivée des bus. |
|
|
|
- En plus de l’API temps réel, Tisséo fourni des fichiers descriptifs de son offre transport sur le portail |
|
OpenData de Toulouse Métropole. Ces fichiers sont disponibles aux formats standard Trident et GTFS sous |
|
la même licence ODBL (voir paragraphe 2.4 de l'API). |
|
|
|
Voici les URL pour récupérer ces fichiers : |
|
Format GTFS : https://data.toulouse-metropole.fr/explore/dataset/tisseo-gtfs/?tab=metas |
|
Format Trident : https://data.toulouse-metropole.fr/explore/dataset/tisseo-offre-de-transport-neptune/?tab=table |
|
|
|
spécifications format GTFS : https://developers.google.com/transit/gtfs/ |
|
spécifications format Trident : http://www.predim.org/spip.php?article1087 |
|
spécification du format Neptune : http://www.normes-donnees-tc.org/category/neptune/ |
|
|
|
La classe Ruby Tisseo ci-dessous permet d'accéder à l'API Tisseo. |
|
|
|
require 'pp' |
|
require 'json' |
|
require 'rest-client' |
|
require 'tisseo.rb' |
|
|
|
key='xxxxxxxx-yyyy-zzzz-kkkk-vvvvvvvvvvvv' |
|
tisseo = Tisseo.new key |
|
|
|
cmd='stop_points' |
|
params = { |
|
:stopAreaId => 1970324837184808, |
|
:displayLines => 1, |
|
:LineId =>11821949021891652 |
|
} |
|
|
|
pp tisseo.get cmd, params |
|
|
|
|
|
|
|
Configuration : |
|
|
|
vous pourrez placer un fichier de configuration dans votre HOME de cette forme : |
|
|
|
#!/usr/bin/ruby -w |
|
# coding: UTF-8 |
|
# in $HOME/.config/tisseo.rb |
|
|
|
class Tisseo |
|
def self.config |
|
return { |
|
:key => 'xxxxxxxx-yyyy-zzzz-kkkk-vvvvvvvvvvvv' |
|
} |
|
end |
|
end |
|
|
|
|
|
lien : |
|
|
|
1 : https://api.tisseo.fr/ |
|
2 : https://fr.wikipedia.org/wiki/Representational_State_Transfer |
|
3 : https://data.toulouse-metropole.fr/explore/dataset/api-temps-reel-tisseo/?tab=table |
|
4 : https://data.toulouse-metropole.fr/explore/dataset/api-temps-reel-tisseo/ |
|
5 : contact[arobase]tisseo.fr |
|
|
|
autres liens : |
|
|
|
- http://www.gtfs-data-exchange.com/agency/tisso/ |
|
- plan interractif : http://www.tisseo.fr/plan-interactif/ |
|
- https://github.com/cyounes/cyounes.github.io/tree/master/projects/tisseo-api.net |
|
- Chouette : http://www.chouette.mobi/pourquoi-chouette/ ; https://github.com/afimb/chouette2 |
|
|
|
=end |
|
|
|
require 'pp' |
|
require 'json' |
|
require 'rest-client' |
|
|
|
class Hash |
|
#pass single or array of keys, which will be removed, returning the remaining hash |
|
def remove!(*keys) |
|
keys.each{|key| self.delete(key) } |
|
self |
|
end |
|
|
|
#non-destructive version |
|
def remove(*keys) |
|
self.dup.remove!(*keys) |
|
end |
|
end |
|
|
|
# REST client based on RestClient class |
|
class Rest |
|
def get url, args |
|
# printf("Rest::get(%s)\n", url) ; pp args |
|
RestClient.get url, {:params => args} |
|
end |
|
end |
|
|
|
# file cache for REST requests |
|
class RestCache < Rest |
|
|
|
def initialize |
|
@opts={ :cache => {}} |
|
@opts[:cache][:keep]=24*2 # how long to keep files in cache (in hours) |
|
end |
|
|
|
# compute an MD5sum hash from both url and args. |
|
def md5 url, args |
|
md5 = Digest::MD5.new |
|
md5 << url |
|
md5 << args.to_json |
|
md5.hexdigest |
|
end |
|
|
|
# filename is in $HOME/.cache/tisseo |
|
# and name is based on a MD5 hash on url+args |
|
def file url, args |
|
sprintf('%s/.cache/tiseo/%s', ENV['HOME'], md5(url, args)) |
|
end |
|
|
|
# age in hours for file. |
|
def file_age path |
|
(Time.now - File.stat(path).mtime).to_i / 60.0 |
|
end |
|
|
|
# add args[:cache]=false to avoid cache functionality. |
|
def get url, args |
|
# bypass cache if requested |
|
if args.key? :cache # && args[:cache] == false |
|
args.remove! :cache |
|
return super url, args |
|
end |
|
|
|
# what is file name ($HOME/.config/tisseo/<hash>) |
|
file=self.file url, args |
|
if File.exist?(file) && (self.file_age(file)<@opts[:cache][:keep]) |
|
IO.read file |
|
else |
|
# create cache dir if it does not exists |
|
dir=File.dirname(file) |
|
Dir.mkdir dir unless File.directory? dir |
|
|
|
# get page content and cache it to file. |
|
content = super url, args |
|
IO.write file, content |
|
return content |
|
end |
|
end |
|
|
|
end |
|
|
|
class TisseoRest < RestCache |
|
|
|
def initialize key, api='https://api.tisseo.fr', version='v1' |
|
|
|
super() |
|
|
|
@key=key |
|
@api=api |
|
@version=version |
|
@opts[:lang] = :en |
|
@opts[:network] = 'Tisséo' # default network |
|
|
|
@supported_commands = [ |
|
:stop_areas, # 4.2 - p14 |
|
:stop_points, # 4.3 - p16 |
|
:places, # 4.4 - p18 |
|
:networks, # 4.5 - p22 - réseau supportés ; actuellement uniquement Tisséo |
|
:lines, # 4.6 - p23 |
|
:stops_schedules, # 4.7 - p26 |
|
:rolling_stocks, # 4.8 - p30 |
|
:journeys, # 4.9 - p31 |
|
:messages, # 4.10 - p36 - message de service |
|
:service_density # 4.11 - p38 |
|
] |
|
end |
|
|
|
def opts |
|
@opts |
|
end |
|
|
|
def get cmd, args={}, format='json' |
|
|
|
raise "TISSEO unsupported command '#{cmd.to_s}'" unless @supported_commands.include? cmd.to_sym |
|
|
|
args[:key]=@key |
|
args[:lang]=@opts[:lang] |
|
|
|
url="#{@api}/#{@version}/#{cmd.to_s}.#{format}" |
|
res = super url, args |
|
JSON::parse res |
|
end |
|
end |
|
|
|
|
|
|
|
class Tisseo |
|
|
|
def initialize rest |
|
@rest=rest |
|
end |
|
|
|
# cache opt allow to bypass cache functionality |
|
def get cmd, args={}, format='json' |
|
@rest.get cmd, args, format |
|
end |
|
|
|
end |
|
|
|
|
|
# API Tisseo au format Restful |
|
# - le premier mot clé correspond à un commande |
|
# - les mots clés suivants permettent le filtrage, généralement, dans un sous-ensemble |
|
# ex : |
|
# |
|
# rest = rest = TisseoRest.new ... |
|
# tisseo=TisseoRestful.new rest |
|
# |
|
# tisseo.get '/networks' -> liste tous les réseaux |
|
# tisseo.get '/networks/tisseo -> selectionne le réseau Tisseo |
|
# |
|
# tisseo.get '/places/<expr>' -> tous les lieux liés exp |
|
# tisseo.get '/places/city/<expr>' -> filtre uniquement les villes (city) |
|
# tisseo.get '/places/road/<expr>' -> filtre uniquement les noms de rues |
|
# |
|
# tisseo.get '/lines' -> toutes les lignes (TISSEO) |
|
# tisseo.get '/line/57' -> la ligne 57 |
|
# tisseo.get '/line/57/mounede' -> l'arret Mounede sur la ligne 57 |
|
# tisseo.get '/line/57/mounede/next' -> prochains passages sur l'arret mounede / ligne 57 |
|
# |
|
class TisseoRestful < Tisseo |
|
|
|
=begin |
|
|
|
API XPATH like |
|
|
|
/networks |
|
/networks/tisseo |
|
/networks/<regexp> |
|
|
|
/messages |
|
/messages/level/<level> # filtrage par niveau d'importance (normal, important) |
|
/messages/scope/[line|event|global] # filtrage par niveau d'importance |
|
/message/type/[traffic|...] |
|
|
|
/message/line/<regexp> # selectionne les messages attachés à une ligne |
|
|
|
/message/name/<expr> |
|
/message/title/<expr> |
|
/message/message/<expr> |
|
/message/id/<id> |
|
|
|
|
|
/places/<name> |
|
/places/<name>/stop |
|
/places/<name>/road |
|
/places/<name>/public_place |
|
/places/city/<name> #filtrage par ville |
|
/place/type/<name> # filtrage par type church, mail, administration ... |
|
|
|
/lines # liste toutes les lignes |
|
/line/57 # liste (les stop-points) de la ligne 57 |
|
/line/57/rose # liste les points d'arrets ou stop_area, de point Rose / 57 |
|
/line/57/rose/1 # selectionne le premier de la liste des arrets. |
|
/line/57/rose/next # prochains passages à l'arret 57/roses |
|
/line/id/xxxx # filtrage par l'id de ligne |
|
|
|
=end |
|
|
|
def get arg |
|
|
|
case arg |
|
|
|
when /\/networks\/tiss[ée]o$/i |
|
super(:networks)['networks'].select{ |v| pp v['name']=~/tiss[ée]o/i} |
|
|
|
when /\/networks\/(.+)$/ |
|
super(:networks)['networks'].select{ |v| v['name']=~/$1/i} |
|
|
|
when /\/networks\/*$/ |
|
super(:networks) |
|
|
|
when /\/messages\/*$/ |
|
super :messages |
|
|
|
when /\/messages\/level\/(.*)/ # normal, important |
|
super(:messages)['messages'].select{|v| v['message']['importanceLevel']==$1} |
|
|
|
when /\/messages\/scope\/(.*)/ # global, event, line |
|
super(:messages)['messages'].select{|v| v['message']['scope']==$1} |
|
|
|
# KO |
|
when /\/messages\/name\/(.*)/ # traffic, ... |
|
expr=$1 |
|
super(:messages)['messages'].select{|v| v['message'].has_key?('name') && v['message']['name'].match(expr)} |
|
|
|
# KO |
|
when /\/messages\/type\/(.*)/ # traffic, ... |
|
expr=$1 |
|
super(:messages)['messages'].select{|v| v['message'].has_key?('type') && v['message']['type'].match(expr)} |
|
|
|
when /\/lines\/*$/ |
|
super(:lines) |
|
|
|
when /\/line\/id\/(.+)$/ |
|
super :lines, {:lineId => $1} |
|
|
|
# KO |
|
when /\/line\/name\/(.+)$/ |
|
super(:lines)['lines']['line'].select{|v| v.has_key?('name') && v['name'].match($1)} |
|
|
|
# /line/<number>/<name>/<number> - selectionne <number> dans la liste (1=premier) |
|
when /\/line\/(\d+)\/(.*)\/(\d+)$/ |
|
self.get("/line/#{$1}/#{$2}")[$3.to_i-1] |
|
|
|
# /line/<number>/<name>/next - prochains passages |
|
when /\/line\/(\d+)\/(.*)\/next$/ |
|
res=[] |
|
stops=self.xpath("/line/#{$1}/#{$2}") |
|
stops.each do |stop| |
|
res << super(:stops_schedules, { :stopPointId => stop['id'], :cache => false}) |
|
end |
|
return res |
|
|
|
# /line/<number> - filtre par le n° de ligne - recupère l'enregistrement |
|
when /\/line\/(\d+)$/ |
|
super(:lines, { :shortName => $1})['lines']['line'] |
|
|
|
# /line/<number>/ - filtre par le n° de ligne - liste TOUS les points d'arrets |
|
when /\/line\/(\d+)\/$/ |
|
line=self.xpath("/line/#{$1}") |
|
super(:stop_points, { :lineId => line[0]['id']})['physicalStops']['physicalStop'] |
|
|
|
# /line/<number>/<name> - non sensitive case search. |
|
when /\/line\/(\d+)\/(.*)$/ |
|
line=$1 |
|
name=expr=$2.downcase |
|
self.get("/line/#{line}/").select{|v| v.has_key?('name') && v['name'].downcase.include?(name) } |
|
|
|
# /place/city/<expr> - recherche une ville (city) |
|
when /\/places\/city\/(.*)\/*$/ |
|
super(:places, { :term => $1, :displayOnlyCities => 1 }) |
|
|
|
# /place/public/<expr> - recherche portant sur un lieu public (place) |
|
when /\/places\/public\/(.*)\/*$/ |
|
self.get(:places, { :term => $1, :displayOnlyPublicPlaces => 1 }) |
|
|
|
# /place/road/<expr> - recherche portant sur un lieu public (place) |
|
when /\/places\/road\/(.*)\/*$/ |
|
super(:places, { :term => $1, :displayOnlyRoads => 1 }) |
|
|
|
# /place/stop/<expr> - recherche les arrets pour <expr> |
|
when /\/places\/stop\/(.*)\/*$/ |
|
super(:places, { :term => $1, :displayOnlyStopAreas => 1 }) |
|
|
|
# /place/<expr>/ - recherche portant sur un lieu (place) |
|
when /\/places\/(.*)\/*$/ |
|
super(:places, { :term => $1 }) |
|
|
|
else |
|
return "commande non reconnue" |
|
|
|
end |
|
end |
|
|
|
end |
|
|
|
# la clé ne doit pas être publique |
|
# si vous publiez vos sources, elle pourra être stockée dans $HOME/.config/tisseo.rb |
|
user_cnf=sprintf('%s/.config/tisseo.rb', ENV['HOME']) |
|
|
|
if File.exist? user_cnf |
|
require user_cnf |
|
@config=Tisseo::config |
|
else |
|
@config = { |
|
# vous n'avez pas de clé : envoyez un message a contact[arobase]tisseo.fr |
|
# en précisant : votre identité, l'usage que vous ferez de l'API TISSEO |
|
:key => 'xxxxxxxx-yyyy-zzzz-kkkk-vvvvvvvvvvvv', |
|
:lang => :en # only supported : fr, en |
|
} |
|
|
|
end |
|
|
|
rest = TisseoRest.new @config[:key] |
|
rest.opts[:lang] = :en |
|
|
|
tisseo = TisseoRestful.new rest |
|
|
|
if ARGV.size>=1 |
|
cmd=ARGV[0] |
|
else |
|
puts "usage : ruby tisseo.rb '/restfull/expr'" |
|
end |
|
|
|
pp tisseo.get ARGV[0] |
|
|
|
|
|
|
Bonjour,
sauriez-vous comment obtenir une clef API pour Tisséo aujourd'hui ?
Merci !