diff --git a/src/it/reyboz/bustorino/middleware/StopsDB.java b/src/it/reyboz/bustorino/middleware/StopsDB.java index cbb71c5..8f0df0f 100644 --- a/src/it/reyboz/bustorino/middleware/StopsDB.java +++ b/src/it/reyboz/bustorino/middleware/StopsDB.java @@ -1,238 +1,306 @@ /* BusTO ("backend" components) Copyright (C) 2016 Ludovico Pavesi This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package it.reyboz.bustorino.middleware; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.readystatesoftware.sqliteasset.SQLiteAssetHelper; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import it.reyboz.bustorino.backend.Route; import it.reyboz.bustorino.backend.Stop; import it.reyboz.bustorino.backend.StopsDBInterface; public class StopsDB extends SQLiteAssetHelper implements StopsDBInterface { private static String QUERY_TABLE_stops = "stops"; private static String QUERY_WHERE_ID = "ID = ?"; + private static String QUERY_WHERE_LAT_AND_LNG_IN_RANGE = "lat >= ? AND lat <= ? AND lon >= ? AND lon <= ?"; private static String[] QUERY_COLUMN_name = {"name"}; private static String[] QUERY_COLUMN_location = {"location"}; private static String[] QUERY_COLUMN_route = {"route"}; private static String[] QUERY_COLUMN_everything = {"name", "location", "type", "lat", "lon"}; + private static String[] QUERY_COLUMN_everything_and_ID = {"ID", "name", "location", "type", "lat", "lon"}; private static String DB_NAME = "stops.sqlite"; private static int DB_VERSION = 1; private SQLiteDatabase db; private AtomicInteger openCounter = new AtomicInteger(); public StopsDB(Context context) { super(context, DB_NAME, null, DB_VERSION); // WARNING: do not remove the following line, do not save anything in this database, it will be overwritten on every update! setForcedUpgrade(); // remove old database (BusTo version 1.8.5 and below) File filename = new File(context.getFilesDir(), "busto.sqlite"); if(filename.exists()) { //noinspection ResultOfMethodCallIgnored filename.delete(); } } /** * Through the magic of an atomic counter, the database gets opened and closed without race * conditions between threads (HOPEFULLY). * * @return database or null if cannot be opened */ @Nullable public synchronized SQLiteDatabase openIfNeeded() { openCounter.incrementAndGet(); this.db = getReadableDatabase(); return this.db; } /** * Through the magic of an atomic counter, the database gets really closed only when no thread * is using it anymore (HOPEFULLY). */ public synchronized void closeIfNeeded() { // is anybody still using the database or can we close it? if(openCounter.decrementAndGet() <= 0) { super.close(); this.db = null; } } public List getRoutesByStop(@NonNull String stopID) { String[] uselessArray = {stopID}; int count; Cursor result; if(this.db == null) { return null; } try { result = this.db.query("routemap", QUERY_COLUMN_route, "stop = ?", uselessArray, null, null, null); } catch(SQLiteException e) { return null; } count = result.getCount(); if(count == 0) { return null; } List routes = new ArrayList<>(count); while(result.moveToNext()) { routes.add(result.getString(0)); } result.close(); return routes; } public String getNameFromID(@NonNull String stopID) { String[] uselessArray = {stopID}; int count; String name; Cursor result; if(this.db == null) { return null; } try { result = this.db.query(QUERY_TABLE_stops, QUERY_COLUMN_name, QUERY_WHERE_ID, uselessArray, null, null, null); } catch(SQLiteException e) { return null; } count = result.getCount(); if(count == 0) { return null; } result.moveToNext(); name = result.getString(0); result.close(); return name; } public String getLocationFromID(@NonNull String stopID) { String[] uselessArray = {stopID}; int count; String name; Cursor result; if(this.db == null) { return null; } try { result = this.db.query(QUERY_TABLE_stops, QUERY_COLUMN_location, QUERY_WHERE_ID, uselessArray, null, null, null); } catch(SQLiteException e) { return null; } count = result.getCount(); if(count == 0) { return null; } result.moveToNext(); name = result.getString(0); result.close(); return name; } public Stop getAllFromID(@NonNull String stopID) { Cursor result; int count; Stop s; if(this.db == null) { return null; } try { result = this.db.query(QUERY_TABLE_stops, QUERY_COLUMN_everything, QUERY_WHERE_ID, new String[] {stopID}, null, null, null); int colName = result.getColumnIndex("name"); int colLocation = result.getColumnIndex("location"); int colType = result.getColumnIndex("type"); int colLat = result.getColumnIndex("lat"); int colLon = result.getColumnIndex("lon"); count = result.getCount(); if(count == 0) { return null; } result.moveToNext(); Route.Type type = routeTypeFromSymbol(result.getString(colType)); String locationWhichSometimesIsAnEmptyString = result.getString(colLocation); if(locationWhichSometimesIsAnEmptyString.length() <= 0) { locationWhichSometimesIsAnEmptyString = null; } s = new Stop(stopID, result.getString(colName), null, locationWhichSometimesIsAnEmptyString, type, getRoutesByStop(stopID), result.getDouble(colLat), result.getDouble(colLon)); } catch(SQLiteException e) { return null; } result.close(); return s; } + /** + * Query some bus stops inside a map view + * + * You can obtain the coordinates from OSMDroid using something like this: + * BoundingBoxE6 bb = mMapView.getBoundingBox(); + * double latFrom = bb.getLatSouthE6() / 1E6; + * double latTo = bb.getLatNorthE6() / 1E6; + * double lngFrom = bb.getLonWestE6() / 1E6; + * double lngTo = bb.getLonEastE6() / 1E6; + */ + public Stop[] queryAllInsideMapView(double minLat, double maxLat, double minLng, double maxLng) { + Stop[] stops = new Stop[0]; + + Cursor result; + int count; + + // coordinates must be strings in the where condition + String minLatRaw = String.valueOf(minLat); + String maxLatRaw = String.valueOf(maxLat); + String minLngRaw = String.valueOf(minLng); + String maxLngRaw = String.valueOf(maxLng); + + String stopID; + Route.Type type; + + if(this.db == null) { + return stops; + } + + try { + result = this.db.query(QUERY_TABLE_stops, QUERY_COLUMN_everything_and_ID, QUERY_WHERE_LAT_AND_LNG_IN_RANGE, new String[] {minLatRaw, maxLatRaw, minLngRaw, maxLngRaw}, null, null, null); + + int colID = result.getColumnIndex("ID"); + int colName = result.getColumnIndex("name"); + int colLocation = result.getColumnIndex("location"); + int colType = result.getColumnIndex("type"); + int colLat = result.getColumnIndex("lat"); + int colLon = result.getColumnIndex("lon"); + + count = result.getCount(); + stops = new Stop[count]; + + int i = 0; + while(result.moveToNext()) { + + stopID = result.getString(colID); + type = routeTypeFromSymbol(result.getString(colType)); + + String locationWhichSometimesIsAnEmptyString = result.getString(colLocation); + if (locationWhichSometimesIsAnEmptyString.length() <= 0) { + locationWhichSometimesIsAnEmptyString = null; + } + + stops[i++] = new Stop(stopID, result.getString(colName), null, locationWhichSometimesIsAnEmptyString, type, getRoutesByStop(stopID), result.getDouble(colLat), result.getDouble(colLon)); + } + + } catch(SQLiteException e) { + // TODO: put a warning in the log + return stops; + } + + result.close(); + + return stops; + } + /** * Get a Route Type from its char symbol * * @param route The route symbol (e.g. "B") * @return The related Route.Type (e.g. Route.Type.Bus) */ public static Route.Type routeTypeFromSymbol(String route) { switch (route) { case "M": return Route.Type.METRO; case "T": return Route.Type.RAILWAY; } // default with case "B" return Route.Type.BUS; } }