package net.osmand.plus; import java.io.File; import java.text.Collator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import net.osmand.Algoritms; import net.osmand.IProgress; import net.osmand.LogUtil; import net.osmand.data.Building; import net.osmand.data.City; import net.osmand.data.MapObject; import net.osmand.data.MapObjectComparator; import net.osmand.data.PostCode; import net.osmand.data.Street; import net.osmand.data.City.CityType; import net.osmand.data.index.IndexConstants; import net.osmand.osm.LatLon; import net.osmand.osm.Node; import net.osmand.osm.Way; import org.apache.commons.logging.Log; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; public class RegionAddressRepositoryOdb implements RegionAddressRepository { private static final Log log = LogUtil.getLog(RegionAddressRepositoryOdb.class); private SQLiteDatabase db; private String name; private LinkedHashMap<Long, City> cities = new LinkedHashMap<Long, City>(); private Map<CityType, List<City>> cityTypes = new HashMap<CityType, List<City>>(); private Map<String, PostCode> postCodes = new TreeMap<String, PostCode>(Collator.getInstance()); private boolean useEnglishNames = false; public boolean initialize(final IProgress progress, File file) { long start = System.currentTimeMillis(); if(db != null){ // close previous db db.close(); } db = SQLiteDatabase.openOrCreateDatabase(file, null); // add * as old format name = file.getName().substring(0, file.getName().indexOf('.'))+" *"; //$NON-NLS-1$ if(db.getVersion() != IndexConstants.ADDRESS_TABLE_VERSION){ db.close(); db = null; return false; } if (log.isDebugEnabled()) { log.debug("Initializing address db " + file.getAbsolutePath() + " " + (System.currentTimeMillis() - start) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } return true; } public void close(){ clearCities(); if(db != null){ db.close(); } } public String getName() { return name; } public void clearCities(){ cities.clear(); cityTypes.clear(); postCodes.clear(); } public boolean areCitiesPreloaded(){ return !cities.isEmpty(); } public boolean arePostcodesPreloaded(){ return !postCodes.isEmpty(); } public PostCode getPostcode(String name){ if(name == null){ return null; } preloadPostcodes(); return postCodes.get(name.toUpperCase()); } public City getCityById(Long id){ if(id == -1){ // do not preload cities for that case return null; } preloadCities(); return cities.get(id); } public Street getStreetByName(MapObject city, String name) { preloadStreets(city); if (city instanceof City) { return ((City) city).getStreet(name); } else { return ((PostCode) city).getStreet(name); } } public Building getBuildingByName(Street street, String name){ if(street.getBuildings().isEmpty()){ preloadBuildings(street); } for(Building b : street.getBuildings()){ String bName = useEnglishNames ? b.getEnName() : b.getName(); if(bName.equals(name)){ return b; } } return null; } public boolean useEnglishNames(){ return useEnglishNames; } public void setUseEnglishNames(boolean useEnglishNames) { this.useEnglishNames = useEnglishNames; // sort streets for (City c : cities.values()) { if (!c.isEmptyWithStreets()) { ArrayList<Street> list = new ArrayList<Street>(c.getStreets()); c.removeAllStreets(); for (Street s : list) { c.registerStreet(s, useEnglishNames); } } } // sort cities ArrayList<City> list = new ArrayList<City>(cities.values()); Collections.sort(list, new MapObjectComparator(useEnglishNames)); cities.clear(); cityTypes.clear(); for(City c : list){ addCityToPreloadedList(c); } } public void fillWithSuggestedBuildings(PostCode postcode, Street street, String name, List<Building> buildingsToFill){ preloadBuildings(street); name = name.toLowerCase(); int ind = 0; boolean empty = name.length() == 0; if(empty && postcode == null){ buildingsToFill.addAll(street.getBuildings()); return; } for (Building building : street.getBuildings()) { if(postcode != null && !postcode.getName().equals(building.getPostcode())){ continue; } else if(empty){ buildingsToFill.add(building); continue; } String bName = useEnglishNames ? building.getEnName() : building.getName(); String lowerCase = bName.toLowerCase(); if (lowerCase.startsWith(name)) { buildingsToFill.add(ind, building); ind++; } else if (lowerCase.contains(name)) { buildingsToFill.add(building); } } } public void fillWithSuggestedStreetsIntersectStreets(City city, Street st, List<Street> streetsToFill) { if (st != null) { Set<Long> strIds = new TreeSet<Long>(); log.debug("Start loading instersection streets for " + city.getName()); //$NON-NLS-1$ Cursor query = db.rawQuery("SELECT B.STREET FROM street_node A JOIN street_node B ON A.ID = B.ID WHERE A.STREET = ?", //$NON-NLS-1$ new String[] { st.getId() + "" }); //$NON-NLS-1$ if (query.moveToFirst()) { do { if (st.getId() != query.getLong(0)) { strIds.add(query.getLong(0)); } } while (query.moveToNext()); } query.close(); for (Street s : city.getStreets()) { if (strIds.contains(s.getId())) { streetsToFill.add(s); } } log.debug("Loaded " + strIds.size() + " streets"); //$NON-NLS-1$ //$NON-NLS-2$ preloadWayNodes(st); } } public void fillWithSuggestedStreets(MapObject o, String name, List<Street> streetsToFill){ assert o instanceof PostCode || o instanceof City; City city = (City) (o instanceof City ? o : null); PostCode post = (PostCode) (o instanceof PostCode ? o : null); preloadStreets(o); name = name.toLowerCase(); Collection<Street> streets = post == null ? city.getStreets() : post.getStreets() ; int ind = 0; if(name.length() == 0){ streetsToFill.addAll(streets); return; } ind = 0; for (Street s : streets) { String sName = useEnglishNames ? s.getEnName() : s.getName(); String lowerCase = sName.toLowerCase(); if (lowerCase.startsWith(name)) { streetsToFill.add(ind, s); ind++; } else if (lowerCase.contains(name)) { streetsToFill.add(s); } } } public void fillWithSuggestedCities(String name, List<MapObject> citiesToFill, LatLon currentLocation){ preloadCities(); // essentially index is created that cities towns are first in cities map int ind = 0; if (name.length() >= 2 && Character.isDigit(name.charAt(0)) && Character.isDigit(name.charAt(1))) { preloadPostcodes(); // also try to identify postcodes String uName = name.toUpperCase(); for (String code : postCodes.keySet()) { if (code.startsWith(uName)) { citiesToFill.add(ind++, postCodes.get(code)); } else if(code.contains(uName)){ citiesToFill.add(postCodes.get(code)); } } } if(name.length() < 3){ EnumSet<CityType> set = EnumSet.of(CityType.CITY, CityType.TOWN); for(CityType t : set){ List<City> list = cityTypes.get(t); if(list == null){ continue; } if(name.length() == 0){ citiesToFill.addAll(list); } else { name = name.toLowerCase(); for (City c : list) { String cName = useEnglishNames ? c.getEnName() : c.getName(); String lowerCase = cName.toLowerCase(); if(lowerCase.startsWith(name)){ citiesToFill.add(c); } } } } } else { name = name.toLowerCase(); Collection<City> src = cities.values(); for (City c : src) { String cName = useEnglishNames ? c.getEnName() : c.getName(); String lowerCase = cName.toLowerCase(); if (lowerCase.startsWith(name)) { citiesToFill.add(ind, c); ind++; } else if (lowerCase.contains(name)) { citiesToFill.add(c); } } int initialsize = citiesToFill.size(); log.debug("Start loading cities for " +getName() + " filter " + name); //$NON-NLS-1$ //$NON-NLS-2$ // lower function in SQLite requires ICU extension name = Algoritms.capitalizeFirstLetterAndLowercase(name); int i = name.indexOf('\''); if(i != -1){ // SQL quotation name = name.replace("'", "''"); //$NON-NLS-1$ //$NON-NLS-2$ } StringBuilder where = new StringBuilder(80); where. append("city_type not in ("). //$NON-NLS-1$ append('\'').append(CityType.valueToString(CityType.CITY)).append('\'').append(", "). //$NON-NLS-1$ append('\'').append(CityType.valueToString(CityType.TOWN)).append('\'').append(") and "). //$NON-NLS-1$ append(useEnglishNames ? "name_en" : "name").append(" LIKE '"+name+"%'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ Cursor query = db.query("city", new String[]{"id","latitude", "longitude", "name", "name_en", "city_type"}, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ where.toString(), null, null, null, null); if (query.moveToFirst()) { List<City> hamlets = new ArrayList<City>(); do { hamlets.add(parseCityFromCursor(query)); } while (query.moveToNext()); Collections.sort(hamlets, new MapObjectNameDistanceComparator(useEnglishNames, currentLocation)); citiesToFill.addAll(hamlets); } query.close(); log.debug("Loaded citites " + (citiesToFill.size() - initialsize)); //$NON-NLS-1$ } } public void preloadWayNodes(Street street){ if(street.getWayNodes().isEmpty()){ Cursor query = db.query("street_node", new String[]{"id", "latitude", "longitude", "street", "way"}, "? = street", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ new String[] { street.getId() + "" }, null, null, null); //$NON-NLS-1$ log.debug("Start loading waynodes for " + street.getName()); //$NON-NLS-1$ Map<Long, Way> ways = new LinkedHashMap<Long, Way>(); if (query.moveToFirst()) { do { Node n = new Node(query.getDouble(1), query.getDouble(2), query.getLong(0)); long way = query.getLong(4); if(!ways.containsKey(way)){ ways.put(way, new Way(way)); } ways.get(way).addNode(n); } while (query.moveToNext()); } query.close(); for(Way w : ways.values()){ street.getWayNodes().add(w); } log.debug("Loaded " + ways.size() + " ways"); //$NON-NLS-1$ //$NON-NLS-2$ } } public void preloadPostcodes() { if (postCodes.isEmpty()) { // check if it possible to load postcodes Cursor query = db.query(true, "building", new String[] { "postcode" }, null, //$NON-NLS-1$//$NON-NLS-2$ null, null, null, null, null); log.debug("Start loading postcodes for "); //$NON-NLS-1$ if (query.moveToFirst()) { do { String postcode = query.getString(0); if (postcode != null) { postCodes.put(postcode, new PostCode(postcode)); } } while (query.moveToNext()); } query.close(); log.debug("Loaded " + postCodes.size() + " postcodes "); //$NON-NLS-1$ //$NON-NLS-2$ } } public void preloadBuildings(Street street){ if (street.getBuildings().isEmpty()) { Cursor query = db.query("building", //$NON-NLS-1$ new String[]{"id","latitude", "longitude", "name", "name_en", "street", "postcode"} //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ , "? = street", //$NON-NLS-1$ new String[] { street.getId() + "" }, null, null, null); //$NON-NLS-1$ log.debug("Start loading buildings for " + street.getName()); //$NON-NLS-1$ if (query.moveToFirst()) { do { Building building = new Building(); building.setId(query.getLong(0)); building.setLocation(query.getDouble(1), query.getDouble(2)); building.setName(query.getString(3)); building.setEnName(query.getString(4)); building.setPostcode(query.getString(6)); street.registerBuilding(building); } while (query.moveToNext()); street.sortBuildings(); } query.close(); log.debug("Loaded " + street.getBuildings().size() + " buildings"); //$NON-NLS-1$ //$NON-NLS-2$ } } public void preloadStreets(MapObject o){ assert o instanceof PostCode || o instanceof City; City city = (City) (o instanceof City ? o : null); PostCode post = (PostCode) (o instanceof PostCode ? o : null); if (city != null && city.isEmptyWithStreets()) { log.debug("Start loading streets for " + city.getName()); //$NON-NLS-1$ Cursor query = db.query("street", new String[]{"id","latitude", "longitude", "name", "name_en", "city"}, "? = city", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ new String[] { city.getId() + "" }, null, null, null); //$NON-NLS-1$ if (query.moveToFirst()) { do { Street street = new Street(city); street.setId(query.getLong(0)); street.setLocation(query.getDouble(1), query.getDouble(2)); street.setName(query.getString(3)); street.setEnName(query.getString(4)); city.registerStreet(street, useEnglishNames); } while (query.moveToNext()); } query.close(); log.debug("Loaded " + city.getStreets().size() + " streets"); //$NON-NLS-1$ //$NON-NLS-2$ } else if(post != null && post.isEmptyWithStreets()){ log.debug("Start loading streets for " + post.getName()); //$NON-NLS-1$ Cursor query = db.rawQuery("SELECT B.CITY, B.ID,B.LATITUDE, B.LONGITUDE, B.NAME, B.NAME_EN FROM building A JOIN street B ON A.street = B.ID WHERE A.postcode = ?", //$NON-NLS-1$ new String[] { post.getName() + "" }); //$NON-NLS-1$ if (query.moveToFirst()) { do { city = getCityById(query.getLong(0)); Street street = null; if(city != null){ preloadStreets(city); street = city.getStreet(useEnglishNames ? query.getString(5) : query.getString(4)); } if(street == null){ street = new Street(city); street.setId(query.getLong(1)); street.setLocation(query.getDouble(2), query.getDouble(3)); street.setName(query.getString(4)); street.setEnName(query.getString(5)); if(city != null){ city.registerStreet(street, useEnglishNames); } } post.registerStreet(street, useEnglishNames); } while (query.moveToNext()); } query.close(); log.debug("Loaded " +post.getStreets().size() + " streets"); //$NON-NLS-1$ //$NON-NLS-2$ } } public void addCityToPreloadedList(City city){ cities.put(city.getId(), city); if(!cityTypes.containsKey(city.getType())){ cityTypes.put(city.getType(), new ArrayList<City>()); } cityTypes.get(city.getType()).add(city); } protected City parseCityFromCursor(Cursor query){ CityType type = CityType.valueFromString(query.getString(5)); if (type != null) { City city = new City(type); city.setId(query.getLong(0)); city.setLocation(query.getDouble(1), query.getDouble(2)); city.setName(query.getString(3)); city.setEnName(query.getString(4)); return city; } return null; } public void preloadCities(){ if (cities.isEmpty()) { log.debug("Start loading cities for " +getName()); //$NON-NLS-1$ StringBuilder where = new StringBuilder(); where.append("city_type="). //$NON-NLS-1$ append('\'').append(CityType.valueToString(CityType.CITY)).append('\'').append(" or "). //$NON-NLS-1$ append("city_type="). //$NON-NLS-1$ append('\'').append(CityType.valueToString(CityType.TOWN)).append('\''); Cursor query = db.query("city", new String[]{"id","latitude", "longitude", "name", "name_en", "city_type"}, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ where.toString(), null, null, null, null); if(query.moveToFirst()){ do { City city = parseCityFromCursor(query); if (city != null) { cities.put(city.getId(), city); if(!cityTypes.containsKey(city.getType())){ cityTypes.put(city.getType(), new ArrayList<City>()); } cityTypes.get(city.getType()).add(city); } } while(query.moveToNext()); } log.debug("Loaded " + cities.size() + " cities"); //$NON-NLS-1$ //$NON-NLS-2$ query.close(); } } @Override public boolean isMapRepository() { return false; } @Override public void clearCache() { clearCities(); } @Override public LatLon findStreetIntersection(Street street, Street street2) { preloadWayNodes(street2); preloadWayNodes(street); for(Way w : street2.getWayNodes()){ for(Way w2 : street.getWayNodes()){ for(Node n : w.getNodes()){ for(Node n2 : w2.getNodes()){ if(n.getId() == n2.getId()){ return n.getLatLon(); } } } } } return null; } }