diff --git a/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java b/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java index 49f08c1372c..5d182f23208 100644 --- a/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java +++ b/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java @@ -2,6 +2,7 @@ import static net.osmand.IndexConstants.GPX_FILE_EXT; import static net.osmand.IndexConstants.GPX_GZ_FILE_EXT; +import static net.osmand.data.Amenity.ROUTE_ID; import java.io.File; import java.io.FileOutputStream; @@ -696,7 +697,7 @@ public List getOriginalNames() { public String getRouteID() { for (Map.Entry entry : getTags().entrySet()) { - if ("route_id".equals(entry.getKey())) { + if (ROUTE_ID.equals(entry.getKey())) { return entry.getValue(); } } 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 13e72fae141..8006d1130fc 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java @@ -1,7 +1,11 @@ package net.osmand.data; -import static net.osmand.gpx.GPXUtilities.AMENITY_PREFIX; -import static net.osmand.gpx.GPXUtilities.OSM_PREFIX; +import static net.osmand.gpx.GPXUtilities.*; +import static net.osmand.osm.MapPoiTypes.ROUTES_PREFIX; +import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE; +import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE_POINT; +import static net.osmand.osm.MapPoiTypes.ROUTE_TRACK; +import static net.osmand.osm.MapPoiTypes.ROUTE_TRACK_POINT; import net.osmand.Location; import net.osmand.binary.BinaryMapIndexReader.TagValuePair; @@ -48,6 +52,7 @@ public class Amenity extends MapObject { public static final String IS_AGGR_PART = "is_aggr_part"; public static final String CONTENT_JSON = "content_json"; public static final String ROUTE_ID = "route_id"; + public static final String ROUTE_ID_OSM_PREFIX = "OSM"; public static final String ROUTE_SOURCE = "route_source"; public static final String ROUTE_NAME = "route_name"; public static final String COLOR = "color"; @@ -309,7 +314,7 @@ public StringBuilder printNamesAndAdditional() { private void printNames(String prefix, Map stringMap, StringBuilder s) { for (Entry e : stringMap.entrySet()) { if (e.getValue().startsWith(" gz ")) { - s.append(prefix).append(e.getKey()).append("='gzip ...'"); + s.append(prefix).append(e.getKey()).append("='gzip ...' "); } else { s.append(prefix).append(e.getKey()).append("='").append(e.getValue()).append("' "); } @@ -347,10 +352,11 @@ public String getColor() { } public String getGpxIcon() { - return getAdditionalInfo(GPX_ICON); + String wikiVoyageIcon = getAdditionalInfo(GPX_ICON); + String travelGpxIcon = getAdditionalInfo(ICON_NAME_EXTENSION); + return Algorithms.isEmpty(wikiVoyageIcon) ? travelGpxIcon : wikiVoyageIcon; } - public String getContentLanguage(String tag, String lang, String defLang) { if (lang != null) { String translateName = getAdditionalInfo(tag + ":" + lang); @@ -447,6 +453,25 @@ public String getRouteId() { return getAdditionalInfo(ROUTE_ID); } + public boolean hasOsmRouteId() { + String routeId = getRouteId(); + return routeId != null && routeId.startsWith(ROUTE_ID_OSM_PREFIX); + } + + public String getGpxFileName(String lang) { + final String gpxFileName = lang != null ? getName(lang) : getEnName(true); + if (!Algorithms.isEmpty(gpxFileName)) { + return gpxFileName; + } + if (!Algorithms.isEmpty(getRouteId())) { + return getRouteId(); + } + if (!Algorithms.isEmpty(getSubType())) { + return getType().getKeyName() + " " + getSubType(); + } + return getType().getKeyName(); + } + public String getStrictTagContent(String tag, String lang) { if (lang != null) { String translateName = getAdditionalInfo(tag + ":" + lang); @@ -549,6 +574,14 @@ public boolean isPrivateAccess() { return PRIVATE_VALUE.equals(getTagContent(ACCESS_PRIVATE_TAG)); } + public boolean isRouteTrack() { + return subType != null && (subType.equals(ROUTE_TRACK) || subType.startsWith(ROUTES_PREFIX)); + } + + public boolean isRoutePoint() { + return subType != null && (subType.equals(ROUTE_TRACK_POINT) || subType.equals(ROUTE_ARTICLE_POINT)); + } + public JSONObject toJSON() { JSONObject json = super.toJSON(); json.put("subType", subType); 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 05170babbd1..8e14cf29fd8 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java @@ -31,7 +31,7 @@ public class MapPoiTypes { - private static final String OTHER_MAP_CATEGORY = "Other"; + public static final String OTHER_MAP_CATEGORY = "Other"; private static MapPoiTypes DEFAULT_INSTANCE = null; private static final Log log = PlatformUtil.getLog(MapRenderingTypes.class); private String resourceName; @@ -48,7 +48,8 @@ public class MapPoiTypes { public static final String ROUTE_ARTICLE = "route_article"; public static final String ROUTE_ARTICLE_POINT = "route_article_point"; public static final String CATEGORY = "category"; - public static final String ROUTE_TRACK = "route_track"; + public static final String ROUTE_TRACK = "route_track"; // routes:route_track (no activity) + public static final String ROUTES_PREFIX = "routes_"; // routes:routes_xxx (any activity type) public static final String ROUTE_TRACK_POINT = "route_track_point"; private PoiTranslator poiTranslator = null; @@ -970,11 +971,7 @@ public String replaceDeprecatedSubtype(PoiCategory type, String subtype) { } public Amenity parseAmenity(String tag, String val, boolean relation, Map otherTags) { - initPoiTypesByTag(); - PoiType pt = poiTypesByTag.get(tag + "/" + val); - if (pt == null) { - pt = poiTypesByTag.get(tag); - } + PoiType pt = getPoiTypeByTagValue(tag, val); if (pt == null || pt.isAdditional()) { return null; } @@ -1032,6 +1029,19 @@ public Amenity parseAmenity(String tag, String val, boolean relation, Map routeExtension = true - tagName == "types" -> typesExtension = true + tagName == "route" && insideTagDepth["trk"]!! > 0 -> routeExtension = true + tagName == "types" && insideTagDepth["trk"]!! > 0 -> typesExtension = true tagName == "points_groups" -> pointsGroupsExtension = true tagName == "network_route" -> networkRoute = true else -> { @@ -1314,7 +1317,8 @@ object GpxUtilities { } } else if (tok == XmlPullParser.END_TAG) { val parse = parserState.lastOrNull() - val tag = parser.getName() + val tag = parser.getName() ?: "" + insideTagDepth[tag]?.let { insideTagDepth[tag] = it - 1} if (tag.equals("routepointextension", ignoreCase = true)) { routePointExtension = false diff --git a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/RouteActivityHelper.kt b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/RouteActivityHelper.kt index 0cc947be854..85c99daf1ff 100644 --- a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/RouteActivityHelper.kt +++ b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/RouteActivityHelper.kt @@ -45,6 +45,15 @@ object RouteActivityHelper { return cachedActivities } + fun findActivityByTag(tag: String): RouteActivity? { + for (activity in getActivities()) { + if (activity.tags != null && activity.tags.contains(tag)) { + return activity + } + } + return null + } + fun saveRouteActivity(trackItems: Collection, routeActivity: RouteActivity?) { runAsync { trackItems.forEach { trackItem -> @@ -124,7 +133,8 @@ object RouteActivityHelper { val activityId = activityJson["id"]!!.jsonPrimitive.content val activityLabel = activityJson["label"]!!.jsonPrimitive.content val iconName = activityJson["icon_name"]!!.jsonPrimitive.content - val activity = RouteActivity(activityId, activityLabel, iconName, activitiesGroup) + val tags = activityJson["tags"]?.jsonArray?.map { it.jsonPrimitive.content }?.toSet() + val activity = RouteActivity(activityId, activityLabel, iconName, activitiesGroup, tags) cachedActivities.add(activity) activities.add(activity) } diff --git a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/GpxExtensions.kt b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/GpxExtensions.kt index dde416446f2..aa771f6ae15 100644 --- a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/GpxExtensions.kt +++ b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/GpxExtensions.kt @@ -87,8 +87,4 @@ open class GpxExtensions { fun removeColor() { getExtensionsToWrite().remove(GpxUtilities.COLOR_NAME_EXTENSION) } - - companion object { - const val OBF_GPX_EXTENSION_TAG_PREFIX = "gpx_" // enlisted in poi_types.xml under name="route_track" - } } diff --git a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/RouteActivity.kt b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/RouteActivity.kt index ffa8981e566..4df75a8906d 100644 --- a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/RouteActivity.kt +++ b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/gpx/primitives/RouteActivity.kt @@ -4,7 +4,8 @@ data class RouteActivity( val id: String, val label: String, val iconName: String, - val group: RouteActivityGroup + val group: RouteActivityGroup, + val tags: Set? = null ) { override fun toString(): String { return id diff --git a/OsmAnd/res/values-b+sr+Latn/phrases.xml b/OsmAnd/res/values-b+sr+Latn/phrases.xml index 773b98f0f5d..1fb2f10f421 100644 --- a/OsmAnd/res/values-b+sr+Latn/phrases.xml +++ b/OsmAnd/res/values-b+sr+Latn/phrases.xml @@ -4884,17 +4884,6 @@ Tačke na putovanjima Druge rute Tačke drugih ruta - Bicikl - Pešačenje - Trčanje - Hodanje - Sportovi na vodi - Zimski sportovi - Koturaljke - Jahanje konja - Motorne sanke - Staze za trčanje - Brdski biciklizam Etimologija imena Naziv leve strane Naziv desne strane @@ -4904,4 +4893,4 @@ Osnovno Standardno Uslužno - \ No newline at end of file + diff --git a/OsmAnd/res/values-cs/phrases.xml b/OsmAnd/res/values-cs/phrases.xml index 6e6da50a1eb..1f1fa432cbe 100644 --- a/OsmAnd/res/values-cs/phrases.xml +++ b/OsmAnd/res/values-cs/phrases.xml @@ -4884,17 +4884,6 @@ Body na cestách Další trasy Další body trasy - Kolo - Pěší turistika - Běh - Chůze - Vodní sporty - Jízda na koni - Zimní sporty - Sněžný skútr - Fitness stezky - Inline brusle - Horská cyklistika Původ názvu Název pravé strany Název levé strany @@ -4904,4 +4893,4 @@ Základní Standardní Se službami - \ No newline at end of file + diff --git a/OsmAnd/res/values-de/phrases.xml b/OsmAnd/res/values-de/phrases.xml index c380b1c3d24..ef8eaa990ae 100644 --- a/OsmAnd/res/values-de/phrases.xml +++ b/OsmAnd/res/values-de/phrases.xml @@ -4881,20 +4881,9 @@ Tunnel für Fußgänger verboten Tunnel für Radfahrer verboten Reisepunkte - Wassersport - Gehen - Reiten Reiseartikel Andere Routen Weitere Routenpunkte - Fahrrad - Wandern - Laufen - Wintersport - Schneemobil - Fitness-Parcour - Inlineskates - Mountainbiken Namensetymologie Name der linken Seite Name der rechten Seite @@ -4904,4 +4893,4 @@ Standard Einfach Kletterfels - \ No newline at end of file + diff --git a/OsmAnd/res/values-eo/phrases.xml b/OsmAnd/res/values-eo/phrases.xml index 2152de546ed..20f3c2bb54b 100644 --- a/OsmAnd/res/values-eo/phrases.xml +++ b/OsmAnd/res/values-eo/phrases.xml @@ -4880,17 +4880,6 @@ Vojaĝaj punktoj Aliaj kursoj Aliaj kursoj: punktoj - Biciklado - Marŝado - Kurado - Irado - Akvaj sportoj - Ĉevalrajdado - Vintraj sportoj - Motorsledado - Vojetoj de sportiloj - Rulglitado - Montbiciklado Parco Parco malakceptata Parkster @@ -4904,4 +4893,4 @@ kun necesejoj kaj akvo kun necesejoj, akvo kaj elektro luksa - \ No newline at end of file + diff --git a/OsmAnd/res/values-es-rAR/phrases.xml b/OsmAnd/res/values-es-rAR/phrases.xml index 56a46027054..084f86324ac 100644 --- a/OsmAnd/res/values-es-rAR/phrases.xml +++ b/OsmAnd/res/values-es-rAR/phrases.xml @@ -4884,17 +4884,6 @@ Puntos turísticos Otras rutas Otros puntos de ruta - Bicicleta - Senderismo - Correr - Peatón - Deportes acuáticos - Equitación - Deportes de invierno - Motonieve - Circuitos aeróbicos - Patines en línea - Ciclismo de montaña Nombre etimológico Nombre del lado izquierdo Nombre del lado derecho @@ -4904,4 +4893,4 @@ Con servicios Tipo de campamento De lujo - \ No newline at end of file + diff --git a/OsmAnd/res/values-es-rUS/phrases.xml b/OsmAnd/res/values-es-rUS/phrases.xml index 44dae07f6ea..ad7ef86f7d8 100644 --- a/OsmAnd/res/values-es-rUS/phrases.xml +++ b/OsmAnd/res/values-es-rUS/phrases.xml @@ -4979,15 +4979,4 @@ Puntos turísticos Otras rutas Otros puntos de ruta - Bicicleta - Senderismo - Correr - Peatón - Deportes acuáticos - Equitación - Deportes de invierno - Motonieve - Circuitos aeróbicos - Patines en línea - Ciclismo de montaña - \ No newline at end of file + diff --git a/OsmAnd/res/values-es/phrases.xml b/OsmAnd/res/values-es/phrases.xml index bc9644cadbc..ac8db966179 100644 --- a/OsmAnd/res/values-es/phrases.xml +++ b/OsmAnd/res/values-es/phrases.xml @@ -4880,21 +4880,10 @@ Parco Túnel prohibido para peatones Túnel prohibido para ciclistas - Bicicleta - Senderismo - Correr - Peatonal - Deportes acuáticos - Equitación - Motonieve - Circuitos aeróbicos - Patines en línea - Bicicleta de montaña Artículos de viajes Puntos del viaje Otras rutas Otros puntos de la ruta - Deportes de invierno Nombre en el lado izquierdo Etimología del nombre Nombre en el lado derecho @@ -4904,4 +4893,4 @@ De lujo Tipo de camping Básico - \ No newline at end of file + diff --git a/OsmAnd/res/values-et/phrases.xml b/OsmAnd/res/values-et/phrases.xml index 1a027924ebb..31608e0a329 100644 --- a/OsmAnd/res/values-et/phrases.xml +++ b/OsmAnd/res/values-et/phrases.xml @@ -4872,19 +4872,8 @@ Rattateede võrgustik Kategooria: määramine Kategooria: servituut - Veespordialad - Kõndimine - Jooksmine - Matkamine - Rattasõit Muud teekonnapunktid Muud teekonnad - Talispordialad - Ratsutamine - Rulluisutamine üherealiste uiskudega - Terviserajad - Mootorsaanisõit - Mägirattasõit Parksteri teenus pole kasutatav Parkster Parco teenus pole kasutatav @@ -4897,4 +4886,4 @@ Lisateenustega Standardne Luksuslik - \ No newline at end of file + diff --git a/OsmAnd/res/values-hu/phrases.xml b/OsmAnd/res/values-hu/phrases.xml index fb3fb66137b..5c8ae442586 100644 --- a/OsmAnd/res/values-hu/phrases.xml +++ b/OsmAnd/res/values-hu/phrases.xml @@ -4880,14 +4880,5 @@ Parcót nem fogad el Parkster Parkstert nem fogad el - Vízi sportok - Lovaglás - Téli sportok - Hójáró - Hegyi kerékpározás Egyéb útvonalak - Kerékpározás - Futás - Gyaloglás - Túrázás - \ No newline at end of file + diff --git a/OsmAnd/res/values-is/phrases.xml b/OsmAnd/res/values-is/phrases.xml index 0bbfa51f5e5..0714eac1ab4 100644 --- a/OsmAnd/res/values-is/phrases.xml +++ b/OsmAnd/res/values-is/phrases.xml @@ -4932,24 +4932,13 @@ Flúð Fjársjóðaleit á strönd Túlkunarsvæði - Línuskautar - Fjallahjólreiðar Göng bönnuð fyrir gangandi Göng bönnuð fyrir hjólandi Ferðagreinar Punktar ferðar Aðrar leiðir Punktar í öðrum leiðum - Hjólandi - Gönguferðir - Hlaupandi - Gangandi - Vatnaíþróttir - Hestaferðir - Vetraríþróttir - Vélsleðar - Fitness-leiðir Nafn hægra megin Orðsifjar nafns Nafn vinstra megin - \ No newline at end of file + diff --git a/OsmAnd/res/values-pt-rBR/phrases.xml b/OsmAnd/res/values-pt-rBR/phrases.xml index 6f9c21b7334..81282c24ebe 100644 --- a/OsmAnd/res/values-pt-rBR/phrases.xml +++ b/OsmAnd/res/values-pt-rBR/phrases.xml @@ -4874,21 +4874,11 @@ Depósito de dinheiro Acesso Outras rotas - Corrida Túnel proibido para pedestres Túnel proibido para ciclistas Artigos de viagem Pontos de viagem Outros pontos de rotas - Bicicleta - Caminhada - A pé - Esportes aquáticos - Local de equitação - Esportes de inverno - Moto de neve - Trilhas de exercícios - Patins em linha Parco não aceito Parkster não aceito - \ No newline at end of file + diff --git a/OsmAnd/res/values-sr/phrases.xml b/OsmAnd/res/values-sr/phrases.xml index a7468220dcd..8c644c9f313 100644 --- a/OsmAnd/res/values-sr/phrases.xml +++ b/OsmAnd/res/values-sr/phrases.xml @@ -4884,20 +4884,9 @@ Тунел забрањен за бициклисте Тунел забрањен за пешаке Тачке на путовањима - Планинарење - Спортови на води - Јахање коња - Зимски спортови - Моторне санке - Брдски бициклизам Чланци о путовањима Друге руте Тачке других рута - Бицикл - Пешачење - Трчање - Стазе за трчање - Котураљке Етимологија имена Назив леве стране Назив десне стране @@ -4907,4 +4896,4 @@ Основно Стандардно Делукс - \ No newline at end of file + diff --git a/OsmAnd/res/values-uk/phrases.xml b/OsmAnd/res/values-uk/phrases.xml index d6bbc9af9cf..e19c1ae12b4 100644 --- a/OsmAnd/res/values-uk/phrases.xml +++ b/OsmAnd/res/values-uk/phrases.xml @@ -4881,17 +4881,6 @@ Пункти Інші маршрути Інші пункти маршруту - Велосипед - Піший туризм - Біг - Ходьба - Водні види спорту - Верхова їзда - Зимові види спорту - Снігохід - Фітнес-стежки - Роликові ковзани - Катання на гірських велосипедах Паркомісце не прийнято Паркомісце Паркер не прийнято @@ -4904,4 +4893,4 @@ Делюкс Тип турбази Стандартний - \ No newline at end of file + diff --git a/OsmAnd/res/values-zh-rTW/phrases.xml b/OsmAnd/res/values-zh-rTW/phrases.xml index 38881b1b4e4..89a3ad1e70d 100644 --- a/OsmAnd/res/values-zh-rTW/phrases.xml +++ b/OsmAnd/res/values-zh-rTW/phrases.xml @@ -4882,19 +4882,8 @@ 隧道禁止腳踏車騎士通行 其他路線 其他路線點 - 腳踏車 - 騎馬 - 冬季運動 - 雪地摩托車 - 直排輪鞋 - 山區騎乘 旅遊點 旅遊文章 - 健行 - 跑步 - 步行 - 水上運動 - 健身路徑 名稱詞源學 左側的名稱 右側的名稱 @@ -4904,4 +4893,4 @@ 露營地類型 基本 豪華 - \ No newline at end of file + diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index a2157f05ebb..5b8e7cf788d 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -5454,19 +5454,15 @@ Travel Articles Travel Points - Other routes - Other routes points - Bicycle - Hiking - Running - Walking - Water sports - Horse riding - Winter sports - Snowmobile - Fitness trails - Inline skates - Mountain biking + Route points + Driving + Motorcycling + Foot + Winter sports + Cycling + Air Sports + Water sports + Other routes Name etymology Name of the left side diff --git a/OsmAnd/src/net/osmand/plus/configmap/TravelRoutesFragment.java b/OsmAnd/src/net/osmand/plus/configmap/TravelRoutesFragment.java index 5e21d5813b3..4b306030660 100644 --- a/OsmAnd/src/net/osmand/plus/configmap/TravelRoutesFragment.java +++ b/OsmAnd/src/net/osmand/plus/configmap/TravelRoutesFragment.java @@ -297,7 +297,7 @@ private void setupRouteTypes(ViewGroup container) { updateItemView(tracksView, getString(R.string.display_route_tracks), R.drawable.ic_action_track_16, selected, DescriptionType.VISIBLE_HIDDEN); app.runInUIThread(() -> { - rendererHelper.updateRouteTrackFilter(); + rendererHelper.updateRouteTrackFilters(); rendererHelper.updateRouteTypesVisibility(); app.getOsmandMap().refreshMap(true); app.getOsmandMap().getMapLayers().updateLayers((MapActivity) getMyActivity()); @@ -316,7 +316,7 @@ private void setupRouteTypes(ViewGroup container) { updateItemView(tracksAsPoiView, getString(R.string.display_route_tracks_as_poi), R.drawable.ic_action_info_dark, selected, DescriptionType.VISIBLE_HIDDEN); app.runInUIThread(() -> { - rendererHelper.updateRouteTrackFilter(); + rendererHelper.updateRouteTrackFilters(); rendererHelper.updateRouteTypesVisibility(); app.getOsmandMap().refreshMap(true); app.getOsmandMap().getMapLayers().updateLayers((MapActivity) getMyActivity()); diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java index 6790a7068b3..0ee897ea1be 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java @@ -1,12 +1,14 @@ package net.osmand.plus.mapcontextmenu.controllers; import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE_POINT; +import static net.osmand.osm.MapPoiTypes.ROUTE_TRACK_POINT; import android.graphics.drawable.Drawable; import android.text.TextUtils; import androidx.annotation.NonNull; +import net.osmand.PlatformUtil; import net.osmand.data.Amenity; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; @@ -25,13 +27,18 @@ import net.osmand.plus.transport.TransportStopRoute; import net.osmand.plus.wikipedia.WikipediaDialogFragment; 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.TravelObfHelper; import net.osmand.util.Algorithms; import net.osmand.util.OpeningHoursParser; +import org.apache.commons.logging.Log; + import java.util.List; public class AmenityMenuController extends MenuController { + private static final Log LOG = PlatformUtil.getLog(AmenityMenuController.class); private Amenity amenity; private final MapMarker marker; @@ -69,7 +76,7 @@ public AmenityMenuController(@NonNull MapActivity mapActivity, new MapMarkerMenuController(mapActivity, marker.getPointDescription(mapActivity), marker); leftTitleButtonController = markerMenuController.getLeftTitleButtonController(); rightTitleButtonController = markerMenuController.getRightTitleButtonController(); - } else if (amenity.getSubType().equals(ROUTE_ARTICLE_POINT)) { + } else if (amenity.isRoutePoint()) { TitleButtonController openTrackButtonController = new TitleButtonController() { @Override public void buttonPressed() { @@ -101,11 +108,20 @@ public void buttonPressed() { void openTrack(MapActivity mapActivity) { TravelHelper travelHelper = mapActivity.getMyApplication().getTravelHelper(); - String lang = amenity.getTagSuffix(Amenity.LANG_YES + ":"); - String name = amenity.getTagContent(Amenity.ROUTE_NAME); - TravelArticle article = travelHelper.getArticleByTitle(name, lang, true, null); - if (article != null) { - travelHelper.openTrackMenu(article, mapActivity, name, amenity.getLocation()); + if (ROUTE_ARTICLE_POINT.equals(amenity.getSubType())) { + String lang = amenity.getTagSuffix(Amenity.LANG_YES + ":"); + String name = amenity.getTagContent(Amenity.ROUTE_NAME); + TravelArticle article = travelHelper.getArticleByTitle(name, lang, true, null); + if (article != null) { + travelHelper.openTrackMenu(article, mapActivity, name, amenity.getLocation(), false); + } + } else if (ROUTE_TRACK_POINT.equals(amenity.getSubType())) { + TravelGpx travelGpx = travelHelper.searchGpx(amenity.getLocation(), amenity.getRouteId(), amenity.getRef()); + if (travelGpx != null) { + travelHelper.openTrackMenu(travelGpx, mapActivity, travelGpx.getTitle(), amenity.getLocation(), false); + } else { + LOG.error("openTrack() searchGpx() travelGpx is null"); + } } } diff --git a/OsmAnd/src/net/osmand/plus/poi/PoiUIFilter.java b/OsmAnd/src/net/osmand/plus/poi/PoiUIFilter.java index 133a0fac7c0..ebccc7fbcef 100644 --- a/OsmAnd/src/net/osmand/plus/poi/PoiUIFilter.java +++ b/OsmAnd/src/net/osmand/plus/poi/PoiUIFilter.java @@ -6,7 +6,6 @@ import static net.osmand.osm.MapPoiTypes.ROUTES; import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE; import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE_POINT; -import static net.osmand.osm.MapPoiTypes.ROUTE_TRACK; import static net.osmand.osm.MapPoiTypes.WIKI_PLACE; import android.content.Context; @@ -203,10 +202,6 @@ public boolean isRouteArticlePointFilter() { return filterId.startsWith(STD_PREFIX + ROUTE_ARTICLE_POINT); } - public boolean isRouteTrackFilter() { - return filterId.startsWith(STD_PREFIX + ROUTE_TRACK); - } - public boolean isShowPrivateNeeded() { return filterByName != null && filterByName.contains("access:private"); } diff --git a/OsmAnd/src/net/osmand/plus/render/TravelRendererHelper.java b/OsmAnd/src/net/osmand/plus/render/TravelRendererHelper.java index ea49370d3ad..74cd7cc1776 100644 --- a/OsmAnd/src/net/osmand/plus/render/TravelRendererHelper.java +++ b/OsmAnd/src/net/osmand/plus/render/TravelRendererHelper.java @@ -3,6 +3,7 @@ import static net.osmand.IProgress.EMPTY_PROGRESS; import static net.osmand.IndexConstants.BINARY_TRAVEL_GUIDE_MAP_INDEX_EXT; import static net.osmand.IndexConstants.WIKIVOYAGE_INDEX_DIR; +import static net.osmand.osm.MapPoiTypes.ROUTES_PREFIX; import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE; import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE_POINT; import static net.osmand.osm.MapPoiTypes.ROUTE_TRACK; @@ -20,6 +21,8 @@ import net.osmand.StateChangedListener; import net.osmand.core.android.MapRendererContext; import net.osmand.osm.MapPoiTypes; +import net.osmand.osm.PoiCategory; +import net.osmand.osm.PoiType; import net.osmand.plus.OsmandApplication; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RendererRegistry.IRendererLoadedEventListener; @@ -42,6 +45,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; public class TravelRendererHelper implements IRendererLoadedEventListener { @@ -68,7 +72,7 @@ public class TravelRendererHelper implements IRendererLoadedEventListener { private PoiUIFilter routeArticleFilter; private PoiUIFilter routeArticlePointsFilter; - private PoiUIFilter routeTrackFilter; + private Set routeTrackFilters; public interface OnFileVisibilityChangeListener { void fileVisibilityChanged(); @@ -235,11 +239,11 @@ public PoiUIFilter getRouteArticlePointsFilter() { } @Nullable - public PoiUIFilter getRouteTrackFilter() { - if (routeTrackFilter == null && app.getPoiTypes().isInit()) { - updateRouteTrackFilter(); + public Set getRouteTrackFilters() { + if (routeTrackFilters == null) { + updateRouteTrackFilters(); } - return routeTrackFilter; + return routeTrackFilters; } public void updateRouteArticleFilter() { @@ -249,26 +253,38 @@ public void updateRouteArticleFilter() { } public void updateRouteArticlePointsFilter() { - if (!app.getPoiTypes().isInit()) { - return; - } - PoiUIFilter routeArticlePointsFilter = app.getPoiFilters().getFilterById(PoiUIFilter.STD_PREFIX + ROUTE_ARTICLE_POINT); - if (routeArticlePointsFilter != null) { - Set selectedCategories = new HashSet<>(); - List categories = app.getResourceManager().searchPoiSubTypesByPrefix(MapPoiTypes.CATEGORY); - for (String category : categories) { - CommonPreference prop = getRoutePointCategoryProperty(category); - if (prop.get()) { - selectedCategories.add(category.replace('_', ':').toLowerCase()); + if (app.getPoiTypes().isInit()) { + PoiUIFilter routeArticlePointsFilter = app.getPoiFilters().getFilterById(PoiUIFilter.STD_PREFIX + ROUTE_ARTICLE_POINT); + if (routeArticlePointsFilter != null) { + Set selectedCategories = new HashSet<>(); + List categories = app.getResourceManager().searchPoiSubTypesByPrefix(MapPoiTypes.CATEGORY); + for (String category : categories) { + CommonPreference prop = getRoutePointCategoryProperty(category); + if (prop.get()) { + selectedCategories.add(category.replace('_', ':').toLowerCase()); + } } + routeArticlePointsFilter.setFilterByName(TextUtils.join(" ", selectedCategories)); } - routeArticlePointsFilter.setFilterByName(TextUtils.join(" ", selectedCategories)); + this.routeArticlePointsFilter = routeArticlePointsFilter; } - this.routeArticlePointsFilter = routeArticlePointsFilter; } - public void updateRouteTrackFilter() { - routeTrackFilter = app.getPoiFilters().getFilterById(PoiUIFilter.STD_PREFIX + ROUTE_TRACK); + public void updateRouteTrackFilters() { + if (app.getPoiTypes().isInit()) { + routeTrackFilters = new TreeSet<>(); + PoiCategory routes = app.getPoiTypes().getRoutes(); + for (PoiType subType : routes.getPoiTypes()) { + String subTypeKeyName = subType.getKeyName(); + if (subTypeKeyName.startsWith(ROUTES_PREFIX)) { + routeTrackFilters.add(app.getPoiFilters().getFilterById(PoiUIFilter.STD_PREFIX + subTypeKeyName)); + } + } + PoiUIFilter filter = app.getPoiFilters().getFilterById(PoiUIFilter.STD_PREFIX + ROUTE_TRACK); + if (filter != null) { + routeTrackFilters.add(filter); + } + } } public boolean updateRouteTypeVisibility(RenderingRulesStorage storage, String name, boolean selected) { diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java index 9b33f16ed5f..cff31fc2397 100644 --- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java +++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java @@ -1571,7 +1571,7 @@ public Map getBackupIndexes(Map map) { if (lf != null) { for (File f : lf) { if (f != null && f.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) { - map.put(f.getName(), AndroidUtils.formatDate(context, f.lastModified())); + map.put(f.getName(), getDateFormat().format(f.lastModified())); } } } diff --git a/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchListFragment.java b/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchListFragment.java index 07e5509261d..b0314566fcf 100644 --- a/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchListFragment.java +++ b/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchListFragment.java @@ -16,7 +16,12 @@ import androidx.fragment.app.FragmentActivity; import net.osmand.IndexConstants; +import net.osmand.PlatformUtil; +import net.osmand.data.Amenity; import net.osmand.data.PointDescription; +import net.osmand.plus.wikivoyage.data.TravelGpx; +import net.osmand.plus.wikivoyage.data.TravelHelper; +import net.osmand.plus.wikivoyage.data.TravelObfHelper; import net.osmand.shared.gpx.GpxFile; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; @@ -43,11 +48,14 @@ import net.osmand.search.core.SearchResult; import net.osmand.util.Algorithms; +import org.apache.commons.logging.Log; + import java.io.File; import java.util.ArrayList; import java.util.List; public abstract class QuickSearchListFragment extends OsmAndListFragment { + private static final Log LOG = PlatformUtil.getLog(QuickSearchListFragment.class); protected OsmandApplication app; private QuickSearchDialogFragment dialogFragment; @@ -175,27 +183,46 @@ public boolean isShowResult() { public void showResult(SearchResult searchResult) { showResult = false; if (searchResult.objectType == ObjectType.GPX_TRACK) { - GPXInfo gpxInfo = (GPXInfo) searchResult.relatedObject; - if (dialogFragment.getSearchType().isTargetPoint()) { - File file = gpxInfo.getFile(); - if (file != null) { - selectTrack(file); - } - } else { - showTrackMenuFragment(gpxInfo); - } + showGpxTrackResult(searchResult); } else if (searchResult.location != null) { - Pair pair = QuickSearchListItem.getPointDescriptionObject(app, searchResult); + showResultWithLocation(searchResult); + } + } - dialogFragment.hideToolbar(); - dialogFragment.hide(); + private void showResultWithLocation(SearchResult searchResult) { + Pair pair = QuickSearchListItem.getPointDescriptionObject(app, searchResult); + dialogFragment.hideToolbar(); + dialogFragment.hide(); + + if (pair.second instanceof Amenity && ((Amenity) pair.second).isRouteTrack()) { + Amenity amenity = (Amenity) pair.second; + TravelHelper travelHelper = app.getTravelHelper(); + TravelGpx travelGpx = travelHelper.searchGpx(amenity.getLocation(), amenity.getRouteId(), amenity.getRef()); + if (travelGpx != null) { + travelHelper.openTrackMenu(travelGpx, getMapActivity(), amenity.getGpxFileName(null), amenity.getLocation(), true); + } else { + LOG.error("showResultWithLocation() searchGpx() travelGpx is null"); + } + } else { showOnMap(getMapActivity(), dialogFragment, searchResult.location.getLatitude(), searchResult.location.getLongitude(), searchResult.preferredZoom, pair.first, pair.second); } } + private void showGpxTrackResult(SearchResult searchResult) { + GPXInfo gpxInfo = (GPXInfo) searchResult.relatedObject; + if (dialogFragment.getSearchType().isTargetPoint()) { + File file = gpxInfo.getFile(); + if (file != null) { + selectTrack(file); + } + } else { + showTrackMenuFragment(gpxInfo); + } + } + private void selectTrack(@NonNull File file) { SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(file.getAbsolutePath()); if (selectedGpxFile != null) { diff --git a/OsmAnd/src/net/osmand/plus/track/helpers/GpxUiHelper.java b/OsmAnd/src/net/osmand/plus/track/helpers/GpxUiHelper.java index 12877fab62b..30b7ac44cbf 100644 --- a/OsmAnd/src/net/osmand/plus/track/helpers/GpxUiHelper.java +++ b/OsmAnd/src/net/osmand/plus/track/helpers/GpxUiHelper.java @@ -629,7 +629,8 @@ public static void saveAndOpenGpx(@NonNull MapActivity mapActivity, @NonNull GpxFile gpxFile, @NonNull WptPt selectedPoint, @Nullable GpxTrackAnalysis analyses, - @Nullable RouteKey routeKey) { + @Nullable RouteKey routeKey, + boolean adjustMapPosition) { SaveGpxHelper.saveGpx(file, gpxFile, errorMessage -> { if (errorMessage == null) { OsmandApplication app = mapActivity.getMyApplication(); @@ -638,7 +639,7 @@ public static void saveAndOpenGpx(@NonNull MapActivity mapActivity, GpxTrackAnalysis trackAnalysis = analyses != null ? analyses : selectedGpxFile.getTrackAnalysis(app); SelectedGpxPoint selectedGpxPoint = new SelectedGpxPoint(selectedGpxFile, selectedPoint); Bundle bundle = new Bundle(); - bundle.putBoolean(TrackMenuFragment.ADJUST_MAP_POSITION, false); + bundle.putBoolean(TrackMenuFragment.ADJUST_MAP_POSITION, adjustMapPosition); TrackMenuFragment.showInstance(mapActivity, selectedGpxFile, selectedGpxPoint, trackAnalysis, routeKey, bundle); } else { diff --git a/OsmAnd/src/net/osmand/plus/views/layers/MapSelectionHelper.java b/OsmAnd/src/net/osmand/plus/views/layers/MapSelectionHelper.java index 0e0f17e2e1a..a8c94dcc5e6 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/MapSelectionHelper.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/MapSelectionHelper.java @@ -2,10 +2,13 @@ import static net.osmand.IndexConstants.GPX_FILE_EXT; import static net.osmand.binary.BinaryMapIndexReader.ACCEPT_ALL_POI_TYPE_FILTER; +import static net.osmand.data.Amenity.ROUTE; +import static net.osmand.data.Amenity.ROUTE_ID; import static net.osmand.data.FavouritePoint.DEFAULT_BACKGROUND_TYPE; import static net.osmand.data.MapObject.AMENITY_ID_RIGHT_SHIFT; import static net.osmand.osm.OsmRouteType.HIKING; import static net.osmand.plus.transport.TransportLinesMenu.RENDERING_CATEGORY_TRANSPORT; +import static net.osmand.plus.wikivoyage.data.TravelGpx.TRAVEL_MAP_TO_POI_TAG; import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_HIDDEN; import static net.osmand.router.network.NetworkRouteSelector.NetworkRouteSelectorFilter; import static net.osmand.router.network.NetworkRouteSelector.RouteKey; @@ -216,7 +219,7 @@ private void selectObjectsFromNative(@NonNull MapSelectionResult result, @NonNul if (renderedObjects != null) { double cosRotateTileSize = Math.cos(Math.toRadians(rc.rotate)) * TILE_SIZE; double sinRotateTileSize = Math.sin(Math.toRadians(rc.rotate)) * TILE_SIZE; - boolean selectedRoutes = false; + boolean isRouteGpxSelected = false; for (RenderedObject renderedObject : renderedObjects) { String routeID = renderedObject.getRouteID(); String fileName = renderedObject.getGpxFileName(); @@ -253,14 +256,14 @@ private void selectObjectsFromNative(@NonNull MapSelectionResult result, @NonNul result.objectLatLon = renderedObject.getLabelLatLon(); } LatLon searchLatLon = result.objectLatLon != null ? result.objectLatLon : result.pointLatLon; - if (isTravelGpx) { - addTravelGpx(result, renderedObject, filter); + if (isTravelGpx && !isRouteGpxSelected) { + isRouteGpxSelected = addTravelGpx(result, filter, renderedObject.getTagValue("ref")); } else { - if (isRoute && !selectedRoutes) { - selectedRoutes = true; + if (isRoute && !isRouteGpxSelected) { NetworkRouteSelectorFilter routeFilter = createRouteFilter(); if (!Algorithms.isEmpty(routeFilter.typeFilter)) { - addRoute(result, tileBox, point, routeFilter); + addOsmRoute(result, tileBox, point, routeFilter); + isRouteGpxSelected = true; } } boolean amenityAdded = addAmenity(result, renderedObject, searchLatLon); @@ -279,7 +282,7 @@ private void selectObjectsFromOpenGl(@NonNull MapSelectionResult result, @NonNul int delta = 20; PointI tl = new PointI((int) point.x - delta, (int) point.y - delta); PointI br = new PointI((int) point.x + delta, (int) point.y + delta); - boolean selectedRoutes = false; + boolean isRouteGpxSelected = false; MapSymbolInformationList symbols = rendererView.getSymbolsIn(new AreaI(tl, br), false); for (int i = 0; i < symbols.size(); i++) { MapSymbolInformation symbolInfo = symbols.get(i); @@ -336,14 +339,18 @@ private void selectObjectsFromOpenGl(@NonNull MapSelectionResult result, @NonNul } if (obfMapObject != null) { Map tags = getTags(obfMapObject.getResolvedAttributes()); - boolean isRoute = !Algorithms.isEmpty(OsmRouteType.getRouteKeys(tags)); - if (isRoute && !selectedRoutes) { - selectedRoutes = true; + boolean isOsmRoute = !Algorithms.isEmpty(OsmRouteType.getRouteKeys(tags)); + if (isOsmRoute && !isRouteGpxSelected) { NetworkRouteSelectorFilter routeFilter = createRouteFilter(); if (!Algorithms.isEmpty(routeFilter.typeFilter)) { - addRoute(result, tileBox, point, routeFilter); + addOsmRoute(result, tileBox, point, routeFilter); + isRouteGpxSelected = true; } } + boolean isTravelGpx = app.getTravelHelper().isTravelGpxTags(tags); + if (isTravelGpx && !isRouteGpxSelected) { + isRouteGpxSelected = addTravelGpx(result, tags.get(ROUTE_ID), null); + } IOnPathMapSymbol onPathMapSymbol = getOnPathMapSymbol(symbolInfo); if (onPathMapSymbol == null) { LatLon latLon = result.objectLatLon; @@ -352,10 +359,10 @@ private void selectObjectsFromOpenGl(@NonNull MapSelectionResult result, @NonNul latLon = l == null ? latLon : l; tags.remove(TAG_POI_LAT_LON); } - amenity = getAmenity(latLon, obfMapObject); + amenity = getAmenity(latLon, obfMapObject, tags); if (amenity != null) { amenity.setMapIconName(getMapIconName(symbolInfo)); - } else if (!isRoute) { + } else if (!isOsmRoute && !isTravelGpx) { addRenderedObject(result, symbolInfo, obfMapObject, tags); } } @@ -433,13 +440,16 @@ private RasterMapSymbol getRasterMapSymbol(@NonNull MapSymbolInformation symbolI return null; } - private Amenity getAmenity(LatLon latLon, ObfMapObject obfMapObject) { + private Amenity getAmenity(LatLon latLon, ObfMapObject obfMapObject, Map tags) { Amenity amenity; List names = getValues(obfMapObject.getCaptionsInAllLanguages()); String caption = obfMapObject.getCaptionInNativeLanguage(); if (!caption.isEmpty()) { names.add(caption); } + if (!Algorithms.isEmpty(tags) && tags.containsKey(TRAVEL_MAP_TO_POI_TAG) && "point".equals(tags.get(ROUTE))) { + names.add(tags.get(TRAVEL_MAP_TO_POI_TAG)); // additional attribute for TravelGpx points (route_id) + } long id = obfMapObject.getId().getId().longValue(); amenity = findAmenity(app, latLon, names, id); if (amenity != null && obfMapObject.getPoints31().size() > 1) { @@ -452,24 +462,24 @@ private Amenity getAmenity(LatLon latLon, ObfMapObject obfMapObject) { return amenity; } - private void addTravelGpx(@NonNull MapSelectionResult result, @NonNull RenderedObject object, @Nullable String filter) { - TravelGpx travelGpx = app.getTravelHelper().searchGpx(result.pointLatLon, filter, object.getTagValue("ref")); + private boolean addTravelGpx(@NonNull MapSelectionResult result, @Nullable String routeId, @Nullable String ref) { + TravelGpx travelGpx = app.getTravelHelper().searchGpx(result.pointLatLon, routeId, ref); if (travelGpx != null && isUniqueGpx(result.selectedObjects, travelGpx)) { WptPt selectedPoint = new WptPt(); selectedPoint.setLat(result.pointLatLon.getLatitude()); selectedPoint.setLon(result.pointLatLon.getLongitude()); SelectedGpxPoint selectedGpxPoint = new SelectedGpxPoint(null, selectedPoint); result.selectedObjects.put(new Pair<>(travelGpx, selectedGpxPoint), mapLayers.getTravelSelectionLayer()); + return true; + } else if (travelGpx == null) { + log.error("addTravelGpx() searchGpx() travelGpx is null"); } + return false; } private boolean isUniqueGpx(@NonNull Map selectedObjects, @NonNull TravelGpx travelGpx) { - String tracksDir = app.getAppPath(IndexConstants.GPX_TRAVEL_DIR).getPath(); - File file = new File(tracksDir, travelGpx.getRouteId() + GPX_FILE_EXT); - if (file.exists()) { - return false; - } + String travelGpxFileName = travelGpx.getGpxFileName() + GPX_FILE_EXT; for (Map.Entry entry : selectedObjects.entrySet()) { if (entry.getKey() instanceof Pair && entry.getValue() instanceof GPXLayer && ((Pair) entry.getKey()).first instanceof TravelGpx) { @@ -478,12 +488,18 @@ private boolean isUniqueGpx(@NonNull Map selectedO return false; } } + if (entry.getKey() instanceof SelectedGpxPoint && entry.getValue() instanceof GPXLayer) { + SelectedGpxPoint selectedGpxPoint = (SelectedGpxPoint) entry.getKey(); + if (selectedGpxPoint.getSelectedGpxFile().getGpxFile().getPath().endsWith(travelGpxFileName)) { + return false; + } + } } return true; } - private void addRoute(@NonNull MapSelectionResult result, @NonNull RotatedTileBox tileBox, @NonNull PointF point, - @NonNull NetworkRouteSelectorFilter selectorFilter) { + private void addOsmRoute(@NonNull MapSelectionResult result, @NonNull RotatedTileBox tileBox, @NonNull PointF point, + @NonNull NetworkRouteSelectorFilter selectorFilter) { int searchRadius = (int) (OsmandMapLayer.getScaledTouchRadius(app, tileBox.getDefaultRadiusPoi()) * 1.5f); LatLon minLatLon = NativeUtilities.getLatLonFromElevatedPixel(view.getMapRenderer(), tileBox, point.x - searchRadius, point.y - searchRadius); @@ -703,13 +719,22 @@ public static Amenity findAmenityByOsmId(@NonNull List amenities, long @Nullable public static Amenity findAmenityByName(@NonNull List amenities, @Nullable List names) { if (!Algorithms.isEmpty(names)) { - for (Amenity amenity : amenities) { - for (String name : names) { - if (name.equals(amenity.getName()) && !amenity.isClosed()) { - return amenity; - } - } - } + return amenities.stream() + .filter(amenity -> !amenity.isClosed()) + .filter(amenity -> names.contains(amenity.getName())) + .findAny() + .orElseGet(() -> + amenities.stream() + .filter(amenity -> !amenity.isClosed()) + .filter(amenity -> amenity.isRoutePoint()) + .filter(amenity -> amenity.getName().isEmpty()) + .filter(amenity -> { + String travelRouteId = amenity.getAdditionalInfo(TRAVEL_MAP_TO_POI_TAG); + return travelRouteId != null && names.contains(travelRouteId); + }) + .findAny() + .orElse(null) + ); } return null; } diff --git a/OsmAnd/src/net/osmand/plus/views/layers/NetworkRouteSelectionLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/NetworkRouteSelectionLayer.java index be9503aa55d..5d7959ef482 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/NetworkRouteSelectionLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/NetworkRouteSelectionLayer.java @@ -154,7 +154,7 @@ private void saveAndOpenGpx(@NonNull GpxFile gpxFile, @NonNull Pair routeTrackFilters; private String routeArticlePointsFilterByName; private boolean fileVisibilityChanged; public CustomMapObjects customObjectsDelegate; @@ -119,7 +120,7 @@ public POIMapLayer(@NonNull Context context) { routeArticlePointsFilterEnabled = travelRendererHelper.getRouteArticlePointsProperty().get(); routeArticleFilter = travelRendererHelper.getRouteArticleFilter(); routeArticlePointsFilter = travelRendererHelper.getRouteArticlePointsFilter(); - routeTrackFilter = travelRendererHelper.getRouteTrackFilter(); + routeTrackFilters = travelRendererHelper.getRouteTrackFilters(); routeArticlePointsFilterByName = routeArticlePointsFilter != null ? routeArticlePointsFilter.getFilterByName() : null; routingHelper.addListener(this); @@ -158,6 +159,7 @@ protected List calculateResult(@NonNull QuadRect latLonBounds, int zoom int z = (int) Math.floor(zoom + Math.log(getMapDensity()) / Math.log(2)); List res = new ArrayList<>(); + Set uniqueRouteIds = new HashSet<>(); PoiFilterUtils.combineStandardPoiFilters(calculatedFilters, app); for (PoiUIFilter filter : calculatedFilters) { List amenities = filter.searchAmenities(latLonBounds.top, latLonBounds.left, @@ -173,24 +175,14 @@ public boolean isCancelled() { return isInterrupted(); } }); - if (filter.isRouteTrackFilter()) { - for (Amenity amenity : amenities) { - boolean hasRoute = false; + for (Amenity amenity : amenities) { + if (amenity.isRouteTrack()) { String routeId = amenity.getRouteId(); - if (!Algorithms.isEmpty(routeId)) { - for (Amenity a : res) { - if (routeId.equals(a.getRouteId())) { - hasRoute = true; - break; - } - } - } - if (!hasRoute) { - res.add(amenity); + if (routeId != null && !uniqueRouteIds.add(routeId)) { + continue; // duplicate } } - } else { - res.addAll(amenities); + res.add(amenity); } } @@ -224,9 +216,8 @@ private Set collectFilters() { calculatedFilters.add(routeArticlePointsFilter); } boolean routeTrackAsPoiFilterEnabled = this.routeTrackAsPoiFilterEnabled; - PoiUIFilter routeTrackFilter = this.routeTrackFilter; - if (routeTrackAsPoiFilterEnabled && routeTrackFilter != null) { - calculatedFilters.add(routeTrackFilter); + if (routeTrackAsPoiFilterEnabled && this.routeTrackFilters != null) { + calculatedFilters.addAll(this.routeTrackFilters); } } return calculatedFilters; @@ -307,7 +298,7 @@ private boolean shouldDraw(int zoom) { && zoom >= START_ZOOM) { return true; } - if (travelRendererHelper.getRouteTracksAsPoiProperty().get() && routeTrackFilter != null) { + if (travelRendererHelper.getRouteTracksAsPoiProperty().get() && routeTrackFilters != null) { return travelRendererHelper.getRouteTracksProperty().get() ? zoom >= START_ZOOM : zoom >= START_ZOOM_ROUTE_TRACK; } @@ -321,7 +312,7 @@ private boolean shouldDraw(@NonNull RotatedTileBox tileBox, @NonNull Amenity ame } else { boolean routeArticle = ROUTE_ARTICLE_POINT.equals(amenity.getSubType()) || ROUTE_ARTICLE.equals(amenity.getSubType()); - boolean routeTrack = ROUTE_TRACK.equals(amenity.getSubType()); + boolean routeTrack = amenity.isRouteTrack(); if (routeArticle) { return tileBox.getZoom() >= START_ZOOM; } else if (routeTrack) { @@ -352,7 +343,7 @@ public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSett boolean routeTrackAsPoiFilterEnabled = travelRendererHelper.getRouteTracksAsPoiProperty().get(); PoiUIFilter routeArticleFilter = travelRendererHelper.getRouteArticleFilter(); PoiUIFilter routeArticlePointsFilter = travelRendererHelper.getRouteArticlePointsFilter(); - PoiUIFilter routeTrackFilter = travelRendererHelper.getRouteTrackFilter(); + Set routeTrackFilters = travelRendererHelper.getRouteTrackFilters(); String routeArticlePointsFilterByName = routeArticlePointsFilter != null ? routeArticlePointsFilter.getFilterByName() : null; boolean dataChanged = false; if (this.filters != selectedPoiFilters @@ -363,7 +354,7 @@ public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSett || this.routeTrackAsPoiFilterEnabled != routeTrackAsPoiFilterEnabled || this.routeArticleFilter != routeArticleFilter || this.routeArticlePointsFilter != routeArticlePointsFilter - || this.routeTrackFilter != routeTrackFilter + || this.routeTrackFilters != routeTrackFilters || this.fileVisibilityChanged || !Algorithms.stringsEqual(this.routeArticlePointsFilterByName, routeArticlePointsFilterByName)) { this.filters = selectedPoiFilters; @@ -374,7 +365,7 @@ public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSett this.routeTrackAsPoiFilterEnabled = routeTrackAsPoiFilterEnabled; this.routeArticleFilter = routeArticleFilter; this.routeArticlePointsFilter = routeArticlePointsFilter; - this.routeTrackFilter = routeTrackFilter; + this.routeTrackFilters = routeTrackFilters; this.routeArticlePointsFilterByName = routeArticlePointsFilterByName; this.fileVisibilityChanged = false; data.clearCache(); @@ -602,26 +593,30 @@ public LatLon getObjectLocation(Object o) { public boolean showMenuAction(@Nullable Object object) { OsmandApplication app = view.getApplication(); MapActivity mapActivity = view.getMapActivity(); - TravelHelper travelHelper = app.getTravelHelper(); if (mapActivity != null && object instanceof Amenity) { + TravelHelper travelHelper = app.getTravelHelper(); Amenity amenity = (Amenity) object; - if (amenity.getSubType().equals(ROUTE_TRACK)) { - TravelGpx travelGpx = travelHelper.searchGpx(amenity.getLocation(), amenity.getRouteId(), amenity.getRef()); - if (travelGpx == null) { + String subType = amenity.getSubType(); + if (amenity.getType().getKeyName().equals(ROUTES)) { + if (subType.equals(ROUTE_ARTICLE)) { + String lang = app.getLanguage(); + lang = amenity.getContentLanguage(Amenity.DESCRIPTION, lang, "en"); + String name = amenity.getGpxFileName(lang); + TravelArticle article = travelHelper.getArticleByTitle(name, lang, true, null); + if (article == null) { + return true; + } + travelHelper.openTrackMenu(article, mapActivity, name, amenity.getLocation(), false); return true; - } - travelHelper.openTrackMenu(travelGpx, mapActivity, amenity.getRouteId(), amenity.getLocation()); - return true; - } else if (amenity.getSubType().equals(ROUTE_ARTICLE)) { - String lang = app.getLanguage(); - lang = amenity.getContentLanguage(Amenity.DESCRIPTION, lang, "en"); - String name = amenity.getName(lang); - TravelArticle article = travelHelper.getArticleByTitle(name, lang, true, null); - if (article == null) { + } else if (amenity.isRouteTrack()) { + TravelGpx travelGpx = travelHelper.searchGpx(amenity.getLocation(), amenity.getRouteId(), amenity.getRef()); + if (travelGpx != null) { + travelHelper.openTrackMenu(travelGpx, mapActivity, amenity.getGpxFileName(null), amenity.getLocation(), false); + } else { + log.error("showMenuAction() searchGpx() travelGpx is null"); + } return true; } - travelHelper.openTrackMenu(article, mapActivity, name, amenity.getLocation()); - return true; } } return false; diff --git a/OsmAnd/src/net/osmand/plus/views/layers/TravelSelectionLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/TravelSelectionLayer.java index c0b92cbd4ea..899aef2bc5a 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/TravelSelectionLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/TravelSelectionLayer.java @@ -56,8 +56,13 @@ public PointDescription getObjectName(Object o) { Pair pair = (Pair) o; if (pair.first instanceof TravelGpx && pair.second instanceof SelectedGpxPoint) { TravelGpx travelGpx = (TravelGpx) ((Pair) o).first; - String name = Algorithms.isEmpty(travelGpx.getDescription()) ? travelGpx.getTitle() : travelGpx.getDescription(); - return new PointDescription(PointDescription.POINT_TYPE_GPX, name); + if (!Algorithms.isEmpty(travelGpx.getTitle())) { + return new PointDescription(PointDescription.POINT_TYPE_GPX, travelGpx.getTitle()); + } else if (!Algorithms.isEmpty(travelGpx.getDescription())) { + return new PointDescription(PointDescription.POINT_TYPE_GPX, travelGpx.getDescription()); + } else { + return new PointDescription(PointDescription.POINT_TYPE_GPX, travelGpx.getRouteId()); // nullable + } } } return null; @@ -75,7 +80,8 @@ public boolean showMenuAction(@Nullable Object object) { WptPt wptPt = selectedGpxPoint.getSelectedPoint(); TravelHelper travelHelper = app.getTravelHelper(); - travelHelper.openTrackMenu(travelGpx, mapActivity, travelGpx.getRouteId(), new LatLon(wptPt.getLat(), wptPt.getLon())); + travelHelper.openTrackMenu(travelGpx, mapActivity, travelGpx.getGpxFileName(), + new LatLon(wptPt.getLat(), wptPt.getLon()), false); return true; } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java index 0272ad2f932..218a591c8a3 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java @@ -127,6 +127,27 @@ public String getRouteId() { return routeId; } + public boolean hasOsmRouteId() { + String routeId = getRouteId(); + return routeId != null && routeId.startsWith(Amenity.ROUTE_ID_OSM_PREFIX); + } + + @NonNull + public String getGpxFileName() { + String gpxFileName = !Algorithms.isEmpty(title) ? title : routeId; + if (gpxFileName != null) { + return gpxFileName + .replace('/', '_') + .replace('\'', '_') + .replace('\"', '_') + .replace('\r', '_') + .replace('\n', '_'); + } else { + LOG.error("Empty travel article in " + this.file); + return "Travel Article File"; // @NonNull + } + } + public String getRouteSource() { return routeSource; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java index 6d4d4d40df7..efd1655a980 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java @@ -1,5 +1,7 @@ package net.osmand.plus.wikivoyage.data; +import static net.osmand.IndexConstants.GPX_FILE_EXT; + import android.text.TextUtils; import androidx.annotation.NonNull; @@ -203,7 +205,6 @@ public void saveOrRemoveArticle(@NonNull TravelArticle article, boolean save) { } } - public List getExistingTravelBooks() { return existingTravelBooks; } @@ -713,6 +714,11 @@ public String formatTravelBookName(File tb) { return nm.substring(0, nm.indexOf('.')).replace('_', ' '); } + @Override + public boolean isTravelGpxTags(@NonNull Map tags) { + return false; // stub + } + @Nullable @Override public TravelGpx searchGpx(@NonNull LatLon location, @Nullable String fileName, @Nullable String ref) { @@ -721,15 +727,13 @@ public TravelGpx searchGpx(@NonNull LatLon location, @Nullable String fileName, @Override public void openTrackMenu(@NonNull TravelArticle article, @NonNull MapActivity mapActivity, - @NonNull String gpxFileName, @NonNull LatLon location) { - + @NonNull String gpxFileName, @NonNull LatLon location, boolean adjustMapPosition) { } @NonNull @Override public String getGPXName(@NonNull TravelArticle article) { - return article.getTitle().replace('/', '_').replace('\'', '_') - .replace('\"', '_') + IndexConstants.GPX_FILE_EXT; + return article.getGpxFileName() + GPX_FILE_EXT; } @NonNull diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java index a724a764404..cbf22a3cd15 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java @@ -14,8 +14,7 @@ import net.osmand.data.Amenity; import net.osmand.util.Algorithms; -import java.util.HashMap; -import java.util.Map; +import java.util.Set; import net.osmand.shared.gpx.GpxTrackAnalysis; @@ -27,9 +26,12 @@ public class TravelGpx extends TravelArticle { public static final String MAX_ELEVATION = "max_ele"; public static final String MIN_ELEVATION = "min_ele"; public static final String AVERAGE_ELEVATION = "avg_ele"; - public static final String ROUTE_RADIUS = "route_radius"; + public static final String START_ELEVATION = "start_ele"; + public static final String ELE_GRAPH = "ele_graph"; + public static final String ROUTE_BBOX_RADIUS = "route_bbox_radius"; public static final String USER = "user"; public static final String ACTIVITY_TYPE = "route_activity_type"; + public static final String TRAVEL_MAP_TO_POI_TAG = "route_id"; public String user; public String activityType; @@ -62,6 +64,8 @@ public GpxTrackAnalysis getAnalysis() { return analysis; } + private static final Set doNotSaveWptTags = Set.of("route_id", "route_name"); + @NonNull @Override public WptPt createWptPt(@NonNull Amenity amenity, @Nullable String lang) { @@ -72,26 +76,24 @@ public WptPt createWptPt(@NonNull Amenity amenity, @Nullable String lang) { for (String obfTag : amenity.getAdditionalInfoKeys()) { String value = amenity.getAdditionalInfo(obfTag); if (!Algorithms.isEmpty(value)) { - String gpxTag = allowedPointObfToGpxTags.get(obfTag); - if (gpxTag != null) { - wptPt.getExtensionsToWrite().put(gpxTag, value); - } if (OBF_POINTS_GROUPS_CATEGORY.equals(obfTag)) { wptPt.setCategory(value); + } else if ("name".equals(obfTag)) { + wptPt.setName(value); + } else if ("description".equals(obfTag)) { + wptPt.setDesc(value); + } else if ("note".equals(obfTag)) { + wptPt.setComment(value); + } else if ("colour".equals(obfTag) && amenity.getAdditionalInfoKeys().contains("color")) { + // ignore "colour" if "color" exists + } else if (!doNotSaveWptTags.contains(obfTag)) { + wptPt.getExtensionsToWrite().put(obfTag, value); } } } return wptPt; } - private final static Map allowedPointObfToGpxTags = new HashMap<>(); - - static { - allowedPointObfToGpxTags.put("color", "color"); - allowedPointObfToGpxTags.put("gpx_icon", "icon"); - allowedPointObfToGpxTags.put("gpx_bg", "background"); - } - @NonNull @Override public String getPointFilterString() { @@ -101,6 +103,6 @@ public String getPointFilterString() { @NonNull @Override public String getMainFilterString() { - return ROUTE_TRACK; + return ROUTE_TRACK; // considered together with ROUTES_PREFIX } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java index 91ab8552c18..7c7c948ce4c 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java @@ -61,11 +61,13 @@ interface GpxReadCallback { @NonNull ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articleId); + boolean isTravelGpxTags(@NonNull Map tags); + @Nullable TravelGpx searchGpx(@NonNull LatLon location, @Nullable String fileName, @Nullable String ref); void openTrackMenu(@NonNull TravelArticle article, @NonNull MapActivity mapActivity, - @NonNull String gpxFileName, @NonNull LatLon location); + @NonNull String gpxFileName, @NonNull LatLon location, boolean adjustMapPosition); @NonNull String getGPXName(@NonNull TravelArticle article); @@ -80,5 +82,4 @@ void openTrackMenu(@NonNull TravelArticle article, @NonNull MapActivity mapActiv String getWikivoyageFileName(); void saveOrRemoveArticle(@NonNull TravelArticle article, boolean save); - } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java index ded6432c219..4a44ea6b73b 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java @@ -2,7 +2,9 @@ import static net.osmand.IndexConstants.GPX_FILE_EXT; import static net.osmand.data.Amenity.REF; +import static net.osmand.data.Amenity.ROUTE; import static net.osmand.data.Amenity.ROUTE_ID; +import static net.osmand.osm.MapPoiTypes.ROUTES_PREFIX; import static net.osmand.osm.MapPoiTypes.ROUTE_ARTICLE; import static net.osmand.osm.MapPoiTypes.ROUTE_TRACK; import static net.osmand.osm.MapPoiTypes.ROUTE_TRACK_POINT; @@ -12,13 +14,16 @@ import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELEVATION_DOWN; import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELEVATION_UP; import static net.osmand.plus.wikivoyage.data.TravelGpx.DISTANCE; +import static net.osmand.plus.wikivoyage.data.TravelGpx.ELE_GRAPH; import static net.osmand.plus.wikivoyage.data.TravelGpx.MAX_ELEVATION; import static net.osmand.plus.wikivoyage.data.TravelGpx.MIN_ELEVATION; -import static net.osmand.plus.wikivoyage.data.TravelGpx.ROUTE_RADIUS; +import static net.osmand.plus.wikivoyage.data.TravelGpx.ROUTE_BBOX_RADIUS; +import static net.osmand.plus.wikivoyage.data.TravelGpx.START_ELEVATION; import static net.osmand.plus.wikivoyage.data.TravelGpx.USER; import static net.osmand.shared.gpx.GpxUtilities.PointsGroup.OBF_POINTS_GROUPS_BACKGROUNDS; import static net.osmand.shared.gpx.GpxUtilities.PointsGroup.OBF_POINTS_GROUPS_COLORS; import static net.osmand.shared.gpx.GpxUtilities.PointsGroup.OBF_POINTS_GROUPS_DELIMITER; +import static net.osmand.shared.gpx.GpxUtilities.PointsGroup.OBF_POINTS_GROUPS_EMPTY_NAME_STUB; import static net.osmand.shared.gpx.GpxUtilities.PointsGroup.OBF_POINTS_GROUPS_ICONS; import static net.osmand.shared.gpx.GpxUtilities.PointsGroup.OBF_POINTS_GROUPS_NAMES; import static net.osmand.shared.gpx.GpxUtilities.PointsGroup.OBF_POINTS_GROUPS_PREFIX; @@ -26,8 +31,6 @@ import static net.osmand.shared.gpx.GpxUtilities.TRAVEL_GPX_CONVERT_FIRST_LETTER; import static net.osmand.shared.gpx.GpxUtilities.TRAVEL_GPX_CONVERT_MULT_1; import static net.osmand.shared.gpx.GpxUtilities.TRAVEL_GPX_CONVERT_MULT_2; -import static net.osmand.shared.gpx.primitives.GpxExtensions.OBF_GPX_EXTENSION_TAG_PREFIX; -import static net.osmand.util.Algorithms.capitalizeFirstLetter; import android.os.AsyncTask; import android.text.TextUtils; @@ -42,6 +45,7 @@ import net.osmand.OsmAndCollator; import net.osmand.PlatformUtil; import net.osmand.ResultMatcher; +import net.osmand.plus.Version; import net.osmand.plus.shared.SharedUtil; import net.osmand.binary.BinaryMapDataObject; import net.osmand.binary.BinaryMapIndexReader; @@ -62,8 +66,9 @@ import net.osmand.search.core.SearchPhrase.NameStringMatcher; import net.osmand.search.core.SearchSettings; import net.osmand.shared.gpx.GpxFile; -import net.osmand.shared.gpx.GpxHelper; import net.osmand.shared.gpx.GpxUtilities; +import net.osmand.shared.gpx.RouteActivityHelper; +import net.osmand.shared.gpx.primitives.RouteActivity; import net.osmand.shared.gpx.primitives.Track; import net.osmand.shared.gpx.primitives.TrkSegment; import net.osmand.shared.gpx.primitives.WptPt; @@ -88,7 +93,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import gnu.trove.set.TLongSet; @@ -113,6 +120,12 @@ public class TravelObfHelper implements TravelHelper { private final List> foundAmenities = new ArrayList<>(); public volatile int requestNumber = 0; + // Do not clutter GPX with tags that are always generated. + private static final Set doNotSaveAmenityGpxTags = Set.of( + "date", "distance", "route_name", "route_bbox_radius", + "avg_ele", "min_ele", "max_ele", "start_ele", "ele_graph", "diff_ele_up", "diff_ele_down", + "avg_speed", "min_speed", "max_speed", "time_moving", "time_moving_no_gaps", "time_span", "time_span_no_gaps" + ); public TravelObfHelper(OsmandApplication app) { this.app = app; @@ -206,8 +219,15 @@ public int compare(Pair article1, Pair article2) { return popularArticles; } + @Override + public boolean isTravelGpxTags(@NonNull Map tags) { + return tags.containsKey(ROUTE_ID) && "segment".equals(tags.get(ROUTE)); + } + @Nullable public synchronized TravelGpx searchGpx(@NonNull LatLon location, @Nullable String filter, @Nullable String ref) { + final String lcFilter = filter != null ? filter.toLowerCase() : null; + final String lcSearchRef = ref != null ? ref.toLowerCase() : null; List> foundAmenities = new ArrayList<>(); int searchRadius = ARTICLE_SEARCH_RADIUS; TravelGpx travelGpx = null; @@ -221,15 +241,23 @@ public synchronized TravelGpx searchGpx(@NonNull LatLon location, @Nullable Stri } for (Pair foundGpx : foundAmenities) { Amenity amenity = foundGpx.second; - if ((Algorithms.objectEquals(amenity.getRouteId(), filter) - || Algorithms.objectEquals(amenity.getName(), filter)) - && Algorithms.objectEquals(amenity.getRef(), ref)) { + final String aRef = amenity.getRef(); + final String aName = amenity.getName(); + final String aRouteId = amenity.getRouteId(); + final String lcRef = aRef != null ? aRef.toLowerCase() : null; + final String lcName = aName != null ? aName.toLowerCase() : null; + final String lcRouteId = aRouteId != null ? aRouteId.toLowerCase() : null; + if ((Algorithms.objectEquals(lcRouteId, lcFilter) || Algorithms.objectEquals(lcName, lcFilter)) + && (lcSearchRef == null || Algorithms.objectEquals(lcRef, lcSearchRef))) { travelGpx = getTravelGpx(foundGpx.first, amenity); break; } } searchRadius *= 2; } while (travelGpx == null && searchRadius < MAX_SEARCH_RADIUS); + if (travelGpx == null) { + LOG.error(String.format("searchGpx(%s, %s, %s) failed", location, filter, ref)); + } return travelGpx; } @@ -257,7 +285,7 @@ public boolean isCancelled() { private TravelArticle cacheTravelArticles(File file, Amenity amenity, String lang, boolean readPoints, @Nullable GpxReadCallback callback) { TravelArticle article = null; Map articles; - if (ROUTE_TRACK.equals(amenity.getSubType())) { + if (amenity.isRouteTrack()) { articles = readRoutePoint(file, amenity); } else { articles = readArticles(file, amenity); @@ -282,41 +310,40 @@ private TravelGpx getTravelGpx(File file, Amenity amenity) { TravelGpx travelGpx = new TravelGpx(); travelGpx.file = file; String title = amenity.getName("en"); - travelGpx.title = createTitle(Algorithms.isEmpty(title) ? amenity.getName() : title); + travelGpx.title = Algorithms.isEmpty(title) ? amenity.getName() : title; travelGpx.lat = amenity.getLocation().getLatitude(); travelGpx.lon = amenity.getLocation().getLongitude(); travelGpx.description = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.DESCRIPTION)); travelGpx.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID)); travelGpx.user = Algorithms.emptyIfNull(amenity.getTagContent(USER)); - travelGpx.activityType = Algorithms.emptyIfNull(amenity.getTagContent(ACTIVITY_TYPE)); + travelGpx.activityType = Algorithms.emptyIfNull(amenity.getTagContent(ACTIVITY_TYPE)); // TODO travelGpx.ref = Algorithms.emptyIfNull(amenity.getRef()); - try { - travelGpx.totalDistance = Float.parseFloat(Algorithms.emptyIfNull(amenity.getTagContent(DISTANCE))); - travelGpx.diffElevationUp = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELEVATION_UP))); - travelGpx.diffElevationDown = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELEVATION_DOWN))); - travelGpx.maxElevation = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(MAX_ELEVATION))); - travelGpx.minElevation = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(MIN_ELEVATION))); - travelGpx.avgElevation = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(AVERAGE_ELEVATION))); - String radius = amenity.getTagContent(ROUTE_RADIUS); - if (radius != null) { - travelGpx.routeRadius = MapUtils.convertCharToDist(radius.charAt(0), TRAVEL_GPX_CONVERT_FIRST_LETTER, - TRAVEL_GPX_CONVERT_FIRST_DIST, TRAVEL_GPX_CONVERT_MULT_1, TRAVEL_GPX_CONVERT_MULT_2); - } - } catch (NumberFormatException e) { - LOG.debug(e.getMessage(), e); + travelGpx.totalDistance = Algorithms.parseFloatSilently(amenity.getTagContent(DISTANCE), 0); + travelGpx.diffElevationUp = Algorithms.parseDoubleSilently(amenity.getTagContent(DIFF_ELEVATION_UP), 0); + travelGpx.diffElevationDown = Algorithms.parseDoubleSilently(amenity.getTagContent(DIFF_ELEVATION_DOWN), 0); + travelGpx.minElevation = Algorithms.parseDoubleSilently(amenity.getTagContent(MIN_ELEVATION), 0); + travelGpx.avgElevation = Algorithms.parseDoubleSilently(amenity.getTagContent(AVERAGE_ELEVATION), 0); + travelGpx.maxElevation = Algorithms.parseDoubleSilently(amenity.getTagContent(MAX_ELEVATION), 0); + String radius = amenity.getTagContent(ROUTE_BBOX_RADIUS); + if (radius != null) { + travelGpx.routeRadius = MapUtils.convertCharToDist(radius.charAt(0), TRAVEL_GPX_CONVERT_FIRST_LETTER, + TRAVEL_GPX_CONVERT_FIRST_DIST, TRAVEL_GPX_CONVERT_MULT_1, TRAVEL_GPX_CONVERT_MULT_2); } return travelGpx; } @NonNull - public static SearchPoiTypeFilter getSearchFilter(String... filterSubcategory) { + public static SearchPoiTypeFilter getSearchFilter(String... filterSubcategories) { return new SearchPoiTypeFilter() { @Override public boolean accept(PoiCategory type, String subcategory) { - for (String filterSubcategory : filterSubcategory) { - if (subcategory.equals(filterSubcategory)) { + for (String filter : filterSubcategories) { + if (subcategory.equals(filter)) { return true; } + if (ROUTE_TRACK.equals(filter) && subcategory.startsWith(ROUTES_PREFIX)) { + return true; // include routes:routes_xxx with routes:route_track filter + } } return false; } @@ -694,7 +721,7 @@ private TravelArticle getCachedArticle(@NonNull TravelArticleIdentifier articleI @Override public void openTrackMenu(@NonNull TravelArticle article, @NonNull MapActivity mapActivity, - @NonNull String gpxFileName, @NonNull LatLon latLon) { + @NonNull String gpxFileName, @NonNull LatLon latLon, boolean adjustMapPosition) { GpxReadCallback callback = new GpxReadCallback() { @Override public void onGpxFileReading() { @@ -709,7 +736,7 @@ public void onGpxFileRead(@Nullable GpxFile gpxFile) { String name = gpxFileName.endsWith(GPX_FILE_EXT) ? gpxFileName : gpxFileName + GPX_FILE_EXT; File file = new File(FileUtils.getTempDir(app), name); - GpxUiHelper.saveAndOpenGpx(mapActivity, file, gpxFile, wptPt, article.getAnalysis(), null); + GpxUiHelper.saveAndOpenGpx(mapActivity, file, gpxFile, wptPt, article.getAnalysis(), null, adjustMapPosition); } } }; @@ -1037,8 +1064,7 @@ public ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articl @NonNull @Override public String getGPXName(@NonNull TravelArticle article) { - return article.getTitle().replace('/', '_').replace('\'', '_') - .replace('\"', '_') + GPX_FILE_EXT; + return article.getGpxFileName() + GPX_FILE_EXT; } @NonNull @@ -1070,15 +1096,10 @@ public void saveOrRemoveArticle(@NonNull TravelArticle article, boolean save) { } } - @Nullable - private synchronized GpxFile buildGpxFile(@NonNull List readers, TravelArticle article) { - List segmentList = new ArrayList<>(); - Map gpxFileExtensions = new HashMap<>(); - List pointList = new ArrayList<>(); - List pgNames = new ArrayList<>(); - List pgIcons = new ArrayList<>(); - List pgColors = new ArrayList<>(); - List pgBackgrounds = new ArrayList<>(); + private void fetchSegmentsAndPoints(List readers, TravelArticle article, + List segmentList, List pointList, + Map gpxFileExtensions, List pgNames, + List pgIcons, List pgColors, List pgBackgrounds) { for (BinaryMapIndexReader reader : readers) { try { if (article.file != null && !article.file.equals(reader.getFile())) { @@ -1087,75 +1108,17 @@ private synchronized GpxFile buildGpxFile(@NonNull List re if (article instanceof TravelGpx) { 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 (object.getTagValue(REF).equals(article.ref) - && (object.getTagValue(ROUTE_ID).equals(article.routeId) - || createTitle(object.getName()).equals(article.getTitle()))) { - segmentList.add(object); - } - } - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - }); + matchSegmentsByRefTitleRouteId(article, segmentList)); if (article.routeRadius >= 0) { sr.setBBoxRadius(article.lat, article.lon, article.routeRadius); } reader.searchMapIndex(sr); } - BinaryMapIndexReader.SearchRequest pointRequest = BinaryMapIndexReader.buildSearchPoiRequest( 0, 0, Algorithms.emptyIfNull(article.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(article.getMainFilterString(), article.getPointFilterString()), - new ResultMatcher() { - @Override - public boolean publish(Amenity amenity) { - if (amenity.getRouteId().equals(article.getRouteId())) { - if (ROUTE_TRACK.equals(amenity.getSubType())) { - for (String key : amenity.getAdditionalInfoKeys()) { - if (key.startsWith(OBF_GPX_EXTENSION_TAG_PREFIX)) { - String tag = key.replaceFirst(OBF_GPX_EXTENSION_TAG_PREFIX, ""); - String val = amenity.getAdditionalInfo(key); - gpxFileExtensions.put(tag, val); - } else if (key.startsWith(OBF_POINTS_GROUPS_PREFIX)) { - final String delimiter = OBF_POINTS_GROUPS_DELIMITER; - String joinedValues = amenity.getAdditionalInfo(key); - List values = Arrays.asList(joinedValues.split(delimiter)); - if (OBF_POINTS_GROUPS_NAMES.equals(key)) { - pgNames.addAll(values); - } else if (OBF_POINTS_GROUPS_ICONS.equals(key)) { - pgIcons.addAll(values); - } else if (OBF_POINTS_GROUPS_COLORS.equals(key)) { - pgColors.addAll(values); - } else if (OBF_POINTS_GROUPS_BACKGROUNDS.equals(key)) { - pgBackgrounds.addAll(values); - } - } - } - } else if (ROUTE_TRACK_POINT.equals(amenity.getSubType())) { - pointList.add(amenity); - } else { - String amenityLang = amenity.getTagSuffix(Amenity.LANG_YES + ":"); - if (Algorithms.stringsEqual(article.lang, amenityLang)) { - pointList.add(amenity); - } - } - } - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - }, null); + matchPointsAndTags(article, pointList, gpxFileExtensions, pgNames, pgIcons, pgColors, pgBackgrounds), + null); if (article.routeRadius >= 0) { pointRequest.setBBoxRadius(article.lat, article.lon, article.routeRadius); } @@ -1171,9 +1134,130 @@ public boolean isCancelled() { LOG.error(e.getMessage()); } } - GpxFile gpxFile = null; - String description = article.getDescription(); - String title = FileUtils.isValidFileName(description) ? description : article.getTitle(); + } + + @NonNull + private ResultMatcher matchPointsAndTags(TravelArticle article, List pointList, Map gpxFileExtensions, List pgNames, List pgIcons, List pgColors, List pgBackgrounds) { + return new ResultMatcher() { + boolean isAlreadyProcessed = false; + @Override + public boolean publish(Amenity amenity) { + if (amenity.getRouteId().equals(article.getRouteId())) { + if (amenity.isRouteTrack()) { + if (!isAlreadyProcessed) { + isAlreadyProcessed = true; + reconstructGpxTagsFromAmenityType(amenity, gpxFileExtensions); + for (String tag : amenity.getAdditionalInfoKeys()) { + String value = amenity.getAdditionalInfo(tag); + if (tag.startsWith(OBF_POINTS_GROUPS_PREFIX)) { + final String delimiter = OBF_POINTS_GROUPS_DELIMITER; + List values = Arrays.asList(value.split(delimiter)); + if (OBF_POINTS_GROUPS_NAMES.equals(tag)) { + pgNames.addAll(values); + } else if (OBF_POINTS_GROUPS_ICONS.equals(tag)) { + pgIcons.addAll(values); + } else if (OBF_POINTS_GROUPS_COLORS.equals(tag)) { + pgColors.addAll(values); + } else if (OBF_POINTS_GROUPS_BACKGROUNDS.equals(tag)) { + pgBackgrounds.addAll(values); + } + } else if (!doNotSaveAmenityGpxTags.contains(tag)) { + gpxFileExtensions.put(tag, value); + } + } + } + } else if (ROUTE_TRACK_POINT.equals(amenity.getSubType())) { + pointList.add(amenity); + } else { + String amenityLang = amenity.getTagSuffix(Amenity.LANG_YES + ":"); + if (Algorithms.stringsEqual(article.lang, amenityLang)) { + pointList.add(amenity); + } + } + } + return false; + } + @Override + public boolean isCancelled() { + return false; + } + }; + } + + private void reconstructGpxTagsFromAmenityType(Amenity amenity, Map gpxFileExtensions) { + if (amenity.isRouteTrack() && amenity.getSubType() != null) { + String subType = amenity.getSubType(); + if (subType.startsWith(ROUTES_PREFIX)) { + String osmValue = amenity.getType().getPoiTypeByKeyName(subType).getOsmValue(); + if (!Algorithms.isEmpty(osmValue)) { + if (amenity.hasOsmRouteId()) { + gpxFileExtensions.put("route_type", osmValue); // instead of type and route tags + } + RouteActivityHelper helper = app.getRouteActivityHelper(); + RouteActivity activity = helper.findActivityByTag(osmValue); + if (activity != null) { + gpxFileExtensions.put(GpxUtilities.ACTIVITY_TYPE, activity.getId()); + } + } + } + } + } + + @NonNull + private ResultMatcher matchSegmentsByRefTitleRouteId( + TravelArticle article, List segmentList) { + return new ResultMatcher() { + @Override + public boolean publish(BinaryMapDataObject object) { + if (object.getPointsLength() > 1) { + if (object.getTagValue(REF).equals(article.ref) + && (object.getTagValue(ROUTE_ID).equals(article.routeId) + || object.getName().equals(article.getTitle()))) { + segmentList.add(object); + } + } + return false; + } + @Override + public boolean isCancelled() { + return false; + } + }; + } + + @Nullable + private synchronized GpxFile buildGpxFile(@NonNull List readers, TravelArticle article) { + List segmentList = new ArrayList<>(); + Map gpxFileExtensions = new TreeMap<>(); + List pointList = new ArrayList<>(); + List pgNames = new ArrayList<>(); + List pgIcons = new ArrayList<>(); + List pgColors = new ArrayList<>(); + List pgBackgrounds = new ArrayList<>(); + + fetchSegmentsAndPoints(readers, article, segmentList, pointList, gpxFileExtensions, + pgNames, pgIcons, pgColors, pgBackgrounds); + + GpxFile gpxFile; + if (article instanceof TravelGpx) { + gpxFile = new GpxFile(Version.getFullVersion(app)); + gpxFile.getMetadata().setName(Objects.requireNonNullElse(article.title, article.routeId)); // path is name + if (!Algorithms.isEmpty(article.title) && article.hasOsmRouteId()) { + gpxFileExtensions.putIfAbsent("name", article.title); + } + if (!Algorithms.isEmpty(article.description)) { + gpxFile.getMetadata().setDesc(article.description); + } + } else { + String description = article.getDescription(); + String title = FileUtils.isValidFileName(description) ? description : article.getTitle(); + gpxFile = new GpxFile(title, article.getLang(), article.getContent()); + } + + if (!Algorithms.isEmpty(article.getImageTitle())) { + gpxFile.getMetadata().setLink(TravelArticle.getImageUrl(article.getImageTitle(), false)); + } + if (!segmentList.isEmpty()) { boolean hasAltitude = false; Track track = new Track(); @@ -1185,38 +1269,30 @@ public boolean isCancelled() { point.setLon(MapUtils.get31LongitudeX(segment.getPoint31XTile(i))); trkSegment.getPoints().add(point); } - String ele_graph = segment.getTagValue("ele_graph"); + String ele_graph = segment.getTagValue(ELE_GRAPH); if (!Algorithms.isEmpty(ele_graph)) { hasAltitude = true; List heightRes = KMapAlgorithms.INSTANCE.decodeIntHeightArrayGraph(ele_graph, 3); - double startEle = 0; - try { - startEle = Double.parseDouble(segment.getTagValue("start_ele")); - } catch (NumberFormatException e) { - LOG.debug(e.getMessage(), e); - } + double startEle = Algorithms.parseDoubleSilently(segment.getTagValue(START_ELEVATION), 0); KMapAlgorithms.INSTANCE.augmentTrkSegmentWithAltitudes(trkSegment, heightRes, startEle); } track.getSegments().add(trkSegment); } - gpxFile = new GpxFile(title, article.getLang(), article.getContent()); - if (!Algorithms.isEmpty(article.getImageTitle())) { - gpxFile.getMetadata().setLink(TravelArticle.getImageUrl(article.getImageTitle(), false)); - } gpxFile.setTracks(new ArrayList<>()); gpxFile.getTracks().add(track); - gpxFile.setRef(article.ref); + if (!(article instanceof TravelGpx)) { + gpxFile.setRef(article.ref); + } gpxFile.setHasAltitude(hasAltitude); - gpxFile.getExtensionsToWrite().putAll(gpxFileExtensions); + if (gpxFileExtensions.containsKey(GpxUtilities.ACTIVITY_TYPE)) { + gpxFile.getMetadata().getExtensionsToWrite() + .put(GpxUtilities.ACTIVITY_TYPE, gpxFileExtensions.get(GpxUtilities.ACTIVITY_TYPE)); + gpxFileExtensions.remove(GpxUtilities.ACTIVITY_TYPE); // move activity to metadata TODO fixme + } + gpxFile.getExtensionsToWrite().putAll(gpxFileExtensions); // finally } reconstructPointsGroups(gpxFile, pgNames, pgIcons, pgColors, pgBackgrounds); // create groups before points if (!pointList.isEmpty()) { - if (gpxFile == null) { - gpxFile = new GpxFile(title, article.getLang(), article.getContent()); - if (!Algorithms.isEmpty(article.getImageTitle())) { - gpxFile.getMetadata().setLink(TravelArticle.getImageUrl(article.getImageTitle(), false)); - } - } for (Amenity wayPoint : pointList) { gpxFile.addPoint(article.createWptPt(wayPoint, article.getLang())); } @@ -1234,18 +1310,15 @@ private void reconstructPointsGroups(GpxFile gpxFile, List pgNames, List String icon = pgIcons.get(i); String background = pgBackgrounds.get(i); int color = KAlgorithms.INSTANCE.parseColor(pgColors.get(i)); - if (name.isEmpty()) name = GpxFile.DEFAULT_WPT_GROUP_NAME; // follow current default + if (name.isEmpty() || OBF_POINTS_GROUPS_EMPTY_NAME_STUB.equals(name)) { + name = GpxFile.DEFAULT_WPT_GROUP_NAME; // follow current default + } GpxUtilities.PointsGroup pg = new GpxUtilities.PointsGroup(name, icon, background, color); gpxFile.addPointsGroup(pg); } } } - @NonNull - public String createTitle(@NonNull String name) { - return capitalizeFirstLetter(GpxHelper.INSTANCE.getGpxTitle(name)); - } - private class GpxFileReader extends AsyncTask { private final TravelArticle article;