Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
409
libs/search/reverse_geocoder.cpp
Normal file
409
libs/search/reverse_geocoder.cpp
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
#include "search/reverse_geocoder.hpp"
|
||||
|
||||
#include "search/city_finder.hpp"
|
||||
#include "search/house_to_street_table.hpp"
|
||||
#include "search/mwm_context.hpp"
|
||||
#include "search/region_info_getter.hpp"
|
||||
#include "search/street_vicinity_loader.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "editor/osm_editor.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/fake_feature_ids.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/feature_algo.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
namespace search
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
int constexpr kQueryScale = scales::GetUpperScale();
|
||||
/// Max number of tries (nearest houses with housenumber) to check when getting point address.
|
||||
size_t constexpr kMaxNumTriesToApproxAddress = 10;
|
||||
|
||||
using AppendStreet = function<void(FeatureType & ft)>;
|
||||
using FillStreets = function<void(MwmSet::MwmHandle && handle, m2::RectD const & rect, AppendStreet && addStreet)>;
|
||||
|
||||
m2::RectD GetLookupRect(m2::PointD const & center, double radiusM)
|
||||
{
|
||||
return mercator::RectByCenterXYAndSizeInMeters(center, radiusM);
|
||||
}
|
||||
|
||||
// Following methods join only non-empty arguments in order with
|
||||
// commas.
|
||||
string Join(string const & s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
string Join(string const & s, Args &&... args)
|
||||
{
|
||||
auto tail = Join(std::forward<Args>(args)...);
|
||||
if (s.empty())
|
||||
return tail;
|
||||
if (tail.empty())
|
||||
return s;
|
||||
return s + ", " + tail;
|
||||
}
|
||||
|
||||
ReverseGeocoder::Building FromFeatureImpl(FeatureType & ft, std::string const & hn, double distMeters)
|
||||
{
|
||||
return {ft.GetID(), distMeters, hn, feature::GetCenter(ft)};
|
||||
}
|
||||
|
||||
std::string const & GetHouseNumber(FeatureType & ft)
|
||||
{
|
||||
std::string const & hn = ft.GetHouseNumber();
|
||||
if (hn.empty() && ftypes::IsAddressInterpolChecker::Instance()(ft))
|
||||
return ft.GetRef();
|
||||
return hn;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReverseGeocoder::ReverseGeocoder(DataSource const & dataSource) : m_dataSource(dataSource) {}
|
||||
|
||||
template <class ObjT, class FilterT>
|
||||
vector<ObjT> GetNearbyObjects(search::MwmContext & context, m2::PointD const & center, double radiusM,
|
||||
FilterT && filter, bool ignoreEditedStatus = false)
|
||||
{
|
||||
vector<ObjT> objs;
|
||||
|
||||
m2::RectD const rect = GetLookupRect(center, radiusM);
|
||||
context.ForEachFeature(rect, [&](FeatureType & ft)
|
||||
{
|
||||
if (filter(ft))
|
||||
{
|
||||
string_view const name = ft.GetReadableName();
|
||||
if (!name.empty())
|
||||
objs.emplace_back(ft.GetID(), feature::GetMinDistanceMeters(ft, center), name, ft.GetNames());
|
||||
}
|
||||
}, ignoreEditedStatus);
|
||||
|
||||
sort(objs.begin(), objs.end(), base::LessBy(&ObjT::m_distanceMeters));
|
||||
return objs;
|
||||
}
|
||||
|
||||
vector<ReverseGeocoder::Street> ReverseGeocoder::GetNearbyStreets(search::MwmContext & context,
|
||||
m2::PointD const & center, double radiusM, bool ignoreEditedStatus)
|
||||
{
|
||||
return GetNearbyObjects<Street>(context, center, radiusM,
|
||||
[](FeatureType & ft) { return StreetVicinityLoader::IsStreet(ft); }, ignoreEditedStatus);
|
||||
}
|
||||
|
||||
vector<ReverseGeocoder::Street> ReverseGeocoder::GetNearbyStreets(MwmSet::MwmId const & id,
|
||||
m2::PointD const & center) const
|
||||
{
|
||||
MwmSet::MwmHandle mwmHandle = m_dataSource.GetMwmHandleById(id);
|
||||
if (mwmHandle.IsAlive())
|
||||
{
|
||||
search::MwmContext context(std::move(mwmHandle));
|
||||
return GetNearbyStreets(context, center);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
vector<ReverseGeocoder::Street> ReverseGeocoder::GetNearbyStreets(FeatureType & ft) const
|
||||
{
|
||||
ASSERT(ft.GetID().IsValid(), ());
|
||||
return GetNearbyStreets(ft.GetID().m_mwmId, feature::GetCenter(ft));
|
||||
}
|
||||
|
||||
std::vector<ReverseGeocoder::Place> ReverseGeocoder::GetNearbyPlaces(search::MwmContext & context,
|
||||
m2::PointD const & center, double radiusM, bool ignoreEditedStatus)
|
||||
{
|
||||
return GetNearbyObjects<Place>(context, center, radiusM, [](FeatureType & ft)
|
||||
{
|
||||
return (ftypes::IsLocalityChecker::Instance().GetType(ft) >= ftypes::LocalityType::City ||
|
||||
ftypes::IsSuburbChecker::Instance()(ft));
|
||||
}, ignoreEditedStatus);
|
||||
}
|
||||
|
||||
string ReverseGeocoder::GetFeatureStreetName(FeatureType & ft) const
|
||||
{
|
||||
Address addr;
|
||||
HouseTable table(m_dataSource);
|
||||
GetNearbyAddress(table, FromFeature(ft, 0.0 /* distMeters */), false /* ignoreEdits */, addr);
|
||||
return addr.m_street.m_name;
|
||||
}
|
||||
|
||||
string ReverseGeocoder::GetOriginalFeatureStreetName(FeatureID const & fid) const
|
||||
{
|
||||
Address addr;
|
||||
HouseTable table(m_dataSource);
|
||||
Building bld;
|
||||
|
||||
m_dataSource.ReadFeature([&](FeatureType & ft) { bld = FromFeature(ft, 0.0 /* distMeters */); }, fid);
|
||||
GetNearbyAddress(table, bld, true /* ignoreEdits */, addr);
|
||||
return addr.m_street.m_name;
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::GetOriginalStreetByHouse(FeatureType & house, FeatureID & streetId) const
|
||||
{
|
||||
Address addr;
|
||||
HouseTable table(m_dataSource);
|
||||
|
||||
// Ignore edits here, because this function is called once with edits check before.
|
||||
if (GetNearbyAddress(table, FromFeature(house, 0.0 /* distMeters */), true /* ignoreEdits */, addr))
|
||||
{
|
||||
streetId = addr.m_street.m_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReverseGeocoder::GetNearbyAddress(m2::PointD const & center, Address & addr) const
|
||||
{
|
||||
GetNearbyAddress(center, kLookupRadiusM, addr);
|
||||
}
|
||||
|
||||
void ReverseGeocoder::GetNearbyAddress(m2::PointD const & center, double maxDistanceM, Address & addr,
|
||||
bool placeAsStreet /* = false*/) const
|
||||
{
|
||||
vector<Building> buildings;
|
||||
GetNearbyBuildings(center, maxDistanceM, buildings);
|
||||
|
||||
HouseTable table(m_dataSource, placeAsStreet);
|
||||
size_t triesCount = 0;
|
||||
|
||||
for (auto const & b : buildings)
|
||||
{
|
||||
// It's quite enough to analyze nearest kMaxNumTriesToApproxAddress houses for the exact nearby address.
|
||||
// When we can't guarantee suitable address for the point with distant houses.
|
||||
if (GetNearbyAddress(table, b, false /* ignoreEdits */, addr) || (++triesCount == kMaxNumTriesToApproxAddress))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::GetExactAddress(FeatureType & ft, Address & addr, bool placeAsStreet /* = false*/) const
|
||||
{
|
||||
std::string const & hn = GetHouseNumber(ft);
|
||||
if (hn.empty())
|
||||
return false;
|
||||
|
||||
HouseTable table(m_dataSource, placeAsStreet);
|
||||
return GetNearbyAddress(table, FromFeatureImpl(ft, hn, 0.0 /* distMeters */), false /* ignoreEdits */, addr);
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::GetExactAddress(FeatureID const & fid, Address & addr) const
|
||||
{
|
||||
bool res;
|
||||
m_dataSource.ReadFeature([&](FeatureType & ft) { res = GetExactAddress(ft, addr, true /* placeAsStreet */); }, fid);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::GetNearbyAddress(HouseTable & table, Building const & bld, bool ignoreEdits, Address & addr) const
|
||||
{
|
||||
string street;
|
||||
if (!ignoreEdits && osm::Editor::Instance().GetEditedFeatureStreet(bld.m_id, street))
|
||||
{
|
||||
addr.m_building = bld;
|
||||
addr.m_street.m_name = street;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto const res = table.Get(bld.m_id);
|
||||
if (!res)
|
||||
return false;
|
||||
|
||||
switch (res->m_type)
|
||||
{
|
||||
// Not used since OM.
|
||||
// case HouseToStreetTable::StreetIdType::Index:
|
||||
// {
|
||||
// vector<Street> streets;
|
||||
// // Get streets without squares and suburbs for backward compatibility with data.
|
||||
// GetNearbyStreetsWaysOnly(bld.m_id.m_mwmId, bld.m_center, streets);
|
||||
// if (res->m_streetId < streets.size())
|
||||
// {
|
||||
// addr.m_building = bld;
|
||||
// addr.m_street = streets[res->m_streetId];
|
||||
// return true;
|
||||
// }
|
||||
// LOG(LWARNING, ("Out of bound street index", res->m_streetId, "for", bld.m_id));
|
||||
// return false;
|
||||
// }
|
||||
case HouseToStreetTable::StreetIdType::FeatureId:
|
||||
{
|
||||
FeatureID streetFeature(bld.m_id.m_mwmId, res->m_streetId);
|
||||
CHECK(bld.m_id.m_mwmId.IsAlive(), (bld.m_id.m_mwmId));
|
||||
m_dataSource.ReadFeature([&bld, &addr](FeatureType & ft)
|
||||
{
|
||||
double distance = feature::GetMinDistanceMeters(ft, bld.m_center);
|
||||
addr.m_street = Street(ft.GetID(), distance, ft.GetReadableName(), ft.GetNames());
|
||||
}, streetFeature);
|
||||
|
||||
CHECK(!addr.m_street.m_multilangName.IsEmpty(), (bld.m_id.m_mwmId, res->m_streetId));
|
||||
addr.m_building = bld;
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Prior call of table.Get() is expected to fail.
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReverseGeocoder::GetNearbyBuildings(m2::PointD const & center, double radius, vector<Building> & buildings) const
|
||||
{
|
||||
auto const addBuilding = [&](FeatureType & ft)
|
||||
{
|
||||
std::string const & hn = GetHouseNumber(ft);
|
||||
if (hn.empty())
|
||||
return;
|
||||
|
||||
auto const distance = feature::GetMinDistanceMeters(ft, center);
|
||||
if (distance <= radius)
|
||||
buildings.push_back(FromFeatureImpl(ft, hn, distance));
|
||||
};
|
||||
|
||||
auto const stop = [&]() { return buildings.size() >= kMaxNumTriesToApproxAddress; };
|
||||
|
||||
m_dataSource.ForClosestToPoint(addBuilding, stop, center, radius, kQueryScale);
|
||||
sort(buildings.begin(), buildings.end(), base::LessBy(&Building::m_distanceMeters));
|
||||
}
|
||||
|
||||
// static
|
||||
ReverseGeocoder::RegionAddress ReverseGeocoder::GetNearbyRegionAddress(m2::PointD const & center,
|
||||
storage::CountryInfoGetter const & infoGetter,
|
||||
CityFinder & cityFinder)
|
||||
{
|
||||
RegionAddress addr;
|
||||
addr.m_featureId = cityFinder.GetCityFeatureID(center);
|
||||
if (!addr.m_featureId.IsValid() || addr.m_featureId.m_mwmId.GetInfo()->GetType() == MwmInfo::WORLD)
|
||||
addr.m_countryId = infoGetter.GetRegionCountryId(center);
|
||||
return addr;
|
||||
}
|
||||
|
||||
string ReverseGeocoder::GetLocalizedRegionAddress(RegionAddress const & addr, RegionInfoGetter const & nameGetter) const
|
||||
{
|
||||
if (!addr.IsValid())
|
||||
return {};
|
||||
|
||||
string addrStr;
|
||||
if (addr.m_featureId.IsValid())
|
||||
{
|
||||
m_dataSource.ReadFeature([&addrStr](FeatureType & ft) { addrStr = ft.GetReadableName(); }, addr.m_featureId);
|
||||
|
||||
auto const countryName = addr.GetCountryName();
|
||||
if (!countryName.empty())
|
||||
{
|
||||
RegionInfoGetter::NameBufferT nameParts;
|
||||
nameGetter.GetLocalizedFullName(countryName, nameParts);
|
||||
nameParts.insert(nameParts.begin(), std::move(addrStr));
|
||||
nameParts.erase(unique(nameParts.begin(), nameParts.end()), nameParts.end());
|
||||
addrStr = strings::JoinStrings(nameParts, ", ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(storage::IsCountryIdValid(addr.m_countryId), ());
|
||||
addrStr = nameGetter.GetLocalizedFullName(addr.m_countryId);
|
||||
}
|
||||
|
||||
return addrStr;
|
||||
}
|
||||
|
||||
// static
|
||||
ReverseGeocoder::Building ReverseGeocoder::FromFeature(FeatureType & ft, double distMeters)
|
||||
{
|
||||
return FromFeatureImpl(ft, ft.GetHouseNumber(), distMeters);
|
||||
}
|
||||
|
||||
std::optional<HouseToStreetTable::Result> ReverseGeocoder::HouseTable::Get(FeatureID const & fid)
|
||||
{
|
||||
if (feature::FakeFeatureIds::IsEditorCreatedFeature(fid.m_index))
|
||||
return {};
|
||||
|
||||
if (m_handle.GetId() != fid.m_mwmId)
|
||||
{
|
||||
auto handle = m_dataSource.GetMwmHandleById(fid.m_mwmId);
|
||||
if (!handle.IsAlive())
|
||||
{
|
||||
LOG(LWARNING, ("MWM", fid, "is dead"));
|
||||
return {};
|
||||
}
|
||||
m_handle = std::move(handle);
|
||||
}
|
||||
|
||||
auto value = m_handle.GetValue();
|
||||
if (!value->m_house2street)
|
||||
value->m_house2street = LoadHouseToStreetTable(*value);
|
||||
|
||||
auto res = value->m_house2street->Get(fid.m_index);
|
||||
if (!res && m_placeAsStreet)
|
||||
{
|
||||
if (!value->m_house2place)
|
||||
value->m_house2place = LoadHouseToPlaceTable(*value);
|
||||
res = value->m_house2place->Get(fid.m_index);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
string ReverseGeocoder::Address::FormatAddress() const
|
||||
{
|
||||
// Check whether we can format address according to the query type
|
||||
// and actual address distance.
|
||||
|
||||
// TODO (@m, @y): we can add "Near" prefix here in future according
|
||||
// to the distance.
|
||||
if (m_building.m_distanceMeters > 200.0)
|
||||
return {};
|
||||
|
||||
return Join(m_street.m_name, m_building.m_name);
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::RegionAddress::IsValid() const
|
||||
{
|
||||
return storage::IsCountryIdValid(m_countryId) || m_featureId.IsValid();
|
||||
}
|
||||
|
||||
string ReverseGeocoder::RegionAddress::GetCountryName() const
|
||||
{
|
||||
if (m_featureId.IsValid() && m_featureId.m_mwmId.GetInfo()->GetType() != MwmInfo::WORLD)
|
||||
return m_featureId.m_mwmId.GetInfo()->GetCountryName();
|
||||
return m_countryId;
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::RegionAddress::operator==(RegionAddress const & rhs) const
|
||||
{
|
||||
return m_countryId == rhs.m_countryId && m_featureId == rhs.m_featureId;
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::RegionAddress::operator!=(RegionAddress const & rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool ReverseGeocoder::RegionAddress::operator<(RegionAddress const & rhs) const
|
||||
{
|
||||
if (m_countryId != rhs.m_countryId)
|
||||
return m_countryId < rhs.m_countryId;
|
||||
return m_featureId < rhs.m_featureId;
|
||||
}
|
||||
|
||||
string DebugPrint(ReverseGeocoder::Object const & obj)
|
||||
{
|
||||
return obj.m_name;
|
||||
}
|
||||
|
||||
string DebugPrint(ReverseGeocoder::Address const & addr)
|
||||
{
|
||||
return "{ " + DebugPrint(addr.m_building) + ", " + DebugPrint(addr.m_street) + " }";
|
||||
}
|
||||
|
||||
} // namespace search
|
||||
Loading…
Add table
Add a link
Reference in a new issue