Update stable search restuls

This commit is contained in:
Victor Shcherb 2016-08-05 14:33:29 +03:00
parent 0d649f772d
commit 1106994c01
4 changed files with 249 additions and 177 deletions

View file

@ -1,4 +1,14 @@
package net.osmand.search;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import net.osmand.Collator;
import net.osmand.OsmAndCollator;
@ -25,23 +35,12 @@ import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class SearchUICore {
private static final int TIMEOUT_BETWEEN_CHARS = 200;
private static final Log LOG = PlatformUtil.getLog(SearchUICore.class);
private SearchPhrase phrase;
private SearchResultCollection currentSearchResult = new SearchResultCollection();
private SearchResultCollection currentSearchResult;
private ThreadPoolExecutor singleThreadedExecutor;
private LinkedBlockingQueue<Runnable> taskQueue;
@ -52,7 +51,6 @@ public class SearchUICore {
List<SearchCoreAPI> apis = new ArrayList<>();
private SearchSettings searchSettings;
private MapPoiTypes poiTypes;
private Collator collator;
public SearchUICore(MapPoiTypes poiTypes, String locale) {
@ -60,31 +58,146 @@ public class SearchUICore {
taskQueue = new LinkedBlockingQueue<Runnable>();
searchSettings = new SearchSettings(new ArrayList<BinaryMapIndexReader>());
searchSettings = searchSettings.setLang(locale);
phrase = new SearchPhrase(searchSettings);
phrase = new SearchPhrase(searchSettings, OsmAndCollator.primaryCollator());
currentSearchResult = new SearchResultCollection(phrase);
singleThreadedExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, taskQueue);
collator = OsmAndCollator.primaryCollator();
}
public static class SearchResultCollection {
private List<SearchResult> searchResults;
private SearchPhrase phrase;
public SearchResultCollection(List<SearchResult> requestResults, SearchPhrase phrase) {
searchResults = requestResults;
public SearchResultCollection(SearchPhrase phrase) {
searchResults = new ArrayList<>();
this.phrase = phrase;
}
public SearchResultCollection() {
searchResults = new ArrayList<>();
public SearchResultCollection combineWithCollection(SearchResultCollection collection, boolean resort, boolean removeDuplicates) {
SearchResultCollection src = new SearchResultCollection(phrase);
src.addSearchResults(searchResults, false, false);
src.addSearchResults(collection.searchResults, resort, removeDuplicates);
return src;
}
public void addSearchResults(List<SearchResult> sr, boolean resortAll, boolean removeDuplicates) {
if(resortAll) {
this.searchResults.addAll(sr);
sortSearchResults();
if(removeDuplicates) {
filterSearchDuplicateResults();
}
} else {
if(removeDuplicates) {
ArrayList<SearchResult> addedResults = new ArrayList<>(sr);
SearchResultComparator cmp = new SearchResultComparator(phrase);
Collections.sort(addedResults, cmp);
filterSearchDuplicateResults(addedResults);
int i = 0;
int j = 0;
while(j < addedResults.size()) {
SearchResult addedResult = addedResults.get(j);
if(i >= searchResults.size()) {
if(searchResults.size() == 0 ||
!sameSearchResult(addedResult, searchResults.get(searchResults.size() - 1))) {
searchResults.add(addedResult);
}
j++;
continue;
}
SearchResult existingResult = searchResults.get(i);
if(sameSearchResult(addedResult, existingResult)) {
j++;
continue;
}
int compare = cmp.compare(existingResult, addedResult);
if(compare == 0) {
// existingResult == addedResult
j++;
} else if(compare > 0) {
// existingResult > addedResult
this.searchResults.add(addedResults.get(j));
j++;
} else {
// existingResult < addedResult
i++;
}
}
}
}
}
public List<SearchResult> getCurrentSearchResults() {
return searchResults;
return Collections.unmodifiableList(searchResults);
}
public SearchPhrase getPhrase() {
return phrase;
}
public void sortSearchResults() {
Collections.sort(searchResults, new SearchResultComparator(phrase));
}
public void filterSearchDuplicateResults() {
filterSearchDuplicateResults(searchResults);
}
private void filterSearchDuplicateResults(List<SearchResult> lst) {
Iterator<SearchResult> it = lst.iterator();
SearchResult found = null;
while(it.hasNext()) {
SearchResult r = it.next();
if(found != null && sameSearchResult(found, r)) {
it.remove();
} else {
found = r;
}
}
}
public boolean sameSearchResult(SearchResult r1, SearchResult r2) {
if(r1.location != null && r2.location != null) {
Amenity a1 = null;
if(r1.object instanceof Amenity) {
a1 = (Amenity) r1.object;
}
Amenity a2 = null;
if(r2.object instanceof Amenity) {
a2 = (Amenity) r2.object;
}
if (r1.localeName.equals(r2.localeName)) {
double similarityRadius = 30;
if (a1 != null && a2 != null) {
// here 2 points are amenity
String type1 = a1.getType().getKeyName();
String type2 = a2.getType().getKeyName();
String subType1 = a1.getSubType();
String subType2 = a2.getSubType();
if (!type1.equals(type2)) {
return false;
}
if (type1.equals("natural")) {
similarityRadius = 10000;
} else if (subType1.equals(subType2)) {
if (subType1.contains("cn_ref") || subType1.contains("wn_ref")
|| (subType1.startsWith("route_hiking_") && subType1.endsWith("n_poi"))) {
similarityRadius = 10000;
}
}
} else if(ObjectType.isAddress(r1.objectType) && ObjectType.isAddress(r2.objectType)) {
similarityRadius = 100;
}
return MapUtils.getDistance(r1.location, r2.location) < similarityRadius;
}
}
return false;
}
}
public void setPoiTypes(MapPoiTypes poiTypes) {
@ -111,7 +224,6 @@ public class SearchUICore {
public <T extends SearchCoreAPI> SearchResultCollection shallowSearch(Class<T> cl,
String text, final ResultMatcher<SearchResult> matcher) throws IOException {
SearchResultCollection quickRes = new SearchResultCollection();
T api = getApiByClass(cl);
if(api != null) {
SearchPhrase sphrase = this.phrase.generateNewPhrase(text, searchSettings);
@ -120,14 +232,13 @@ public class SearchUICore {
SearchResultMatcher rm = new SearchResultMatcher(matcher, ai.get(), ai, totalLimit);
api.search(sphrase, rm);
sortSearchResults(sphrase, rm.getRequestResults());
filterSearchDuplicateResults(sphrase, rm.getRequestResults());
LOG.info(">> Shallow Search phrase " + phrase + " " + rm.getRequestResults().size());
return new SearchResultCollection(rm.getRequestResults(),
SearchResultCollection collection = new SearchResultCollection(
sphrase);
collection.addSearchResults(rm.getRequestResults(), true, true);
LOG.info(">> Shallow Search phrase " + phrase + " " + rm.getRequestResults().size());
return collection;
}
return quickRes;
return null;
}
public void init() {
@ -201,11 +312,10 @@ public class SearchUICore {
}
public SearchResultCollection search(final String text, final ResultMatcher<SearchResult> matcher) {
SearchResultCollection quickRes = new SearchResultCollection();
final int request = requestNumber.incrementAndGet();
final SearchPhrase phrase = this.phrase.generateNewPhrase(text, searchSettings);
this.phrase = phrase;
quickRes.phrase = phrase;
SearchResultCollection quickRes = new SearchResultCollection(phrase);
filterCurrentResults(quickRes.searchResults, phrase);
LOG.info("> Search phrase " + phrase + " " + quickRes.searchResults.size());
singleThreadedExecutor.submit(new Runnable() {
@ -222,12 +332,11 @@ public class SearchUICore {
}
searchInBackground(phrase, rm);
if (!rm.isCancelled()) {
sortSearchResults(phrase, rm.getRequestResults());
filterSearchDuplicateResults(phrase, rm.getRequestResults());
LOG.info(">> Search phrase " + phrase + " " + rm.getRequestResults().size());
SearchResultCollection collection = new SearchResultCollection(rm.getRequestResults(),
SearchResultCollection collection = new SearchResultCollection(
phrase);
collection.addSearchResults(rm.getRequestResults(), true, true);
LOG.info(">> Search phrase " + phrase + " " + rm.getRequestResults().size());
currentSearchResult = collection;
if (onResultsComplete != null) {
onResultsComplete.run();
@ -267,6 +376,7 @@ public class SearchUICore {
}
try {
api.search(phrase, matcher);
matcher.apiSearchFinished(api, phrase);
} catch (Throwable e) {
e.printStackTrace();
@ -284,92 +394,10 @@ public class SearchUICore {
phrase.sortFiles();
}
public boolean sameSearchResult(SearchResult r1, SearchResult r2) {
if(r1.location != null && r2.location != null) {
Amenity a1 = null;
if(r1.object instanceof Amenity) {
a1 = (Amenity) r1.object;
}
Amenity a2 = null;
if(r2.object instanceof Amenity) {
a2 = (Amenity) r2.object;
}
if (r1.localeName.equals(r2.localeName)) {
double similarityRadius = 30;
if (a1 != null && a2 != null) {
// here 2 points are amenity
String type1 = a1.getType().getKeyName();
String type2 = a2.getType().getKeyName();
String subType1 = a1.getSubType();
String subType2 = a2.getSubType();
if (!type1.equals(type2)) {
return false;
}
if (type1.equals("natural")) {
similarityRadius = 10000;
} else if (subType1.equals(subType2)) {
if (subType1.contains("cn_ref") || subType1.contains("wn_ref")
|| (subType1.startsWith("route_hiking_") && subType1.endsWith("n_poi"))) {
similarityRadius = 10000;
}
}
} else if(ObjectType.isAddress(r1.objectType) && ObjectType.isAddress(r2.objectType)) {
similarityRadius = 100;
}
return MapUtils.getDistance(r1.location, r2.location) < similarityRadius;
}
}
return false;
}
public void filterSearchDuplicateResults(SearchPhrase sp, List<SearchResult> searchResults) {
Iterator<SearchResult> it = searchResults.iterator();
SearchResult found = null;
while(it.hasNext()) {
SearchResult r = it.next();
if(found != null && sameSearchResult(found, r)) {
it.remove();
} else {
found = r;
}
}
}
public void sortSearchResults(SearchPhrase sp, List<SearchResult> searchResults) {
// sort SearchResult by 1. searchDistance 2. Name
final LatLon loc = sp.getLastTokenLocation();
Collections.sort(searchResults, new Comparator<SearchResult>() {
@Override
public int compare(SearchResult o1, SearchResult o2) {
if(o1.getFoundWordCount() != o2.getFoundWordCount()) {
return -Algorithms.compare(o1.getFoundWordCount(), o2.getFoundWordCount());
}
double s1 = o1.getSearchDistance(loc);
double s2 = o2.getSearchDistance(loc);
int cmp = Double.compare(s1, s2);
if(cmp != 0) {
return cmp;
}
int st1 = Algorithms.extractFirstIntegerNumber(o1.localeName);
int st2 = Algorithms.extractFirstIntegerNumber(o2.localeName);
if(st1 != st2) {
return Algorithms.compare(st1, st2);
}
cmp = collator.compare(o1.localeName, o2.localeName);
if(cmp != 0) {
return cmp;
}
s1 = o1.getSearchDistance(loc, 1);
s2 = o2.getSearchDistance(loc, 1);
return Double.compare(s1, s2);
}
});
}
public static class SearchResultMatcher
implements ResultMatcher<SearchResult>{
public static class SearchResultMatcher implements ResultMatcher<SearchResult>{
private final List<SearchResult> requestResults = new ArrayList<>();
private final ResultMatcher<SearchResult> matcher;
private final int request;
@ -439,5 +467,43 @@ public class SearchUICore {
boolean cancelled = request != requestNumber.get();
return cancelled || (matcher != null && matcher.isCancelled());
}
}
}
public static class SearchResultComparator implements Comparator<SearchResult> {
private SearchPhrase sp;
private Collator collator;
private LatLon loc;
public SearchResultComparator(SearchPhrase sp) {
this.sp = sp;
this.collator = sp.getCollator();
loc = sp.getLastTokenLocation();
}
@Override
public int compare(SearchResult o1, SearchResult o2) {
if(o1.getFoundWordCount() != o2.getFoundWordCount()) {
return -Algorithms.compare(o1.getFoundWordCount(), o2.getFoundWordCount());
}
double s1 = o1.getSearchDistance(loc);
double s2 = o2.getSearchDistance(loc);
int cmp = Double.compare(s1, s2);
if(cmp != 0) {
return cmp;
}
int st1 = Algorithms.extractFirstIntegerNumber(o1.localeName);
int st2 = Algorithms.extractFirstIntegerNumber(o2.localeName);
if(st1 != st2) {
return Algorithms.compare(st1, st2);
}
cmp = collator.compare(o1.localeName, o2.localeName);
if(cmp != 0) {
return cmp;
}
s1 = o1.getSearchDistance(loc, 1);
s2 = o2.getSearchDistance(loc, 1);
return Double.compare(s1, s2);
}
}
}

View file

@ -9,8 +9,10 @@ import java.util.List;
import java.util.TreeSet;
import java.util.regex.Pattern;
import net.osmand.Collator;
import net.osmand.CollatorStringMatcher;
import net.osmand.CollatorStringMatcher.StringMatcherMode;
import net.osmand.OsmAndCollator;
import net.osmand.StringMatcher;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
@ -37,6 +39,7 @@ public class SearchPhrase {
private static final String DELIMITER = " ";
private static final String ALLDELIMITERS = "\\s|,";
private static final Pattern reg = Pattern.compile(ALLDELIMITERS);
private Collator clt;
public enum SearchPhraseDataType {
@ -44,12 +47,17 @@ public class SearchPhrase {
}
public SearchPhrase(SearchSettings settings) {
public SearchPhrase(SearchSettings settings, Collator clt) {
this.settings = settings;
this.clt = clt;
}
public Collator getCollator() {
return clt;
}
public SearchPhrase generateNewPhrase(String text, SearchSettings settings) {
SearchPhrase sp = new SearchPhrase(settings);
SearchPhrase sp = new SearchPhrase(settings, this.clt);
String restText = text;
List<SearchWord> leftWords = this.words;
String thisTxt = getText(true);
@ -259,7 +267,7 @@ public class SearchPhrase {
}
public SearchPhrase selectWord(SearchResult res, List<String> unknownWords, boolean lastComplete) {
SearchPhrase sp = new SearchPhrase(this.settings);
SearchPhrase sp = new SearchPhrase(this.settings, this.clt);
addResult(res, sp);
SearchResult prnt = res.parentSearchResult;
while(prnt != null) {

View file

@ -2,6 +2,7 @@ package net.osmand.search;
import java.io.IOException;
import net.osmand.OsmAndCollator;
import net.osmand.data.LatLon;
import net.osmand.search.SearchUICore.SearchResultMatcher;
import net.osmand.search.core.SearchCoreFactory;
@ -16,7 +17,7 @@ public class LocationSearchTest {
private void search(String string, LatLon latLon) throws IOException {
SearchResultMatcher srm = new SearchUICore.SearchResultMatcher(null, 0, null, 100);
new SearchCoreFactory.SearchLocationAndUrlAPI().
search(new SearchPhrase(null).generateNewPhrase(string, null), srm);
search(new SearchPhrase(null, OsmAndCollator.primaryCollator()).generateNewPhrase(string, null), srm);
Assert.assertEquals(1, srm.getRequestResults().size());
Assert.assertEquals(latLon, srm.getRequestResults().get(0).location);
}

View file

@ -1,5 +1,45 @@
package net.osmand.plus.search;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.osmand.AndroidUtils;
import net.osmand.Location;
import net.osmand.OsmAndCollator;
import net.osmand.ResultMatcher;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.osm.AbstractPoiType;
import net.osmand.plus.AppInitializer;
import net.osmand.plus.AppInitializer.AppInitializeListener;
import net.osmand.plus.GPXUtilities;
import net.osmand.plus.GPXUtilities.GPXFile;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.LockableViewPager;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmAndLocationProvider.OsmAndCompassListener;
import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.SearchHistoryHelper;
import net.osmand.plus.helpers.SearchHistoryHelper.HistoryEntry;
import net.osmand.plus.poi.PoiUIFilter;
import net.osmand.plus.search.QuickSearchHelper.SearchHistoryAPI;
import net.osmand.search.SearchUICore;
import net.osmand.search.SearchUICore.SearchResultCollection;
import net.osmand.search.core.ObjectType;
import net.osmand.search.core.SearchCoreAPI;
import net.osmand.search.core.SearchCoreFactory.SearchAmenityTypesAPI;
import net.osmand.search.core.SearchPhrase;
import net.osmand.search.core.SearchResult;
import net.osmand.search.core.SearchSettings;
import net.osmand.search.core.SearchWord;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.DialogInterface;
@ -34,46 +74,6 @@ import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import net.osmand.AndroidUtils;
import net.osmand.Location;
import net.osmand.ResultMatcher;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.osm.AbstractPoiType;
import net.osmand.plus.AppInitializer;
import net.osmand.plus.AppInitializer.AppInitializeListener;
import net.osmand.plus.GPXUtilities;
import net.osmand.plus.GPXUtilities.GPXFile;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.LockableViewPager;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmAndLocationProvider.OsmAndCompassListener;
import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.SearchHistoryHelper;
import net.osmand.plus.helpers.SearchHistoryHelper.HistoryEntry;
import net.osmand.plus.poi.PoiUIFilter;
import net.osmand.plus.search.QuickSearchHelper.SearchHistoryAPI;
import net.osmand.search.SearchUICore;
import net.osmand.search.SearchUICore.SearchResultCollection;
import net.osmand.search.core.ObjectType;
import net.osmand.search.core.SearchCoreAPI;
import net.osmand.search.core.SearchCoreFactory.SearchAmenityTypesAPI;
import net.osmand.search.core.SearchPhrase;
import net.osmand.search.core.SearchResult;
import net.osmand.search.core.SearchSettings;
import net.osmand.search.core.SearchWord;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class QuickSearchDialogFragment extends DialogFragment implements OsmAndCompassListener, OsmAndLocationListener {
public static final String TAG = "QuickSearchDialogFragment";
@ -697,13 +697,13 @@ public class QuickSearchDialogFragment extends DialogFragment implements OsmAndC
try {
SearchResultCollection res = searchUICore.shallowSearch(SearchHistoryAPI.class,
"", null);
List<QuickSearchListItem> rows = new ArrayList<>();
if (res != null) {
List<QuickSearchListItem> rows = new ArrayList<>();
for (SearchResult sr : res.getCurrentSearchResults()) {
rows.add(new QuickSearchListItem(app, sr));
}
historySearchFragment.updateListAdapter(rows, false);
}
historySearchFragment.updateListAdapter(rows, false);
} catch (IOException e) {
e.printStackTrace();
app.showToastMessage(e.getMessage());
@ -764,7 +764,7 @@ public class QuickSearchDialogFragment extends DialogFragment implements OsmAndC
if (paused) {
if (results.size() > 0) {
getResultCollection().getCurrentSearchResults().addAll(results);
getResultCollection().addSearchResults(results, true, true);
}
return false;
}
@ -783,7 +783,6 @@ public class QuickSearchDialogFragment extends DialogFragment implements OsmAndC
apiResults = regionCollection.getCurrentSearchResults();
} else {
apiResults = results;
searchUICore.sortSearchResults(phrase, apiResults);
}
regionResultApi = null;
@ -796,9 +795,12 @@ public class QuickSearchDialogFragment extends DialogFragment implements OsmAndC
if (!paused) {
boolean appended = false;
if (getResultCollection() == null || getResultCollection().getPhrase() != phrase) {
setResultCollection(new SearchResultCollection(apiResults, phrase));
SearchResultCollection resCollection =
new SearchResultCollection(phrase);
resCollection.addSearchResults(results, true, true);
setResultCollection(resCollection);
} else {
getResultCollection().getCurrentSearchResults().addAll(apiResults);
getResultCollection().addSearchResults(apiResults, false, true );
appended = true;
}
if (!hasRegionCollection) {
@ -810,22 +812,17 @@ public class QuickSearchDialogFragment extends DialogFragment implements OsmAndC
break;
case SEARCH_API_REGION_FINISHED:
regionResultApi = (SearchCoreAPI) object.object;
final List<SearchResult> regionResults = new ArrayList<>(results);
final SearchPhrase regionPhrase = object.requiredSearchPhrase;
searchUICore.sortSearchResults(regionPhrase, regionResults);
regionResultCollection = new SearchResultCollection(regionPhrase);
regionResultCollection.addSearchResults(results, true, true);
app.runInUIThread(new Runnable() {
@Override
public void run() {
if (!paused) {
boolean appended = getResultCollection() != null && getResultCollection().getPhrase() == regionPhrase;
regionResultCollection = new SearchResultCollection(regionResults, regionPhrase);
if (appended) {
List<SearchResult> res = new ArrayList<>(getResultCollection().getCurrentSearchResults());
res.addAll(regionResults);
SearchResultCollection resCollection = new SearchResultCollection(res, regionPhrase);
searchUICore.filterSearchDuplicateResults(regionPhrase, resCollection.getCurrentSearchResults());
SearchResultCollection resCollection =
getResultCollection().combineWithCollection(regionResultCollection, false, true);
updateSearchResult(resCollection, true);
} else {
updateSearchResult(regionResultCollection, false);