diff --git a/generator/osm2meta.hpp b/generator/osm2meta.hpp index 474b9ea722..825ca8dfce 100644 --- a/generator/osm2meta.hpp +++ b/generator/osm2meta.hpp @@ -39,6 +39,12 @@ public: if (!value.empty()) md.Add(Metadata::FMD_PHONE_NUMBER, value); } + else if (k == "maxspeed") + { + string const & value = ValidateAndFormat_maxspeed(v); + if (!value.empty()) + md.Add(Metadata::FMD_MAXSPEED, value); + } else if (k == "stars") { string const & value = ValidateAndFormat_stars(v); @@ -117,6 +123,14 @@ public: protected: /// Validation and formatting functions + string ValidateAndFormat_maxspeed(string const & v) const + { + if (!ftypes::IsSpeedCamChecker::Instance()(m_params.m_Types)) + return string(); + + return v; + } + string ValidateAndFormat_stars(string const & v) const { if (v.empty()) diff --git a/generator/osm_translator.hpp b/generator/osm_translator.hpp index 33dc944de4..d92aedcfab 100644 --- a/generator/osm_translator.hpp +++ b/generator/osm_translator.hpp @@ -142,7 +142,7 @@ protected: for (auto const & p : e.tags) { // Store only this tags to use it in railway stations processing for the particular city. - if (p.first == "network" || p.first == "operator" || p.first == "route") + if (p.first == "network" || p.first == "operator" || p.first == "route" || p.first == "maxspeed") if (!TBase::IsKeyTagExists(p.first)) TBase::m_current->AddTag(p.first, p.second); } diff --git a/indexer/feature_meta.hpp b/indexer/feature_meta.hpp index f81ef76198..13f447e0b9 100644 --- a/indexer/feature_meta.hpp +++ b/indexer/feature_meta.hpp @@ -35,6 +35,7 @@ namespace feature FMD_EMAIL = 14, FMD_POSTCODE = 15, FMD_WIKIPEDIA = 16, + FMD_MAXSPEED = 17, FMD_COUNT }; diff --git a/indexer/ftypes_matcher.cpp b/indexer/ftypes_matcher.cpp index b52cb4fb0d..b453836636 100644 --- a/indexer/ftypes_matcher.cpp +++ b/indexer/ftypes_matcher.cpp @@ -70,6 +70,19 @@ IsATMChecker const & IsATMChecker::Instance() return inst; } +IsSpeedCamChecker::IsSpeedCamChecker() +{ + Classificator const & c = classif(); + m_types.push_back(c.GetTypeByPath({"highway", "speed_camera"})); +} + +// static +IsSpeedCamChecker const & IsSpeedCamChecker::Instance() +{ + static const IsSpeedCamChecker instance; + return instance; +} + IsFuelStationChecker::IsFuelStationChecker() { Classificator const & c = classif(); diff --git a/indexer/ftypes_matcher.hpp b/indexer/ftypes_matcher.hpp index 0e8b4ada31..9d985fe0d5 100644 --- a/indexer/ftypes_matcher.hpp +++ b/indexer/ftypes_matcher.hpp @@ -46,6 +46,14 @@ public: static IsATMChecker const & Instance(); }; +class IsSpeedCamChecker : public BaseChecker +{ + IsSpeedCamChecker(); + +public: + static IsSpeedCamChecker const & Instance(); +}; + class IsFuelStationChecker : public BaseChecker { public: diff --git a/map/framework.cpp b/map/framework.cpp index 13b2e222f6..b894790e99 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -396,9 +396,6 @@ void Framework::DeleteCountry(storage::TIndex const & index, MapOptions opt) { InvalidateRect(GetCountryBounds(countryFile.GetNameWithoutExt()), true /* doForceUpdate */); } - // TODO (@ldragunov, @gorshenin): rewrite routing session to use MwmHandles. Thus, - // it won' be needed to reset it after maps update. - m_routingSession.Reset(); return; } case MapOptions::CarRouting: @@ -2293,7 +2290,7 @@ void Framework::CheckLocationForRouting(GpsInfo const & info) return; m2::PointD const & position = GetLocationState()->Position(); - if (m_routingSession.OnLocationPositionChanged(position, info) == RoutingSession::RouteNeedRebuild) + if (m_routingSession.OnLocationPositionChanged(position, info, m_model.GetIndex()) == RoutingSession::RouteNeedRebuild) { auto readyCallback = [this](Route const & route, IRouter::ResultCode code) { diff --git a/platform/location.hpp b/platform/location.hpp index 65e14dd2dc..6132896dd0 100644 --- a/platform/location.hpp +++ b/platform/location.hpp @@ -101,6 +101,7 @@ namespace location m_exitNum(0), m_time(0), m_completionPercent(0), + m_speedWarningSignal(false), m_pedestrianTurn(routing::turns::PedestrianDirection::None), m_pedestrianDirectionPos(0., 0.) { @@ -156,6 +157,9 @@ namespace location // Percentage of the route completion. double m_completionPercent; + // Speed cam warning signal. + bool m_speedWarningSignal; + /// @name Pedestrian direction information //@{ routing::turns::PedestrianDirection m_pedestrianTurn; diff --git a/routing/route.hpp b/routing/route.hpp index e2347f6ebe..86f199aedd 100644 --- a/routing/route.hpp +++ b/routing/route.hpp @@ -60,6 +60,8 @@ public: uint32_t GetTotalTimeSec() const; uint32_t GetCurrentTimeToEndSec() const; + FollowedPolyline const & GetFollowedPolyline() const { return m_poly; } + string const & GetRouterId() const { return m_router; } m2::PolylineD const & GetPoly() const { return m_poly.GetPolyline(); } TTurns const & GetTurns() const { return m_turns; } @@ -76,6 +78,7 @@ public: /// \param distanceToTurnMeters is a distance from current position to the nearest turn. /// \param turn is information about the nearest turn. bool GetCurrentTurn(double & distanceToTurnMeters, turns::TurnItem & turn) const; + /// @return true if GetNextTurn() returns a valid result in parameters, false otherwise. /// \param distanceToTurnMeters is a distance from current position to the second turn. /// \param turn is information about the second turn. diff --git a/routing/routing.pro b/routing/routing.pro index 79cb3c65df..9d904325b6 100644 --- a/routing/routing.pro +++ b/routing/routing.pro @@ -36,6 +36,7 @@ SOURCES += \ routing_algorithm.cpp \ routing_mapping.cpp \ routing_session.cpp \ + speed_camera.cpp \ turns.cpp \ turns_generator.cpp \ turns_sound.cpp \ @@ -71,6 +72,7 @@ HEADERS += \ routing_mapping.hpp \ routing_session.hpp \ routing_settings.hpp \ + speed_camera.hpp \ turns.hpp \ turns_generator.hpp \ turns_sound.hpp \ diff --git a/routing/routing_session.cpp b/routing/routing_session.cpp index c5bb64c5fd..5bd4681135 100644 --- a/routing/routing_session.cpp +++ b/routing/routing_session.cpp @@ -1,4 +1,6 @@ -#include "routing/routing_session.hpp" +#include "routing_session.hpp" + +#include "speed_camera.hpp" #include "indexer/mercator.hpp" @@ -27,16 +29,25 @@ double constexpr kShowTheTurnAfterTheNextM = 500.; // @todo(kshalnev) The distance may depend on the current speed. double constexpr kShowPedestrianTurnInMeters = 5.; +// Minimal distance to speed camera to make sound bell on overspeed. +double constexpr kSpeedCameraMinimalWarningMeters = 200.; +// Seconds to warning user before speed camera for driving with current speed. +double constexpr kSpeedCameraWarningSeconds = 30; + +double constexpr kKmHToMps = 1000. / 3600.; + +double constexpr kInvalidSpeedCameraDistance = -1; } // namespace namespace routing { - RoutingSession::RoutingSession() : m_router(nullptr), m_route(string()), m_state(RoutingNotActive), m_endPoint(m2::PointD::Zero()), + m_lastWarnedSpeedCameraIndex(0), + m_speedWarningSignal(false), m_passedDistanceOnRouteMeters(0.0) { } @@ -123,10 +134,14 @@ void RoutingSession::Reset() m_turnsSound.Reset(); m_passedDistanceOnRouteMeters = 0.0; + m_lastWarnedSpeedCameraIndex = 0; + m_lastCheckedCamera = SpeedCameraRestriction(); + m_speedWarningSignal = false; } RoutingSession::State RoutingSession::OnLocationPositionChanged(m2::PointD const & position, - GpsInfo const & info) + GpsInfo const & info, + Index const & index) { ASSERT(m_state != RoutingNotActive, ()); ASSERT(m_router != nullptr, ()); @@ -156,7 +171,26 @@ RoutingSession::State RoutingSession::OnLocationPositionChanged(m2::PointD const alohalytics::LogEvent("RouteTracking_ReachedDestination", params); } else + { m_state = OnRoute; + + // Warning signals checks + if (m_routingSettings.m_speedCameraWarning && !m_speedWarningSignal) + { + double const warningDistanceM = max(kSpeedCameraMinimalWarningMeters, + info.m_speed * kSpeedCameraWarningSeconds); + SpeedCameraRestriction cam(0, 0); + double const camDistance = GetDistanceToCurrentCamM(cam, index); + if (kInvalidSpeedCameraDistance != camDistance && camDistance < warningDistanceM) + { + if (cam.m_index > m_lastWarnedSpeedCameraIndex && info.m_speed > cam.m_maxSpeedKmH * kKmHToMps) + { + m_speedWarningSignal = true; + m_lastWarnedSpeedCameraIndex = cam.m_index; + } + } + } + } m_lastGoodPosition = position; } else @@ -201,73 +235,78 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const threads::MutexGuard guard(m_routeSessionMutex); UNUSED_VALUE(guard); - if (m_route.IsValid() && IsNavigable()) + if (!m_route.IsValid() || !IsNavigable()) { - formatDistFn(m_route.GetCurrentDistanceToEndMeters(), info.m_distToTarget, info.m_targetUnitsSuffix); + // nothing should be displayed on the screen about turns if these lines are executed + info = FollowingInfo(); + return; + } - double distanceToTurnMeters = 0., distanceToNextTurnMeters = 0.; - turns::TurnItem turn; - turns::TurnItem nextTurn; - m_route.GetCurrentTurn(distanceToTurnMeters, turn); - formatDistFn(distanceToTurnMeters, info.m_distToTurn, info.m_turnUnitsSuffix); - info.m_turn = turn.m_turn; + formatDistFn(m_route.GetCurrentDistanceToEndMeters(), info.m_distToTarget, info.m_targetUnitsSuffix); - // The turn after the next one. - if (m_route.GetNextTurn(distanceToNextTurnMeters, nextTurn)) + double distanceToTurnMeters = 0.; + double distanceToNextTurnMeters = 0.; + turns::TurnItem turn; + turns::TurnItem nextTurn; + m_route.GetCurrentTurn(distanceToTurnMeters, turn); + formatDistFn(distanceToTurnMeters, info.m_distToTurn, info.m_turnUnitsSuffix); + info.m_turn = turn.m_turn; + + // The turn after the next one. + if (m_route.GetNextTurn(distanceToNextTurnMeters, nextTurn)) + { + double const distBetweenTurnsM = distanceToNextTurnMeters - distanceToTurnMeters; + ASSERT_LESS_OR_EQUAL(0, distBetweenTurnsM, ()); + + if (m_routingSettings.m_showTurnAfterNext && + distanceToTurnMeters < kShowTheTurnAfterTheNextM && distBetweenTurnsM < turns::kMaxTurnDistM) { - double const distBetweenTurnsM = distanceToNextTurnMeters - distanceToTurnMeters; - ASSERT_LESS_OR_EQUAL(0, distBetweenTurnsM, ()); - - if (m_routingSettings.m_showTurnAfterNext && - distanceToTurnMeters < kShowTheTurnAfterTheNextM && distBetweenTurnsM < turns::kMaxTurnDistM) - { - info.m_nextTurn = nextTurn.m_turn; - } - else - { - info.m_nextTurn = routing::turns::TurnDirection::NoTurn; - } + info.m_nextTurn = nextTurn.m_turn; } else { info.m_nextTurn = routing::turns::TurnDirection::NoTurn; } - info.m_exitNum = turn.m_exitNum; - info.m_time = m_route.GetCurrentTimeToEndSec(); - info.m_sourceName = turn.m_sourceName; - info.m_targetName = turn.m_targetName; - info.m_completionPercent = 100.0 * - (m_passedDistanceOnRouteMeters + m_route.GetCurrentDistanceFromBeginMeters()) / - (m_passedDistanceOnRouteMeters + m_route.GetTotalDistanceMeters()); - - // Lane information. - if (distanceToTurnMeters < kShowLanesDistInMeters) - { - // There are two nested loops below. Outer one is for lanes and inner one (ctor of - // SingleLaneInfo) is - // for each lane's directions. The size of turn.m_lanes is relatively small. Less than 10 in - // most cases. - info.m_lanes.clear(); - for (size_t j = 0; j < turn.m_lanes.size(); ++j) - info.m_lanes.emplace_back(turn.m_lanes[j]); - } - else - { - info.m_lanes.clear(); - } - - // Pedestrian info - m2::PointD pos; - m_route.GetCurrentDirectionPoint(pos); - info.m_pedestrianDirectionPos = MercatorBounds::ToLatLon(pos); - info.m_pedestrianTurn = - (distanceToTurnMeters < kShowPedestrianTurnInMeters) ? turn.m_pedestrianTurn : turns::PedestrianDirection::None; } else { - // nothing should be displayed on the screen about turns if these lines are executed - info = FollowingInfo(); + info.m_nextTurn = routing::turns::TurnDirection::NoTurn; } + info.m_exitNum = turn.m_exitNum; + info.m_time = m_route.GetCurrentTimeToEndSec(); + info.m_sourceName = turn.m_sourceName; + info.m_targetName = turn.m_targetName; + info.m_completionPercent = 100.0 * + (m_passedDistanceOnRouteMeters + m_route.GetCurrentDistanceFromBeginMeters()) / + (m_passedDistanceOnRouteMeters + m_route.GetTotalDistanceMeters()); + + // Lane information. + if (distanceToTurnMeters < kShowLanesDistInMeters) + { + // There are two nested loops below. Outer one is for lanes and inner one (ctor of + // SingleLaneInfo) is + // for each lane's directions. The size of turn.m_lanes is relatively small. Less than 10 in + // most cases. + info.m_lanes.clear(); + info.m_lanes.reserve(turn.m_lanes.size()); + for (size_t j = 0; j < turn.m_lanes.size(); ++j) + info.m_lanes.emplace_back(turn.m_lanes[j]); + } + else + { + info.m_lanes.clear(); + } + + // Speedcam signal information. + info.m_speedWarningSignal = m_speedWarningSignal; + m_speedWarningSignal = false; + + // Pedestrian info + m2::PointD pos; + m_route.GetCurrentDirectionPoint(pos); + info.m_pedestrianDirectionPos = MercatorBounds::ToLatLon(pos); + info.m_pedestrianTurn = + (distanceToTurnMeters < kShowPedestrianTurnInMeters) ? turn.m_pedestrianTurn : turns::PedestrianDirection::None; } void RoutingSession::GenerateTurnSound(vector & turnNotifications) @@ -305,6 +344,8 @@ void RoutingSession::AssignRoute(Route & route, IRouter::ResultCode e) route.SetRoutingSettings(m_routingSettings); m_route.Swap(route); + m_lastWarnedSpeedCameraIndex = 0; + m_lastCheckedCamera = SpeedCameraRestriction(); } void RoutingSession::SetRouter(unique_ptr && router, @@ -383,4 +424,30 @@ string RoutingSession::GetTurnNotificationsLocale() const UNUSED_VALUE(guard); return m_turnsSound.GetLocale(); } + +double RoutingSession::GetDistanceToCurrentCamM(SpeedCameraRestriction & camera, Index const & index) +{ + auto const & m_poly = m_route.GetFollowedPolyline(); + auto const & currentIter = m_poly.GetCurrentIter(); + if (currentIter.m_ind < m_lastCheckedCamera.m_index && + m_lastCheckedCamera.m_index < m_poly.GetPolyline().GetSize()) + { + camera = m_lastCheckedCamera; + return m_poly.GetDistanceM(currentIter, m_poly.GetIterToIndex(camera.m_index)); + } + size_t const currentIndex = max(currentIter.m_ind, + m_lastCheckedCamera.m_index + 1); + for (size_t i = currentIndex; i < m_poly.GetPolyline().GetSize(); ++i) + { + uint8_t speed = CheckCameraInPoint(m_poly.GetPolyline().GetPoint(i), index); + if (speed != kNoSpeedCamera) + { + camera = SpeedCameraRestriction(static_cast(i), speed); + m_lastCheckedCamera = camera; + return m_poly.GetDistanceM(currentIter, m_poly.GetIterToIndex(i)); + } + } + m_lastCheckedCamera.m_index = m_poly.GetPolyline().GetSize(); + return kInvalidSpeedCameraDistance; +} } // namespace routing diff --git a/routing/routing_session.hpp b/routing/routing_session.hpp index e251291289..3ff1de6bb4 100644 --- a/routing/routing_session.hpp +++ b/routing/routing_session.hpp @@ -14,6 +14,7 @@ #include "base/deferred_task.hpp" #include "base/mutex.hpp" +#include "std/limits.hpp" #include "std/unique_ptr.hpp" namespace location @@ -23,6 +24,15 @@ class RouteMatchingInfo; namespace routing { +struct SpeedCameraRestriction +{ + size_t m_index; // Index of a polyline point where camera is located. + uint8_t m_maxSpeedKmH; // Maximum speed allowed by the camera. + + SpeedCameraRestriction(size_t index, uint8_t maxSpeed) : m_index(index), m_maxSpeedKmH(maxSpeed) {} + SpeedCameraRestriction() : m_index(0), m_maxSpeedKmH(numeric_limits::max()) {} +}; + class RoutingSession { public: @@ -77,7 +87,8 @@ public: bool IsOnRoute() const { return (m_state == OnRoute); } void Reset(); - State OnLocationPositionChanged(m2::PointD const & position, location::GpsInfo const & info); + State OnLocationPositionChanged(m2::PointD const & position, location::GpsInfo const & info, + Index const & index); void GetRouteFollowingInfo(location::FollowingInfo & info) const; void MatchLocationToRoute(location::GpsInfo & location, @@ -115,6 +126,10 @@ private: void AssignRoute(Route & route, IRouter::ResultCode e); + /// Returns a nearest speed camera record on your way and distance to it. + /// Returns kInvalidSpeedCameraDistance if there is no cameras on your way. + double GetDistanceToCurrentCamM(SpeedCameraRestriction & camera, Index const & index); + /// RemoveRoute removes m_route and resets route attributes (m_state, m_lastDistance, m_moveAwayCounter). void RemoveRoute(); void RemoveRouteImpl(); @@ -124,6 +139,12 @@ private: Route m_route; State m_state; m2::PointD m_endPoint; + size_t m_lastWarnedSpeedCameraIndex; + SpeedCameraRestriction m_lastCheckedCamera; + // TODO (ldragunov) Rewrite UI interop to message queue and avoid mutable. + /// This field is mutable because it's modified in a constant getter. Note that the notification + /// about camera will be sent at most once. + mutable bool m_speedWarningSignal; mutable threads::Mutex m_routeSessionMutex; diff --git a/routing/routing_settings.hpp b/routing/routing_settings.hpp index bbf408d4d1..1eba59bb9f 100644 --- a/routing/routing_settings.hpp +++ b/routing/routing_settings.hpp @@ -32,19 +32,22 @@ struct RoutingSettings /// \brief if m_showTurnAfterNext is equal to true end users see a notification /// about the turn after the next in some cases. bool m_showTurnAfterNext; + + /// \brief m_speedCameraWarning is a flag for enabling user notifications about speed cameras. + bool m_speedCameraWarning; }; inline RoutingSettings GetPedestrianRoutingSettings() { return RoutingSettings({ false /* m_matchRoute */, false /* m_soundDirection */, 20. /* m_matchingThresholdM */, true /* m_keepPedestrianInfo */, - false /* m_showTurnAfterNext */}); + false /* m_showTurnAfterNext */, false /* m_speedCameraWarning*/}); } inline RoutingSettings GetCarRoutingSettings() { return RoutingSettings({ true /* m_matchRoute */, true /* m_soundDirection */, 50. /* m_matchingThresholdM */, false /* m_keepPedestrianInfo */, - true /* m_showTurnAfterNext */}); + true /* m_showTurnAfterNext */, true /* m_speedCameraWarning*/}); } } // namespace routing diff --git a/routing/speed_camera.cpp b/routing/speed_camera.cpp new file mode 100644 index 0000000000..d65f7137e5 --- /dev/null +++ b/routing/speed_camera.cpp @@ -0,0 +1,65 @@ +#include "speed_camera.hpp" + +#include "indexer/classificator.hpp" +#include "indexer/ftypes_matcher.hpp" +#include "indexer/index.hpp" +#include "indexer/scales.hpp" + +#include "coding/read_write_utils.hpp" +#include "coding/reader.hpp" +#include "coding/writer.hpp" + +#include "base/string_utils.hpp" +#include "base/math.hpp" + +#include "std/limits.hpp" + +namespace +{ +double constexpr kCameraCheckRadiusMeters = 2.0; +double constexpr kCoordinateEqualityDelta = 0.000001; +} // namespace + +namespace routing +{ +uint8_t const kNoSpeedCamera = numeric_limits::max(); + +uint8_t ReadCameraRestriction(FeatureType & ft) +{ + using feature::Metadata; + ft.ParseMetadata(); + feature::Metadata const & md = ft.GetMetadata(); + string const & speed = md.Get(Metadata::FMD_MAXSPEED); + if (speed.empty()) + return 0; + int result; + if (strings::to_int(speed, result)) + return result; + return 0; +} + +uint8_t CheckCameraInPoint(m2::PointD const & point, Index const & index) +{ + uint32_t speedLimit = kNoSpeedCamera; + + auto const f = [&point, &speedLimit](FeatureType & ft) + { + if (ft.GetFeatureType() != feature::GEOM_POINT) + return; + + feature::TypesHolder hl = ft; + if (!ftypes::IsSpeedCamChecker::Instance()(hl)) + return; + + if (my::AlmostEqualAbs(ft.GetCenter().x, point.x, kCoordinateEqualityDelta) && + my::AlmostEqualAbs(ft.GetCenter().y, point.y, kCoordinateEqualityDelta)) + speedLimit = ReadCameraRestriction(ft); + }; + + index.ForEachInRect(f, + MercatorBounds::RectByCenterXYAndSizeInMeters(point, + kCameraCheckRadiusMeters), + scales::GetUpperScale()); + return speedLimit; +} +} // namespace routing diff --git a/routing/speed_camera.hpp b/routing/speed_camera.hpp new file mode 100644 index 0000000000..29e76635f3 --- /dev/null +++ b/routing/speed_camera.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "geometry/point2d.hpp" + +#include "std/cstdint.hpp" + +class Index; + +namespace routing +{ +extern uint8_t const kNoSpeedCamera; + +uint8_t CheckCameraInPoint(m2::PointD const & point, Index const & index); +} // namespace routing diff --git a/xcode/routing/routing.xcodeproj/project.pbxproj b/xcode/routing/routing.xcodeproj/project.pbxproj index c604234f33..fb25ed31db 100644 --- a/xcode/routing/routing.xcodeproj/project.pbxproj +++ b/xcode/routing/routing.xcodeproj/project.pbxproj @@ -70,6 +70,8 @@ A1616E2B1B6B60AB003F078E /* router_delegate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1616E291B6B60AB003F078E /* router_delegate.cpp */; }; A1616E2C1B6B60AB003F078E /* router_delegate.hpp in Headers */ = {isa = PBXBuildFile; fileRef = A1616E2A1B6B60AB003F078E /* router_delegate.hpp */; }; A1616E2E1B6B60B3003F078E /* astar_progress.hpp in Headers */ = {isa = PBXBuildFile; fileRef = A1616E2D1B6B60B3003F078E /* astar_progress.hpp */; }; + A1876BC61BB19C4300C9C743 /* speed_camera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1876BC41BB19C4300C9C743 /* speed_camera.cpp */; settings = {ASSET_TAGS = (); }; }; + A1876BC71BB19C4300C9C743 /* speed_camera.hpp in Headers */ = {isa = PBXBuildFile; fileRef = A1876BC51BB19C4300C9C743 /* speed_camera.hpp */; settings = {ASSET_TAGS = (); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -138,6 +140,8 @@ A1616E291B6B60AB003F078E /* router_delegate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = router_delegate.cpp; sourceTree = ""; }; A1616E2A1B6B60AB003F078E /* router_delegate.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = router_delegate.hpp; sourceTree = ""; }; A1616E2D1B6B60B3003F078E /* astar_progress.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = astar_progress.hpp; sourceTree = ""; }; + A1876BC41BB19C4300C9C743 /* speed_camera.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = speed_camera.cpp; sourceTree = ""; }; + A1876BC51BB19C4300C9C743 /* speed_camera.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = speed_camera.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -182,6 +186,8 @@ 675343FA1A3F640D00A0A8C3 /* routing */ = { isa = PBXGroup; children = ( + A1876BC41BB19C4300C9C743 /* speed_camera.cpp */, + A1876BC51BB19C4300C9C743 /* speed_camera.hpp */, 671F58BA1B874EA20032311E /* base */, 67AB92E01B7B3E6E00AB5194 /* routing_mapping.hpp */, 67AB92E11B7B3E6E00AB5194 /* turns_tts_text.cpp */, @@ -281,6 +287,7 @@ 6753441D1A3F644F00A0A8C3 /* router.hpp in Headers */, A1616E2E1B6B60B3003F078E /* astar_progress.hpp in Headers */, 670EE5721B664796001E8064 /* directions_engine.hpp in Headers */, + A1876BC71BB19C4300C9C743 /* speed_camera.hpp in Headers */, 67C7D42A1B4EB48F00FE41AA /* car_model.hpp in Headers */, 670D049F1B0B4A970013A7AC /* nearest_edge_finder.hpp in Headers */, A120B34F1B4A7C0A002F3808 /* online_absent_fetcher.hpp in Headers */, @@ -359,6 +366,7 @@ 674F9BD61B0A580E00704FFA /* turns_generator.cpp in Sources */, 675344171A3F644F00A0A8C3 /* osrm_router.cpp in Sources */, 674F9BD21B0A580E00704FFA /* road_graph_router.cpp in Sources */, + A1876BC61BB19C4300C9C743 /* speed_camera.cpp in Sources */, 671F58BD1B874EC80032311E /* followed_polyline.cpp in Sources */, 670EE55D1B6001E7001E8064 /* routing_session.cpp in Sources */, A120B3451B4A7BE5002F3808 /* cross_mwm_road_graph.cpp in Sources */,