#include "generator/booking_dataset.hpp" #include "generator/feature_builder.hpp" #include "generator/sponsored_scoring.hpp" #include "indexer/classificator.hpp" #include "indexer/ftypes_matcher.hpp" #include "base/string_utils.hpp" #include "boost/algorithm/string/replace.hpp" namespace generator { // BookingHotel ------------------------------------------------------------------------------------ BookingHotel::BookingHotel(string const & src) { vector rec; strings::ParseCSVRow(src, '\t', rec); CHECK_EQUAL(rec.size(), FieldsCount(), ("Error parsing hotels.tsv line:", boost::replace_all_copy(src, "\t", "\\t"))); strings::to_uint(rec[FieldIndex(Fields::Id)], m_id.Get()); // TODO(mgsergio): Use ms::LatLon. strings::to_double(rec[FieldIndex(Fields::Latitude)], m_latLon.lat); strings::to_double(rec[FieldIndex(Fields::Longtitude)], m_latLon.lon); m_name = rec[FieldIndex(Fields::Name)]; m_address = rec[FieldIndex(Fields::Address)]; strings::to_uint(rec[FieldIndex(Fields::Stars)], m_stars); strings::to_uint(rec[FieldIndex(Fields::PriceCategory)], m_priceCategory); strings::to_double(rec[FieldIndex(Fields::RatingBooking)], m_ratingBooking); strings::to_double(rec[FieldIndex(Fields::RatingUsers)], m_ratingUser); m_descUrl = rec[FieldIndex(Fields::DescUrl)]; strings::to_uint(rec[FieldIndex(Fields::Type)], m_type); m_translations = rec[FieldIndex(Fields::Translations)]; } ostream & operator<<(ostream & s, BookingHotel const & h) { s << fixed << setprecision(7); s << "Id: " << h.m_id << "\t Name: " << h.m_name << "\t Address: " << h.m_address << "\t lat: " << h.m_latLon.lat << " lon: " << h.m_latLon.lon; return s; } // BookingDataset ---------------------------------------------------------------------------------- template <> bool BookingDataset::NecessaryMatchingConditionHolds(FeatureBuilder1 const & fb) const { if (fb.GetName(StringUtf8Multilang::kDefaultCode).empty()) return false; return ftypes::IsHotelChecker::Instance()(fb.GetTypes()); } template <> void BookingDataset::PreprocessMatchedOsmObject(ObjectId, FeatureBuilder1 & fb, function const fn) const { // Turn a hotel into a simple building. if (fb.GetGeomType() == feature::GEOM_AREA) { // Remove all information about the hotel. auto params = fb.GetParams(); params.ClearName(); auto & meta = params.GetMetadata(); meta.Drop(feature::Metadata::EType::FMD_STARS); meta.Drop(feature::Metadata::EType::FMD_WEBSITE); meta.Drop(feature::Metadata::EType::FMD_PHONE_NUMBER); auto const tourism = classif().GetTypeByPath({"tourism"}); my::EraseIf(params.m_Types, [tourism](uint32_t type) { ftype::TruncValue(type, 1); return type == tourism; }); fb.SetParams(params); } fn(fb); } template <> void BookingDataset::BuildObject(Object const & hotel, function const & fn) const { FeatureBuilder1 fb; FeatureParams params; fb.SetCenter(MercatorBounds::FromLatLon(hotel.m_latLon.lat, hotel.m_latLon.lon)); auto & metadata = params.GetMetadata(); metadata.Set(feature::Metadata::FMD_SPONSORED_ID, strings::to_string(hotel.m_id.Get())); metadata.Set(feature::Metadata::FMD_WEBSITE, hotel.m_descUrl); metadata.Set(feature::Metadata::FMD_RATING, strings::to_string(hotel.m_ratingUser)); metadata.Set(feature::Metadata::FMD_STARS, strings::to_string(hotel.m_stars)); metadata.Set(feature::Metadata::FMD_PRICE_RATE, strings::to_string(hotel.m_priceCategory)); // params.AddAddress(hotel.address); // TODO(mgsergio): addr:full ??? if (!hotel.m_street.empty()) fb.AddStreet(hotel.m_street); if (!hotel.m_houseNumber.empty()) fb.AddHouseNumber(hotel.m_houseNumber); params.AddName(StringUtf8Multilang::GetLangByCode(StringUtf8Multilang::kDefaultCode), hotel.m_name); if (!hotel.m_translations.empty()) { // TODO(mgsergio): Move parsing to the hotel costruction stage. vector parts; strings::ParseCSVRow(hotel.m_translations, '|', parts); CHECK_EQUAL(parts.size() % 3, 0, ("Invalid translation string:", hotel.m_translations)); for (auto i = 0; i < parts.size(); i += 3) { auto const langCode = StringUtf8Multilang::GetLangIndex(parts[i]); params.AddName(StringUtf8Multilang::GetLangByCode(langCode), parts[i + 1]); // TODO(mgsergio): e.AddTag("addr:full:" + parts[i], parts[i + 2]); } } auto const & clf = classif(); params.AddType(clf.GetTypeByPath({"sponsored", "booking"})); // Matching booking.com hotel types to OpenStreetMap values. // Booking types are listed in the closed API docs. switch (hotel.m_type) { case 19: case 205: params.AddType(clf.GetTypeByPath({"tourism", "motel"})); break; case 21: case 206: case 212: params.AddType(clf.GetTypeByPath({"tourism", "resort"})); break; case 3: case 23: case 24: case 25: case 202: case 207: case 208: case 209: case 210: case 216: case 220: case 223: params.AddType(clf.GetTypeByPath({"tourism", "guest_house"})); break; case 14: case 204: case 213: case 218: case 219: case 226: case 222: params.AddType(clf.GetTypeByPath({"tourism", "hotel"})); break; case 211: case 224: case 228: params.AddType(clf.GetTypeByPath({"tourism", "chalet"})); break; case 13: case 225: case 203: params.AddType(clf.GetTypeByPath({"tourism", "hostel"})); break; case 215: case 221: case 227: case 2: case 201: params.AddType(clf.GetTypeByPath({"tourism", "apartment"})); break; case 214: params.AddType(clf.GetTypeByPath({"tourism", "camp_site"})); break; default: params.AddType(clf.GetTypeByPath({"tourism", "hotel"})); break; } fb.SetParams(params); fn(fb); } template <> BookingDataset::ObjectId BookingDataset::FindMatchingObjectIdImpl(FeatureBuilder1 const & fb) const { auto const name = fb.GetName(StringUtf8Multilang::kDefaultCode); if (name.empty()) return Object::InvalidObjectId(); // Find |kMaxSelectedElements| nearest values to a point. auto const bookingIndexes = GetNearestObjects(MercatorBounds::ToLatLon(fb.GetKeyPoint()), kMaxSelectedElements, kDistanceLimitInMeters); for (auto const j : bookingIndexes) { if (sponsored_scoring::Match(GetObjectById(j), fb).IsMatched()) return j; } return Object::InvalidObjectId(); } } // namespace generator