OsmAnd/OsmAnd-java/src/net/osmand/search/SearchUICore.java

383 lines
11 KiB
Java
Raw Normal View History

2016-07-13 11:59:44 +02:00
package net.osmand.search;
2016-07-08 14:32:02 +02:00
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
2016-07-21 11:34:54 +02:00
import java.util.Iterator;
2016-07-08 14:32:02 +02:00
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;
2016-07-21 11:34:54 +02:00
import net.osmand.Collator;
2016-07-12 20:16:42 +02:00
import net.osmand.OsmAndCollator;
2016-07-08 14:32:02 +02:00
import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapIndexReader;
2016-07-21 11:34:54 +02:00
import net.osmand.data.Amenity;
2016-07-08 14:32:02 +02:00
import net.osmand.data.LatLon;
2016-07-10 11:46:39 +02:00
import net.osmand.osm.MapPoiTypes;
2016-07-13 22:32:52 +02:00
import net.osmand.search.core.ObjectType;
2016-07-13 11:59:44 +02:00
import net.osmand.search.core.SearchCoreAPI;
import net.osmand.search.core.SearchCoreFactory;
2016-07-24 22:34:26 +02:00
import net.osmand.search.core.SearchCoreFactory.SearchBuildingAndIntersectionsByStreetAPI;
import net.osmand.search.core.SearchCoreFactory.SearchStreetByCityAPI;
2016-07-13 11:59:44 +02:00
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.search.core.SearchPhrase.NameStringMatcher;
2016-07-08 14:32:02 +02:00
import net.osmand.util.Algorithms;
2016-07-21 11:34:54 +02:00
import net.osmand.util.MapUtils;
2016-07-08 14:32:02 +02:00
2016-07-11 10:01:25 +02:00
import org.apache.commons.logging.Log;
2016-07-08 14:32:02 +02:00
public class SearchUICore {
2016-07-21 11:34:54 +02:00
private static final int TIMEOUT_BETWEEN_CHARS = 200;
2016-07-08 14:32:02 +02:00
private static final Log LOG = PlatformUtil.getLog(SearchUICore.class);
private SearchPhrase phrase;
2016-07-11 10:01:25 +02:00
private SearchResultCollection currentSearchResult = new SearchResultCollection();
2016-07-08 14:32:02 +02:00
private ThreadPoolExecutor singleThreadedExecutor;
private LinkedBlockingQueue<Runnable> taskQueue;
private Runnable onResultsComplete = null;
private AtomicInteger requestNumber = new AtomicInteger();
2016-07-10 11:46:39 +02:00
private int totalLimit = -1; // -1 unlimited - not used
2016-07-08 14:32:02 +02:00
List<SearchCoreAPI> apis = new ArrayList<>();
private SearchSettings searchSettings;
2016-07-10 11:46:39 +02:00
private MapPoiTypes poiTypes;
2016-07-21 11:34:54 +02:00
private Collator collator;
2016-07-08 14:32:02 +02:00
2016-07-10 11:46:39 +02:00
public SearchUICore(MapPoiTypes poiTypes, String locale, BinaryMapIndexReader[] searchIndexes) {
this.poiTypes = poiTypes;
2016-07-08 14:32:02 +02:00
List<BinaryMapIndexReader> searchIndexesList = Arrays.asList(searchIndexes);
taskQueue = new LinkedBlockingQueue<Runnable>();
searchSettings = new SearchSettings(searchIndexesList);
2016-07-10 11:46:39 +02:00
searchSettings = searchSettings.setLang(locale);
2016-07-08 14:32:02 +02:00
phrase = new SearchPhrase(searchSettings);
singleThreadedExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, taskQueue);
init();
2016-07-21 11:34:54 +02:00
collator = OsmAndCollator.primaryCollator();
2016-07-08 14:32:02 +02:00
}
2016-07-11 10:01:25 +02:00
public static class SearchResultCollection {
private List<SearchResult> searchResults;
private SearchPhrase phrase;
public SearchResultCollection(List<SearchResult> requestResults, SearchPhrase phrase) {
searchResults = requestResults;
this.phrase = phrase;
}
public SearchResultCollection() {
searchResults = new ArrayList<>();
}
public List<SearchResult> getCurrentSearchResults() {
return searchResults;
}
public SearchPhrase getPhrase() {
return phrase;
}
}
2016-07-08 14:32:02 +02:00
public int getTotalLimit() {
return totalLimit;
}
public void setTotalLimit(int totalLimit) {
this.totalLimit = totalLimit;
}
public void init() {
2016-07-24 22:34:26 +02:00
apis.add(new SearchCoreFactory.SearchLocationAndUrlAPI());
2016-07-10 11:46:39 +02:00
apis.add(new SearchCoreFactory.SearchAmenityTypesAPI(poiTypes));
2016-07-10 12:20:18 +02:00
apis.add(new SearchCoreFactory.SearchAmenityByTypeAPI(poiTypes));
2016-07-10 11:46:39 +02:00
apis.add(new SearchCoreFactory.SearchAmenityByNameAPI());
2016-07-24 22:34:26 +02:00
SearchBuildingAndIntersectionsByStreetAPI streetsApi =
new SearchCoreFactory.SearchBuildingAndIntersectionsByStreetAPI();
apis.add(streetsApi);
SearchStreetByCityAPI cityApi = new SearchCoreFactory.SearchStreetByCityAPI(streetsApi);
apis.add(cityApi);
apis.add(new SearchCoreFactory.SearchAddressByNameAPI(streetsApi, cityApi));
2016-07-08 14:32:02 +02:00
}
2016-07-12 15:47:34 +02:00
public void registerAPI(SearchCoreAPI api) {
apis.add(api);
}
2016-07-11 10:01:25 +02:00
public SearchResultCollection getCurrentSearchResult() {
return currentSearchResult;
2016-07-08 14:32:02 +02:00
}
public SearchPhrase getPhrase() {
return phrase;
}
public void setOnResultsComplete(Runnable onResultsComplete) {
this.onResultsComplete = onResultsComplete;
}
2016-07-19 17:39:21 +02:00
public SearchSettings getSearchSettings() {
return searchSettings;
}
2016-07-10 11:46:39 +02:00
public void updateSettings(SearchSettings settings) {
searchSettings = settings;
2016-07-08 14:32:02 +02:00
}
2016-07-11 10:01:25 +02:00
private List<SearchResult> filterCurrentResults(List<SearchResult> rr, SearchPhrase phrase) {
List<SearchResult> l = currentSearchResult.searchResults;
2016-07-08 14:32:02 +02:00
for(SearchResult r : l) {
if(filterOneResult(r, phrase)) {
rr.add(r);
}
}
return rr;
}
private boolean filterOneResult(SearchResult object, SearchPhrase phrase) {
2016-07-10 11:46:39 +02:00
NameStringMatcher nameStringMatcher = phrase.getNameStringMatcher();
return nameStringMatcher.matches(object.localeName) || nameStringMatcher.matches(object.otherNames);
2016-07-08 14:32:02 +02:00
}
public boolean selectSearchResult(SearchResult r) {
this.phrase = this.phrase.selectWord(r);
return true;
}
2016-07-21 11:34:54 +02:00
2016-07-11 10:01:25 +02:00
public SearchResultCollection search(final String text, final ResultMatcher<SearchResult> matcher) {
SearchResultCollection quickRes = new SearchResultCollection();
2016-07-08 14:32:02 +02:00
final int request = requestNumber.incrementAndGet();
final SearchPhrase phrase = this.phrase.generateNewPhrase(text, searchSettings);
this.phrase = phrase;
2016-07-11 10:01:25 +02:00
quickRes.phrase = phrase;
filterCurrentResults(quickRes.searchResults, phrase);
2016-07-12 20:16:42 +02:00
LOG.info("> Search phrase " + phrase + " " + quickRes.searchResults.size());
2016-07-08 14:32:02 +02:00
singleThreadedExecutor.submit(new Runnable() {
@Override
public void run() {
try {
2016-07-13 11:59:44 +02:00
SearchResultMatcher rm = new SearchResultMatcher(matcher, request, requestNumber, totalLimit);
2016-07-21 11:34:54 +02:00
if(TIMEOUT_BETWEEN_CHARS > 0) {
Thread.sleep(TIMEOUT_BETWEEN_CHARS);
}
2016-07-24 19:05:51 +02:00
if(rm.isCancelled()) {
return;
}
2016-07-08 14:32:02 +02:00
searchInBackground(phrase, rm);
if (!rm.isCancelled()) {
sortSearchResults(phrase, rm.getRequestResults());
2016-07-21 11:34:54 +02:00
filterSearchDuplicateResults(phrase, rm.getRequestResults());
2016-07-24 19:05:51 +02:00
2016-07-12 20:16:42 +02:00
LOG.info(">> Search phrase " + phrase + " " + rm.getRequestResults().size());
2016-07-11 10:01:25 +02:00
SearchResultCollection collection = new SearchResultCollection(rm.getRequestResults(),
phrase);
currentSearchResult = collection;
2016-07-08 14:32:02 +02:00
if (onResultsComplete != null) {
onResultsComplete.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
2016-07-11 10:01:25 +02:00
return quickRes;
2016-07-08 14:32:02 +02:00
}
2016-07-22 14:25:45 +02:00
2016-07-24 22:34:26 +02:00
2016-07-24 19:05:51 +02:00
2016-07-08 14:32:02 +02:00
private void searchInBackground(final SearchPhrase phrase, SearchResultMatcher matcher) {
for (SearchWord sw : phrase.getWords()) {
2016-07-12 13:45:42 +02:00
if(sw.getResult() != null && sw.getResult().file != null) {
phrase.selectFile(sw.getResult().file);
2016-07-08 14:32:02 +02:00
}
}
2016-07-10 11:46:39 +02:00
phrase.sortFiles();
2016-07-08 14:32:02 +02:00
ArrayList<SearchCoreAPI> lst = new ArrayList<>(apis);
Collections.sort(lst, new Comparator<SearchCoreAPI>() {
@Override
public int compare(SearchCoreAPI o1, SearchCoreAPI o2) {
return Algorithms.compare(o1.getSearchPriority(phrase),
o2.getSearchPriority(phrase));
}
});
for(SearchCoreAPI api : lst) {
if(matcher.isCancelled()) {
break;
}
2016-07-10 11:46:39 +02:00
if(api.getSearchPriority(phrase) == -1) {
continue;
}
2016-07-08 14:32:02 +02:00
try {
api.search(phrase, matcher);
2016-07-13 22:32:52 +02:00
matcher.apiSearchFinished(api, phrase);
2016-07-12 20:16:42 +02:00
} catch (Throwable e) {
2016-07-08 14:32:02 +02:00
e.printStackTrace();
2016-07-12 20:16:42 +02:00
LOG.error(e.getMessage(), e);
2016-07-08 14:32:02 +02:00
}
}
}
2016-07-21 11:34:54 +02:00
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)) {
2016-07-25 20:20:01 +02:00
double similarityRadius = 30;
2016-07-21 11:34:54 +02:00
if(a1 != null && a2 != null) {
if(a1.getType().getKeyName().equals("natural") &&
a2.getType().getKeyName().equals("natural")) {
2016-07-21 11:37:24 +02:00
similarityRadius = 10000;
2016-07-21 11:34:54 +02:00
}
}
2016-07-25 20:20:01 +02:00
if(ObjectType.isAddress(r1.objectType) && ObjectType.isAddress(r2.objectType)) {
similarityRadius = 100;
}
2016-07-21 11:34:54 +02:00
return MapUtils.getDistance(r1.location, r2.location) < similarityRadius;
}
}
return false;
}
2016-07-08 14:32:02 +02:00
2016-07-21 11:34:54 +02:00
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;
}
}
}
2016-07-08 14:32:02 +02:00
2016-07-13 22:32:52 +02:00
public void sortSearchResults(SearchPhrase sp, List<SearchResult> searchResults) {
2016-07-08 14:32:02 +02:00
// 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) {
2016-07-24 22:34:26 +02:00
if(o1.getFoundWordCount() != o2.getFoundWordCount()) {
return -Algorithms.compare(o1.getFoundWordCount(), o2.getFoundWordCount());
2016-07-24 19:05:51 +02:00
}
2016-07-08 14:32:02 +02:00
double s1 = o1.getSearchDistance(loc);
double s2 = o2.getSearchDistance(loc);
int cmp = Double.compare(s1, s2);
if(cmp != 0) {
return cmp;
}
2016-07-12 13:22:47 +02:00
int st1 = Algorithms.extractFirstIntegerNumber(o1.localeName);
int st2 = Algorithms.extractFirstIntegerNumber(o2.localeName);
if(st1 != st2) {
return Algorithms.compare(st1, st2);
}
2016-07-24 23:15:55 +02:00
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);
2016-07-08 14:32:02 +02:00
}
});
}
2016-07-24 22:34:26 +02:00
public static class SearchResultMatcher
implements ResultMatcher<SearchResult>{
2016-07-08 14:32:02 +02:00
private final List<SearchResult> requestResults = new ArrayList<>();
2016-07-13 11:59:44 +02:00
private final ResultMatcher<SearchResult> matcher;
2016-07-08 14:32:02 +02:00
private final int request;
2016-07-13 11:59:44 +02:00
private final int totalLimit;
2016-07-24 19:05:51 +02:00
private SearchResult parentSearchResult;
2016-07-13 11:59:44 +02:00
private final AtomicInteger requestNumber;
2016-07-08 14:32:02 +02:00
int count = 0;
2016-07-13 11:59:44 +02:00
public SearchResultMatcher(ResultMatcher<SearchResult> matcher, int request,
AtomicInteger requestNumber, int totalLimit) {
2016-07-08 14:32:02 +02:00
this.matcher = matcher;
this.request = request;
2016-07-13 11:59:44 +02:00
this.requestNumber = requestNumber;
this.totalLimit = totalLimit;
2016-07-08 14:32:02 +02:00
}
2016-07-24 22:34:26 +02:00
public SearchResult setParentSearchResult(SearchResult parentSearchResult) {
SearchResult prev = this.parentSearchResult;
2016-07-24 19:05:51 +02:00
this.parentSearchResult = parentSearchResult;
2016-07-24 22:34:26 +02:00
return prev;
2016-07-24 19:05:51 +02:00
}
2016-07-08 14:32:02 +02:00
public List<SearchResult> getRequestResults() {
return requestResults;
}
2016-07-12 20:16:42 +02:00
2016-07-24 22:34:26 +02:00
public int getCount() {
return requestResults.size();
}
2016-07-13 22:32:52 +02:00
public void apiSearchFinished(SearchCoreAPI api, SearchPhrase phrase) {
if(matcher != null) {
SearchResult sr = new SearchResult(phrase);
sr.objectType = ObjectType.SEARCH_API_FINISHED;
sr.object = api;
2016-07-24 19:05:51 +02:00
sr.parentSearchResult = parentSearchResult;
2016-07-13 22:32:52 +02:00
matcher.publish(sr);
}
}
public void apiSearchRegionFinished(SearchCoreAPI api, BinaryMapIndexReader region, SearchPhrase phrase) {
if(matcher != null) {
SearchResult sr = new SearchResult(phrase);
sr.objectType = ObjectType.SEARCH_API_REGION_FINISHED;
sr.object = api;
2016-07-24 19:05:51 +02:00
sr.parentSearchResult = parentSearchResult;
sr.file = region;
matcher.publish(sr);
}
}
2016-07-08 14:32:02 +02:00
@Override
public boolean publish(SearchResult object) {
if(matcher == null || matcher.publish(object)) {
count++;
2016-07-24 19:05:51 +02:00
object.parentSearchResult = parentSearchResult;
2016-07-08 14:32:02 +02:00
if(totalLimit == -1 || count < totalLimit) {
requestResults.add(object);
}
return true;
}
return false;
}
@Override
public boolean isCancelled() {
boolean cancelled = request != requestNumber.get();
return cancelled || (matcher != null && matcher.isCancelled());
}
}
}