WIP: Visual clustering of POIs on map #8372

Draft
beasmm wants to merge 1 commit from beasmm/feature-#5074 into master
6 changed files with 186 additions and 8 deletions

View file

@ -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

63
map/clustering.cpp Normal file
View file

@ -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<Cluster>& 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;
}

29
map/clustering.hpp Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "geometry/point2d.hpp"
#include <vector>
// Define POI structure
struct POI
{
int id;
m2::PointD coords; // screen coordinates
};
// Define Cluster structure
struct Cluster
{
std::vector<POI> 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<Cluster>& clusters);

View file

@ -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<POI> pois;
for (auto it = beg; it != end; ++it)
{
auto const & r = *it;
if (!r.HasPoint())
continue;
auto * mark = editSession.CreateUserMark<SearchMarkPoint>(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<Cluster> clusters;
for (const POI &poi: pois)
clusters.push_back(Cluster(poi));
hierarchicalGreedyClustering(threshold, clusters);
for (const Cluster &cluster: clusters)
{
auto *mark = editSession.CreateUserMark<SearchMarkPoint>(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)

View file

@ -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<std::string, SearchMarkType::Count> 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>(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);

View file

@ -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);