Przystanki

1. Projekt konceptualny

1.1 Sformułowanie zadania projektowego

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

  1. Must
    1. Lokalizacja położenia posiadacza aplikacji i naniesienie jego pozycji na mapę
    2. Odnalezienie najbliżej znajdujących się przystanków względem położenia posiadacza aplikacji i naniesienie ich pozycji na mapę
    3. 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
  1. Could
    1. Wyznaczenie trasy przejścia od miejsca znajdowania się posiadacza aplikacji do wybranego przystanku
    2. Lokalizacja dowolnego przystanka za pomocą wyszukiwarki
  1. Won’t
    1. Wyznaczenie dogodnych połączeń z punktu startowego do punktu końcowego podróży

1.4 Określenie scenariuszy użycia

  1. Geolokalizacja
    1. Włączenie aplikacji
    2. Wybranie opcji geolokalizacji
    3. Odnalezienie pozycji użytkownika
    4. Odnalezienie najbliższych przystanków
    5. Wyświetlenie znaczników na mapie
  1. Sprawdzenie kursów danej linii MPK
    1. Włączenie aplikacji
    2. Wybranie opcji wyszukiwania połączeń po linach
    3. Wybranie linii
    4. Wybranie przystanku
    5. Wyświetlenie podziału godzinowego
  1. Sprawdzenie kursów z danego przystanka MPK
    1. Włączenie aplikacji
    2. Wybranie opcji wyszukiwania połączeń po przystankach
    3. Wybranie przystanka
    4. Wybranie linii
    5. Wyświetlenie podziału godzinowego

1.5 Identyfikacja funkcji

Istnieją trzy możliwości użycia aplikacji:

  1. Geolokalizacja – urządzenie za pomocą GPS sprawdza nasze położenie i względem znanej lokalizacji przystanków znajduje dla nas te najbliższe
  2. Sprawdzenie kursów danej linii MPK – po wybraniu odpowiedniej linii komunikacyjnej dowiadujemy się o której godzinie pojawia się na konkretnych przystankach
  3. 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

  1. Stops
    1. stop_id (int) PK
    2. localisationX (real)
    3. localisationY (real)
    4. name (text)
  1. Lines
    1. line_id (int) PK
    2. number (int)
    3. from_id (int) FK (references Przystanki(stop _id))
    4. destination_id (int) FK (references Przystanki(stop _id))
  1. Connections
    1. stop_id (int) FK (references Stops(stop _id))
    2. line_id (int) FK (references Lines(line _id))
    3. day (text)
    4. hour (int)
    5. minute (int)
    6. legend (text)
    7. 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

  1. gdy musimy je wyświetlić na mapie
  2. 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

  1. Stops
    1. stop_id (int) PK - unikalny identyfikator przystanka
    2. localisationX (real) - szerokość geograficzna przystanka
    3. localisationY (real) - długość geograficzna przystanka
    4. name (text) - nazwa przystanka
  1. Lines
    1. line_id (int) PK - unikalny identyfikator linii
    2. number (int) - numer linii
    3. from_id (int) FK (references Przystanki(stop _id)) - przystanek źródłowy linii
    4. destination_id (int) FK (references Przystanki(stop _id)) - przystanek docelowy linii
  1. Connections
    1. stop_id (int) FK (references Stops(stop _id)) - referencja do identyfikatora przystanka
    2. line_id (int) FK (references Lines(line _id)) - referencja do identyfikatora linii
    3. day (text) - rodzaj dnia tygodnia dla tabel czasowych MPK
    4. hour (int) - godzina dla poszczególnego elementu tabel czasowych MPK
    5. minute (int) - minuta dla poszczególnego elementu tabel czasowych MPK
    6. legend (text) - dodatkowe informacje dla poszczególnego elementu tabel czasowych MPK
    7. 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):

  1. Mapa
    1. odnalezienie pobliskiego przystanku na mapie (widok mapy)
    2. wybranie odpowiadającej użytkownikowi linii (widok linii dla danego przystanka)
    3. sprawdzenie godzin odjazdów danej linii z danego przystanka (widok połączenia)
  2. Wyszukiwarka przystanków
    1. wyszukanie przystanku po nazwie (widok wyszukiwarki)
    2. wybranie odpowiadającej użytkownikowi linii (widok linii dla danego przystanka)
    3. sprawdzenie godzin odjazdów danej linii z danego przystanka (widok połączenia)
  3. Wyszukiwarka przystanków
    1. wyszukanie przystanku po numerze (widok wyszukiwarki)
    2. wybranie odpowiadającego użytkownikowi przystanku (widok przystanków dla danej linii)
    3. 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:

  1. wyznaczanie tras dojścia do najbliższych przystanków
  2. wyświetlanie czasu odjazdów dla aktualnej i zadanej pory
  3. 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

2011/09/11 17:03
pl/dydaktyka/ztb/2011/projekty/przystanki.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0