Instantly share code, notes, and snippets.
Created
November 10, 2022 17:22
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save alemantrix-settyl/5bff0bb621dd53b8309605dba9613f0c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2019-2022 HERE Europe B.V. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License") | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
* SPDX-License-Identifier: Apache-2.0 | |
* License-Filename: LICENSE | |
*/ | |
import 'dart:ui'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/services.dart'; | |
import 'package:here_sdk/core.dart'; | |
import 'package:here_sdk/core.errors.dart'; | |
import 'package:here_sdk/gestures.dart'; | |
import 'package:here_sdk/location.dart'; | |
import 'package:here_sdk/mapview.dart'; | |
import 'package:here_sdk/navigation.dart'; | |
import 'package:here_sdk/routing.dart' as HERE; | |
import 'package:here_sdk/routing.dart'; | |
import 'package:here_sdk/trafficawarenavigation.dart'; | |
import 'package:mobileapp/controllers/maps/liveMap.dart'; | |
import 'package:mobileapp/controllers/maps/smartRoute.dart'; | |
import 'HEREPositioningProvider.dart'; | |
import 'HEREPositioningSimulator.dart'; | |
import 'LanguageCodeConverter.dart'; | |
// Shows how to start and stop turn-by-turn navigation along a route. | |
class NavigationExample { | |
final HereMapController _hereMapController; | |
final SmartRouteController _smartRouteController = SmartRouteController(); | |
late VisualNavigator _visualNavigator; | |
late HEREPositioningSimulator _locationSimulationProvider; | |
late HEREPositioningProvider _herePositioningProvider; | |
late DynamicRoutingEngine _dynamicRoutingEngine; | |
MapMatchedLocation? _lastMapMatchedLocation; | |
int _previousManeuverIndex = -1; | |
final ValueChanged<String> _updateMessageState; | |
final ValueChanged<String> _updateSpeedState; | |
final ValueChanged<String> _updateRemainingDistance; | |
final ValueChanged<String> _updateStreetName; | |
final ValueChanged<String> _updateNextStreetName; | |
final ValueChanged<bool> _isCameraTrackingEnabled; | |
final ValueChanged<HERE.Maneuver> _updateManeuver; | |
final ValueChanged<String> _updateDeviation; | |
NavigationExample( | |
HereMapController hereMapController, | |
ValueChanged<String> updateMessageState, | |
ValueChanged<String> updateSpeedState, | |
ValueChanged<String> updateRemainingDistance, | |
ValueChanged<String> updateStreetName, | |
ValueChanged<String> updateNextStreetName, | |
ValueChanged<bool> isCameraTrackingEnabled, | |
ValueChanged<HERE.Maneuver> _updateManeuver, | |
ValueChanged<String> _updateDeviation) | |
: _hereMapController = hereMapController, | |
_updateSpeedState = updateSpeedState, | |
_updateRemainingDistance = updateRemainingDistance, | |
_updateStreetName = updateStreetName, | |
_updateNextStreetName = updateNextStreetName, | |
_isCameraTrackingEnabled = isCameraTrackingEnabled, | |
_updateManeuver = _updateManeuver, | |
_updateDeviation = _updateDeviation, | |
_updateMessageState = updateMessageState { | |
try { | |
_visualNavigator = VisualNavigator(); | |
} on InstantiationException { | |
throw Exception("Initialization of VisualNavigator failed."); | |
} | |
// This enables a navigation view including a rendered navigation arrow. | |
_visualNavigator.startRendering(_hereMapController); | |
// For easy testing, this location provider simulates location events along a route. | |
// You can use HERE positioning to feed real locations, see the "Positioning"-section in | |
// our Developer's Guide for an example. | |
_locationSimulationProvider = HEREPositioningSimulator(); | |
// Access the device's GPS sensor and other data. | |
_herePositioningProvider = HEREPositioningProvider(); | |
_herePositioningProvider.startLocating( | |
_visualNavigator, LocationAccuracy.navigation); | |
_createDynamicRoutingEngine(); | |
setupListeners(); | |
_hereMapController.gestures.tapListener = TapListener((Point2D touchPoint) { | |
_visualNavigator.cameraMode = CameraTrackingMode.disabled; | |
isCameraTrackingEnabled(false); | |
}); | |
_hereMapController.gestures.panListener = | |
PanListener((GestureState gx, Point2D pt1, Point2D pt2, double value) { | |
_visualNavigator.cameraMode = CameraTrackingMode.disabled; | |
isCameraTrackingEnabled(false); | |
}); | |
} | |
void _createDynamicRoutingEngine() { | |
var dynamicRoutingOptions = DynamicRoutingEngineOptions.withAllDefaults(); | |
// We want an update for each poll iteration, so we specify 0 difference. | |
dynamicRoutingOptions.minTimeDifference = Duration.zero; | |
dynamicRoutingOptions.minTimeDifferencePercentage = 0.0; | |
dynamicRoutingOptions.pollInterval = Duration(minutes: 5); | |
try { | |
// With the dynamic routing engine you can poll the HERE backend services to search for routes with less traffic. | |
// THis can happen during guidance - or you can periodically update a route that is shown in a route planner. | |
_dynamicRoutingEngine = DynamicRoutingEngine(dynamicRoutingOptions); | |
} on InstantiationException { | |
throw Exception("Initialization of DynamicRoutingEngine failed."); | |
} | |
} | |
Location? getLastKnownLocation() { | |
return _herePositioningProvider.getLastKnownLocation(); | |
} | |
void startNavigationSimulation(HERE.Route route) { | |
_prepareNavigation(route); | |
// Stop in case it was started before. | |
_herePositioningProvider.stop(); | |
// Simulates location events based on the given route. | |
// The navigator is set as listener to receive location updates. | |
_locationSimulationProvider.startLocating(route, _visualNavigator); | |
_startDynamicSearchForBetterRoutes(route); | |
_visualNavigator.startRendering(_hereMapController); // just an experiment | |
} | |
void startNavigation(HERE.Route route) { | |
_prepareNavigation(route); | |
// Stop in case it was started before. | |
_locationSimulationProvider.stop(); | |
// Access the device's GPS sensor and other data. | |
// The navigator is set as listener to receive location updates. | |
_herePositioningProvider.startLocating( | |
_visualNavigator, LocationAccuracy.navigation); | |
_startDynamicSearchForBetterRoutes(route); | |
_visualNavigator.startRendering(_hereMapController); // just an experiment | |
} | |
void _startDynamicSearchForBetterRoutes(HERE.Route route) { | |
try { | |
_dynamicRoutingEngine.start( | |
route, | |
// Notifies on traffic-optimized routes that are considered better than the current route. | |
DynamicRoutingListener((Route newRoute, int etaDifferenceInSeconds, | |
int distanceDifferenceInMeters) { | |
_updateMessageState("DynamicRoutingEngine: Calculated a new route"); | |
print( | |
"DynamicRoutingEngine: etaDifferenceInSeconds: $etaDifferenceInSeconds."); | |
print( | |
"DynamicRoutingEngine: distanceDifferenceInMeters: $distanceDifferenceInMeters."); | |
// An implementation can decide to switch to the new route: | |
// _visualNavigator.route = newRoute; | |
}, (RoutingError routingError) { | |
final error = routingError.toString(); | |
_updateMessageState( | |
"Error while dynamically searching for a better route: $error"); | |
})); | |
} on DynamicRoutingEngineStartException { | |
throw Exception( | |
"Start of DynamicRoutingEngine failed. Is the RouteHandle missing?"); | |
} | |
} | |
void _prepareNavigation(HERE.Route route) { | |
setupSpeedWarnings(); | |
setupVoiceTextMessages(); | |
// Set the route to follow. | |
_visualNavigator.route = route; | |
} | |
void setTracking(bool isTracking) { | |
if (isTracking) { | |
_visualNavigator.cameraMode = CameraTrackingMode.enabled; | |
} else { | |
_visualNavigator.cameraMode = CameraTrackingMode.disabled; | |
} | |
} | |
void stopNavigation() { | |
// Stop in case it was started before. | |
_locationSimulationProvider.stop(); | |
_dynamicRoutingEngine.stop(); | |
startTracking(); | |
_updateMessageState("Tracking device's location."); | |
} | |
void detach() { | |
// It is recommended to stop rendering before leaving the app. | |
// This also removes the current location marker. | |
_visualNavigator.stopRendering(); | |
// It is recommended to stop the LocationEngine before leaving the app. | |
_herePositioningProvider.stop(); | |
} | |
// Starts tracking the device's location using HERE Positioning. | |
void startTracking() { | |
// Leaves guidance (if it was running) and enables tracking mode. The camera may optionally follow, see toggleTracking(). | |
_visualNavigator.route = null; | |
_herePositioningProvider.startLocating( | |
_visualNavigator, LocationAccuracy.navigation); | |
} | |
String _getRoadName(Maneuver maneuver) { | |
RoadTexts currentRoadTexts = maneuver.roadTexts; | |
RoadTexts nextRoadTexts = maneuver.nextRoadTexts; | |
String? currentRoadName = currentRoadTexts.names.getDefaultValue(); | |
String? currentRoadNumber = currentRoadTexts.numbers.getDefaultValue(); | |
String? nextRoadName = nextRoadTexts.names.getDefaultValue(); | |
String? nextRoadNumber = nextRoadTexts.numbers.getDefaultValue(); | |
String? roadName = nextRoadName == null ? nextRoadNumber : nextRoadName; | |
// On highways, we want to show the highway number instead of a possible road name, | |
// while for inner city and urban areas road names are preferred over road numbers. | |
if (maneuver.nextRoadType == RoadType.highway) { | |
roadName = nextRoadNumber == null ? nextRoadName : nextRoadNumber; | |
} | |
if (maneuver.action == ManeuverAction.arrive) { | |
// We are approaching the destination, so there's no next road. | |
roadName = currentRoadName == null ? currentRoadNumber : currentRoadName; | |
} | |
// Happens only in rare cases, when also the fallback above is null. | |
roadName ??= 'unnamed road'; | |
return roadName; | |
} | |
void setMySpeed(String speed) { | |
_smartRouteController.setSpeed(speed); | |
} | |
void setupListeners() { | |
// Notifies on the progress along the route including maneuver instructions. | |
// These maneuver instructions can be used to compose a visual representation of the next maneuver actions. | |
_visualNavigator.routeProgressListener = | |
RouteProgressListener((RouteProgress routeProgress) { | |
// Handle results from onRouteProgressUpdated(): | |
List<SectionProgress> sectionProgressList = routeProgress.sectionProgress; | |
// sectionProgressList is guaranteed to be non-empty. | |
SectionProgress lastSectionProgress = | |
sectionProgressList.elementAt(sectionProgressList.length - 1); | |
print('Distance to destination in meters: ' + | |
lastSectionProgress.remainingDistanceInMeters.toString()); | |
_updateRemainingDistance( | |
lastSectionProgress.remainingDistanceInMeters.toString()); | |
_smartRouteController.setRemainingDistance( | |
lastSectionProgress.remainingDistanceInMeters.toString()); | |
print('Traffic delay ahead in seconds: ' + | |
lastSectionProgress.trafficDelay.inSeconds.toString()); | |
// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any. | |
List<ManeuverProgress> nextManeuverList = routeProgress.maneuverProgress; | |
if (nextManeuverList.isEmpty) { | |
print('No next maneuver available.'); | |
return; | |
} | |
ManeuverProgress nextManeuverProgress = nextManeuverList.first; | |
int nextManeuverIndex = nextManeuverProgress.maneuverIndex; | |
Maneuver? nextManeuver = _visualNavigator.getManeuver(nextManeuverIndex); | |
if (nextManeuver == null) { | |
// Should never happen as we retrieved the next maneuver progress above. | |
return; | |
} | |
// print("_______________________________________"); | |
// print('road text ${nextManeuver.roadTexts.names.getDefaultValue()}'); | |
var roadText = nextManeuver.roadTexts.names.getDefaultValue(); | |
_smartRouteController | |
.setCurrentStreetName(roadText != null ? roadText : ""); | |
// print("_______________________________________"); | |
_updateStreetName(roadText == null ? "" : roadText); | |
// print('########################################'); | |
var nextStreet = nextManeuver.nextRoadTexts.names.getDefaultValue(); | |
_smartRouteController | |
.setNextStreetName(nextStreet != null ? nextStreet : ""); | |
// print( | |
// 'next road text ${nextManeuver.nextRoadTexts.names.getDefaultValue()}'); | |
// print("########################################"); | |
_updateNextStreetName(nextStreet == null ? "" : nextStreet); | |
ManeuverAction action = nextManeuver.action; | |
String roadName = _getRoadName(nextManeuver); | |
_updateManeuver(nextManeuver); | |
String logMessage = describeEnum(action) + | |
' on ' + | |
roadName + | |
' in ' + | |
nextManeuverProgress.remainingDistanceInMeters.toString() + | |
' meters.'; | |
if (_previousManeuverIndex != nextManeuverIndex) { | |
_updateMessageState('New maneuver: $logMessage'); | |
} else { | |
// A maneuver update contains a different distance to reach the next maneuver. | |
_updateMessageState("Maneuver update: $logMessage"); | |
} | |
_previousManeuverIndex = nextManeuverIndex; | |
if (_lastMapMatchedLocation != null) { | |
// Update the route based on the current location of the driver. | |
// We periodically want to search for better traffic-optimized routes. | |
_dynamicRoutingEngine.updateCurrentLocation( | |
_lastMapMatchedLocation!, routeProgress.sectionIndex); | |
} | |
}); | |
// Notifies on the current map-matched location and other useful information while driving or walking. | |
// The map-matched location is used to update the map view. | |
_visualNavigator.navigableLocationListener = | |
NavigableLocationListener((NavigableLocation currentNavigableLocation) { | |
// Handle results from onNavigableLocationUpdated(): | |
MapMatchedLocation? mapMatchedLocation = | |
currentNavigableLocation.mapMatchedLocation; | |
if (mapMatchedLocation == null) { | |
print("This new location could not be map-matched. Are you off-road?"); | |
return; | |
} | |
_lastMapMatchedLocation = mapMatchedLocation; | |
var speed = | |
currentNavigableLocation.originalLocation.speedInMetersPerSecond; | |
var accuracy = currentNavigableLocation | |
.originalLocation.speedAccuracyInMetersPerSecond; | |
_updateSpeedState(speed.toString()); | |
// print('yyyyyyyyyyyyyyyyyyyyyy'); | |
// setMySpeed(speed.toString()); | |
print("Driving speed (m/s): $speed plus/minus an accuracy of: $accuracy"); | |
}); | |
// Notifies when the destination of the route is reached. | |
_visualNavigator.destinationReachedListener = | |
DestinationReachedListener(() { | |
// Handle results from onDestinationReached(). | |
_updateMessageState( | |
"Destination reached. Stopping turn-by-turn navigation."); | |
stopNavigation(); | |
}); | |
// Notifies when a waypoint on the route is reached or missed | |
_visualNavigator.milestoneStatusListener = MilestoneStatusListener( | |
(Milestone milestone, MilestoneStatus milestoneStatus) { | |
// Handle results from onMilestoneStatusUpdated(). | |
if (milestone.waypointIndex != null && | |
milestoneStatus == MilestoneStatus.reached) { | |
print("A user-defined waypoint was reached, index of waypoint: " + | |
milestone.waypointIndex.toString()); | |
print("Original coordinates: " + | |
milestone.originalCoordinates.toString()); | |
} else if (milestone.waypointIndex != null && | |
milestoneStatus == MilestoneStatus.missed) { | |
print("A user-defined waypoint was missed, index of waypoint: " + | |
milestone.waypointIndex.toString()); | |
print("Original coordinates: " + | |
milestone.originalCoordinates.toString()); | |
} else if (milestone.waypointIndex == null && | |
milestoneStatus == MilestoneStatus.reached) { | |
// For example, when transport mode changes due to a ferry a system-defined waypoint may have been added. | |
print("A system-defined waypoint was reached at: " + | |
milestone.mapMatchedCoordinates.toString()); | |
} else if (milestone.waypointIndex == null && | |
milestoneStatus == MilestoneStatus.reached) { | |
// For example, when transport mode changes due to a ferry a system-defined waypoint may have been added. | |
print("A system-defined waypoint was missed at: " + | |
milestone.mapMatchedCoordinates.toString()); | |
} | |
}); | |
// Notifies on the current speed limit valid on the current road. | |
_visualNavigator.speedLimitListener = | |
SpeedLimitListener((SpeedLimit speedLimit) { | |
// Handle results from onSpeedLimitUpdated(). | |
double? currentSpeedLimit = _getCurrentSpeedLimit(speedLimit); | |
if (currentSpeedLimit == null) { | |
print("Warning: Speed limits unknown, data could not be retrieved."); | |
} else if (currentSpeedLimit == 0) { | |
print( | |
"No speed limits on this road! Drive as fast as you feel safe ..."); | |
} else { | |
_smartRouteController | |
.setCurrentSpeedLimit(currentSpeedLimit.toString()); | |
print("Current speed limit (m/s): $currentSpeedLimit"); | |
} | |
}); | |
// Notifies when the current speed limit is exceeded. | |
_visualNavigator.speedWarningListener = | |
SpeedWarningListener((SpeedWarningStatus speedWarningStatus) { | |
// Handle results from onSpeedWarningStatusChanged(). | |
if (speedWarningStatus == SpeedWarningStatus.speedLimitExceeded) { | |
// Driver is faster than current speed limit (plus an optional offset, see setupSpeedWarnings()). | |
// Play a click sound to indicate this to the driver. | |
// As Flutter itself does not provide support for sounds, | |
// alternatively use a 3rd party plugin to play an alert sound of your choice. | |
// Note that this may not include temporary special speed limits, see SpeedLimitListener. | |
SystemSound.play(SystemSoundType.click); | |
print("Speed limit exceeded."); | |
} | |
if (speedWarningStatus == SpeedWarningStatus.speedLimitRestored) { | |
print( | |
"Driver is again slower than current speed limit (plus an optional offset.)"); | |
} | |
}); | |
// Notifies on a possible deviation from the route. | |
// When deviation is too large, an app may decide to recalculate the route from current location to destination. | |
_visualNavigator.routeDeviationListener = | |
RouteDeviationListener((RouteDeviation routeDeviation) { | |
// Handle results from onRouteDeviation(). | |
HERE.Route? route = _visualNavigator.route; | |
if (route == null) { | |
// May happen in rare cases when route was set to null inbetween. | |
return; | |
} | |
// Get current geographic coordinates. | |
MapMatchedLocation? currentMapMatchedLocation = | |
routeDeviation.currentLocation.mapMatchedLocation; | |
GeoCoordinates currentGeoCoordinates = currentMapMatchedLocation == null | |
? routeDeviation.currentLocation.originalLocation.coordinates | |
: currentMapMatchedLocation.coordinates; | |
// Get last geographic coordinates on route. | |
GeoCoordinates lastGeoCoordinatesOnRoute; | |
if (routeDeviation.lastLocationOnRoute != null) { | |
MapMatchedLocation? lastMapMatchedLocationOnRoute = | |
routeDeviation.lastLocationOnRoute!.mapMatchedLocation; | |
lastGeoCoordinatesOnRoute = lastMapMatchedLocationOnRoute == null | |
? routeDeviation.lastLocationOnRoute!.originalLocation.coordinates | |
: lastMapMatchedLocationOnRoute.coordinates; | |
} else { | |
print( | |
"User was never following the route. So, we take the start of the route instead."); | |
lastGeoCoordinatesOnRoute = | |
route.sections.first.departurePlace.originalCoordinates!; | |
} | |
int distanceInMeters = | |
currentGeoCoordinates.distanceTo(lastGeoCoordinatesOnRoute) as int; | |
_updateDeviation(distanceInMeters.toString()); | |
_smartRouteController | |
.setRouteDeviationInMeters(distanceInMeters.toString()); | |
print("RouteDeviation in meters is " + distanceInMeters.toString()); | |
}); | |
// Notifies on voice maneuver messages. | |
_visualNavigator.maneuverNotificationListener = | |
ManeuverNotificationListener((String voiceText) { | |
// Handle results lambda_onManeuverNotification(). | |
// Flutter itself does not provide a text-to-speech engine. Use one of the available TTS plugins to speak | |
// the voiceText message. | |
_smartRouteController.setVoiceText(voiceText); | |
print("Voice guidance text: $voiceText"); | |
}); | |
// Notifies on the attributes of the current road including usage and physical characteristics. | |
_visualNavigator.roadAttributesListener = | |
RoadAttributesListener((RoadAttributes roadAttributes) { | |
// Handle results from onRoadAttributesUpdated(). | |
// This is called whenever any road attribute has changed. | |
// If all attributes are unchanged, no new event is fired. | |
// Note that a road can have more than one attribute at the same time. | |
print("Received road attributes update."); | |
if (roadAttributes.isBridge) { | |
// Identifies a structure that allows a road, railway, or walkway to pass over another road, railway, | |
// waterway, or valley serving map display and route guidance functionalities. | |
print("Road attributes: This is a bridge."); | |
} | |
if (roadAttributes.isControlledAccess) { | |
// Controlled access roads are roads with limited entrances and exits that allow uninterrupted | |
// high-speed traffic flow. | |
print("Road attributes: This is a controlled access road."); | |
} | |
if (roadAttributes.isDirtRoad) { | |
// Indicates whether the navigable segment is paved. | |
print("Road attributes: This is a dirt road."); | |
} | |
if (roadAttributes.isDividedRoad) { | |
// Indicates if there is a physical structure or painted road marking intended to legally prohibit | |
// left turns in right-side driving countries, right turns in left-side driving countries, | |
// and U-turns at divided intersections or in the middle of divided segments. | |
print("Road attributes: This is a divided road."); | |
} | |
if (roadAttributes.isNoThrough) { | |
// Identifies a no through road. | |
print("Road attributes: This is a no through road."); | |
} | |
if (roadAttributes.isPrivate) { | |
// Private identifies roads that are not maintained by an organization responsible for maintenance of | |
// public roads. | |
print("Road attributes: This is a private road."); | |
} | |
if (roadAttributes.isRamp) { | |
// Range is a ramp: connects roads that do not intersect at grade. | |
print('Road attributes: This is a ramp.'); | |
} | |
if (roadAttributes.isRightDrivingSide) { | |
// Indicates if vehicles have to drive on the right-hand side of the road or the left-hand side. | |
// For example, in New York it is always true and in London always false as the United Kingdom is | |
// a left-hand driving country. | |
print("Road attributes: isRightDrivingSide = " + | |
roadAttributes.isRightDrivingSide.toString()); | |
} | |
if (roadAttributes.isRoundabout) { | |
// Indicates the presence of a roundabout. | |
print("Road attributes: This is a roundabout."); | |
} | |
if (roadAttributes.isTollway) { | |
// Identifies a road for which a fee must be paid to use the road. | |
print("Road attributes change: This is a road with toll costs."); | |
} | |
if (roadAttributes.isTunnel) { | |
// Identifies an enclosed (on all sides) passageway through or under an obstruction. | |
print("Road attributes: This is a tunnel."); | |
} | |
}); | |
// Notifies which lane(s) lead to the next (next) maneuvers. | |
_visualNavigator.maneuverViewLaneAssistanceListener = | |
ManeuverViewLaneAssistanceListener( | |
(ManeuverViewLaneAssistance laneAssistance) { | |
// Handle events from onLaneAssistanceUpdated(). | |
// This lane list is guaranteed to be non-empty. | |
List<Lane> lanes = laneAssistance.lanesForNextManeuver; | |
print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'); | |
print(lanes.length); | |
for (var l in lanes) { | |
print(l.directionCategory); | |
} | |
logLaneRecommendations(lanes); | |
List<Lane> nextLanes = laneAssistance.lanesForNextNextManeuver; | |
if (nextLanes.isNotEmpty) { | |
print("Attention, the next next maneuver is very close."); | |
print("Please take the following lane(s) after the next maneuver: "); | |
print( | |
'@@@@@@@@@@@@@@@@@@@@@@@@@@ NON EMPTY @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'); | |
print(lanes.length); | |
logLaneRecommendations(nextLanes); | |
} | |
}); | |
// Notifies which lane(s) allow to follow the route. | |
_visualNavigator.junctionViewLaneAssistanceListener = | |
JunctionViewLaneAssistanceListener( | |
(JunctionViewLaneAssistance junctionViewLaneAssistance) { | |
List<Lane> lanes = junctionViewLaneAssistance.lanesForNextJunction; | |
if (lanes.isEmpty) { | |
_updateMessageState("You have passed the complex junction."); | |
} else { | |
_updateMessageState("Attention, a complex junction is ahead."); | |
logLaneRecommendations(lanes); | |
} | |
}); | |
// Notifies truck drivers on road restrictions ahead. | |
// For example, there can be a bridge ahead not high enough to pass a big truck | |
// or there can be a road ahead where the weight of the truck is beyond it's permissible weight. | |
// This event notifies on truck restrictions in general, | |
// so it will also deliver events, when the transport type was set to a non-truck transport type. | |
// The given restrictions are based on the HERE database of the road network ahead. | |
_visualNavigator.truckRestrictionsWarningListener = | |
TruckRestrictionsWarningListener((List<TruckRestrictionWarning> list) { | |
// The list is guaranteed to be non-empty. | |
for (TruckRestrictionWarning truckRestrictionWarning in list) { | |
if (truckRestrictionWarning.distanceType == DistanceType.ahead) { | |
print( | |
"TruckRestrictionWarning ahead in: ${truckRestrictionWarning.distanceInMeters} meters."); | |
} else if (truckRestrictionWarning.distanceType == | |
DistanceType.passed) { | |
print("A restriction just passed."); | |
} | |
// One of the following restrictions applies ahead, if more restrictions apply at the same time, | |
// they are part of another TruckRestrictionWarning element contained in the list. | |
if (truckRestrictionWarning.weightRestriction != null) { | |
// For now only one weight type (= truck) is exposed. | |
WeightRestrictionType type = | |
truckRestrictionWarning.weightRestriction!.type; | |
int value = | |
truckRestrictionWarning.weightRestriction!.valueInKilograms; | |
print("TruckRestriction for weight (kg): ${type.toString()}: $value"); | |
} | |
if (truckRestrictionWarning.dimensionRestriction != null) { | |
// Can be either a length, width or height restriction of the truck. For example, a height | |
// restriction can apply for a tunnel. Other possible restrictions are delivered in | |
// separate TruckRestrictionWarning objects contained in the list, if any. | |
DimensionRestrictionType type = | |
truckRestrictionWarning.dimensionRestriction!.type; | |
int value = | |
truckRestrictionWarning.dimensionRestriction!.valueInCentimeters; | |
print("TruckRestriction for dimension: ${type.toString()}: $value"); | |
} | |
} | |
}); | |
// Notifies whenever any textual attribute of the current road changes, i.e., the current road texts differ | |
// from the previous one. This can be useful during tracking mode, when no maneuver information is provided. | |
_visualNavigator.roadTextsListener = | |
RoadTextsListener((RoadTexts roadTexts) { | |
// See _getRoadName() how to get the current road name from the provided RoadTexts. | |
}); | |
} | |
void logLaneRecommendations(List<Lane> lanes) { | |
// The lane at index 0 is the leftmost lane adjacent to the middle of the road. | |
// The lane at the last index is the rightmost lane. | |
int laneNumber = 0; | |
for (Lane lane in lanes) { | |
// This state is only possible if laneAssistance.lanesForNextNextManeuver is not empty. | |
// For example, when two lanes go left, this lanes leads only to the next maneuver, | |
// but not to the maneuver after the next maneuver, while the highly recommended lane also leads | |
// to this next next maneuver. | |
if (lane.recommendationState == LaneRecommendationState.recommended) { | |
print( | |
"Lane $laneNumber leads to next maneuver, but not to the next next maneuver."); | |
} | |
// If laneAssistance.lanesForNextNextManeuver is not empty, this lane leads also to the | |
// maneuver after the next maneuver. | |
if (lane.recommendationState == | |
LaneRecommendationState.highlyRecommended) { | |
print( | |
"Lane $laneNumber leads to next maneuver and eventually to the next next maneuver."); | |
} | |
if (lane.recommendationState == LaneRecommendationState.notRecommended) { | |
print("Do not take lane $laneNumber to follow the route."); | |
} | |
laneNumber++; | |
} | |
} | |
void setCameraTrackingModeTapGestureHandler(CameraTrackingMode _cmt) { | |
_visualNavigator.cameraMode = _cmt; | |
} | |
void setupSpeedWarnings() { | |
SpeedLimitOffset speedLimitOffset = SpeedLimitOffset.withDefaults(); | |
speedLimitOffset.lowSpeedOffsetInMetersPerSecond = 2; | |
speedLimitOffset.highSpeedOffsetInMetersPerSecond = 4; | |
speedLimitOffset.highSpeedBoundaryInMetersPerSecond = 25; | |
_visualNavigator.speedWarningOptions = | |
SpeedWarningOptions(speedLimitOffset); | |
} | |
void setupVoiceTextMessages() { | |
LanguageCode ttsLanguageCode = getLanguageCodeForDevice( | |
VisualNavigator.getAvailableLanguagesForManeuverNotifications()); | |
_visualNavigator.maneuverNotificationOptions = | |
ManeuverNotificationOptions(ttsLanguageCode, UnitSystem.metric); | |
print("LanguageCode for maneuver notifications: $ttsLanguageCode."); | |
} | |
LanguageCode getLanguageCodeForDevice( | |
List<LanguageCode> supportedVoiceSkins) { | |
final Locale localeForCurrenDevice = window.locales.first; | |
// Determine supported voice skins from HERE SDK. | |
LanguageCode languageCodeForCurrenDevice = | |
LanguageCodeConverter.getLanguageCode(localeForCurrenDevice); | |
if (!supportedVoiceSkins.contains(languageCodeForCurrenDevice)) { | |
print( | |
"No voice skins available for $languageCodeForCurrenDevice, falling back to enUs."); | |
languageCodeForCurrenDevice = LanguageCode.enUs; | |
} | |
return languageCodeForCurrenDevice; | |
} | |
double? _getCurrentSpeedLimit(SpeedLimit speedLimit) { | |
// Note that all speedLimit properties can be null if no data is available. | |
// The regular speed limit if available. In case of unbounded speed limit, the value is zero. | |
print("speedLimitInMetersPerSecond: " + | |
speedLimit.speedLimitInMetersPerSecond.toString()); | |
// A conditional school zone speed limit as indicated on the local road signs. | |
print("schoolZoneSpeedLimitInMetersPerSecond: " + | |
speedLimit.schoolZoneSpeedLimitInMetersPerSecond.toString()); | |
// A conditional time-dependent speed limit as indicated on the local road signs. | |
// It is in effect considering the current local time provided by the device's clock. | |
print("timeDependentSpeedLimitInMetersPerSecond: " + | |
speedLimit.timeDependentSpeedLimitInMetersPerSecond.toString()); | |
// A conditional non-legal speed limit that recommends a lower speed, | |
// for example, due to bad road conditions. | |
print("advisorySpeedLimitInMetersPerSecond: " + | |
speedLimit.advisorySpeedLimitInMetersPerSecond.toString()); | |
// A weather-dependent speed limit as indicated on the local road signs. | |
// The HERE SDK cannot detect the current weather condition, so a driver must decide | |
// based on the situation if this speed limit applies. | |
print("fogSpeedLimitInMetersPerSecond: " + | |
speedLimit.fogSpeedLimitInMetersPerSecond.toString()); | |
print("rainSpeedLimitInMetersPerSecond: " + | |
speedLimit.rainSpeedLimitInMetersPerSecond.toString()); | |
print("snowSpeedLimitInMetersPerSecond: " + | |
speedLimit.snowSpeedLimitInMetersPerSecond.toString()); | |
// For convenience, this returns the effective (lowest) speed limit between | |
// - speedLimitInMetersPerSecond | |
// - schoolZoneSpeedLimitInMetersPerSecond | |
// - timeDependentSpeedLimitInMetersPerSecond | |
return speedLimit.effectiveSpeedLimitInMetersPerSecond(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment