Merge pull request #2661 from osmandapp/faster-search

Improve speed and usability of address search
This commit is contained in:
vshcherb 2016-06-06 17:10:58 +03:00
commit 52b7a8e502
9 changed files with 370 additions and 141 deletions

View file

@ -1,9 +1,12 @@
package net.osmand.binary; package net.osmand.binary;
import gnu.trove.list.array.TIntArrayList; import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -22,6 +25,7 @@ import net.osmand.data.City;
import net.osmand.data.City.CityType; import net.osmand.data.City.CityType;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
import net.osmand.data.MapObject; import net.osmand.data.MapObject;
import net.osmand.data.Postcode;
import net.osmand.data.Street; import net.osmand.data.Street;
import net.osmand.util.MapUtils; import net.osmand.util.MapUtils;
import net.sf.junidecode.Junidecode; import net.sf.junidecode.Junidecode;
@ -40,7 +44,7 @@ public class BinaryMapAddressReaderAdapter {
public final static int STREET_TYPE = 4; public final static int STREET_TYPE = 4;
private static final Log LOG = PlatformUtil.getLog(BinaryMapAddressReaderAdapter.class); private static final Log LOG = PlatformUtil.getLog(BinaryMapAddressReaderAdapter.class);
public final static int[] TYPES = { CITY_TOWN_TYPE, POSTCODES_TYPE, VILLAGES_TYPE, STREET_TYPE }; public final static List<Integer> TYPES = Arrays.asList(CITY_TOWN_TYPE, POSTCODES_TYPE, VILLAGES_TYPE, STREET_TYPE);
public final static int[] CITY_TYPES = { CITY_TOWN_TYPE, POSTCODES_TYPE, VILLAGES_TYPE }; public final static int[] CITY_TYPES = { CITY_TOWN_TYPE, POSTCODES_TYPE, VILLAGES_TYPE };
public static class AddressRegion extends BinaryIndexPart { public static class AddressRegion extends BinaryIndexPart {
@ -175,7 +179,7 @@ public class BinaryMapAddressReaderAdapter {
int fp = codedIS.getTotalBytesRead(); int fp = codedIS.getTotalBytesRead();
int length = codedIS.readRawVarint32(); int length = codedIS.readRawVarint32();
int oldLimit = codedIS.pushLimit(length); int oldLimit = codedIS.pushLimit(length);
City c = readCityHeader(matcher, fp, additionalTagsTable); City c = readCityHeader(new DefaultCityMatcher(matcher), fp, additionalTagsTable);
if (c != null) { if (c != null) {
if (resultMatcher == null || resultMatcher.publish(c)) { if (resultMatcher == null || resultMatcher.publish(c)) {
cities.add(c); cities.add(c);
@ -228,7 +232,36 @@ public class BinaryMapAddressReaderAdapter {
} }
} }
protected City readCityHeader(StringMatcher matcher, int filePointer, List<String> additionalTagsTable) throws IOException{ interface CityMatcher {
boolean matches(City city);
}
private class DefaultCityMatcher implements CityMatcher {
private StringMatcher stringMatcher = null;
DefaultCityMatcher(StringMatcher stringMatcher) {
this.stringMatcher = stringMatcher;
}
@Override
public boolean matches(City city) {
if (stringMatcher == null) {
return true;
}
boolean matches = stringMatcher.matches(city.getName());
if (!matches) {
for (String n : city.getAllNames()) {
matches = stringMatcher.matches(n);
if (matches) {
break;
}
}
}
return matches;
}
}
protected City readCityHeader(CityMatcher matcher, int filePointer, List<String> additionalTagsTable) throws IOException{
int x = 0; int x = 0;
int y = 0; int y = 0;
City c = null; City c = null;
@ -238,21 +271,7 @@ public class BinaryMapAddressReaderAdapter {
int tag = WireFormat.getTagFieldNumber(t); int tag = WireFormat.getTagFieldNumber(t);
switch (tag) { switch (tag) {
case 0: case 0:
if(matcher != null) { return (matcher == null || matcher.matches(c)) ? c : null;
boolean matches = matcher.matches(c.getName());
if(!matches) {
for(String n : c.getAllNames()) {
matches = matcher.matches(n);
if(matches) {
break;
}
}
}
if(!matches ) {
return null;
}
}
return c;
case OsmandOdb.CityIndex.CITY_TYPE_FIELD_NUMBER : case OsmandOdb.CityIndex.CITY_TYPE_FIELD_NUMBER :
int type = codedIS.readUInt32(); int type = codedIS.readUInt32();
c = new City(CityType.values()[type]); c = new City(CityType.values()[type]);
@ -536,9 +555,18 @@ public class BinaryMapAddressReaderAdapter {
} }
} }
public void searchAddressDataByName(AddressRegion reg, SearchRequest<MapObject> req, int[] typeFilter) throws IOException { public void searchAddressDataByName(AddressRegion reg, SearchRequest<MapObject> req, List<Integer> typeFilter) throws IOException {
TIntArrayList loffsets = new TIntArrayList(); TIntArrayList loffsets = new TIntArrayList();
CollatorStringMatcher matcher = new CollatorStringMatcher( req.nameQuery, StringMatcherMode.CHECK_STARTS_FROM_SPACE); CollatorStringMatcher stringMatcher = new CollatorStringMatcher(req.nameQuery, StringMatcherMode.CHECK_STARTS_FROM_SPACE);
String postcode = Postcode.normalize(req.nameQuery, map.getCountryName());
final CityMatcher postcodeMatcher = new DefaultCityMatcher(new CollatorStringMatcher(postcode, StringMatcherMode.CHECK_STARTS_FROM_SPACE));
final CityMatcher cityMatcher = new DefaultCityMatcher(stringMatcher);
final CityMatcher cityPostcodeMatcher = new CityMatcher() {
@Override
public boolean matches(City city) {
return city.isPostcode() ? postcodeMatcher.matches(city) : cityMatcher.matches(city);
}
};
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
int indexOffset = 0; int indexOffset = 0;
while (true) { while (true) {
@ -555,7 +583,7 @@ public class BinaryMapAddressReaderAdapter {
indexOffset = codedIS.getTotalBytesRead(); indexOffset = codedIS.getTotalBytesRead();
int oldLimit = codedIS.pushLimit(length); int oldLimit = codedIS.pushLimit(length);
// here offsets are sorted by distance // here offsets are sorted by distance
map.readIndexedStringTable(matcher.getCollator(), req.nameQuery, "", loffsets, 0); map.readIndexedStringTable(stringMatcher.getCollator(), req.nameQuery, "", loffsets, 0);
codedIS.popLimit(oldLimit); codedIS.popLimit(oldLimit);
break; break;
case OsmAndAddressNameIndexData.ATOM_FIELD_NUMBER: case OsmAndAddressNameIndexData.ATOM_FIELD_NUMBER:
@ -593,13 +621,13 @@ public class BinaryMapAddressReaderAdapter {
} }
} }
if (typeFilter == null) { if (typeFilter == null) {
typeFilter = new int[] { CITY_TOWN_TYPE, POSTCODES_TYPE, VILLAGES_TYPE, STREET_TYPE }; typeFilter = TYPES;
} }
for (int i = 0; i < typeFilter.length && !req.isCancelled(); i++) { for (int i = 0; i < typeFilter.size() && !req.isCancelled(); i++) {
TIntArrayList list = refs[typeFilter[i]]; TIntArrayList list = refs[typeFilter.get(i)];
if (typeFilter[i] == STREET_TYPE) { if (typeFilter.get(i) == STREET_TYPE) {
for (int j = 0; j < list.size() && !req.isCancelled(); j += 2) { for (int j = 0; j < list.size() && !req.isCancelled(); j += 2) {
City obj = null; City obj;
{ {
codedIS.seek(list.get(j + 1)); codedIS.seek(list.get(j + 1));
int len = codedIS.readRawVarint32(); int len = codedIS.readRawVarint32();
@ -617,10 +645,10 @@ public class BinaryMapAddressReaderAdapter {
readStreet(s, null, false, MapUtils.get31TileNumberX(l.getLongitude()) >> 7, readStreet(s, null, false, MapUtils.get31TileNumberX(l.getLongitude()) >> 7,
MapUtils.get31TileNumberY(l.getLatitude()) >> 7, obj.isPostcode() ? obj.getName() : null, MapUtils.get31TileNumberY(l.getLatitude()) >> 7, obj.isPostcode() ? obj.getName() : null,
reg.attributeTagsTable); reg.attributeTagsTable);
boolean matches = matcher.matches(s.getName()); boolean matches = stringMatcher.matches(s.getName());
if (!matches) { if (!matches) {
for (String n : s.getAllNames()) { for (String n : s.getAllNames()) {
matches = matcher.matches(n); matches = stringMatcher.matches(n);
if (matches) { if (matches) {
break; break;
} }
@ -634,13 +662,16 @@ public class BinaryMapAddressReaderAdapter {
} }
} else { } else {
list.sort(); list.sort();
TIntSet published = new TIntHashSet();
for (int j = 0; j < list.size() && !req.isCancelled(); j++) { for (int j = 0; j < list.size() && !req.isCancelled(); j++) {
codedIS.seek(list.get(j)); int offset = list.get(j);
codedIS.seek(offset);
int len = codedIS.readRawVarint32(); int len = codedIS.readRawVarint32();
int old = codedIS.pushLimit(len); int old = codedIS.pushLimit(len);
City obj = readCityHeader(matcher, list.get(j), reg.attributeTagsTable); City obj = readCityHeader(cityPostcodeMatcher, list.get(j), reg.attributeTagsTable);
if (obj != null) { if (obj != null && !published.contains(offset)) {
req.publish(obj); req.publish(obj);
published.add(offset);
} }
codedIS.popLimit(old); codedIS.popLimit(old);
} }

View file

@ -17,6 +17,7 @@ import java.io.InputStreamReader;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -384,6 +385,25 @@ public class BinaryMapIndexReader {
return file; return file;
} }
private List<String> getCountryAndRegionNames() {
return new ArrayList<>(Arrays.asList(getRegionNames().get(0).split("_")));
}
public String getCountryName() {
return getCountryAndRegionNames().get(0);
}
private String getRegionName() {
List<String> names = getCountryAndRegionNames();
if (names.size() >= 2) {
String region = names.get(1);
region = region.substring(0, 1).toUpperCase() + region.substring(1);
return region;
} else {
return null;
}
}
public int readByte() throws IOException { public int readByte() throws IOException {
byte b = codedIS.readRawByte(); byte b = codedIS.readRawByte();
if (b < 0) { if (b < 0) {
@ -1278,10 +1298,7 @@ public class BinaryMapIndexReader {
return dataObject; return dataObject;
} }
public List<MapObject> searchAddressDataByName(SearchRequest<MapObject> req, int[] typeFilter) throws IOException { public List<MapObject> searchAddressDataByName(SearchRequest<MapObject> req, List<Integer> typeFilter) throws IOException {
if (req.nameQuery == null || req.nameQuery.length() == 0) {
throw new IllegalArgumentException();
}
for (AddressRegion reg : addressIndexes) { for (AddressRegion reg : addressIndexes) {
if (reg.indexNameOffset != -1) { if (reg.indexNameOffset != -1) {
codedIS.seek(reg.indexNameOffset); codedIS.seek(reg.indexNameOffset);

View file

@ -1,13 +1,8 @@
package net.osmand.data; package net.osmand.data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import net.osmand.OsmAndCollator;
import net.osmand.util.Algorithms;
public class City extends MapObject { public class City extends MapObject {
public enum CityType { public enum CityType {

View file

@ -0,0 +1,208 @@
package net.osmand.data;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.osmand.PlatformUtil;
import org.apache.commons.logging.Log;
public class Postcode {
private final static Log log = PlatformUtil.getLog(Postcode.class);
// © CC BY 3.0 2016 GeoNames.org
// with adaptations
private final static Map<String, List<String>> rules = new TreeMap<String, List<String>>() {{
put("Algeria", Arrays.asList("(?i)(?:DZ-?)?(\\d{5})", "$1"));
put("Andorra", Arrays.asList("(?i)(?:AD-?)?(\\d{3})", "$1"));
put("Argentina", Arrays.asList("(?i)(?:AR-?)?([A-Z]\\d{4}[A-Z]{3}|\\d{4})", "$1"));
put("Armenia", Arrays.asList("(?i)(?:AM-?)?(\\d{6})", "$1"));
put("Australia-oceania", Arrays.asList("(?i)(?:AU-?)?(\\d{4})", "$1"));
put("Austria", Arrays.asList("(?i)(?:AT-?)?(\\d{4})", "$1"));
put("Azerbaijan", Arrays.asList("(?i)(?:AZ-?)?(\\d{4})", "$1"));
put("Bahrain", Arrays.asList("(?i)(?:BH-?)?(\\d{3}\\d?)", "$1"));
put("Bangladesh", Arrays.asList("(?i)(?:BD-?)?(\\d{4})", "$1"));
put("Barbados", Arrays.asList("(?i)(?:BB-?)?(\\d{5})", "$1"));
put("Belarus", Arrays.asList("(?i)(?:BY-?)?(\\d{6})", "$1"));
put("Belgium", Arrays.asList("(?i)(?:BE-?)?(\\d{4})", "$1"));
put("Bermuda", Arrays.asList("(?i)(?:BM-?)?([A-Z]{2})\\W*(\\d{2})", "$1$2"));
put("Bosnia-herzegovina", Arrays.asList("(?i)(?:BA-?)?(\\d{5})", "$1"));
put("Brazil", Arrays.asList("(?i)(?:BR-?)?(\\d{5})\\W*(\\d{3})", "$1-$2"));
put("Brunei", Arrays.asList("(?i)(?:BN-?)?([A-Z]{2})\\W*(\\d{4})", "$1$2"));
put("Bulgaria", Arrays.asList("(?i)(?:BG-?)?(\\d{4})", "$1"));
put("Cambodia", Arrays.asList("(?i)(?:KH-?)?(\\d{5})", "$1"));
put("Canada", Arrays.asList("(?i)(?:CA-?)?([ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJKLMNPRSTVWXYZ])\\W*(\\d[ABCEGHJKLMNPRSTVWXYZ]\\d)$", "$1 $2"));
put("Cape-verde", Arrays.asList("(?i)(?:CV-?)?(\\d{4})", "$1"));
put("Chile", Arrays.asList("(?i)(?:CL-?)?(\\d{7})", "$1"));
put("China", Arrays.asList("(?i)(?:CN-?)?(\\d{6})", "$1"));
put("Christmas-island", Arrays.asList("(?i)(?:CX-?)?(\\d{4})", "$1"));
put("Costa-rica", Arrays.asList("(?i)(?:CR-?)?(\\d{4})", "$1"));
put("Croatia", Arrays.asList("(?i)(?:HR-?)?(\\d{5})", "$1"));
put("Cuba", Arrays.asList("(?i)(?:C[PU]-?)?(\\d{5})", "$1"));
put("Cyprus", Arrays.asList("(?i)(?:CY-?)?(\\d{4})", "$1"));
put("Czech-republic", Arrays.asList("(?i)(?:CZ-?)?(\\d{5})", "$1"));
put("Denmark", Arrays.asList("(?i)(?:DK-?)?(\\d{4})", "$1"));
put("Dominican-republic", Arrays.asList("(?i)(?:DO-?)?(\\d{5})", "$1"));
put("Ecuador", Arrays.asList("(?i)(?:EC-?)?(\\d{6})", "$1"));
put("Egypt", Arrays.asList("(?i)(?:EG-?)?(\\d{5})", "$1"));
put("El-salvador", Arrays.asList("(?i)(?:SV-?)?(\\d{4})", "$1"));
put("Estonia", Arrays.asList("(?i)(?:EE-?)?(\\d{5})", "$1"));
put("Ethiopia", Arrays.asList("(?i)(?:ET-?)?(\\d{4})", "$1"));
put("Faroe-islands", Arrays.asList("(?i)(?:FO-?)?(\\d{3})", "$1"));
put("Finland", Arrays.asList("(?i)(?:FI-?)?(\\d{5})", "$1"));
put("France", Arrays.asList("(?i)(?:FR-?)?(\\d{5})", "$1"));
put("French-guiana", Arrays.asList("(?i)(?:GF-?)?((97|98)3\\d{2})", "$1"));
put("French-southern-and-antarctic-lands", Arrays.asList("(?i)(?:PF-?)?((97|98)7\\d{2})", "$1"));
put("GB", Arrays.asList("(?i)(?:UK-?)?([A-Z]{1,2}[0-9]{1,2}[A-Z]?)\\W*([0-9][A-Z]{2})", "$1 $2"));
put("Georgia", Arrays.asList("(?i)(?:GE-?)?(\\d{4})", "$1"));
put("Germany", Arrays.asList("(?i)(?:DE-?)?(\\d{5})", "$1"));
put("Greece", Arrays.asList("(?i)(?:GR-?)?(\\d{5})", "$1"));
put("Greenland", Arrays.asList("(?i)(?:GL-?)?(\\d{4})", "$1"));
put("Guadeloupe", Arrays.asList("(?i)(?:GP-?)?((97|98)\\d{3})", "$1"));
put("Guatemala", Arrays.asList("(?i)(?:GT-?)?(\\d{5})", "$1"));
put("Guinea-bissau", Arrays.asList("(?i)(?:GW-?)?(\\d{4})", "$1"));
put("Haiti", Arrays.asList("(?i)(?:HT-?)?(\\d{4})", "$1"));
put("Honduras", Arrays.asList("(?i)(?:HN-?)?([A-Z]{2})\\W*(\\d{4}))", "$1$2"));
put("Hungary", Arrays.asList("(?i)(?:HU-?)?(\\d{4})", "$1"));
put("Iceland", Arrays.asList("(?i)(?:IS-?)?(\\d{3})", "$1"));
put("India", Arrays.asList("(?i)(?:IN-?)?(\\d{6})", "$1"));
put("Indonesia", Arrays.asList("(?i)(?:ID-?)?(\\d{5})", "$1"));
put("Iran", Arrays.asList("(?i)(?:IR-?)?(\\d{10})", "$1"));
put("Iraq", Arrays.asList("(?i)(?:IQ-?)?(\\d{5})", "$1"));
// put("Ireland", Arrays.asList("(?i)(?:IE-?)?([A-Z]{3}[A-Z]{4})", "$1")); // It's complicated
put("Israel", Arrays.asList("(?i)(?:IL-?)?(\\d{5})", "$1"));
put("Italy", Arrays.asList("(?i)(?:IT-?)?(\\d{5})", "$1"));
put("Japan", Arrays.asList("(?i)(?:JP-?)?(\\d{7})", "$1"));
put("Jordan", Arrays.asList("(?i)(?:JO-?)?(\\d{5})", "$1"));
put("Kazakhstan", Arrays.asList("(?i)(?:KZ-?)?(\\d{6})", "$1"));
put("Kenya", Arrays.asList("(?i)(?:KE-?)?(\\d{5})", "$1"));
put("Kuwait", Arrays.asList("(?i)(?:KW-?)?(\\d{5})", "$1"));
put("Kyrgyzstan", Arrays.asList("(?i)(?:KG-?)?(\\d{6})", "$1"));
put("Laos", Arrays.asList("(?i)(?:LA-?)?(\\d{5})", "$1"));
put("Latvia", Arrays.asList("(?i)(?:LV-?)?(\\d{4})", "$1"));
put("Lebanon", Arrays.asList("(?i)(?:LB-?)?(\\d{4}(\\d{4})?)", "$1"));
put("Lesotho", Arrays.asList("(?i)(?:LS-?)?(\\d{3})", "$1"));
put("Liberia", Arrays.asList("(?i)(?:LR-?)?(\\d{4})", "$1"));
put("Liechtenstein", Arrays.asList("(?i)(?:LI-?)?(\\d{4})", "$1"));
put("Lithuania", Arrays.asList("(?i)(?:LT-?)?(\\d{5})", "$1"));
put("Luxembourg", Arrays.asList("(?i)(?:LU-?)?(\\d{4})", "$1"));
put("Macedonia", Arrays.asList("(?i)(?:MK-?)?(\\d{4})", "$1"));
put("Madagascar", Arrays.asList("(?i)(?:MG-?)?(\\d{3})", "$1"));
put("Malaysia", Arrays.asList("(?i)(?:MY-?)?(\\d{5})", "$1"));
put("Maldives", Arrays.asList("(?i)(?:MV-?)?(\\d{5})", "$1"));
put("Malta", Arrays.asList("(?i)(?:MT-?)?([A-Z]{3})\\W*(\\d{4})", "$1 $2"));
put("Martinique", Arrays.asList("(?i)(?:MQ-?)?(\\d{5})", "$1"));
put("Mayotte", Arrays.asList("(?i)(?:YT-?)?(\\d{5})", "$1"));
put("Mexico", Arrays.asList("(?i)(?:MX-?)?(\\d{5})", "$1"));
put("Moldova", Arrays.asList("(?i)(?:MD-?)?(\\d{4})", "$1"));
put("Monaco", Arrays.asList("(?i)(?:MC-?)?(\\d{5})", "$1"));
put("Mongolia", Arrays.asList("(?i)(?:MN-?)?(\\d{6})", "$1"));
put("Montenegro", Arrays.asList("(?i)(?:ME-?)?(\\d{5})", "$1"));
put("Morocco", Arrays.asList("(?i)(?:MA-?)?(\\d{5})", "$1"));
put("Mozambique", Arrays.asList("(?i)(?:MZ-?)?(\\d{4})", "$1"));
put("Myanmar", Arrays.asList("(?i)(?:MM-?)?(\\d{5})", "$1"));
put("Nepal", Arrays.asList("(?i)(?:NP-?)?(\\d{5})", "$1"));
put("Netherlands", Arrays.asList("(?i)(?:NL-?)?(\\d{4})\\W*([A-Z]{2})", "$1$2"));
put("New-zealand", Arrays.asList("(?i)(?:NZ-?)?(\\d{4})", "$1"));
put("Nicaragua", Arrays.asList("(?i)(?:NI-?)?(\\d{7})", "$1"));
put("Niger", Arrays.asList("(?i)(?:NE-?)?(\\d{4})", "$1"));
put("Nigeria", Arrays.asList("(?i)(?:NG-?)?(\\d{6})", "$1"));
put("North-korea", Arrays.asList("(?i)(?:KP-?)?(\\d{6})", "$1"));
put("Norway", Arrays.asList("(?i)(?:NO-?)?(\\d{4})", "$1"));
put("Oman", Arrays.asList("(?i)(?:OM-?)?(\\d{3})", "$1"));
put("Pakistan", Arrays.asList("(?i)(?:PK-?)?(\\d{5})", "$1"));
put("Papua-new-guinea", Arrays.asList("(?i)(?:PG-?)?(\\d{3})", "$1"));
put("Paraguay", Arrays.asList("(?i)(?:PY-?)?(\\d{4})", "$1"));
put("Philippines", Arrays.asList("(?i)(?:PH-?)?(\\d{4})", "$1"));
put("Poland", Arrays.asList("(?i)(?:PL-?)?(\\d{5})", "$1"));
put("Portugal", Arrays.asList("(?i)(?:PT-?)?(\\d{7})", "$1"));
put("Puerto-rico", Arrays.asList("(?i)(?:PR-?)?(\\d{9})", "$1"));
put("Reunion", Arrays.asList("(?i)(?:RE-?)?((97|98)(4|7|8)\\d{2})", "$1"));
put("Romania", Arrays.asList("(?i)(?:RO-?)?(\\d{6})", "$1"));
put("Russia", Arrays.asList("(?i)(?:RU-?)?(\\d{6})", "$1"));
put("Saint-helena-ascension-and-tristan-da-cunha", Arrays.asList("(?i)(?:SH-?)?(STHL)\\W*(1ZZ)", "$1 $2"));
put("Saint-pierre-and-miquelon", Arrays.asList("(?i)(?:PM-?)?(97500)", "$1"));
put("San-marino", Arrays.asList("(?i)(?:SM-?)?(4789\\d)", "$1"));
put("Saudi-arabia", Arrays.asList("(?i)(?:SA-?)?(\\d{5})", "$1"));
put("Senegal", Arrays.asList("(?i)(?:SN-?)?(\\d{5})", "$1"));
put("Serbia", Arrays.asList("(?i)(?:RS-?)?(\\d{6})", "$1"));
put("Singapore", Arrays.asList("(?i)(?:SG-?)?(\\d{6})", "$1"));
put("Slovakia", Arrays.asList("(?i)(?:SK-?)?(\\d{5})", "$1"));
put("Slovenia", Arrays.asList("(?i)(?:SI-?)?(\\d{4})", "$1"));
put("Somalia", Arrays.asList("(?i)(?:SO-?)?([A-Z]{2})\\W*(\\d{5})", "$1$2"));
put("South-africa", Arrays.asList("(?i)(?:ZA-?)?(\\d{4})", "$1"));
put("South-korea", Arrays.asList("(?i)(?:KR-?)?(?:SEOUL)?(\\d{3})\\W*(\\d{2,3})", "$1$2"));
put("Spain", Arrays.asList("(?i)(?:ES-?)?(\\d{5})", "$1"));
put("Sri-lanka", Arrays.asList("(?i)(?:LK-?)?(\\d{5})", "$1"));
put("Sudan", Arrays.asList("(?i)(?:SD-?)?(\\d{5})", "$1"));
put("Swaziland", Arrays.asList("(?i)(?:SZ-?)?([A-Z]\\d{3})", "$1"));
put("Sweden", Arrays.asList("(?i)(?:SE-?)?(\\d{5})", "$1"));
put("Switzerland", Arrays.asList("(?i)(?:CH-?)?(\\d{4})", "$1"));
put("Taiwan", Arrays.asList("(?i)(?:TW-?)?(\\d{5})", "$1"));
put("Tajikistan", Arrays.asList("(?i)(?:TJ-?)?(\\d{6})", "$1"));
put("Thailand", Arrays.asList("(?i)(?:TH-?)?(\\d{5})", "$1"));
put("Tunisia", Arrays.asList("(?i)(?:TN-?)?(\\d{4})", "$1"));
put("Turkey", Arrays.asList("(?i)(?:TR-?)?(\\d{5})", "$1"));
put("Turkmenistan", Arrays.asList("(?i)(?:TM-?)?(\\d{6})", "$1"));
put("Turks-and-caicos-islands", Arrays.asList("(?i)(?:TC-?)?(TKCA)\\W*(1ZZ)", "$1 $2"));
put("Virgin-islands-us", Arrays.asList("(?i)(?:VI-?)?(\\d{5})\\W*(-\\d{4})?", "$1$2"));
put("Ukraine", Arrays.asList("(?i)(?:UA-?)?(\\d{2})\\W*(\\d{3})", "$1$2"));
put("Us", Arrays.asList("(?i)(?:US-?)?(\\d{5})\\W*(-\\d{4})?", "$1$2"));
put("Uruguay", Arrays.asList("(?i)(?:UY-?)?(\\d{5})", "$1"));
put("Uzbekistan", Arrays.asList("(?i)(?:UZ-?)?(\\d{6})", "$1"));
put("Venezuela", Arrays.asList("(?i)(?:VE-?)?(\\d{4})", "$1"));
put("Vietnam", Arrays.asList("(?i)(?:VN-?)?(\\d{6})", "$1"));
put("Zambia", Arrays.asList("(?i)(?:ZM-?)?(\\d{5})", "$1"));
}};
public static void main(String[] args) {
System.out.println(normalize("1101 DL", "Netherlands"));
System.out.println(normalize("1101-DL", "Netherlands"));
System.out.println(normalize("b288qp", "United Kingdom"));
System.out.println(normalize("GIR 0AA", "United Kingdom"));
System.out.println(normalize("IV21 2LR", "United Kingdom"));
}
private static boolean isCountryKnown(String country) {
return rules.containsKey(country);
}
private static Pattern getPattern(String country) {
return Pattern.compile(rules.get(country).get(0));
}
private static Matcher getMatcher(String postcode, String country) {
return isCountryKnown(country) ? getPattern(country).matcher(postcode) : null;
}
public static String normalize(String postcode, String country) {
postcode = postcode.toUpperCase();
String result = postcode;
if (isCountryKnown(country)) {
String replacement = rules.get(country).get(1);
Matcher matcher = getMatcher(postcode, country);
result = matcher.replaceAll(replacement);
if (!result.equals(postcode)) {
log.info("Normalize " + country + "'s postcode: " + postcode + " -> " + result);
}
if (!matcher.matches()) {
log.info("Not matches " + country + "'s postcode regex: " + postcode);
}
}
return result;
}
public static boolean looksLikePostcodeStart(String s, String country) {
boolean result = false;
if (isCountryKnown(country)) {
Matcher matcher = getMatcher(s, country);
result = (matcher != null && matcher.find()) || s.matches("(.+\\d+.*|.*\\d+.+)");
}
return result;
}
}

View file

@ -370,17 +370,19 @@ public abstract class SearchByNameAbstractActivity<T> extends OsmandListActivity
namesFilter.cancelPreviousFilter(currentFilter); namesFilter.cancelPreviousFilter(currentFilter);
} }
protected boolean filterLoop(String query, Collection<T> list) {
protected void filterLoop(String query, Collection<T> list) { boolean result = false;
for (T obj : list) { for (T obj : list) {
if (namesFilter.isCancelled){ if (namesFilter.isCancelled){
break; break;
} }
if (filterObject(obj, query)){ if (filterObject(obj, query)){
result = true;
Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, obj); Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, obj);
msg.sendToTarget(); msg.sendToTarget();
} }
} }
return result;
} }

View file

@ -16,6 +16,7 @@ import net.osmand.ResultMatcher;
import net.osmand.data.City; import net.osmand.data.City;
import net.osmand.data.City.CityType; import net.osmand.data.City.CityType;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
import net.osmand.data.Postcode;
import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings;
@ -122,12 +123,17 @@ public class SearchCityByNameActivity extends SearchByNameAbstractActivity<City>
} }
@Override @Override
protected void filterLoop(String query, Collection<City> list) { protected boolean filterLoop(String query, Collection<City> list) {
redefineSearchVillagesMode(query.length()); final boolean[] result = {false};
if (!initializeTaskIsFinished() || (query.length() <= 3 && !searchVillages())) { redefineSearchVillagesMode(query);
super.filterLoop(query, list); if (!initializeTaskIsFinished() || !isVillagesSearchEnabled()) {
} else { result[0] = super.filterLoop(query, list);
region.fillWithSuggestedCities(query, new ResultMatcher<City>() { if (!result[0] && !isVillagesSearchEnabled()) {
setVillagesSearchEnabled(true);
}
}
if (!result[0]) {
ResultMatcher<City> resultMatcher = new ResultMatcher<City>() {
@Override @Override
public boolean isCancelled() { public boolean isCancelled() {
return namesFilter.isCancelled; return namesFilter.isCancelled;
@ -135,39 +141,47 @@ public class SearchCityByNameActivity extends SearchByNameAbstractActivity<City>
@Override @Override
public boolean publish(City object) { public boolean publish(City object) {
result[0] = true;
Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, object); Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, object);
msg.sendToTarget(); msg.sendToTarget();
return true; return true;
} }
}, searchVillages(), locationToSearch); };
region.fillWithSuggestedCities(query, resultMatcher, isVillagesSearchEnabled(), locationToSearch);
} }
return result[0];
} }
private void setVillagesSearchEnabled(final boolean enable) {
private boolean searchVillages() { searchVillagesMode = enable ? 0 : -1;
return searchVillagesMode >= 0;
}
private void redefineSearchVillagesMode(int queryLen) {
if (searchVillagesMode == 1) {
searchVillagesMode = 0;
} else if (searchVillagesMode == 0 && queryLen <= 3 && !initialListToFilter.isEmpty()) {
searchVillagesMode = -1;
uiHandler.post(new Runnable() { uiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
searchVillages.setVisibility(View.VISIBLE); searchVillages.setVisibility(enable ? View.GONE : View.VISIBLE);
} }
}); });
} }
private boolean isVillagesSearchEnabled() {
return searchVillagesMode >= 0;
}
private void redefineSearchVillagesMode(String query) {
if (searchVillagesMode == 1) {
searchVillagesMode = 0;
} else if (searchVillagesMode == 0 && !initialListToFilter.isEmpty() && query.isEmpty()) {
setVillagesSearchEnabled(false);
} else if (searchVillagesMode == -1 && Postcode.looksLikePostcodeStart(query, region.getCountryName())) {
setVillagesSearchEnabled(true);
}
} }
@Override @Override
public String getText(City obj) { public String getText(City obj) {
LatLon l = obj.getLocation(); LatLon l = obj.getLocation();
if (getCurrentFilter().length() > 2) {
String name = getShortText(obj); String name = getShortText(obj);
if (isVillagesSearchEnabled()) {
if (obj.getClosestCity() != null) { if (obj.getClosestCity() != null) {
name += " - " + obj.getClosestCity().getName(region.getLang()); name += " - " + obj.getClosestCity().getName(region.getLang());
LatLon loc = obj.getClosestCity().getLocation(); LatLon loc = obj.getClosestCity().getLocation();
@ -180,10 +194,8 @@ public class SearchCityByNameActivity extends SearchByNameAbstractActivity<City>
name += " - " + OsmAndFormatter.toPublicString(obj.getType(), getMyApplication()); name += " - " + OsmAndFormatter.toPublicString(obj.getType(), getMyApplication());
} }
} }
return name;
} else {
return getShortText(obj);
} }
return name;
} }
@Override @Override

View file

@ -124,7 +124,8 @@ public class SearchStreetByNameActivity extends SearchByNameAbstractActivity<Str
} }
@Override @Override
protected void filterLoop(String query, Collection<Street> list) { protected boolean filterLoop(String query, Collection<Street> list) {
final boolean[] result = {false};
if (searchWithCity == -1) { if (searchWithCity == -1) {
filter(query, list); filter(query, list);
} else if (searchWithCity == 0) { } else if (searchWithCity == 0) {
@ -133,6 +134,7 @@ public class SearchStreetByNameActivity extends SearchByNameAbstractActivity<Str
break; break;
} }
if (filterObject(obj, query)) { if (filterObject(obj, query)) {
result[0] = true;
Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, obj); Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, obj);
msg.sendToTarget(); msg.sendToTarget();
} }
@ -145,6 +147,7 @@ public class SearchStreetByNameActivity extends SearchByNameAbstractActivity<Str
if (object instanceof Street) { if (object instanceof Street) {
if (city == null || if (city == null ||
MapUtils.getDistance(city.getLocation(), object.getLocation()) < 100*1000) { MapUtils.getDistance(city.getLocation(), object.getLocation()) < 100*1000) {
result[0] = true;
Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, object); Message msg = uiHandler.obtainMessage(MESSAGE_ADD_ENTITY, object);
msg.sendToTarget(); msg.sendToTarget();
return true; return true;
@ -166,8 +169,7 @@ public class SearchStreetByNameActivity extends SearchByNameAbstractActivity<Str
} }
}); });
} }
return result[0];
} }

View file

@ -19,6 +19,8 @@ public interface RegionAddressRepository {
public String getName(); public String getName();
public String getCountryName();
public String getFileName() ; public String getFileName() ;
public String getLang(); public String getLang();

View file

@ -11,8 +11,6 @@ import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import net.osmand.Collator; import net.osmand.Collator;
import net.osmand.CollatorStringMatcher;
import net.osmand.CollatorStringMatcher.StringMatcherMode;
import net.osmand.OsmAndCollator; import net.osmand.OsmAndCollator;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher; import net.osmand.ResultMatcher;
@ -38,8 +36,8 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository {
private static final Log log = PlatformUtil.getLog(RegionAddressRepositoryBinary.class); private static final Log log = PlatformUtil.getLog(RegionAddressRepositoryBinary.class);
private BinaryMapIndexReader file; private BinaryMapIndexReader file;
private LinkedHashMap<Long, City> cities = new LinkedHashMap<Long, City>(); private LinkedHashMap<Long, City> cities = new LinkedHashMap<Long, City>();
private int POSTCODE_MIN_QUERY_LENGTH = 2;
private int ZOOM_QTREE = 10; private int ZOOM_QTREE = 10;
private QuadTree<City> citiesQtree = new QuadTree<City>(new QuadRect(0, 0, 1 << (ZOOM_QTREE + 1), private QuadTree<City> citiesQtree = new QuadTree<City>(new QuadRect(0, 0, 1 << (ZOOM_QTREE + 1),
1 << (ZOOM_QTREE + 1)), 8, 0.55f); 1 << (ZOOM_QTREE + 1)), 8, 0.55f);
@ -172,7 +170,7 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository {
// private StringMatcherMode[] streetsCheckMode = new StringMatcherMode[] {StringMatcherMode.CHECK_ONLY_STARTS_WITH, // private StringMatcherMode[] streetsCheckMode = new StringMatcherMode[] {StringMatcherMode.CHECK_ONLY_STARTS_WITH,
// StringMatcherMode.CHECK_STARTS_FROM_SPACE_NOT_BEGINNING}; // StringMatcherMode.CHECK_STARTS_FROM_SPACE_NOT_BEGINNING};
public synchronized List<MapObject> searchMapObjectsByName(String name, ResultMatcher<MapObject> resultMatcher, int[] typeFilter) { public synchronized List<MapObject> searchMapObjectsByName(String name, ResultMatcher<MapObject> resultMatcher, List<Integer> typeFilter) {
SearchRequest<MapObject> req = BinaryMapIndexReader.buildAddressByNameRequest(resultMatcher, name); SearchRequest<MapObject> req = BinaryMapIndexReader.buildAddressByNameRequest(resultMatcher, name);
try { try {
log.debug("file=" + file + "; req=" + req); log.debug("file=" + file + "; req=" + req);
@ -188,7 +186,7 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository {
return searchMapObjectsByName(name, resultMatcher, null); return searchMapObjectsByName(name, resultMatcher, null);
} }
private List<City> fillWithCities(String name, final ResultMatcher<City> resultMatcher, final int type) throws IOException { private List<City> fillWithCities(String name, final ResultMatcher<City> resultMatcher, final List<Integer> typeFilter) throws IOException {
List<City> result = new ArrayList<City>(); List<City> result = new ArrayList<City>();
ResultMatcher<MapObject> matcher = new ResultMatcher<MapObject>() { ResultMatcher<MapObject> matcher = new ResultMatcher<MapObject>() {
List<City> cache = new ArrayList<City>(); List<City> cache = new ArrayList<City>();
@ -196,7 +194,8 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository {
@Override @Override
public boolean publish(MapObject o) { public boolean publish(MapObject o) {
City c = (City) o; City c = (City) o;
if (type == BinaryMapAddressReaderAdapter.VILLAGES_TYPE) { City.CityType type = c.getType();
if (type != null && type.ordinal() >= City.CityType.VILLAGE.ordinal()) {
if (c.getLocation() != null) { if (c.getLocation() != null) {
City ct = getClosestCity(c.getLocation(), cache); City ct = getClosestCity(c.getLocation(), cache);
c.setClosestCity(ct); c.setClosestCity(ct);
@ -210,7 +209,7 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository {
return resultMatcher.isCancelled(); return resultMatcher.isCancelled();
} }
}; };
List<MapObject> foundCities = searchMapObjectsByName(name, matcher, new int[]{type}); List<MapObject> foundCities = searchMapObjectsByName(name, matcher, typeFilter);
for (MapObject o : foundCities) { for (MapObject o : foundCities) {
result.add((City) o); result.add((City) o);
@ -221,67 +220,23 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository {
return result; return result;
} }
private List<Integer> getCityTypeFilter(String name, boolean searchVillages) {
List<Integer> cityTypes = new ArrayList<>();
cityTypes.add(BinaryMapAddressReaderAdapter.CITY_TOWN_TYPE);
if (searchVillages) {
cityTypes.add(BinaryMapAddressReaderAdapter.VILLAGES_TYPE);
if (name.length() >= POSTCODE_MIN_QUERY_LENGTH) {
cityTypes.add(BinaryMapAddressReaderAdapter.POSTCODES_TYPE);
}
}
return cityTypes;
}
@Override @Override
public synchronized List<City> fillWithSuggestedCities(String name, final ResultMatcher<City> resultMatcher, boolean searchVillages, LatLon currentLocation) { public synchronized List<City> fillWithSuggestedCities(String name, final ResultMatcher<City> resultMatcher, boolean searchVillages, LatLon currentLocation) {
List<City> citiesToFill = new ArrayList<City>(); List<City> citiesToFill = new ArrayList<>(cities.values());
if (cities.isEmpty()) {
preloadCities(resultMatcher);
citiesToFill.addAll(cities.values());
if (!citiesToFill.isEmpty()) {
return citiesToFill;
}
}
String lang = getLang();
preloadCities(null);
if (name.length() == 0) {
citiesToFill.addAll(cities.values());
if (searchVillages) {
for (City c : citiesToFill) {
resultMatcher.publish(c);
}
try { try {
citiesToFill.addAll(fillWithCities(name, resultMatcher, BinaryMapAddressReaderAdapter.VILLAGES_TYPE)); citiesToFill.addAll(fillWithCities(name, resultMatcher, getCityTypeFilter(name, searchVillages)));
} catch (IOException e) {
log.error("Disk operation failed", e); //$NON-NLS-1$
}
}
if (!citiesToFill.isEmpty()) {
return citiesToFill;
}
}
try {
// essentially index is created that cities towns are first in cities map
if (/*name.length() >= 2 && Algorithms.containsDigit(name) && */searchVillages) {
// also try to identify postcodes
String uName = name.toUpperCase();
List<City> foundCities = fillWithCities(uName, resultMatcher, BinaryMapAddressReaderAdapter.POSTCODES_TYPE);
for (City code : foundCities) {
citiesToFill.add(code);
if (resultMatcher.isCancelled()) {
return citiesToFill;
}
}
}
name = name.toLowerCase();
for (City c : cities.values()) {
String cName = c.getName(lang); // lower case not needed, collator ensures that
if (CollatorStringMatcher.cmatches(collator, cName, name, StringMatcherMode.CHECK_STARTS_FROM_SPACE)) {
if (resultMatcher.publish(c)) {
citiesToFill.add(c);
}
}
if (resultMatcher.isCancelled()) {
return citiesToFill;
}
}
int initialSize = citiesToFill.size();
if (/*name.length() >= 3 && */searchVillages) {
citiesToFill.addAll(fillWithCities(name, resultMatcher, BinaryMapAddressReaderAdapter.VILLAGES_TYPE));
}
log.debug("Loaded citites " + (citiesToFill.size() - initialSize)); //$NON-NLS-1$
} catch (IOException e) { } catch (IOException e) {
log.error("Disk operation failed", e); //$NON-NLS-1$ log.error("Disk operation failed", e); //$NON-NLS-1$
} }
@ -320,6 +275,11 @@ public class RegionAddressRepositoryBinary implements RegionAddressRepository {
return fileName; return fileName;
} }
@Override
public String getCountryName() {
return file.getCountryName();
}
@Override @Override
public String getFileName() { public String getFileName() {
return fileName; return fileName;