From c6bcbbf590b7c6cea9eda6d21b17b65915b24668 Mon Sep 17 00:00:00 2001 From: Max Kojin Date: Tue, 12 May 2026 12:44:26 +0500 Subject: [PATCH 1/9] Relation POIs: on multipolgyons - implement main frame --- OsmAnd.xcodeproj/project.pbxproj | 4 + .../TargetMenu/POI/OAPOIViewController.mm | 66 +++++++++++++ Sources/Helpers/OAAmenitySearcher.h | 2 + Sources/Helpers/OAAmenitySearcher.mm | 49 ++++++++++ Sources/Helpers/SearchByRouteIdTask.swift | 93 +++++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 Sources/Helpers/SearchByRouteIdTask.swift diff --git a/OsmAnd.xcodeproj/project.pbxproj b/OsmAnd.xcodeproj/project.pbxproj index 5946de61e4..083499144f 100644 --- a/OsmAnd.xcodeproj/project.pbxproj +++ b/OsmAnd.xcodeproj/project.pbxproj @@ -599,6 +599,7 @@ 32D7D6D22CD9564D00EB752F /* ObfConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D7D6D12CD9564D00EB752F /* ObfConstants.swift */; }; 32DA38A82BA9C4D500A3AC3C /* CLLocation+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 32DA38A72BA9C4D500A3AC3C /* CLLocation+Extension.m */; }; 32DE2BE42B6D13730025F2B9 /* OATwoButtonsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DE2BE32B6D13730025F2B9 /* OATwoButtonsTableViewCell.swift */; }; + 32E080C22FAB2DCA0095A278 /* SearchByRouteIdTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32E080C12FAB2DC50095A278 /* SearchByRouteIdTask.swift */; }; 32E2A6E027206ACF00F018B5 /* OAQuickSearchCoordinateFormatsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32E2A6DF2720699200F018B5 /* OAQuickSearchCoordinateFormatsViewController.xib */; }; 32E2D30229B9DB5700A17D0B /* ic_custom_coordinates_location@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 32E2D2FE29B9DB5500A17D0B /* ic_custom_coordinates_location@3x.png */; }; 32E2D30329B9DB5700A17D0B /* ic_custom_coordinates_map_center@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 32E2D2FF29B9DB5600A17D0B /* ic_custom_coordinates_map_center@2x.png */; }; @@ -4233,6 +4234,7 @@ 32DA38A62BA9C4C600A3AC3C /* CLLocation+Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CLLocation+Extension.h"; sourceTree = ""; }; 32DA38A72BA9C4D500A3AC3C /* CLLocation+Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CLLocation+Extension.m"; sourceTree = ""; }; 32DE2BE32B6D13730025F2B9 /* OATwoButtonsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OATwoButtonsTableViewCell.swift; sourceTree = ""; }; + 32E080C12FAB2DC50095A278 /* SearchByRouteIdTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchByRouteIdTask.swift; sourceTree = ""; }; 32E2A6DF2720699200F018B5 /* OAQuickSearchCoordinateFormatsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OAQuickSearchCoordinateFormatsViewController.xib; sourceTree = ""; }; 32E2D2FE29B9DB5500A17D0B /* ic_custom_coordinates_location@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "ic_custom_coordinates_location@3x.png"; path = "Resources/Icons/ic_custom_coordinates_location@3x.png"; sourceTree = ""; }; 32E2D2FF29B9DB5600A17D0B /* ic_custom_coordinates_map_center@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "ic_custom_coordinates_map_center@2x.png"; path = "Resources/Icons/ic_custom_coordinates_map_center@2x.png"; sourceTree = ""; }; @@ -13844,6 +13846,7 @@ 32EB34472E462D58004F7C75 /* OAAmenitySearcher.h */, 32EB34482E462D68004F7C75 /* OAAmenitySearcher.mm */, 32F6B3902E47A842007F3902 /* OAAmenitySearcher+cpp.h */, + 32E080C12FAB2DC50095A278 /* SearchByRouteIdTask.swift */, DA5A80E026C563A600F274C7 /* OAAppSettings.h */, DA5A80B226C563A600F274C7 /* OAAppSettings.m */, 2C8E8A542D5104E600746A69 /* OAArabicNormalizer.h */, @@ -18491,6 +18494,7 @@ DA5A83EA26C563A800F274C7 /* OACollapsableWaypointsView.mm in Sources */, 27D33B572E7459FA00AD4F70 /* MoveToMyLocationAction.swift in Sources */, DA5A837D26C563A800F274C7 /* GpxUIHelper.swift in Sources */, + 32E080C22FAB2DCA0095A278 /* SearchByRouteIdTask.swift in Sources */, DA21F1BB29BA101E004985BA /* OANetworkRouteDrawable.mm in Sources */, DA4ABF252876DA6800B996EF /* OAFileSettingsItem.mm in Sources */, FA7341142BBC1C2000CBF7EC /* OARequiredMapsResourceViewController.mm in Sources */, diff --git a/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm b/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm index 25108488b4..ea50dfb5d6 100644 --- a/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm +++ b/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm @@ -46,6 +46,10 @@ static const NSInteger WAY_MODULO_REMAINDER = 1; static const NSInteger kOrderShortDescrRow = -10000; +static NSString * const ROUTE_MEMBERS_ROW_KEY = @"route_members_row_key"; +static NSString * const ROUTE_PART_OF_ROW_KEY = @"route_part_of_row_key"; +static NSString * const ROUTE_RELATED_ROUTES_ROW_KEY = @"route_related_routes_row_key"; + @interface OAPOIViewController () @end @@ -225,6 +229,7 @@ - (void)buildNearestRowsForAmenity:(NSMutableArray *)rows { [self buildNearestPoiRowForAmenity:rows]; } + [self buildRouteRows:rows]; } - (void)buildNearestWikiForAmenity:(NSMutableArray *)rows @@ -237,6 +242,67 @@ - (void)buildNearestPoiRowForAmenity:(NSMutableArray *)rows [self buildNearestPoiRow:rows listener:nil]; } +- (void)buildRouteRows:(NSMutableArray *)rows +{ + //TODO: test + if (!self.poi) + return; + + if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_MEMBERS_IDS])) + { + // buildRouteRow(amenities -> { + // String title = app.getString(R.string.route_members); + // buildRouteRow(amenities, viewGroupRef, position, ROUTE_MEMBERS_ROW_KEY, title); + // }, SearchType.MEMBERS); + [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:@"SearchType.MEMBERS"]; + } + + if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_ID])) + { + // buildRouteRow(amenities -> { + // String title = app.getString(R.string.route_part_of); + // buildRouteRow(amenities, viewGroupRef, position, ROUTE_PART_OF_ROW_KEY, title); + // }, SearchType.PART_OF); + // + // buildRouteRow(amenities -> { + // String title = app.getString(R.string.multipoligon_related); + // buildRouteRow(amenities, viewGroupRef, position, ROUTE_RELATED_ROUTES_ROW_KEY, title); + // }, SearchType.RELATED); + + [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:@"SearchType.PART_OF"]; + + [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:@"SearchType.RELATED"]; + } +} + + +//protected void buildRouteRow(SearchByRouteIdListener listener, SearchType type) { +// if (amenity != null) { +// OsmAndTaskManager.executeTask(new SearchByRouteIdTask(amenity, type, app, listener)); +// } +//} + +- (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString *)tag searchType:(NSString *)searchType +{ + //TODO: test + if (self.poi) + { + SearchByRouteIdTask *task = [[SearchByRouteIdTask alloc] initWithAmenity:self.poi searchType:searchType]; + [task execute]; + } +} + + + + +//TODO: implement +//SearchByRouteIdTask.doInBackground() +// List list = amenitySearcher.searchRoutePartOf(routeId); + + + + + - (void)buildNamesRow:(NSMutableArray *)rows { if (!_amenityUIHelper) diff --git a/Sources/Helpers/OAAmenitySearcher.h b/Sources/Helpers/OAAmenitySearcher.h index 6829ec6dd0..aeb9d5b6d5 100644 --- a/Sources/Helpers/OAAmenitySearcher.h +++ b/Sources/Helpers/OAAmenitySearcher.h @@ -65,6 +65,8 @@ NS_ASSUME_NONNULL_BEGIN + (NSArray *)filterUniqueAmenitiesByOsmIdOrWikidata:(NSArray *)amenities; +- (NSArray *)searchRoutePartOf:(NSString *)routeId; + @end diff --git a/Sources/Helpers/OAAmenitySearcher.mm b/Sources/Helpers/OAAmenitySearcher.mm index aabbbed532..59b7bff013 100644 --- a/Sources/Helpers/OAAmenitySearcher.mm +++ b/Sources/Helpers/OAAmenitySearcher.mm @@ -1819,4 +1819,53 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return [NSArray arrayWithArray:arr]; } +//public List searchRoutePartOf(final String routeId) { +// ResultMatcher matcher = new ResultMatcher() { +// public boolean publish(Amenity amenity) { +// String members = amenity.getAdditionalInfo("route_members_ids"); +// if (members != null) { +// HashSet ids = new HashSet(); +// Collections.addAll(ids, members.split(" ")); +// return ids.contains(routeId); +// } else { +// return false; +// } +// } +// +// public boolean isCancelled() { +// return false; +// } +// }; +// return this.searchRouteByName(routeId, StringMatcherMode.CHECK_EQUALS_FROM_SPACE, matcher); +//} + +- (NSArray *)searchRoutePartOf:(NSString *)routeId +{ + //TODO: implement + return [self searchRouteByName]; +} + + + +//private List searchRouteByName(String multipleSearch, CollatorStringMatcher.StringMatcherMode mode, ResultMatcher matcher) { +// List result = new ArrayList(); +// BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, multipleSearch, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, matcher); +// req.setMatcherMode(mode); +// +// for(AmenityIndexRepository index : this.getAmenityRepositories(false, (Predicate)null)) { +// List amenities = index.searchPoiByName(req); +// if (!Algorithms.isEmpty(amenities)) { +// result.addAll(amenities); +// } +// } +// +// return result; +//} + +- (NSArray *)searchRouteByName +{ + //TODO: implement + return nil; +} + @end diff --git a/Sources/Helpers/SearchByRouteIdTask.swift b/Sources/Helpers/SearchByRouteIdTask.swift new file mode 100644 index 0000000000..a8c39ada52 --- /dev/null +++ b/Sources/Helpers/SearchByRouteIdTask.swift @@ -0,0 +1,93 @@ +// +// SearchByRouteIdTask.swift +// OsmAnd +// +// Created by Max Kojin on 06/05/26. +// Copyright © 2026 OsmAnd. All rights reserved. +// + +final class SearchByRouteIdTask : OAAsyncTask { + +// protected SearchByRouteIdTask(@Nullable Amenity amenity, SearchType type, OsmandApplication app, SearchByRouteIdListener listener) { +// this.listener = listener; +// if (amenity != null) { +// routeId = amenity.getAdditionalInfo(Amenity.ROUTE_ID); +// routeMembersIds = amenity.getAdditionalInfo(Amenity.ROUTE_MEMBERS_IDS); +// } else { +// routeId = null; +// routeMembersIds = null; +// } +// searchType = type; +// this.app = app; +// this.amenity = amenity; +// } +// +// public SearchByRouteIdTask(String routeId, String routeMembersIds, SearchType type, OsmandApplication app, SearchByRouteIdListener listener) { +// this.routeId = routeId; +// this.routeMembersIds = routeMembersIds; +// this.listener = listener; +// this.searchType = type; +// this.app = app; +// this.amenity = null; +// } + +// init(routeIds: [String: CLLocation], callback: @escaping (([String: [String: TravelArticle]]) -> Void)) { +// self.routeIds = routeIds +// self.callback = callback +// super.init() +// } + + private var amenity: OAPOI? + private var searchType: String + + private var routeId: String? + private var routeMembersIds: String? + + init(amenity: OAPOI?, searchType: String) { + if let amenity { + routeId = amenity.getAdditionalInfo(ROUTE_ID) + routeMembersIds = amenity.getAdditionalInfo(ROUTE_MEMBERS_IDS) + } + + self.searchType = searchType + self.amenity = amenity + //this.listener = listener; + + super.init() + } + + override func doInBackground() -> Any? { + //TODO: implement + var amenities = [OAPOI]() + let amenitySearcher = OAAmenitySearcher() + + if searchType == "MEMBERS" { + //TODO: implement + + } else if searchType == "RELATED" { + //TODO: implement + + } else if searchType == "PART_OF" { + if let routeId, !routeId.isEmpty { + let list = amenitySearcher.searchRoutePart(of: routeId) + var routeIdHash = Set() + + for am in list { + if let routeId = am.getAdditionalInfo(ROUTE_ID) { + if !routeIdHash.contains(routeId) { + amenities.append(am) + routeIdHash.insert(routeId) + } + } + } + + } + } + return amenities + } + + override func onPostExecute(result: Any?) { + //TODO: implement + super.onPostExecute(result: result) + } +} From 686b06237be38f2d2cf011930dcd68fecb1b7092 Mon Sep 17 00:00:00 2001 From: Max Kojin Date: Wed, 13 May 2026 22:42:22 +0500 Subject: [PATCH 2/9] part_of search implementation --- .../en.lproj/Localizable.strings | 6 + Sources/Common/OACollatorStringMatcher.h | 1 + .../TargetMenu/OATargetInfoViewController.mm | 148 +++++++++++--- .../TargetMenu/POI/OAPOIViewController.mm | 192 ++++++++++++------ Sources/Helpers/OAAmenitySearcher.h | 1 + Sources/Helpers/OAAmenitySearcher.mm | 153 +++++++++++++- Sources/Helpers/SearchByRouteIdTask.swift | 77 +++++-- 7 files changed, 474 insertions(+), 104 deletions(-) diff --git a/Resources/Localizations/en.lproj/Localizable.strings b/Resources/Localizations/en.lproj/Localizable.strings index 0367b99158..905c74966d 100644 --- a/Resources/Localizations/en.lproj/Localizable.strings +++ b/Resources/Localizations/en.lproj/Localizable.strings @@ -1500,6 +1500,8 @@ "ltr_or_rtl_combine_via_per" = "%@ per %@"; "ltr_or_rtl_combine_with_brackets" = "%@ (%@)"; +"ltr_or_rtl_triple_combine_via_space" = "%@ %@ %@"; + "of" = "%d of %d"; "downloaded_bytes" = "%@ of %@"; @@ -4324,6 +4326,10 @@ "rendering_attr_showClimbingRoutes_description" = "Show climbing routes"; "rendering_attr_showClimbingRoutes_name" = "Climbing routes"; +"route_members" = "Members"; +"route_part_of" = "Part of"; +"multipoligon_related" = "Related"; + "shared_string_view_all" = "View all"; "no_internet_descr" = "Please check your connection and try again."; "no_photos_available" = "No photos available"; diff --git a/Sources/Common/OACollatorStringMatcher.h b/Sources/Common/OACollatorStringMatcher.h index 76fa09472a..82e3543016 100644 --- a/Sources/Common/OACollatorStringMatcher.h +++ b/Sources/Common/OACollatorStringMatcher.h @@ -27,6 +27,7 @@ typedef enum CHECK_CONTAINS, // simple collator equals CHECK_EQUALS, + MULTISEARCH, } StringMatcherMode; diff --git a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm index 550c3045aa..4f5fd181e6 100644 --- a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm +++ b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm @@ -80,6 +80,9 @@ NSString * const TYPE_WIKIDATA_PHOTO = @"wikidata-photo"; static NSString *WITHIN_POLYGONS_ROW_KEY = @"within_polygons"; +static NSString * const ROUTE_MEMBERS_ROW_KEY = @"route_members_row_key"; +static NSString * const ROUTE_PART_OF_ROW_KEY = @"route_part_of_row_key"; +static NSString * const ROUTE_RELATED_ROUTES_ROW_KEY = @"route_related_routes_row_key"; // HTML for ViewPort static NSString *const kViewPortHtml = @"
"; @@ -613,35 +616,132 @@ static inline BOOL OARowsContainKey(NSArray *rows, NSString - (void)buildRouteRows:(NSMutableArray *)rows { - // TODO: implement + //TODO: test + if (![self getTargetObj] || ![[self getTargetObj] isKindOfClass:OAPOI.class]) + return; + OAPOI *amenity = [self getTargetObj]; -// if (amenity == null) { -// return; -// } -// WeakReference viewGroupRef = new WeakReference<>(viewGroup); -// int position = viewGroup.getChildCount(); -// if (amenity.getAdditionalInfo(Amenity.ROUTE_MEMBERS_IDS) != null) { -// -// buildRouteRow(amenities -> { -// String title = app.getString(R.string.route_members); -// buildRouteRow(amenities, viewGroupRef, position, ROUTE_MEMBERS_ROW_KEY, title); -// }, SearchType.MEMBERS); + if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_MEMBERS_IDS])) + { + // buildRouteRow(amenities -> { + // String title = app.getString(R.string.route_members); + // buildRouteRow(amenities, viewGroupRef, position, ROUTE_MEMBERS_ROW_KEY, title); + // }, SearchType.MEMBERS); + + [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(id _Nullable result) { + NSArray *amenities = result; + if (!NSArrayIsEmpty(amenities)) + { + NSString *title = OALocalizedString(@"route_members"); + OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_MEMBERS_ROW_KEY title:title]; + [self appendInfoRow:row]; + } + }]; + } + + if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_ID])) + { + // buildRouteRow(amenities -> { + // String title = app.getString(R.string.route_part_of); + // buildRouteRow(amenities, viewGroupRef, position, ROUTE_PART_OF_ROW_KEY, title); + // }, SearchType.PART_OF); + // + // buildRouteRow(amenities -> { + // String title = app.getString(R.string.multipoligon_related); + // buildRouteRow(amenities, viewGroupRef, position, ROUTE_RELATED_ROUTES_ROW_KEY, title); + // }, SearchType.RELATED); + + + //TODO: add build row with new results + + //TODO: uncomment + [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(id _Nullable result) { + NSArray *amenities = result; + if (!NSArrayIsEmpty(amenities)) + { + NSString *title = OALocalizedString(@"route_part_of"); + OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_PART_OF_ROW_KEY title:title]; + [self appendInfoRow:row]; + } + }]; + + + + [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeRelated completionHandler:^(NSArray * _Nullable amenities) { + if (!NSArrayIsEmpty(amenities)) + { + NSString *title = OALocalizedString(@"multipoligon_related"); + OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_RELATED_ROUTES_ROW_KEY title:title]; + [self appendInfoRow:row]; + } + }]; + } +} + + +//protected void buildRouteRow(SearchByRouteIdListener listener, SearchType type) { +// if (amenity != null) { +// OsmAndTaskManager.executeTask(new SearchByRouteIdTask(amenity, type, app, listener)); // } -// -// if (amenity.getAdditionalInfo(Amenity.ROUTE_ID) != null) { -// -// buildRouteRow(amenities -> { -// String title = app.getString(R.string.route_part_of); -// buildRouteRow(amenities, viewGroupRef, position, ROUTE_PART_OF_ROW_KEY, title); -// }, SearchType.PART_OF); -// -// buildRouteRow(amenities -> { -// String title = app.getString(R.string.multipoligon_related); -// buildRouteRow(amenities, viewGroupRef, position, ROUTE_RELATED_ROUTES_ROW_KEY, title); -// }, SearchType.RELATED); +//} + +- (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString *)tag searchType:(EOASearchByRouteIdTaskSearchType)searchType completionHandler:(void (^ _Nullable)(NSArray * _Nullable amenities))completionHandler +{ + if (![self getTargetObj] || ![[self getTargetObj] isKindOfClass:OAPOI.class]) + return; + OAPOI *amenity = [self getTargetObj]; + + if (amenity) + { + SearchByRouteIdTask *task = [[SearchByRouteIdTask alloc] initWithAmenity:amenity searchType:searchType]; + task.completionHandler = completionHandler; + [task execute]; + } +} + +//private void buildRouteRow(List amenities, WeakReference viewGroupRef, int position, String key, String title) { +// ViewGroup viewGroup1 = viewGroupRef.get(); +// if (viewGroup1 == null || Algorithms.isEmpty(amenities)) { +// return; // } +// String type = "\"" + AmenityMenuController.getTypeStr(app, amenity) + "\""; +// String count = "(" + amenities.size() + ")"; +// String text = app.getString(R.string.ltr_or_rtl_triple_combine_via_space, title, type, count); +// View wikiRow = viewGroup1.findViewWithTag(NEAREST_WIKI_KEY); +// View amenitiesRow = createRowContainer(viewGroup1.getContext(), key); +// firstRow = position == 0 || isDividerAtPosition(viewGroup1, position - 1); +// int iconId = AmenityMenuController.getRightIconId(app, amenity); +// CollapsableView collapsableView = getCollapsableView(amenitiesRow.getContext(), true, amenities, key); +// buildRow(amenitiesRow, new BuildRowAttrs.Builder().setIconId(iconId).setText(text) +// .setCollapsable(true).setCollapsableView(collapsableView).build()); +// viewGroup1.addView(amenitiesRow, position); +// buildNearestRowDividerIfMissing(viewGroup1, position); +// requestMenuRelayout(viewGroup1); +//} + + +- (OAAmenityInfoRow *)buildRouteRow:(NSMutableArray *)rows amenities:(NSArray *)amenities key:(NSString *)key title:(NSString *)title +{ + + NSString *type = [self getTypeStr]; //TODO: test this line + NSString *count = [NSString stringWithFormat:@"(%lu)", amenities.count]; + NSString *text = [NSString stringWithFormat:OALocalizedString(@"ltr_or_rtl_triple_combine_via_space"), title, type, count]; + + UIImage *icon = [self getIcon]; + + OAAmenityInfoRow *row = [[OAAmenityInfoRow alloc] initWithKey:key icon:icon textPrefix:nil text:text textColor:nil isText:YES needLinks:NO order:0 typeName:nil isPhoneNumber:NO isUrl:NO]; + + //TODO: implement + + row.collapsableView = nil; + + return row; } + + + + - (void)buildPluginRows:(NSMutableArray *)rows { [self addOsmRowInfoIfNeeded:rows]; diff --git a/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm b/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm index ea50dfb5d6..f297169c05 100644 --- a/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm +++ b/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm @@ -46,9 +46,9 @@ static const NSInteger WAY_MODULO_REMAINDER = 1; static const NSInteger kOrderShortDescrRow = -10000; -static NSString * const ROUTE_MEMBERS_ROW_KEY = @"route_members_row_key"; -static NSString * const ROUTE_PART_OF_ROW_KEY = @"route_part_of_row_key"; -static NSString * const ROUTE_RELATED_ROUTES_ROW_KEY = @"route_related_routes_row_key"; +//static NSString * const ROUTE_MEMBERS_ROW_KEY = @"route_members_row_key"; +//static NSString * const ROUTE_PART_OF_ROW_KEY = @"route_part_of_row_key"; +//static NSString * const ROUTE_RELATED_ROUTES_ROW_KEY = @"route_related_routes_row_key"; @interface OAPOIViewController () @@ -59,6 +59,10 @@ @implementation OAPOIViewController OAPOIHelper *_poiHelper; AmenityUIHelper *_amenityUIHelper; std::vector> _openingHoursInfo; + +// OAAmenityInfoRow *routeMembersRow; +// OAAmenityInfoRow *routePartOfRow; +// OAAmenityInfoRow *routeRelatedRow; } static const NSArray *kContactUrlTags = @[@"youtube", @"facebook", @"instagram", @"twitter", @"x", @"vk", @"ok", @"webcam", @"telegram", @"linkedin", @"pinterest", @"foursquare", @"xing", @"flickr", @"email", @"mastodon", @"diaspora", @"gnusocial", @"skype"]; @@ -229,7 +233,7 @@ - (void)buildNearestRowsForAmenity:(NSMutableArray *)rows { [self buildNearestPoiRowForAmenity:rows]; } - [self buildRouteRows:rows]; +// [self buildRouteRows:rows]; } - (void)buildNearestWikiForAmenity:(NSMutableArray *)rows @@ -242,65 +246,135 @@ - (void)buildNearestPoiRowForAmenity:(NSMutableArray *)rows [self buildNearestPoiRow:rows listener:nil]; } -- (void)buildRouteRows:(NSMutableArray *)rows -{ - //TODO: test - if (!self.poi) - return; - - if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_MEMBERS_IDS])) - { - // buildRouteRow(amenities -> { - // String title = app.getString(R.string.route_members); - // buildRouteRow(amenities, viewGroupRef, position, ROUTE_MEMBERS_ROW_KEY, title); - // }, SearchType.MEMBERS); - [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:@"SearchType.MEMBERS"]; - } - - if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_ID])) - { - // buildRouteRow(amenities -> { - // String title = app.getString(R.string.route_part_of); - // buildRouteRow(amenities, viewGroupRef, position, ROUTE_PART_OF_ROW_KEY, title); - // }, SearchType.PART_OF); - // - // buildRouteRow(amenities -> { - // String title = app.getString(R.string.multipoligon_related); - // buildRouteRow(amenities, viewGroupRef, position, ROUTE_RELATED_ROUTES_ROW_KEY, title); - // }, SearchType.RELATED); - - [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:@"SearchType.PART_OF"]; - - [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:@"SearchType.RELATED"]; - } -} +//- (void)buildRouteRows:(NSMutableArray *)rows +//{ +//// //TODO: test +//// if (!self.poi) +//// return; +//// +//// if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_MEMBERS_IDS])) +//// { +//// // buildRouteRow(amenities -> { +//// // String title = app.getString(R.string.route_members); +//// // buildRouteRow(amenities, viewGroupRef, position, ROUTE_MEMBERS_ROW_KEY, title); +//// // }, SearchType.MEMBERS); +//// +////// [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(id _Nullable result) { +////// NSArray *amenities = result; +////// if (NSArrayIsEmpty(amenities)) +////// { +////// NSString *title = OALocalizedString(@"route_members"); +////// [self buildRouteRow:rows amenities:amenities key:ROUTE_MEMBERS_ROW_KEY title:title]; +////// } +////// }]; +//// } +//// +//// if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_ID])) +//// { +//// // buildRouteRow(amenities -> { +//// // String title = app.getString(R.string.route_part_of); +//// // buildRouteRow(amenities, viewGroupRef, position, ROUTE_PART_OF_ROW_KEY, title); +//// // }, SearchType.PART_OF); +//// // +//// // buildRouteRow(amenities -> { +//// // String title = app.getString(R.string.multipoligon_related); +//// // buildRouteRow(amenities, viewGroupRef, position, ROUTE_RELATED_ROUTES_ROW_KEY, title); +//// // }, SearchType.RELATED); +//// +//// +//// //TODO: add build row with new results +//// +//// //TODO: uncomment +////// [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(id _Nullable result) { +////// NSArray *amenities = result; +////// if (NSArrayIsEmpty(amenities)) +////// { +////// NSString *title = OALocalizedString(@"route_part_of"); +////// [self buildRouteRow:rows amenities:amenities key:ROUTE_PART_OF_ROW_KEY title:title]; +////// } +////// }]; +//// +//// +//// if (routeRelatedRow) +//// { +////// [rows addObject:routeRelatedRow]; +//// [self appendInfoRow:routeRelatedRow]; +//// } +//// else +//// { +//// [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeRelated completionHandler:^(NSArray * _Nullable amenities) { +////// NSArray *amenities = (NSArray *)result; +//// if (!NSArrayIsEmpty(amenities)) +//// { +//// NSString *title = OALocalizedString(@"multipoligon_related"); +//// routeRelatedRow = [self buildRouteRow:rows amenities:amenities key:ROUTE_RELATED_ROUTES_ROW_KEY title:title]; +////// [rows addObject:routeRelatedRow]; +////// [self rebuildRows]; +//// [self appendInfoRow:routeRelatedRow]; +//// } +//// }]; +//// } +//// } +//} -//protected void buildRouteRow(SearchByRouteIdListener listener, SearchType type) { -// if (amenity != null) { -// OsmAndTaskManager.executeTask(new SearchByRouteIdTask(amenity, type, app, listener)); +////protected void buildRouteRow(SearchByRouteIdListener listener, SearchType type) { +//// if (amenity != null) { +//// OsmAndTaskManager.executeTask(new SearchByRouteIdTask(amenity, type, app, listener)); +//// } +////} +// +//- (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString *)tag searchType:(EOASearchByRouteIdTaskSearchType)searchType completionHandler:(void (^ _Nullable)(NSArray * _Nullable amenities))completionHandler +//{ +// if (self.poi) +// { +// SearchByRouteIdTask *task = [[SearchByRouteIdTask alloc] initWithAmenity:self.poi searchType:searchType]; +// task.completionHandler = completionHandler; +// [task execute]; // } //} - -- (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString *)tag searchType:(NSString *)searchType -{ - //TODO: test - if (self.poi) - { - SearchByRouteIdTask *task = [[SearchByRouteIdTask alloc] initWithAmenity:self.poi searchType:searchType]; - [task execute]; - } -} - - - - -//TODO: implement -//SearchByRouteIdTask.doInBackground() -// List list = amenitySearcher.searchRoutePartOf(routeId); - - - +// +////private void buildRouteRow(List amenities, WeakReference viewGroupRef, int position, String key, String title) { +//// ViewGroup viewGroup1 = viewGroupRef.get(); +//// if (viewGroup1 == null || Algorithms.isEmpty(amenities)) { +//// return; +//// } +//// String type = "\"" + AmenityMenuController.getTypeStr(app, amenity) + "\""; +//// String count = "(" + amenities.size() + ")"; +//// String text = app.getString(R.string.ltr_or_rtl_triple_combine_via_space, title, type, count); +//// View wikiRow = viewGroup1.findViewWithTag(NEAREST_WIKI_KEY); +//// View amenitiesRow = createRowContainer(viewGroup1.getContext(), key); +//// firstRow = position == 0 || isDividerAtPosition(viewGroup1, position - 1); +//// int iconId = AmenityMenuController.getRightIconId(app, amenity); +//// CollapsableView collapsableView = getCollapsableView(amenitiesRow.getContext(), true, amenities, key); +//// buildRow(amenitiesRow, new BuildRowAttrs.Builder().setIconId(iconId).setText(text) +//// .setCollapsable(true).setCollapsableView(collapsableView).build()); +//// viewGroup1.addView(amenitiesRow, position); +//// buildNearestRowDividerIfMissing(viewGroup1, position); +//// requestMenuRelayout(viewGroup1); +////} +// +// +//- (OAAmenityInfoRow *)buildRouteRow:(NSMutableArray *)rows amenities:(NSArray *)amenities key:(NSString *)key title:(NSString *)title +//{ +// +// NSString *type = [self getTypeStr]; //TODO: test this line +// NSString *count = [NSString stringWithFormat:@"(%lu)", amenities.count]; +// NSString *text = [NSString stringWithFormat:OALocalizedString(@"ltr_or_rtl_triple_combine_via_space"), title, type, count]; +// +// UIImage *icon = [self getIcon]; +// +// OAAmenityInfoRow *row = [[OAAmenityInfoRow alloc] initWithKey:key icon:icon textPrefix:nil text:text textColor:nil isText:YES needLinks:NO order:0 typeName:nil isPhoneNumber:NO isUrl:NO]; +// +// //TODO: implement +// +// row.collapsableView = nil; +// +//// [rows addObject:row]; +// +//// [self rebuildRows]; +// return row; +//} - (void)buildNamesRow:(NSMutableArray *)rows diff --git a/Sources/Helpers/OAAmenitySearcher.h b/Sources/Helpers/OAAmenitySearcher.h index aeb9d5b6d5..8a64ad293b 100644 --- a/Sources/Helpers/OAAmenitySearcher.h +++ b/Sources/Helpers/OAAmenitySearcher.h @@ -66,6 +66,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSArray *)filterUniqueAmenitiesByOsmIdOrWikidata:(NSArray *)amenities; - (NSArray *)searchRoutePartOf:(NSString *)routeId; +- (NSDictionary *> *)searchRouteMembers:(NSString *)multipleSearch; @end diff --git a/Sources/Helpers/OAAmenitySearcher.mm b/Sources/Helpers/OAAmenitySearcher.mm index 59b7bff013..4ad31028ef 100644 --- a/Sources/Helpers/OAAmenitySearcher.mm +++ b/Sources/Helpers/OAAmenitySearcher.mm @@ -1842,7 +1842,95 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr - (NSArray *)searchRoutePartOf:(NSString *)routeId { //TODO: implement - return [self searchRouteByName]; + + OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:CHECK_EQUALS_FROM_SPACE]; + + + return [self searchRouteByName:routeId mode:mode matcher:nil]; +} + + + +//public Map> searchRouteMembers(String multipleSearch) { + +- (NSDictionary *> *)searchRouteMembers:(NSString *)multipleSearch +{ + //TODO: implement + QString qRouteIdKey = QString::fromNSString(ROUTE_ID); + + QSet routeIds; + NSArray *components = [multipleSearch componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + for (NSString *routeId in components) + { + if (routeId.length > 0) + routeIds.insert(QString::fromNSString(routeId)); + } + + NSMutableArray *result = [NSMutableArray new]; + + + OAResultMatcher *matcher = [[OAResultMatcher alloc] initWithPublishFunc:^BOOL(__autoreleasing id *objectPtr) { + + if (objectPtr == nil || *objectPtr == nil) + return false; + + NSValue *value = (NSValue *)*objectPtr; + const OsmAnd::ISearch::IResultEntry *resultEntry = static_cast([value pointerValue]); + + if (resultEntry) + { + const auto amenity = OAGetAmenityFromSearchResult(*resultEntry); + if (amenity) + { + QHash valuesHash = amenity->getDecodedValuesHash(); + + const auto it = valuesHash.constFind(qRouteIdKey); + if (it != valuesHash.constEnd()) + { + const QString qRouteIdValue = it.value(); + + if (routeIds.contains(qRouteIdValue)) + { + OAPOI *poi = [OAAmenitySearcher parsePOI:*resultEntry]; + if (poi) + { + [result addObject:poi]; + return YES; + } + } + } + } + } + return NO; + + } cancelledFunc:^BOOL{ + return false; + }]; + + + + OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:MULTISEARCH]; +// NSArray *result = [self searchRouteByName:multipleSearch mode:mode matcher:matcher]; + [self searchRouteByName:multipleSearch mode:mode matcher:matcher]; + NSMutableDictionary *> *map = [NSMutableDictionary new]; + + for (OAPOI *am in result) + { + NSString *routeId = [am getAdditionalInfo:ROUTE_ID]; + if (map[routeId] == nil) + { + map[routeId] = [NSMutableArray new]; + } + [map[routeId] addObject:am]; + } + +// for (String id : ids) { +// if (!map.containsKey(id)) { +// map.put(id, null); +// } +// } + + return map; } @@ -1862,10 +1950,69 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr // return result; //} -- (NSArray *)searchRouteByName +- (NSArray *)searchRouteByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher { //TODO: implement - return nil; + NSMutableArray *result = [NSMutableArray new]; + + //TODO: use this like in androd +// OsmAnd::PointI topLeft = OsmAnd::PointI(0, 0); +// OsmAnd::PointI bottomRight = OsmAnd::PointI(INT_MAX, INT_MAX); +// OsmAnd::AreaI bbox31 = OsmAnd::AreaI(topLeft, bottomRight); + + //TODO: for quick testing + OsmAnd::LatLon latLon(50.448514, 30.495601); + const auto location = OsmAnd::Utilities::convertLatLonTo31(latLon); + OsmAnd::AreaI bbox31 = (OsmAnd::AreaI)OsmAnd::Utilities::boundingBox31FromAreaInMeters(10000, location); + + + NSMutableArray * amenities = [self searchPoiByName:multipleSearch mode:mode matcher:matcher bbox31:bbox31]; + if (!NSArrayIsEmpty(amenities)) + { + [result addObjectsFromArray:amenities]; + } + + return result; //TODO: delete? +} + + +- (NSMutableArray *) searchPoiByName:(NSString *)code mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher bbox31:(OsmAnd::AreaI)bbox31 +{ +// NSString *code = @"R5121502"; + QString qKey = QString::fromNSString(ROUTE_ID); + QString qCode = QString::fromNSString(code); + + + OsmAndAppInstance app = [OsmAndApp instance]; + const auto& obfsCollection = app.resourcesManager->obfsCollection; + + std::shared_ptr ctrl; + ctrl.reset(new OsmAnd::FunctorQueryController([&matcher] + (const OsmAnd::FunctorQueryController* const controller) + { + return [matcher isCancelled]; + })); + + const std::shared_ptr& searchCriteria = std::shared_ptr(new OsmAnd::AmenitiesInAreaSearch::Criteria); + + + searchCriteria->bbox31 = bbox31; + + const auto search = std::shared_ptr(new OsmAnd::AmenitiesInAreaSearch(obfsCollection)); + NSMutableArray *res = [NSMutableArray new]; + + search->performSearch(*searchCriteria, +// [&osmId, &res, &cancel] + [&res, &code, &qCode, &qKey, &matcher] + (const OsmAnd::ISearch::Criteria& criteria, const OsmAnd::ISearch::IResultEntry& resultEntry) + { + + BOOL foobar = [matcher publish:[NSValue valueWithPointer:&resultEntry]]; + + }, + ctrl); + + return res; //TODO: delete? } @end diff --git a/Sources/Helpers/SearchByRouteIdTask.swift b/Sources/Helpers/SearchByRouteIdTask.swift index a8c39ada52..a61663e7e8 100644 --- a/Sources/Helpers/SearchByRouteIdTask.swift +++ b/Sources/Helpers/SearchByRouteIdTask.swift @@ -6,8 +6,13 @@ // Copyright © 2026 OsmAnd. All rights reserved. // +typealias SearchByRouteIdTaskResultBlock = (_ result: Any?) -> Void + +@objcMembers final class SearchByRouteIdTask : OAAsyncTask { + var completionHandler: (([OAPOI]) -> Void)? = nil + // protected SearchByRouteIdTask(@Nullable Amenity amenity, SearchType type, OsmandApplication app, SearchByRouteIdListener listener) { // this.listener = listener; // if (amenity != null) { @@ -37,13 +42,20 @@ final class SearchByRouteIdTask : OAAsyncTask { // super.init() // } + @objc(EOASearchByRouteIdTaskSearchType) + enum SearchType: Int { + case related + case partOf + case members + } + private var amenity: OAPOI? - private var searchType: String + private var searchType: SearchType private var routeId: String? private var routeMembersIds: String? - init(amenity: OAPOI?, searchType: String) { + init(amenity: OAPOI?, searchType: SearchType) { if let amenity { routeId = amenity.getAdditionalInfo(ROUTE_ID) routeMembersIds = amenity.getAdditionalInfo(ROUTE_MEMBERS_IDS) @@ -57,37 +69,66 @@ final class SearchByRouteIdTask : OAAsyncTask { } override func doInBackground() -> Any? { - //TODO: implement var amenities = [OAPOI]() let amenitySearcher = OAAmenitySearcher() - if searchType == "MEMBERS" { - //TODO: implement - - } else if searchType == "RELATED" { - //TODO: implement - - } else if searchType == "PART_OF" { + if searchType == .members { +// if let routeMembersIds, !routeMembersIds.isEmpty { +// let members = amenitySearcher.searchRouteMembers(routeMembersIds) +// +// for entry in members { +// let amenityList = entry.value +// if !amenityList.isEmpty { +// amenities.append(amenityList[0]) +// } +// } +// } + } else if searchType == .related { if let routeId, !routeId.isEmpty { - let list = amenitySearcher.searchRoutePart(of: routeId) - var routeIdHash = Set() + let related = amenitySearcher.searchRouteMembers(routeId) + var amenityList = [OAPOI]() - for am in list { - if let routeId = am.getAdditionalInfo(ROUTE_ID) { - if !routeIdHash.contains(routeId) { + for entry in related { + let relatedList = entry.value + if !relatedList.isEmpty { + amenityList.append(contentsOf: relatedList) + } + } + + var routeIdHash = Set() + for am in amenityList { + let location = am.getLocation() + if !routeIdHash.contains(location) { + if let amenity, amenity.obfId == am.obfId { amenities.append(am) - routeIdHash.insert(routeId) } } + routeIdHash.insert(location) } - } + } else if searchType == .partOf { +// if let routeId, !routeId.isEmpty { +// let list = amenitySearcher.searchRoutePart(of: routeId) +// var routeIdHash = Set() +// +// for am in list { +// if let routeId = am.getAdditionalInfo(ROUTE_ID) { +// if !routeIdHash.contains(routeId) { +// amenities.append(am) +// } +// routeIdHash.insert(routeId) +// } +// } +// } } return amenities } + override func onPostExecute(result: Any?) { //TODO: implement - super.onPostExecute(result: result) + if let completionHandler, let amenities = result as? [OAPOI] { + completionHandler(amenities) + } } } From a8878f00f4836e8e6643165495a0e226f45e56b4 Mon Sep 17 00:00:00 2001 From: Max Kojin Date: Wed, 13 May 2026 22:47:03 +0500 Subject: [PATCH 3/9] clean code --- .../TargetMenu/OATargetInfoViewController.mm | 53 ------- .../TargetMenu/POI/OAPOIViewController.mm | 140 ------------------ 2 files changed, 193 deletions(-) diff --git a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm index 4f5fd181e6..e6c8ba378b 100644 --- a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm +++ b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm @@ -623,11 +623,6 @@ - (void)buildRouteRows:(NSMutableArray *)rows if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_MEMBERS_IDS])) { - // buildRouteRow(amenities -> { - // String title = app.getString(R.string.route_members); - // buildRouteRow(amenities, viewGroupRef, position, ROUTE_MEMBERS_ROW_KEY, title); - // }, SearchType.MEMBERS); - [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(id _Nullable result) { NSArray *amenities = result; if (!NSArrayIsEmpty(amenities)) @@ -641,20 +636,6 @@ - (void)buildRouteRows:(NSMutableArray *)rows if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_ID])) { - // buildRouteRow(amenities -> { - // String title = app.getString(R.string.route_part_of); - // buildRouteRow(amenities, viewGroupRef, position, ROUTE_PART_OF_ROW_KEY, title); - // }, SearchType.PART_OF); - // - // buildRouteRow(amenities -> { - // String title = app.getString(R.string.multipoligon_related); - // buildRouteRow(amenities, viewGroupRef, position, ROUTE_RELATED_ROUTES_ROW_KEY, title); - // }, SearchType.RELATED); - - - //TODO: add build row with new results - - //TODO: uncomment [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(id _Nullable result) { NSArray *amenities = result; if (!NSArrayIsEmpty(amenities)) @@ -665,8 +646,6 @@ - (void)buildRouteRows:(NSMutableArray *)rows } }]; - - [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeRelated completionHandler:^(NSArray * _Nullable amenities) { if (!NSArrayIsEmpty(amenities)) { @@ -678,13 +657,6 @@ - (void)buildRouteRows:(NSMutableArray *)rows } } - -//protected void buildRouteRow(SearchByRouteIdListener listener, SearchType type) { -// if (amenity != null) { -// OsmAndTaskManager.executeTask(new SearchByRouteIdTask(amenity, type, app, listener)); -// } -//} - - (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString *)tag searchType:(EOASearchByRouteIdTaskSearchType)searchType completionHandler:(void (^ _Nullable)(NSArray * _Nullable amenities))completionHandler { if (![self getTargetObj] || ![[self getTargetObj] isKindOfClass:OAPOI.class]) @@ -699,27 +671,6 @@ - (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString * } } -//private void buildRouteRow(List amenities, WeakReference viewGroupRef, int position, String key, String title) { -// ViewGroup viewGroup1 = viewGroupRef.get(); -// if (viewGroup1 == null || Algorithms.isEmpty(amenities)) { -// return; -// } -// String type = "\"" + AmenityMenuController.getTypeStr(app, amenity) + "\""; -// String count = "(" + amenities.size() + ")"; -// String text = app.getString(R.string.ltr_or_rtl_triple_combine_via_space, title, type, count); -// View wikiRow = viewGroup1.findViewWithTag(NEAREST_WIKI_KEY); -// View amenitiesRow = createRowContainer(viewGroup1.getContext(), key); -// firstRow = position == 0 || isDividerAtPosition(viewGroup1, position - 1); -// int iconId = AmenityMenuController.getRightIconId(app, amenity); -// CollapsableView collapsableView = getCollapsableView(amenitiesRow.getContext(), true, amenities, key); -// buildRow(amenitiesRow, new BuildRowAttrs.Builder().setIconId(iconId).setText(text) -// .setCollapsable(true).setCollapsableView(collapsableView).build()); -// viewGroup1.addView(amenitiesRow, position); -// buildNearestRowDividerIfMissing(viewGroup1, position); -// requestMenuRelayout(viewGroup1); -//} - - - (OAAmenityInfoRow *)buildRouteRow:(NSMutableArray *)rows amenities:(NSArray *)amenities key:(NSString *)key title:(NSString *)title { @@ -738,10 +689,6 @@ - (OAAmenityInfoRow *)buildRouteRow:(NSMutableArray *)rows a return row; } - - - - - (void)buildPluginRows:(NSMutableArray *)rows { [self addOsmRowInfoIfNeeded:rows]; diff --git a/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm b/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm index f297169c05..25108488b4 100644 --- a/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm +++ b/Sources/Controllers/TargetMenu/POI/OAPOIViewController.mm @@ -46,10 +46,6 @@ static const NSInteger WAY_MODULO_REMAINDER = 1; static const NSInteger kOrderShortDescrRow = -10000; -//static NSString * const ROUTE_MEMBERS_ROW_KEY = @"route_members_row_key"; -//static NSString * const ROUTE_PART_OF_ROW_KEY = @"route_part_of_row_key"; -//static NSString * const ROUTE_RELATED_ROUTES_ROW_KEY = @"route_related_routes_row_key"; - @interface OAPOIViewController () @end @@ -59,10 +55,6 @@ @implementation OAPOIViewController OAPOIHelper *_poiHelper; AmenityUIHelper *_amenityUIHelper; std::vector> _openingHoursInfo; - -// OAAmenityInfoRow *routeMembersRow; -// OAAmenityInfoRow *routePartOfRow; -// OAAmenityInfoRow *routeRelatedRow; } static const NSArray *kContactUrlTags = @[@"youtube", @"facebook", @"instagram", @"twitter", @"x", @"vk", @"ok", @"webcam", @"telegram", @"linkedin", @"pinterest", @"foursquare", @"xing", @"flickr", @"email", @"mastodon", @"diaspora", @"gnusocial", @"skype"]; @@ -233,7 +225,6 @@ - (void)buildNearestRowsForAmenity:(NSMutableArray *)rows { [self buildNearestPoiRowForAmenity:rows]; } -// [self buildRouteRows:rows]; } - (void)buildNearestWikiForAmenity:(NSMutableArray *)rows @@ -246,137 +237,6 @@ - (void)buildNearestPoiRowForAmenity:(NSMutableArray *)rows [self buildNearestPoiRow:rows listener:nil]; } -//- (void)buildRouteRows:(NSMutableArray *)rows -//{ -//// //TODO: test -//// if (!self.poi) -//// return; -//// -//// if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_MEMBERS_IDS])) -//// { -//// // buildRouteRow(amenities -> { -//// // String title = app.getString(R.string.route_members); -//// // buildRouteRow(amenities, viewGroupRef, position, ROUTE_MEMBERS_ROW_KEY, title); -//// // }, SearchType.MEMBERS); -//// -////// [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(id _Nullable result) { -////// NSArray *amenities = result; -////// if (NSArrayIsEmpty(amenities)) -////// { -////// NSString *title = OALocalizedString(@"route_members"); -////// [self buildRouteRow:rows amenities:amenities key:ROUTE_MEMBERS_ROW_KEY title:title]; -////// } -////// }]; -//// } -//// -//// if (!NSStringIsEmpty([self.poi getAdditionalInfo:ROUTE_ID])) -//// { -//// // buildRouteRow(amenities -> { -//// // String title = app.getString(R.string.route_part_of); -//// // buildRouteRow(amenities, viewGroupRef, position, ROUTE_PART_OF_ROW_KEY, title); -//// // }, SearchType.PART_OF); -//// // -//// // buildRouteRow(amenities -> { -//// // String title = app.getString(R.string.multipoligon_related); -//// // buildRouteRow(amenities, viewGroupRef, position, ROUTE_RELATED_ROUTES_ROW_KEY, title); -//// // }, SearchType.RELATED); -//// -//// -//// //TODO: add build row with new results -//// -//// //TODO: uncomment -////// [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(id _Nullable result) { -////// NSArray *amenities = result; -////// if (NSArrayIsEmpty(amenities)) -////// { -////// NSString *title = OALocalizedString(@"route_part_of"); -////// [self buildRouteRow:rows amenities:amenities key:ROUTE_PART_OF_ROW_KEY title:title]; -////// } -////// }]; -//// -//// -//// if (routeRelatedRow) -//// { -////// [rows addObject:routeRelatedRow]; -//// [self appendInfoRow:routeRelatedRow]; -//// } -//// else -//// { -//// [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeRelated completionHandler:^(NSArray * _Nullable amenities) { -////// NSArray *amenities = (NSArray *)result; -//// if (!NSArrayIsEmpty(amenities)) -//// { -//// NSString *title = OALocalizedString(@"multipoligon_related"); -//// routeRelatedRow = [self buildRouteRow:rows amenities:amenities key:ROUTE_RELATED_ROUTES_ROW_KEY title:title]; -////// [rows addObject:routeRelatedRow]; -////// [self rebuildRows]; -//// [self appendInfoRow:routeRelatedRow]; -//// } -//// }]; -//// } -//// } -//} - - -////protected void buildRouteRow(SearchByRouteIdListener listener, SearchType type) { -//// if (amenity != null) { -//// OsmAndTaskManager.executeTask(new SearchByRouteIdTask(amenity, type, app, listener)); -//// } -////} -// -//- (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString *)tag searchType:(EOASearchByRouteIdTaskSearchType)searchType completionHandler:(void (^ _Nullable)(NSArray * _Nullable amenities))completionHandler -//{ -// if (self.poi) -// { -// SearchByRouteIdTask *task = [[SearchByRouteIdTask alloc] initWithAmenity:self.poi searchType:searchType]; -// task.completionHandler = completionHandler; -// [task execute]; -// } -//} -// -////private void buildRouteRow(List amenities, WeakReference viewGroupRef, int position, String key, String title) { -//// ViewGroup viewGroup1 = viewGroupRef.get(); -//// if (viewGroup1 == null || Algorithms.isEmpty(amenities)) { -//// return; -//// } -//// String type = "\"" + AmenityMenuController.getTypeStr(app, amenity) + "\""; -//// String count = "(" + amenities.size() + ")"; -//// String text = app.getString(R.string.ltr_or_rtl_triple_combine_via_space, title, type, count); -//// View wikiRow = viewGroup1.findViewWithTag(NEAREST_WIKI_KEY); -//// View amenitiesRow = createRowContainer(viewGroup1.getContext(), key); -//// firstRow = position == 0 || isDividerAtPosition(viewGroup1, position - 1); -//// int iconId = AmenityMenuController.getRightIconId(app, amenity); -//// CollapsableView collapsableView = getCollapsableView(amenitiesRow.getContext(), true, amenities, key); -//// buildRow(amenitiesRow, new BuildRowAttrs.Builder().setIconId(iconId).setText(text) -//// .setCollapsable(true).setCollapsableView(collapsableView).build()); -//// viewGroup1.addView(amenitiesRow, position); -//// buildNearestRowDividerIfMissing(viewGroup1, position); -//// requestMenuRelayout(viewGroup1); -////} -// -// -//- (OAAmenityInfoRow *)buildRouteRow:(NSMutableArray *)rows amenities:(NSArray *)amenities key:(NSString *)key title:(NSString *)title -//{ -// -// NSString *type = [self getTypeStr]; //TODO: test this line -// NSString *count = [NSString stringWithFormat:@"(%lu)", amenities.count]; -// NSString *text = [NSString stringWithFormat:OALocalizedString(@"ltr_or_rtl_triple_combine_via_space"), title, type, count]; -// -// UIImage *icon = [self getIcon]; -// -// OAAmenityInfoRow *row = [[OAAmenityInfoRow alloc] initWithKey:key icon:icon textPrefix:nil text:text textColor:nil isText:YES needLinks:NO order:0 typeName:nil isPhoneNumber:NO isUrl:NO]; -// -// //TODO: implement -// -// row.collapsableView = nil; -// -//// [rows addObject:row]; -// -//// [self rebuildRows]; -// return row; -//} - - - (void)buildNamesRow:(NSMutableArray *)rows { if (!_amenityUIHelper) From ae3d7b7fef210698c16334ee0fc573355a7a40c3 Mon Sep 17 00:00:00 2001 From: Max Kojin Date: Thu, 14 May 2026 20:20:40 +0500 Subject: [PATCH 4/9] show related poi in context menu ui --- OsmAnd.xcodeproj/project.pbxproj | 4 + Sources/Controllers/Map/Layers/OAPOILayer.h | 2 + Sources/Controllers/Map/Layers/OAPOILayer.mm | 7 +- .../Panels/OAMapPanelViewController.mm | 4 +- .../TargetMenu/OACollapsablePoiView.swift | 117 ++++++++++++++++++ .../TargetMenu/OATargetInfoViewController.mm | 54 ++++---- Sources/Helpers/OAAmenitySearcher.mm | 40 ++++-- Sources/Helpers/SearchByRouteIdTask.swift | 3 - 8 files changed, 191 insertions(+), 40 deletions(-) create mode 100644 Sources/Controllers/TargetMenu/OACollapsablePoiView.swift diff --git a/OsmAnd.xcodeproj/project.pbxproj b/OsmAnd.xcodeproj/project.pbxproj index 083499144f..56807b0400 100644 --- a/OsmAnd.xcodeproj/project.pbxproj +++ b/OsmAnd.xcodeproj/project.pbxproj @@ -575,6 +575,7 @@ 32BFF098299137D60023D067 /* OAUploadGPXFilesTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BFF097299137D60023D067 /* OAUploadGPXFilesTask.m */; }; 32C1C4ED2DD4C4F200A053D4 /* OAClickableWayHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 32C1C4E42DD4C4F200A053D4 /* OAClickableWayHelper.mm */; }; 32C1C4EF2DD4C4F200A053D4 /* OAMapSelectionHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 32C1C4E72DD4C4F200A053D4 /* OAMapSelectionHelper.mm */; }; + 32C1EF132FB608B6008BBA40 /* OACollapsablePoiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F01E3C2FB6049D00736C97 /* OACollapsablePoiView.swift */; }; 32C21CB127DFC18E00DE4266 /* OAAlertBottomSheetViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 32C21CB027DFC18E00DE4266 /* OAAlertBottomSheetViewController.mm */; }; 32C21CB327E08EFD00DE4266 /* OAAlertBottomSheetViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32C21CB227E08E4D00DE4266 /* OAAlertBottomSheetViewController.xib */; }; 32C21CB727E0A66B00DE4266 /* OARecordSettingsBottomSheetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C21CB627E0A66A00DE4266 /* OARecordSettingsBottomSheetViewController.m */; }; @@ -4253,6 +4254,7 @@ 32ECBEAD2657AD7F005D33BD /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; 32EEB6302E28651B002216B0 /* OATravelSelectionLayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OATravelSelectionLayer.h; sourceTree = ""; }; 32EEB6312E286521002216B0 /* OATravelSelectionLayer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = OATravelSelectionLayer.mm; sourceTree = ""; }; + 32F01E3C2FB6049D00736C97 /* OACollapsablePoiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OACollapsablePoiView.swift; sourceTree = ""; }; 32F3A6A92D5E1958008AE4CA /* PoiIconCollectionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoiIconCollectionHandler.swift; sourceTree = ""; }; 32F3A6AB2D5E1A41008AE4CA /* IconsAppearanceCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconsAppearanceCategory.swift; sourceTree = ""; }; 32F6B3902E47A842007F3902 /* OAAmenitySearcher+cpp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OAAmenitySearcher+cpp.h"; sourceTree = ""; }; @@ -12730,6 +12732,7 @@ DA5A7E1C26C563A300F274C7 /* OACollapsableTransportStopRoutesView.h */, DA5A7DF926C563A300F274C7 /* OACollapsableTransportStopRoutesView.mm */, 320DF4112F43799C00F80CE3 /* OACollapsableTravelGuidesView.swift */, + 32F01E3C2FB6049D00736C97 /* OACollapsablePoiView.swift */, DA5A7EB726C563A400F274C7 /* OACollapsableWaypointsView.h */, DA5A7E2426C563A400F274C7 /* OACollapsableWaypointsView.mm */, DA5A7EB426C563A400F274C7 /* OAEditPointViewController.h */, @@ -17961,6 +17964,7 @@ DA8DC3982A0CF47D006C116B /* MapMarkerSideWidget.swift in Sources */, FA6408752D3E8047005FD12D /* WikiImageCard.swift in Sources */, DA8DC39D2A0E2247006C116B /* OAMapViewHelper.mm in Sources */, + 32C1EF132FB608B6008BBA40 /* OACollapsablePoiView.swift in Sources */, DA5A81EB26C563A700F274C7 /* OAHistoryDB.mm in Sources */, 32D253B227F336C200324717 /* OACloudBackupViewController.mm in Sources */, DA5A846526C563A900F274C7 /* OADownloadedRegionsLayer.mm in Sources */, diff --git a/Sources/Controllers/Map/Layers/OAPOILayer.h b/Sources/Controllers/Map/Layers/OAPOILayer.h index 3b927965c4..a8e31b7b06 100644 --- a/Sources/Controllers/Map/Layers/OAPOILayer.h +++ b/Sources/Controllers/Map/Layers/OAPOILayer.h @@ -11,4 +11,6 @@ @interface OAPOILayer : OASymbolMapLayer ++ (OATargetPoint *)getTargetPoint:(id)obj; + @end diff --git a/Sources/Controllers/Map/Layers/OAPOILayer.mm b/Sources/Controllers/Map/Layers/OAPOILayer.mm index ffad737e78..5570ca0780 100644 --- a/Sources/Controllers/Map/Layers/OAPOILayer.mm +++ b/Sources/Controllers/Map/Layers/OAPOILayer.mm @@ -1457,6 +1457,11 @@ - (NSString *) getAmenityName:(OAPOI *)amenity #pragma mark - OAContextMenuProvider - (OATargetPoint *)getTargetPoint:(id)obj touchLocation:(CLLocation *)touchLocation +{ + return [self.class getTargetPoint:obj]; +} + ++ (OATargetPoint *)getTargetPoint:(id)obj { if ([obj isKindOfClass:OAPOI.class]) return [self getTargetPoint:obj renderedObject:nil placeDetailsObject:nil]; @@ -1467,7 +1472,7 @@ - (OATargetPoint *)getTargetPoint:(id)obj touchLocation:(CLLocation *)touchLocat return nil; } -- (OATargetPoint *) getTargetPoint:(OAPOI *)poi renderedObject:(OARenderedObject *)renderedObject placeDetailsObject:(BaseDetailsObject *)placeDetailsObject ++ (OATargetPoint *) getTargetPoint:(OAPOI *)poi renderedObject:(OARenderedObject *)renderedObject placeDetailsObject:(BaseDetailsObject *)placeDetailsObject { if (placeDetailsObject) poi = placeDetailsObject.syntheticAmenity; diff --git a/Sources/Controllers/Panels/OAMapPanelViewController.mm b/Sources/Controllers/Panels/OAMapPanelViewController.mm index f9a2fcc0a4..13097226ee 100644 --- a/Sources/Controllers/Panels/OAMapPanelViewController.mm +++ b/Sources/Controllers/Panels/OAMapPanelViewController.mm @@ -1389,7 +1389,7 @@ - (void) showContextMenuWithPoints:(NSArray *)targetPoints sele if (_activeTargetType == OATargetRouteIntermediateSelection && targetPoints.count > 1) { [validPoints addObjectsFromArray:targetPoints]; - if (selectedObjects) + if (!NSArrayIsEmpty(selectedObjects)) [validSelectedObjects addObjectsFromArray:selectedObjects]; } else @@ -1400,7 +1400,7 @@ - (void) showContextMenuWithPoints:(NSArray *)targetPoints sele if ([self processTargetPoint:targetPoint]) { [validPoints addObject:targetPoint]; - if (selectedObjects) + if (!NSArrayIsEmpty(selectedObjects)) [validSelectedObjects addObject:selectedObjects[i]]; } } diff --git a/Sources/Controllers/TargetMenu/OACollapsablePoiView.swift b/Sources/Controllers/TargetMenu/OACollapsablePoiView.swift new file mode 100644 index 0000000000..ce35731126 --- /dev/null +++ b/Sources/Controllers/TargetMenu/OACollapsablePoiView.swift @@ -0,0 +1,117 @@ +// +// OACollapsablePoiView.swift +// OsmAnd +// +// Created by Max Kojin on 14/05/26. +// Copyright © 2026 OsmAnd. All rights reserved. +// + +@objcMembers +final class OACollapsablePoiView: OACollapsableView { + + private let kButtonHeight: CGFloat = 36.0 + + private var titles = [String]() + private var amenities = [OAPOI]() + private var buttons = [OAButton]() + private var selectedButtonIndex = 0 + + func setData(titles: [String], amenities: [OAPOI]) { + self.amenities = amenities + self.titles = titles + buildViews() + } + + func updateLayout(width: CGFloat) { + var y: CGFloat = 0.0 + var viewHeight: CGFloat = 0.0 + var i = 0 + for button in buttons { + if i > 0 { + y += kButtonHeight + 10.0 + viewHeight += 10.0 + } + + let height: CGFloat = kButtonHeight + button.frame = CGRect(x: kMarginLeft, y: y, width: width - kMarginLeft - kMarginRight, height: height) + viewHeight += button.frame.size.height + i += 1 + } + + viewHeight += 8.0 + frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: width, height: viewHeight) + } + + private func buildViews() { + for i in 0.. OAButton { + let btn = OAButton(type: .system) + btn.setTitle(title, for: .normal) + btn.contentHorizontalAlignment = .left + btn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 12.0, bottom: 0, right: 12.0) + btn.titleLabel?.lineBreakMode = .byTruncatingTail + btn.titleLabel?.font = .preferredFont(forTextStyle: .footnote) + btn.layer.cornerRadius = 4.0 + btn.layer.masksToBounds = true + btn.layer.borderWidth = 0.8 + btn.layer.borderColor = UIColor.customSeparator.cgColor + btn.setBackgroundImage(OAUtilities.image(with: .clear), for: .normal) + btn.tintColor = UIColor.iconColorActive + btn.delegate = self + return btn + } + + override func copy(_ sender: Any?) { + guard buttons.count > selectedButtonIndex else { return } + let button = buttons[selectedButtonIndex] + let pasteboard = UIPasteboard.general + pasteboard.string = button.titleLabel?.text + } + + private func updateButtonBorderColor() { + for button in buttons { + button.layer.borderColor = UIColor.customSeparator.cgColor + } + } + + override var canBecomeFirstResponder: Bool { + true + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + updateButtonBorderColor() + } + } + + override func adjustHeight(forWidth width: CGFloat) { + updateLayout(width: width) + } +} + +extension OACollapsablePoiView: OAButtonDelegate { + + func onButtonTapped(_ tag: Int) { + guard amenities.count > tag else { return } + let amenity = amenities[tag] + if let targetPoint = OAPOILayer.getTargetPoint(amenity) { + targetPoint.centerMap = true + OARootViewController.instance().mapPanel.showContextMenu(with: [targetPoint], selectedObjects: [], touchPointLatLon: CLLocation(latitude: targetPoint.location.latitude, longitude: targetPoint.location.longitude)) + } + } + + func onButtonLongPressed(_ tag: Int) { + selectedButtonIndex = tag + guard buttons.count > selectedButtonIndex else { return } + OAUtilities.showMenu(in: self, from: buttons[selectedButtonIndex]) + } +} diff --git a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm index e6c8ba378b..1b09b7d5fe 100644 --- a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm +++ b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm @@ -623,28 +623,28 @@ - (void)buildRouteRows:(NSMutableArray *)rows if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_MEMBERS_IDS])) { - [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(id _Nullable result) { - NSArray *amenities = result; - if (!NSArrayIsEmpty(amenities)) - { - NSString *title = OALocalizedString(@"route_members"); - OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_MEMBERS_ROW_KEY title:title]; - [self appendInfoRow:row]; - } - }]; +// [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(id _Nullable result) { +// NSArray *amenities = result; +// if (!NSArrayIsEmpty(amenities)) +// { +// NSString *title = OALocalizedString(@"route_members"); +// OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_MEMBERS_ROW_KEY title:title]; +// [self appendInfoRow:row]; +// } +// }]; } if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_ID])) { - [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(id _Nullable result) { - NSArray *amenities = result; - if (!NSArrayIsEmpty(amenities)) - { - NSString *title = OALocalizedString(@"route_part_of"); - OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_PART_OF_ROW_KEY title:title]; - [self appendInfoRow:row]; - } - }]; +// [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(id _Nullable result) { +// NSArray *amenities = result; +// if (!NSArrayIsEmpty(amenities)) +// { +// NSString *title = OALocalizedString(@"route_part_of"); +// OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_PART_OF_ROW_KEY title:title]; +// [self appendInfoRow:row]; +// } +// }]; [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeRelated completionHandler:^(NSArray * _Nullable amenities) { if (!NSArrayIsEmpty(amenities)) @@ -673,18 +673,26 @@ - (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString * - (OAAmenityInfoRow *)buildRouteRow:(NSMutableArray *)rows amenities:(NSArray *)amenities key:(NSString *)key title:(NSString *)title { - - NSString *type = [self getTypeStr]; //TODO: test this line + NSString *type = [NSString stringWithFormat:@"\"%@\"", [self getTypeStr]]; NSString *count = [NSString stringWithFormat:@"(%lu)", amenities.count]; NSString *text = [NSString stringWithFormat:OALocalizedString(@"ltr_or_rtl_triple_combine_via_space"), title, type, count]; UIImage *icon = [self getIcon]; - + if (!icon && [self getTargetObj]) + icon = [[OAPOILayer getTargetPoint:[self getTargetObj]] icon]; + OAAmenityInfoRow *row = [[OAAmenityInfoRow alloc] initWithKey:key icon:icon textPrefix:nil text:text textColor:nil isText:YES needLinks:NO order:0 typeName:nil isPhoneNumber:NO isUrl:NO]; - //TODO: implement + NSMutableArray *titles = [NSMutableArray new]; + for (OAPOI *amenity in amenities) + { + NSString * title = [[OAPOILayer getTargetPoint:amenity] title]; + [titles addObject:title ? title : @""]; + } - row.collapsableView = nil; + OACollapsablePoiView *collapsableView = [[OACollapsablePoiView alloc] init]; + [collapsableView setDataWithTitles:titles amenities:amenities]; + row.collapsableView = collapsableView; return row; } diff --git a/Sources/Helpers/OAAmenitySearcher.mm b/Sources/Helpers/OAAmenitySearcher.mm index 4ad31028ef..dda103e1a4 100644 --- a/Sources/Helpers/OAAmenitySearcher.mm +++ b/Sources/Helpers/OAAmenitySearcher.mm @@ -1955,12 +1955,12 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr //TODO: implement NSMutableArray *result = [NSMutableArray new]; - //TODO: use this like in androd +// //TODO: use this like in androd // OsmAnd::PointI topLeft = OsmAnd::PointI(0, 0); // OsmAnd::PointI bottomRight = OsmAnd::PointI(INT_MAX, INT_MAX); // OsmAnd::AreaI bbox31 = OsmAnd::AreaI(topLeft, bottomRight); - //TODO: for quick testing +// TODO: for quick testing OsmAnd::LatLon latLon(50.448514, 30.495601); const auto location = OsmAnd::Utilities::convertLatLonTo31(latLon); OsmAnd::AreaI bbox31 = (OsmAnd::AreaI)OsmAnd::Utilities::boundingBox31FromAreaInMeters(10000, location); @@ -1976,15 +1976,14 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr } -- (NSMutableArray *) searchPoiByName:(NSString *)code mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher bbox31:(OsmAnd::AreaI)bbox31 +- (NSMutableArray *) searchPoiByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher bbox31:(OsmAnd::AreaI)bbox31 { -// NSString *code = @"R5121502"; QString qKey = QString::fromNSString(ROUTE_ID); - QString qCode = QString::fromNSString(code); - + QString qCode = QString::fromNSString(multipleSearch); OsmAndAppInstance app = [OsmAndApp instance]; const auto& obfsCollection = app.resourcesManager->obfsCollection; + NSMutableArray *res = [NSMutableArray new]; std::shared_ptr ctrl; ctrl.reset(new OsmAnd::FunctorQueryController([&matcher] @@ -1993,25 +1992,44 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return [matcher isCancelled]; })); + //TODO: delete AmenitiesInAreaSearch const std::shared_ptr& searchCriteria = std::shared_ptr(new OsmAnd::AmenitiesInAreaSearch::Criteria); - - searchCriteria->bbox31 = bbox31; const auto search = std::shared_ptr(new OsmAnd::AmenitiesInAreaSearch(obfsCollection)); - NSMutableArray *res = [NSMutableArray new]; search->performSearch(*searchCriteria, // [&osmId, &res, &cancel] - [&res, &code, &qCode, &qKey, &matcher] + [&res, &multipleSearch, &qCode, &qKey, &matcher] (const OsmAnd::ISearch::Criteria& criteria, const OsmAnd::ISearch::IResultEntry& resultEntry) { - + BOOL foobar = [matcher publish:[NSValue valueWithPointer:&resultEntry]]; }, ctrl); + + + //TODO: use AmenitiesByNameSearch + +// const std::shared_ptr& searchCriteria = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch::Criteria); +// +// searchCriteria->name = multipleSearch; +//// searchCriteria->obfInfoAreaFilter = _visibleArea; // ??? +// searchCriteria->bbox31 = bbox31; +// +// const auto search = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch(obfsCollection)); +// search->performSearch(*searchCriteria, +// [self, &matcher] +// (const OsmAnd::ISearch::Criteria& criteria, const OsmAnd::ISearch::IResultEntry& resultEntry) +// { +//// [self onPOIFound:resultEntry]; +// BOOL foobar = [matcher publish:[NSValue valueWithPointer:&resultEntry]]; +// }, +// ctrl); + + return res; //TODO: delete? } diff --git a/Sources/Helpers/SearchByRouteIdTask.swift b/Sources/Helpers/SearchByRouteIdTask.swift index a61663e7e8..fb267f9d59 100644 --- a/Sources/Helpers/SearchByRouteIdTask.swift +++ b/Sources/Helpers/SearchByRouteIdTask.swift @@ -63,8 +63,6 @@ final class SearchByRouteIdTask : OAAsyncTask { self.searchType = searchType self.amenity = amenity - //this.listener = listener; - super.init() } @@ -126,7 +124,6 @@ final class SearchByRouteIdTask : OAAsyncTask { override func onPostExecute(result: Any?) { - //TODO: implement if let completionHandler, let amenities = result as? [OAPOI] { completionHandler(amenities) } From 14c32ecf67a2767bb528adbc632e0e4bd2095273 Mon Sep 17 00:00:00 2001 From: Max Kojin Date: Fri, 15 May 2026 13:52:32 +0500 Subject: [PATCH 5/9] clean code --- .../TargetMenu/OATargetInfoViewController.mm | 84 ++++++++--------- Sources/Helpers/OAAmenitySearcher.mm | 49 ++++++++-- Sources/Helpers/SearchByRouteIdTask.swift | 89 +++++++------------ 3 files changed, 114 insertions(+), 108 deletions(-) diff --git a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm index 1b09b7d5fe..04f49ff560 100644 --- a/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm +++ b/Sources/Controllers/TargetMenu/OATargetInfoViewController.mm @@ -214,6 +214,16 @@ + (UIImage *) getIcon:(NSString *)fileName size:(CGSize)size return img; } +- (OAPOI *) getTargetPoiIfExisted +{ + id obj = [self getTargetObj]; + if (obj && [[self getTargetObj] isKindOfClass:OAPOI.class]) + { + return ((OAPOI *) obj); + } + return nil; +} + - (void) buildTopInternal:(NSMutableArray *)rows { [self buildDescription:rows]; @@ -487,10 +497,9 @@ - (void)buildNearestWikiRow:(NSMutableArray *)rows listener: } else { - id targetObj = [self getTargetObj]; - if ([targetObj isKindOfClass:OAPOI.class]) + OAPOI *poi = [self getTargetPoiIfExisted]; + if (poi) { - OAPOI *poi = (OAPOI *) targetObj; [self processNearestWiki:poi]; NSArray *nearest = _nearestWiki; @@ -531,8 +540,8 @@ - (void)buildNearestPoiRow:(NSMutableArray *)rows listener:( if (OARowsContainKey(rows, @"nearest_poi")) return; - OAPOI *poi = [self getTargetObj]; - if (![poi isKindOfClass:OAPOI.class] || ![self showNearestPoi]) + OAPOI *poi = [self getTargetPoiIfExisted]; + if (!poi || ![self showNearestPoi]) return; OAPOIUIFilter *filter = [self getPoiFilterForType:poi isWiki:NO]; @@ -616,35 +625,32 @@ static inline BOOL OARowsContainKey(NSArray *rows, NSString - (void)buildRouteRows:(NSMutableArray *)rows { - //TODO: test - if (![self getTargetObj] || ![[self getTargetObj] isKindOfClass:OAPOI.class]) + OAPOI *amenity = [self getTargetPoiIfExisted]; + if (!amenity) return; - OAPOI *amenity = [self getTargetObj]; if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_MEMBERS_IDS])) { -// [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(id _Nullable result) { -// NSArray *amenities = result; -// if (!NSArrayIsEmpty(amenities)) -// { -// NSString *title = OALocalizedString(@"route_members"); -// OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_MEMBERS_ROW_KEY title:title]; -// [self appendInfoRow:row]; -// } -// }]; + [self buildRouteRow:rows tag:ROUTE_MEMBERS_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeMembers completionHandler:^(NSArray * _Nullable amenities) { + if (!NSArrayIsEmpty(amenities)) + { + NSString *title = OALocalizedString(@"route_members"); + OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_MEMBERS_ROW_KEY title:title]; + [self appendInfoRow:row]; + } + }]; } if (!NSStringIsEmpty([amenity getAdditionalInfo:ROUTE_ID])) { -// [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(id _Nullable result) { -// NSArray *amenities = result; -// if (!NSArrayIsEmpty(amenities)) -// { -// NSString *title = OALocalizedString(@"route_part_of"); -// OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_PART_OF_ROW_KEY title:title]; -// [self appendInfoRow:row]; -// } -// }]; + [self buildRouteRow:rows tag:ROUTE_PART_OF_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypePartOf completionHandler:^(NSArray * _Nullable amenities) { + if (!NSArrayIsEmpty(amenities)) + { + NSString *title = OALocalizedString(@"route_part_of"); + OAAmenityInfoRow *row = [self buildRouteRow:rows amenities:amenities key:ROUTE_PART_OF_ROW_KEY title:title]; + [self appendInfoRow:row]; + } + }]; [self buildRouteRow:rows tag:ROUTE_RELATED_ROUTES_ROW_KEY searchType:EOASearchByRouteIdTaskSearchTypeRelated completionHandler:^(NSArray * _Nullable amenities) { if (!NSArrayIsEmpty(amenities)) @@ -659,14 +665,10 @@ - (void)buildRouteRows:(NSMutableArray *)rows - (void)buildRouteRow:(NSMutableArray *)rows tag:(NSString *)tag searchType:(EOASearchByRouteIdTaskSearchType)searchType completionHandler:(void (^ _Nullable)(NSArray * _Nullable amenities))completionHandler { - if (![self getTargetObj] || ![[self getTargetObj] isKindOfClass:OAPOI.class]) - return; - OAPOI *amenity = [self getTargetObj]; - + OAPOI *amenity = [self getTargetPoiIfExisted]; if (amenity) { - SearchByRouteIdTask *task = [[SearchByRouteIdTask alloc] initWithAmenity:amenity searchType:searchType]; - task.completionHandler = completionHandler; + SearchByRouteIdTask *task = [[SearchByRouteIdTask alloc] initWithAmenity:amenity searchType:searchType completionHandler:completionHandler]; [task execute]; } } @@ -958,9 +960,9 @@ - (void)sendNearbyOtherImagesRequest:(NSMutableArray *)cards return; NSString *openPlaceReviewsTagContent = nil; - if ([self.getTargetObj isKindOfClass:OAPOI.class]) + OAPOI *poi = [self getTargetPoiIfExisted]; + if (poi) { - OAPOI *poi = self.getTargetObj; openPlaceReviewsTagContent = @(poi.obfId >> 1).stringValue; } @@ -1740,8 +1742,8 @@ - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath if (![self isKindOfClass:OAPOIViewController.class]) return; - id target = [self getTargetObj]; - if (![target isKindOfClass:OAPOI.class]) + OAPOI *poi = [self getTargetPoiIfExisted]; + if (!poi) return; if (!isWikiPurchased) @@ -1751,7 +1753,7 @@ - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } OAWikiWebViewController *wikiController = - [[OAWikiWebViewController alloc] initWithPoi:target]; + [[OAWikiWebViewController alloc] initWithPoi:poi]; [OARootViewController.instance.mapPanel.navigationController pushViewController:wikiController @@ -1783,9 +1785,9 @@ - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath else if ([info.typeName isEqualToString:kShortDescriptionTravelRowType]) { NSString *routeId = info.hiddenUrl; - if (!NSStringIsEmpty(routeId) && [[self getTargetObj] isKindOfClass:OAPOI.class]) + OAPOI *poi = [self getTargetPoiIfExisted]; + if (!NSStringIsEmpty(routeId) && poi) { - OAPOI *poi = [self getTargetObj]; NSDictionary *routeIdMap = @{routeId : [poi getLocation]}; SearchTravelArticlesTask *task = [[SearchTravelArticlesTask alloc] initWithRouteIds:routeIdMap callback:^(NSDictionary *> * _Nonnull result) { @@ -1858,9 +1860,9 @@ - (void)startLoadingImages } onlinePhotoCardsView.isLoading = YES; - if ([self.getTargetObj isKindOfClass:OAPOI.class]) + OAPOI *poi = [self getTargetPoiIfExisted]; + if (poi) { - OAPOI *poi = self.getTargetObj; onlinePhotoCardsView.title = poi.nameLocalized ?: poi.name; } diff --git a/Sources/Helpers/OAAmenitySearcher.mm b/Sources/Helpers/OAAmenitySearcher.mm index dda103e1a4..80a6d6f750 100644 --- a/Sources/Helpers/OAAmenitySearcher.mm +++ b/Sources/Helpers/OAAmenitySearcher.mm @@ -1841,21 +1841,54 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr - (NSArray *)searchRoutePartOf:(NSString *)routeId { - //TODO: implement - OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:CHECK_EQUALS_FROM_SPACE]; + OAResultMatcher *matcher = [[OAResultMatcher alloc] initWithPublishFunc:^BOOL(__autoreleasing id *objectPtr) { + + if (objectPtr == nil || *objectPtr == nil) + return false; + + NSValue *value = (NSValue *)*objectPtr; + const OsmAnd::ISearch::IResultEntry *resultEntry = static_cast([value pointerValue]); + + if (resultEntry) + { + const auto amenity = OAGetAmenityFromSearchResult(*resultEntry); + if (amenity) + { + //TODO: implement + +// QHash valuesHash = amenity->getDecodedValuesHash(); +// +// const auto it = valuesHash.constFind(qRouteIdKey); +// if (it != valuesHash.constEnd()) +// { +// const QString qRouteIdValue = it.value(); +// +// if (routeIds.contains(qRouteIdValue)) +// { +// OAPOI *poi = [OAAmenitySearcher parsePOI:*resultEntry]; +// if (poi) +// { +// [result addObject:poi]; +// return YES; +// } +// } +// } + } + } + return NO; + + } cancelledFunc:^BOOL{ + return false; + }]; + - return [self searchRouteByName:routeId mode:mode matcher:nil]; + return [self searchRouteByName:routeId mode:mode matcher:matcher]; } - - -//public Map> searchRouteMembers(String multipleSearch) { - - (NSDictionary *> *)searchRouteMembers:(NSString *)multipleSearch { - //TODO: implement QString qRouteIdKey = QString::fromNSString(ROUTE_ID); QSet routeIds; diff --git a/Sources/Helpers/SearchByRouteIdTask.swift b/Sources/Helpers/SearchByRouteIdTask.swift index fb267f9d59..629ec1975c 100644 --- a/Sources/Helpers/SearchByRouteIdTask.swift +++ b/Sources/Helpers/SearchByRouteIdTask.swift @@ -9,38 +9,7 @@ typealias SearchByRouteIdTaskResultBlock = (_ result: Any?) -> Void @objcMembers -final class SearchByRouteIdTask : OAAsyncTask { - - var completionHandler: (([OAPOI]) -> Void)? = nil - -// protected SearchByRouteIdTask(@Nullable Amenity amenity, SearchType type, OsmandApplication app, SearchByRouteIdListener listener) { -// this.listener = listener; -// if (amenity != null) { -// routeId = amenity.getAdditionalInfo(Amenity.ROUTE_ID); -// routeMembersIds = amenity.getAdditionalInfo(Amenity.ROUTE_MEMBERS_IDS); -// } else { -// routeId = null; -// routeMembersIds = null; -// } -// searchType = type; -// this.app = app; -// this.amenity = amenity; -// } -// -// public SearchByRouteIdTask(String routeId, String routeMembersIds, SearchType type, OsmandApplication app, SearchByRouteIdListener listener) { -// this.routeId = routeId; -// this.routeMembersIds = routeMembersIds; -// this.listener = listener; -// this.searchType = type; -// this.app = app; -// this.amenity = null; -// } - -// init(routeIds: [String: CLLocation], callback: @escaping (([String: [String: TravelArticle]]) -> Void)) { -// self.routeIds = routeIds -// self.callback = callback -// super.init() -// } +final class SearchByRouteIdTask: OAAsyncTask { @objc(EOASearchByRouteIdTaskSearchType) enum SearchType: Int { @@ -49,16 +18,19 @@ final class SearchByRouteIdTask : OAAsyncTask { case members } + var completionHandler: (([OAPOI]) -> Void)? + private var amenity: OAPOI? private var searchType: SearchType private var routeId: String? private var routeMembersIds: String? - init(amenity: OAPOI?, searchType: SearchType) { + init(amenity: OAPOI?, searchType: SearchType, completionHandler: (([OAPOI]) -> Void)?) { + self.completionHandler = completionHandler if let amenity { - routeId = amenity.getAdditionalInfo(ROUTE_ID) - routeMembersIds = amenity.getAdditionalInfo(ROUTE_MEMBERS_IDS) + self.routeId = amenity.getAdditionalInfo(ROUTE_ID) + self.routeMembersIds = amenity.getAdditionalInfo(ROUTE_MEMBERS_IDS) } self.searchType = searchType @@ -71,16 +43,16 @@ final class SearchByRouteIdTask : OAAsyncTask { let amenitySearcher = OAAmenitySearcher() if searchType == .members { -// if let routeMembersIds, !routeMembersIds.isEmpty { -// let members = amenitySearcher.searchRouteMembers(routeMembersIds) -// -// for entry in members { -// let amenityList = entry.value -// if !amenityList.isEmpty { -// amenities.append(amenityList[0]) -// } -// } -// } + if let routeMembersIds, !routeMembersIds.isEmpty { + let members = amenitySearcher.searchRouteMembers(routeMembersIds) + + for entry in members { + let amenityList = entry.value + if !amenityList.isEmpty { + amenities.append(amenityList[0]) + } + } + } } else if searchType == .related { if let routeId, !routeId.isEmpty { let related = amenitySearcher.searchRouteMembers(routeId) @@ -105,24 +77,23 @@ final class SearchByRouteIdTask : OAAsyncTask { } } } else if searchType == .partOf { -// if let routeId, !routeId.isEmpty { -// let list = amenitySearcher.searchRoutePart(of: routeId) -// var routeIdHash = Set() -// -// for am in list { -// if let routeId = am.getAdditionalInfo(ROUTE_ID) { -// if !routeIdHash.contains(routeId) { -// amenities.append(am) -// } -// routeIdHash.insert(routeId) -// } -// } -// } + if let routeId, !routeId.isEmpty { + let list = amenitySearcher.searchRoutePart(of: routeId) + var routeIdHash = Set() + + for am in list { + if let routeId = am.getAdditionalInfo(ROUTE_ID) { + if !routeIdHash.contains(routeId) { + amenities.append(am) + } + routeIdHash.insert(routeId) + } + } + } } return amenities } - override func onPostExecute(result: Any?) { if let completionHandler, let amenities = result as? [OAPOI] { completionHandler(amenities) From e462d6ba76b3bd42780fb8a716b40b06cc36b11d Mon Sep 17 00:00:00 2001 From: Max Kojin Date: Mon, 18 May 2026 12:39:04 +0500 Subject: [PATCH 6/9] clean code --- Sources/Helpers/OAAmenitySearcher.mm | 118 ++++++++------------------- 1 file changed, 35 insertions(+), 83 deletions(-) diff --git a/Sources/Helpers/OAAmenitySearcher.mm b/Sources/Helpers/OAAmenitySearcher.mm index 80a6d6f750..53f993e6f8 100644 --- a/Sources/Helpers/OAAmenitySearcher.mm +++ b/Sources/Helpers/OAAmenitySearcher.mm @@ -1819,32 +1819,13 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return [NSArray arrayWithArray:arr]; } -//public List searchRoutePartOf(final String routeId) { -// ResultMatcher matcher = new ResultMatcher() { -// public boolean publish(Amenity amenity) { -// String members = amenity.getAdditionalInfo("route_members_ids"); -// if (members != null) { -// HashSet ids = new HashSet(); -// Collections.addAll(ids, members.split(" ")); -// return ids.contains(routeId); -// } else { -// return false; -// } -// } -// -// public boolean isCancelled() { -// return false; -// } -// }; -// return this.searchRouteByName(routeId, StringMatcherMode.CHECK_EQUALS_FROM_SPACE, matcher); -//} - - (NSArray *)searchRoutePartOf:(NSString *)routeId { - OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:CHECK_EQUALS_FROM_SPACE]; + NSMutableArray *result = [NSMutableArray new]; + QString qRouteMebersIdKey = QString::fromNSString(@"route_members_ids"); + QString qRouteId = QString::fromNSString(routeId); OAResultMatcher *matcher = [[OAResultMatcher alloc] initWithPublishFunc:^BOOL(__autoreleasing id *objectPtr) { - if (objectPtr == nil || *objectPtr == nil) return false; @@ -1856,25 +1837,25 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr const auto amenity = OAGetAmenityFromSearchResult(*resultEntry); if (amenity) { - //TODO: implement - -// QHash valuesHash = amenity->getDecodedValuesHash(); -// -// const auto it = valuesHash.constFind(qRouteIdKey); -// if (it != valuesHash.constEnd()) -// { -// const QString qRouteIdValue = it.value(); -// -// if (routeIds.contains(qRouteIdValue)) -// { -// OAPOI *poi = [OAAmenitySearcher parsePOI:*resultEntry]; -// if (poi) -// { -// [result addObject:poi]; -// return YES; -// } -// } -// } + QHash valuesHash = amenity->getDecodedValuesHash(); + const auto it = valuesHash.constFind(qRouteMebersIdKey); + if (it != valuesHash.constEnd()) + { + const QString members = it.value(); + if (!members.isEmpty()) + { + const QStringList ids = members.split(QLatin1Char(' '), Qt::SkipEmptyParts); + if (ids.contains(qRouteId)) + { + OAPOI *poi = [OAAmenitySearcher parsePOI:*resultEntry]; + if (poi) + { + [result addObject:poi]; + return YES; + } + } + } + } } } return NO; @@ -1883,12 +1864,14 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return false; }]; - - return [self searchRouteByName:routeId mode:mode matcher:matcher]; + OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:CHECK_EQUALS_FROM_SPACE]; + [self searchRouteByName:routeId mode:mode matcher:matcher]; + return result; } - (NSDictionary *> *)searchRouteMembers:(NSString *)multipleSearch { + NSMutableArray *result = [NSMutableArray new]; QString qRouteIdKey = QString::fromNSString(ROUTE_ID); QSet routeIds; @@ -1899,29 +1882,23 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr routeIds.insert(QString::fromNSString(routeId)); } - NSMutableArray *result = [NSMutableArray new]; - OAResultMatcher *matcher = [[OAResultMatcher alloc] initWithPublishFunc:^BOOL(__autoreleasing id *objectPtr) { - if (objectPtr == nil || *objectPtr == nil) return false; NSValue *value = (NSValue *)*objectPtr; const OsmAnd::ISearch::IResultEntry *resultEntry = static_cast([value pointerValue]); - if (resultEntry) { const auto amenity = OAGetAmenityFromSearchResult(*resultEntry); if (amenity) { QHash valuesHash = amenity->getDecodedValuesHash(); - const auto it = valuesHash.constFind(qRouteIdKey); if (it != valuesHash.constEnd()) { const QString qRouteIdValue = it.value(); - if (routeIds.contains(qRouteIdValue)) { OAPOI *poi = [OAAmenitySearcher parsePOI:*resultEntry]; @@ -1940,10 +1917,7 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return false; }]; - - OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:MULTISEARCH]; -// NSArray *result = [self searchRouteByName:multipleSearch mode:mode matcher:matcher]; [self searchRouteByName:multipleSearch mode:mode matcher:matcher]; NSMutableDictionary *> *map = [NSMutableDictionary new]; @@ -1957,17 +1931,10 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr [map[routeId] addObject:am]; } -// for (String id : ids) { -// if (!map.containsKey(id)) { -// map.put(id, null); -// } -// } - return map; } - //private List searchRouteByName(String multipleSearch, CollatorStringMatcher.StringMatcherMode mode, ResultMatcher matcher) { // List result = new ArrayList(); // BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, multipleSearch, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, matcher); @@ -1983,9 +1950,9 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr // return result; //} -- (NSArray *)searchRouteByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher +//TODO: implement +- (void)searchRouteByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher { - //TODO: implement NSMutableArray *result = [NSMutableArray new]; // //TODO: use this like in androd @@ -1993,23 +1960,15 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr // OsmAnd::PointI bottomRight = OsmAnd::PointI(INT_MAX, INT_MAX); // OsmAnd::AreaI bbox31 = OsmAnd::AreaI(topLeft, bottomRight); -// TODO: for quick testing +// TODO: delete after quick testing OsmAnd::LatLon latLon(50.448514, 30.495601); const auto location = OsmAnd::Utilities::convertLatLonTo31(latLon); OsmAnd::AreaI bbox31 = (OsmAnd::AreaI)OsmAnd::Utilities::boundingBox31FromAreaInMeters(10000, location); - - NSMutableArray * amenities = [self searchPoiByName:multipleSearch mode:mode matcher:matcher bbox31:bbox31]; - if (!NSArrayIsEmpty(amenities)) - { - [result addObjectsFromArray:amenities]; - } - - return result; //TODO: delete? + [self searchPoiByName:multipleSearch mode:mode matcher:matcher bbox31:bbox31]; } - -- (NSMutableArray *) searchPoiByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher bbox31:(OsmAnd::AreaI)bbox31 +- (void) searchPoiByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher bbox31:(OsmAnd::AreaI)bbox31 { QString qKey = QString::fromNSString(ROUTE_ID); QString qCode = QString::fromNSString(multipleSearch); @@ -2032,24 +1991,20 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr const auto search = std::shared_ptr(new OsmAnd::AmenitiesInAreaSearch(obfsCollection)); search->performSearch(*searchCriteria, -// [&osmId, &res, &cancel] [&res, &multipleSearch, &qCode, &qKey, &matcher] (const OsmAnd::ISearch::Criteria& criteria, const OsmAnd::ISearch::IResultEntry& resultEntry) { - - BOOL foobar = [matcher publish:[NSValue valueWithPointer:&resultEntry]]; - + [matcher publish:[NSValue valueWithPointer:&resultEntry]]; }, ctrl); //TODO: use AmenitiesByNameSearch - // const std::shared_ptr& searchCriteria = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch::Criteria); // // searchCriteria->name = multipleSearch; -//// searchCriteria->obfInfoAreaFilter = _visibleArea; // ??? +// //searchCriteria->obfInfoAreaFilter = _visibleArea; // ??? // searchCriteria->bbox31 = bbox31; // // const auto search = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch(obfsCollection)); @@ -2057,13 +2012,10 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr // [self, &matcher] // (const OsmAnd::ISearch::Criteria& criteria, const OsmAnd::ISearch::IResultEntry& resultEntry) // { -//// [self onPOIFound:resultEntry]; -// BOOL foobar = [matcher publish:[NSValue valueWithPointer:&resultEntry]]; +// //[self onPOIFound:resultEntry]; +// [matcher publish:[NSValue valueWithPointer:&resultEntry]]; // }, // ctrl); - - - return res; //TODO: delete? } @end From c86a4ef8b752d3de3934d777d3e75cd509cd8dce Mon Sep 17 00:00:00 2001 From: ivanPyrohivskyi Date: Wed, 24 Jun 2026 15:38:26 +0300 Subject: [PATCH 7/9] Read related objects --- Sources/Helpers/OAAmenitySearcher.mm | 93 +++++----------------------- 1 file changed, 16 insertions(+), 77 deletions(-) diff --git a/Sources/Helpers/OAAmenitySearcher.mm b/Sources/Helpers/OAAmenitySearcher.mm index 53f993e6f8..aa24b85dc9 100644 --- a/Sources/Helpers/OAAmenitySearcher.mm +++ b/Sources/Helpers/OAAmenitySearcher.mm @@ -579,7 +579,7 @@ - (nullable OAPOI *)findByName:(NSArray *)amenities names:(NSArray - (NSArray *)searchRoutePartOf:(NSString *)routeId { NSMutableArray *result = [NSMutableArray new]; - QString qRouteMebersIdKey = QString::fromNSString(@"route_members_ids"); + QString qRouteMebersIdKey = QString::fromNSString(ROUTE_MEMBERS_IDS); QString qRouteId = QString::fromNSString(routeId); OAResultMatcher *matcher = [[OAResultMatcher alloc] initWithPublishFunc:^BOOL(__autoreleasing id *objectPtr) { @@ -1864,8 +1864,7 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return false; }]; - OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:CHECK_EQUALS_FROM_SPACE]; - [self searchRouteByName:routeId mode:mode matcher:matcher]; + [self searchRouteByName:routeId mode:OsmAnd::StringMatcherMode::CHECK_EQUALS_FROM_SPACE matcher:matcher]; return result; } @@ -1917,8 +1916,7 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return false; }]; - OACollatorStringMatcher *mode = [[OACollatorStringMatcher alloc] initWithPart:nil mode:MULTISEARCH]; - [self searchRouteByName:multipleSearch mode:mode matcher:matcher]; + [self searchRouteByName:multipleSearch mode:OsmAnd::StringMatcherMode::MULTISEARCH matcher:matcher]; NSMutableDictionary *> *map = [NSMutableDictionary new]; for (OAPOI *am in result) @@ -1934,88 +1932,29 @@ + (NSString *) getAmenityTypeIdKey:(const std::shared_ptr return map; } - -//private List searchRouteByName(String multipleSearch, CollatorStringMatcher.StringMatcherMode mode, ResultMatcher matcher) { -// List result = new ArrayList(); -// BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, multipleSearch, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, matcher); -// req.setMatcherMode(mode); -// -// for(AmenityIndexRepository index : this.getAmenityRepositories(false, (Predicate)null)) { -// List amenities = index.searchPoiByName(req); -// if (!Algorithms.isEmpty(amenities)) { -// result.addAll(amenities); -// } -// } -// -// return result; -//} - -//TODO: implement -- (void)searchRouteByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher -{ - NSMutableArray *result = [NSMutableArray new]; - -// //TODO: use this like in androd -// OsmAnd::PointI topLeft = OsmAnd::PointI(0, 0); -// OsmAnd::PointI bottomRight = OsmAnd::PointI(INT_MAX, INT_MAX); -// OsmAnd::AreaI bbox31 = OsmAnd::AreaI(topLeft, bottomRight); - -// TODO: delete after quick testing - OsmAnd::LatLon latLon(50.448514, 30.495601); - const auto location = OsmAnd::Utilities::convertLatLonTo31(latLon); - OsmAnd::AreaI bbox31 = (OsmAnd::AreaI)OsmAnd::Utilities::boundingBox31FromAreaInMeters(10000, location); - - [self searchPoiByName:multipleSearch mode:mode matcher:matcher bbox31:bbox31]; -} - -- (void) searchPoiByName:(NSString *)multipleSearch mode:(OACollatorStringMatcher *)mode matcher:(OAResultMatcher *)matcher bbox31:(OsmAnd::AreaI)bbox31 +- (void)searchRouteByName:(NSString *)multipleSearch mode:(OsmAnd::StringMatcherMode)mode matcher:(OAResultMatcher *)matcher { - QString qKey = QString::fromNSString(ROUTE_ID); - QString qCode = QString::fromNSString(multipleSearch); - - OsmAndAppInstance app = [OsmAndApp instance]; - const auto& obfsCollection = app.resourcesManager->obfsCollection; - NSMutableArray *res = [NSMutableArray new]; - + const auto& obfsCollection = _app.resourcesManager->obfsCollection; std::shared_ptr ctrl; ctrl.reset(new OsmAnd::FunctorQueryController([&matcher] (const OsmAnd::FunctorQueryController* const controller) { - return [matcher isCancelled]; + return matcher && [matcher isCancelled]; })); - - //TODO: delete AmenitiesInAreaSearch - const std::shared_ptr& searchCriteria = std::shared_ptr(new OsmAnd::AmenitiesInAreaSearch::Criteria); - searchCriteria->bbox31 = bbox31; - - const auto search = std::shared_ptr(new OsmAnd::AmenitiesInAreaSearch(obfsCollection)); - + const std::shared_ptr& searchCriteria = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch::Criteria); + + searchCriteria->name = QString::fromNSString(multipleSearch); + searchCriteria->obfInfoAreaFilter = OsmAnd::AreaI(0, 0, INT_MAX, INT_MAX); + searchCriteria->matcherMode = mode; + + const auto search = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch(obfsCollection)); search->performSearch(*searchCriteria, - [&res, &multipleSearch, &qCode, &qKey, &matcher] + [&matcher] (const OsmAnd::ISearch::Criteria& criteria, const OsmAnd::ISearch::IResultEntry& resultEntry) { - [matcher publish:[NSValue valueWithPointer:&resultEntry]]; + [matcher publish:[NSValue valueWithPointer:&resultEntry]]; }, ctrl); - - - - //TODO: use AmenitiesByNameSearch -// const std::shared_ptr& searchCriteria = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch::Criteria); -// -// searchCriteria->name = multipleSearch; -// //searchCriteria->obfInfoAreaFilter = _visibleArea; // ??? -// searchCriteria->bbox31 = bbox31; -// -// const auto search = std::shared_ptr(new OsmAnd::AmenitiesByNameSearch(obfsCollection)); -// search->performSearch(*searchCriteria, -// [self, &matcher] -// (const OsmAnd::ISearch::Criteria& criteria, const OsmAnd::ISearch::IResultEntry& resultEntry) -// { -// //[self onPOIFound:resultEntry]; -// [matcher publish:[NSValue valueWithPointer:&resultEntry]]; -// }, -// ctrl); } @end From ad7eecb0540d24e0da6ab581fe042df57028ba57 Mon Sep 17 00:00:00 2001 From: ivanPyrohivskyi Date: Wed, 24 Jun 2026 16:28:37 +0300 Subject: [PATCH 8/9] Fix related objects --- Sources/Helpers/SearchByRouteIdTask.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Sources/Helpers/SearchByRouteIdTask.swift b/Sources/Helpers/SearchByRouteIdTask.swift index 629ec1975c..673e2c9924 100644 --- a/Sources/Helpers/SearchByRouteIdTask.swift +++ b/Sources/Helpers/SearchByRouteIdTask.swift @@ -65,15 +65,20 @@ final class SearchByRouteIdTask: OAAsyncTask { } } - var routeIdHash = Set() + var seenCoords = Set() for am in amenityList { - let location = am.getLocation() - if !routeIdHash.contains(location) { - if let amenity, amenity.obfId == am.obfId { + let loc = am.getLocation() + let lat = loc.coordinate.latitude + let lon = loc.coordinate.longitude + let key = String(format: "%.5f,%.5f", lat, lon) + if !seenCoords.contains(key) { + if let amenity, amenity.obfId != am.obfId { + amenities.append(am) + } else if amenity == nil { amenities.append(am) } } - routeIdHash.insert(location) + seenCoords.insert(key) } } } else if searchType == .partOf { @@ -100,3 +105,4 @@ final class SearchByRouteIdTask: OAAsyncTask { } } } + From 390330297eb52f086decf02b95b40dca34e6379c Mon Sep 17 00:00:00 2001 From: ivanPyrohivskyi Date: Wed, 24 Jun 2026 17:16:56 +0300 Subject: [PATCH 9/9] Fix geometry --- .../Controllers/Panels/OAMapPanelViewController.mm | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/Controllers/Panels/OAMapPanelViewController.mm b/Sources/Controllers/Panels/OAMapPanelViewController.mm index 5b7ac073e3..d6191e2b0b 100644 --- a/Sources/Controllers/Panels/OAMapPanelViewController.mm +++ b/Sources/Controllers/Panels/OAMapPanelViewController.mm @@ -1559,16 +1559,26 @@ - (void)showContextMenu:(OATargetPoint *)targetPoint saveState:(BOOL)saveState p - (void)setSelectedObject:(OATargetPoint *)targetPoint { + + OAMapObject *obj = nil; if ([targetPoint.targetObj isKindOfClass:OAMapObject.class]) + { + obj = targetPoint.targetObj; + + } + else if([targetPoint.targetObj isKindOfClass:BaseDetailsObject.class]) + { + BaseDetailsObject *baseDetails = (BaseDetailsObject *) targetPoint.targetObj; + obj = (OAMapObject *) [baseDetails syntheticAmenity]; + } + if (obj != nil) { QVector points; - OAMapObject *obj = targetPoint.targetObj; if (obj.x && obj.x.count > 0) { for (int i = 0; i < obj.x.count; i++) points.push_back(OsmAnd::PointI(obj.x[i].intValue, obj.y[i].intValue)); } - [_mapViewController.mapLayers.contextMenuLayer highlightPolygon:points]; } }