To jest stara wersja strony!
1. Projekt konceptualny
Tematem projektu jest aplikacja działająca na systemie mobilnym Android, która pozwoli użytkownikowi na odnalezienie najbliżej znajdujących się przystanków komunikacji miejskiej, zlokalizowanie siebie oraz przystanku na mapie oraz sprawdzenie rozkładu jazdy komunikacji miejskiej. W odróżnieniu od jakdojade.pl czy aplikacji Transportoid nie chcemy wyznaczać połączeń, które umożliwiają dostanie się do zamierzonej lokacji, a pomoc zagubionym mieszkańcom i turystom w znalezieniu najbliższego węzła komunikacji miejskiej.
Naszym celem jest zapoznanie się przy tworzeniu projektu z nowoczesnymi technologami związanymi z platformą Android oraz posiadanie pierwszej aplikacji w Android Market.
1.2 Analiza stanu wyjściowego
Przy implementacji aplikacji zostanie użyty język Java oraz Android SDK, a kluczowymi elementami będzie wykorzystanie GPS oraz Google API. Natomiast implementacja samej bazy danych zostanie wykonana w SQLite. Dystrybucja oraz aktualizowanie rozkładów jazdy w bazie będzie dokonywane za pomocą Android Market.
1.3 Analiza wymagań użytkownika
Must
Lokalizacja położenia posiadacza aplikacji i naniesienie jego pozycji na mapę
Odnalezienie najbliżej znajdujących się przystanków względem położenia posiadacza aplikacji i naniesienie ich pozycji na mapę
Posiadanie bazy linii, przystanków oraz połączeń komunikacji miejskiej oraz umożliwienie przeglądania ich za równo z poziomu przystanka jak i linii
Could
Wyznaczenie trasy przejścia od miejsca znajdowania się posiadacza aplikacji do wybranego przystanku
Lokalizacja dowolnego przystanka za pomocą wyszukiwarki
Won’t
Wyznaczenie dogodnych połączeń z punktu startowego do punktu końcowego podróży
1.4 Określenie scenariuszy użycia
Geolokalizacja
Włączenie aplikacji
Wybranie opcji geolokalizacji
Odnalezienie pozycji użytkownika
Odnalezienie najbliższych przystanków
Wyświetlenie znaczników na mapie
Sprawdzenie kursów danej linii MPK
Włączenie aplikacji
Wybranie opcji wyszukiwania połączeń po linach
Wybranie linii
Wybranie przystanku
Wyświetlenie podziału godzinowego
Sprawdzenie kursów z danego przystanka MPK
Włączenie aplikacji
Wybranie opcji wyszukiwania połączeń po przystankach
Wybranie przystanka
Wybranie linii
Wyświetlenie podziału godzinowego
1.5 Identyfikacja funkcji
Istnieją trzy możliwości użycia aplikacji:
Geolokalizacja – urządzenie za pomocą GPS sprawdza nasze położenie i względem znanej lokalizacji przystanków znajduje dla nas te najbliższe
Sprawdzenie kursów danej linii MPK – po wybraniu odpowiedniej linii komunikacyjnej dowiadujemy się o której godzinie pojawia się na konkretnych przystankach
Sprawdzenie kursów z danego przystanka MPK – po wybraniu odpowiedniego przystanka dowiadujemy się o jakich godzinach odjeżdżają z niego konkretne linie
1.6 Data Flow Diagram
Data Flow Diagram - poziom zerowy
Data Flow Diagram - poziom pierwszy
1.7 Wybór encji (obiektów) i ich atrybutów
Stops
stop_id (int) PK
localisationX (real)
localisationY (real)
name (text)
Lines
line_id (int) PK
number (int)
from_id (int) FK (references Przystanki(stop _id))
destination_id (int) FK (references Przystanki(stop _id))
Connections
stop_id (int) FK (references Stops(stop _id))
line_id (int) FK (references Lines(line _id))
day (text)
hour (int)
minute (int)
legend (text)
order (int)
1.8 Entity-Relationship Diagram
1.9 State Transition Diagram
1.9 Wzorce projektowe
Poniżej opisane zostają wzorce, które zostały użyte projekcie w celu zoptymalizowania jej działania, głównie pod kątem bazy danych i operacji na niej.
1.9.1 Leniwe inicjowanie
Wzorzec ten polega jak największym opóźnieniu utworzeniu obiektów aż do momentu zapotrzebowania na dany obiekt. Ma to na celu ograniczenie liczby zapytań kierowanych do bazy danych co powoduje jej odciążenie i zmniejszenie zużycia zasobów co przekłada się na zminimalizowanie zapotrzebowania na energie co w przypadku aplikacji mobilnych jest priorytetową sprawą.
W projekcie potrzebne linie i przystanki tworzone są w 2 przypadkach
gdy musimy je wyświetlić na mapie
gdy wyszukujemy je za pomocą wyszukiwarki
Zatem żeby odciążyć bazę obiekty opakowujące przystanki i linie są wywoływane tylko wtedy gdy znajdują się one na ekranie mapy bądź zaczynają się na daną literę bądź cyfrę wpisaną w wyszukiwarce. Po zainicjalizowaniu przechowywane są na liście stworzonych obiektów. Gdy zajdzie potrzeba ponownego ich wykorzystania program sprawdzając listę zobaczy, że obiekty były już utworzone i nie wywoła zapytania do bazy co odciąży zasoby i przyspieszy aplikację.
1.9.2 Mapowanie danych
Z reguły w przypadku użycia wzorca leniwej inicjalizacji korzysta się również z drugiego wzorca - mapowania danych. Opisuje on tabele zawarte w bazie danych w postaci obiektów języka Java prezentując odpowiednie wiersze w postaci pól. Dodatkowo klasy te zawierają w sobie pomocnicze metody do przeszukiwania bazy z poziomu encji takie jak wyszukiwanie przystanków dla danego rejonu bądź linii na daną literę alfabetu. Przez zastosowanie tego wzorca ułatwiamy programiście dostęp do danych.
1.9.2 Obiekt zapytania
Ostatnim użyty przez nas wzorzec naturalnie wypływa z używania technologii androidowych. Ponieważ bazę i zapytania do niej konstruujemy za pomocą DatabaseAdaptera to programista nie jest zmuszony do bezpośredniego wpisywania kwerend SQLowych, a używa funkcji w języku Java, które je obudowują. Przyczynia się to do zwiększenia przejrzystości kodu i łatwiejszego rozszerzania aplikacji.
2. Projekt logiczny
2.1 Projektowanie tabel, kluczy, kluczy obcych, powiązań między tabelami, indeksów, etc. w oparciu o zdefiniowany diagram ERD
// STOPS
public static final String PK_KEY_STOPID = "stop_id";
public static final String STOPID_OPTIONS = "INTEGER PRIMARY KEY AUTOINCREMENT";
public static final String KEY_LOCALISATIONX = "localisationX";
public static final String LOCALISATIONX_OPTIONS = "REAL NOT NULL";
public static final String KEY_LOCALISATIONY = "localisationy";
public static final String LOCALISATIONY_OPTIONS = "REAL NOT NULL";
public static final String KEY_NAME = "name";
public static final String NAME_OPTIONS = "TEXT NOT NULL";
// LINES
public static final String PK_KEY_LINEID = "line_id";
public static final String LINEID_OPTIONS = "INTEGER PRIMARY KEY AUTOINCREMENT";
public static final String KEY_NUMBER = "number";
public static final String NUMBER_OPTIONS = "INTEGER NOT NULL";
public static final String FK_KEY_FROMID = "from_id";
public static final String FROMID_OPTIONS = "INTEGER NOT NULL";
public static final String FK_KEY_DESTINATIONID = "destination_id";
public static final String DESTINATIONID_OPTIONS = "INTEGER NOT NULL";
// CONNECTIONS
public static final String FK_KEY_STOPID = "stop_id";
public static final String STOPID_FK_OPTIONS = "INTEGER NOT NULL";
public static final String FK_KEY_LINEID = "line_id";
public static final String LINEID_FK_OPTIONS = "INTEGER NOT NULL";
public static final String KEY_ORDERID = "order_id";
public static final String ORDERID_OPTIONS = "INTEGER NOT NULL";
public static final String KEY_DAY = "day";
public static final String DAY_OPTIONS = "TEXT NOT NULL";
public static final String KEY_HOUR = "hour";
public static final String HOUR_OPTIONS = "INTEGER NOT NULL";
public static final String KEY_MINUTE = "minute";
public static final String MINUTE_OPTIONS = "INTEGER NOT NULL";
public static final String KEY_LEGEND = "legend";
public static final String LEGEND_OPTIONS = "TEXT";
// CREATE STOPS
private static final String CREATE_STOPS = "create table " + TABLE_STOPS
+ " (" + PK_KEY_STOPID + " " + STOPID_OPTIONS + ", "
+ KEY_LOCALISATIONX + " " + LOCALISATIONX_OPTIONS + ", "
+ KEY_LOCALISATIONY + " " + LOCALISATIONY_OPTIONS + ", " + KEY_NAME
+ " " + NAME_OPTIONS + ");";
// CREATE LINES
private static final String CREATE_LINES = "create table " + TABLE_LINES
+ " (" + PK_KEY_LINEID + " " + LINEID_OPTIONS + ", " + KEY_NUMBER
+ " " + NUMBER_OPTIONS + ", " + FK_KEY_FROMID + " "
+ FROMID_OPTIONS + ", " + FK_KEY_DESTINATIONID + " "
+ DESTINATIONID_OPTIONS + ", " + "FOREIGN KEY(" + FK_KEY_FROMID
+ ") REFERENCES " + TABLE_STOPS + "(" + PK_KEY_STOPID + "), "
+ "FOREIGN KEY(" + FK_KEY_DESTINATIONID + ") REFERENCES "
+ TABLE_STOPS + "(" + PK_KEY_STOPID + ")" + ");";
// CREATE CONNECTIONS
private static final String CREATE_CONNECTIONS = "create table "
+ TABLE_CONNECTIONS + " (" + FK_KEY_STOPID + " "
+ STOPID_FK_OPTIONS + ", " + FK_KEY_LINEID + " "
+ LINEID_FK_OPTIONS + ", " + KEY_ORDERID + " " + ORDERID_OPTIONS
+ ", " + KEY_DAY + " " + DAY_OPTIONS + ", " + KEY_HOUR + " "
+ HOUR_OPTIONS + ", " + KEY_MINUTE + " " + MINUTE_OPTIONS + ", "
+ KEY_LEGEND + " " + LEGEND_OPTIONS + ", " + "FOREIGN KEY("
+ FK_KEY_STOPID + ") REFERENCES " + TABLE_STOPS + "("
+ PK_KEY_STOPID + "), " + "FOREIGN KEY(" + FK_KEY_LINEID
+ ") REFERENCES " + TABLE_LINES + "(" + PK_KEY_LINEID + ")" + ");";
W języku SQL kwerendy te wyglądją następująco:
CREATE TABLE stops (
stop_id INTEGER PRIMARY KEY AUTOINCREMENT,
localisationX REAL NOT NULL, localisationy REAL NOT NULL,
name TEXT NOT NULL);
CREATE TABLE lines (
line_id INTEGER PRIMARY KEY AUTOINCREMENT,
number INTEGER NOT NULL, from INTEGER NOT NULL,
destination INTEGER NOT NULL,
FOREIGN KEY(from) REFERENCES stops(stop_id),
FOREIGN KEY(destination) REFERENCES stops(stop_id));
CREATE TABLE connections (
stop_id INTEGER NOT NULL,
line_id INTEGER NOT NULL,
order_id INTEGER NOT NULL,
day TEXT NOT NULL, hour INTEGER NOT NULL,
minute INTEGER NOT NULL, legend TEXT,
FOREIGN KEY(stop_id) REFERENCES stops(stop_id),
FOREIGN KEY(line_id) REFERENCES lines(line_id));
2.2 Słownik pól bazy danych
Stops
stop_id (int) PK - unikalny identyfikator przystanka
localisationX (real) - szerokość geograficzna przystanka
localisationY (real) - długość geograficzna przystanka
name (text) - nazwa przystanka
Lines
line_id (int) PK - unikalny identyfikator linii
number (int) - numer linii
from_id (int) FK (references Przystanki(stop _id)) - przystanek źródłowy linii
destination_id (int) FK (references Przystanki(stop _id)) - przystanek docelowy linii
Connections
stop_id (int) FK (references Stops(stop _id)) - referencja do identyfikatora przystanka
line_id (int) FK (references Lines(line _id)) - referencja do identyfikatora linii
day (text) - rodzaj dnia tygodnia dla tabel czasowych MPK
hour (int) - godzina dla poszczególnego elementu tabel czasowych MPK
minute (int) - minuta dla poszczególnego elementu tabel czasowych MPK
legend (text) - dodatkowe informacje dla poszczególnego elementu tabel czasowych MPK
order (int) - kolejność przejazdu przez przystanki danej linii
2.3 Analiza zależności funkcyjnych i normalizacja tabel
Baza danych spełnia 1NF ponieważ każda składowa w każdej kropce jest atomowa i nie da się jej podzielić.
Baza danych spełnia 2NF ponieważ spełnia 1NF oraz każdy element jest zależnie funkcyjny od kluczy poszczególnych tabeli (jedyną tabelą, która nie posiada klucza głównego jest tabela connections, jest ona tabelą asocjacyjną i nie posiada niekluczowych elementów).
Baza danych spełnie 3NF ponieważ spełnia 2NF oraz każdy atrybut jest bezpośrednio zależny od klucza głównego w poszczególnych tabelach.
2.4 Projektowanie operacji na danych
W projekcie operacje na danych przeprowadzane są dwupoziomowo. Budowa bezpośrednich zapytań jest funkcjonalnością klasy DatabaseAdapter natomiast wstępne formowanie argumentów jest zadaniem odpowiednich klas obudowujących - Stop, Line i Connection.
2.4.1 Projektowanie operacji na danych z poziomu Database Adaptera
public Cursor getAllStops() {
String[] columns = { PK_KEY_STOPID, KEY_LOCALISATIONX,
KEY_LOCALISATIONY, KEY_NAME };
return _database.query(TABLE_STOPS, columns, null, null, null, null,
null);
}
public Cursor getAllLines() {
String[] columns = { PK_KEY_LINEID, KEY_NUMBER, FK_KEY_FROMID,
FK_KEY_DESTINATIONID };
return _database.query(TABLE_LINES, columns, null, null, null, null,
null);
}
public Cursor getAllConnections() {
String[] columns = { FK_KEY_STOPID, FK_KEY_LINEID, KEY_ORDERID,
KEY_DAY, KEY_HOUR, KEY_MINUTE, KEY_LEGEND };
return _database.query(TABLE_CONNECTIONS, columns, null, null, null,
null, null);
}
public Cursor getStop(int stopId) {
String[] columns = { PK_KEY_STOPID, KEY_LOCALISATIONX,
KEY_LOCALISATIONY, KEY_NAME };
String where = PK_KEY_STOPID + "=" + stopId;
Cursor cursor = _database.query(true, TABLE_STOPS, columns, where,
null, null, null, null, null);
return cursor;
}
public Cursor getStop(String name) {
String[] columns = { PK_KEY_STOPID, KEY_LOCALISATIONX,
KEY_LOCALISATIONY, KEY_NAME };
String where = KEY_NAME + "= '" + name + "'";
Cursor cursor = _database.query(true, TABLE_STOPS, columns, where,
null, null, null, null, null);
return cursor;
}
public Cursor getStopsExcept(Area area, List<Integer> stopIds) {
String[] columns = { PK_KEY_STOPID, KEY_LOCALISATIONX,
KEY_LOCALISATIONY, KEY_NAME };
String where = area.getLeft().toString() + " < " + KEY_LOCALISATIONX
+ " AND " + KEY_LOCALISATIONX + " < "
+ area.getRight().toString() + " AND "
+ area.getBottom().toString() + " < " + KEY_LOCALISATIONY
+ " AND " + KEY_LOCALISATIONY + " < "
+ area.getTop().toString();
if (!stopIds.isEmpty()) {
where = where + " AND " + PK_KEY_STOPID + " NOT IN (";
for (Integer id : stopIds) {
if (!where.endsWith("(")) {
where = where + ", ";
}
where = where + id.toString();
}
where = where + ")";
}
Cursor cursor = _database.query(true, TABLE_STOPS, columns, where,
null, null, null, null, null);
return cursor;
}
public Cursor getLine(int lineId) {
String[] columns = { PK_KEY_LINEID, KEY_NUMBER, FK_KEY_FROMID,
FK_KEY_DESTINATIONID };
String where = PK_KEY_LINEID + " = " + lineId;
Cursor cursor = _database.query(true, TABLE_LINES, columns, where,
null, null, null, null, null);
return cursor;
}
public Cursor getConnection(int stopId, int lineId) {
String[] columns = { FK_KEY_STOPID, FK_KEY_LINEID, KEY_ORDERID,
KEY_DAY, KEY_HOUR, KEY_MINUTE, KEY_LEGEND };
String where = FK_KEY_STOPID + "=" + stopId + " AND " + FK_KEY_LINEID
+ "=" + lineId;
Cursor cursor = _database.query(TABLE_CONNECTIONS, columns,
where, null, null, null, null);
return cursor;
}
public Cursor getStops(Character letter, List<Integer> ids) {
String[] columns = { PK_KEY_STOPID, KEY_LOCALISATIONX,
KEY_LOCALISATIONY, KEY_NAME };
String where = KEY_NAME + " LIKE '" + Character.toUpperCase(letter)
+ "%'";
if (ids.size() > 0) {
where += PK_KEY_STOPID + " IS NOT IN (";
for(Integer i : ids) {
if(! where.endsWith("(")) {
where += ", ";
}
where += i;
}
where += ")";
}
return _database.query(TABLE_STOPS, columns, where, null, null, null,
KEY_NAME);
}
public Cursor getLinesByStopId(int stopId) {
String[] columns = {FK_KEY_LINEID};
String where = FK_KEY_STOPID + "=" + stopId;
return _database.query(true ,TABLE_CONNECTIONS, columns, where, null, null, null,
KEY_ORDERID, null);
}
public Cursor getStopsByLineId(int lineId) {
String[] columns = {FK_KEY_STOPID};
String where = FK_KEY_LINEID + "=" + lineId;
return _database.query(true ,TABLE_CONNECTIONS, columns, where, null, null, null,
KEY_ORDERID, null);
}
public Cursor getLinesByLetter(Character letter) {
String[] columns = { PK_KEY_LINEID, KEY_NUMBER, FK_KEY_FROMID,
FK_KEY_DESTINATIONID };
String where = "CAST(" + KEY_NUMBER + " AS TEXT) LIKE '" + letter
+ "%'";
return _database.query(TABLE_LINES, columns, where, null, null, null,
null);
}
2.4.2 Projektowanie operacji na danych z poziomu Stop
private static Stop getStopFromCache(final int id) {
for (Stop stop : _stopList) {
if (stop.get_id() == id)
return stop;
}
return null;
}
public static Stop getStopFromCache(String name) {
for (Stop stop : _stopList) {
if (stop.getName() == name)
return stop;
}
return null;
}
private static Stop getStopFromCursor(final Cursor cursor) {
final int id = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.PK_KEY_STOPID));
final Pair<Double, Double> coordinates = new Pair<Double, Double>(
(double) cursor.getFloat(cursor
.getColumnIndex(DatabaseAdapter.KEY_LOCALISATIONY)),
(double) cursor.getFloat(cursor
.getColumnIndex(DatabaseAdapter.KEY_LOCALISATIONX)));
final String name = cursor.getString(cursor
.getColumnIndex(DatabaseAdapter.KEY_NAME));
return new Stop(id, coordinates, name);
}
private static List<Stop> changeCursorInToList(Cursor cursor) {
final List<Stop> stops = new ArrayList<Stop>();
if (cursor != null && cursor.moveToFirst())
while (true) {
stops.add(getStopFromCursor(cursor));
if (!cursor.moveToNext())
break;
}
return stops;
}
private static Stop getStopFromDB(final int id, final DatabaseAdapter dba) {
final Cursor cursor = dba.getStop(id);
Stop result = null;
if (cursor.moveToFirst())
result = getStopFromCursor(cursor);
cursor.close();
return result;
}
private static Stop getStopFromDB(final String name,
final DatabaseAdapter dba) {
final Cursor cursor = dba.getStop(name);
Stop result = null;
if (cursor.moveToFirst())
result = getStopFromCursor(cursor);
cursor.close();
return result;
}
public static Stop getStop(int id, DatabaseAdapter dba) {
Stop stop = getStopFromCache(id);
if (stop == null) {
stop = getStopFromDB(id, dba);
if (stop != null) {
_stopList.add(stop);
}
}
return stop;
}
public static Stop getStop(String name, DatabaseAdapter dba) {
Stop stop = getStopFromCache(name);
if (stop == null) {
stop = getStopFromDB(name, dba);
if (stop != null) {
_stopList.add(stop);
}
}
return stop;
}
public static Stop getStop(final Pair<Double, Double> coordination,
final DatabaseAdapter dba) {
Double radius = BASE_RADIUS;
Area area = null;
Stop stop = null;
while (stop == null || radius < 5 * BASE_RADIUS) {
Pair<Double, Double> topLeft = new Pair<Double, Double>(
coordination.first + radius, coordination.second - radius);
Pair<Double, Double> bottomRight = new Pair<Double, Double>(
coordination.first - radius, coordination.second + radius);
area = new Rectangle(topLeft, bottomRight);
final List<Stop> stopsFromDB = getStops(area, dba);
_stopList.addAll(stopsFromDB);
stop = getStopFromCache(coordination, radius);
radius = radius + BASE_RADIUS;
}
return stop;
}
private static Stop getStopFromCache(
final Pair<Double, Double> coordination, final Double radius) {
Pair<Double, Stop> result = new Pair<Double, Stop>(Double.MAX_VALUE,
null);
for (Stop stop : _stopList) {
Double distance = Math.sqrt(Math.pow(stop.get_coordinates().first
- coordination.first, 2)
+ Math.pow(stop.get_coordinates().second
- coordination.second, 2));
if (distance < radius && distance < result.first) {
result = new Pair<Double, Stop>(distance, stop);
}
}
return result.second;
}
private static List<Stop> getStopsFromCache(final Area area) {
final List<Stop> stopsInArea = new ArrayList<Stop>();
for (Stop stop : _stopList) {
if (area.isPointInArea(stop.get_coordinates()))
stopsInArea.add(stop);
}
return stopsInArea;
}
private static List<Stop> getStopsFromCache(final String name) {
final List<Stop> stopsByLetter = new ArrayList<Stop>();
for (Stop stop : _stopList) {
if (stop.getName().toUpperCase().startsWith(name.toUpperCase()))
stopsByLetter.add(stop);
}
return stopsByLetter;
}
private static List<Stop> getStopsFromDB(final Area area,
final DatabaseAdapter dba) {
final List<Integer> except = new ArrayList<Integer>();
final List<Stop> stopsInArea = getStopsFromCache(area);
for (Stop stop : stopsInArea)
except.add(stop.get_id());
Cursor cursor = dba.getStopsExcept(area, except);
final List<Stop> result = changeCursorInToList(cursor);
cursor.close();
return result;
}
private static int getStopIdFromCursor(final Cursor cursor) {
return (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.FK_KEY_STOPID));
}
private static List<Stop> getStopsByLineIdFromDB(final int lineId,
final DatabaseAdapter dba) {
final List<Stop> stops = new ArrayList<Stop>();
final Cursor cursor = dba.getStopsByLineId(lineId);
if (cursor != null && cursor.moveToFirst())
while (true) {
Stop result = getStop(getStopIdFromCursor(cursor), dba);
stops.add(result);
if (!cursor.moveToNext())
break;
}
cursor.close();
return stops;
}
private static List<Stop> getStopsFromDB(final Character letter,
final DatabaseAdapter dba) {
List<Integer> intsList = new ArrayList<Integer>();
List<Stop> stopsList = getStopsFromCache(letter.toString().toUpperCase());
for(Stop s : stopsList) {
intsList.add(s.get_id());
}
Cursor cursor = dba.getStops(letter, intsList);
List<Stop> sl = changeCursorInToList(cursor);
cursor.close();
return sl;
}
public static List<Stop> getStops(final Area area, final DatabaseAdapter dba) {
if (area != null) {
final List<Stop> stops = getStopsFromDB(area, dba);
_stopList.addAll(stops);
}
return getStopsFromCache(area);
}
public static List<Stop> getStops(final String name,
final DatabaseAdapter dba) {
Character letter = null;
if (name.length() > 0) {
letter = name.toUpperCase().charAt(0);
}
if (letter == null) {
return null;
}
if (!_letters.contains(letter)) {
final List<Stop> stops = getStopsFromDB(letter, dba);
_stopList.addAll(stops);
_letters.add(letter);
}
return getStopsFromCache(name);
}
public static List<Stop> getStopsByLineId(int lineId, DatabaseAdapter dba) {
return getStopsByLineIdFromDB(lineId, dba);
}
2.4.3 Projektowanie operacji na danych z poziomu Line
private static Line getLineFromCache(final int id) {
for (Line line : _lineList) {
if (line.get_id() == id)
return line;
}
return null;
}
private static List<Line> getLinesFromCache(final String name) {
final List<Line> linesByLetter = new ArrayList<Line>();
for (Line line : _lineList) {
if (new Integer(line.getNumber()).toString().toUpperCase()
.startsWith(name.toUpperCase()))
linesByLetter.add(line);
}
return linesByLetter;
}
private static Line getLineFromCursor(final Cursor cursor) {
final int id = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.PK_KEY_LINEID));
final int number = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.KEY_NUMBER));
final int from = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.FK_KEY_FROMID));
final int destination = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.FK_KEY_DESTINATIONID));
return new Line(id, number, from, destination);
}
private static int getLineIdFromCursor(final Cursor cursor) {
return (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.FK_KEY_LINEID));
}
private static List<Line> getLineByStopIdFromDB(final int stopId,
final DatabaseAdapter dba) {
final List<Line> lines = new ArrayList<Line>();
final Cursor cursor = dba.getLinesByStopId(stopId);
if (cursor != null && cursor.moveToFirst())
while (true) {
Line result = getLine(getLineIdFromCursor(cursor), dba);
lines.add(result);
if (!cursor.moveToNext())
break;
}
cursor.close();
return lines;
}
private static Line getLineFromDB(final int id, final DatabaseAdapter dba) {
final Cursor cursor = dba.getLine(id);
Line result = null;
if (cursor.moveToFirst())
result = getLineFromCursor(cursor);
cursor.close();
return result;
}
private static List<Line> changeCursorInToList(Cursor cursor) {
final List<Line> lines = new ArrayList<Line>();
if (cursor != null && cursor.moveToFirst())
while (true) {
lines.add(getLineFromCursor(cursor));
if (!cursor.moveToNext())
break;
}
return lines;
}
private static List<Line> getLinesFromDB(final Character letter,
final DatabaseAdapter dba) {
List<Integer> intsList = new ArrayList<Integer>();
List<Line> linesList = getLinesFromCache(letter.toString().toUpperCase());
for(Line s : linesList) {
intsList.add(s.get_id());
}
Cursor cursor = dba.getLinesByLetter(letter);
List<Line> ll = changeCursorInToList(cursor);
cursor.close();
return ll;
}
public static Line getLine(int id, DatabaseAdapter dba) {
Line line = getLineFromCache(id);
if (line == null) {
line = getLineFromDB(id, dba);
if (line != null) {
_lineList.add(line);
}
}
return line;
}
public static List<Line> getLines(final String name,
final DatabaseAdapter dba) {
Character letter = null;
if (name.length() > 0) {
letter = name.toUpperCase().charAt(0);
}
if (letter == null) {
return null;
}
if (!_letters.contains(letter)) {
final List<Line> stops = getLinesFromDB(letter, dba);
_lineList.addAll(stops);
_letters.add(letter);
}
return getLinesFromCache(name);
}
public static List<Line> getLinesByStopId(int stopId, DatabaseAdapter dba) {
return getLineByStopIdFromDB(stopId, dba);
}
2.4.4 Projektowanie operacji na danych z poziomu Connection
private static Connection getConnectionFromCache(final int stopId,
final int lineId) {
for (Connection conn : _connectionList) {
if (conn.get_stopId() == stopId && conn.get_lineId() == lineId)
return conn;
}
return null;
}
private void addTime(Pair<String, Pair<Integer, Integer>> time) {
if (_TimeTable.containsKey(time.first)) {
List<Pair<Integer, Integer>> timeList = _TimeTable.get(time.first);
timeList.add(time.second);
_TimeTable.put(time.first, timeList);
} else {
List<Pair<Integer, Integer>> timeList = new ArrayList<Pair<Integer, Integer>>();
timeList.add(time.second);
_TimeTable.put(time.first, timeList);
}
}
private static Connection getConnectionFromCursor(final Cursor cursor) {
final int stopId = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.FK_KEY_STOPID));
final int lineId = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.FK_KEY_LINEID));
final int orderId = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.KEY_ORDERID));
final String legend = cursor.getString(cursor
.getColumnIndex(DatabaseAdapter.KEY_LEGEND));
return new Connection(stopId, lineId, orderId, legend);
}
private static Pair<String, Pair<Integer, Integer>> getTimeFromCursor(
final Cursor cursor) {
final String day = cursor.getString(cursor
.getColumnIndex(DatabaseAdapter.KEY_DAY));
final int hour = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.KEY_HOUR));
final int minute = (int) cursor.getLong(cursor
.getColumnIndex(DatabaseAdapter.KEY_MINUTE));
return new Pair<String, Pair<Integer, Integer>>(day,
new Pair<Integer, Integer>(hour, minute));
}
private static Connection getConnectionFromDB(final int stopId,
final int lineId, final DatabaseAdapter dba) {
final Cursor cursor = dba.getConnection(stopId, lineId);
Connection result = null;
if (cursor.moveToFirst())
result = getConnectionFromCursor(cursor);
while (true) {
result.addTime(getTimeFromCursor(cursor));
if (!cursor.moveToNext())
break;
}
cursor.close();
return result;
}
public static Connection getConnection(int stopId, int lineId,
DatabaseAdapter dba) {
Connection conn = getConnectionFromCache(stopId, lineId);
if (conn == null) {
conn = getConnectionFromDB(stopId, lineId, dba);
if (conn != null) {
_connectionList.add(conn);
}
}
return getConnectionFromDB(stopId, lineId, dba);
}
3. Raport końcowy
3.1 Implementacja bazy danych
Baza danych aplikacji mobilnej została zaimplementowana z wykorzystaniem motoru bazy SQLite. Elementy tworzące bazę, zostały zaprezentowane w projekcie logicznym.
3.2 Interfejsy do obsługi danych
W aplikacji istnieją następujące przepływy danych przez widoku (aktywności Androida):
Mapa
odnalezienie pobliskiego przystanku na mapie (widok mapy)
wybranie odpowiadającej użytkownikowi linii (widok linii dla danego przystanka)
sprawdzenie godzin odjazdów danej linii z danego przystanka (widok połączenia)
Wyszukiwarka przystanków
wyszukanie przystanku po nazwie (widok wyszukiwarki)
wybranie odpowiadającej użytkownikowi linii (widok linii dla danego przystanka)
sprawdzenie godzin odjazdów danej linii z danego przystanka (widok połączenia)
Wyszukiwarka przystanków
wyszukanie przystanku po numerze (widok wyszukiwarki)
wybranie odpowiadającego użytkownikowi przystanku (widok przystanków dla danej linii)
sprawdzenie godzin odjazdów danej linii z danego przystanka (widok połączenia)
Każdorazowo pobierane są odpowiednie dane z bazy (przystanki, linie, połączenia) ze zminimalizowanym obciążeniem bazy danych poprzez zastosowanie interfejsu lazy load (dane inicjowane są dopiero gdy są potrzebne i są przechowywane żeby uniknąć niepotrzebnych zapytać do bazy).
Widok mapy
Widok linii przy wyszukiwaniu
Widok linii dla danego przystanka
3.3 Dalszy rozwój aplikacji mobilnej
Aplikacja aktualnie spełnia wszystkie podstawowe założenia, które założyliśmy w fazie projektowej. W przyszłości planujemy rozwój:
wyznaczanie tras dojścia do najbliższych przystanków
wyświetlanie czasu odjazdów dla aktualnej i zadanej pory
wyświetlanie tras przejazdu danej linii
3.4 Wnioski
Google dostarczyło nam narzędzie, które jest łatwo przyswajalne i dobrze opisane w dokumentacji wraz z licznymi przykładami użycia. Sprawiło to, że napisanie aplikacji na system Android nie sprawiło zespołowi problemów.
SQLite jako baza danych dla aplikacji mobilnych wydaje się odpowiednim wyborem. Nie dostarcza co prawda zbyt wielu funkcjonalności, ale jest prosta w obsłudze, a do tego lekka (co powoduje małe zużycie energii co w aplikacjach mobilnych jest kluczowe) i szybka.
3.4 Literatura