PebbleTransilien is an unofficial app for checking SNCF Transilien™ trains on your wrist. Transilien™ is the brand name of the suburban railway service of the SNCF-owned railway network operating within the Île-de-France.
Train station codes and coordinates are extracted from the official app's database. If it's the iOS version, the database is located in /Documents/Transilien.sqlite
.
It's a SQLite database, we can simply query what we want, for example, I need stations' codes, names, coordinates (average value if duplicated), so my query is like the following one:
SELECT ZGARE.ZCODETR3A AS CODE, ZGARE.ZNAME AS NAME,AVG(ZPOSITION.ZLATITUDE) AS LAT,AVG(ZPOSITION.ZLONGITUDE) AS LONG
FROM ZGARE, ZPOSITION
WHERE ZGARE.Z_PK = ZPOSITION.ZGARE
GROUP BY CODE
ORDER BY CODE
To facilitate the usages, data is organized into several binaries by using this Mac tool TransilienStations. (Remember to pull CocoaPods dependencies before building in Xcode.)
The binaries are:
- station_code.bin (ordered station codes)
- station_name_pos.bin (positions of all the names in
station_name.bin
) - station_name.bin (all station names in alphabetic order)
- station_name_search_pos.bin (positions of all the names in
station_name_search.bin
) - station_name_search.bin (all station names without white space or accent in alphabetic order)
- station_latlng.bin (station indexes grouped into latitude/longitude grids)
For Windows/Linux users, don't worry, let me explain the data structures, so that you can develop another tool to generate the same binaries.
[DATA]
[3 Byte] char[3] station_code
[DATA]
[2 Byte] uint8_t[2] station_name_position
[DATA]
[N Byte] char* station_name (NULL-terminated string)
[DATA]
[2 Byte] uint8_t[2] station_name_position
[DATA]
[N Byte] char* station_name (NULL-terminated string)
[HEAD]
[4 Byte] float min_lat
[4 Byte] float max_lat
[4 Byte] float min_lng
[4 Byte] float max_lng
[2 Byte] unsigned int grid_count_lat
[2 Byte] unsigned int grid_count_lng
[DATA]
[2 Byte] unsigned int latitude_longitude_indexes_key
[2 Byte] unsigned int number_of_stations
[2xN Byte] unsigned int station_indexes
- First you have to know the station's
index
- Then read 3 bytes at the position
3*index
ofstation_code.bin
.
-
If you know a station's
index
- Read 2 bytes at position
2*index
ofstation_name_pos.bin
and store them into a variableuint8_t pos[2]
- Then we get the name position
size_t position = (pos[0] << 8) + pos[1]
- Then at the
position
ofstation_name.bin
, get name's length by usingstrlen()
(names are null-terminated strings), then read a string in that length.
- Read 2 bytes at position
-
If you want to search a name
- Compare all the letters in
station_name_search.bin
to find the index you want. - To reduce memory footprint, you can compare names one by one with the help of
station_name_search_pos.bin
, which contains each name's position.
- Compare all the letters in
- First you have the current location
curr_lat
andcurr_lng
. - Calculate the grid sizes, e.g.
grid_size_lat
=(max_lat - min_lat) / count_lat
- Calculate the indexes, e.g.
floorf((curr_lat - min_lat)/grid_size_lat)
- The key is
index_lat * 100 + index_lng
- Create a dictionary with the keys and station indexes
- Get all the stations in the 9 grids around the position
for (int offset_lat = -1; offset_lat <= 1; ++ offset_lat) {
for (int offset_lng = -1; offset_lng <= 1; ++ offset_lng) {
int32_t key = (index_lat + offset_lat)* 100 + (index_lng + offset_lng);
// Check if the key exists
// If true, get stations' indexes
}
}
To request the realtime information from SNCF, we have to use their web services. I'm lazy, I didn't request for the Open Data APIs, so I just use the same APIs as the official iOS version of SNCF Transilien.
http://transilien.ods.ocito.com/ods/transilien/iphone
This is the request URL used in the current iOS version of SNCF Translien.
All the requests are HTTP POST requests with POST Body. All the request/response data are in JSON format ("Content-Type": "application/json; charset=utf-8"
).
POST Body
[
{
"serial": "1",
"target": "/transilien/autourDeMoi",
"map": {
"longitude": 2.265482,
"latitude": 48.905355,
"distance": 300,
"hasBeenFiltered": "true"
}
}
]
Response
[
{
"binary": null,
"data": [
{
"codeDUA": "DUA59:4274261",
"codeTR3A": null,
"latitude": 48.905146072906966,
"lignes": [
{
"directions": [
"Pont de Levallois / Bécon",
"AUDRA"
],
"externalCode": "DUA100100167",
"nom": "167",
"type": "Bus"
}
],
"longitude": 2.263350926842547,
"modeTypeExternalCode": "Bus",
"nom": "MICHEL RICARD",
"stopAreaExternalCode": "DUA59:4274261",
"type": null,
"x": 594634,
"y": 2434100
},
{
"codeDUA": "DUA59:4037345",
"codeTR3A": null,
"latitude": 48.90698093809426,
"lignes": [
{
"directions": [
"MADELEINE",
"ARGENTEUIL"
],
"externalCode": "DUA100987752",
"nom": "N52",
"type": "Bus"
}
],
"longitude": 2.2658159459896807,
"modeTypeExternalCode": "Bus",
"nom": "CHEVREUL",
"stopAreaExternalCode": "DUA59:4037345",
"type": null,
"x": 594815,
"y": 2434304
},
{
"codeDUA": "DUA76:445",
"codeTR3A": "BEC",
"latitude": 48.9055805743721,
"lignes": [
{
"directions": [
"VERSAILLES RIVE DROITE",
"PARIS SAINT-LAZARE"
],
"externalCode": "DUA800854542",
"nom": "L",
"type": "Bus"
}
],
"longitude": 2.268571738926075,
"modeTypeExternalCode": "Bus",
"nom": "GARE DE BECON LES BRUYERES",
"stopAreaExternalCode": "DUA8738200",
"type": null,
"x": 595017,
"y": 2434148
},
{
"codeDUA": "DUA76:446",
"codeTR3A": "BEC",
"latitude": 48.9055805743721,
"lignes": [
{
"directions": [
"VERSAILLES RIVE DROITE",
"PARIS SAINT-LAZARE"
],
"externalCode": "DUA800854542",
"nom": "L",
"type": "Bus"
}
],
"longitude": 2.268571738926075,
"modeTypeExternalCode": "Bus",
"nom": "GARE DE BECON LES BRUYERES",
"stopAreaExternalCode": "DUA8738200",
"type": null,
"x": 595017,
"y": 2434148
}
]
}
]
POST Body
[
{
"target": "/transilien/getNextTrains",
"map": {
"codeArrivee": "",
"codeDepart": "BEC",
"theoric": "false"
}
}
]
Response
[
{
"binary": null,
"data": [
{
"trainDock": "D",
"trainHour": "26/07/2015 00:19",
"trainLane": null,
"trainMention": "Retardé",
"trainMissionCode": "SEBO",
"trainNumber": "134735",
"trainTerminus": "SNB",
"type": "R"
},
{
"trainDock": "C",
"trainHour": "26/07/2015 00:26",
"trainLane": null,
"trainMention": null,
"trainMissionCode": "PORO",
"trainNumber": "134708",
"trainTerminus": "PSL",
"type": "R"
},
{
"trainDock": "B",
"trainHour": "26/07/2015 00:28",
"trainLane": null,
"trainMention": null,
"trainMissionCode": "SOPE",
"trainNumber": "135493",
"trainTerminus": "SVL",
"type": "R"
},
{
"trainDock": "D",
"trainHour": "26/07/2015 00:31",
"trainLane": null,
"trainMention": null,
"trainMissionCode": "VULE",
"trainNumber": "133877",
"trainTerminus": "VRD",
"type": "R"
},
{
"trainDock": "A",
"trainHour": "26/07/2015 00:33",
"trainLane": null,
"trainMention": null,
"trainMissionCode": "POPI",
"trainNumber": "135354",
"trainTerminus": "PSL",
"type": "R"
}
],
"headers": null,
"list": [
"La version d'application utilisée est obsolète. Nous vous invitons à télécharger la dernière version en date afin de profiter de toutes les fonctionnalités à jour."
],
"listOfMap": null,
"map": {
"codeArrivee": "",
"hasBeenFiltered": "true",
"codeDepart": "BEC"
},
"serial": 0,
"state": 200,
"target": null
}
]
###Request next trains from one station to another station
POST Body
[
{
"serial": 0,
"target": "/transilien/getNextTrains",
"map": {
"codeArrivee": "PSL",
"codeDepart": "BEC",
"theoric": "false"
}
},
{
"serial": 1,
"target": "/transilien/getNextTrains",
"map": {
"codeArrivee": "BEC",
"codeDepart": "PSL",
"theoric": "false"
}
}
]
Response
[
{
"binary": null,
"data": [
{
"trainDock": "C",
"trainHour": "26/07/2015 00:26",
"trainLane": null,
"trainMention": null,
"trainMissionCode": "PORO",
"trainNumber": "134708",
"trainTerminus": "PSL",
"type": "R"
},
{
"trainDock": "A",
"trainHour": "26/07/2015 00:33",
"trainLane": null,
"trainMention": null,
"trainMissionCode": "POPI",
"trainNumber": "135354",
"trainTerminus": "PSL",
"type": "R"
},
{
"trainDock": "C",
"trainHour": "26/07/2015 00:42",
"trainLane": null,
"trainMention": null,
"trainMissionCode": "POBO",
"trainNumber": "133868",
"trainTerminus": "PSL",
"type": "R"
}
],
"headers": null,
"list": [
"La version d'application utilisée est obsolète. Nous vous invitons à télécharger la dernière version en date afin de profiter de toutes les fonctionnalités à jour."
],
"listOfMap": null,
"map": {
"codeArrivee": "PSL",
"hasBeenFiltered": "true",
"codeDepart": "BEC"
},
"serial": 0,
"state": 200,
"target": null
},
{
"binary": null,
"data": [
{
"trainDock": null,
"trainHour": "26/07/2015 00:25",
"trainLane": "3",
"trainMention": null,
"trainMissionCode": "VULE",
"trainNumber": "133877",
"trainTerminus": "VRD",
"type": "R"
},
{
"trainDock": null,
"trainHour": "26/07/2015 00:43",
"trainLane": "BL",
"trainMention": null,
"trainMissionCode": "SEBO",
"trainNumber": "134741",
"trainTerminus": "SNB",
"type": "R"
},
{
"trainDock": null,
"trainHour": "26/07/2015 00:49",
"trainLane": "BL",
"trainMention": null,
"trainMissionCode": "SOPE",
"trainNumber": "135497",
"trainTerminus": "SVL",
"type": "R"
}
],
"headers": null,
"list": [
"La version d'application utilisée est obsolète. Nous vous invitons à télécharger la dernière version en date afin de profiter de toutes les fonctionnalités à jour."
],
"listOfMap": null,
"map": {
"codeArrivee": "BEC",
"hasBeenFiltered": "true",
"codeDepart": "PSL"
},
"serial": 1,
"state": 200,
"target": null
}
]
POST Body
[
{
"target": "/transilien/getTrainDetails",
"map": {
"trainNumber": "133871",
"theoric": "false"
}
}
]
Response
[
{
"binary": null,
"data": [
{
"codeGare": "PSL",
"lane": "BL",
"mention": "N",
"time": "23/07/2015 23:55",
"typeTrain": "C"
},
{
"codeGare": "BEC",
"lane": "12",
"mention": "N",
"time": "24/07/2015 00:01",
"typeTrain": "C"
},
{
"codeGare": "KOU",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:04",
"typeTrain": "C"
},
{
"codeGare": "LDU",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:06",
"typeTrain": "C"
},
{
"codeGare": "PTX",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:08",
"typeTrain": "C"
},
{
"codeGare": "MVH",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:11",
"typeTrain": "C"
},
{
"codeGare": "VDO",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:13",
"typeTrain": "C"
},
{
"codeGare": "SCD",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:15",
"typeTrain": "C"
},
{
"codeGare": "VDV",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:18",
"typeTrain": "C"
},
{
"codeGare": "CWJ",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:21",
"typeTrain": "C"
},
{
"codeGare": "VFD",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:23",
"typeTrain": "C"
},
{
"codeGare": "MFL",
"lane": "1",
"mention": "N",
"time": "24/07/2015 00:25",
"typeTrain": "C"
},
{
"codeGare": "VRD",
"lane": "2",
"mention": "M",
"time": "24/07/2015 00:28",
"typeTrain": "C"
}
],
"headers": null,
"list": null,
"listOfMap": null,
"map": {
"trainNumber": "133871"
},
"serial": 0,
"state": 200,
"target": null
}
]
POST Body
[
{
"target": "/transilien/getAllTrainsDetails",
"map": {
"trainNumbers": "133871,133864",
"theoric": "false"
},
"serial": "18"
}
]
Response
[
{
"binary": null,
"data": {
"133871": [
{
"codeGare": "VDV",
"lane": "1",
"mention": "N",
"time": "26/07/2015 00:19",
"typeTrain": "C"
},
{
"codeGare": "CWJ",
"lane": "1",
"mention": "N",
"time": "26/07/2015 00:22",
"typeTrain": "C"
},
{
"codeGare": "VFD",
"lane": "1",
"mention": "N",
"time": "26/07/2015 00:24",
"typeTrain": "C"
},
{
"codeGare": "MFL",
"lane": "1",
"mention": "N",
"time": "26/07/2015 00:26",
"typeTrain": "C"
},
{
"codeGare": "VRD",
"lane": "2",
"mention": "M",
"time": "26/07/2015 00:29",
"typeTrain": "C"
}
],
"133864": [
{
"codeGare": "VRD",
"lane": "2",
"mention": "",
"time": "23:45",
"typeTrain": "C"
},
{
"codeGare": "MFL",
"lane": "2",
"mention": "",
"time": "23:47",
"typeTrain": "C"
},
{
"codeGare": "VFD",
"lane": "2",
"mention": "",
"time": "23:49",
"typeTrain": "C"
},
{
"codeGare": "CWJ",
"lane": "2",
"mention": "",
"time": "23:51",
"typeTrain": "C"
},
{
"codeGare": "VDV",
"lane": "2",
"mention": "",
"time": "23:54",
"typeTrain": "C"
},
{
"codeGare": "SCD",
"lane": "2",
"mention": "",
"time": "23:57",
"typeTrain": "C"
},
{
"codeGare": "VDO",
"lane": "2",
"mention": "",
"time": "23:59",
"typeTrain": "C"
},
{
"codeGare": "MVH",
"lane": "2",
"mention": "",
"time": "00:02",
"typeTrain": "C"
},
{
"codeGare": "PTX",
"lane": "2",
"mention": "",
"time": "00:04",
"typeTrain": "C"
},
{
"codeGare": "LDU",
"lane": "2",
"mention": "",
"time": "00:07",
"typeTrain": "C"
},
{
"codeGare": "KOU",
"lane": "2",
"mention": "",
"time": "00:09",
"typeTrain": "C"
},
{
"codeGare": "BEC",
"lane": null,
"mention": "",
"time": "00:11",
"typeTrain": "C"
},
{
"codeGare": "PSL",
"lane": "BL",
"mention": "M",
"time": "26/07/2015 00:19",
"typeTrain": "C"
}
]
},
"headers": null,
"list": null,
"listOfMap": null,
"map": {
"trainNumbers": "133871,133864"
},
"serial": 18,
"state": 200,
"target": null
}
]
Copyright (c) 2015 CocoaBob
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.