From 96d740db6476c78e680561b6f4784e60be3a0468 Mon Sep 17 00:00:00 2001 From: Rodrigo Damazio Date: Fri, 6 Jan 2012 04:38:54 -0200 Subject: [PATCH] Adressing many TODOs and improving documentation of the search engine. --- .../apps/mytracks/ExportAllTracks.java | 2 +- .../apps/mytracks/content/SearchEngine.java | 123 +++++++++++++++--- .../apps/mytracks/content/TrackDataHub.java | 4 +- .../content/MyTracksProviderUtils.java | 6 +- .../content/MyTracksProviderUtilsImpl.java | 18 +-- 5 files changed, 125 insertions(+), 28 deletions(-) diff --git a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java index a74ff7268..3cf3b11d8 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java @@ -160,7 +160,7 @@ public class ExportAllTracks { try { MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(activity); - cursor = providerUtils.getTracksCursor(null, null); + cursor = providerUtils.getTracksCursor(null, null, TracksColumns._ID); if (cursor == null) { return; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/SearchEngine.java b/MyTracks/src/com/google/android/apps/mytracks/content/SearchEngine.java index ecd9e9789..3f4957356 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/SearchEngine.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/SearchEngine.java @@ -27,21 +27,34 @@ import java.util.Comparator; import java.util.SortedSet; import java.util.TreeSet; +/** + * Engine for searching for tracks and waypoints by text. + * + * @author Rodrigo Damazio + */ public class SearchEngine { + /** How much we promote a match in the track category. */ private static final double TRACK_CATEGORY_PROMOTION = 2.0; + /** How much we promote a match in the track description. */ private static final double TRACK_DESCRIPTION_PROMOTION = 8.0; + /** How much we promote a match in the track name. */ private static final double TRACK_NAME_PROMOTION = 16.0; + /** How much we promote a waypoint result if it's in the currently-selected track. */ private static final double CURRENT_TRACK_WAYPOINT_PROMOTION = 2.0; + /** How much we promote a track result if it's the currently-selected track. */ private static final double CURRENT_TRACK_DEMOTION = 0.5; /** Maximum number of waypoints which will be retrieved and scored. */ private static final int MAX_SCORED_WAYPOINTS = 100; + /** + * Description of a search query, along with all contextual data needed to execute it. + */ public static class SearchQuery { public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, long currentTimestamp) { @@ -51,12 +64,15 @@ public class SearchEngine { this.currentTimestamp = currentTimestamp; } - final String textQuery; - final Location currentLocation; - final long currentTrackId; - final long currentTimestamp; + public final String textQuery; + public final Location currentLocation; + public final long currentTrackId; + public final long currentTimestamp; } + /** + * Description of a search result which has been retrieved and scored. + */ public static class ScoredResult { ScoredResult(Track track, double score) { this.track = track; @@ -70,11 +86,12 @@ public class SearchEngine { this.score = score; } - final Track track; - final Waypoint waypoint; - final double score; + public final Track track; + public final Waypoint waypoint; + public final double score; } + /** Comparador for scored results. */ private static final Comparator SCORED_RESULT_COMPARATOR = new Comparator() { @Override @@ -88,7 +105,8 @@ public class SearchEngine { // Arbitrary ordering, by ID. long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId(); long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId(); - return (int) (id2 - id1); + long diff = id2 - id1; + return Long.signum(diff); } }; @@ -98,6 +116,12 @@ public class SearchEngine { providerUtils = MyTracksProviderUtils.Factory.get(ctx); } + /** + * Executes a search query and returns a set of sorted results. + * + * @param query the query to execute + * @return a set of results, sorted according to their score + */ public SortedSet doSearch(SearchQuery query) { ArrayList tracks = new ArrayList(); ArrayList waypoints = new ArrayList(); @@ -112,20 +136,25 @@ public class SearchEngine { return scoredResults; } + /** + * Retrieves tracks matching the given query from the database. + * + * @param query the query to retrieve for + * @param tracks list to fill with the resulting tracks + */ private void retrieveTracks(SearchQuery query, ArrayList tracks) { - // TODO: Use a different sort order as a ranking function. String queryLikeSelection = "%" + query.textQuery + "%"; String trackSelection = TracksColumns.NAME + " LIKE ? OR " + TracksColumns.DESCRIPTION + " LIKE ? OR " + TracksColumns.CATEGORY + " LIKE ?"; + String order = TracksColumns._ID + " DESC LIMIT 1000"; // Favor recent tracks. String[] trackSelectionArgs = new String[] { queryLikeSelection, queryLikeSelection, queryLikeSelection }; - // TODO: Limit number of results to be scored. - Cursor tracksCursor = providerUtils.getTracksCursor(trackSelection, trackSelectionArgs); + Cursor tracksCursor = providerUtils.getTracksCursor(trackSelection, trackSelectionArgs, order); if (tracksCursor != null) { try { tracks.ensureCapacity(tracksCursor.getCount()); @@ -139,18 +168,25 @@ public class SearchEngine { } } + /** + * Retrieves waypoints matching the given query from the database. + * + * @param query the query to retrieve for + * @param tracks list to fill with the resulting waypoints + */ private void retrieveWaypoints(SearchQuery query, ArrayList waypoints) { - // TODO: Use a different sort order as a ranking function. String queryLikeSelection2 = "%" + query.textQuery + "%"; String waypointSelection = WaypointsColumns.NAME + " LIKE ? OR " + WaypointsColumns.DESCRIPTION + " LIKE ? OR " + WaypointsColumns.CATEGORY + " LIKE ?"; + String order = WaypointsColumns._ID + " DESC"; // Favor recent waypoints. String[] waypointSelectionArgs = new String[] { queryLikeSelection2, queryLikeSelection2, queryLikeSelection2 }; - Cursor waypointsCursor = providerUtils.getWaypointsCursor(waypointSelection, waypointSelectionArgs, MAX_SCORED_WAYPOINTS); + Cursor waypointsCursor = providerUtils.getWaypointsCursor(waypointSelection, waypointSelectionArgs, order, + MAX_SCORED_WAYPOINTS); if (waypointsCursor != null) { try { waypoints.ensureCapacity(waypointsCursor.getCount()); @@ -164,6 +200,13 @@ public class SearchEngine { } } + /** + * Scores a collection of track results. + * + * @param tracks the results to score + * @param query the query to score for + * @param output the collection to fill with scored results + */ private void scoreTrackResults(Collection tracks, SearchQuery query, Collection output) { for (Track track : tracks) { // Calculate the score. @@ -174,12 +217,20 @@ public class SearchEngine { } } + /** + * Scores a single track result. + * + * @param query the query to score for + * @param track the results to score + * @return the score for the track + */ private double scoreTrackResult(SearchQuery query, Track track) { double score = 1.0; score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory()); TripStatistics statistics = track.getStatistics(); + // TODO: Also boost for proximity to the currently-centered position on the map. double meanLatitude = (statistics.getTop() + statistics.getBottom()) / 2000000.0; double meanLongitude = (statistics.getRight() + statistics.getLeft()) / 2000000.0; score *= getDistanceBoost(query, meanLatitude, meanLongitude); @@ -195,6 +246,13 @@ public class SearchEngine { return score; } + /** + * Scores a collection of waypoint results. + * + * @param waypoints the results to score + * @param query the query to score for + * @param output the collection to fill with scored results + */ private void scoreWaypointResults(Collection waypoints, SearchQuery query, Collection output) { for (Waypoint waypoint : waypoints) { // Calculate the score. @@ -205,11 +263,19 @@ public class SearchEngine { } } + /** + * Scores a single waypoint result. + * + * @param query the query to score for + * @param waypoint the results to score + * @return the score for the waypoint + */ private double scoreWaypointResult(SearchQuery query, Waypoint waypoint) { double score = 1.0; Location location = waypoint.getLocation(); score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory()); + // TODO: Also boost for proximity to the currently-centered position on the map. score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude()); score *= getTimeBoost(query, location.getTime()); @@ -221,6 +287,15 @@ public class SearchEngine { return score; } + /** + * Calculates the boosting of the score due to the field(s) in which the match occured. + * + * @param query the query to boost for + * @param name the name of the track or waypoint + * @param description the description of the track or waypoint + * @param category the category of the track or waypoint + * @return the total boost to be applied to the result + */ private double getTitleBoost(SearchQuery query, String name, String description, String category) { // Title boost: track name > description > category. @@ -237,6 +312,13 @@ public class SearchEngine { return boost; } + /** + * Calculates the boosting of the score due to the recency of the matched entity. + * + * @param query the query to boost for + * @param timestamp the timestamp to calculate the boost for + * @return the total boost to be applied to the result + */ private double getTimeBoost(SearchQuery query, long timestamp) { // Score recent tracks higher. long timeAgoHours = (query.currentTimestamp - timestamp) / (60L * 60L); @@ -248,6 +330,14 @@ public class SearchEngine { } } + /** + * Calculates the boosting of the score due to proximity to a location. + * + * @param query the query to boost for + * @param latitude the latitude to calculate the boost for + * @param longitude the longitude to calculate the boost for + * @return the total boost to be applied to the result + */ private double getDistanceBoost(SearchQuery query, double latitude, double longitude) { if (query.currentLocation == null) { return 1.0; @@ -271,7 +361,10 @@ public class SearchEngine { } } - private static double squash(double timeAgoHours) { - return 1.0 / Math.log1p(timeAgoHours); + /** + * Squashes a number by calculating 1 / log (1 + x). + */ + private static double squash(double x) { + return 1.0 / Math.log1p(x); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java index b4968f680..57b798c28 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java @@ -196,7 +196,7 @@ public class TrackDataHub { private DoubleBufferedLocationFactory locationFactory; private boolean started = false; - + /** * Builds a new {@link TrackDataHub} instance. */ @@ -274,7 +274,7 @@ public class TrackDataHub { Log.w(TAG, "Not started, ignoring"); return; } - + // Unregister internal listeners even if there are external listeners registered. dataSourceManager.unregisterAllListeners(); listenerHandlerThread.getLooper().quit(); diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java index b52f41d9c..87d04eef8 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java @@ -184,10 +184,11 @@ public interface MyTracksProviderUtils { * * @param selection a given selection * @param selectionArgs arguments for the given selection + * @param order the order in which to return results * @param maxWaypoints the maximum number of waypoints to return * @return a cursor of the selected waypoints */ - Cursor getWaypointsCursor(String selection, String[] selectionArgs, int maxWaypoints); + Cursor getWaypointsCursor(String selection, String[] selectionArgs, String order, int maxWaypoints); /** * Finds a track by given unique track id. @@ -215,9 +216,10 @@ public interface MyTracksProviderUtils { * * @param selection a given selection * @param selecttionArgs parameters for the given selection + * @param order the order to return results in * @return a cursor of the selected tracks */ - Cursor getTracksCursor(String selection, String[] selectionArgs); + Cursor getTracksCursor(String selection, String[] selectionArgs, String order); /** * Inserts a track in the tracks provider. diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java index c9096a319..ee4cc81b6 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java @@ -889,18 +889,20 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { selectionArgs = new String[] { Long.toString(trackId) }; } - return getWaypointsCursor(selection, selectionArgs, maxWaypoints); + return getWaypointsCursor(selection, selectionArgs, null, maxWaypoints); } @Override - public Cursor getWaypointsCursor(String selection, String[] selectionArgs, int maxWaypoints) { - String sortOrder = "_id ASC"; + public Cursor getWaypointsCursor(String selection, String[] selectionArgs, String order, int maxWaypoints) { + if (order == null) { + order = "_id ASC"; + } if (maxWaypoints > 0) { - sortOrder += " LIMIT " + maxWaypoints; + order += " LIMIT " + maxWaypoints; } return contentResolver.query( - WaypointsColumns.CONTENT_URI, null, selection, selectionArgs, sortOrder); + WaypointsColumns.CONTENT_URI, null, selection, selectionArgs, order); } @Override @@ -915,7 +917,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public List getAllTracks() { - Cursor cursor = getTracksCursor(null, null); + Cursor cursor = getTracksCursor(null, null, TracksColumns._ID); ArrayList tracks = new ArrayList(); if (cursor != null) { tracks.ensureCapacity(cursor.getCount()); @@ -933,9 +935,9 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { } @Override - public Cursor getTracksCursor(String selection, String[] selectionArgs) { + public Cursor getTracksCursor(String selection, String[] selectionArgs, String order) { return contentResolver.query( - TracksColumns.CONTENT_URI, null, selection, selectionArgs, "_id"); + TracksColumns.CONTENT_URI, null, selection, selectionArgs, order); } @Override