/**
* MapData
* Class which provides interfaces to access the underlying JSON data. The actual
* JSON parsing is done by MapJSONParser, and this class interfaces with that class
* through the cursors it provides.
*
* To add a new building: Include it in the appropriate JSON file. That's it.
* To add a new building category:
* -> create a new json file for that category
* -> define that json file in schemas.json
* -> create a static string for that category here under static variables
* -> modify getBuildingCategories(), getBuildings(), and getBuildings(String) appropriately
* To add a new layer:
* -> create the json file and define it in schemas.json
* -> create a static string for that layer here under static variables
* -> modify getLayersList() and getLayerPoints(String) appropriately
* To add a new bus route:
* -> create the json file and define it in schemas.json
* -> create a static string for that route here under static variables
* -> modify getBusRoutes() and getBusRoute(String) appropriately
*
* Noticing a pattern?
* You dont ever have to touch MapJSONParser unless your JSON is really weird, and if your JSON
* is formatted exactly like the others we already have (your bus route is like all the other bus
* routes, as an example), it's like 4 lines of code to add in here.
*/
package edu.purdue.app.map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.google.android.gms.maps.model.LatLng;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.widget.Toast;
public class MapData {
/** ===== STATIC VARIABLES ===== */
public final static double PURDUE_CENTER_LAT = 40.427231;
public final static double PURDUE_CENTER_LNG = -86.916683;
public final static double PURDUE_NORTHSIDE_BOUND_LAT = 40.5;
public final static double PURDUE_SOUTHSIDE_BOUND_LAT = 40.3;
public final static double PURDUE_WESTSIDE_BOUND_LNG = -87.0;
public final static double PURDUE_EASTSIDE_BOUND_LNG = -86.8;
public final static float PURDUE_CAMPUS_ZOOM = 14.7f;
// Constants to access building categories
public final static String BLDG_CAT_ACADEMIC = "Academic Buildings";
public final static String BLDG_CAT_ADMIN = "Administrative Buildings";
public final static String BLDG_CAT_RESHALL = "Residence Halls";
public final static String BLDG_CAT_DINING = "Dining Courts";
public final static String BLDG_CAT_PARKING = "Parking Garages";
public final static String BLDG_CAT_MISC = "Misc. Buildings";
// Constants for every layer recognized by the app
public final static String LAYER_BIKERACKS = "Bike Racks";
// Constants for every bus route recognized by the app
public final static String BUSROUTE_GOLDLOOP = "Gold Loop";
/** ===== STATIC METHODS ===== */
/** Determines whether a given LatLng point is inside of the Purdue Campus. */
public static boolean isNearPurdue(double lat, double lng) {
if (lat <= PURDUE_NORTHSIDE_BOUND_LAT && lat >= PURDUE_SOUTHSIDE_BOUND_LAT) {
if (lng <= PURDUE_EASTSIDE_BOUND_LNG && lng >= PURDUE_WESTSIDE_BOUND_LNG) {
return true;
}
}
return false;
}
/** Determines how far away two LatLng points are from one-another in kilometers. */
public static double distanceBetween(double lat1, double lng1, double lat2, double lng2) {
double dLat = Math.toRadians(lat2-lat1);
double dLng = Math.toRadians(lng2-lng1);
double step1 = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLng/2) * Math.sin(dLng/2) * Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2));
double step2 = 6371 * 2 * Math.atan2(Math.sqrt(step1), Math.sqrt(1-step1));
return step2;
}
/** ===== INTERNAL CLASS DEFINITIONS ===== */
class Building {
String full_name, short_name;
double lat, lng;
}
/** ===== OBJECT MEMBERS ===== */
/** JSON Parser which contains all the data we need */
private MapJSONParser json;
/** Data structures which contain information about locations around campus
* Because this information never changes, these kind of act as "cache" structures; they
* start as null and are generated whenever their respective getters are called. If the
* getter is called again, it doesn't have to be generated again. */
private List<String> buildingCategories, layersList, busRoutes;
private List<Building> allBuildings;
private List<Building> academicBuildings, adminBuildings, resHalls, diningCourts, parking, miscBuildings;
private List<LatLng> bikeRacks;
private List<LatLng> goldLoop;
/** ===== OBJECT METHODS ===== */
/** Constructor which parses the included json files into Location array lists */
public MapData(Context c) {
json = new MapJSONParser(c);
}
/** Returns a printable list of all the different building categories */
public List<String> getBuildingCategories() {
if (buildingCategories != null) {
return buildingCategories;
}
// Generate category list
buildingCategories = new ArrayList<String>();
buildingCategories.add(BLDG_CAT_ACADEMIC);
buildingCategories.add(BLDG_CAT_ADMIN);
buildingCategories.add(BLDG_CAT_RESHALL);
buildingCategories.add(BLDG_CAT_DINING);
buildingCategories.add(BLDG_CAT_PARKING);
buildingCategories.add(BLDG_CAT_MISC);
return buildingCategories;
}
/** Returns an unordered list of all the buildings on campus */
public List<Building> getBuildings() {
if (allBuildings != null) {
return allBuildings;
}
allBuildings = new ArrayList<Building>();
for (Building b : getBuildings(BLDG_CAT_ACADEMIC)) {
allBuildings.add(b);
}
for (Building b : getBuildings(BLDG_CAT_ADMIN)) {
allBuildings.add(b);
}
for (Building b : getBuildings(BLDG_CAT_RESHALL)) {
allBuildings.add(b);
}
for (Building b : getBuildings(BLDG_CAT_DINING)) {
allBuildings.add(b);
}
for (Building b : getBuildings(BLDG_CAT_PARKING)) {
allBuildings.add(b);
}
for (Building b : getBuildings(BLDG_CAT_MISC)) {
allBuildings.add(b);
}
return allBuildings;
}
/** Returns a list of buildings on campus under the specific category.
* Pass in one of the static BLDG_ strings in this class. */
public List<Building> getBuildings(String category) {
// Get the list and cursor for the category selected
List<Building> selL = null;
Cursor selC = null;
if (category.equals(BLDG_CAT_ACADEMIC)) {
selL = academicBuildings;
selC = json.fileCursorMap.get("buildings_acad");
} else if (category.equals(BLDG_CAT_ADMIN)) {
selL = adminBuildings;
selC = json.fileCursorMap.get("buildings_admin");
} else if (category.equals(BLDG_CAT_RESHALL)) {
selL = resHalls;
selC = json.fileCursorMap.get("buildings_reshall");
} else if (category.equals(BLDG_CAT_DINING)) {
selL = diningCourts;
selC = json.fileCursorMap.get("buildings_dining");
} else if (category.equals(BLDG_CAT_PARKING)) {
selL = parking;
selC = json.fileCursorMap.get("buildings_parking");
} else if (category.equals(BLDG_CAT_MISC)) {
selL = miscBuildings;
selC = json.fileCursorMap.get("buildings_misc");
}
// If the cursor is null, return null
if (selC == null) {
return null;
}
// If the list is not null, return it
if (selL != null) {
return selL;
}
// Otherwise, generate the building list
selL = new ArrayList<Building>();
selC.moveToFirst();
do {
Building b = new Building();
b.short_name = selC.getString(0);
b.full_name = selC.getString(1);
b.lat = selC.getDouble(2);
b.lng = selC.getDouble(3);
selL.add(b);
} while (selC.moveToNext());
// Alphabetize the buildings
Collections.sort(selL, new Comparator<Building>() {
public int compare(Building arg0, Building arg1) {
return arg0.full_name.compareTo(arg1.full_name);
}
});
return selL;
}
/** Returns a list of all the layers recognized by the application */
public List<String> getLayersList() {
if (layersList != null) {
return layersList;
}
// Generate layers list
layersList = new ArrayList<String>();
layersList.add(LAYER_BIKERACKS);
return layersList;
}
/** Returns a list of LatLng points for the requested layer
* Use the static LAYER_ constants inside this class as your argument */
public List<LatLng> getLayerPoints(String layer) {
// Get the list and cursor for the layer selected
List<LatLng> selL = null;
Cursor selC = null;
// Find the one we selected
if (layer.equals(LAYER_BIKERACKS)) {
selL = bikeRacks;
selC = json.fileCursorMap.get("layer_bikeracks");
}
// If the cursor is null, return null
if (selC == null) {
return null;
}
// If the list isn't null, just return it
if (selL != null) {
return selL;
}
// Otherwise, generate the list
selL = new ArrayList<LatLng>();
selC.moveToFirst();
do {
double lat = selC.getDouble(0);
double lng = selC.getDouble(1);
LatLng point = new LatLng(lat, lng);
selL.add(point);
} while (selC.moveToNext());
return selL;
}
/** Returns a printable list of all the bus routes recognized by the app */
public List<String> getBusRoutes() {
if (busRoutes != null) {
return busRoutes;
}
// Generate the bus routes list
busRoutes = new ArrayList<String>();
busRoutes.add(BUSROUTE_GOLDLOOP);
return busRoutes;
}
/** Returns the requested list of latlng points for a given bus route */
public List<LatLng> getBusRoute(String route) {
// Get a generic list and cursor
List<LatLng> selL = null;
Cursor selC = null;
// Find the one we selected
if (route.equals(BUSROUTE_GOLDLOOP)) {
selL = goldLoop;
selC = json.fileCursorMap.get("bus_goldloop");
}
// If the cursor is null, return null
if (selC == null) {
return null;
}
// If the list isn't null, return it
if (selL != null) {
return selL;
}
// Otherwise, generate the list
selL = new ArrayList<LatLng>();
selC.moveToFirst();
do {
double lat = selC.getDouble(0);
double lng = selC.getDouble(1);
selL.add(new LatLng(lat, lng));
} while (selC.moveToNext());
return selL;
}
/** Searches for a building in the map data given a string
* Very poorly implemented right now, will be improved later. */
public Building searchBuilding(String s) {
s = s.toLowerCase();
Building result;
Log.d(MapActivity.LOG_MAP_TAG, "SEARCH QUERY" + s);
for (Building b : getBuildings()) {
Log.d(MapActivity.LOG_MAP_TAG, b.full_name);
}
if ((result = searchExactMatch(s)) != null) {
return result;
}
if ((result = searchPartialMatch(s)) != null) {
return result;
}
return null;
}
/** Finds exact string matches (not including case) across all the buildings */
private Building searchExactMatch(String s) {
for (Building b : getBuildings()) {
if (b.full_name.toLowerCase().equals(s) || b.short_name.toLowerCase().equals(s)) {
return b;
}
}
return null;
}
/** Finds partial string matches across all the buildings */
private Building searchPartialMatch(String s) {
for (Building b : getBuildings()) {
if (b.full_name.toLowerCase().contains(s) || b.short_name.toLowerCase().contains(s)) {
return b;
}
}
return null;
}
}