I have 16000 Locations in my Sql Lite DB.
And I am fetching all into cursor and setting each into LOCATION object and I am using distaneTo() method of LOCATION class.
Is there any best way to do it to find nearest Location based on user's location.
My Code::::
Location userLocation = new Location("Point A");
userLocation.setLatitude(latitude);
userLocation.setLongitude(longitude);
Location stopLocation = new Location("Point B");
List<FavouriteStop> allFavourites = new ArrayList<FavouriteStop>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor c = db.rawQuery(
"Select * from tbl_stop order by routeName desc", null);
FavouriteStop stop = new FavouriteStop();
// looping through all rows and adding to list
if (c.moveToFirst()) {
do {
if (c.getString(c.getColumnIndex("stopId")) != null) {
// Setting Stop Location for Distance
stopLocation.setLatitude(c.getDouble(c
.getColumnIndex("lat")));
stopLocation.setLongitude(c.getDouble(c
.getColumnIndex("lon")));
float distance = userLocation.distanceTo(stopLocation);
if (distance < radius) {
if (allStopsHash.containsKey(c.getString(c
.getColumnIndex("routeName")))) {
// Contains
} else {
// Not Contain
stop.setFirstElement(true);
allStopsHash.put(
c.getString(c.getColumnIndex("routeName")),
c.getString(c.getColumnIndex("routeName")));
}
stop.settag(c.getString(c.getColumnIndex("tag")));
stop.settitle(c.getString(c.getColumnIndex("title")));
stop.setlat(c.getDouble(c.getColumnIndex("lat")));
stop.setlon(c.getDouble(c.getColumnIndex("lon")));
stop.setstopId(c.getString(c.getColumnIndex("stopId")));
stop.setDirection(c.getString(c
.getColumnIndex("towards")));
stop.setRouteName(c.getString(c
.getColumnIndex("routeName")));
stop.setDistance((distance / 1000));
allFavourites.add(stop);
// }
}// Distance Radius Over
}// If StopId != Null Over
} while (c.moveToNext());
}
db.close();
SQLite can't do queries sorted by distance based on coordinates. Iterating through the points is the right solution in this case. However, you can improve your implementation by caching the column indexes. Instead of calling multiple times getColumnIndex("lat") for example you can have something like
int latIndex = getColumnIndex("lat");
and than always use latIndex. I've never done tests to check the performance improvement but it's certainly the better way of doing it.
Related
I'm having a problem with my database and loading markers in my Maps activity. Right now, I have a database holding MapsMarkers (my own class of marker) and a column called "marker_string_id" which holds the ID of the Google markers, which are generated as following: m0, m1, m2, etc. I use this to delete my MapsMarkers, as I delete by the Google marker id (marker_string_id). This works great until the user decides to create more markers and delete more markers. Each time the application is reloaded, the Google markers' IDs are reset, but the MapsMarker marker_string_id's are not. Ex. You have 3 markers, with ID m0, m1, m2. Delete one and reload the application, now you have 2 markers with ID m0, m1 but their IDs in the database are m1, m2. Deleting them no longer works. How can I make it so that the markers load with the correct ID?
Here is my method in my maps activity for loading the markers:
/**
* Method that adds the actual Google marker to the map in loadmarkers
* #param lat
*/
private void loadMarkersAdd(LatLng lat) {
Marker uMarker = Map.addMarker(new MarkerOptions()
.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)) //Blue marker
.position(lat) //Places marker at hold area
.snippet(description));
String acc = uMarker.getId();
Log.i(LOGTAG, "New marker ID is " + acc);
}
/**
* Method for loading the markers onto the map in onResume, onCreate
*/
private void loadMarkers() {
try {
databaseHelper.open();
} catch (Exception e) {
Log.i(LOGTAG, "Loadmarkers DBOpen failed");
}
List<MapsMarker> markersList = databaseHelper.getAllMarkers();
for(int i = 0; i < markersList.size(); i ++) {
LatLng lat = new LatLng(markersList.get(i).getLat(), markersList.get(i).getLon());
String userMarkerIds = markersList.get(i).getId();
if (userMarkerLatLng.contains(lat) || userMarkerIDs.contains(userMarkerIds)) {
Log.i(LOGTAG, "Breaking contains");
break;
} else {
Log.i(LOGTAG, "Adding google maps marker");
loadMarkersAdd(lat); //Loads google maps markers if the marker isnt already there, prevents multiple google markers from being added
}
Log.i(LOGTAG, "LoadMarks looping");
}
}
My method for getting all markers from the database (used when loading markers):
/**
* Returns a list of all markers currently in the database
*
* #return
*/
public List<MapsMarker> getAllMarkers() { //Cursor is basically the "pointer" where the database is looking at on the table at a given moment
List<MapsMarker> markers = new ArrayList<>();
try {
open();
} catch (Exception e) {
Log.i(LOGTAG, "Database opening failed");
}
Cursor cursor = db.query(TABLE_LOCATION, allMarkerColumns,
null, null, null, null, null);
cursor.moveToFirst();
Log.i(LOGTAG, "Returned " + cursor.getCount() + " marker table rows");
if (cursor.getCount() > 0) {
Log.i(LOGTAG, "GetAllMarkers if statement was run");
while (!cursor.isAfterLast()) { //while successfully moves to new row in table, return true
Log.i(LOGTAG, "While loop running");
MapsMarker marker = new MapsMarker(null, null, null);
marker.setId(cursor.getString(cursor.getColumnIndex(COLUMN_MARKER_ID)));
marker.setLat(cursor.getDouble(cursor.getColumnIndex(COLUMN_LAT)));
marker.setLon(cursor.getDouble(cursor.getColumnIndex(COLUMN_LONGITUDE)));
markers.add(marker);
cursor.moveToNext();
}
}
Log.i(LOGTAG, "Closing getAllMarkers cursor");
cursor.close(); //frees up cursor
close();
return markers;
}
And finally, my save and delete methods:
/**
* Method for adding a marker to the database and returning its ID if needed (for deletion, find, etc.)
* #return
*/
public void saveMapsMarker(MapsMarker marker) {
open();
ContentValues values = new ContentValues(); //Dont .put COLUMN_MARKER_ID because it is autoincrementing
values.put(COLUMN_MARKER_STRING_ID, marker.getId());
values.put(COLUMN_LAT, marker.getLat());
values.put(COLUMN_LONGITUDE, marker.getLon());
db.insert(TABLE_LOCATION, null, values);
close();
}
/**
* Method for deleting marker by ID
*
* #param id
*/
public void deleteMapsMarker(String id) {
open();
db.delete(TABLE_LOCATION,
COLUMN_MARKER_STRING_ID + "=?", new String[]{String.valueOf(id)});
close();
}
EDIT:
Here are the new methods I've added. It sort of works, but I'm getting the same problem again. Now, when I add a new marker to the map to check its ID against the IDs of the markers in the database, the new marker's ID is reset as well.
private void loadMarkers() {
try {
MyApplication.getDBHelper().open();
} catch (Exception e) {
Log.i(LOGTAG, "Loadmarkers DBOpen failed");
}
MapsMarker mapsMarker = new MapsMarker();
List<MapsMarker> markersList = mapsMarker.getAllMarkers();
for(int i = 0; i < markersList.size(); i ++) {
LatLng lat = new LatLng(markersList.get(i).getLat(), markersList.get(i).getLon());
Marker IdMarker = Map.addMarker(new MarkerOptions().position(lat)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.building_icon)));
MapsMarker replaceMapsMarker = new MapsMarker(); //Create new placeholder mapsMarker to check against database ID
replaceMapsMarker.setId(IdMarker.getId());
replaceMapsMarker.setLat(IdMarker.getPosition().latitude);
replaceMapsMarker.setLon(IdMarker.getPosition().longitude);
if(replaceMapsMarker.getLat() == markersList.get(i).getLat() && replaceMapsMarker.getLon() == markersList.get(i).getLon()) {
//Query database and update marker_string_id
String mId = replaceMapsMarker.getId(); //Pull new marker string ID
MyApplication.getDBHelper().updateMarkerId(markersList.get(i), mId); //Set database MapsMarker to new marker String ID???
} else { //If marker isnt in database, remove replacement and google maps markers
IdMarker.remove();
replaceMapsMarker.delete();
}
}
}
And my update method
/**
* Update Marker ID
*/
public void updateMarkerId(MapsMarker marker, String id) {
open();
ContentValues values = new ContentValues();
String markerId = marker.getId();
values.put(COLUMN_MARKER_STRING_ID, id);
db.update(TABLE_LOCATION, values, COLUMN_MARKER_STRING_ID + "=" + markerId, null);
close();
}
Here's a suggestion. If you don't need to reload map often, and you have only limited number of markers (say less then 500 or so), you can simply update your "marker_string_id" by going through a simple forloop on all your markers whenever you detect a reload.
Here's the idea and sudo code:
On map reload:
for(marker m: all_existing_markers){
//search in database by matching lat/lng
if(m.lat==database_marker.lat&&m.lng==database_marker.lng){
update database_marker.marker_string_id;
}
}
Note this is just a sudo code, so you have to do a query for each marker in database. Hope this gives you some idea.
Currently right now I have a a database (Call is database1) of map elements (Marker Points,Polyline Points and Polygon points with their information) and for the polyline/polygon points I keep their points (Lat/Lng) in a separate database (call this database2) where each point is a separate database item referenced with the element id of database1.
When the MapFragment loads I pull everything from the database in an AsyncTask
c = getActivity().getContentResolver().query(MapElements.CONTENT_URI,new String[] { MapElements.ID,MapElements.LOCATION_STRING,MapElements.LAYER_ID,MapElements.NEW_OR_MOD}, null, null, null);
loop through the cursor and if the element is a polyline or polygon I pull all the points with the id of that element and create a List to later put into creating the line or polygon
Cursor c2 = context.getContentResolver().query(MapPoints.CONTENT_URI,new String[] { MapPoints.LAYER_ID, MapPoints.LATITUDE,MapPoints.LONGITUDE },
MapPoints.ELEMENT_ID + "=?",new String[] { String.valueOf(id) }, null);
if (c2 != null && c2.moveToFirst()) {
do {
locationArray.add(new LatLng(c2.getFloat(1), c2.getFloat(2)));
} while (c2.moveToNext());
}
c2.close();
Then I plot it to the map with the list
Polyline line = map.addPolyline(new PolylineOptions().addAll(mElement.getLocationArray()));
This whole process can take a long time, for example using 250 different elements takes about 10-15 seconds to load them all and the number of map elements could be far-more or less so obviously the more points the longer it will take.
Looking at the google maps app it looks like all their markers load up real quick, Is there any way I can speed this up??
UPDATE
I did a a little digging around with what I could understand from the traceview and it says that Handler operations were the second and third longest running operations. So what I did was take out the handlers in my asynctask that I use to call back to the main thread to put to the map and the process completed in 2 seconds...
I pulled out the handler code and put it in its own method for now and this is the method
private void test(final MapElement mElement){
if (mElement.getType() > 0) {
try{
Handler h = new Handler(getActivity().getMainLooper());
if (mElement.getType() == 3) {
h.post(new Runnable(){
public void run() {
Polygon poly = map.addPolygon(new PolygonOptions()
.addAll(mElement.getLocationArray()));
String color = mElement.getFillColor();
String borderColor = mElement.getBorderCOlor();
poly.setFillColor(Color.parseColor("#"+color));
poly.setStrokeColor(Color.parseColor("#"+borderColor));
poly.setStrokeWidth(4);
poly.setVisible(false);
Marker m = map.addMarker(new MarkerOptions()
.position(mElement.getPoint())
.icon(BitmapDescriptorFactory.fromResource(mElement.getMarkerIcon())));
m.setVisible(false);
m.setSnippet(String.valueOf(mElement.getID()));
mElement.setMarker(m);
mElement.setPolygon(poly);
}
});
mapElements.put(mElement.getID(), mElement);
} else if (mElement.getType() == 2) {
h.post(new Runnable(){
public void run() {
Polyline line = map
.addPolyline(new PolylineOptions()
.addAll(mElement.getLocationArray()));
String borderColor = mElement.getBorderCOlor();
if(borderColor == null){
line.setColor(Color.BLUE);
}else{
line.setColor(Color.parseColor("#"+borderColor));
}
line.setWidth(mElement.getThickness());
line.setVisible(false);
if(mElement.getLayerId() != 16){
Marker m = map.addMarker(new MarkerOptions()
.position(mElement.getPoint())
.icon(BitmapDescriptorFactory.fromResource(mElement.getMarkerIcon())));
m.setVisible(false);
m.setSnippet(String.valueOf(mElement.getID()));
mElement.setMarker(m);
}
mElement.setPolyLine(line);
}
});
mapElements.put(mElement.getID(), mElement);
} else {
h.post(new Runnable(){
public void run() {
Marker m = map.addMarker(new MarkerOptions()
.position(mElement.getPoint()).icon(
BitmapDescriptorFactory.fromResource(mElement.getMarkerIcon())));
m.setVisible(false);
m.setSnippet(String.valueOf(mElement.getID()));
mElement.setMarker(m);
}
});
mapElements.put(mElement.getID(), mElement);
}
ContentValues values = new ContentValues();
values.put(MapElements.PLOTTED, 1);
getActivity().getContentResolver().update(Uri.withAppendedPath(MapElements.CONTENT_ID_URI_BASE,String.valueOf(mElement.getID())), values, null, null);
}catch(NullPointerException e){
e.printStackTrace();
}
}
}
Even taking out the handlers and putting the test method in the onPostExecute still causes the delay. It take 0.058 seconds to complete this method once so multiplying that by 250 comes out to 15 seconds
So this seems the be the problem here, should I be handling this callback to the main thread differently?
loop through the cursor and if the element is a polyline or polygon I pull all the points with the id of that element
This is most probably your culprit. Every Hibernate user can tell you N+1 queries are very, very bad. Remove the loop, load all the points in one query and make the logic in code.
I am making an android app that will list specific places that are not on google places. I have all the latitude and longitudes and place names and they will not be changing. I can display them in my custom list and it works fine the problem is I want to sort them all by distance from your(the users) location and display the distance next to them.
I have tried lots of different ways but have become a bit stuck. I would like to say that I am new to programming and sort of stumbling my way through this app, If anyone could help it would be really appreciated.
So the question im asking is how can/should I sort locations by distance so I can add them to my custom list.
// create array to hold place names to be looped through later
String[] placenames = { "place1", "place2",
"place3", "place4" };
// // create arrays to hold all the latitudes and longitudes
double[] latArray = new double[] { 51.39649, 51.659775, 51.585433,
51.659775 };
double[] lngArray = new double[] { 0.836523, 0.539901, 0.555385,
0.539901, };
// hard code my location for test purposes only
Location MyLocation = new Location("My location");
MyLocation.setLatitude(51.659775);
MyLocation.setLongitude(0.539901);
for (int i = 0; i < placenames.length;) {
// Place location object
Location PlaceName = new Location(placenames[i]);
PlaceName.setLatitude(latArray[i]);
PlaceName.setLongitude(lngArray[i]);
i++;
// calculate distance in meters
float distanceInMeters = PlaceName.distanceTo(MyLocation);
// convert to double
double DistanceInMiles = distanceInMeters * 0.000621371;
dimint = (int) DistanceInMiles;
// format numbers to two decimal places
DecimalFormat df = new DecimalFormat("#.##");
dim = df.format(DistanceInMiles);
//make treemap and then sortedmap to sort places by distance
TreeMap<Integer, String> treemap = new TreeMap<Integer, String>();
SortedMap<Integer, String> treemapsorted = new TreeMap<Integer,String>();
treemap.put(dimint, PlaceName.getProvider());
treemapsorted = treemap.subMap(0, 5);
// Toast for test purpose to see if sort is working
Toast tst = Toast.makeText(this, treemapsorted.entrySet()
.toString(), Toast.LENGTH_SHORT);
tst.show();
CustomList place_data[] = new CustomList[] {
// This is the problem part
new CustomList(R.drawable.picture1, treemapsorted.get(dimint)),
};
CustomListAdapter adapter = new CustomListAdapter(this,
R.layout.listview_item_row, place_data);
listView1 = (ListView) findViewById(R.id.listView1);
listView1.setAdapter(adapter);
;
}
I just reviewed your code and found lots of problems:
Use i++ in your for loop statement instead of in the loop body, like this:
for (int i = 0; i < placenames.length; i++) {
}
You calculate the distance in miles, cast this to int (I would use Math.round() here) and then create a string with DecimalFormat which you don't use.
You create a TreeMap in every loop iteration. Move the creation in front of the loop and add items to it in the loop body.
TreeMap is not the right class for this task. I tested your code and the Map contained only 3 items after the loop. The reason is, that a TreeMap contains key value pairs, where the key has to be unique. Adding an element with a key (distance in your case), which is already in the map, results in overwriting that element. So instead of a TreeMap I recommend using an ArrayList. You need to create a class with all the variables you need, like distance and place name. This class needs to implement the interface Comparable<Class>. You will then have to implement the method public int compareTo(T other) in which you compare the distance with the distance of the other object. Then you can sort the ArrayList using Collections.sort(arrayList). In the for loop body you should add items to that ArrayList, then sort it, then iterate over the ArrayList items and add them to your ListView.
I'm developing an Android app which is using Google Places API.
Once I get all the places result, I want to sort it according to the algorithm.
Which is, the places result will only being put into the Hash Map if the algorithm is >= 0.
But the problem now is, when I run it, the algorithm result in the for loop did not change during the looping.
My algorithm is:
balance = user_hour-visi-duration.
balance = 240-60-20 = 160
Let's say the balance is 160, it will remain 160 until the for loop ended.
I wanted each time of the looping, the value of balance will decreased untill negative value.
FYI, balance variable is not a local variable.
Does anybody know how to solve this?
Here is the part of the code.
// loop through each place
for (Place p : nearPlaces.results) {
balance = user_hour - duration - visit;
HashMap<String, String> map = new HashMap<String, String>();
//////////////////////////////////////////////////////////////////
googlePlaces = new GooglePlaces();
try {
placeDetails = googlePlaces.getPlaceDetails(p.reference);
} catch (Exception e) {
e.printStackTrace();
}
if(placeDetails != null){
String statuss = placeDetails.status;
// check place deatils status
// Check for all possible status
if(statuss.equals("OK")){
lat = gps.getLatitude();
lang = gps.getLongitude();
double endlat = placeDetails.result.geometry.location.lat;
double endlong = placeDetails.result.geometry.location.lng;
Location locationA = new Location("point A");
locationA.setLatitude(lat);
locationA.setLongitude(lang);
Location locationB = new Location("point B");
locationB.setLatitude(endlat);
locationB.setLongitude(endlong);
double distance = locationA.distanceTo(locationB)/1000;
Double dist = distance;
Integer dist2 = dist.intValue();
//p.distance = String.valueOf(dist2);
p.distance = String.valueOf(balance);
dist3 = p.distance;
}
else if(status.equals("ZERO_RESULTS")){
alert.showAlertDialog(MainActivity.this, "Near Places",
"Sorry no place found.",
false);
}
}
///////////////////////////////////////////////////////////////
if (balance > 0){
// Place reference won't display in listview - it will be hidden
// Place reference is used to get "place full details"
map.put(KEY_REFERENCE, p.reference);
// Place name
map.put(KEY_NAME, p.name);
map.put(KEY_DISTANCE, p.distance);
// adding HashMap to ArrayList
placesListItems.add(map);
}
else {
//
}
}//end for loop
What exactly are you trying to do here?
You have balance = user_hour - duration - visit; on the first line after your for loop. I cannot see where user_hour, duration or visit is declared, but I'm assuming it's outside the loop. This means it will always be the same value for each Place in nearPlaces.results. If this code is genuinely how you want it, you might as well declare it before the loop as you are pointlessly re-calculating it for every Place.
You also never do anything with balance except to print it out or set another value to it, so it's tricky to work out what you're expecting to happen.
I currently have a CSV file that I parse and am trying to insert the data into the android database. The problem I am having is that it is taking way too long to insert all of the data. It's a good amount of data but I feel like it shouldn't take 20min or so to complete.
Basically, I create my database, then begin the parsing. While parsing through each individual CSV row, I grab the required data and insert it into the database. In total there are around 40000 rows.
Is there any way I can speed up this process? I have tried batch inserts but it never really helped (unless I did it wrong).
Code down below.
Thanks.
DatabaseHelper (i have two insert commands based on the amount of data in each csv row):
// add zipcode
public void add9Zipcode(String zip, String city, String state, String lat,
String longi, String decom) {
// get db and content values
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
db.beginTransaction();
try{
// add the values
values.put(KEY_ZIP, zip);
values.put(KEY_STATE, state);
values.put(KEY_CITY, city);
values.put(KEY_LAT, lat);
values.put(KEY_LONG, longi);
values.put(KEY_DECOM, decom);
// execute the statement
db.insert(TABLE_NAME, null, values);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
db.close();
}
public void add12Zipcode(String zip, String city, String state, String lat,
String longi, String decom, String tax, String pop, String wages) {
// get db and content values
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
db.beginTransaction();
try{
// add the values
values.put(KEY_ZIP, zip);
values.put(KEY_STATE, state);
values.put(KEY_CITY, city);
values.put(KEY_LAT, lat);
values.put(KEY_LONG, longi);
values.put(KEY_DECOM, decom);
values.put(KEY_TAX, tax);
values.put(KEY_POP, pop);
values.put(KEY_WAGES, wages);
// execute the statement
db.insert(TABLE_NAME, null, values);
db.setTransactionSuccessful();
} finally{
db.endTransaction();
}
db.close();
}
Parse File:
public void parse(ArrayList<String> theArray, DatabaseHandler db) {
String[] data = null;
// while loop to get split the data into new lines
// for loop to split each string in the array list of zipcodes
for (int x = 0; x < theArray.size(); x++) {
if(x == 10000 || x == 20000 || x == 30000 || x == 40000){
Log.d(TAG, "x is 10k, 20k, 30k, 40k");
}
// split string first into an array
data = theArray.get(x).split(",");
// separate based on the size of the array: 9 or 12
if (data.length == 9) {
db.add9Zipcode(data[0], data[2], data[3], data[5], data[6],
data[8]);
} else if (data.length == 12) {
db.add12Zipcode(data[0], data[2], data[3], data[5], data[6],
data[8], data[9], data[10], data[11]);
/*
* theZip.zip = data[0]; theZip.city = data[2]; theZip.state =
* data[3]; theZip.lat = data[5]; theZip.longi = data[6];
* theZip.decom = data[8]; theZip. = data[9]; theZip.population
* = data[10]; theZip.wages = data[11];
*/
}
}
Refer to this answer I made previously: Inserting 1000000 rows in sqlite3 database
In short, use an InsertHelper and do more than one insert per transaction - unless you did something wonky, the speed increase should be noticeable.
Edit:
In short:
Your SQLiteOpenHelper should be a singleton used across your entire application.
Don't go around calling close() on your SQLiteDatabase instance - it's cached in the SQLiteOpenHelper and every time you close you force the helper to reopen it.
Batch your inserts, start a transaction outside the call to the addZipCode methods and mark it as successful after you've done all the inserts - then commit the transaction.
Use an InsertHelper - it will format the insert properly as a prepared statement and is nice and reusable.
Be mindful of synchronizing access to the database - unless you intend to do all your database work on the UI-thread (which is not recommended) - you either need to enable locking or guard access to the database to avoid concurrent access.