diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java index 1fa3ef589c..e2b5432c6e 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java @@ -1424,7 +1424,7 @@ public class BinaryMapIndexReader { return addressIndexes; } - protected List getPoiIndexes() { + public List getPoiIndexes() { return poiIndexes; } diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java index a004d013dc..aa51de0c4b 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java @@ -229,17 +229,7 @@ public class BinaryMapPoiReaderAdapter { region.subcategories.add(new ArrayList()); break; case OsmandOdb.OsmAndCategoryTable.SUBCATEGORIES_FIELD_NUMBER: - String subCat = codedIS.readString().intern(); - PoiCategory lastCat = poiTypes.getPoiCategoryByName(region.categories.get(region.categories.size() - 1)); - PoiType poiType = new PoiType(MapPoiTypes.getDefault(), lastCat, null, subCat); - List filters = new ArrayList<>(); - for (PoiType poi : lastCat.getPoiTypes()) { - filters.add(poi.getKeyName()); - } - if (!filters.contains(subCat)) { - lastCat.getPoiTypes().add(poiType); - } - region.subcategories.get(region.subcategories.size() - 1).add(subCat); + region.subcategories.get(region.subcategories.size() - 1).add(codedIS.readString().intern()); break; default: skipUnknownField(t); diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java b/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java index 6635c77936..00f254c816 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java @@ -918,11 +918,4 @@ public class MapPoiTypes { return pat.isText(); } } - - - - - - - } diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/PoiFilter.java b/OsmAnd-java/src/main/java/net/osmand/osm/PoiFilter.java index 8063c0c5ab..4bbf2e2e4b 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/PoiFilter.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/PoiFilter.java @@ -69,5 +69,5 @@ public class PoiFilter extends AbstractPoiType { public List getPoiTypes() { return poiTypes; } - + } diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index ab7a807e37..670d83510d 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -23,7 +23,6 @@ import net.osmand.IProgress; import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.aidl.OsmandAidlApi; -import net.osmand.binary.BinaryMapIndexReader; import net.osmand.map.OsmandRegions; import net.osmand.map.OsmandRegions.RegionTranslation; import net.osmand.map.WorldRegion; @@ -406,17 +405,6 @@ public class AppInitializer implements IProgress { }); } - private void readPoiTypesFromMap() { - final BinaryMapIndexReader[] currentFile = app.resourceManager.getPoiSearchFiles(); - for (BinaryMapIndexReader r : currentFile) { - try { - r.initCategories(); - } catch (IOException e) { - LOG.error("Error while read poi types from map " + e); - } - } - } - public void onCreateApplication() { // always update application mode to default OsmandSettings osmandSettings = app.getSettings(); @@ -667,7 +655,6 @@ public class AppInitializer implements IProgress { initPoiTypes(); notifyEvent(InitEvents.POI_TYPES_INITIALIZED); app.resourceManager.reloadIndexesOnStart(this, warnings); - readPoiTypesFromMap(); // native depends on renderers initNativeCore(); diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index cf6c191124..2c650364b8 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -333,7 +333,6 @@ public class OsmandApplication extends MultiDexApplication { return poiFilters; } - public GpxSelectionHelper getSelectedGpxHelper() { return selectedGpxHelper; } diff --git a/OsmAnd/src/net/osmand/plus/poi/PoiFiltersHelper.java b/OsmAnd/src/net/osmand/plus/poi/PoiFiltersHelper.java index b708b4afd6..9d8f4940d0 100644 --- a/OsmAnd/src/net/osmand/plus/poi/PoiFiltersHelper.java +++ b/OsmAnd/src/net/osmand/plus/poi/PoiFiltersHelper.java @@ -3,7 +3,10 @@ package net.osmand.plus.poi; import android.os.Bundle; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.util.Pair; +import net.osmand.PlatformUtil; import net.osmand.osm.AbstractPoiType; import net.osmand.osm.MapPoiTypes; import net.osmand.osm.PoiCategory; @@ -12,12 +15,18 @@ import net.osmand.plus.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; import net.osmand.plus.R; +import net.osmand.plus.api.SQLiteAPI; import net.osmand.plus.api.SQLiteAPI.SQLiteConnection; import net.osmand.plus.api.SQLiteAPI.SQLiteCursor; import net.osmand.plus.api.SQLiteAPI.SQLiteStatement; import net.osmand.plus.wikipedia.WikipediaPoiMenu; import net.osmand.util.Algorithms; +import org.apache.commons.logging.Log; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -36,6 +45,7 @@ import static net.osmand.osm.MapPoiTypes.WIKI_PLACE; public class PoiFiltersHelper { + private static final Log LOG = PlatformUtil.getLog(PoiFiltersHelper.class); private final OsmandApplication application; private NominatimPoiFilter nominatimPOIFilter; @@ -625,6 +635,33 @@ public class PoiFiltersHelper { } } + @Nullable + public Pair>> getCacheByResourceName(String fileName) { + Pair>> cache = null; + PoiFilterDbHelper helper = openDbHelper(); + if (helper != null) { + cache = helper.getCacheByResourceName(helper.getReadableDatabase(), fileName); + helper.close(); + } + return cache; + } + + public void updateCacheForResource(String fileName, long lastModified, Map> categories) { + PoiFilterDbHelper helper = openDbHelper(); + if (helper != null) { + helper.updateCacheForResource(helper.getReadableDatabase(), fileName, lastModified, categories); + helper.close(); + } + } + + public void insertCacheForResource(String fileName, long lastModified, Map> categories) { + PoiFilterDbHelper helper = openDbHelper(); + if (helper != null) { + helper.insertCacheForResource(helper.getReadableDatabase(), fileName, lastModified, categories); + helper.close(); + } + } + private void saveSelectedPoiFilters() { Set filters = new HashSet<>(); for (Set template : selectedPoiFilters.values()) { @@ -641,7 +678,7 @@ public class PoiFiltersHelper { private static final int FALSE_INT = 0; public static final String DATABASE_NAME = "poi_filters"; - private static final int DATABASE_VERSION = 6; + private static final int DATABASE_VERSION = 7; private static final String FILTER_NAME = "poi_filters"; private static final String FILTER_COL_NAME = "name"; @@ -669,6 +706,20 @@ public class PoiFiltersHelper { CATEGORIES_COL_CATEGORY + ", " + CATEGORIES_COL_SUBCATEGORY + ");"; + private static final String POI_TYPES_CACHE_NAME = "poi_types_cache"; + private static final String MAP_FILE_NAME = "map_name"; + private static final String MAP_FILE_DATE = "map_date"; + private static final String CACHED_POI_CATEGORIES = "cached_categories"; + + private static final String POI_CACHE_TABLE_CREATE = "CREATE TABLE " + + POI_TYPES_CACHE_NAME + " (" + + MAP_FILE_NAME + ", " + + MAP_FILE_DATE + ", " + + CACHED_POI_CATEGORIES + ");"; + + private static final String CATEGORY_KEY = "category"; + private static final String SUB_CATEGORIES_KEY = "sub_categories"; + private OsmandApplication context; private SQLiteConnection conn; private MapPoiTypes mapPoiTypes; @@ -714,6 +765,7 @@ public class PoiFiltersHelper { public void onCreate(SQLiteConnection conn) { conn.execSQL(FILTER_TABLE_CREATE); conn.execSQL(CATEGORIES_TABLE_CREATE); + conn.execSQL(POI_CACHE_TABLE_CREATE); } @@ -725,6 +777,9 @@ public class PoiFiltersHelper { conn.execSQL("ALTER TABLE " + FILTER_NAME + " ADD " + FILTER_COL_HISTORY + " int DEFAULT " + FALSE_INT); conn.execSQL("ALTER TABLE " + FILTER_NAME + " ADD " + FILTER_COL_DELETED + " int DEFAULT " + FALSE_INT); } + if (oldVersion < 7) { + conn.execSQL(POI_CACHE_TABLE_CREATE); + } } private void deleteOldFilters(SQLiteConnection conn) { @@ -887,5 +942,89 @@ public class PoiFiltersHelper { db.execSQL("DELETE FROM " + FILTER_NAME + " WHERE " + FILTER_COL_ID + " = ?", new Object[]{key}); db.execSQL("DELETE FROM " + CATEGORIES_NAME + " WHERE " + CATEGORIES_FILTER_ID + " = ?", new Object[]{key}); } + + @Nullable + protected Pair>> getCacheByResourceName(SQLiteConnection db, String fileName) { + Pair>> cache = null; + if (db != null) { + SQLiteAPI.SQLiteCursor query = db.rawQuery("SELECT " + + MAP_FILE_DATE + ", " + + CACHED_POI_CATEGORIES + + " FROM " + + POI_TYPES_CACHE_NAME + + " WHERE " + MAP_FILE_NAME + " = ?", new String[]{fileName}); + if (query != null && query.moveToFirst()) { + long lastModified = query.getLong(0); + Map> categories = getCategories(query.getString(1)); + cache = new Pair<>(lastModified, categories); + } + if (query != null) { + query.close(); + } + db.close(); + } + return cache; + } + + private Map> getCategories(String json) { + Map> categories = new HashMap<>(); + try { + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String category = jsonObject.optString(CATEGORY_KEY); + List subCategories = getSubCategories(jsonObject.optString(SUB_CATEGORIES_KEY)); + categories.put(category, subCategories); + } + } catch (JSONException e) { + LOG.error("Error parsing categories: " + e); + } + return categories; + } + + protected void updateCacheForResource(SQLiteConnection db, String fileName, long lastModified, Map> categories) { + try { + db.execSQL("UPDATE " + POI_TYPES_CACHE_NAME + " SET " + + MAP_FILE_DATE + " = ?, " + + CACHED_POI_CATEGORIES + " = ? " + + "WHERE " + MAP_FILE_NAME + " = ?", + new Object[]{lastModified, getCategoriesJson(categories), fileName}); + } catch (JSONException e) { + LOG.error("Error converting category to json: " + e); + } + } + + protected void insertCacheForResource(SQLiteConnection db, String fileName, long lastModified, Map> categories) { + try { + db.execSQL("INSERT INTO " + POI_TYPES_CACHE_NAME + " VALUES(?,?,?)", + new Object[]{fileName, lastModified, getCategoriesJson(categories)}); + } catch (JSONException e) { + LOG.error("Error converting category to json: " + e); + } + } + + private String getCategoriesJson(Map> categories) throws JSONException { + JSONArray json = new JSONArray(); + for (Map.Entry> entry : categories.entrySet()) { + JSONObject jsonObject = new JSONObject(); + JSONArray subCategories = new JSONArray(); + for (String subCategory : entry.getValue()) { + subCategories.put(subCategory); + } + jsonObject.put(CATEGORY_KEY, entry.getKey()); + jsonObject.put(SUB_CATEGORIES_KEY, subCategories); + json.put(jsonObject); + } + return json.toString(); + } + + private List getSubCategories(@NonNull String json) throws JSONException { + List subCategories = new ArrayList<>(); + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + subCategories.add(jsonArray.optString(i)); + } + return subCategories; + } } } diff --git a/OsmAnd/src/net/osmand/plus/resources/AmenityIndexRepositoryBinary.java b/OsmAnd/src/net/osmand/plus/resources/AmenityIndexRepositoryBinary.java index 4c57457588..930012f998 100644 --- a/OsmAnd/src/net/osmand/plus/resources/AmenityIndexRepositoryBinary.java +++ b/OsmAnd/src/net/osmand/plus/resources/AmenityIndexRepositoryBinary.java @@ -1,6 +1,7 @@ package net.osmand.plus.resources; import androidx.annotation.Nullable; +import androidx.core.util.Pair; import net.osmand.Location; import net.osmand.PlatformUtil; @@ -9,8 +10,12 @@ import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader.MapIndex; import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; +import net.osmand.binary.BinaryMapPoiReaderAdapter; import net.osmand.data.Amenity; +import net.osmand.osm.MapPoiTypes; import net.osmand.osm.PoiCategory; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.poi.PoiFiltersHelper; import net.osmand.plus.resources.ResourceManager.BinaryMapReaderResource; import net.osmand.plus.resources.ResourceManager.BinaryMapReaderResourceType; import net.osmand.util.MapUtils; @@ -18,6 +23,7 @@ import net.osmand.util.MapUtils; import org.apache.commons.logging.Log; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -27,9 +33,73 @@ public class AmenityIndexRepositoryBinary implements AmenityIndexRepository { private final static Log log = PlatformUtil.getLog(AmenityIndexRepositoryBinary.class); private BinaryMapReaderResource resource; + private MapPoiTypes poiTypes; + private Map> deltaPoiCategories = new HashMap<>(); - public AmenityIndexRepositoryBinary(BinaryMapReaderResource resource) { + public AmenityIndexRepositoryBinary(BinaryMapReaderResource resource, OsmandApplication app) { this.resource = resource; + poiTypes = app.getPoiTypes(); + checkCachedCategories(app.getPoiFilters()); + } + + public Map> getDeltaPoiCategories() { + return deltaPoiCategories; + } + + private void checkCachedCategories(PoiFiltersHelper poiFiltersHelper) { + String fileName = resource.getFileName(); + long lastModified = resource.getFileLastModified(); + Pair>> cache = poiFiltersHelper.getCacheByResourceName(fileName); + if (cache == null || cache.first != null && cache.first != lastModified) { + deltaPoiCategories = new HashMap<>(); + try { + BinaryMapIndexReader reader = getOpenFile(); + if (reader != null) { + reader.initCategories(); + List regions = reader.getPoiIndexes(); + for (BinaryMapPoiReaderAdapter.PoiRegion region : regions) { + calculateDeltaSubcategories(region); + } + if (cache == null) { + poiFiltersHelper.insertCacheForResource(fileName, lastModified, deltaPoiCategories); + } else { + poiFiltersHelper.updateCacheForResource(fileName, lastModified, deltaPoiCategories); + } + } + } catch (IOException e) { + log.error("Error initializing categories ", e); + } + } else if (cache.second != null) { + deltaPoiCategories = cache.second; + } + } + + private void calculateDeltaSubcategories(BinaryMapPoiReaderAdapter.PoiRegion region) { + List categories = region.getCategories(); + List> subCategories = region.getSubcategories(); + for (int i = 0; i < categories.size(); i++) { + String categoryName = categories.get(i); + + PoiCategory poiCategory = poiTypes.getPoiCategoryByName(categoryName); + List deltaSubCategories = null; + for (List subList : subCategories) { + for (String subCategory : subList) { + if (poiCategory.getPoiTypeByKeyName(subCategory) != null) { + if (deltaSubCategories == null) { + deltaSubCategories = new ArrayList<>(); + } + deltaSubCategories.add(subCategory); + } + } + } + if (deltaSubCategories != null) { + if(deltaPoiCategories.containsKey(categoryName)) { + deltaPoiCategories.get(categoryName).addAll(deltaSubCategories); + } else { + deltaPoiCategories.put(categoryName, deltaSubCategories); + } + } + } } @Nullable @@ -54,8 +124,8 @@ public class AmenityIndexRepositoryBinary implements AmenityIndexRepository { BinaryMapIndexReader reader = getOpenFile(); return reader != null && reader.containsPoiData(left31, top31, right31, bottom31); } - - + + public synchronized Map> searchAmenityCategoriesByName(String query, Map> map) { try { BinaryMapIndexReader reader = getOpenFile(); @@ -65,12 +135,12 @@ public class AmenityIndexRepositoryBinary implements AmenityIndexRepository { } return map; } - - + + public synchronized List searchAmenitiesByName(int x, int y, int l, int t, int r, int b, String query, ResultMatcher resulMatcher) { long now = System.currentTimeMillis(); List amenities = Collections.emptyList(); - SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(x, y, query, l, r, t, b,resulMatcher); + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(x, y, query, l, r, t, b, resulMatcher); try { BinaryMapIndexReader index = getOpenFile(); if (index != null) { @@ -90,10 +160,10 @@ public class AmenityIndexRepositoryBinary implements AmenityIndexRepository { } return amenities; } - + @Override - public synchronized List searchAmenities(int stop, int sleft, int sbottom, int sright, int zoom, - final SearchPoiTypeFilter filter, ResultMatcher matcher) { + public synchronized List searchAmenities(int stop, int sleft, int sbottom, int sright, int zoom, + final SearchPoiTypeFilter filter, ResultMatcher matcher) { long now = System.currentTimeMillis(); SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(sleft, sright, stop, sbottom, zoom, filter, matcher); @@ -118,7 +188,7 @@ public class AmenityIndexRepositoryBinary implements AmenityIndexRepository { long now = System.currentTimeMillis(); List result = null; SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(locations, radius, - filter, matcher ); + filter, matcher); try { BinaryMapIndexReader reader = getOpenFile(); if (reader != null) { @@ -129,10 +199,10 @@ public class AmenityIndexRepositoryBinary implements AmenityIndexRepository { return result; } if (log.isDebugEnabled() && result != null) { - log.debug(String.format("Search done in %s ms found %s.", (System.currentTimeMillis() - now), result.size())); //$NON-NLS-1$ + log.debug(String.format("Search done in %s ms found %s.", (System.currentTimeMillis() - now), result.size())); //$NON-NLS-1$ } return result; - + } - + } diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java index 1a4be8a631..44a50c880c 100644 --- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java +++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java @@ -32,6 +32,7 @@ import net.osmand.map.MapTileDownloader.DownloadRequest; import net.osmand.map.OsmandRegions; import net.osmand.osm.MapPoiTypes; import net.osmand.osm.PoiCategory; +import net.osmand.osm.PoiType; import net.osmand.plus.AppInitializer; import net.osmand.plus.AppInitializer.InitEvents; import net.osmand.plus.OsmandApplication; @@ -152,6 +153,10 @@ public class ResourceManager { return filename.getName(); } + public long getFileLastModified() { + return filename.lastModified(); + } + // should not use methods to read from file! @Nullable public BinaryMapIndexReader getShallowReader() { @@ -735,7 +740,7 @@ public class ResourceManager { resource.setUseForPublicTransport(true); } if (mapReader.containsPoiData()) { - amenityRepositories.put(f.getName(), new AmenityIndexRepositoryBinary(resource)); + amenityRepositories.put(f.getName(), new AmenityIndexRepositoryBinary(resource, context)); } } } catch (SQLiteException e) { @@ -746,6 +751,17 @@ public class ResourceManager { warnings.add(MessageFormat.format(context.getString(R.string.version_index_is_big_for_memory), f.getName())); } } + for (AmenityIndexRepository repo : amenityRepositories.values()) { + Map> categories = ((AmenityIndexRepositoryBinary) repo).getDeltaPoiCategories(); + if (!categories.isEmpty()) { + for (Map.Entry> entry : categories.entrySet()) { + PoiCategory poiCategory = context.getPoiTypes().getPoiCategoryByName(entry.getKey(), true); + for (String s : entry.getValue()) { + poiCategory.addPoiType(new PoiType(MapPoiTypes.getDefault(), poiCategory, null, s)); + } + } + } + } log.debug("All map files initialized " + (System.currentTimeMillis() - val) + " ms"); if (files.size() > 0 && (!indCache.exists() || indCache.canWrite())) { try { @@ -1122,21 +1138,6 @@ public class ResourceManager { return readers.toArray(new BinaryMapIndexReader[readers.size()]); } - public BinaryMapIndexReader[] getPoiSearchFiles() { - Collection fileReaders = getFileReaders(); - List readers = new ArrayList<>(fileReaders.size()); - for (BinaryMapReaderResource r : fileReaders) { - BinaryMapIndexReader shallowReader = r.getShallowReader(); - if (shallowReader != null && shallowReader.containsPoiData()) { - BinaryMapIndexReader reader = r.getReader(BinaryMapReaderResourceType.POI); - if (reader != null) { - readers.add(reader); - } - } - } - return readers.toArray(new BinaryMapIndexReader[readers.size()]); - } - public Map getIndexFileNames() { return new LinkedHashMap(indexFileNames); } diff --git a/OsmAnd/src/net/osmand/plus/search/QuickSearchCustomPoiFragment.java b/OsmAnd/src/net/osmand/plus/search/QuickSearchCustomPoiFragment.java index 9bac75a198..4bdfcf9642 100644 --- a/OsmAnd/src/net/osmand/plus/search/QuickSearchCustomPoiFragment.java +++ b/OsmAnd/src/net/osmand/plus/search/QuickSearchCustomPoiFragment.java @@ -43,6 +43,8 @@ import net.osmand.plus.render.RenderingIcons; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -90,6 +92,12 @@ public class QuickSearchCustomPoiFragment extends DialogFragment { this.nightMode = app.getSettings().OSMAND_THEME.get() == OsmandSettings.OSMAND_DARK_THEME; setStyle(STYLE_NO_FRAME, nightMode ? R.style.OsmandDarkTheme : R.style.OsmandLightTheme); poiCategoryList = app.getPoiTypes().getCategories(false); + Collections.sort(poiCategoryList, new Comparator() { + @Override + public int compare(PoiCategory poiCategory, PoiCategory t1) { + return poiCategory.getTranslation().compareTo(t1.getTranslation()); + } + }); } @Override