diff --git a/OsmAnd-java/src/net/osmand/binary/BinaryMapAddressReaderAdapter.java b/OsmAnd-java/src/net/osmand/binary/BinaryMapAddressReaderAdapter.java index a14123754d..aa12210fa6 100644 --- a/OsmAnd-java/src/net/osmand/binary/BinaryMapAddressReaderAdapter.java +++ b/OsmAnd-java/src/net/osmand/binary/BinaryMapAddressReaderAdapter.java @@ -16,6 +16,7 @@ import net.osmand.CollatorStringMatcher.StringMatcherMode; import net.osmand.PlatformUtil; import net.osmand.StringMatcher; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; +import net.osmand.binary.BinaryMapPoiReaderAdapter.PoiRegion; import net.osmand.binary.OsmandOdb.AddressNameIndexDataAtom; import net.osmand.binary.OsmandOdb.OsmAndAddressIndex.CitiesIndex; import net.osmand.binary.OsmandOdb.OsmAndAddressNameIndexData; @@ -55,6 +56,10 @@ public class BinaryMapAddressReaderAdapter { List cities = new ArrayList(); LatLon calculatedCenter = null; + public int bottom31; + public int top31; + public int right31; + public int left31; public String getEnName() { return enName; @@ -108,12 +113,40 @@ public class BinaryMapAddressReaderAdapter { private int readInt() throws IOException { return map.readInt(); } + + private void readBoundariesIndex(AddressRegion region) throws IOException { + while (true) { + int t = codedIS.readTag(); + int tag = WireFormat.getTagFieldNumber(t); + switch (tag) { + case 0: + return; + case OsmandOdb.OsmAndTileBox.LEFT_FIELD_NUMBER: + region.left31 = codedIS.readUInt32(); + break; + case OsmandOdb.OsmAndTileBox.RIGHT_FIELD_NUMBER: + region.right31 = codedIS.readUInt32(); + break; + case OsmandOdb.OsmAndTileBox.TOP_FIELD_NUMBER: + region.top31 = codedIS.readUInt32(); + break; + case OsmandOdb.OsmAndTileBox.BOTTOM_FIELD_NUMBER: + region.bottom31 = codedIS.readUInt32(); + break; + default: + skipUnknownField(t); + break; + } + } + } protected void readAddressIndex(AddressRegion region) throws IOException { while (true) { int t = codedIS.readTag(); int tag = WireFormat.getTagFieldNumber(t); + int length; + int oldLimit; switch (tag) { case 0: if (region.enName == null || region.enName.length() == 0) { @@ -126,9 +159,16 @@ public class BinaryMapAddressReaderAdapter { case OsmandOdb.OsmAndAddressIndex.NAME_EN_FIELD_NUMBER: region.enName = codedIS.readString(); break; + case OsmandOdb.OsmAndAddressIndex.BOUNDARIES_FIELD_NUMBER: + length = codedIS.readRawVarint32(); + oldLimit = codedIS.pushLimit(length); + readBoundariesIndex(region); + codedIS.popLimit(oldLimit); + region.enName = codedIS.readString(); + break; case OsmandOdb.OsmAndAddressIndex.ATTRIBUTETAGSTABLE_FIELD_NUMBER: - int length2 = codedIS.readRawVarint32(); - int oldLimit = codedIS.pushLimit(length2); + length = codedIS.readRawVarint32(); + oldLimit = codedIS.pushLimit(length); region.attributeTagsTable = map.readStringTable(); codedIS.popLimit(oldLimit); break; @@ -156,7 +196,7 @@ public class BinaryMapAddressReaderAdapter { break; case OsmandOdb.OsmAndAddressIndex.NAMEINDEX_FIELD_NUMBER: region.indexNameOffset = codedIS.getTotalBytesRead(); - int length = readInt(); + length = readInt(); codedIS.seek(region.indexNameOffset + length + 4); break; default: diff --git a/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java b/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java index 5cd124373e..63731b6c44 100644 --- a/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java +++ b/OsmAnd-java/src/net/osmand/binary/BinaryMapIndexReader.java @@ -340,6 +340,24 @@ public class BinaryMapIndexReader { } return false; } + + public boolean containsPoiData(int left31x, int top31y, int right31x, int bottom31y) { + for (PoiRegion index : poiIndexes) { + if (right31x >= index.left31 && left31x <= index.right31 && index.top31 <= bottom31y && index.bottom31 >= top31y) { + return true; + } + } + return false; + } + + public boolean containsAddressData(int left31x, int top31y, int right31x, int bottom31y) { + for (AddressRegion index : addressIndexes) { + if (right31x >= index.left31 && left31x <= index.right31 && index.top31 <= bottom31y && index.bottom31 >= top31y) { + return true; + } + } + return false; + } public boolean containsMapData(int tile31x, int tile31y, int zoom) { for (MapIndex mapIndex : mapIndexes) { diff --git a/OsmAnd-java/src/net/osmand/binary/BinaryMapPoiReaderAdapter.java b/OsmAnd-java/src/net/osmand/binary/BinaryMapPoiReaderAdapter.java index bebf11b025..005110a870 100644 --- a/OsmAnd-java/src/net/osmand/binary/BinaryMapPoiReaderAdapter.java +++ b/OsmAnd-java/src/net/osmand/binary/BinaryMapPoiReaderAdapter.java @@ -59,6 +59,10 @@ public class BinaryMapPoiReaderAdapter { double rightLongitude; double topLatitude; double bottomLatitude; + int left31; + int right31; + int top31; + int bottom31; public double getLeftLongitude() { return leftLongitude; @@ -134,16 +138,20 @@ public class BinaryMapPoiReaderAdapter { case 0: return; case OsmandOdb.OsmAndTileBox.LEFT_FIELD_NUMBER: - region.leftLongitude = MapUtils.get31LongitudeX(codedIS.readUInt32()); + region.left31 = codedIS.readUInt32(); + region.leftLongitude = MapUtils.get31LongitudeX(region.left31); break; case OsmandOdb.OsmAndTileBox.RIGHT_FIELD_NUMBER: - region.rightLongitude = MapUtils.get31LongitudeX(codedIS.readUInt32()); + region.right31 = codedIS.readUInt32(); + region.rightLongitude = MapUtils.get31LongitudeX(region.right31); break; case OsmandOdb.OsmAndTileBox.TOP_FIELD_NUMBER: - region.topLatitude = MapUtils.get31LatitudeY(codedIS.readUInt32()); + region.top31 = codedIS.readUInt32(); + region.topLatitude = MapUtils.get31LatitudeY(region.top31); break; case OsmandOdb.OsmAndTileBox.BOTTOM_FIELD_NUMBER: - region.bottomLatitude = MapUtils.get31LatitudeY(codedIS.readUInt32()); + region.bottom31 = codedIS.readUInt32(); + region.bottomLatitude = MapUtils.get31LatitudeY(region.bottom31); break; default: skipUnknownField(t); diff --git a/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java b/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java index 2c5b76fdaf..45b4b08a38 100644 --- a/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchCoreFactory.java @@ -1,9 +1,11 @@ package net.osmand.search.example.core; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -13,6 +15,7 @@ import net.osmand.CollatorStringMatcher.StringMatcherMode; import net.osmand.OsmAndCollator; import net.osmand.ResultMatcher; import net.osmand.StringMatcher; +import net.osmand.binary.BinaryMapAddressReaderAdapter; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; @@ -31,30 +34,32 @@ import net.osmand.osm.PoiFilter; import net.osmand.osm.PoiType; import net.osmand.search.example.SearchUICore.SearchResultMatcher; import net.osmand.search.example.core.SearchPhrase.NameStringMatcher; +import net.osmand.search.example.core.SearchPhrase.SearchPhraseDataType; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; public class SearchCoreFactory { - // TODO fix search amenity by type (category/additional) - - // TODO streets by city + // TODO display closest city to villages // TODO search only closest file (global bbox) // TODO limit to one file if city/street/village/poi selected + // TODO fix search amenity by type (category/additional) + // TODO streets by city + // TODO amenity by name + // TODO add location parse // TODO add url parse (geo) - // TODO amenity by name - // TODO display closest city to villages (and city to street) - // TODO automatically increase radius if nothing found // TODO buildings interpolation // TODO show buildings if street is one or default ( , CITY (den ilp), 1186RM) // TODO exclude duplicate streets/cities... - // TODO display results momentarily - - // TODO add full text search with comma - // TODO add full text search without comma + + // TODO MED display results momentarily + // TODO MED add full text search with comma + // TODO MED add full text search without comma + + // TODO LOW automatically increase radius if nothing found public static abstract class SearchBaseAPI implements SearchCoreAPI { @@ -63,20 +68,8 @@ public class SearchCoreFactory { 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; - double pw = MapUtils.getPowZoom(31 - SearchRequest.ZOOM_TO_SEARCH_POI); - return new QuadRect(topLeftX * pw, topLeftY * pw, bottomRightX * pw, bottomRightY * pw); + public QuadRect getBBoxToSearch(SearchPhrase phrase, int radiusInMeters) { + return phrase.getBBoxToSearch(phrase.getRadiusLevel() * radiusInMeters); } @Override @@ -125,6 +118,7 @@ public class SearchCoreFactory { private static final int DEFAULT_BBOX_RADIUS = 1000*1000; private static final int LIMIT = 10000; + private Map> townCities = new LinkedHashMap<>(); public boolean isLastWordPoi(SearchPhrase p) { return p.isLastWord(ObjectType.POI); @@ -150,14 +144,10 @@ public class SearchCoreFactory { // int letters = phrase.getLastWord().length() / 3 + 1; final boolean locSpecified = phrase.getLastTokenLocation() != null; LatLon loc = phrase.getLastTokenLocation(); - final QuadRect streetBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS, phrase.getRadiusLevel(), - phrase.getLastTokenLocation()); - final QuadRect postcodeBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS * 5, phrase.getRadiusLevel(), - phrase.getLastTokenLocation()); - final QuadRect villagesBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS * 5, phrase.getRadiusLevel(), - phrase.getLastTokenLocation()); - final QuadRect cityBbox = getBBoxToSearch(DEFAULT_BBOX_RADIUS * 10, phrase.getRadiusLevel(), - phrase.getLastTokenLocation()); + final QuadRect streetBbox = getBBoxToSearch(phrase, DEFAULT_BBOX_RADIUS); + final QuadRect postcodeBbox = getBBoxToSearch(phrase, DEFAULT_BBOX_RADIUS * 5); + final QuadRect villagesBbox = getBBoxToSearch(phrase, DEFAULT_BBOX_RADIUS * 5); + final QuadRect cityBbox = getBBoxToSearch(phrase, DEFAULT_BBOX_RADIUS * 10); final int priority = isNoSelectedType(phrase) ? 1 : 3; final BinaryMapIndexReader[] currentFile = new BinaryMapIndexReader[1]; ResultMatcher rm = new ResultMatcher() { @@ -217,6 +207,16 @@ public class SearchCoreFactory { resultMatcher.isCancelled(); } }; + Iterator offlineIndexes = phrase.getOfflineIndexes(DEFAULT_BBOX_RADIUS * 10, SearchPhraseDataType.ADDRESS); + while(offlineIndexes.hasNext()) { + BinaryMapIndexReader r = offlineIndexes.next(); + if(!townCities.containsKey(r)) { + BinaryMapIndexReader.buildAddressRequest(null); + List l = r.getCities(null, BinaryMapAddressReaderAdapter.CITY_TOWN_TYPE); + townCities.put(r, l); + } + } + for(BinaryMapIndexReader r : phrase.getOfflineIndexes()) { currentFile[0] = r; SearchRequest req = BinaryMapIndexReader.buildAddressByNameRequest(rm, @@ -328,11 +328,13 @@ public class SearchCoreFactory { public boolean search(final SearchPhrase phrase, final SearchResultMatcher resultMatcher) throws IOException { if(phrase.isLastWord(ObjectType.POI_TYPE)) { final AbstractPoiType pt = (AbstractPoiType) phrase.getLastSelectedWord().getResult().object; - QuadRect bbox = getBBoxToSearch(10000, phrase.getRadiusLevel(), phrase.getLastTokenLocation()); + QuadRect bbox = getBBoxToSearch(phrase, 10000); List oo = phrase.getOfflineIndexes(); final BinaryMapIndexReader[] selected = new BinaryMapIndexReader[1]; final NameStringMatcher ns = phrase.getNameStringMatcher(); - SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest((int)bbox.left, (int)bbox.right, (int)bbox.top, (int)bbox.bottom, -1, + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( + (int)bbox.left, (int)bbox.right, + (int)bbox.top, (int)bbox.bottom, -1, new SearchPoiTypeFilter() { @Override @@ -384,10 +386,9 @@ public class SearchCoreFactory { return resultMatcher.isCancelled(); } }); - for(BinaryMapIndexReader o : oo) { + for (BinaryMapIndexReader o : oo) { selected[0] = o; - - o.searchPoi(req); + o.searchPoi(req); } } return true; 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 3d0ed4159a..f61514f03f 100644 --- a/OsmAnd-java/src/net/osmand/search/example/core/SearchPhrase.java +++ b/OsmAnd-java/src/net/osmand/search/example/core/SearchPhrase.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -11,7 +12,9 @@ import net.osmand.CollatorStringMatcher; import net.osmand.StringMatcher; import net.osmand.CollatorStringMatcher.StringMatcherMode; import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.data.LatLon; +import net.osmand.data.QuadRect; import net.osmand.util.MapUtils; //immutable object @@ -23,6 +26,12 @@ public class SearchPhrase { private SearchSettings settings; private List indexes; private String lastWordTrim; + private QuadRect cache1kmRect; + + public enum SearchPhraseDataType { + MAP, ADDRESS, ROUTING, POI + } + public SearchPhrase(SearchSettings settings) { this.settings = settings; @@ -72,6 +81,93 @@ public class SearchPhrase { return words; } + + + + public QuadRect getBBoxToSearch(int radiusInMeters) { + QuadRect cache1kmRect = get1km31Rect(); + if(cache1kmRect == null) { + return null; + } + int max = (1 << 31) - 1; + double dx = (cache1kmRect.width() / 2) * radiusInMeters / 1000; + double dy = (cache1kmRect.height() / 2) * radiusInMeters / 1000; + double topLeftX = Math.max(0, cache1kmRect.left - dx); + double topLeftY = Math.max(0, cache1kmRect.top - dy); + double bottomRightX = Math.min(max, cache1kmRect.right + dx); + double bottomRightY = Math.min(max, cache1kmRect.bottom + dy); + return new QuadRect(topLeftX, topLeftY, bottomRightX, bottomRightY); + } + + public QuadRect get1km31Rect() { + if(cache1kmRect != null) { + return cache1kmRect; + } + LatLon l = getLastTokenLocation(); + if (l == null) { + return null; + } + float coeff = (float) (1000 / MapUtils.getTileDistanceWidth(SearchRequest.ZOOM_TO_SEARCH_POI)); + double tx = MapUtils.getTileNumberX(SearchRequest.ZOOM_TO_SEARCH_POI, l.getLongitude()); + double ty = MapUtils.getTileNumberY(SearchRequest.ZOOM_TO_SEARCH_POI, l.getLatitude()); + double topLeftX = Math.max(0, tx - coeff); + double topLeftY = Math.max(0, ty - coeff); + int max = (1 << SearchRequest.ZOOM_TO_SEARCH_POI) - 1; + double bottomRightX = Math.min(max, tx + coeff); + double bottomRightY = Math.min(max, ty + coeff); + double pw = MapUtils.getPowZoom(31 - SearchRequest.ZOOM_TO_SEARCH_POI); + cache1kmRect = new QuadRect(topLeftX * pw, topLeftY * pw, bottomRightX * pw, bottomRightY * pw); + return cache1kmRect; + } + + + public Iterator getOfflineIndexes(int meters, final SearchPhraseDataType dt) { + List list = indexes != null ? indexes : settings.getOfflineIndexes(); + final Iterator lit = list.iterator(); + final QuadRect rect = meters > 0 ? getBBoxToSearch(meters) : null; + return new Iterator() { + BinaryMapIndexReader next = null; + @Override + public boolean hasNext() { + while (lit.hasNext()) { + next = lit.next(); + if(rect != null) { + if(dt == SearchPhraseDataType.POI) { + if(next.containsPoiData((int)rect.left, (int)rect.right, (int)rect.top, (int)rect.bottom)) { + return true; + } + } else if(dt == SearchPhraseDataType.ADDRESS) { + if(next.containsAddressData((int)rect.left, (int)rect.right, (int)rect.top, (int)rect.bottom)) { + return true; + } + } else if(dt == SearchPhraseDataType.ROUTING) { + if(next.containsRouteData((int)rect.left, (int)rect.right, (int)rect.top, (int)rect.bottom, 15)) { + return true; + } + } else { + if(next.containsMapData((int)rect.left, (int)rect.right, (int)rect.top, (int)rect.bottom, 15)) { + return true; + } + } + } else { + return true; + } + } + return false; + } + + @Override + public BinaryMapIndexReader next() { + return next; + } + + @Override + public void remove() { + } + }; + + } + public List getOfflineIndexes() { if(indexes != null) { return indexes;