diff --git a/map/CMakeLists.txt b/map/CMakeLists.txt index 2bd9dc7c6a..23fd309367 100644 --- a/map/CMakeLists.txt +++ b/map/CMakeLists.txt @@ -14,6 +14,8 @@ set(SRC bookmarks_search_params.hpp chart_generator.cpp chart_generator.hpp + clustering.hpp + clustering.cpp elevation_info.cpp elevation_info.hpp everywhere_search_callback.cpp diff --git a/map/clustering.cpp b/map/clustering.cpp new file mode 100644 index 0000000000..ba543de0be --- /dev/null +++ b/map/clustering.cpp @@ -0,0 +1,63 @@ +#include "clustering.hpp" + +Cluster::Cluster(POI poi) +{ + pois.push_back(poi); + center = poi.coords; +} + +void Cluster::mergeCluster(Cluster& other) +{ + pois.insert(pois.end(), other.pois.begin(), other.pois.end()); + updateCenter(); +} + +void Cluster::updateCenter() +{ + m2::PointD sum(0, 0); + for (const POI& poi : pois) + sum += poi.coords; + + center = sum / pois.size(); +} + +double Cluster::distanceTo(const Cluster& other) const +{ + return center.Length(other.center); +} + +void hierarchicalGreedyClustering(double threshold, std::vector& clusters) +{ + bool reloop = false; + + int i = 0; + int j; + while (true) + { + if (i == clusters.size() - 1) + break; + j = i + 1; + + while (true) + { + if (j == clusters.size() - 1) + break; + + double distance = clusters[i].distanceTo(clusters[j]); + + if (distance < threshold) + { + clusters[i].mergeCluster(clusters[j]); + clusters.erase(clusters.begin() + j); + reloop = true; + } + else + ++j; + } + ++i; + } + if (reloop) + hierarchicalGreedyClustering(threshold, clusters); + + return; +} diff --git a/map/clustering.hpp b/map/clustering.hpp new file mode 100644 index 0000000000..7ee78f1767 --- /dev/null +++ b/map/clustering.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "geometry/point2d.hpp" +#include + +// Define POI structure +struct POI +{ + int id; + m2::PointD coords; // screen coordinates +}; + +// Define Cluster structure +struct Cluster +{ + std::vector pois; + m2::PointD center; + + Cluster(POI poi); + + void mergeCluster(Cluster& other); + + void updateCenter(); + + double distanceTo(const Cluster& other) const; +}; + +// Hierarchical Greedy Clustering +void hierarchicalGreedyClustering(double threshold, std::vector& clusters); diff --git a/map/framework.cpp b/map/framework.cpp index d0ae4f0efa..bfae814490 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -3,6 +3,7 @@ #include "map/gps_tracker.hpp" #include "map/user_mark.hpp" #include "map/track_mark.hpp" +#include "map/clustering.hpp" #include "ge0/url_generator.hpp" @@ -1394,26 +1395,55 @@ void Framework::FillSearchResultsMarks(SearchResultsIterT beg, SearchResultsIter editSession.ClearGroup(UserMark::Type::SEARCH); editSession.SetIsVisible(UserMark::Type::SEARCH, true); + std::vector pois; for (auto it = beg; it != end; ++it) { auto const & r = *it; if (!r.HasPoint()) continue; - auto * mark = editSession.CreateUserMark(r.GetFeatureCenter()); - mark->SetMatchedName(r.GetString()); + POI poi; + poi.id = std::distance(beg, it); + poi.coords = r.GetFeatureCenter(); + pois.push_back(poi); + } - if (r.GetResultType() == search::Result::Type::Feature) + auto viewPortRect = m_currentModelView.ClipRect(); + double threshold = viewPortRect.SizeX()/15.0; + + std::vector clusters; + for (const POI &poi: pois) + clusters.push_back(Cluster(poi)); + + hierarchicalGreedyClustering(threshold, clusters); + + for (const Cluster &cluster: clusters) + { + auto *mark = editSession.CreateUserMark(cluster.center); + auto clusterSize = cluster.pois.size(); + + if (clusterSize == 1) { - auto const fID = r.GetFeatureID(); - mark->SetFoundFeature(fID); - mark->SetFromType(r.GetFeatureType()); - mark->SetVisited(m_searchMarks.IsVisited(fID)); - mark->SetSelected(m_searchMarks.IsSelected(fID)); + auto it = beg + cluster.pois[0].id; + auto const &r = *it; + + mark->SetMatchedName(r.GetString()); + + if (r.GetResultType() == search::Result::Type::Feature) + { + auto const fID = r.GetFeatureID(); + mark->SetFoundFeature(fID); + mark->SetFromType(r.GetFeatureType()); + mark->SetVisited(m_searchMarks.IsVisited(fID)); + mark->SetSelected(m_searchMarks.IsSelected(fID)); + } } + else + mark->SetClusterType(clusterSize); } } + bool Framework::GetDistanceAndAzimut(m2::PointD const & point, double lat, double lon, double north, platform::Distance & distance, double & azimut) diff --git a/map/search_mark.cpp b/map/search_mark.cpp index 73cb5ab0e3..d9d13c728c 100644 --- a/map/search_mark.cpp +++ b/map/search_mark.cpp @@ -49,6 +49,25 @@ enum SearchMarkPoint::SearchMarkType : uint8_t ThemePark, WaterPark, Zoo, + Cluster2, + Cluster3, + Cluster4, + Cluster5, + Cluster6, + Cluster7, + Cluster8, + Cluster9, + Cluster10, + Cluster11, + Cluster12, + Cluster13, + Cluster14, + Cluster15, + Cluster16, + Cluster17, + Cluster18, + Cluster19, + ClusterPlus, NotFound, // Service value used in developer tools. Count @@ -97,6 +116,25 @@ std::array const kSymbols = { "search-result-theme-park", // ThemePark. "search-result-water-park", // WaterPark. "search-result-zoo", // Zoo. + "route-point-2", // Cluster2. + "route-point-3", // Cluster3. + "route-point-4", // Cluster4. + "route-point-5", // Cluster5. + "route-point-6", // Cluster6. + "route-point-7", // Cluster7. + "route-point-8", // Cluster8. + "route-point-9", // Cluster9. + "route-point-10" // Cluster10, + "route-point-11" // Cluster11, + "route-point-12" // Cluster12, + "route-point-13" // Cluster13, + "route-point-14" // Cluster14, + "route-point-15" // Cluster15, + "route-point-16" // Cluster16, + "route-point-17" // Cluster17, + "route-point-18" // Cluster18, + "route-point-19" // Cluster19, + "route-point-20" // ClusterPlus, "non-found-search-result", // NotFound. }; @@ -279,6 +317,21 @@ void SearchMarkPoint::SetFromType(uint32_t type) SetAttributeValue(m_type, GetSearchMarkType(type)); } +void SearchMarkPoint::SetClusterType(int size) +{ + SearchMarkType type; + + if (size >= 2 && size <= 19) { + type = static_cast(SearchMarkType::Cluster2 + (size - 2)); + } else if (size > 19) { + type = SearchMarkType::ClusterPlus; + } else { + type = SearchMarkType::Default; + } + + SetAttributeValue(m_type, type); +} + void SearchMarkPoint::SetNotFoundType() { SetAttributeValue(m_type, SearchMarkType::NotFound); diff --git a/map/search_mark.hpp b/map/search_mark.hpp index 1ad9830ae9..46f909a8c1 100644 --- a/map/search_mark.hpp +++ b/map/search_mark.hpp @@ -48,6 +48,7 @@ public: void SetMatchedName(std::string const & name); void SetFromType(uint32_t type); + void SetClusterType(int size); void SetNotFoundType(); void SetPreparing(bool isPreparing);