Bimba.git

commit e6817ed77921516db93283af632d038da5114d57

Author: Adam Pioterek <adam.pioterek@protonmail.ch>

initial commit

 .idea/misc.xml | 12 
 app/build.gradle | 20 +
 app/src/main/AndroidManifest.xml | 11 
 app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt | 93 ++++++
 app/src/main/res/drawable/logo.xml | 55 +++
 app/src/main/res/layout/activity_main.xml | 20 +
 app/src/main/res/values-night/colors.xml | 2 
 app/src/main/res/values-night/styles.xml | 9 
 app/src/main/res/values-pl/strings.xml | 2 
  | 0 
  | 3 
 build.gradle | 4 
 gradle/wrapper/gradle-wrapper.properties | 4 
 research/badges.md | 5 
 research/datasources.md | 9 
 research/scraper.py | 193 +++++++++++++


diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7c1371c54da8eafc01d4252c2465d08c996e6451..ba7052b8197ddf8ba8756022d905d03055c7ad60 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -24,17 +24,7 @@         
       </value>
     </option>
   </component>
-  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
-    <OptionsSetting value="true" id="Add" />
-    <OptionsSetting value="true" id="Remove" />
-    <OptionsSetting value="true" id="Checkout" />
-    <OptionsSetting value="true" id="Update" />
-    <OptionsSetting value="true" id="Status" />
-    <OptionsSetting value="true" id="Edit" />
-    <ConfirmationsSetting value="0" id="Add" />
-    <ConfirmationsSetting value="0" id="Remove" />
-  </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">




diff --git a/app/build.gradle b/app/build.gradle
index 6e3219af510796cf25b8dbf28e6c506b01583277..6d5f42648d5fd1a6ba9774ddcad3d665dc161867 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,15 +1,17 @@
 apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
 
 android {
     compileSdkVersion 25
-    buildToolsVersion "25.0.2"
+    buildToolsVersion "25.0.3"
     defaultConfig {
         applicationId "ml.adamsprogs.bimba"
-        minSdkVersion 15
+        minSdkVersion 19
         targetSdkVersion 25
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        vectorDrawables.useSupportLibrary = true
     }
     buildTypes {
         release {
@@ -24,6 +26,18 @@     compile fileTree(dir: 'libs', include: ['*.jar'])
     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
         exclude group: 'com.android.support', module: 'support-annotations'
     })
-    compile 'com.android.support:appcompat-v7:25.2.0'
+    compile 'com.android.support:appcompat-v7:25.3.1'
+    compile 'com.android.support:cardview-v7:25.3.1'
+    compile 'com.android.support:design:25.3.1'
+    compile 'com.android.support:support-vector-drawable:25.4.0'
     testCompile 'junit:junit:4.12'
+    compile 'com.android.support.constraint:constraint-layout:1.0.2'
+    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+    compile 'com.github.arimorty:floatingsearchview:2.1.1'
+}
+repositories {
+    maven {
+        url "https://maven.google.com"
+    }
+    mavenCentral()
 }




diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 528acc4bbb7cbdfe62c838439b890116ecc9ffe1..4e04cbad3e2b067a1dcc24ea26c2b9a77a7dacd8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,13 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="ml.adamsprogs.bimba">
 
     <application
         android:allowBackup="true"
