diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
index 14d0abab79..861a7c9b93 100644
--- a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
+++ b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
@@ -274,6 +274,10 @@ public class Amenity extends MapObject {
return null;
}
+ public String getTagContent(String tag) {
+ return getTagContent(tag, null);
+ }
+
public String getTagContent(String tag, String lang) {
if (lang != null) {
String translateName = getAdditionalInfo(tag + ":" + lang);
diff --git a/OsmAnd/res/layout/wikivoyage_travel_gpx_card.xml b/OsmAnd/res/layout/wikivoyage_travel_gpx_card.xml
new file mode 100644
index 0000000000..44077a0dcd
--- /dev/null
+++ b/OsmAnd/res/layout/wikivoyage_travel_gpx_card.xml
@@ -0,0 +1,282 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java
new file mode 100644
index 0000000000..185fdc922c
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java
@@ -0,0 +1,14 @@
+package net.osmand.plus.wikivoyage.data;
+
+public class TravelGpx extends TravelArticle {
+
+ public static final String DISTANCE = "distance";
+ public static final String DIFF_ELE_UP = "diff_ele_up";
+ public static final String DIFF_ELE_DOWN = "diff_ele_down";
+ public static final String USER = "user";
+
+ public String user;
+ public float totalDistance = 0;
+ public double diffElevationUp = 0;
+ public double diffElevationDown = 0;
+}
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java
index 02ee5a32c8..c2ccc442c3 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java
@@ -143,7 +143,8 @@ public class TravelLocalDataHelper {
@Nullable
private TravelArticle getArticle(String title, String lang) {
for (TravelArticle article : savedArticles) {
- if (article.title != null && article.title.equals(title) && article.lang != null && article.lang.equals(lang)) {
+ if (Algorithms.stringsEqual(article.title, title)
+ && Algorithms.stringsEqual(article.lang, lang)) {
return article;
}
}
@@ -477,12 +478,12 @@ public class TravelLocalDataHelper {
SQLiteConnection conn = openConnection(false);
if (conn != null) {
try {
- conn.execSQL("DELETE FROM " + BOOKMARKS_TABLE_NAME +
- " WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ?" +
- " AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
- " AND " + BOOKMARKS_COL_LANG + " = ?" +
- " AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?",
- new Object[]{article.title, article.routeId, article.lang, travelBook});
+ String query = "DELETE FROM " + BOOKMARKS_TABLE_NAME +
+ " WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ?" +
+ " AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
+ " AND " + BOOKMARKS_COL_LANG + ((article.lang != null) ? " = '" + article.lang + "'" : " IS NULL") +
+ " AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?";
+ conn.execSQL(query, new Object[]{article.title, article.routeId, travelBook});
} finally {
conn.close();
}
@@ -537,7 +538,12 @@ public class TravelLocalDataHelper {
@NonNull
private TravelArticle readSavedArticle(SQLiteCursor cursor) {
- TravelArticle res = new TravelArticle();
+ TravelArticle res;
+ if (cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_LANG)) == null) {
+ res = new TravelGpx();
+ } else {
+ res = new TravelArticle();
+ }
res.title = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_ARTICLE_TITLE));
res.lang = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_LANG));
res.aggregatedPartOf = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_IS_PART_OF));
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java
index 0f14896a94..294cf2cd13 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java
@@ -2,6 +2,7 @@ package net.osmand.plus.wikivoyage.data;
import android.os.AsyncTask;
import android.text.TextUtils;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -13,6 +14,7 @@ import net.osmand.IndexConstants;
import net.osmand.OsmAndCollator;
import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
+import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
@@ -46,8 +48,18 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import gnu.trove.iterator.TIntObjectIterator;
+
+import static net.osmand.GPXUtilities.Track;
+import static net.osmand.GPXUtilities.TrkSegment;
import static net.osmand.GPXUtilities.WptPt;
import static net.osmand.GPXUtilities.writeGpxFile;
+import static net.osmand.plus.helpers.GpxUiHelper.getGpxTitle;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELE_DOWN;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELE_UP;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.DISTANCE;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.USER;
+import static net.osmand.util.Algorithms.capitalizeFirstLetter;
public class TravelObfHelper implements TravelHelper {
@@ -55,9 +67,13 @@ public class TravelObfHelper implements TravelHelper {
private static final String WORLD_WIKIVOYAGE_FILE_NAME = "World_wikivoyage.travel.obf";
public static final String ROUTE_ARTICLE = "route_article";
public static final String ROUTE_ARTICLE_POINT = "route_article_point";
+ public static final String ROUTE_TRACK = "route_track";
public static final int POPULAR_ARTICLES_SEARCH_RADIUS = 100000;
public static final int ARTICLE_SEARCH_RADIUS = 50000;
+ public static final int GPX_TRACKS_SEARCH_RADIUS = 10000;
public static final int MAX_POPULAR_ARTICLES_COUNT = 30;
+ public static final String REF_TAG = "ref";
+ public static final String NAME_TAG = "name";
private final OsmandApplication app;
private final Collator collator;
@@ -91,47 +107,69 @@ public class TravelObfHelper implements TravelHelper {
public synchronized List loadPopularArticles() {
String lang = app.getLanguage();
List popularArticles = new ArrayList<>();
- for (BinaryMapIndexReader reader : getReaders()) {
+ final List> amenities = new ArrayList<>();
+ final LatLon location = app.getMapViewTrackingUtilities().getMapLocation();
+ for (final BinaryMapIndexReader reader : getReaders()) {
try {
- final LatLon location = app.getMapViewTrackingUtilities().getMapLocation();
- SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(
- location, POPULAR_ARTICLES_SEARCH_RADIUS, -1, getSearchFilter(false), null);
- List amenities = reader.searchPoi(req);
- if (amenities.size() > 0) {
- Collections.sort(amenities, new Comparator() {
- @Override
- public int compare(Amenity a1, Amenity a2) {
- int d1 = (int) (MapUtils.getDistance(a1.getLocation().getLatitude(), a1.getLocation().getLongitude(),
- location.getLatitude(), location.getLongitude()));
- int d2 = (int) (MapUtils.getDistance(a2.getLocation().getLatitude(), a2.getLocation().getLongitude(),
- location.getLatitude(), location.getLongitude()));
- return d1 < d2 ? -1 : (d1 == d2 ? 0 : 1);
- }
- });
- for (Amenity amenity : amenities) {
- if (!Algorithms.isEmpty(amenity.getName(lang))) {
- TravelArticle article = cacheTravelArticles(reader.getFile(), amenity, lang, false, null);
- if (article != null) {
- popularArticles.add(article);
- if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) {
- break;
- }
- }
+ searchAmenity(amenities, location, reader, POPULAR_ARTICLES_SEARCH_RADIUS, -1, ROUTE_ARTICLE);
+ searchAmenity(amenities, location, reader, GPX_TRACKS_SEARCH_RADIUS, 15, ROUTE_TRACK);
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+
+ if (amenities.size() > 0) {
+ Collections.sort(amenities, new Comparator>() {
+ @Override
+ public int compare(Pair article1, Pair article2) {
+ int d1 = (int) (MapUtils.getDistance(((Amenity) article1.second).getLocation(), location));
+ int d2 = (int) (MapUtils.getDistance(((Amenity) article2.second).getLocation(), location));
+ return d1 < d2 ? -1 : (d1 == d2 ? 0 : 1);
+ }
+ });
+ for (Pair amenity : amenities) {
+ if (!Algorithms.isEmpty(amenity.second.getName(lang))) {
+ TravelArticle article = cacheTravelArticles(amenity.first, amenity.second, lang, false, null);
+ if (article != null) {
+ popularArticles.add(article);
+ if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) {
+ break;
}
}
}
- } catch (Exception e) {
- LOG.error(e.getMessage(), e);
}
}
this.popularArticles = popularArticles;
return popularArticles;
}
+ private void searchAmenity(final List> amenitiesList, LatLon location,
+ final BinaryMapIndexReader reader, int searchRadius, int zoom,
+ String searchFilter) throws IOException {
+ reader.searchPoi(BinaryMapIndexReader.buildSearchPoiRequest(
+ location, searchRadius, zoom, getSearchFilter(searchFilter), new ResultMatcher() {
+ @Override
+ public boolean publish(Amenity object) {
+ amenitiesList.add(new Pair<>(reader.getFile(), object));
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ }));
+ }
+
@Nullable
private TravelArticle cacheTravelArticles(File file, Amenity amenity, String lang, boolean readPoints, @Nullable GpxReadCallback callback) {
TravelArticle article = null;
- Map articles = readArticles(file, amenity);
+ Map articles;
+ if (ROUTE_TRACK.equals(amenity.getSubType())) {
+ articles = readRoutePoint(file, amenity);
+ } else {
+ articles = readArticles(file, amenity);
+ }
if (!Algorithms.isEmpty(articles)) {
TravelArticleIdentifier newArticleId = articles.values().iterator().next().generateIdentifier();
cachedArticles.put(newArticleId, articles);
@@ -140,12 +178,41 @@ public class TravelObfHelper implements TravelHelper {
return article;
}
+ private Map readRoutePoint(File file, Amenity amenity) {
+ Map articles = new HashMap<>();
+ TravelGpx res = new TravelGpx();
+ res.file = file;
+ String title = amenity.getName("en");
+ res.title = createTitle(Algorithms.isEmpty(title) ? amenity.getName() : title);
+ res.lat = amenity.getLocation().getLatitude();
+ res.lon = amenity.getLocation().getLongitude();
+ res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID));
+ try {
+ res.totalDistance = Float.parseFloat(Algorithms.emptyIfNull(amenity.getTagContent(DISTANCE)));
+ } catch (NumberFormatException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ try {
+ res.diffElevationUp = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELE_UP)));
+ } catch (NumberFormatException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ try {
+ res.diffElevationDown = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELE_DOWN)));
+ } catch (NumberFormatException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ res.user = Algorithms.emptyIfNull(amenity.getTagContent(USER));
+ articles.put("en", res);
+ return articles;
+ }
+
@NonNull
- private SearchPoiTypeFilter getSearchFilter(final boolean articlePoints) {
+ private SearchPoiTypeFilter getSearchFilter(final String filterSubcategory) {
return new SearchPoiTypeFilter() {
@Override
public boolean accept(PoiCategory type, String subcategory) {
- return subcategory.equals(articlePoints ? ROUTE_ARTICLE_POINT : ROUTE_ARTICLE);
+ return subcategory.equals(filterSubcategory);
}
@Override
@@ -176,9 +243,9 @@ public class TravelObfHelper implements TravelHelper {
res.isParentOf = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IS_PARENT_OF, lang));
res.lat = amenity.getLocation().getLatitude();
res.lon = amenity.getLocation().getLongitude();
- res.imageTitle = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, null));
- res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null));
- res.routeSource = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null));
+ res.imageTitle = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE));
+ res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID));
+ res.routeSource = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE));
res.originalId = 0;
res.lang = lang;
res.contentsJson = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.CONTENT_JSON, lang));
@@ -186,6 +253,82 @@ public class TravelObfHelper implements TravelHelper {
return res;
}
+ @Nullable
+ private GPXFile buildTravelGpxFile(@NonNull final TravelGpx article) {
+ String routeId = article.getRouteId();
+ final String ref = routeId.substring(routeId.length() - 3);
+ final List segmentList = new ArrayList<>();
+
+ for (BinaryMapIndexReader reader : getReaders()) {
+ try {
+ if (article.file != null && !article.file.equals(reader.getFile())) {
+ continue;
+ }
+ BinaryMapIndexReader.SearchRequest sr = BinaryMapIndexReader.buildSearchRequest(
+ 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, 15, null,
+ new ResultMatcher() {
+ @Override
+ public boolean publish(BinaryMapDataObject object) {
+ if (object.getPointsLength() > 1) {
+ if (getTagValue(object, REF_TAG).equals(ref)
+ && createTitle(getTagValue(object, NAME_TAG)).equals(article.title)) {
+ segmentList.add(object);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ });
+ reader.searchMapIndex(sr);
+ if (!Algorithms.isEmpty(segmentList)) {
+ break;
+ }
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ }
+ GPXFile gpxFile = null;
+ if (!segmentList.isEmpty()) {
+ Track track = new Track();
+ for (BinaryMapDataObject segment : segmentList) {
+ TrkSegment trkSegment = new TrkSegment();
+ for (int i = 0; i < segment.getPointsLength(); i++) {
+ WptPt point = new WptPt();
+ point.lat = MapUtils.get31LatitudeY(segment.getPoint31YTile(i));
+ point.lon = MapUtils.get31LongitudeX(segment.getPoint31XTile(i));
+ trkSegment.points.add(point);
+ }
+ track.segments.add(trkSegment);
+ }
+ gpxFile = new GPXFile(article.getTitle(), article.getLang(), "");
+ gpxFile.tracks = new ArrayList<>();
+ gpxFile.tracks.add(track);
+ }
+ article.gpxFile = gpxFile;
+ return gpxFile;
+ }
+
+ private String getTagValue(BinaryMapDataObject object, String tag) {
+ BinaryMapIndexReader.MapIndex mi = object.getMapIndex();
+ TIntObjectIterator it = object.getObjectNames().iterator();
+ while (it.hasNext()) {
+ it.advance();
+ BinaryMapIndexReader.TagValuePair tp = mi.decodeType(it.key());
+ if (tp.tag.equals(tag)) {
+ return it.value();
+ }
+ }
+ return "";
+ }
+
+ private String createTitle(String name) {
+ return capitalizeFirstLetter(getGpxTitle(name));
+ }
+
@NonNull
private synchronized List getPointList(@NonNull final TravelArticle article) {
final List pointList = new ArrayList<>();
@@ -197,14 +340,14 @@ public class TravelObfHelper implements TravelHelper {
}
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
Algorithms.emptyIfNull(article.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
- getSearchFilter(true), new ResultMatcher() {
+ getSearchFilter(ROUTE_ARTICLE_POINT), new ResultMatcher() {
@Override
public boolean publish(Amenity amenity) {
String amenityLang = amenity.getTagSuffix(Amenity.LANG_YES + ":");
if (Algorithms.stringsEqual(lang, amenityLang)
&& Algorithms.stringsEqual(article.routeId,
- Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null)))) {
+ Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID)))) {
pointList.add(amenity);
}
return false;
@@ -246,7 +389,7 @@ public class TravelObfHelper implements TravelHelper {
}
String category = amenity.getTagSuffix("category_");
if (category != null) {
- wptPt.category = Algorithms.capitalizeFirstLetter(category);
+ wptPt.category = capitalizeFirstLetter(category);
}
return wptPt;
}
@@ -266,7 +409,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest searchRequest = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, searchQuery,
- 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(false), new ResultMatcher() {
+ 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(ROUTE_ARTICLE), new ResultMatcher() {
@Override
public boolean publish(Amenity object) {
List otherNames = object.getAllNames(false);
@@ -441,7 +584,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(
- 0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(false),
+ 0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(ROUTE_ARTICLE),
new ResultMatcher() {
boolean done = false;
@@ -530,12 +673,13 @@ public class TravelObfHelper implements TravelHelper {
}
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
Algorithms.emptyIfNull(articleId.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
- getSearchFilter(false), new ResultMatcher() {
+ getSearchFilter(ROUTE_ARTICLE), new ResultMatcher() {
boolean done = false;
@Override
public boolean publish(Amenity amenity) {
- if (Algorithms.stringsEqual(articleId.routeId, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null))) || isDbArticle) {
+ if (Algorithms.stringsEqual(articleId.routeId,
+ Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID))) || isDbArticle) {
amenities.add(amenity);
done = true;
}
@@ -606,7 +750,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(
- x, y, title, left, right, top, bottom, getSearchFilter(false),
+ x, y, title, left, right, top, bottom, getSearchFilter(ROUTE_ARTICLE),
new ResultMatcher() {
boolean done = false;
@@ -693,7 +837,12 @@ public class TravelObfHelper implements TravelHelper {
@NonNull
@Override
public File createGpxFile(@NonNull TravelArticle article) {
- final GPXFile gpx = article.getGpxFile();
+ final GPXFile gpx;
+ if (article instanceof TravelGpx) {
+ gpx = buildTravelGpxFile((TravelGpx) article);
+ } else {
+ gpx = article.getGpxFile();
+ }
File file = app.getAppPath(IndexConstants.GPX_TRAVEL_DIR + getGPXName(article));
writeGpxFile(file, gpx);
return file;
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java
index a95614bdb8..5341edcb9d 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java
@@ -20,6 +20,8 @@ import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard.StartEditingTravelVH;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard.DownloadUpdateVH;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard.TravelGpxVH;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard.NeededMapsVH;
@@ -48,6 +50,9 @@ public class ExploreRvAdapter extends RecyclerView.Adapter 0; i--) {
BaseTravelCard o = items.get(i);
- if (o instanceof ArticleTravelCard) {
+ if (o instanceof ArticleTravelCard || o instanceof TravelGpxCard) {
return i;
}
}
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java
index f4e4408cba..9055acd4ed 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java
@@ -29,6 +29,7 @@ import net.osmand.plus.download.DownloadResources;
import net.osmand.plus.download.DownloadValidationManager;
import net.osmand.plus.download.IndexItem;
import net.osmand.plus.wikivoyage.data.TravelArticle;
+import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelHelper;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import net.osmand.plus.wikivoyage.explore.travelcards.ArticleTravelCard;
@@ -37,6 +38,7 @@ import net.osmand.plus.wikivoyage.explore.travelcards.HeaderTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.OpenBetaTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard;
import java.io.IOException;
@@ -178,17 +180,21 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv
List popularArticles = app.getTravelHelper().getPopularArticles();
for (TravelArticle article : popularArticles) {
- items.add(new ArticleTravelCard(app, nightMode, article, activity.getSupportFragmentManager()));
+ if (article instanceof TravelGpx) {
+ items.add(new TravelGpxCard(app, nightMode, (TravelGpx) article, getActivity()));
+ } else {
+ items.add(new ArticleTravelCard(app, nightMode, article, activity.getSupportFragmentManager()));
+ }
+ }
+ items.add(new StartEditingTravelCard(activity, nightMode));
+ adapter.setItems(items);
+ final DownloadIndexesThread downloadThread = app.getDownloadThread();
+ if (!downloadThread.getIndexes().isDownloadedFromInternet) {
+ waitForIndexes = true;
+ downloadThread.runReloadIndexFilesSilent();
+ } else {
+ checkDownloadIndexes();
}
- }
- items.add(new StartEditingTravelCard(activity, nightMode));
- adapter.setItems(items);
- final DownloadIndexesThread downloadThread = app.getDownloadThread();
- if (!downloadThread.getIndexes().isDownloadedFromInternet) {
- waitForIndexes = true;
- downloadThread.runReloadIndexFilesSilent();
- } else {
- checkDownloadIndexes();
}
}
}
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java
index 8996e4d375..a01de6e6cb 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java
@@ -7,6 +7,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
@@ -16,16 +18,20 @@ import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
+import net.osmand.AndroidUtils;
import net.osmand.PicassoUtils;
+import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
-import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
+import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.widgets.tools.CropCircleTransformation;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.WikivoyageUtils;
import net.osmand.plus.wikivoyage.data.TravelArticle;
+import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
import java.util.ArrayList;
import java.util.List;
@@ -33,7 +39,8 @@ import java.util.List;
public class SavedArticlesRvAdapter extends RecyclerView.Adapter {
private static final int HEADER_TYPE = 0;
- private static final int ITEM_TYPE = 1;
+ private static final int ARTICLE_TYPE = 1;
+ private static final int GPX_TYPE = 2;
private final OsmandApplication app;
private final OsmandSettings settings;
@@ -45,6 +52,7 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter