#include "traffic/traffic_info.hpp" #include "platform/http_client.hpp" #include "platform/platform.hpp" #include "routing_common/car_model.hpp" #include "indexer/feature_algo.hpp" #include "indexer/feature_processor.hpp" #include "coding/bit_streams.hpp" #include "coding/elias_coder.hpp" #include "coding/file_container.hpp" #include "coding/reader.hpp" #include "coding/url_encode.hpp" #include "coding/varint.hpp" #include "coding/write_to_sink.hpp" #include "coding/writer.hpp" #include "coding/zlib.hpp" #include "base/assert.hpp" #include "base/bits.hpp" #include "base/logging.hpp" #include "base/string_utils.hpp" #include "std/algorithm.hpp" #include "std/limits.hpp" #include "std/sstream.hpp" #include "std/string.hpp" #include "defines.hpp" #include "private.h" #include "3party/Alohalytics/src/alohalytics.h" namespace traffic { namespace { bool ReadRemoteFile(string const & url, vector & contents, int & errorCode) { platform::HttpClient request(url); request.SetRawHeader("User-Agent", GetPlatform().GetAppUserAgent()); if (!request.RunHttpRequest()) { errorCode = request.ErrorCode(); LOG(LINFO, ("Couldn't run traffic request", url, ". Error:", errorCode)); return false; } errorCode = request.ErrorCode(); string const & result = request.ServerResponse(); contents.resize(result.size()); memcpy(contents.data(), result.data(), result.size()); if (errorCode != 200) { LOG(LINFO, ("Traffic request", url, "failed. HTTP Error:", errorCode)); return false; } return true; } string MakeRemoteURL(string const & name, uint64_t version) { if (string(TRAFFIC_DATA_BASE_URL).empty()) return {}; stringstream ss; ss << TRAFFIC_DATA_BASE_URL; if (version != 0) ss << version << "/"; ss << UrlEncode(name) << TRAFFIC_FILE_EXTENSION; return ss.str(); } char const kETag[] = "etag"; } // namespace // TrafficInfo::RoadSegmentId ----------------------------------------------------------------- TrafficInfo::RoadSegmentId::RoadSegmentId() : m_fid(0), m_idx(0), m_dir(0) {} TrafficInfo::RoadSegmentId::RoadSegmentId(uint32_t fid, uint16_t idx, uint8_t dir) : m_fid(fid), m_idx(idx), m_dir(dir) { } // TrafficInfo -------------------------------------------------------------------------------- // static uint8_t const TrafficInfo::kLatestKeysVersion = 0; uint8_t const TrafficInfo::kLatestValuesVersion = 0; TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion) : m_mwmId(mwmId) , m_currentDataVersion(currentDataVersion) { if (!mwmId.IsAlive()) { LOG(LWARNING, ("Attempt to create a traffic info for dead mwm.")); return; } string const mwmPath = mwmId.GetInfo()->GetLocalFile().GetPath(MapOptions::Map); try { FilesContainerR rcont(mwmPath); if (rcont.IsExist(TRAFFIC_KEYS_FILE_TAG)) { auto reader = rcont.GetReader(TRAFFIC_KEYS_FILE_TAG); vector buf(reader.Size()); reader.Read(0, buf.data(), buf.size()); LOG(LINFO, ("Reading keys for", mwmId, "from section")); try { DeserializeTrafficKeys(buf, m_keys); } catch (Reader::Exception const & e) { auto const info = mwmId.GetInfo(); LOG(LINFO, ("Could not read traffic keys from section. MWM:", info->GetCountryName(), "Version:", info->GetVersion())); alohalytics::LogEvent( "$TrafficReadSectionError", alohalytics::TStringMap({{"mwm", info->GetCountryName()}, {"version", strings::to_string(info->GetVersion())}})); } } else { LOG(LINFO, ("Reading traffic keys for", mwmId, "from the web")); ReceiveTrafficKeys(); } } catch (RootException const & e) { LOG(LWARNING, ("Could not initialize traffic keys")); } } // static TrafficInfo TrafficInfo::BuildForTesting(Coloring && coloring) { TrafficInfo info; info.m_coloring = move(coloring); return info; } void TrafficInfo::SetTrafficKeysForTesting(vector const & keys) { m_keys = keys; m_availability = Availability::IsAvailable; } bool TrafficInfo::ReceiveTrafficData(string & etag) { vector values; switch (ReceiveTrafficValues(etag, values)) { case ServerDataStatus::New: return UpdateTrafficData(values); case ServerDataStatus::NotChanged: return true; case ServerDataStatus::NotFound: case ServerDataStatus::Error: return false; } return false; } SpeedGroup TrafficInfo::GetSpeedGroup(RoadSegmentId const & id) const { auto const it = m_coloring.find(id); if (it == m_coloring.cend()) return SpeedGroup::Unknown; return it->second; } // static void TrafficInfo::ExtractTrafficKeys(string const & mwmPath, vector & result) { result.clear(); feature::ForEachFromDat(mwmPath, [&](FeatureType & ft, uint32_t const fid) { if (!routing::CarModel::AllLimitsInstance().IsRoad(ft)) return; ft.ParseGeometry(FeatureType::BEST_GEOMETRY); auto const numPoints = static_cast(ft.GetPointsCount()); uint8_t const numDirs = routing::CarModel::AllLimitsInstance().IsOneWay(ft) ? 1 : 2; for (uint16_t i = 0; i + 1 < numPoints; ++i) { for (uint8_t dir = 0; dir < numDirs; ++dir) result.emplace_back(fid, i, dir); } }); ASSERT(is_sorted(result.begin(), result.end()), ()); } // static void TrafficInfo::CombineColorings(vector const & keys, TrafficInfo::Coloring const & knownColors, TrafficInfo::Coloring & result) { result.clear(); size_t numKnown = 0; size_t numUnknown = 0; size_t numUnexpectedKeys = knownColors.size(); for (auto const & key : keys) { auto it = knownColors.find(key); if (it == knownColors.end()) { result[key] = SpeedGroup::Unknown; ++numUnknown; } else { result[key] = it->second; ASSERT_GREATER(numUnexpectedKeys, 0, ()); --numUnexpectedKeys; ++numKnown; } } LOG(LINFO, ("Road segments: known/unknown/total =", numKnown, numUnknown, numKnown + numUnknown)); ASSERT_EQUAL(numUnexpectedKeys, 0, ()); } // static void TrafficInfo::SerializeTrafficKeys(vector const & keys, vector & result) { vector fids; vector numSegs; vector oneWay; for (size_t i = 0; i < keys.size();) { size_t j = i; while (j < keys.size() && keys[i].m_fid == keys[j].m_fid) ++j; bool ow = true; for (size_t k = i; k < j; ++k) { if (keys[k].m_dir == RoadSegmentId::kReverseDirection) { ow = false; break; } } auto const numDirs = ow ? 1 : 2; size_t numSegsForThisFid = j - i; CHECK_GREATER(numDirs, 0, ()); CHECK_EQUAL(numSegsForThisFid % numDirs, 0, ()); numSegsForThisFid /= numDirs; fids.push_back(keys[i].m_fid); numSegs.push_back(numSegsForThisFid); oneWay.push_back(ow); i = j; } MemWriter> memWriter(result); WriteToSink(memWriter, kLatestKeysVersion); WriteVarUint(memWriter, fids.size()); { BitWriter bitWriter(memWriter); uint32_t prevFid = 0; for (auto const & fid : fids) { uint64_t const fidDiff = static_cast(fid - prevFid); bool ok = coding::GammaCoder::Encode(bitWriter, fidDiff + 1); ASSERT(ok, ()); UNUSED_VALUE(ok); prevFid = fid; } for (auto const & s : numSegs) { bool ok = coding::GammaCoder::Encode(bitWriter, s + 1); ASSERT(ok, ()); UNUSED_VALUE(ok); } for (auto const val : oneWay) bitWriter.Write(val ? 1 : 0, 1 /* numBits */); } } // static void TrafficInfo::DeserializeTrafficKeys(vector const & data, vector & result) { MemReaderWithExceptions memReader(data.data(), data.size()); ReaderSource src(memReader); auto const version = ReadPrimitiveFromSource(src); CHECK_EQUAL(version, kLatestKeysVersion, ("Unsupported version of traffic values.")); auto const n = static_cast(ReadVarUint(src)); vector fids(n); vector numSegs(n); vector oneWay(n); { BitReader bitReader(src); uint32_t prevFid = 0; for (size_t i = 0; i < n; ++i) { prevFid += coding::GammaCoder::Decode(bitReader) - 1; fids[i] = prevFid; } for (size_t i = 0; i < n; ++i) numSegs[i] = coding::GammaCoder::Decode(bitReader) - 1; for (size_t i = 0; i < n; ++i) oneWay[i] = bitReader.Read(1) > 0; } ASSERT_EQUAL(src.Size(), 0, ()); result.clear(); for (size_t i = 0; i < n; ++i) { auto const fid = fids[i]; uint8_t numDirs = oneWay[i] ? 1 : 2; for (size_t j = 0; j < numSegs[i]; ++j) { for (uint8_t dir = 0; dir < numDirs; ++dir) { RoadSegmentId key(fid, j, dir); result.push_back(key); } } } } // static void TrafficInfo::SerializeTrafficValues(vector const & values, vector & result) { vector buf; MemWriter> memWriter(buf); WriteToSink(memWriter, kLatestValuesVersion); WriteVarUint(memWriter, values.size()); { BitWriter bitWriter(memWriter); auto const numSpeedGroups = static_cast(SpeedGroup::Count); static_assert(numSpeedGroups <= 8, "A speed group's value may not fit into 3 bits"); for (auto const & v : values) { uint8_t const u = static_cast(v); CHECK_LESS(u, numSpeedGroups, ()); bitWriter.Write(u, 3); } } using Deflate = coding::ZLib::Deflate; Deflate deflate(Deflate::Format::ZLib, Deflate::Level::BestCompression); deflate(buf.data(), buf.size(), back_inserter(result)); } // static void TrafficInfo::DeserializeTrafficValues(vector const & data, vector & result) { using Inflate = coding::ZLib::Inflate; vector decompressedData; Inflate inflate(Inflate::Format::ZLib); inflate(data.data(), data.size(), back_inserter(decompressedData)); MemReaderWithExceptions memReader(decompressedData.data(), decompressedData.size()); ReaderSource src(memReader); auto const version = ReadPrimitiveFromSource(src); CHECK_EQUAL(version, kLatestValuesVersion, ("Unsupported version of traffic keys.")); auto const n = ReadVarUint(src); result.resize(n); BitReader bitReader(src); for (size_t i = 0; i < static_cast(n); ++i) { // SpeedGroup's values fit into 3 bits. result[i] = static_cast(bitReader.Read(3)); } ASSERT_EQUAL(src.Size(), 0, ()); } // todo(@m) This is a temporary method. Do not refactor it. bool TrafficInfo::ReceiveTrafficKeys() { if (!m_mwmId.IsAlive()) return false; auto const & info = m_mwmId.GetInfo(); if (!info) return false; string const url = MakeRemoteURL(info->GetCountryName(), info->GetVersion()); if (url.empty()) return false; vector contents; int errorCode; if (!ReadRemoteFile(url + ".keys", contents, errorCode)) return false; if (errorCode != 200) { LOG(LWARNING, ("Network error when reading keys")); return false; } vector keys; try { DeserializeTrafficKeys(contents, keys); } catch (Reader::Exception const & e) { LOG(LINFO, ("Could not read traffic keys received from server. MWM:", info->GetCountryName(), "Version:", info->GetVersion())); return false; } m_keys.swap(keys); return true; } TrafficInfo::ServerDataStatus TrafficInfo::ReceiveTrafficValues(string & etag, vector & values) { if (!m_mwmId.IsAlive()) return ServerDataStatus::Error; auto const & info = m_mwmId.GetInfo(); if (!info) return ServerDataStatus::Error; auto const version = info->GetVersion(); string const url = MakeRemoteURL(info->GetCountryName(), version); if (url.empty()) return ServerDataStatus::Error; platform::HttpClient request(url); request.LoadHeaders(true); request.SetRawHeader("User-Agent", GetPlatform().GetAppUserAgent()); request.SetRawHeader("If-None-Match", etag); if (!request.RunHttpRequest() || request.ErrorCode() != 200) return ProcessFailure(request, version); try { string const & response = request.ServerResponse(); vector contents(response.cbegin(), response.cend()); DeserializeTrafficValues(contents, values); } catch (Reader::Exception const & e) { m_availability = Availability::NoData; LOG(LWARNING, ("Could not read traffic values received from server. MWM:", info->GetCountryName(), "Version:", info->GetVersion())); alohalytics::LogEvent( "$TrafficReadError", alohalytics::TStringMap({{"mwm", info->GetCountryName()}, {"version", strings::to_string(info->GetVersion())}})); return ServerDataStatus::Error; } // Update ETag for this MWM. auto const & headers = request.GetHeaders(); auto const it = headers.find(kETag); if (it != headers.end()) etag = it->second; m_availability = Availability::IsAvailable; return ServerDataStatus::New; } bool TrafficInfo::UpdateTrafficData(vector const & values) { m_coloring.clear(); if (m_keys.size() != values.size()) { LOG(LWARNING, ("The number of received traffic values does not correspond to the number of keys:", m_keys.size(), "keys", values.size(), "values.")); alohalytics::LogEvent( "$TrafficUpdateError", alohalytics::TStringMap({{"keysCount", strings::to_string(m_keys.size())}, {"valuesCount", strings::to_string(values.size())}})); m_availability = Availability::NoData; return false; } for (size_t i = 0; i < m_keys.size(); ++i) { if (values[i] != SpeedGroup::Unknown) m_coloring.emplace(m_keys[i], values[i]); } return true; } TrafficInfo::ServerDataStatus TrafficInfo::ProcessFailure(platform::HttpClient const & request, int64_t const mwmVersion) { switch (request.ErrorCode()) { case 404: /* Not Found */ { int64_t version = 0; VERIFY(strings::to_int64(request.ServerResponse().c_str(), version), ()); if (version > mwmVersion && version <= m_currentDataVersion) m_availability = Availability::ExpiredData; else if (version > m_currentDataVersion) m_availability = Availability::ExpiredApp; else m_availability = Availability::NoData; return ServerDataStatus::NotFound; } case 304: /* Not Modified */ { m_availability = Availability::IsAvailable; return ServerDataStatus::NotChanged; } } m_availability = Availability::Unknown; alohalytics::LogEvent( "$TrafficNetworkError", alohalytics::TStringMap({{"code", strings::to_string(request.ErrorCode())}})); return ServerDataStatus::Error; } string DebugPrint(TrafficInfo::RoadSegmentId const & id) { string const dir = id.m_dir == TrafficInfo::RoadSegmentId::kForwardDirection ? "Forward" : "Backward"; ostringstream oss; oss << "RoadSegmentId [" << " fid = " << id.m_fid << " idx = " << id.m_idx << " dir = " << dir << " ]"; return oss.str(); } } // namespace traffic