-        android:icon="@mipmap/ic_launcher"
+        android:icon="@drawable/logo"
         android:label="@string/app_name"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
 
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 
-</manifest>
+</manifest>
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..40075d65bc0b76db5a03373c5c3f2541da95c6fa
--- /dev/null
+++ b/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt
@@ -0,0 +1,93 @@
+package ml.adamsprogs.bimba
+
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.support.v7.app.AppCompatActivity
+import android.text.Html
+import android.widget.Toast
+import com.arlib.floatingsearchview.FloatingSearchView
+import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
+
+
+class MainActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        val context = this as Context
+
+        val stops = listOf(Suggestion("Kołłątaja\n610 -> Dębiec"), Suggestion("Dębiecka\n610 -> Górczyn")) //todo get from db
+        val searchView = findViewById(R.id.search_view) as FloatingSearchView
+
+        searchView.setOnQueryChangeListener({ _, newQuery ->
+            searchView.swapSuggestions(stops.filter { deAccent(it.body.split("\n")[0]).contains(newQuery, true) })
+        })
+
+        searchView.setOnSearchListener(object : FloatingSearchView.OnSearchListener {
+            override fun onSuggestionClicked(searchSuggestion: SearchSuggestion) {
+                Toast.makeText(context, "clicked "+ searchSuggestion.body, Toast.LENGTH_SHORT).show()
+                //todo to next screen
+            }
+            override fun onSearchAction(query: String) {
+            }
+        })
+
+        searchView.setOnBindSuggestionCallback { _, _, textView, item, _ ->
+            val suggestion = item as Suggestion
+            val text = suggestion.body.split("\n")
+            val t = "<small><font color=\"#a0a0a0\">" + text[1] + "</font></small>"
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                textView.text = Html.fromHtml(text[0]+"<br/>"+t, Html.FROM_HTML_MODE_LEGACY)
+            } else {
+                @Suppress("DEPRECATION")
+                textView.text = Html.fromHtml(text[0]+"<br/>"+t)
+            }
+        }
+
+        //todo searchView.attachNavigationDrawerToMenuButton(mDrawerLayout)
+    }
+
+    fun deAccent(str: String): String {
+        var result = str.replace('ę', 'e')
+        result = result.replace('ó', 'o')
+        result = result.replace('ą', 'a')
+        result = result.replace('ś', 's')
+        result = result.replace('ł', 'l')
+        result = result.replace('ż', 'ż')
+        result = result.replace('ź', 'ź')
+        result = result.replace('ć', 'ć')
+        result = result.replace('ń', 'n')
+        return result
+    }
+
+    class Suggestion(text: String) : SearchSuggestion {
+        private val body: String = text
+
+        constructor(parcel: Parcel) : this(parcel.readString())
+
+        override fun describeContents(): Int {
+            TODO("not implemented")
+        }
+
+        override fun writeToParcel(dest: Parcel?, flags: Int) {
+            TODO("not implemented")
+        }
+
+        override fun getBody(): String {
+            return body
+        }
+
+        companion object CREATOR : Parcelable.Creator<Suggestion> {
+            override fun createFromParcel(parcel: Parcel): Suggestion {
+                return Suggestion(parcel)
+            }
+
+            override fun newArray(size: Int): Array<Suggestion?> {
+                return arrayOfNulls(size)
+            }
+        }
+    }
+}




