From 9899c8de5ed55b59da7f25436906219678d0e5a9 Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Fri, 8 Jul 2016 15:32:02 +0300 Subject: [PATCH] Implement search example --- .../osmand/binary/BinaryMapIndexReader.java | 5 +- .../search/example/SearchUIAdapter.java | 141 -------- .../osmand/search/example/SearchUICore.java | 234 ++++++++++++++ .../search/example/core/ObjectType.java | 2 +- .../search/example/core/SearchCore.java | 190 ----------- .../search/example/core/SearchCoreAPI.java | 19 +- .../example/core/SearchCoreFactory.java | 300 ++++++++++++++++++ .../search/example/core/SearchPhrase.java | 127 ++++++-- .../search/example/core/SearchResult.java | 23 +- .../search/example/core/SearchSettings.java | 54 ++++ .../search/example/core/SearchWord.java | 10 +- 11 files changed, 718 insertions(+), 387 deletions(-) delete mode 100644 OsmAnd-java/src/net/osmand/search/example/SearchUIAdapter.java create mode 100644 OsmAnd-java/src/net/osmand/search/example/SearchUICore.java delete mode 100644 OsmAnd-java/src/net/osmand/search/example/core/SearchCore.java create mode 100644 OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java create mode 100644 OsmAnd-java/src/net/osmand/search/example/core/SearchSettings.java diff --git a/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java b/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java index bdb635526c..5cd124373e 100644 --- a/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java +++ b/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java @@ -17,7 +17,6 @@ import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.io.Reader; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -1462,7 +1461,7 @@ public class BinaryMapIndexReader { StringMatcherMode matcherMode) { SearchRequest request = new SearchRequest(); request.resultMatcher = resultMatcher; - request.nameQuery = nameRequest; + request.nameQuery = nameRequest.trim(); request.matcherMode = matcherMode; return request; } @@ -1553,7 +1552,7 @@ public class BinaryMapIndexReader { request.top = stop; request.bottom = sbottom; request.resultMatcher = resultMatcher; - request.nameQuery = nameFilter; + request.nameQuery = nameFilter.trim(); return request; } diff --git a/OsmAnd-java/src/net/osmand/search/example/SearchUIAdapter.java b/OsmAnd-java/src/net/osmand/search/example/SearchUIAdapter.java deleted file mode 100644 index 58ee62de7d..0000000000 --- a/OsmAnd-java/src/net/osmand/search/example/SearchUIAdapter.java +++ /dev/null @@ -1,141 +0,0 @@ -package net.osmand.search.example; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import net.osmand.ResultMatcher; -import net.osmand.StringMatcher; -import net.osmand.binary.BinaryMapIndexReader; -import net.osmand.data.LatLon; -import net.osmand.search.example.core.SearchPhrase; -import net.osmand.search.example.core.SearchResult; - -public class SearchUIAdapter { - - private SearchPhrase phrase = new SearchPhrase(null); - private List currentSearchResults = new ArrayList<>(); - - private final BinaryMapIndexReader[] searchIndexes; - - private ThreadPoolExecutor singleThreadedExecutor; - private LinkedBlockingQueue taskQueue; - private Runnable onResultsComplete = null; - private AtomicInteger requestNumber = new AtomicInteger(); - - public SearchUIAdapter(BinaryMapIndexReader[] searchIndexes) { - this.searchIndexes = searchIndexes; - taskQueue = new LinkedBlockingQueue(); - singleThreadedExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,taskQueue); - } - - public List getCurrentSearchResults() { - return currentSearchResults; - } - - public void setOnResultsComplete(Runnable onResultsComplete) { - this.onResultsComplete = onResultsComplete; - } - - public void setSearchLocation(LatLon l) { - phrase = new SearchPhrase(l); - } - - public void updateSearchPhrase(SearchPhrase p) { - // TODO update logic - boolean hasSameConstantWords = p.hasSameConstantWords(this.phrase); - this.phrase = p; - if(hasSameConstantWords) { - filterCurrentResults(phrase); - } - boolean lastWordLooksLikeURLOrNumber = true; - if(lastWordLooksLikeURLOrNumber) { - // add search result location - } - - boolean poiTypeWasNotSelected = true; - if(poiTypeWasNotSelected) { - // add search result poi filters - sortSearchResults(currentSearchResults); - } - - } - - - - private List filterCurrentResults(SearchPhrase phrase) { - List rr = new ArrayList<>(); - List l = currentSearchResults; - for(SearchResult r : l) { - if(filterOneResult(r, phrase)) { - rr.add(r); - } - } - return rr; - } - - private boolean filterOneResult(SearchResult object, SearchPhrase phrase) { - StringMatcher nameStringMatcher = phrase.getNameStringMatcher(); - if(phrase.getLastWord().length() <= 2 && phrase.isNoSelectedType()) { - return true; - } - return nameStringMatcher.matches(object.mainName); - } - - public List search(final String text) { - List list = new ArrayList<>(); - final int request = requestNumber.incrementAndGet(); - final SearchPhrase phrase = this.phrase.generateNewPhrase(text); - this.phrase = phrase; - list.addAll(filterCurrentResults(phrase)); - System.out.println("> Search phrase " + phrase + " " + list.size()); - singleThreadedExecutor.getQueue().drainTo(new ArrayList<>()); - singleThreadedExecutor.submit(new Runnable() { - - @Override - public void run() { - try { - Thread.sleep(200); - List rs = new ArrayList<>(); - for (BinaryMapIndexReader bmir : searchIndexes) { - if (bmir.getRegionCenter() != null) { - SearchResult sr = new SearchResult(); - sr.mainName = bmir.getRegionName(); - sr.location = bmir.getRegionCenter(); - sr.preferredZoom = 6; - if(filterOneResult(sr, phrase)) { - rs.add(sr); - } - } - } - boolean cancelled = request != requestNumber.get(); - if (!cancelled) { - sortSearchResults(rs); - System.out.println(">> Search phrase " + phrase + " " + rs.size()); - currentSearchResults = rs; - if (onResultsComplete != null) { - onResultsComplete.run(); - } - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - - } - }); - return list; - } - - private void sortSearchResults(List searchResults) { - // sort SearchResult by 1. searchDistance 2. Name - // TODO - } - - - - -} diff --git a/OsmAnd-java/src/net/osmand/search/example/SearchUICore.java b/OsmAnd-java/src/net/osmand/search/example/SearchUICore.java new file mode 100644 index 0000000000..55e181b19b --- /dev/null +++ b/OsmAnd-java/src/net/osmand/search/example/SearchUICore.java @@ -0,0 +1,234 @@ +package net.osmand.search.example; + +import java.io.IOException; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +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 org.apache.commons.logging.Log; + +import net.osmand.PlatformUtil; +import net.osmand.ResultMatcher; +import net.osmand.StringMatcher; +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.data.LatLon; +import net.osmand.search.example.core.ObjectType; +import net.osmand.search.example.core.SearchCoreAPI; +import net.osmand.search.example.core.SearchCoreFactory; +import net.osmand.search.example.core.SearchPhrase; +import net.osmand.search.example.core.SearchResult; +import net.osmand.search.example.core.SearchSettings; +import net.osmand.search.example.core.SearchWord; +import net.osmand.util.Algorithms; + +public class SearchUICore { + + private static final Log LOG = PlatformUtil.getLog(SearchUICore.class); + private SearchPhrase phrase; + private List currentSearchResults = new ArrayList<>(); + + private ThreadPoolExecutor singleThreadedExecutor; + private LinkedBlockingQueue taskQueue; + private Runnable onResultsComplete = null; + private AtomicInteger requestNumber = new AtomicInteger(); + private int totalLimit = 20; // -1 unlimited + + List apis = new ArrayList<>(); + private SearchSettings searchSettings; + + + public SearchUICore(BinaryMapIndexReader[] searchIndexes) { + List searchIndexesList = Arrays.asList(searchIndexes); + taskQueue = new LinkedBlockingQueue(); + searchSettings = new SearchSettings(searchIndexesList); + phrase = new SearchPhrase(searchSettings); + singleThreadedExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, taskQueue); + init(); + } + + public int getTotalLimit() { + return totalLimit; + } + + public void setTotalLimit(int totalLimit) { + this.totalLimit = totalLimit; + } + + public void init() { +// apis.add(new SearchAmenityByNameAPI()); +// apis.add(new SearchAmenityByTypeAPI()); +// apis.add(new SearchAddressByNameAPI()); +// apis.add(new SearchStreetByCityAPI()); +// apis.add(new SearchBuildingAndIntersectionsByStreetAPI()); + apis.add(new SearchCoreFactory.SearchRegionByNameAPI()); + apis.add(new SearchCoreFactory.SearchAddressByNameAPI()); + } + + public List getCurrentSearchResults() { + return currentSearchResults; + } + + public SearchPhrase getPhrase() { + return phrase; + } + + public void setOnResultsComplete(Runnable onResultsComplete) { + this.onResultsComplete = onResultsComplete; + } + + + public void setSearchLocation(LatLon l) { + searchSettings = searchSettings.setOriginalLocation(l); + } + + + private List filterCurrentResults(SearchPhrase phrase) { + List rr = new ArrayList<>(); + List l = currentSearchResults; + for(SearchResult r : l) { + if(filterOneResult(r, phrase)) { + rr.add(r); + } + } + return rr; + } + + private boolean filterOneResult(SearchResult object, SearchPhrase phrase) { + StringMatcher nameStringMatcher = phrase.getNameStringMatcher(); + + return nameStringMatcher.matches(object.mainName); + } + + public boolean selectSearchResult(SearchResult r) { + this.phrase = this.phrase.selectWord(r); + return true; + } + + public List search(final String text, final ResultMatcher matcher) { + List list = new ArrayList<>(); + final int request = requestNumber.incrementAndGet(); + final SearchPhrase phrase = this.phrase.generateNewPhrase(text, searchSettings); + this.phrase = phrase; + list.addAll(filterCurrentResults(phrase)); + System.out.println("> Search phrase " + phrase + " " + list.size()); + singleThreadedExecutor.submit(new Runnable() { + + @Override + public void run() { + try { + SearchResultMatcher rm = new SearchResultMatcher(matcher, request); + if(rm.isCancelled()) { + return; + } + Thread.sleep(200); // FIXME + searchInBackground(phrase, rm); + if (!rm.isCancelled()) { + sortSearchResults(phrase, rm.getRequestResults()); + System.out.println(">> Search phrase " + phrase + " " + rm.getRequestResults().size()); + currentSearchResults = rm.getRequestResults(); + if (onResultsComplete != null) { + onResultsComplete.run(); + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + + + }); + return list; + } + + private void searchInBackground(final SearchPhrase phrase, SearchResultMatcher matcher) { + for (SearchWord sw : phrase.getWords()) { + if (sw.getType() == ObjectType.REGION) { + phrase.selectFile((BinaryMapIndexReader) sw.getResult().object); + } + } + ArrayList lst = new ArrayList<>(apis); + Collections.sort(lst, new Comparator() { + + @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; + } + try { + api.search(phrase, matcher); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + e.printStackTrace(); + } + } + } + + + + + + private void sortSearchResults(SearchPhrase sp, List searchResults) { + // sort SearchResult by 1. searchDistance 2. Name + final LatLon loc = sp.getLastTokenLocation(); + final Collator clt = Collator.getInstance(); + Collections.sort(searchResults, new Comparator() { + + @Override + public int compare(SearchResult o1, SearchResult o2) { + double s1 = o1.getSearchDistance(loc); + double s2 = o2.getSearchDistance(loc); + int cmp = Double.compare(s1, s2); + if(cmp != 0) { + return cmp; + } + return clt.compare(o1.mainName, o2.mainName); + } + }); + } + + public class SearchResultMatcher implements ResultMatcher{ + private final List requestResults = new ArrayList<>(); + private ResultMatcher matcher; + private final int request; + int count = 0; + + + public SearchResultMatcher(ResultMatcher matcher, int request) { + this.matcher = matcher; + this.request = request; + } + + public List getRequestResults() { + return requestResults; + } + @Override + public boolean publish(SearchResult object) { + if(matcher == null || matcher.publish(object)) { + count++; + 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()); + } + } +} diff --git a/OsmAnd-java/src/net/osmand/search/example/core/ObjectType.java b/OsmAnd-java/src/net/osmand/search/example/core/ObjectType.java index 19f4aa935d..d73cbe60b4 100644 --- a/OsmAnd-java/src/net/osmand/search/example/core/ObjectType.java +++ b/OsmAnd-java/src/net/osmand/search/example/core/ObjectType.java @@ -3,7 +3,7 @@ package net.osmand.search.example.core; public enum ObjectType { CITY(true), VILLAGE(true), POSTCODE(true), STREET(true), HOUSE(true), POI_TYPE(false), POI(true), LOCATION(true), FAVORITE(true), - RECENT_OBJ(true), WPT(true), UNKNOWN_NAME_FILTER(false); + REGION(true), RECENT_OBJ(true), WPT(true), UNKNOWN_NAME_FILTER(false); private boolean hasLocation; private ObjectType(boolean location) { this.hasLocation = location; diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchCore.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchCore.java deleted file mode 100644 index 56d529de9b..0000000000 --- a/OsmAnd-java/src/net/osmand/search/example/core/SearchCore.java +++ /dev/null @@ -1,190 +0,0 @@ -package net.osmand.search.example.core; - -import gnu.trove.map.hash.TLongObjectHashMap; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import net.osmand.Location; -import net.osmand.ResultMatcher; -import net.osmand.binary.BinaryMapIndexReader.SearchRequest; -import net.osmand.data.LatLon; -import net.osmand.data.QuadRect; -import net.osmand.util.MapUtils; - - -public class SearchCore { - List apis = new ArrayList<>(); - - public static abstract class SearchBaseAPI implements SearchCoreAPI { - @Override - public List search(SearchPhrase phrase, int radiusLevel, SearchCallback callback, - List existingSearchResults) { - return Collections.emptyList(); - } - - public QuadRect getBBoxToSearch(int radiusInMeters, int radiusLevel, LatLon loc) { - float calcRadios = radiusLevel * radiusInMeters; - float coeff = (float) (calcRadios / MapUtils.getTileDistanceWidth(SearchRequest.ZOOM_TO_SEARCH_POI)); - double tx = MapUtils.getTileNumberX(SearchRequest.ZOOM_TO_SEARCH_POI, loc.getLongitude()); - double ty = MapUtils.getTileNumberY(SearchRequest.ZOOM_TO_SEARCH_POI, loc.getLatitude()); - double topLeftX = tx - coeff; - double topLeftY = ty - coeff; - double bottomRightX = tx + coeff; - double bottomRightY = ty + coeff; - return new QuadRect(topLeftX, bottomRightY, bottomRightX, topLeftY); - } - - @Override - public int getSearchPriority(SearchPhrase p) { - return 10; - } - } - - public static class SearchAmenityByNameAPI extends SearchBaseAPI { - // BBOX QuadRect bbox = getBBoxToSearch(10000 * typedLettersInStreet, radiusLevel, phrase.getLastTokenLocation()); - // if(poiObjectNotSelected) { - // LIMIT 100 - // if(poiTypeSelected) { - // BBOX - result priority 5, distPriority 1 - // } else { - // BBOX - result priority 5, distPriority 4 - // } - // } - @Override - public int getSearchPriority(SearchPhrase p) { - return 10; - } - } - - public static class SearchAmenityByTypeAPI extends SearchBaseAPI { - public boolean isLastWordPoi(SearchPhrase p ) { - return p.isLastWord(ObjectType.POI_TYPE); - } - - @Override - public List search(SearchPhrase phrase, int radiusLevel, SearchCallback callback, - List existingSearchResults) { - if(isLastWordPoi(phrase)) { - QuadRect bbox = getBBoxToSearch(10000, radiusLevel, phrase.getLastTokenLocation()); - // TODO NO LIMIT , BBOX - result priority 5, distPriority 1 - } - return Collections.emptyList(); - } - - @Override - public int getSearchPriority(SearchPhrase p) { - if(isLastWordPoi(p)) { - return 1; - } - return 10; - } - } - - public static class SearchAddressByNameAPI extends SearchBaseAPI { - - public boolean isLastWordPoi(SearchPhrase p ) { - return p.isLastWord(ObjectType.POI); - } - - public boolean isNoSelectedType(SearchPhrase p ) { - return p.isNoSelectedType(); - } - - @Override - public List search(SearchPhrase phrase, int radiusLevel, SearchCallback callback, - List existingSearchResults) { - // (search streets in neighboor cities for radiusLevel > 2) - if((isLastWordPoi(phrase) || isNoSelectedType(phrase) || radiusLevel >= 2 - ) && !(phrase.isEmpty())) { - int typedLettersInStreet = 1; - QuadRect bbox = getBBoxToSearch(20000 * typedLettersInStreet, radiusLevel, phrase.getLastTokenLocation()); - int priority = isNoSelectedType(phrase) ? 1 : 3; - // priority - // LIMIT 100 * radiusLevel - // BBOX streets - result priority 5, distPriority ${priority} - // BBOX postcodes - result priority 5, distPriority ${priority} - // BBOX / 3 (3 times smaller) villages - result priority 5, distPriority ${priority} - // BBOX * 4 (3 times smaller) cities/towns - result priority 5, distPriority (${priority}/10) - } - return Collections.emptyList(); - } - - @Override - public int getSearchPriority(SearchPhrase p) { - if(isNoSelectedType(p)) { - return 5; - } - if(isLastWordPoi(p)) { - return 8; - } - return 10; - } - - } - - public static class SearchStreetByCityAPI extends SearchBaseAPI { - @Override - public List search(SearchPhrase phrase, int radiusLevel, SearchCallback callback, - List existingSearchResults) { - if(isLastWordCityGroup(phrase)) { - // search all streets - // TODO LIMIT 100 * radiusLevel - priority 1, distPriority 0 (alphabetic) - } - return Collections.emptyList(); - } - - public boolean isLastWordCityGroup(SearchPhrase p ) { - return p.isLastWord(ObjectType.CITY) || p.isLastWord(ObjectType.POSTCODE) || - p.isLastWord(ObjectType.VILLAGE); - } - - @Override - public int getSearchPriority(SearchPhrase p) { - return 1; - } - - } - - public static class SearchBuildingAndIntersectionsByStreetAPI extends SearchBaseAPI { - - @Override - public List search(SearchPhrase phrase, int radiusLevel, SearchCallback callback, - List existingSearchResults) { - if(isLastWordStreet(phrase)) { - // search all buildings - // TODO NO LIMIT - priority 1, distPriority 0 (alphabetic) - } - return Collections.emptyList(); - } - - public boolean isLastWordStreet(SearchPhrase p ) { - return p.isLastWord(ObjectType.STREET); - } - - @Override - public int getSearchPriority(SearchPhrase p) { - return 1; - } - } - - - public void init() { - apis.add(new SearchAmenityByNameAPI()); - apis.add(new SearchAmenityByTypeAPI()); - apis.add(new SearchAddressByNameAPI()); - apis.add(new SearchStreetByCityAPI()); - apis.add(new SearchBuildingAndIntersectionsByStreetAPI()); - } - - private void callToSearchCoreAPI( - SearchPhrase p, - ResultMatcher> publisher, - ResultMatcher visitor) { - // sort apis by prioirity to search phrase - // call apis in for loop - } - - -} diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreAPI.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreAPI.java index d8104d4193..a9df32516c 100644 --- a/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreAPI.java +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreAPI.java @@ -1,20 +1,13 @@ package net.osmand.search.example.core; -import java.util.List; +import java.io.IOException; + +import net.osmand.search.example.SearchUICore.SearchResultMatcher; public interface SearchCoreAPI { - + public int getSearchPriority(SearchPhrase p); - - public interface SearchCallback { - - public boolean accept(SearchResult r); - } - - public List search( - SearchPhrase phrase, - int radiusLevel, - SearchCallback callback, - List existingSearchResults); + + public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) throws IOException; } diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java new file mode 100644 index 0000000000..ae2e968cad --- /dev/null +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java @@ -0,0 +1,300 @@ +package net.osmand.search.example.core; + +import java.io.IOException; + +import net.osmand.CollatorStringMatcher.StringMatcherMode; +import net.osmand.ResultMatcher; +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.binary.BinaryMapIndexReader.SearchRequest; +import net.osmand.data.City; +import net.osmand.data.City.CityType; +import net.osmand.data.LatLon; +import net.osmand.data.MapObject; +import net.osmand.data.QuadRect; +import net.osmand.data.Street; +import net.osmand.search.example.SearchUICore.SearchResultMatcher; +import net.osmand.util.MapUtils; + + +public class SearchCoreFactory { + // TODO sort offline indexes by location + // TODO test add and delete characters + // TODO add location parse + // TODO add url parse (geo) + // TODO ? boolean hasSameConstantWords = p.hasSameConstantWords(this.phrase); + // TODO amenity types + // TODO amenity by type + // TODO streets by city + // TODO buildings by street + // TODO display closest city to villages + // TODO automatically increase radius if nothing found + + public static abstract class SearchBaseAPI implements SearchCoreAPI { + @Override + public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) throws IOException { + return true; + } + + public QuadRect getBBoxToSearch(int radiusInMeters, int radiusLevel, LatLon loc) { + if(loc == null) { + return null; + } + float calcRadios = radiusLevel * radiusInMeters; + float coeff = (float) (calcRadios / MapUtils.getTileDistanceWidth(SearchRequest.ZOOM_TO_SEARCH_POI)); + double tx = MapUtils.getTileNumberX(SearchRequest.ZOOM_TO_SEARCH_POI, loc.getLongitude()); + double ty = MapUtils.getTileNumberY(SearchRequest.ZOOM_TO_SEARCH_POI, loc.getLatitude()); + double topLeftX = tx - coeff; + double topLeftY = ty - coeff; + double bottomRightX = tx + coeff; + double bottomRightY = ty + coeff; + return new QuadRect(topLeftX, bottomRightY, bottomRightX, topLeftY); + } + + @Override + public int getSearchPriority(SearchPhrase p) { + return 10; + } + } + + public static class SearchRegionByNameAPI extends SearchBaseAPI { + + @Override + public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) { + if(phrase.hasObjectType(ObjectType.REGION)) { + return false; + } + for (BinaryMapIndexReader bmir : phrase.getOfflineIndexes()) { + if (bmir.getRegionCenter() != null) { + SearchResult sr = new SearchResult(phrase); + sr.mainName = bmir.getRegionName(); + sr.object = bmir; + sr.objectType = ObjectType.REGION; + sr.location = bmir.getRegionCenter(); + sr.preferredZoom = 6; + if (phrase.getLastWord().length() <= 2 && phrase.isNoSelectedType()) { + resultMatcher.publish(sr); + } else if (phrase.getNameStringMatcher().matches(sr.mainName)) { + resultMatcher.publish(sr); + } + } + } + return true; + } + + @Override + public int getSearchPriority(SearchPhrase p) { + return 1; + } + } + + public static class SearchAddressByNameAPI extends SearchBaseAPI { + + private static final int DEFAULT_BBOX_RADIUS = 1000*10000; + private static final int LIMIT = 100; + + public boolean isLastWordPoi(SearchPhrase p) { + return p.isLastWord(ObjectType.POI); + } + + public boolean isLastWordRegion(SearchPhrase p) { + return p.isLastWord(ObjectType.REGION); + } + + public boolean isNoSelectedType(SearchPhrase p) { + return p.isNoSelectedType(); + } + + @Override + public boolean search(final SearchPhrase phrase, final SearchResultMatcher resultMatcher) throws IOException { + if (!phrase.hasObjectType(ObjectType.REGION)) { + return false; + } + if (phrase.getLastWord().isEmpty()) { + return false; + } + // (search streets in neighboor cities for radiusLevel > 2) + if (isLastWordPoi(phrase) || isNoSelectedType(phrase) || + isLastWordRegion(phrase) || phrase.getRadiusLevel() >= 2) { + int letters = phrase.getLastWord().length() / 3 + 1; + final boolean locSpecified = false; // phrase.getLastTokenLocation() != null; + LatLon loc = phrase.getLastTokenLocation(); + final QuadRect streetBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS * letters, phrase.getRadiusLevel(), + phrase.getLastTokenLocation()); + final QuadRect postcodeBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS * 2 * letters, phrase.getRadiusLevel(), + phrase.getLastTokenLocation()); + final QuadRect villagesBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS * letters, phrase.getRadiusLevel(), + phrase.getLastTokenLocation()); + final QuadRect cityBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS * 4 * letters, phrase.getRadiusLevel(), + phrase.getLastTokenLocation()); + final int priority = isNoSelectedType(phrase) ? 1 : 3; + ResultMatcher rm = new ResultMatcher() { + int limit = 0; + @Override + public boolean publish(MapObject object) { + if(isCancelled()) { + return false; + } + SearchResult sr = new SearchResult(phrase); + sr.object = object; + sr.mainName = object.getName(); + sr.location = object.getLocation(); + sr.priorityDistance = 1; + sr.priority = priority; + int y = MapUtils.get31TileNumberY(object.getLocation().getLatitude()); + int x = MapUtils.get31TileNumberX(object.getLocation().getLongitude()); + if (object instanceof Street) { + if(locSpecified && !streetBbox.contains(x, y, x, y)) { + return false; + } + sr.objectType = ObjectType.STREET; + } else if (object instanceof City) { + CityType type = ((City)object).getType(); + if (type == CityType.CITY || type == CityType.TOWN) { + if (locSpecified && !cityBbox.contains(x, y, x, y)) { + return false; + } + sr.objectType = ObjectType.CITY; + sr.priorityDistance = 0.1; + } else if (((City)object).isPostcode()) { + if (locSpecified && !postcodeBbox.contains(x, y, x, y)) { + return false; + } + sr.objectType = ObjectType.POSTCODE; + } else { + if (locSpecified && !villagesBbox.contains(x, y, x, y)) { + return false; + } + sr.objectType = ObjectType.VILLAGE; + } + } else { + return false; + } + limit ++; + resultMatcher.publish(sr); + return false; + } + + @Override + public boolean isCancelled() { + return limit > LIMIT * phrase.getRadiusLevel() || + resultMatcher.isCancelled(); + } + }; + for(BinaryMapIndexReader r : phrase.getOfflineIndexes()) { + SearchRequest req = BinaryMapIndexReader.buildAddressByNameRequest(rm, + phrase.getLastWord().toLowerCase(), StringMatcherMode.CHECK_ONLY_STARTS_WITH); + if(locSpecified) { + req.setBBoxRadius(loc.getLatitude(), loc.getLongitude(), DEFAULT_BBOX_RADIUS * 4 * letters); + } + r.searchAddressDataByName(req); + } + } + return true; + } + + @Override + public int getSearchPriority(SearchPhrase p) { + if (isNoSelectedType(p)) { + return 5; + } + if (isLastWordPoi(p)) { + return 8; + } + return 10; + } + + } + + public static class SearchAmenityByNameAPI extends SearchBaseAPI { + // TODO + // BBOX QuadRect bbox = getBBoxToSearch(10000 * typedLettersInStreet, radiusLevel, phrase.getLastTokenLocation()); + // if(poiObjectNotSelected) { + // LIMIT 100 + // if(poiTypeSelected) { + // BBOX - result priority 5, distPriority 1 + // } else { + // BBOX - result priority 5, distPriority 4 + // } + // } + @Override + public int getSearchPriority(SearchPhrase p) { + return 10; + } + } + + public static class SearchAmenityByTypeAPI extends SearchBaseAPI { + public boolean isLastWordPoi(SearchPhrase p ) { + return p.isLastWord(ObjectType.POI_TYPE); + } + + @Override + public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) { + if(isLastWordPoi(phrase)) { + QuadRect bbox = getBBoxToSearch(10000, phrase.getRadiusLevel(), phrase.getLastTokenLocation()); + // TODO NO LIMIT , BBOX - result priority 5, distPriority 1 + } + return true; + } + + @Override + public int getSearchPriority(SearchPhrase p) { + if(isLastWordPoi(p)) { + return 1; + } + return 10; + } + } + + + + public static class SearchStreetByCityAPI extends SearchBaseAPI { + @Override + public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) { + if(isLastWordCityGroup(phrase)) { + // search all streets + // TODO LIMIT 100 * radiusLevel - priority 1, distPriority 0 (alphabetic) + } + return true; + } + + public boolean isLastWordCityGroup(SearchPhrase p ) { + return p.isLastWord(ObjectType.CITY) || p.isLastWord(ObjectType.POSTCODE) || + p.isLastWord(ObjectType.VILLAGE); + } + + @Override + public int getSearchPriority(SearchPhrase p) { + return 1; + } + + } + + + + public static class SearchBuildingAndIntersectionsByStreetAPI extends SearchBaseAPI { + + @Override + public boolean search(SearchPhrase phrase, SearchResultMatcher resultMatcher) { + if(isLastWordStreet(phrase)) { + // search all buildings + // TODO NO LIMIT - priority 1, distPriority 0 (alphabetic) + } + return true; + } + + public boolean isLastWordStreet(SearchPhrase p ) { + return p.isLastWord(ObjectType.STREET); + } + + @Override + public int getSearchPriority(SearchPhrase p) { + return 1; + } + } + + + + + + +} diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchPhrase.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchPhrase.java index ff6400fc9a..fcee710c46 100644 --- a/OsmAnd-java/src/net/osmand/search/example/core/SearchPhrase.java +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchPhrase.java @@ -6,50 +6,56 @@ import java.util.List; import net.osmand.CollatorStringMatcher; import net.osmand.StringMatcher; import net.osmand.CollatorStringMatcher.StringMatcherMode; +import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.LatLon; +//immutable object public class SearchPhrase { private List words = new ArrayList<>(); - private LatLon originalLocation; private String text = ""; private String lastWord = ""; private CollatorStringMatcher sm; + private SearchSettings settings; + private List indexes; - public SearchPhrase(LatLon location) { - this.originalLocation = location; + public SearchPhrase(SearchSettings settings) { + this.settings = settings; } - + public List getWords() { return words; } - public SearchPhrase generateNewPhrase(String text) { - SearchPhrase sp = new SearchPhrase(originalLocation); - String atext = text; - if (text.startsWith((this.text + this.lastWord).trim())) { - // string is longer - atext = text.substring(this.text.length()); - sp.text = this.text; - sp.words = new ArrayList<>(this.words); - } else { - sp.text = ""; - } - // TODO Reuse previous search words if it is shorter - if (!atext.contains(",")) { - sp.lastWord = atext; - } else { - String[] ws = atext.split(","); - for (int i = 0; i < ws.length - 1; i++) { - if (ws[i].trim().length() > 0) { - sp.words.add(new SearchWord(ws[i].trim())); - } - sp.text += ws[i] + ","; - } + public List getOfflineIndexes() { + if(indexes != null) { + return indexes; } + return settings.getOfflineIndexes(); + } + + public SearchSettings getSettings() { + return settings; + } + + + public int getRadiusLevel() { + return settings.getRadiusLevel(); + } + + public SearchPhrase selectWord(SearchResult res) { + SearchPhrase sp = new SearchPhrase(this.settings); + sp.words.addAll(this.words); + SearchWord sw = new SearchWord(res.mainName.trim(), res); + sp.words.add(sw); + // sp.text = this.text + sw.getWord() + ", "; + // TODO FIX + sp.text = this.text + " " + sw.getWord() + ", "; return sp; } + + public List excludefilterWords() { List w = new ArrayList<>(); for(SearchWord s : words) { @@ -61,11 +67,11 @@ public class SearchPhrase { } public boolean isLastWord(ObjectType p) { - for(int i = words.size() - 1; i >= 0; i--) { + for (int i = words.size() - 1; i >= 0; i--) { SearchWord sw = words.get(i); - if(sw.getType() == ObjectType.POI) { + if (sw.getType() == p) { return true; - } else if(sw.getType() != ObjectType.UNKNOWN_NAME_FILTER) { + } else if (sw.getType() != ObjectType.UNKNOWN_NAME_FILTER) { return false; } } @@ -84,10 +90,23 @@ public class SearchPhrase { return excludefilterWords().equals(p.excludefilterWords()); } + public boolean hasObjectType(ObjectType p) { + for(SearchWord s : words) { + if(s.getType() == p) { + return true; + } + } + return false; + } + + public String getText() { + return text; + } + public String getStringRerpresentation() { StringBuilder sb = new StringBuilder(); for(SearchWord s : words) { - sb.append(s.getWord()).append(", "); + sb.append(s.getWord()).append(" [" + s.getType() + "], "); } sb.append(lastWord); return sb.toString(); @@ -119,9 +138,55 @@ public class SearchPhrase { } } // last token or myLocationOrVisibleMap if not selected - return originalLocation; + return settings.getOriginalLocation(); } + public SearchPhrase generateNewPhrase(String text, SearchSettings settings) { + SearchPhrase sp = new SearchPhrase(settings); + String atext = text; + List leftWords = this.words; + if (text.startsWith((this.text + this.lastWord).trim())) { + // string is longer + atext = text.substring(this.text.length()); + sp.text = this.text; + sp.words = new ArrayList<>(this.words); + leftWords = leftWords.subList(leftWords.size(), leftWords.size()); + } else { + sp.text = ""; + } + if (!atext.contains(",")) { + sp.lastWord = atext; + } else { + String[] ws = atext.split(","); + for (int i = 0; i < ws.length - 1; i++) { + boolean unknown = true; + if (ws[i].trim().length() > 0) { + if (leftWords.size() > 0) { + if (leftWords.get(0).getWord().equalsIgnoreCase(ws[i].trim())) { + sp.words.add(leftWords.get(0)); + leftWords = leftWords.subList(1, leftWords.size()); + unknown = false; + } + } + if(unknown) { + sp.words.add(new SearchWord(ws[i].trim())); + } + sp.text += ws[i] + ", "; + } + + } + } + sp.text = sp.text.trim(); + return sp; + } + + + public void selectFile(BinaryMapIndexReader object) { + if(indexes == null) { + indexes = new ArrayList<>(); + } + this.indexes.add(object); + } } diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchResult.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchResult.java index 7731b013fe..98be84769c 100644 --- a/OsmAnd-java/src/net/osmand/search/example/core/SearchResult.java +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchResult.java @@ -1,18 +1,31 @@ package net.osmand.search.example.core; import net.osmand.data.LatLon; +import net.osmand.util.MapUtils; public class SearchResult { - + // search phrase that makes search result valid + public SearchPhrase requiredSearchPhrase; + public Object object; public ObjectType objectType; - // calculated by formula priority - 1 / (1 + priorityDistance * distance) - public double searchDistance; + public double priority; + public double priorityDistance; + + public SearchResult(SearchPhrase sp) { + this.requiredSearchPhrase = sp; + } + public double getSearchDistance(LatLon location) { + double distance = 0; + if(location != null && this.location != null) { + distance = MapUtils.getDistance(location, this.location); + } + return priority - 1 / ( 1 + priorityDistance * distance); + } public LatLon location; public int preferredZoom = 15; public String mainName; - // search phrase that makes search result valid - public SearchPhrase requiredSearchPhrase; + } diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchSettings.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchSettings.java new file mode 100644 index 0000000000..19bb1f0f16 --- /dev/null +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchSettings.java @@ -0,0 +1,54 @@ +package net.osmand.search.example.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.data.LatLon; + +// immutable object +public class SearchSettings { + + private LatLon originalLocation; + private List offlineIndexes = new ArrayList<>(); + private int radiusLevel = 1; + private int totalLimit = -1; + + public SearchSettings(SearchSettings s) { + if(s != null) { + this.radiusLevel = s.radiusLevel; + this.totalLimit = s.totalLimit; + this.offlineIndexes = s.offlineIndexes; + this.originalLocation = s.originalLocation; + } + } + + public SearchSettings(List offlineIndexes) { + this.offlineIndexes = Collections.unmodifiableList(offlineIndexes); + } + + + public List getOfflineIndexes() { + return offlineIndexes; + } + + public int getRadiusLevel() { + return radiusLevel; + } + + public int getTotalLimit() { + return totalLimit; + } + + public LatLon getOriginalLocation() { + return originalLocation; + } + + public SearchSettings setOriginalLocation(LatLon l) { + SearchSettings s = new SearchSettings(this); + s.originalLocation = l; + return s; + } + +} diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchWord.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchWord.java index 42d1a3e410..0dd1cbbcb5 100644 --- a/OsmAnd-java/src/net/osmand/search/example/core/SearchWord.java +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchWord.java @@ -5,10 +5,14 @@ import net.osmand.data.LatLon; public class SearchWord { private String word; private SearchResult result; - private LatLon location; public SearchWord(String word) { - this.word = word; + this.word = word.trim(); + } + + public SearchWord(String word, SearchResult res) { + this.word = word.trim(); + this.result = res; } public ObjectType getType() { @@ -24,7 +28,7 @@ public class SearchWord { } public LatLon getLocation() { - return location; + return result == null ? null : result.location; } @Override