diff --git a/app/src/main/res/drawable/logo.xml b/app/src/main/res/drawable/logo.xml
new file mode 100644
index 0000000000000000000000000000000000000000..235d29e4958f2c5944427f6f3b288c1f2d4e280b
--- /dev/null
+++ b/app/src/main/res/drawable/logo.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="512dp"
+    android:height="512dp"
+    android:viewportWidth="512"
+    android:viewportHeight="512">
+
+  <group
+      android:translateY="215">
+    <path
+        android:fillColor="#00ff00"
+        android:strokeColor="#ffffff"
+        android:strokeAlpha="0.15463915"
+        android:strokeWidth="0.30951566"
+        android:pathData="M 0.15475783 -214.84525 H 511.84524783 V 296.84524 H 0.15475783 V -214.84525 Z" />
+    <path
+        android:fillColor="#ffff00"
+        android:strokeColor="#ffffff"
+        android:strokeAlpha="0.15463915"
+        android:strokeWidth="0.26458332"
+        android:pathData="M 264.58334 -195.880935 C 286.293391181 -195.880935 303.892865 -178.281461181 303.892865 -156.57141 C 303.892865 -134.861358819 286.293391181 -117.261885 264.58334 -117.261885 C 242.873288819 -117.261885 225.273815 -134.861358819 225.273815 -156.57141 C 225.273815 -178.281461181 242.873288819 -195.880935 264.58334 -195.880935 Z" />
+    <path
+        android:fillColor="#ffff00"
+        android:strokeColor="#ffffff"
+        android:strokeAlpha="0.15463915"
+        android:strokeWidth="0.26458332"
+        android:pathData="M 258.53571 216.869064 C 276.905753434 216.869064 291.797616 231.760926566 291.797616 250.13097 C 291.797616 268.501013434 276.905753434 283.392876 258.53571 283.392876 C 240.165666566 283.392876 225.273804 268.501013434 225.273804 250.13097 C 225.273804 231.760926566 240.165666566 216.869064 258.53571 216.869064 Z" />
+    <path
+        android:fillColor="#ffff00"
+        android:strokeColor="#ffffff"
+        android:strokeAlpha="0.15463915"
+        android:strokeWidth="0.26458332"
+        android:pathData="M 2.4384184 66.288048 H 19.0693714 V 350.526148 H 2.4384184 V 66.288048 Z" />
+    <path
+        android:fillColor="#ffff00"
+        android:strokeColor="#ffffff"
+        android:strokeAlpha="0.15463915"
+        android:strokeWidth="0.26458332"
+        android:pathData="M 66.288048 -286.67651 H 82.919001 V -2.43841 H 66.288048 V -286.67651 Z" />
+    <group>
+      <path
+          android:fillColor="#ffff00"
+          android:strokeColor="#ffffff"
+          android:strokeAlpha="0.15463915"
+          android:strokeWidth="0.26458332"
+          android:pathData="M 143.47235 236.35587 H 160.103303 V 520.59397 H 143.47235 V 236.35587 Z" />
+      <path
+          android:fillColor="#ffff00"
+          android:strokeColor="#ffffff"
+          android:strokeAlpha="0.15463915"
+          android:strokeWidth="0.26458332"
+          android:pathData="M 236.35587 -427.71045 H 252.986823 V -143.47235 H 236.35587 V -427.71045 Z" />
+    </group>
+  </group>
+</vector>
\ No newline at end of file




diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2118179d144cfce2aee6e45af628f83bf862a2c5
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="ml.adamsprogs.bimba.MainActivity">
+    <com.arlib.floatingsearchview.FloatingSearchView
+        android:id="@+id/search_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:floatingSearch_searchBarMarginLeft="16dp"
+        app:floatingSearch_searchBarMarginTop="16dp"
+        app:floatingSearch_searchBarMarginRight="16dp"
+        app:floatingSearch_searchHint="Search..."
+        app:floatingSearch_suggestionsListAnimDuration="250"
+        app:floatingSearch_showSearchKey="false"
+        app:floatingSearch_leftActionMode="showHamburger"
+        app:floatingSearch_close_search_on_keyboard_dismiss="true"/>
+</android.support.constraint.ConstraintLayout>




diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index cde69bcccec65160d92116f20ffce4fce0b5245c..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ




diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c133a0cbd379f5af6dbf1a899a0293ca5eccfad0..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ




diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index bfa42f0e7b91d006d22352c9ff2f134e504e3c1d..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ




diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72cdd7480cb983fa1bcc7ce686e51ef87fe7..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ




diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index aee44e138434630332d88b1680f33c4b24c70ab3..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ




diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index 3ab3e9cbce07f7cdc941fc8ba424c05e83ed80f0..0000000000000000000000000000000000000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <color name="colorPrimary">#3F51B5</color>
-    <color name="colorPrimaryDark">#303F9F</color>
-    <color name="colorAccent">#FF4081</color>
-</resources>




diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
deleted file mode 100644
index 5885930df6d10edf3d6df40d6556297d11f953da..0000000000000000000000000000000000000000
--- a/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
-
-    <!-- Base application theme. -->
-    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
-        <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
-    </style>
-
-</resources>




diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources></resources>
\ No newline at end of file




diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cf79209801eec4a7fa1c0f8fe8b75478f187f057
--- /dev/null
+++ b/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+</resources>
\ No newline at end of file




diff --git a/app/src/main/res/values-notnight/colors.xml b/app/src/main/res/values-notnight/colors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3ab3e9cbce07f7cdc941fc8ba424c05e83ed80f0
--- /dev/null
+++ b/app/src/main/res/values-notnight/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>




diff --git a/app/src/main/res/values-notnight/styles.xml b/app/src/main/res/values-notnight/styles.xml
new file mode 100644
index 0000000000000000000000000000000000000000..01eb5f588d1855d7fc90b2f6ef8d8dc97c260303
--- /dev/null
+++ b/app/src/main/res/values-notnight/styles.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>




diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96
--- /dev/null
+++ b/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources></resources>
\ No newline at end of file




diff --git a/build.gradle b/build.gradle
index 74b2ab0dd88f66079893ee7c77a46dbace4f0fde..4b5928a5f03c5c7697af0a79987e7e6510fe2bf1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,11 +1,13 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
+    ext.kotlin_version = '1.1.2-4'
     repositories {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.2.3'
+        classpath 'com.android.tools.build:gradle:3.0.0-alpha5'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files




diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 04e285f34080d98841a9fac832466aec720aecec..3d353ea4d1cc03c0e3c288a3862a26c1ebc247c4 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Dec 28 10:00:20 PST 2015
+#Tue Jul 04 17:47:25 CEST 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-milestone-1-all.zip




diff --git a/research/badges.md b/research/badges.md
new file mode 100644
index 0000000000000000000000000000000000000000..34c5ea6f822b37f8dcdd400d596d52f70eb56696
--- /dev/null
+++ b/research/badges.md
@@ -0,0 +1,5 @@
+# CodeCov
+
+* [website](https://codecov.io/)
+* [example android](https://github.com/codecov/example-android)
+* [with travis](https://github.com/codecov/example-android/blob/master/.travis.yml)




diff --git a/research/datasources.md b/research/datasources.md
index 3b4ae5261eabb965ce231f7d04a9280bc4d2ed64..20a66df2c1fbfc301ed7fde011e763d0743a55dc 100644
--- a/research/datasources.md
+++ b/research/datasources.md
@@ -1,12 +1,3 @@
-# offline timetable
-
-* config: http://ztm.poznan.pl/timetablesConfig.json
-* lines: http://ztm.poznan.pl/gtfs-ztm/routes_by_name.json.php
-* stops-on-line: http://ztm.poznan.pl/gtfs-ztm/route_directions.html.php?route_name=6&agency_name=ZTM_MPK&json=dane
-* stop-on-line: http://ztm.poznan.pl/gtfs-ztm/timetable.html.php?route_name=6&direction=0&stop_id=93&agency_name=ZTM_MPK&json=dane
-    * direction in {0,1}
-* stops: http://ztm.poznan.pl/gtfs-ztm/places.json.php
-
 # Poznań API
 
 * bicycles: http://egov.psnc.pl/node/29#stacje-rowerow-miejskich




diff --git a/research/scraper.py b/research/scraper.py
new file mode 100755
index 0000000000000000000000000000000000000000..2f6d4b1c8e16f0a8157a62fea96e2b116ba146ba
--- /dev/null
+++ b/research/scraper.py
@@ -0,0 +1,193 @@
+#!/bin/python
+"""
+js interface: http://ztm.poznan.pl/themes/ztm/dist/js/app.js
+nodes: http://ztm.poznan.pl/goeuropa-api/all-nodes
+stops in node: http://ztm.poznan.pl/goeuropa-api/node_stops/{node:symbol}
+stops: http://ztm.poznan.pl/goeuropa-api/stops-nodes
+bike stations: http://ztm.poznan.pl/goeuropa-api/bike-stations
+
+"""
+import json
+import hashlib
+import re
+import sqlite3
+import sys
+import time
+import requests
+from bs4 import BeautifulSoup
+
+def get_nodes():
+    """
+    get nodes
+    """
+    session = requests.session()
+
+    index = session.get('http://ztm.poznan.pl/goeuropa-api/all-nodes')
+    return [(stop['symbol'], stop['name']) for stop in json.loads(index.text)]
+
+
+def get_stops(node):
+    """
+    get stops
+    """
+    session = requests.session()
+
+    index = session.get('http://ztm.poznan.pl/goeuropa-api/node_stops/{}'.format(node))
+    stops = []
+    for stop in json.loads(index.text):
+        stop_id = stop['stop']['id']
+        number = re.findall("\\d+", stop['stop']['symbol'])[0]
+        lat = stop['stop']['lat']
+        lon = stop['stop']['lon']
+        directions = ', '.join(['{} → {}'.format(transfer['name'], transfer['headsign'])
+                                for transfer in stop['transfers']])
+        stops.append((stop_id, node, number, lat, lon, directions))
+    return stops
+
+
+def get_lines():
+    """
+    get lines
+    """
+    session = requests.session()
+
+    index = session.get('http://ztm.poznan.pl/goeuropa-api/index')
+    soup = BeautifulSoup(index.text, 'html.parser')
+
+    lines = {line['data-lineid']: line.text for line in
+             soup.findAll(attrs={'class': re.compile(r'.*\blineNo-bt\b.*')})}
+
+    return lines
+
+
+def get_route(line_id):
+    """
+    get routes
+    """
+    session = requests.session()
+
+    index = session.get('http://ztm.poznan.pl/goeuropa-api/line-info/{}'.format(line_id))
+    soup = BeautifulSoup(index.text, 'html.parser')
+    directions = soup.findAll(attrs={'class': re.compile(r'.*\baccordion-item\b.*')})
+    routes = {}
+    for direction in directions:
+        direction_id = direction['data-directionid']
+        route = [{'id': stop.find('a')['data-stopid'], 'name': stop['data-name'],
+                  'onDemand': re.search('stop-onDemand', str(stop['class'])) != None}
+                 for stop in direction.findAll(attrs={'class': re.compile(r'.*\bstop-itm\b.*')})]
+        routes[direction_id] = route
+    return routes
+
+
+def get_stop_times(stop_id, line_id, direction_id):
+    """
+    get timetable
+    """
+    session = requests.session()
+
+    index = session.post('http://ztm.poznan.pl/goeuropa-api/stop-info/{}/{}'.
+                         format(stop_id, line_id), data={'directionId': direction_id})
+    soup = BeautifulSoup(index.text, 'html.parser')
+    legends = {}
+    for row in soup.find(attrs={'class': re.compile(r'.*\blegend-box\b.*')}).findAll('li'):
+        row = row.text.split('-')
+        row[0] = row[0].rstrip()
+        row[1] = row[1].lstrip()
+        if row[0] != '_':
+            legends[row[0]] = '-'.join(row[1:])
+    schedules = {}
+    for mode in soup.findAll(attrs={'class': re.compile(r'.*\bmode-tab\b.*')}):
+        mode_name = mode['data-mode']
+        schedule = {row.find('th').text: [
+            {'time': minute.text, 'lowFloor': re.search('n-line', str(minute['class'])) != None}
+            for minute in row.findAll('a')]
+                    for row in mode.find(attrs={'class': re.compile(r'.*\bscheduler-hours\b.*')}).
+                    findAll('tr')}
+        schedule_2 = {hour: times for hour, times in schedule.items() if times != []}
+        schedule = []
+        for hour, deps in schedule_2.items():
+            for dep in deps:
+                schedule.append((hour, *describe(dep['time'], legends), dep['lowFloor']))
+        schedules[mode_name] = schedule
+
+    return schedules, hashlib.sha512(index.text.encode('utf-8')).hexdigest()
+
+
+def describe(dep_time, legend):
+    """
+    describe departure
+    """
+    desc = []
+    while re.match('^\\d+$', dep_time) is None:
+        if dep_time[-1] != ',':
+            desc.append(legend[dep_time[-1]])
+        dep_time = dep_time[:-1]
+    return (int(dep_time), '; '.join(desc))
+
+
+def main():
+    """
+    main function
+    """
+    print(time.time())
+    with sqlite3.connect('timetable.db') as connection:
+        print('creating tables')
+        cursor = connection.cursor()
+        cursor.execute('create table nodes(symbol TEXT PRIMARY KEY, name TEXT)')
+        cursor.execute('create table stops(id TEXT PRIMARY KEY, symbol TEXT \
+                        references node(symbol), number TEXT, lat REAL, lon REAL, headsigns TEXT)')
+        cursor.execute('create table lines(id TEXT PRIMARY KEY, number TEXT)')
+        cursor.execute('create table timetables(id INTEGER PRIMARY KEY, stop_id \
+                        TEXT references stop(id), line_id TEXT references line(id), \
+                        headsign TEXT, checksum TEXT)')
+        cursor.execute('create table departures(id INTEGER PRIMARY KEY, timetable_id INTEGER \
+                        references timetable(id), hour INTEGER, minute INTEGER, mode TEXT, \
+                        lowFloor INTEGER, modification TEXT)')
+
+        print('getting nodes')
+        nodes = get_nodes()
+        cursor.executemany('insert into nodes values(?, ?);', nodes)
+        nodes_no = len(nodes)
+        print('getting stops')
+        node_i = 1
+        for symbol, _ in nodes:
+            print('\rstop {}/{}'.format(node_i, nodes_no), end='')
+            sys.stdout.flush()
+            cursor.executemany('insert into stops values(?, ?, ?, ?, ?, ?);', get_stops(symbol))
+            node_i += 1
+        print('')
+        lines = get_lines()
+        lines_no = len(lines)
+        line_i = 1
+        tti = 0
+        cursor.executemany('insert into lines values(?, ?);', lines.items())
+        for line_id, _ in lines.items():
+            route = get_route(line_id)
+            routes_no = len(route)
+            route_i = 1
+            for direction, stops in route.items():
+                stops_no = len(stops)
+                stop_i = 1
+                for stop in stops:
+                    print('line {}/{} route {}/{} stop {}/{}'.
+                          format(line_i, lines_no, route_i, routes_no, stop_i, stops_no), end='')
+                    sys.stdout.flush()
+                    timetables, checksum = get_stop_times(stop['id'], line_id, direction)
+                    cursor.execute('insert into timetables values(?, ?, ?, ?, ?);',
+                                   (tti, stop['id'], line_id, stops[-1]['name'], checksum))
+                    for mode, times in timetables.items():
+                        cursor.executemany('insert into departures values(null, ?, ?, ?, ?, ?, ?);',
+                                           [(tti, hour, minute, mode, lowfloor, desc)
+                                            for hour, minute, desc, lowfloor in times])
+                    stop_i += 1
+                    tti += 1
+                    print('{}\r'.format(' '*35), end='')
+                    sys.stdout.flush()
+                route_i += 1
+            print('')
+            line_i += 1
+    print(time.time())
+
+
+if __name__ == '__main__':
+    main()




diff --git a/research/timetable.db.xz b/research/timetable.db.xz
new file mode 100644
index 0000000000000000000000000000000000000000..a31038b42c82ca61c77af8629de0cfe1f1da28e3
Binary files /dev/null and b/research/timetable.db.xz differ